diff --git a/.gitattributes b/.gitattributes index 433a2de9a..3ea365552 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,14 @@ .gitattributes export-ignore .github/ export-ignore .gitignore export-ignore +CLAUDE.md export-ignore ncs.* export-ignore phpstan*.neon export-ignore src/**/*.latte export-ignore tests/ export-ignore +tools/latte-convert/ export-ignore *.php* diff=php *.sh text eol=lf +tools/latte-convert/tests/fixtures/*.latte text eol=lf +tools/latte-convert/tests/fixtures/*.phtml text eol=lf diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..8defaa420 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,573 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Tracy is a debugging and error visualization library for PHP (8.2-8.5). It provides beautiful error pages (BlueScreen), an interactive debug toolbar (Bar), advanced variable dumping, and production-ready error logging. + +**Key features:** +- BlueScreen: Beautiful error/exception visualization with stack traces +- Tracy Bar: Floating debug toolbar with extensible panel system +- Dumper: Advanced variable dumping with multiple output formats +- Logger: Error logging with email notifications +- Production/development mode auto-detection +- AI agent support: automated browsers (navigator.webdriver) get markdown output via console.error/console.log + +## Essential Commands + +### Testing +```bash +# Run all tests - ALWAYS use php-cgi (html tests only run with php-cgi) +vendor/bin/tester tests -p php-cgi -s + +# Run single test file +vendor/bin/tester tests/Tracy/Debugger.timer().phpt -p php-cgi -s + +# Run tests in specific directory +vendor/bin/tester tests/Dumper/ -s +``` + +### Code Quality +```bash +# Run PHPStan static analysis (level 7) +composer run phpstan + +# Lint JavaScript assets +npm run lint +npm run lint:fix +``` + +## Core Architecture + +### Main Components (Facade Pattern) + +**Tracy\Debugger** (src/Tracy/Debugger/Debugger.php) - Central facade +- `enable()` - Initialize Tracy +- `dump()` / `bdump()` - Variable dumping +- `log()` - Error logging +- `timer()` - Performance profiling +- Global functions available: `dump()`, `dumpe()`, `bdump()` + +**Strategy Pattern:** +- `DevelopmentStrategy` - Shows full debug info, Tracy Bar +- `ProductionStrategy` - Logs errors, shows user-friendly messages +- `DeferredContent` - Manages deferred rendering for AJAX/redirect requests +- Auto-detection: localhost = development, otherwise production +- Agent detection: `Helpers::isAgent()` checks `$_COOKIE['tracy-webdriver']` — set by `bar.js` when `navigator.webdriver` is `true` (Chrome MCP, Playwright, Puppeteer, Selenium) + +### Core Components + +**BlueScreen** (src/Tracy/BlueScreen/) +- Error/exception page rendering with stack traces +- Multiple output formats: HTML, CLI, markdown (for AI agents via console) +- `renderAgent()` returns markdown string, output via `console.error()` in page ` + +``` + +### Session Storage + +**Use native PHP session:** +```php +session_start(); +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); +``` + +**Complex session initialization:** +```php +Debugger::setSessionStorage(new Tracy\NativeSession); +Debugger::enable(); + +// Custom session initialization +session_start(); + +Debugger::dispatch(); // Inform Tracy session is ready +``` + +### nginx Configuration + +If Tracy doesn't work on nginx, fix the `try_files` directive: + +```nginx +# Wrong +try_files $uri $uri/ /index.php; + +# Correct +try_files $uri $uri/ /index.php$is_args$args; +``` + +### IDE Integration + +Tracy can open files directly in your editor when clicking file names in error pages. + +**Editor integration scripts:** +- Windows: `tools/open-in-editor/windows/` +- Linux: `tools/open-in-editor/linux/` +- macOS: Use built-in URL schemes + +**Built-in editor URLs (macOS):** +```php +// PhpStorm +Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line'; + +// VS Code +Tracy\Debugger::$editor = 'vscode://file/%file:%line'; + +// TextMate +Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line'; +``` + +**Editor path mapping for remote/Docker:** +```php +Debugger::$editorMapping = [ + '/var/www/html' => 'W:\\Projects\\myapp', // Docker to Windows + '/app' => '/Users/dev/projects/myapp', // Container to macOS +]; +``` diff --git a/composer.json b/composer.json index 1c213cbfb..a772617ef 100644 --- a/composer.json +++ b/composer.json @@ -44,11 +44,12 @@ "minimum-stability": "dev", "scripts": { "phpstan": "phpstan analyse", - "tester": "tester tests" + "tester": "tester tests", + "compile-templates": "@php tools/latte-convert/compile.php src/Tracy" }, "extra": { "branch-alias": { - "dev-master": "2.12-dev" + "dev-master": "3.0-dev" } }, "config": { diff --git a/readme.md b/readme.md index 933419c07..2298b2d43 100644 --- a/readme.md +++ b/readme.md @@ -457,5 +457,6 @@ This is a list of unofficial integrations to other frameworks and CMS: - Symfony framework: [kutny/tracy-bundle](https://github.com/kutny/tracy-bundle), [VasekPurchart/Tracy-Blue-Screen-Bundle](https://github.com/VasekPurchart/Tracy-Blue-Screen-Bundle) - [Wordpress](https://github.com/ktstudio/WP-Tracy) - [Joomla! CMS](https://n3t.bitbucket.io/extension/n3t-debug/) +- [Yii3 framework](https://github.com/beastbytes/yii-tracy) ... feel free to be famous, create an integration for your favourite platform! diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php index 1dfeb7965..f56227b85 100644 --- a/src/Bridges/Nette/TracyExtension.php +++ b/src/Bridges/Nette/TracyExtension.php @@ -129,14 +129,14 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void } $initialize->addBody($builder->formatPhp('if ($logger instanceof Tracy\Logger) $logger->mailer = ?;', [ - [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], + [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], // TODO: nette/di must be able to create closures ])); } if ($this->debugMode) { foreach ($config->bar as $item) { if (is_string($item) && str_starts_with($item, '@')) { - $item = new Statement(['@' . $builder::THIS_CONTAINER, 'getService'], [substr($item, 1)]); + $item = new Statement(['@' . $builder::ThisContainer, 'getService'], [substr($item, 1)]); } elseif (is_string($item)) { $item = new Statement($item); } diff --git a/src/Tracy/Bar/assets/bar.css b/src/Tracy/Bar/assets/bar.css index 5f71d82b8..c31c8e09a 100644 --- a/src/Tracy/Bar/assets/bar.css +++ b/src/Tracy/Bar/assets/bar.css @@ -2,40 +2,53 @@ * This file is part of the Tracy (https://tracy.nette.org) */ -/* common styles */ -#tracy-debug { +@layer components { + +/* shadow DOM host */ +:host { --tracy-space: 10px; - display: none; direction: ltr; + display: none; } -body#tracy-debug { /* in popup window */ +body.tracy-debug { /* in popup window */ display: block; } -#tracy-debug:not(body) { - position: absolute; - left: 0; - top: 0; -} +/* popup window overrides (higher specificity to beat bluescreen bare selectors) */ +body.tracy-debug a { color: #125EAE; text-decoration: none; padding: 0; margin: 0; } +body.tracy-debug a:hover, +body.tracy-debug a:focus { background-color: #125EAE; color: white; } +body.tracy-debug h1 { font: normal normal 23px/1.4 Tahoma, sans-serif; line-height: 1; color: #575753; margin: 0; } +body.tracy-debug h2, +body.tracy-debug h3 { font-size: inherit; font-weight: bold; } +body.tracy-debug table { background: #FDF5CE; width: 100%; } +body.tracy-debug td, +body.tracy-debug th { border: 1px solid #E6DFBF; padding: 2px 5px; vertical-align: top; text-align: left; } +body.tracy-debug th { background: #F4F3F1; color: #655E5E; font-size: 90%; font-weight: bold; } +body.tracy-debug pre, +body.tracy-debug code { font: 9pt/1.5 Consolas, monospace; } +body.tracy-debug svg { display: inline; } -#tracy-debug a { +/* common styles */ +a { color: #125EAE; text-decoration: none; } -#tracy-debug a:hover, -#tracy-debug a:focus { +a:hover, +a:focus { background-color: #125EAE; color: white; } -#tracy-debug h2, -#tracy-debug h3 { +h2, +h3 { + font-size: inherit; font-weight: bold; } -#tracy-debug :where(:is( +:where(:is( h1, h2, h3, h4, h5, h6, p, ol, ul, dl, @@ -47,44 +60,44 @@ body#tracy-debug { /* in popup window */ margin-top: var(--tracy-space); } -#tracy-debug table { +table { background: #FDF5CE; width: 100%; } -#tracy-debug tr:nth-child(2n) td { +tr:nth-child(2n) td { background: rgba(0, 0, 0, 0.02); } -#tracy-debug td, -#tracy-debug th { +td, +th { border: 1px solid #E6DFBF; padding: 2px 5px; vertical-align: top; text-align: left; } -#tracy-debug th { +th { background: #F4F3F1; color: #655E5E; font-size: 90%; font-weight: bold; } -#tracy-debug pre, -#tracy-debug code { +pre, +code { font: 9pt/1.5 Consolas, monospace; } -#tracy-debug table .tracy-right { +table .tracy-right { text-align: right; } -#tracy-debug svg { +svg { display: inline; } -#tracy-debug .tracy-dump { +.tracy-dump { margin: 0; padding: 2px 5px; } @@ -182,7 +195,7 @@ body#tracy-debug { /* in popup window */ /* panels */ -#tracy-debug .tracy-panel { +.tracy-panel { display: none; font: normal normal 12px/1.5 sans-serif; background: white; @@ -190,11 +203,11 @@ body#tracy-debug { /* in popup window */ text-align: left; } -body#tracy-debug .tracy-panel { /* in popup window */ +body.tracy-debug .tracy-panel { /* in popup window */ display: block; } -#tracy-debug h1 { +h1 { font: normal normal 23px/1.4 Tahoma, sans-serif; line-height: 1; color: #575753; @@ -202,29 +215,29 @@ body#tracy-debug .tracy-panel { /* in popup window */ word-wrap: break-word; } -#tracy-debug .tracy-inner { +.tracy-inner { overflow: auto; flex: 1; } -#tracy-debug .tracy-panel .tracy-icons { +.tracy-panel .tracy-icons { display: none; } -#tracy-debug .tracy-panel-ajax h1::after, -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-ajax h1::after, +.tracy-panel-redirect h1::after { content: 'ajax'; float: right; font-size: 65%; margin: 0 .3em; } -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-redirect h1::after { content: 'redirect'; } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float { +.tracy-mode-peek, +.tracy-mode-float { position: fixed; flex-direction: column; padding: var(--tracy-space); @@ -235,24 +248,24 @@ body#tracy-debug .tracy-panel { /* in popup window */ border: 1px solid rgba(0, 0, 0, 0.1); } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { +.tracy-mode-peek, +.tracy-mode-float:not(.tracy-panel-resized) { max-width: 700px; max-height: 500px; } @media (max-height: 555px) { - #tracy-debug .tracy-mode-peek, - #tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { + .tracy-mode-peek, + .tracy-mode-float:not(.tracy-panel-resized) { max-height: 100vh; } } -#tracy-debug .tracy-mode-peek h1 { +.tracy-mode-peek h1 { cursor: move; } -#tracy-debug .tracy-mode-float { +.tracy-mode-float { display: flex; opacity: .95; transition: opacity 0.2s; @@ -261,18 +274,18 @@ body#tracy-debug .tracy-panel { /* in popup window */ resize: both; } -#tracy-debug .tracy-focused { +.tracy-focused { display: flex; opacity: 1; transition: opacity 0.1s; } -#tracy-debug .tracy-mode-float h1 { +.tracy-mode-float h1 { cursor: move; padding-right: 25px; } -#tracy-debug .tracy-mode-float .tracy-icons { +.tracy-mode-float .tracy-icons { display: block; position: absolute; top: 0; @@ -280,25 +293,27 @@ body#tracy-debug .tracy-panel { /* in popup window */ font-size: 18px; } -#tracy-debug .tracy-mode-window { +.tracy-mode-window { padding: var(--tracy-space); } -#tracy-debug .tracy-icons a { +.tracy-icons a { color: #575753; } -#tracy-debug .tracy-icons a:hover { +.tracy-icons a:hover { color: white; } -#tracy-debug .tracy-inner-container { +.tracy-inner-container { min-width: fit-content; } @media print { - #tracy-debug * { + * { display: none; } } + +} diff --git a/src/Tracy/Bar/assets/bar.js b/src/Tracy/Bar/assets/bar.js index f90637e81..98ee65579 100644 --- a/src/Tracy/Bar/assets/bar.js +++ b/src/Tracy/Bar/assets/bar.js @@ -26,7 +26,7 @@ function getOption(key) { class Panel { constructor(id) { this.id = id; - this.elem = document.getElementById(this.id); + this.elem = Debug.shadow.querySelector('#' + CSS.escape(this.id)); this.elem.Tracy = this.elem.Tracy || {}; } @@ -37,7 +37,7 @@ class Panel { this.init = function () {}; elem.innerHTML = elem.tracyContent = elem.dataset.tracyContent; delete elem.dataset.tracyContent; - Tracy.Dumper.init(Debug.layer); + Tracy.Dumper.init(Debug.shadow); evalScripts(elem); draggable(elem, { @@ -154,7 +154,7 @@ class Panel { } let doc = win.document; - doc.write(''); + doc.write(''); let script = doc.createElement('script'); script.src = baseUrl + '_tracy_bar=js&XDEBUG_SESSION_STOP=1'; @@ -163,10 +163,10 @@ class Panel { doc.head.appendChild(script); let meta = this.elem.parentElement.lastElementChild; - doc.body.innerHTML = '' + doc.body.innerHTML = '
' + '
' + this.elem.tracyContent + '
' + meta.outerHTML - + ''; + + '
'; evalScripts(doc.body); if (this.elem.querySelector('h1')) { doc.title = this.elem.querySelector('h1').textContent; @@ -253,7 +253,7 @@ Panel.zIndexCounter = 1; class Bar { init() { this.id = 'tracy-debug-bar'; - this.elem = document.getElementById(this.id); + this.elem = Debug.shadow.querySelector('#' + this.id); draggable(this.elem, { handles: this.elem.querySelectorAll('li:first-child'), @@ -357,7 +357,7 @@ class Bar { close() { - document.getElementById('tracy-debug').style.display = 'none'; + Debug.host.style.display = 'none'; } @@ -396,15 +396,31 @@ class Debug { static init(content) { Debug.bar = new Bar; Debug.panels = {}; - Debug.layer = document.createElement('tracy-div'); - Debug.layer.setAttribute('id', 'tracy-debug'); - Debug.layer.innerHTML = content; - (document.body || document.documentElement).appendChild(Debug.layer); - evalScripts(Debug.layer); - Debug.layer.style.display = 'block'; + + // Shadow DOM for CSS isolation + let host = document.createElement('tracy-bar'); + Debug.host = host; + let shadow = host.attachShadow({ mode: 'open' }); + Debug.shadow = shadow; + + // Clone CSS from document.head into shadow root (exclude BlueScreen CSS) + document.querySelectorAll('style.tracy-debug:not(.tracy-bs-css)').forEach((s) => { + shadow.appendChild(s.cloneNode(true)); + }); + + // Insert content directly into shadow root + let temp = document.createElement('div'); + temp.innerHTML = content; + while (temp.firstChild) { + shadow.appendChild(temp.firstChild); + } + + (document.body || document.documentElement).appendChild(host); + evalScripts(shadow); + host.style.display = 'block'; Debug.bar.init(); - Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => { + Debug.shadow.querySelectorAll('.tracy-panel').forEach((panel) => { Debug.panels[panel.id] = new Panel(panel.id); Debug.panels[panel.id].restorePosition(); }); @@ -442,12 +458,16 @@ class Debug { }); } - Debug.layer.insertAdjacentHTML('beforeend', content.panels); - evalScripts(Debug.layer); + let temp = document.createElement('div'); + temp.innerHTML = content.panels; + while (temp.firstChild) { + Debug.shadow.appendChild(temp.firstChild); + } + evalScripts(Debug.shadow); Debug.bar.elem.insertAdjacentHTML('beforeend', content.bar); let ajaxBar = Debug.bar.elem.querySelector('.tracy-row:last-child'); - Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => { + Debug.shadow.querySelectorAll('.tracy-panel').forEach((panel) => { if (!Debug.panels[panel.id]) { Debug.panels[panel.id] = new Panel(panel.id); Debug.panels[panel.id].restorePosition(); diff --git a/src/Tracy/Bar/dist/dumps.agent.phtml b/src/Tracy/Bar/dist/dumps.agent.phtml index ea5e8ea59..23d1512a9 100644 --- a/src/Tracy/Bar/dist/dumps.agent.phtml +++ b/src/Tracy/Bar/dist/dumps.agent.phtml @@ -17,3 +17,4 @@ foreach ($data as $item) /* pos 5:1 */ { echo "\n"; } + diff --git a/src/Tracy/Bar/dist/info.panel.phtml b/src/Tracy/Bar/dist/info.panel.phtml index e026ff40c..e87dfa97c 100644 --- a/src/Tracy/Bar/dist/info.panel.phtml +++ b/src/Tracy/Bar/dist/info.panel.phtml @@ -56,7 +56,7 @@ if ($packages || $devPackages) /* pos 35:3 */ { echo '

Composer Packages ('; echo Tracy\Helpers::escapeHtml(count($packages)) /* pos 37:24 */; - echo Tracy\Helpers::escapeHtml($devPackages ? ' + ' . count($devPackages) . ' dev' : '') /* pos 37:42 */; + echo Tracy\Helpers::escapeHtml($devPackages ? ' + ' . count($devPackages) . ' dev' : null) /* pos 37:42 */; echo ')

@@ -72,7 +72,7 @@ if ($packages || $devPackages) /* pos 35:3 */ { echo ' '; echo Tracy\Helpers::escapeHtml($package->version) /* pos 44:11 */; - echo Tracy\Helpers::escapeHtml(strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '') /* pos 44:30 */; + echo Tracy\Helpers::escapeHtml(strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : null) /* pos 44:30 */; echo ' '; @@ -95,7 +95,7 @@ if ($packages || $devPackages) /* pos 35:3 */ { echo ' '; echo Tracy\Helpers::escapeHtml($package->version) /* pos 54:12 */; - echo Tracy\Helpers::escapeHtml(strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '') /* pos 54:31 */; + echo Tracy\Helpers::escapeHtml(strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : null) /* pos 54:31 */; echo ' '; diff --git a/src/Tracy/Bar/dist/warnings.agent.phtml b/src/Tracy/Bar/dist/warnings.agent.phtml index ba0292e8b..2eda20ace 100644 --- a/src/Tracy/Bar/dist/warnings.agent.phtml +++ b/src/Tracy/Bar/dist/warnings.agent.phtml @@ -12,3 +12,4 @@ foreach ($data as $item => $count) /* pos 5:1 */ { echo "\n"; } + diff --git a/src/Tracy/Bar/dist/warnings.panel.phtml b/src/Tracy/Bar/dist/warnings.panel.phtml index 78b6c0ed5..fd722292a 100644 --- a/src/Tracy/Bar/dist/warnings.panel.phtml +++ b/src/Tracy/Bar/dist/warnings.panel.phtml @@ -10,7 +10,7 @@ foreach ($data as $item => $count) /* pos 6:3 */ { [$file, $line, $message] = explode('|', $item, 3) /* pos 7:4 */; echo ' '; - echo Tracy\Helpers::escapeHtml($count ? $count . '×' : '') /* pos 9:29 */; + echo Tracy\Helpers::escapeHtml($count ? $count . '×' : null) /* pos 9:29 */; echo '
';
 	echo Tracy\Helpers::escapeHtml($message) /* pos 10:14 */;
diff --git a/src/Tracy/Bar/panels/info.panel.latte b/src/Tracy/Bar/panels/info.panel.latte
index d2335706e..747857ae1 100644
--- a/src/Tracy/Bar/panels/info.panel.latte
+++ b/src/Tracy/Bar/panels/info.panel.latte
@@ -34,14 +34,14 @@
 
 		{if $packages || $devPackages}
 			

- Composer Packages ({count($packages)}{$devPackages ? ' + ' . count($devPackages) . ' dev' : ''}) + Composer Packages ({count($packages)}{$devPackages ? ' + ' . count($devPackages) . ' dev'})

- +
{$package->name}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4)}
@@ -51,7 +51,7 @@ - +
{$package->name}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4)}
{/if} diff --git a/src/Tracy/Bar/panels/warnings.panel.latte b/src/Tracy/Bar/panels/warnings.panel.latte index e639c71c0..0d50b83e3 100644 --- a/src/Tracy/Bar/panels/warnings.panel.latte +++ b/src/Tracy/Bar/panels/warnings.panel.latte @@ -6,7 +6,7 @@ {foreach $data as $item => $count} {do [$file, $line, $message] = explode('|', $item, 3)} - {$count ? $count . '×' : ''} + {$count ? $count . '×'}
{$message} in {Tracy\Helpers::editorLink($file, (int) $line)}
{/foreach} diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php index 1ff43695c..f4c0fc226 100644 --- a/src/Tracy/BlueScreen/BlueScreen.php +++ b/src/Tracy/BlueScreen/BlueScreen.php @@ -370,7 +370,7 @@ public static function highlightFile( ? CodeHighlighter::highlightPhp($source, $line, $column) : '
' . CodeHighlighter::highlightLine(htmlspecialchars($source, ENT_IGNORE, 'UTF-8'), $line, $column) . '
'; - if ($editor = Helpers::editorUri($file, $line)) { + if ($editor = Helpers::editorUri($file, line: $line, column: $column)) { $source = substr_replace($source, ' title="Ctrl-Click to open in editor" data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0); } @@ -460,6 +460,8 @@ public function getAgentDumper(): \Closure { return fn($v, $k = null): string => Dumper::toText($v, [ Dumper::DEPTH => 3, + Dumper::TRUNCATE => $this->maxLength, + Dumper::ITEMS => $this->maxItems, Dumper::SCRUBBER => $this->scrubber, Dumper::KEYS_TO_HIDE => $this->keysToHide, ], $k); diff --git a/src/Tracy/BlueScreen/assets/agent.latte b/src/Tracy/BlueScreen/assets/agent.latte index 3e2a32bac..214b93919 100644 --- a/src/Tracy/BlueScreen/assets/agent.latte +++ b/src/Tracy/BlueScreen/assets/agent.latte @@ -45,7 +45,7 @@ This is an error page generated by Tracy (https://tracy.nette.org). {foreach Helpers::getExceptionChain($exception) as $i => $ex} {do $title = $blueScreen->getExceptionTitle($ex)} - {do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''} + {do $code = $ex->getCode() ? ' #' . $ex->getCode()} {if $i === 0} # {$title}: {$ex->getMessage()}{$code} {else} diff --git a/src/Tracy/BlueScreen/assets/bluescreen.css b/src/Tracy/BlueScreen/assets/bluescreen.css index dd80a54a2..09a7db2fe 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.css +++ b/src/Tracy/BlueScreen/assets/bluescreen.css @@ -2,6 +2,13 @@ * This file is part of the Tracy (https://tracy.nette.org) */ +@layer components { + +/* shadow DOM host */ +:host { + display: contents; +} + html.tracy-bs-visible, html.tracy-bs-visible body { display: block; @@ -9,7 +16,7 @@ html.tracy-bs-visible body { position: static; } -#tracy-bs { +.tracy-bs { font: 9pt/1.5 Verdana, sans-serif; background: white; color: #333; @@ -19,404 +26,406 @@ html.tracy-bs-visible body { top: 0; width: 100%; text-align: left; -} -#tracy-bs a { - text-decoration: none; - color: #328ADC; - padding: 0 4px; - margin: 0 -4px; -} + a { + text-decoration: none; + color: #328ADC; + padding: 0 4px; + margin: 0 -4px; + } -#tracy-bs a + a { - margin-left: 0; -} + a + a { + margin-left: 0; + } -#tracy-bs a:hover, -#tracy-bs a:focus { - color: #085AA3; -} + a:hover, + a:focus { + color: #085AA3; + } -#tracy-bs-toggle { - position: absolute; - right: .5em; - top: .5em; - text-decoration: none; - background: #CD1818; - color: white !important; - padding: 3px; -} + .tracy-bs-toggle { + position: absolute; + right: .5em; + top: .5em; + text-decoration: none; + background: #CD1818; + color: white !important; + padding: 3px; + } -#tracy-bs-toggle.tracy-collapsed { - position: fixed; -} + .tracy-bs-toggle.tracy-collapsed { + position: fixed; + } -.tracy-bs-main { - display: flex; - flex-direction: column; - padding-bottom: 80vh; -} + .tracy-bs-main { + display: flex; + flex-direction: column; + padding-bottom: 80vh; + } -.tracy-bs-main.tracy-collapsed { - display: none; -} + .tracy-bs-main.tracy-collapsed { + display: none; + } -#tracy-bs :where(:is( - h1, h2, h3, h4, h5, h6, - p, - ol, ul, dl, - pre, table, hr, - .tracy-section-panel, - .tracy-pane -):not(:first-child)) { - margin-top: var(--tracy-space); -} + :where(:is( + h1, h2, h3, h4, h5, h6, + p, + ol, ul, dl, + pre, table, hr, + .tracy-section-panel, + .tracy-pane + ):not(:first-child)) { + margin-top: var(--tracy-space); + } -#tracy-bs h1 { - font-size: 15pt; - font-weight: normal; - text-shadow: 1px 1px 2px rgba(0, 0, 0, .3); -} + h1 { + font-size: 15pt; + font-weight: normal; + text-shadow: 1px 1px 2px rgba(0, 0, 0, .3); + } -#tracy-bs h1 span { - white-space: pre-wrap; -} + h1 span { + white-space: pre-wrap; + } -#tracy-bs h2 { - font-size: 14pt; - font-weight: normal; -} + h2 { + font-size: 14pt; + font-weight: normal; + } -#tracy-bs h3 { - font-size: 10pt; - font-weight: bold; -} + h3 { + font-size: 10pt; + font-weight: bold; + } -#tracy-bs pre, -#tracy-bs code, -#tracy-bs table { - font: 9pt/1.5 Consolas, monospace !important; -} + pre, + code, + table { + font: 9pt/1.5 Consolas, monospace !important; + } -#tracy-bs pre, -#tracy-bs table { - background: #FDF5CE; - padding: .4em .7em; - border: 2px solid #ffffffa6; - box-shadow: 1px 2px 6px #00000005; - overflow: auto; -} + pre, + table { + background: #FDF5CE; + padding: .4em .7em; + border: 2px solid #ffffffa6; + box-shadow: 1px 2px 6px #00000005; + overflow: auto; + } -#tracy-bs table pre { - padding: 0; - margin: 0; - border: none; - box-shadow: none; -} + table pre { + padding: 0; + margin: 0; + border: none; + box-shadow: none; + } -#tracy-bs table { - border-collapse: collapse; - width: 100%; -} + table { + border-collapse: collapse; + width: 100%; + } -#tracy-bs td, -#tracy-bs th { - vertical-align: top; - text-align: left; - padding: 2px 6px; - border: 1px solid #e6dfbf; -} + td, + th { + vertical-align: top; + text-align: left; + padding: 2px 6px; + border: 1px solid #e6dfbf; + } -#tracy-bs th { - font-weight: bold; -} + th { + font-weight: bold; + } -#tracy-bs tr > :first-child { - width: 20%; -} + tr > :first-child { + width: 20%; + } -#tracy-bs tr:nth-child(2n), -#tracy-bs tr:nth-child(2n) pre { - background-color: #F7F0CB; -} + tr:nth-child(2n), + tr:nth-child(2n) pre { + background-color: #F7F0CB; + } -#tracy-bs .tracy-footer--sticky { - position: fixed; - width: 100%; - bottom: 0; -} + .tracy-footer--sticky { + position: fixed; + width: 100%; + bottom: 0; + } -#tracy-bs footer ul { - font-size: 7pt; - padding: var(--tracy-space); - margin: var(--tracy-space) 0 0; - color: #777; - background: #F6F5F3; - border-top: 1px solid #DDD; - list-style: none; -} + footer ul { + font-size: 7pt; + padding: var(--tracy-space); + margin: var(--tracy-space) 0 0; + color: #777; + background: #F6F5F3; + border-top: 1px solid #DDD; + list-style: none; + } -#tracy-bs .tracy-footer-logo { - position: relative; -} + .tracy-footer-logo { + position: relative; + } -#tracy-bs .tracy-footer-logo a { - position: absolute; - bottom: 0; - right: 0; - width: 100px; - height: 50px; - background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat; - opacity: .6; - padding: 0; - margin: 0; -} + .tracy-footer-logo a { + position: absolute; + bottom: 0; + right: 0; + width: 100px; + height: 50px; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAUBAMAAAD/1DctAAAAMFBMVEWupZzj39rEvbTy8O3X0sz9/PvGwLu8tavQysHq6OS0rKP5+Pbd2dT29fPMxbzPx8DKErMJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVQoFX3TQWgTQRQA0MWLIJJDYehBTykhG5ERTx56K1u8eEhCYtomE7x5L4iLh0ViF7egewuFFqSIYE6hIHsIYQ6CQSg9CDKn4QsNCRlB59C74J/ZNHW1+An5+bOPyf6/s46oz2P+A0yIeZZ2ieEHi6TOnLKTxvWq+b52mxlVO3xnM1s7xLX1504XQH65OnW2dBqn7cCkYsFsfYsWpyY/2salmFTpEyzeR8zosYqMdiPDXdyU52K1wgEa/SjGpdEwUAxqvRfckQCDOyFearsEHe2grvkh/cFAHKvdtI3lcVceKQIOFpv+FOZaNPQBwJZLPp+hfrvT5JZXaUFsR8zqQc9qSgAharkfS5M/5F6nGJJAtXq/eLr3ucZpHccSxOOIPaQhtHohpCH2Xu6rLmQ0djnr4/+J3C6v+AW8/XWYxwYNdlhWj/P5fPSTQwVr0T9lGxdaBCqErNZaqYnEwbkjEB3NasGF3lPdrHa1nnxNOMgj0+neePUPjd2v/qVvUv29ifvc19huQ48qwXShy/9o8o3OSk0cs37mOFd0Ydgvsf/oZEnPVtggfd66lORn9mDyyzXU13SRtH2L6aR5T/snGAcZPfAXz5J1YlJWBEuxdMYqQecpBrlM49xAbmqyHA+xlA1FxBtqT2xmJoNXZlIt74ZBLeJ9ZGDqByNI7p543idzJ23vXEv7IgnsxiS+eNtwNbFdLq7+Bi4wQ0I4SVb9AAAAAElFTkSuQmCC') no-repeat; + opacity: .6; + padding: 0; + margin: 0; + } -#tracy-bs .tracy-footer-logo a:hover, -#tracy-bs .tracy-footer-logo a:focus { - opacity: 1; - transition: opacity 0.1s; -} + .tracy-footer-logo a:hover, + .tracy-footer-logo a:focus { + opacity: 1; + transition: opacity 0.1s; + } -#tracy-bs .tracy-section { - padding: var(--tracy-space); -} + .tracy-section { + padding: var(--tracy-space); + } -#tracy-bs .tracy-section-panel { - background: #5040200E; - padding: var(--tracy-space); - border-radius: 8px; - box-shadow: inset 1px 1px 0px 0 #00000005; - overflow: hidden; -} + .tracy-section-panel { + background: #5040200E; + padding: var(--tracy-space); + border-radius: 8px; + box-shadow: inset 1px 1px 0px 0 #00000005; + overflow: hidden; + } -#tracy-bs .outer, /* deprecated */ -#tracy-bs .tracy-pane { - overflow: auto; -} + .outer, /* deprecated */ + .tracy-pane { + overflow: auto; + } -#tracy-bs.tracy-mac .tracy-pane { - padding-bottom: 12px; -} + &.tracy-mac .tracy-pane { + padding-bottom: 12px; + } -/* header */ -#tracy-bs .tracy-section--error { - background: #CD1818; - color: white; -} + /* header */ + .tracy-section--error { + background: #CD1818; + color: white; + } -#tracy-bs .tracy-section--error p, -#tracy-bs .tracy-section--error h1 { - font-size: 13pt; - color: white; -} + .tracy-section--error p, + .tracy-section--error h1 { + font-size: 13pt; + color: white; + } -#tracy-bs .tracy-section--error::selection, -#tracy-bs .tracy-section--error ::selection { - color: black !important; - background: #FDF5CE !important; -} + .tracy-section--error::selection, + .tracy-section--error ::selection { + color: black !important; + background: #FDF5CE !important; + } -#tracy-bs .tracy-section--error h1 a { - color: #ffefa1 !important; -} + .tracy-section--error h1 a { + color: #ffefa1 !important; + } -#tracy-bs .tracy-section--error span span { - font-size: 80%; - color: rgba(255, 255, 255, 0.5); - text-shadow: none; -} + .tracy-section--error span span { + font-size: 80%; + color: rgba(255, 255, 255, 0.5); + text-shadow: none; + } -#tracy-bs .tracy-section--error a.tracy-action { - color: white !important; - opacity: 0; - font-size: .7em; - border-bottom: none !important; -} + .tracy-section--error a.tracy-action { + color: white !important; + opacity: 0; + font-size: .7em; + border-bottom: none !important; + } -#tracy-bs .tracy-section--error:hover a.tracy-action { - opacity: .6; -} + .tracy-section--error:hover a.tracy-action { + opacity: .6; + } -#tracy-bs .tracy-section--error a.tracy-action:hover { - opacity: 1; -} + .tracy-section--error a.tracy-action:hover { + opacity: 1; + } -#tracy-bs .tracy-section--error i { - color: #ffefa1; - font-style: normal; -} + .tracy-section--error i { + color: #ffefa1; + font-style: normal; + } -#tracy-bs .tracy-section--error:has(.tracy-caused) { - border-radius: 0 0 0 8px; - overflow: hidden; -} + .tracy-section--error:has(.tracy-caused) { + border-radius: 0 0 0 8px; + overflow: hidden; + } -#tracy-bs .tracy-caused { - margin: var(--tracy-space) calc(-1 * var(--tracy-space)) calc(-1 * var(--tracy-space)); - padding: .3em var(--tracy-space); - background: #df8075; - white-space: nowrap; -} + .tracy-caused { + margin: var(--tracy-space) calc(-1 * var(--tracy-space)) calc(-1 * var(--tracy-space)); + padding: .3em var(--tracy-space); + background: #df8075; + white-space: nowrap; + } -#tracy-bs .tracy-caused a { - color: white; -} + .tracy-caused a { + color: white; + } -/* source code */ -#tracy-bs pre.tracy-code > div { - min-width: fit-content; - white-space: pre; -} + /* source code */ + pre.tracy-code > div { + min-width: fit-content; + white-space: pre; + } -#tracy-bs .tracy-code-comment { - color: rgba(0, 0, 0, 0.5); - font-style: italic; -} + .tracy-code-comment { + color: rgba(0, 0, 0, 0.5); + font-style: italic; + } -#tracy-bs .tracy-code-keyword { - color: #D24; - font-weight: bold; -} + .tracy-code-keyword { + color: #D24; + font-weight: bold; + } -#tracy-bs .tracy-code-var { - font-weight: bold; -} + .tracy-code-var { + font-weight: bold; + } -#tracy-bs .tracy-line-highlight { - background: #CD1818; - color: white; - font-weight: bold; - font-style: normal; - display: block; - padding: 0 1ch; - margin: 0 -1ch -1lh; -} + .tracy-line-highlight { + background: #CD1818; + color: white; + font-weight: bold; + font-style: normal; + display: block; + padding: 0 1ch; + margin: 0 -1ch -1lh; + } -#tracy-bs .tracy-column-highlight { - display: inline-block; - backdrop-filter: grayscale(1); - margin: 0 -1px; - padding: 0 1px; -} + .tracy-column-highlight { + display: inline-block; + backdrop-filter: grayscale(1); + margin: 0 -1px; + padding: 0 1px; + } -#tracy-bs .tracy-line { - color: #9F9C7F; - font-weight: normal; - font-style: normal; -} + .tracy-line { + color: #9F9C7F; + font-weight: normal; + font-style: normal; + } -#tracy-bs a.tracy-editor { - color: inherit; - border-bottom: 1px dotted rgba(0, 0, 0, .3); - border-radius: 3px; -} + a.tracy-editor { + color: inherit; + border-bottom: 1px dotted rgba(0, 0, 0, .3); + border-radius: 3px; + } -#tracy-bs a.tracy-editor:hover { - background: #0001; -} + a.tracy-editor:hover { + background: #0001; + } -#tracy-bs span[data-tracy-href] { - border-bottom: 1px dotted rgba(0, 0, 0, .3); -} + span[data-tracy-href] { + border-bottom: 1px dotted rgba(0, 0, 0, .3); + } -#tracy-bs .tracy-dump-whitespace { - color: #0003; -} + .tracy-dump-whitespace { + color: #0003; + } -#tracy-bs .tracy-callstack { - display: grid; - overflow: auto; - grid-template-columns: max-content 1fr; - row-gap: calc(.5 * var(--tracy-space)); -} + .tracy-callstack { + display: grid; + overflow: auto; + grid-template-columns: max-content 1fr; + row-gap: calc(.5 * var(--tracy-space)); + } -#tracy-bs .tracy-callstack-file { - text-align: right; - padding-right: var(--tracy-space); - white-space: nowrap; -} + .tracy-callstack-file { + text-align: right; + padding-right: var(--tracy-space); + white-space: nowrap; + } -#tracy-bs .tracy-callstack-callee { - white-space: nowrap; -} + .tracy-callstack-callee { + white-space: nowrap; + } -#tracy-bs .tracy-callstack-additional { - grid-column-start: 1; - grid-column-end: 3; -} + .tracy-callstack-additional { + grid-column-start: 1; + grid-column-end: 3; + } -#tracy-bs .tracy-callstack-args tr:first-child > * { - position: relative; -} + .tracy-callstack-args tr:first-child > * { + position: relative; + } -#tracy-bs .tracy-callstack-args tr:first-child td:before { - position: absolute; - right: .3em; - content: 'may not be true'; - opacity: .4; -} + .tracy-callstack-args tr:first-child td:before { + position: absolute; + right: .3em; + content: 'may not be true'; + opacity: .4; + } -#tracy-bs .tracy-panel-fadein { - animation: tracy-panel-fadein .12s ease; -} + .tracy-panel-fadein { + animation: tracy-panel-fadein .12s ease; + } -@keyframes tracy-panel-fadein { - 0% { - opacity: 0; + @keyframes tracy-panel-fadein { + 0% { + opacity: 0; + } } -} -#tracy-bs .tracy-section--causedby { - flex-direction: column; - padding: 0; -} + .tracy-section--causedby { + flex-direction: column; + padding: 0; + } -#tracy-bs .tracy-section--causedby:not(.tracy-collapsed) { - display: flex; -} + .tracy-section--causedby:not(.tracy-collapsed) { + display: flex; + } -#tracy-bs .tracy-section--causedby .tracy-section--error { - background: #cd1818a6; -} + .tracy-section--causedby .tracy-section--error { + background: #cd1818a6; + } -#tracy-bs .tracy-section--error + .tracy-section--stack { - margin-top: calc(1.5 * var(--tracy-space)); -} + .tracy-section--error + .tracy-section--stack { + margin-top: calc(1.5 * var(--tracy-space)); + } -/* tabs */ -#tracy-bs .tracy-tab-bar { - display: flex; - list-style: none; - padding-left: 0; - margin: 0; - width: 100%; - font-size: 110%; - column-gap: var(--tracy-space); -} + /* tabs */ + .tracy-tab-bar { + display: flex; + list-style: none; + padding-left: 0; + margin: 0; + width: 100%; + font-size: 110%; + column-gap: var(--tracy-space); + } -#tracy-bs .tracy-tab-bar a { - display: block; - padding: calc(.5 * var(--tracy-space)) var(--tracy-space); - margin: 0; - height: 100%; - box-sizing: border-box; - border-radius: 5px 5px 0 0; - text-decoration: none; - transition: all 0.1s; -} + .tracy-tab-bar a { + display: block; + padding: calc(.5 * var(--tracy-space)) var(--tracy-space); + margin: 0; + height: 100%; + box-sizing: border-box; + border-radius: 5px 5px 0 0; + text-decoration: none; + transition: all 0.1s; + } -#tracy-bs .tracy-tab-bar > .tracy-active a { - background: white; + .tracy-tab-bar > .tracy-active a { + background: white; + } + + .tracy-tab-panel { + border-top: 2px solid white; + padding-top: var(--tracy-space); + overflow: auto; + } } -#tracy-bs .tracy-tab-panel { - border-top: 2px solid white; - padding-top: var(--tracy-space); - overflow: auto; } diff --git a/src/Tracy/BlueScreen/assets/bluescreen.js b/src/Tracy/BlueScreen/assets/bluescreen.js index c8983b207..9af0ea746 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.js +++ b/src/Tracy/BlueScreen/assets/bluescreen.js @@ -6,7 +6,21 @@ class BlueScreen { static init(ajax) { BlueScreen.globalInit(); - let blueScreen = document.getElementById('tracy-bs'); + let blueScreen = document.querySelector('.tracy-bs'); + + // Shadow DOM for CSS isolation + let host = document.createElement('tracy-bs'); + let shadow = host.attachShadow({ mode: 'open' }); + BlueScreen.shadow = shadow; + BlueScreen.host = host; + + // Clone CSS into shadow root (exclude Bar CSS) + document.querySelectorAll('style.tracy-debug:not(.tracy-bar-css)').forEach((s) => { + shadow.appendChild(s.cloneNode(true)); + }); + + shadow.appendChild(blueScreen); + document.body.appendChild(host); document.documentElement.classList.add('tracy-bs-visible'); if (navigator.platform.indexOf('Mac') > -1) { @@ -14,22 +28,23 @@ class BlueScreen { } blueScreen.addEventListener('tracy-toggle', (e) => { - if (e.target.matches('#tracy-bs-toggle')) { // blue screen toggle + let target = e.composedPath()[0] || e.target; + if (target.matches('.tracy-bs-toggle')) { // blue screen toggle document.documentElement.classList.toggle('tracy-bs-visible', !e.detail.collapsed); - } else if (!e.target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle + } else if (!target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle e.detail.relatedTarget.classList.toggle('tracy-panel-fadein', !e.detail.collapsed); } }); if (!ajax) { - document.body.appendChild(blueScreen); - let id = location.href + document.querySelector('.tracy-section--error').textContent; + let id = location.href + shadow.querySelector('.tracy-section--error').textContent; Tracy.Toggle.persist(blueScreen, sessionStorage.getItem('tracy-toggles-bskey') === id); sessionStorage.setItem('tracy-toggles-bskey', id); } - (new ResizeObserver(stickyFooter)).observe(blueScreen); + Tracy.Dumper.init(shadow); + (new ResizeObserver(() => stickyFooter(shadow))).observe(blueScreen); if (document.documentElement.classList.contains('tracy-bs-visible')) { blueScreen.scrollIntoView(); @@ -41,33 +56,37 @@ class BlueScreen { // enables toggling via ESC document.addEventListener('keyup', (e) => { if (e.keyCode === 27 && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { // ESC - Tracy.Toggle.toggle(document.getElementById('tracy-bs-toggle')); + let toggle = BlueScreen.shadow && BlueScreen.shadow.querySelector('.tracy-bs-toggle'); + if (toggle) { + Tracy.Toggle.toggle(toggle); + } } }); Tracy.TableSort.init(); Tracy.Tabs.init(); - window.addEventListener('scroll', stickyFooter); + window.addEventListener('scroll', () => stickyFooter(BlueScreen.shadow)); BlueScreen.globalInit = function () {}; } static loadAjax(content) { - let ajaxBs = document.getElementById('tracy-bs'); - if (ajaxBs) { - ajaxBs.remove(); + let host = document.querySelector('tracy-bs'); + if (host) { + host.remove(); } document.body.insertAdjacentHTML('beforeend', content); - ajaxBs = document.getElementById('tracy-bs'); - Tracy.Dumper.init(ajaxBs); BlueScreen.init(true); } } -function stickyFooter() { - let footer = document.querySelector('#tracy-bs footer'); +function stickyFooter(root) { + let footer = root && root.querySelector('footer'); + if (!footer) { + return; + } footer.classList.toggle('tracy-footer--sticky', false); // to measure footer.offsetTop footer.classList.toggle('tracy-footer--sticky', footer.offsetHeight + footer.offsetTop - window.innerHeight - document.documentElement.scrollTop < 0); } diff --git a/src/Tracy/BlueScreen/assets/content.latte b/src/Tracy/BlueScreen/assets/content.latte index 6b37a21b7..b617acb96 100644 --- a/src/Tracy/BlueScreen/assets/content.latte +++ b/src/Tracy/BlueScreen/assets/content.latte @@ -19,8 +19,8 @@ {varType Fiber[] $fibers} {* *} - -  +
+ 
{do $ex = $exception} @@ -71,4 +71,4 @@
- +
diff --git a/src/Tracy/BlueScreen/assets/page.latte b/src/Tracy/BlueScreen/assets/page.latte index 0458a3597..7f80014b2 100644 --- a/src/Tracy/BlueScreen/assets/page.latte +++ b/src/Tracy/BlueScreen/assets/page.latte @@ -6,7 +6,7 @@ {varType string $js} {varType string $source} {do $title = $blueScreen->getExceptionTitle($exception)} -{do $code = $exception->getCode() ? ' #' . $exception->getCode() : ''} +{do $code = $exception->getCode() ? ' #' . $exception->getCode()} {do $chain = Helpers::getExceptionChain($exception)} {* *} @@ -23,7 +23,7 @@ {if count($chain) > 1} {/if} diff --git a/src/Tracy/BlueScreen/assets/section-header.latte b/src/Tracy/BlueScreen/assets/section-header.latte index b46182da4..98938cc96 100644 --- a/src/Tracy/BlueScreen/assets/section-header.latte +++ b/src/Tracy/BlueScreen/assets/section-header.latte @@ -5,7 +5,7 @@ {varType Tracy\BlueScreen $blueScreen} {do $title = $blueScreen->getExceptionTitle($ex)} -{do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''} +{do $code = $ex->getCode() ? ' #' . $ex->getCode()}

{$title}{$code}

diff --git a/src/Tracy/BlueScreen/dist/agent.phtml b/src/Tracy/BlueScreen/dist/agent.phtml index fdaf58fb1..2a50d0b66 100644 --- a/src/Tracy/BlueScreen/dist/agent.phtml +++ b/src/Tracy/BlueScreen/dist/agent.phtml @@ -63,7 +63,7 @@ Mapped source: '; echo "\n"; foreach (Helpers::getExceptionChain($exception) as $i => $ex) /* pos 46:1 */ { $title = $blueScreen->getExceptionTitle($ex) /* pos 47:2 */; - $code = $ex->getCode() ? ' #' . $ex->getCode() : '' /* pos 48:2 */; + $code = $ex->getCode() ? ' #' . $ex->getCode() : null /* pos 48:2 */; if ($i === 0) /* pos 49:2 */ { echo '# '; echo Tracy\Helpers::escapeMd($title) /* pos 50:5 */; @@ -119,7 +119,7 @@ in '; $argParts = [] /* pos 84:5 */; foreach ($row['args'] as $k => $v) /* pos 85:5 */ { $name = isset($params[$k]) && !$params[$k]->isVariadic() ? '$' . $params[$k]->getName() : '#' . $k /* pos 86:6 */; - $dump = rtrim($agentDump($v)) /* pos 87:6 */; + $dump = rtrim($agentDump($v, $name)) /* pos 87:6 */; if (str_contains($dump, ' ') || strlen($dump) > 50) /* pos 88:6 */ { $allSimple = false /* pos 89:7 */; diff --git a/src/Tracy/BlueScreen/dist/content.phtml b/src/Tracy/BlueScreen/dist/content.phtml index 7d04ce72f..75f2fca22 100644 --- a/src/Tracy/BlueScreen/dist/content.phtml +++ b/src/Tracy/BlueScreen/dist/content.phtml @@ -19,8 +19,8 @@ use Tracy\Helpers; /** @var mixed[][] $obStatus */ /** @var Generator[] $generators */ /** @var Fiber[] $fibers */ -echo ' -  +echo '
+ 
'; @@ -109,5 +109,5 @@ echo ' - +
'; diff --git a/src/Tracy/BlueScreen/dist/page.phtml b/src/Tracy/BlueScreen/dist/page.phtml index 587232dab..b6e2bb73b 100644 --- a/src/Tracy/BlueScreen/dist/page.phtml +++ b/src/Tracy/BlueScreen/dist/page.phtml @@ -8,7 +8,7 @@ use Tracy\Helpers; /** @var string $js */ /** @var string $source */ $title = $blueScreen->getExceptionTitle($exception) /* pos 8:1 */; -$code = $exception->getCode() ? ' #' . $exception->getCode() : '' /* pos 9:1 */; +$code = $exception->getCode() ? ' #' . $exception->getCode() : null /* pos 9:1 */; $chain = Helpers::getExceptionChain($exception) /* pos 10:1 */; echo '

@@ -39,7 +39,7 @@ if (count($chain) > 1) /* pos 24:2 */ { echo Tracy\Helpers::escapeHtml(get_debug_type($ex)) /* pos 26:12 */; echo ': '; echo Tracy\Helpers::escapeHtml($ex->getMessage()) /* pos 26:35 */; - echo Tracy\Helpers::escapeHtml($ex->getCode() ? ' #' . $ex->getCode() : '') /* pos 26:54 */; + echo Tracy\Helpers::escapeHtml($ex->getCode() ? ' #' . $ex->getCode() : null) /* pos 26:54 */; echo ' '; diff --git a/src/Tracy/BlueScreen/dist/section-header.phtml b/src/Tracy/BlueScreen/dist/section-header.phtml index f6d912bc0..4cdd26a20 100644 --- a/src/Tracy/BlueScreen/dist/section-header.phtml +++ b/src/Tracy/BlueScreen/dist/section-header.phtml @@ -7,7 +7,7 @@ use Tracy\Helpers; /** @var Tracy\BlueScreen $blueScreen */ echo "\n"; $title = $blueScreen->getExceptionTitle($ex) /* pos 7:1 */; -$code = $ex->getCode() ? ' #' . $ex->getCode() : '' /* pos 8:1 */; +$code = $ex->getCode() ? ' #' . $ex->getCode() : null /* pos 8:1 */; echo '
'; diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php index 2b581311c..511f9d90c 100644 --- a/src/Tracy/Debugger/Debugger.php +++ b/src/Tracy/Debugger/Debugger.php @@ -17,7 +17,7 @@ */ class Debugger { - public const Version = '2.12.0'; + public const Version = '3.0-dev'; /** server modes for Debugger::enable() */ public const @@ -27,19 +27,19 @@ class Debugger public const CookieSecret = 'tracy-debug'; - /** @deprecated use Debugger::Version */ + #[\Deprecated('use Debugger::Version')] public const VERSION = self::Version; - /** @deprecated use Debugger::Development */ + #[\Deprecated('use Debugger::Development')] public const DEVELOPMENT = self::Development; - /** @deprecated use Debugger::Production */ + #[\Deprecated('use Debugger::Production')] public const PRODUCTION = self::Production; - /** @deprecated use Debugger::Detect */ + #[\Deprecated('use Debugger::Detect')] public const DETECT = self::Detect; - /** @deprecated use Debugger::CookieSecret */ + #[\Deprecated('use Debugger::CookieSecret')] public const COOKIE_SECRET = self::CookieSecret; /** in production mode is suppressed any debugging output */ @@ -93,7 +93,7 @@ class Debugger /** theme for dump() */ public static string $dumpTheme = 'light'; - /** @deprecated */ + #[\Deprecated] public static $maxLen; /********************* logging ****************d*g**/ @@ -495,7 +495,7 @@ public static function dump(mixed $var, bool $return = false): mixed } elseif (!self::$productionMode) { $html = Helpers::isHtmlMode(); - echo $html ? '' : ''; + echo $html ? '' : ''; Dumper::dump($var, [ Dumper::DEPTH => self::$maxDepth, Dumper::TRUNCATE => self::$maxLength, @@ -504,7 +504,7 @@ public static function dump(mixed $var, bool $return = false): mixed Dumper::THEME => self::$dumpTheme, Dumper::KEYS_TO_HIDE => self::$keysToHide, ]); - echo $html ? '' : ''; + echo $html ? '' : ''; if ($html && Helpers::isAgent()) { Helpers::consoleLog(Dumper::toText($var, [ diff --git a/src/Tracy/Debugger/DeferredContent.php b/src/Tracy/Debugger/DeferredContent.php index 2dfe9f688..9eef1d4dc 100644 --- a/src/Tracy/Debugger/DeferredContent.php +++ b/src/Tracy/Debugger/DeferredContent.php @@ -119,16 +119,16 @@ public function sendAssets(): bool private function buildJsCss(): string { - $css = array_map(file_get_contents(...), array_merge([ + $sharedCss = array_map(file_get_contents(...), array_merge([ __DIR__ . '/../assets/reset.css', - __DIR__ . '/../Bar/assets/bar.css', __DIR__ . '/../assets/toggle.css', __DIR__ . '/../assets/table-sort.css', __DIR__ . '/../assets/tabs.css', __DIR__ . '/../Dumper/assets/dumper-light.css', __DIR__ . '/../Dumper/assets/dumper-dark.css', - __DIR__ . '/../BlueScreen/assets/bluescreen.css', ], Debugger::$customCssFiles)); + $barCss = file_get_contents(__DIR__ . '/../Bar/assets/bar.css') ?: throw new \RuntimeException('Cannot read bar.css'); + $bsCss = file_get_contents(__DIR__ . '/../BlueScreen/assets/bluescreen.css') ?: throw new \RuntimeException('Cannot read bluescreen.css'); $js1 = array_map(fn($file) => '(function() {' . file_get_contents($file) . '})();', [ __DIR__ . '/../Bar/assets/bar.js', @@ -143,11 +143,17 @@ private function buildJsCss(): string $str = "'use strict'; (function(){ - var el = document.createElement('style'); - el.setAttribute('nonce', document.currentScript.getAttribute('nonce') || document.currentScript.nonce); - el.className='tracy-debug'; - el.textContent=" . Helpers::jsonEncode(Helpers::minifyCss(implode('', $css))) . "; - document.head.appendChild(el);}) + var n = document.currentScript.getAttribute('nonce') || document.currentScript.nonce; + function s(css, cls) { + var el = document.createElement('style'); + el.setAttribute('nonce', n); + el.className = cls; + el.textContent = css; + document.head.appendChild(el); + } + s(" . json_encode(Helpers::minifyCss(implode('', $sharedCss))) . ",'tracy-debug'); + s(" . json_encode(Helpers::minifyCss($barCss)) . ",'tracy-debug tracy-bar-css'); + s(" . json_encode(Helpers::minifyCss($bsCss)) . ",'tracy-debug tracy-bs-css');}) ();\n" . implode('', $js1) . implode('', $js2); return $str; diff --git a/src/Tracy/Debugger/ProductionStrategy.php b/src/Tracy/Debugger/ProductionStrategy.php index ed760e00a..71410cf70 100644 --- a/src/Tracy/Debugger/ProductionStrategy.php +++ b/src/Tracy/Debugger/ProductionStrategy.php @@ -63,7 +63,7 @@ public function handleError( $err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message) . " in $file:$line"; } - Debugger::tryLog($err, Debugger::ERROR); + Debugger::tryLog($err, Debugger::WARNING); } diff --git a/src/Tracy/Dumper/Dumper.php b/src/Tracy/Dumper/Dumper.php index 5bee48b40..19e2a2945 100644 --- a/src/Tracy/Dumper/Dumper.php +++ b/src/Tracy/Dumper/Dumper.php @@ -179,7 +179,7 @@ public static function renderAssets(): void . file_get_contents(__DIR__ . '/../assets/toggle.css') . file_get_contents(__DIR__ . '/assets/dumper-light.css') . file_get_contents(__DIR__ . '/assets/dumper-dark.css'); - echo "", str_replace('\n"; + echo "\n"; if (!Debugger::isEnabled() || !Debugger::$showBar) { $s = '(function(){' . file_get_contents(__DIR__ . '/../assets/toggle.js') . '})();' diff --git a/src/Tracy/Dumper/assets/dumper-dark.css b/src/Tracy/Dumper/assets/dumper-dark.css index 8346d7216..29a2e323e 100644 --- a/src/Tracy/Dumper/assets/dumper-dark.css +++ b/src/Tracy/Dumper/assets/dumper-dark.css @@ -2,6 +2,8 @@ * This file is part of the Tracy (https://tracy.nette.org) */ +@layer base { + .tracy-dump.tracy-dark { text-align: left; color: #f8f8f2; @@ -143,3 +145,5 @@ span[data-tracy-href] { background: #c0c0c033; } } + +} diff --git a/src/Tracy/Dumper/assets/dumper-light.css b/src/Tracy/Dumper/assets/dumper-light.css index fb865920d..36a408f84 100644 --- a/src/Tracy/Dumper/assets/dumper-light.css +++ b/src/Tracy/Dumper/assets/dumper-light.css @@ -2,6 +2,8 @@ * This file is part of the Tracy (https://tracy.nette.org) */ +@layer base { + .tracy-dump.tracy-light { text-align: left; color: #444; @@ -143,3 +145,5 @@ span[data-tracy-href] { background: #c0c0c033; } } + +} diff --git a/src/Tracy/Dumper/assets/dumper.js b/src/Tracy/Dumper/assets/dumper.js index 81417ad71..59e68b201 100644 --- a/src/Tracy/Dumper/assets/dumper.js +++ b/src/Tracy/Dumper/assets/dumper.js @@ -46,9 +46,9 @@ class Dumper { Dumper.inited = true; document.documentElement.addEventListener('click', (e) => { - let el; + let el, target = e.composedPath()[0] || e.target; // enables & ctrl or cmd key - if ((e.ctrlKey || e.metaKey) && (el = e.target.closest('[data-tracy-href]'))) { + if ((e.ctrlKey || e.metaKey) && (el = target.closest('[data-tracy-href]'))) { location.href = el.getAttribute('data-tracy-href'); return false; } @@ -56,9 +56,9 @@ class Dumper { }); document.documentElement.addEventListener('tracy-beforetoggle', (e) => { - let el; + let el, target = e.composedPath()[0] || e.target; // initializes lazy inside
-			if ((el = e.target.closest('[data-tracy-snapshot]'))) {
+			if ((el = target.closest('[data-tracy-snapshot]'))) {
 				let snapshot = JSON.parse(el.getAttribute('data-tracy-snapshot'));
 				el.removeAttribute('data-tracy-snapshot');
 				el.querySelectorAll('[data-tracy-dump]').forEach((toggler) => {
@@ -72,7 +72,8 @@ class Dumper {
 		});
 
 		document.documentElement.addEventListener('tracy-toggle', (e) => {
-			if (!e.target.matches('.tracy-dump *')) {
+			let target = e.composedPath()[0] || e.target;
+			if (!target.matches('.tracy-dump *')) {
 				return;
 			}
 
@@ -98,21 +99,23 @@ class Dumper {
 		});
 
 		document.documentElement.addEventListener('animationend', (e) => {
+			let target = e.composedPath()[0] || e.target;
 			if (e.animationName === 'tracy-dump-flash') {
-				e.target.classList.toggle('tracy-dump-flash', false);
+				target.classList.toggle('tracy-dump-flash', false);
 			}
 		});
 
 		document.addEventListener('mouseover', (e) => {
-			if (!e.target.matches('.tracy-dump *')) {
+			let target = e.composedPath()[0] || e.target;
+			if (!target.matches('.tracy-dump *')) {
 				return;
 			}
 
 			let el;
 
-			if (e.target.matches('.tracy-dump-hash') && (el = e.target.closest('tracy-div'))) {
+			if (target.matches('.tracy-dump-hash') && (el = target.getRootNode())) {
 				el.querySelectorAll('.tracy-dump-hash').forEach((el) => {
-					if (el.textContent === e.target.textContent) {
+					if (el.textContent === target.textContent) {
 						el.classList.add('tracy-dump-highlight');
 					}
 				});
@@ -125,8 +128,9 @@ class Dumper {
 		});
 
 		document.addEventListener('mouseout', (e) => {
-			if (e.target.matches('.tracy-dump-hash')) {
-				document.querySelectorAll('.tracy-dump-hash.tracy-dump-highlight').forEach((el) => {
+			let target = e.composedPath()[0] || e.target;
+			if (target.matches('.tracy-dump-hash')) {
+				target.getRootNode().querySelectorAll('.tracy-dump-hash.tracy-dump-highlight').forEach((el) => {
 					el.classList.remove('tracy-dump-highlight');
 				});
 			}
@@ -381,6 +385,31 @@ function UnknownEntityException() {}
 let Tracy = window.Tracy = window.Tracy || {};
 Tracy.Dumper = Tracy.Dumper || Dumper;
 
+if (!customElements.get('tracy-dump')) {
+	customElements.define('tracy-dump', class extends HTMLElement {
+		connectedCallback() {
+			if (this.shadowRoot) {
+				return;
+			}
+			let shadow = this.attachShadow({ mode: 'open' });
+
+			// clone CSS from document into shadow root (exclude component-specific CSS)
+			document.querySelectorAll('style.tracy-debug:not(.tracy-bar-css):not(.tracy-bs-css), style.tracy-dump-style').forEach((s) => {
+				shadow.appendChild(s.cloneNode(true));
+			});
+
+			// move only non-style/script children into shadow root
+			Array.from(this.childNodes).forEach((child) => {
+				if (child.nodeType !== 1 || (child.tagName !== 'STYLE' && child.tagName !== 'SCRIPT')) {
+					shadow.appendChild(child);
+				}
+			});
+
+			Tracy.Dumper.init(shadow);
+		}
+	});
+}
+
 function init() {
 	Tracy.Dumper.init();
 }
diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php
index e439f0787..028951533 100644
--- a/src/Tracy/Helpers.php
+++ b/src/Tracy/Helpers.php
@@ -55,6 +55,7 @@ public static function editorUri(
 		string $action = 'open',
 		string $search = '',
 		string $replace = '',
+		?int $column = null,
 	): ?string
 	{
 		if (Debugger::$editor && $file && ($action === 'create' || @is_file($file))) { // @ - may trigger error
@@ -65,7 +66,7 @@ public static function editorUri(
 			return strtr(Debugger::$editor, [
 				'%action' => $action,
 				'%file' => rawurlencode($file),
-				'%line' => $line ?: 1,
+				'%line' => ($line ?: 1) . ($column ? ':' . $column : ''),
 				'%search' => rawurlencode($search),
 				'%replace' => rawurlencode($replace),
 			]);
diff --git a/src/Tracy/Logger/Logger.php b/src/Tracy/Logger/Logger.php
index 62415339c..fa9fb0190 100644
--- a/src/Tracy/Logger/Logger.php
+++ b/src/Tracy/Logger/Logger.php
@@ -16,23 +16,22 @@
  */
 class Logger implements ILogger
 {
-	/** @var ?string name of the directory where errors should be logged */
-	public $directory;
+	/** name of the directory where errors should be logged */
+	public ?string $directory = null;
 
-	/** @var string|string[]|null email or emails to which send error notifications */
-	public $email;
+	/** @var string|string[]|null  email or emails to which send error notifications */
+	public string|array|null $email = null;
 
-	/** @var ?string sender of email notifications */
-	public $fromEmail;
+	/** sender of email notifications */
+	public ?string $fromEmail = null;
 
-	/** @var string|int  interval for sending email is 2 days */
-	public $emailSnooze = '2 days';
+	/** interval for sending email is 2 days */
+	public string|int $emailSnooze = '2 days';
 
-	/** @var callable(mixed $message, string $email): void  handler for sending emails */
-	public $mailer;
+	/** @var \Closure(mixed $message, string $email): void  handler for sending emails */
+	public ?\Closure $mailer = null;
 
-	/** @var ?BlueScreen */
-	private $blueScreen;
+	private ?BlueScreen $blueScreen = null;
 
 
 	/**
@@ -52,7 +51,7 @@ public function __construct(?string $directory, string|array|null $email = null,
 	 * For levels ERROR, EXCEPTION and CRITICAL it sends email.
 	 * @return ?string  logged error filename
 	 */
-	public function log(mixed $message, string $level = self::INFO)
+	public function log(mixed $message, string $level = self::INFO): ?string
 	{
 		if (!$this->directory) {
 			throw new \LogicException('Logging directory is not specified.');
@@ -82,10 +81,7 @@ public function log(mixed $message, string $level = self::INFO)
 	}
 
 
-	/**
-	 * @param  mixed  $message
-	 */
-	public static function formatMessage($message): string
+	public static function formatMessage(mixed $message): string
 	{
 		if ($message instanceof \Throwable) {
 			$tmp = [];
@@ -106,10 +102,7 @@ public static function formatMessage($message): string
 	}
 
 
-	/**
-	 * @param  mixed  $message
-	 */
-	public static function formatLogLine($message, ?string $exceptionFile = null): string
+	public static function formatLogLine(mixed $message, ?string $exceptionFile = null): string
 	{
 		return implode(' ', [
 			date('[Y-m-d H-i-s]'),
@@ -159,10 +152,7 @@ protected function logException(\Throwable $exception, ?string $file = null): st
 	}
 
 
-	/**
-	 * @param  mixed  $message
-	 */
-	protected function sendEmail($message): void
+	protected function sendEmail(mixed $message): void
 	{
 		$snooze = is_numeric($this->emailSnooze)
 			? $this->emailSnooze
@@ -179,12 +169,7 @@ protected function sendEmail($message): void
 	}
 
 
-	/**
-	 * Default mailer.
-	 * @param  mixed  $message
-	 * @internal
-	 */
-	public function defaultMailer($message, string $email): void
+	private function defaultMailer(mixed $message, string $email): void
 	{
 		$host = preg_replace('#[^\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? php_uname('n'));
 		mail(
diff --git a/src/Tracy/assets/helpers.js b/src/Tracy/assets/helpers.js
index 83c71b8e7..c578878f7 100644
--- a/src/Tracy/assets/helpers.js
+++ b/src/Tracy/assets/helpers.js
@@ -5,7 +5,7 @@ function init() {
 		document.addEventListener(
 			'click',
 			(e) => {
-				e.target.closest('a[href^="editor:"]')?.setAttribute('target', '_blank');
+				(e.composedPath()[0] || e.target).closest('a[href^="editor:"]')?.setAttribute('target', '_blank');
 			},
 			true,
 		);
diff --git a/src/Tracy/assets/reset.css b/src/Tracy/assets/reset.css
index b0a6a6fc8..4348889d5 100644
--- a/src/Tracy/assets/reset.css
+++ b/src/Tracy/assets/reset.css
@@ -2,385 +2,40 @@
  * This file is part of the Tracy (https://tracy.nette.org)
  */
 
-:root {
+@layer base, components;
+
+@layer base {
+
+:root,
+:host {
 	--tracy-space: 16px;
 }
 
 @media (max-width: 600px) {
-	:root {
+	:root,
+	:host {
 		--tracy-space: 8px;
 	}
 }
 
-tracy-div:not(a b),
-tracy-div:not(a b) * {
-	font: inherit;
-	line-height: inherit;
-	color: inherit;
-	background: transparent;
+*,
+*::before,
+*::after {
+	box-sizing: border-box;
 	margin: 0;
 	padding: 0;
-	border: none;
-	text-align: inherit;
-	list-style: inherit;
-	opacity: 1;
-	border-radius: 0;
-	box-shadow: none;
-	text-shadow: none;
-	box-sizing: border-box;
-	text-decoration: none;
-	text-transform: inherit;
-	white-space: inherit;
-	float: none;
-	clear: none;
-	max-width: initial;
-	min-width: initial;
-	max-height: initial;
-	min-height: initial;
-}
-
-tracy-div:not(a b) *:hover {
-	color: inherit;
-	background: transparent;
-}
-
-tracy-div:not(a b) *:not(svg):not(img):not(table) {
-	width: initial;
-	height: initial;
-}
-
-tracy-div:not(a b):before,
-tracy-div:not(a b):after,
-tracy-div:not(a b) *:before,
-tracy-div:not(a b) *:after {
-	all: unset;
-}
-
-tracy-div:not(a b) b,
-tracy-div:not(a b) strong {
-	font-weight: bold;
-}
-
-tracy-div:not(a b) small {
-	font-size: smaller;
-}
-
-tracy-div:not(a b) i,
-tracy-div:not(a b) em {
-	font-style: italic;
-}
-
-tracy-div:not(a b) big {
-	font-size: larger;
-}
-
-tracy-div:not(a b) small,
-tracy-div:not(a b) sub,
-tracy-div:not(a b) sup {
-	font-size: smaller;
-}
-
-tracy-div:not(a b) ins {
-	text-decoration: underline;
 }
 
-tracy-div:not(a b) del {
-	text-decoration: line-through;
-}
-
-tracy-div:not(a b) table {
+table {
 	border-collapse: collapse;
 }
 
-tracy-div:not(a b) pre {
-	font-family: monospace;
-	white-space: pre;
-}
-
-tracy-div:not(a b) code,
-tracy-div:not(a b) kbd,
-tracy-div:not(a b) samp {
-	font-family: monospace;
-}
-
-tracy-div:not(a b) input {
-	background-color: white;
+input {
 	padding: 1px;
-	border: 1px solid;
 }
 
-tracy-div:not(a b) textarea {
-	background-color: white;
-	border: 1px solid;
+textarea {
 	padding: 2px;
-	white-space: pre-wrap;
-}
-
-tracy-div:not(a b) select {
-	border: 1px solid;
-	white-space: pre;
-}
-
-tracy-div:not(a b) article,
-tracy-div:not(a b) aside,
-tracy-div:not(a b) details,
-tracy-div:not(a b) div,
-tracy-div:not(a b) figcaption,
-tracy-div:not(a b) footer,
-tracy-div:not(a b) form,
-tracy-div:not(a b) header,
-tracy-div:not(a b) hgroup,
-tracy-div:not(a b) main,
-tracy-div:not(a b) nav,
-tracy-div:not(a b) section,
-tracy-div:not(a b) summary,
-tracy-div:not(a b) pre,
-tracy-div:not(a b) p,
-tracy-div:not(a b) dl,
-tracy-div:not(a b) dd,
-tracy-div:not(a b) dt,
-tracy-div:not(a b) blockquote,
-tracy-div:not(a b) figure,
-tracy-div:not(a b) address,
-tracy-div:not(a b) h1,
-tracy-div:not(a b) h2,
-tracy-div:not(a b) h3,
-tracy-div:not(a b) h4,
-tracy-div:not(a b) h5,
-tracy-div:not(a b) h6,
-tracy-div:not(a b) ul,
-tracy-div:not(a b) ol,
-tracy-div:not(a b) li,
-tracy-div:not(a b) hr {
-	display: block;
-}
-
-tracy-div:not(a b) a,
-tracy-div:not(a b) b,
-tracy-div:not(a b) big,
-tracy-div:not(a b) code,
-tracy-div:not(a b) em,
-tracy-div:not(a b) i,
-tracy-div:not(a b) small,
-tracy-div:not(a b) span,
-tracy-div:not(a b) strong {
-	display: inline;
-}
-
-tracy-div:not(a b) table {
-	display: table;
-}
-
-tracy-div:not(a b) tr {
-	display: table-row;
-}
-
-tracy-div:not(a b) col {
-	display: table-column;
-}
-
-tracy-div:not(a b) colgroup {
-	display: table-column-group;
-}
-
-tracy-div:not(a b) tbody {
-	display: table-row-group;
-}
-
-tracy-div:not(a b) thead {
-	display: table-header-group;
-}
-
-tracy-div:not(a b) tfoot {
-	display: table-footer-group;
-}
-
-tracy-div:not(a b) td {
-	display: table-cell;
-}
-
-tracy-div:not(a b) th {
-	display: table-cell;
-}
-
-
-
-/* TableSort */
-tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > * {
-	position: relative;
-}
-
-tracy-div:not(a b) .tracy-sortable > :first-child > tr:first-child > *:hover:before {
-	position: absolute;
-	right: .3em;
-	content: "\21C5";
-	opacity: .4;
-	font-weight: normal;
-}
-
-
-/* dump */
-tracy-div:not(a b) .tracy-dump div {
-	padding-left: 3ex;
-}
-
-tracy-div:not(a b) .tracy-dump div div {
-	border-left: 1px solid rgba(0, 0, 0, .1);
-	margin-left: .5ex;
-}
-
-tracy-div:not(a b) .tracy-dump div div:hover {
-	border-left-color: rgba(0, 0, 0, .25);
-}
-
-tracy-div:not(a b) .tracy-dump {
-	background: #FDF5CE;
-	padding: .4em .7em;
-	border: 1px dotted silver;
-	overflow: auto;
-}
-
-tracy-div:not(a b) table .tracy-dump.tracy-dump { /* overwrite .tracy-dump.tracy-light etc. */
-	padding: 0;
-	margin: 0;
-	border: none;
-}
-
-tracy-div:not(a b) .tracy-dump-location {
-	color: gray;
-	font-size: 80%;
-	text-decoration: none;
-	background: none;
-	opacity: .5;
-	float: right;
-	cursor: pointer;
-}
-
-tracy-div:not(a b) .tracy-dump-location:hover,
-tracy-div:not(a b) .tracy-dump-location:focus {
-	color: gray;
-	background: none;
-	opacity: 1;
-}
-
-tracy-div:not(a b) .tracy-dump-array,
-tracy-div:not(a b) .tracy-dump-object {
-	color: #C22;
-}
-
-tracy-div:not(a b) .tracy-dump-string {
-	color: #35D;
-	white-space: break-spaces;
-}
-
-tracy-div:not(a b) div.tracy-dump-string {
-	position: relative;
-	padding-left: 3.5ex;
-}
-
-tracy-div:not(a b) .tracy-dump-lq {
-	margin-left: calc(-1ex - 1px);
-}
-
-tracy-div:not(a b) div.tracy-dump-string:before {
-	content: '';
-	position: absolute;
-	left: calc(3ex - 1px);
-	top: 1.5em;
-	bottom: 0;
-	border-left: 1px solid rgba(0, 0, 0, .1);
-}
-
-tracy-div:not(a b) .tracy-dump-virtual span,
-tracy-div:not(a b) .tracy-dump-dynamic span,
-tracy-div:not(a b) .tracy-dump-string span {
-	color: rgba(0, 0, 0, 0.5);
-}
-
-tracy-div:not(a b) .tracy-dump-virtual i,
-tracy-div:not(a b) .tracy-dump-dynamic i,
-tracy-div:not(a b) .tracy-dump-string i {
-	font-size: 80%;
-	font-style: normal;
-	color: rgba(0, 0, 0, 0.5);
-	user-select: none;
-}
-
-tracy-div:not(a b) .tracy-dump-number {
-	color: #090;
-}
-
-tracy-div:not(a b) .tracy-dump-null,
-tracy-div:not(a b) .tracy-dump-bool {
-	color: #850;
-}
-
-tracy-div:not(a b) .tracy-dump-virtual {
-	font-style: italic;
-}
-
-tracy-div:not(a b) .tracy-dump-public::after {
-	content: ' pub';
-}
-
-tracy-div:not(a b) .tracy-dump-protected::after {
-	content: ' pro';
-}
-
-tracy-div:not(a b) .tracy-dump-private::after {
-	content: ' pri';
-}
-
-tracy-div:not(a b) .tracy-dump-public::after,
-tracy-div:not(a b) .tracy-dump-protected::after,
-tracy-div:not(a b) .tracy-dump-private::after,
-tracy-div:not(a b) .tracy-dump-hash {
-	font-size: 85%;
-	color: rgba(0, 0, 0, 0.5);
-}
-
-tracy-div:not(a b) .tracy-dump-indent {
-	display: none;
-}
-
-tracy-div:not(a b) .tracy-dump-highlight {
-	background: #C22;
-	color: white;
-	border-radius: 2px;
-	padding: 0 2px;
-	margin: 0 -2px;
-}
-
-tracy-div:not(a b) span[data-tracy-href] {
-	border-bottom: 1px dotted rgba(0, 0, 0, .2);
-}
-
-
-/* toggle */
-tracy-div:not(a b) .tracy-toggle:after {
-	content: '';
-	display: inline-block;
-	vertical-align: middle;
-	line-height: 0;
-	border-top: .6ex solid;
-	border-right: .6ex solid transparent;
-	border-left: .6ex solid transparent;
-	transform: scale(1, 1.5);
-	margin: 0 .2ex 0 .7ex;
-	transition: .1s transform;
-	opacity: .5;
-}
-
-tracy-div:not(a b) .tracy-toggle.tracy-collapsed:after {
-	transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
-}
-
-
-/* tabs */
-tracy-div:not(a b) .tracy-tab-label {
-	user-select: none;
 }
 
-tracy-div:not(a b) .tracy-tab-panel:not(.tracy-active) {
-	display: none;
 }
diff --git a/src/Tracy/assets/table-sort.css b/src/Tracy/assets/table-sort.css
index 7967a642c..ae4a3e809 100644
--- a/src/Tracy/assets/table-sort.css
+++ b/src/Tracy/assets/table-sort.css
@@ -2,6 +2,8 @@
  * This file is part of the Tracy (https://tracy.nette.org)
  */
 
+@layer base {
+
 .tracy-sortable > :first-child > tr:first-child > * {
 	position: relative;
 }
@@ -13,3 +15,5 @@
 	opacity: .4;
 	font-weight: normal;
 }
+
+}
diff --git a/src/Tracy/assets/table-sort.js b/src/Tracy/assets/table-sort.js
index 39bae80f6..0f7e3e0e9 100644
--- a/src/Tracy/assets/table-sort.js
+++ b/src/Tracy/assets/table-sort.js
@@ -6,10 +6,11 @@
 class TableSort {
 	static init() {
 		document.documentElement.addEventListener('click', (e) => {
+			let target = e.composedPath()[0] || e.target;
 			if ((window.getSelection().type !== 'Range')
-				&& e.target.matches('.tracy-sortable > :first-child > tr:first-child *')
+				&& target.matches('.tracy-sortable > :first-child > tr:first-child *')
 			) {
-				TableSort.sort(e.target.closest('td,th'));
+				TableSort.sort(target.closest('td,th'));
 			}
 		});
 
diff --git a/src/Tracy/assets/tabs.css b/src/Tracy/assets/tabs.css
index 9add853a1..7aac90201 100644
--- a/src/Tracy/assets/tabs.css
+++ b/src/Tracy/assets/tabs.css
@@ -2,6 +2,8 @@
  * This file is part of the Tracy (https://tracy.nette.org)
  */
 
+@layer base {
+
 .tracy-tab-label {
 	user-select: none;
 }
@@ -9,3 +11,5 @@
 .tracy-tab-panel:not(.tracy-active) {
 	display: none;
 }
+
+}
diff --git a/src/Tracy/assets/tabs.js b/src/Tracy/assets/tabs.js
index 8ec2fe7b2..7b109d071 100644
--- a/src/Tracy/assets/tabs.js
+++ b/src/Tracy/assets/tabs.js
@@ -6,11 +6,11 @@
 class Tabs {
 	static init() {
 		document.documentElement.addEventListener('click', (e) => {
-			let label, context;
+			let label, context, target = e.composedPath()[0] || e.target;
 			if (
 				!e.shiftKey && !e.ctrlKey && !e.metaKey
-				&& (label = e.target.closest('.tracy-tab-label'))
-				&& (context = e.target.closest('.tracy-tabs'))
+				&& (label = target.closest('.tracy-tab-label'))
+				&& (context = target.closest('.tracy-tabs'))
 			) {
 				Tabs.toggle(context, label);
 				e.preventDefault();
diff --git a/src/Tracy/assets/toggle.css b/src/Tracy/assets/toggle.css
index acce8e4f8..aea228004 100644
--- a/src/Tracy/assets/toggle.css
+++ b/src/Tracy/assets/toggle.css
@@ -2,6 +2,8 @@
  * This file is part of the Tracy (https://tracy.nette.org)
  */
 
+@layer base {
+
 .tracy-collapsed {
 	display: none;
 }
@@ -33,3 +35,5 @@
 .tracy-toggle.tracy-collapsed:after {
 	transform: rotate(-90deg) scale(1, 1.5) translate(.1ex, 0);
 }
+
+}
diff --git a/src/Tracy/assets/toggle.js b/src/Tracy/assets/toggle.js
index aabefb5f1..7d927affa 100644
--- a/src/Tracy/assets/toggle.js
+++ b/src/Tracy/assets/toggle.js
@@ -16,7 +16,7 @@ class Toggle {
 			let el;
 			if (
 				!e.shiftKey && !e.ctrlKey && !e.metaKey
-				&& (el = e.target.closest('.tracy-toggle'))
+				&& (el = (e.composedPath()[0] || e.target).closest('.tracy-toggle'))
 				&& Math.pow(start[0] - e.clientX, 2) + Math.pow(start[1] - e.clientY, 2) < MOVE_THRESHOLD
 			) {
 				Toggle.toggle(el, undefined, e);
@@ -40,13 +40,14 @@ class Toggle {
 
 		el.dispatchEvent(new CustomEvent('tracy-beforetoggle', {
 			bubbles: true,
+			composed: true,
 			detail: { collapsed: !expand, originalEvent: e },
 		}));
 
 		if (!ref || ref === '#') {
 			ref = '+';
 		} else if (ref.substr(0, 1) === '#') {
-			dest = document;
+			dest = el.getRootNode();
 		}
 		ref = ref.match(/(\^\s*([^+\s]*)\s*)?(\+\s*(\S*)\s*)?(.*)/);
 		dest = ref[1] ? dest.parentNode : dest;
@@ -59,6 +60,7 @@ class Toggle {
 
 		el.dispatchEvent(new CustomEvent('tracy-toggle', {
 			bubbles: true,
+			composed: true,
 			detail: { relatedTarget: dest, collapsed: !expand, originalEvent: e },
 		}));
 	}
diff --git a/tests/Dumper/Dumper.dump().html.phpt b/tests/Dumper/Dumper.dump().html.phpt
index 7e6293144..571ae312a 100644
--- a/tests/Dumper/Dumper.dump().html.phpt
+++ b/tests/Dumper/Dumper.dump().html.phpt
@@ -21,7 +21,7 @@ test('html mode', function () {
 	Assert::same(123, Dumper::dump(123));
 	Assert::match(
 		<<<'XX'
-			
+			
 			
 			
Dumper::dump(123)) 📍
+			%a%
 			
 			
dump(123) 📍123
- +
XX, ob_get_clean(), ); @@ -39,10 +39,10 @@ test('dark theme', function () { dump(123); Assert::match( <<<'XX' -
dump(123) 📍123
- + XX, ob_get_clean(), ); diff --git a/tests/Tracy/Bar.renderAgent().phpt b/tests/Tracy/Bar.renderAgent().phpt index bad6f6e84..9a434206a 100644 --- a/tests/Tracy/Bar.renderAgent().phpt +++ b/tests/Tracy/Bar.renderAgent().phpt @@ -1,11 +1,9 @@ -maxItems = 3; + $blueScreen->maxLength = 10; + $fn = function (array $items, string $text) { + throw new RuntimeException('Large arguments'); + }; + try { + $fn(range(1, 10), str_repeat('x', 50)); + } catch (RuntimeException $e) { + $output = $blueScreen->renderAgent($e); + } + + Assert::contains('$items = array (10)', $output); + Assert::contains(' 0 => 1', $output); + Assert::contains(' 1 => 2', $output); + Assert::contains(' 2 => 3', $output); + Assert::contains(' ...', $output); + Assert::notContains(' 3 => 4', $output); + Assert::contains('$text = \'xxxxxxxxxx ... xxxxxxxxxx\'', $output); +}); + + test('caused by section for chained exceptions', function () use ($blueScreen) { $prev = new InvalidArgumentException('Root cause', 7); $exception = new RuntimeException('Wrapper', 5, $prev); diff --git a/tests/Tracy/Helpers.isAgent().phpt b/tests/Tracy/Helpers.isAgent().phpt index cc05e28ca..d45b30689 100644 --- a/tests/Tracy/Helpers.isAgent().phpt +++ b/tests/Tracy/Helpers.isAgent().phpt @@ -1,11 +1,9 @@ - - -  +
+ 
@@ -251,7 +251,7 @@
- +
diff --git a/tests/Tracy/expected/Debugger.exception.fiber.html.expect b/tests/Tracy/expected/Debugger.exception.fiber.html.expect index e32104186..fa94f698e 100644 --- a/tests/Tracy/expected/Debugger.exception.fiber.html.expect +++ b/tests/Tracy/expected/Debugger.exception.fiber.html.expect @@ -16,8 +16,8 @@ - -  +
+ 
diff --git a/tests/Tracy/expected/Debugger.exception.generator.html.expect b/tests/Tracy/expected/Debugger.exception.generator.html.expect index bba16447c..09c2aea00 100644 --- a/tests/Tracy/expected/Debugger.exception.generator.html.expect +++ b/tests/Tracy/expected/Debugger.exception.generator.html.expect @@ -16,8 +16,8 @@ - -  +
+ 
diff --git a/tests/Tracy/expected/Debugger.exception.html.expect b/tests/Tracy/expected/Debugger.exception.html.expect index 493667edd..aee55b3e1 100644 --- a/tests/Tracy/expected/Debugger.exception.html.expect +++ b/tests/Tracy/expected/Debugger.exception.html.expect @@ -16,8 +16,8 @@ - -  +
+ 
@@ -321,7 +321,7 @@
- +
diff --git a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect index ddaebf9e7..67065fabf 100644 --- a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect +++ b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect @@ -16,8 +16,8 @@ - -  +
+ 
diff --git a/tests/Tracy/expected/Debugger.strict.html.expect b/tests/Tracy/expected/Debugger.strict.html.expect index 6198e27df..1eba3474c 100644 --- a/tests/Tracy/expected/Debugger.strict.html.expect +++ b/tests/Tracy/expected/Debugger.strict.html.expect @@ -16,8 +16,8 @@ - -  +
+ 
@@ -260,7 +260,7 @@
- +
'); + Assert::contains('Tracy\Helpers::jsonEncode($data, true)', $r); +}); + +test('text mode disables escaping', function () { + $r = compileText('{$foo}'); + Assert::notContains('escapeHtml', $r); + Assert::notContains('jsonEncode', $r); +}); + +test('regular expression is escaped', function () { + $r = compile('{$name}'); + Assert::contains('Tracy\Helpers::escapeHtml($name)', $r); +}); + + +// noEscape for known functions + +test('editorLink not escaped', function () { + $r = compile('{Helpers::editorLink($f, $l)}'); + Assert::notContains('escapeHtml', $r); +}); + +test('highlightFile not escaped', function () { + $r = compile('{BlueScreen::highlightFile($f, $l)}'); + Assert::notContains('escapeHtml', $r); +}); + +test('$dump() not escaped', function () { + $r = compile('{$dump($v)}'); + Assert::notContains('escapeHtml', $r); +}); + +test('Dumper::toHtml not escaped', function () { + $r = compile('{Dumper::toHtml($v)}'); + Assert::notContains('escapeHtml', $r); +}); + +test('formatMessage not escaped', function () { + $r = compile('{$blueScreen->formatMessage($ex)}'); + Assert::notContains('escapeHtml', $r); +}); + + +// |json filter + +test('|json in text context', function () { + $r = compile('{=[1,2,3]|json}'); + Assert::contains('Tracy\Helpers::jsonEncode([1, 2, 3])', $r); + Assert::notContains('escapeHtml', $r); +}); + +test('|json in attribute uses single quotes', function () { + $r = compile(''); + Assert::contains('jsonEncode', $r); + Assert::contains("foo=\\'", $r); +}); + +test('|json on data attribute', function () { + $r = compile('
');
+	Assert::contains('Tracy\Helpers::jsonEncode($snapshot)', $r);
+	Assert::contains("data-snapshot=\\'", $r);
+	Assert::notContains('escapeHtml', $r);
+});
+
+
+// Attribute expressions
+
+test('regular attr={$var}', function () {
+	$r = compile('
'); + Assert::contains('escapeHtml', $r); + Assert::contains('attr="', $r); +}); + +test('boolean attr (hidden)', function () { + $r = compile(''); + Assert::contains("' hidden'", $r); +}); + +test('class={[...]} list attribute', function () { + $r = compile('
'); + Assert::contains('array_filter', $r); + Assert::contains('implode', $r); +}); + +test('noEscape in attribute (formatSnapshotAttribute)', function () { + $r = compile(''); + Assert::notContains('escapeHtml', $r); +}); + + +// Tags + +test('{varType}', function () { + $r = compile('{varType string $name}'); + Assert::contains('/** @var string $name */', $r); +}); + +test('{use}', function () { + $r = compile('{use Tracy\Helpers}'); + Assert::contains('use Tracy\Helpers;', $r); +}); + +test('{var}', function () { + $r = compile('{var $x = 42}'); + Assert::contains('$x = 42', $r); +}); + +test('{do}', function () { + $r = compile('{do $x = 1}'); + Assert::contains('$x = 1', $r); +}); + +test('{if}/{else}/{/if}', function () { + $r = compile('{if $cond}A{else}B{/if}'); + Assert::contains('if ($cond)', $r); + Assert::contains('else', $r); +}); + +test('{foreach}', function () { + $r = compile('{foreach $items as $item}{$item}{/foreach}'); + Assert::contains('foreach ($items as $item)', $r); +}); + +test('{exitIf}', function () { + $r = compile('{exitIf $done}'); + Assert::contains('if ($done)', $r); + Assert::contains('return;', $r); +}); + +test('{continueIf}', function () { + $r = compile('{foreach $a as $b}{continueIf $b}{/foreach}'); + Assert::contains('continue;', $r); +}); + +test('{define}/{include} block', function () { + $r = compile('{define foo}hello{/define}{include foo}'); + Assert::contains("\$_blocks['foo'] = function", $r); + Assert::contains("\$_blocks['foo']()", $r); +}); + +test('{include file}', function () { + $r = compile("{include 'section.phtml'}"); + Assert::contains("require __DIR__ . '/section.phtml'", $r); +}); + +test('{try}/{rollback}', function () { + $r = compile('{try}{do $x = dangerous()}{rollback}{do $x = fallback()}{/try}'); + Assert::contains('try {', $r); + Assert::contains('catch (\Throwable)', $r); +}); + + +// Output format + +test('starts with setContentType($textMode ? Latte\ContentType::Text : Latte\ContentType::Html); + Assert::same(file_get_contents($phtmlFile), $engine->compile($latteFile)); + }); +} diff --git a/tools/latte-convert/tests/fixtures/kitchen-sink.latte b/tools/latte-convert/tests/fixtures/kitchen-sink.latte new file mode 100644 index 000000000..95d39cd8e --- /dev/null +++ b/tools/latte-convert/tests/fixtures/kitchen-sink.latte @@ -0,0 +1,72 @@ +{varType string $file} +{varType int $line} +{varType int $expanded} +{use Tracy\Helpers} + + +

Queries: {$count}{$totalTime ? foo(' time ms ', " time ms ", '', "") : Anjb-d}, {$name . (string) $foo} {=\JSON_UNESCAPED_SLASHES}

+ +
+ {define foo} + hello + {exitIf true} + {/define} + + {define bar $arg, $arg} + hello + {/define} + + {include foo} + {include for $var1, var} + {include 'section-exception.phtml'} + + {foreach $events as [$connection, $query, $trace, $time, $rows, $error, $command, $explain]} + + + {if $error} + ERROR + {elseif $time !== null}{sprintf('%0.3f', $time * 1000)} + {/if} + + {if $explain}
explain{/if} + {if $trace}
trace{/if} + + + + {Nette\Database\Helpers::dumpSql($query, $connection)|noescape} + + {if $trace} + {substr_replace(Helpers::editorLink($trace[0][file], $trace[0][line]), ' class="nette-DbConnectionPanel-source"', 2, 0)|noescape} + + {foreach $trace as $row} + + + + + {/foreach} +
{isset($row[file]) ? Tracy\Helpers::editorLink($row[file], $row[line])}{$row[class] ?? ''}{$row[type] ?? ''}{$row[function]}()
+ {/if} + + + {$rows} + + {/foreach} + + {if count($events) < $count}

...and more

{/if} +
+ +
+ + +link +
+ +{try} + {do $x = dangerous()} +{rollback} + {do $x = fallback()} +{/try} diff --git a/tools/latte-convert/tests/fixtures/kitchen-sink.phtml b/tools/latte-convert/tests/fixtures/kitchen-sink.phtml new file mode 100644 index 000000000..e0f2760c2 --- /dev/null +++ b/tools/latte-convert/tests/fixtures/kitchen-sink.phtml @@ -0,0 +1,140 @@ + + #tracy-debug td.nette-DbConnectionPanel-sql { background: white !important; overflow-x: auto; max-width: 0; } + #tracy-debug .nette-DbConnectionPanel-source { color: #BBB !important } + #tracy-debug .nette-DbConnectionPanel-explain td { white-space: pre } + + +

Queries: '; +echo Tracy\Helpers::escapeHtml($count) /* pos 11:14 */; +echo Tracy\Helpers::escapeHtml($totalTime ? foo(' time ms ', ' time ms ', '', '') : 'Anjb-d') /* pos 11:22 */; +echo ', '; +echo Tracy\Helpers::escapeHtml($name . (string) $foo) /* pos 11:85 */; +echo ' '; +echo Tracy\Helpers::escapeHtml(\JSON_UNESCAPED_SLASHES) /* pos 11:109 */; +echo '

+ +
+'; +$_blocks['foo'] = function () use (&$_blocks) { + echo ' hello +'; + if (true) /* pos 16:3 */ return; +}; +echo "\n"; +$_blocks['bar'] = function ($arg,$arg) use (&$_blocks) { + echo ' hello +'; +}; +echo "\n"; +$_blocks['foo'](); +$_blocks['for']($var1,var); +require __DIR__ . '/section-exception.phtml'; +echo "\n"; +foreach ($events as [$connection, $query, $trace, $time, $rows, $error, $command, $explain]) /* pos 27:2 */ { + echo ' + +'; + if ($error) /* pos 30:5 */ { + echo ' ERROR + '; + } elseif ($time !== null) /* pos 32:6 */ { + echo Tracy\Helpers::escapeHtml(sprintf('%0.3f', $time * 1000)) /* pos 32:29 */; + echo "\n"; + } + + echo "\n"; + if ($explain) /* pos 35:5 */ { + echo '
explain'; + } + echo "\n"; + if ($trace) /* pos 36:5 */ { + echo '
trace'; + } + echo ' + + + + '; + echo Nette\Database\Helpers::dumpSql($query, $connection) /* pos 40:5 */; + echo ' + +'; + if ($trace) /* pos 42:5 */ { + echo ' '; + echo substr_replace(Helpers::editorLink($trace[0]['file'], $trace[0]['line']), ' class="nette-DbConnectionPanel-source"', 2, 0) /* pos 43:6 */; + echo ' + +'; + foreach ($trace as $row) /* pos 45:7 */ { + echo ' + + + +'; + + } + + echo '
'; + echo isset($row['file']) ? Tracy\Helpers::editorLink($row['file'], $row['line']) : null /* pos 47:13 */; + echo ''; + echo Tracy\Helpers::escapeHtml($row['class'] ?? '') /* pos 48:13 */; + echo Tracy\Helpers::escapeHtml($row['type'] ?? '') /* pos 48:32 */; + echo Tracy\Helpers::escapeHtml($row['function']) /* pos 48:50 */; + echo '()
+'; + } + echo ' + + '; + echo Tracy\Helpers::escapeHtml($rows) /* pos 55:8 */; + echo ' + +'; + +} + +echo "\n"; +if (count($events) < $count) /* pos 59:2 */ { + echo '

...and more

'; +} +echo ' +
+ +
+ + +link +
+ +'; +try { + $x = dangerous() /* pos 69:2 */; +} catch (\Throwable) { + $x = fallback() /* pos 71:2 */; +} diff --git a/tools/open-in-editor/linux/open-editor.sh b/tools/open-in-editor/linux/open-editor.sh index 69e37be83..8cabf2789 100755 --- a/tools/open-in-editor/linux/open-editor.sh +++ b/tools/open-in-editor/linux/open-editor.sh @@ -6,7 +6,7 @@ declare -A mapping # # Visual Studio Code -#editor='code --goto "$FILE":"$LINE"' +#editor='code --goto "$FILE":"$LINE":"$COLUMN"' # Emacs #editor='emacs +$LINE "$FILE"' # gVim @@ -17,11 +17,9 @@ declare -A mapping #editor='pluma +$LINE "$FILE"' # PHPStorm # To enable PHPStorm command-line interface, folow this guide: https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html -#editor='phpstorm --line $LINE "$FILE"' +#editor='phpstorm --line $LINE --column $COLUMN "$FILE"' # VS Codium #editor='codium --goto "$FILE":"$LINE"' -# Visual Studio Code -#editor='code --goto "$FILE":"$LINE"' # # Optionally configure custom mapping here: @@ -56,16 +54,21 @@ action=`echo $url | sed -r "s/$regex/\1/i"` uri_params=`echo $url | sed -r "s/$regex/\2/i"` file=`get_param $uri_params "file"` -line=`get_param $uri_params "line"` +line_param=`get_param $uri_params "line"` search=`get_param $uri_params "search"` replace=`get_param $uri_params "replace"` +# Parse line and column from line parameter (format: "12:5" or just "12") +IFS=':' read -r line column <<< "$line_param" +column="${column:-1}" + # Debug? #echo "action '$action'" #echo "file '$file'" #echo "line '$line'" #echo "search '$search'" #echo "replace '$replace'" +#echo "column '$column'" # Convert URI encoded codes to normal characters (e.g. '%2F' => '/'). printf -v file "${file//%/\\x}" @@ -102,6 +105,7 @@ fi # Format the command according to the selected editor. command="${editor//\$FILE/$file}" command="${command//\$LINE/$line}" +command="${command//\$COLUMN/$column}" # Debug? #echo $command diff --git a/tools/open-in-editor/windows/open-editor.js b/tools/open-in-editor/windows/open-editor.js index ac1093e70..86f699483 100644 --- a/tools/open-in-editor/windows/open-editor.js +++ b/tools/open-in-editor/windows/open-editor.js @@ -1,7 +1,7 @@ var settings = { // PhpStorm - // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% "%file%"', + // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% --column %column% "%file%"', // title: 'PhpStorm', // NetBeans @@ -14,7 +14,7 @@ var settings = { // editor: '"C:\\Program Files\\SciTE\\scite.exe" "-open:%file%" -goto:%line%', // EmEditor - // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line%', + // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line% /cl %column%', // PSPad Editor // editor: '"C:\\Program Files\\PSPad editor\\PSPad.exe" -%line% "%file%"', @@ -26,7 +26,7 @@ var settings = { // editor: '"C:\\Program Files\\Sublime Text 2\\sublime_text.exe" "%file%:%line%"', // Visual Studio Code / VSCodium - // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%"', + // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%:%column%"', mappings: { // '/remotepath': '/localpath' @@ -41,7 +41,7 @@ if (!settings.editor) { } var url = WScript.Arguments(0); -var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?:&search=([^&]*)&replace=([^&]*))?/.exec(url); +var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?::(\d+))?(?:&search=([^&]*)&replace=([^&]*))?/.exec(url); if (!match) { WScript.Echo('Unexpected URI ' + url); WScript.Quit(); @@ -53,8 +53,9 @@ for (var i in match) { var action = match[1]; var file = match[2]; var line = match[3]; -var search = match[4]; -var replace = match[5]; +var column = match[4] || 1; +var search = match[5]; +var replace = match[6]; var shell = new ActiveXObject('WScript.Shell'); var fileSystem = new ActiveXObject('Scripting.FileSystemObject'); @@ -76,7 +77,7 @@ if (action === 'create' && !fileSystem.FileExists(file)) { fileSystem.OpenTextFile(file, 2).Write(lines.join('\n')); } -var command = settings.editor.replace(/%line%/, line).replace(/%file%/, file); +var command = settings.editor.replace(/%line%/, line).replace(/%column%/, column).replace(/%file%/, file); shell.Exec(command); if (settings.title) {