From a6e0a7317864c945ca55c62beefdefb3c2ae095b Mon Sep 17 00:00:00 2001 From: Adrian Jones Date: Mon, 26 Oct 2026 14:37:17 -0700 Subject: [PATCH] Bar, Dumper, BlueScreen: markup and scripts pass through a 'tracy' Trusted Types policy (#616) Pages that enforce require-trusted-types-for 'script' refuse plain strings at innerHTML, insertAdjacentHTML, script src, and script text, which threw in Debug.init() and left no bar at all. helpers.js, which runs first in all three bundles, creates the policy once per window; without Trusted Types support, or when the page's trusted-types directive doesn't allow the name, the strings pass through unchanged as before. --- src/Tracy/Bar/assets/bar.js | 19 ++++++++++--------- src/Tracy/BlueScreen/assets/bluescreen.js | 2 +- src/Tracy/Dumper/assets/dumper.js | 2 +- src/Tracy/assets/helpers.js | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Tracy/Bar/assets/bar.js b/src/Tracy/Bar/assets/bar.js index f7f1dc546..d64c677cd 100644 --- a/src/Tracy/Bar/assets/bar.js +++ b/src/Tracy/Bar/assets/bar.js @@ -41,7 +41,8 @@ class Panel { let elem = this.elem; this.init = function () {}; - elem.innerHTML = elem.tracyContent = elem.dataset.tracyContent; + elem.tracyContent = elem.dataset.tracyContent; + elem.innerHTML = Tracy.trusted.html(elem.tracyContent); delete elem.dataset.tracyContent; Tracy.Dumper.init(Debug.shadow); evalScripts(elem); @@ -164,7 +165,7 @@ class Panel { doc.body.id = 'tracy-debug'; let script = doc.createElement('script'); - script.src = baseUrl + '_tracy_bar=js&XDEBUG_SESSION_STOP=1'; + script.src = Tracy.trusted.scriptURL(baseUrl + '_tracy_bar=js&XDEBUG_SESSION_STOP=1'); script.async = true; script.addEventListener('load', () => { win.Tracy.adoptStyleSheets(doc, ['shared', 'bar']); @@ -173,10 +174,10 @@ class Panel { doc.head.appendChild(script); let meta = this.elem.parentElement.lastElementChild; - doc.body.innerHTML = '
' + doc.body.innerHTML = Tracy.trusted.html('
' + '
' + this.elem.tracyContent + '
' + meta.outerHTML - + '
'; + + '
'); evalScripts(doc.body); if (this.elem.querySelector('h1')) { doc.title = this.elem.querySelector('h1').textContent; @@ -417,7 +418,7 @@ class Debug { // #tracy-debug wrapper is kept for backward compatibility of third-party panel CSS Debug.layer = document.createElement('tracy-div'); Debug.layer.setAttribute('id', 'tracy-debug'); - Debug.layer.innerHTML = content; + Debug.layer.innerHTML = Tracy.trusted.html(content); shadow.appendChild(Debug.layer); (document.body || document.documentElement).appendChild(host); @@ -467,9 +468,9 @@ class Debug { }); } - Debug.layer.insertAdjacentHTML('beforeend', content.panels); + Debug.layer.insertAdjacentHTML('beforeend', Tracy.trusted.html(content.panels)); evalScripts(Debug.layer); - Debug.bar.elem.insertAdjacentHTML('beforeend', content.bar); + Debug.bar.elem.insertAdjacentHTML('beforeend', Tracy.trusted.html(content.bar)); let ajaxBar = Debug.bar.elem.querySelector('.tracy-row:last-child'); Debug.shadow.querySelectorAll('.tracy-panel').forEach((panel) => { @@ -556,7 +557,7 @@ class Debug { Debug.scriptElem.remove(); } Debug.scriptElem = document.createElement('script'); - Debug.scriptElem.src = url; + Debug.scriptElem.src = Tracy.trusted.scriptURL(url); (document.body || document.documentElement).appendChild(Debug.scriptElem); } } @@ -567,7 +568,7 @@ function evalScripts(elem) { if ((!script.hasAttribute('type') || script.type === 'text/javascript' || script.type === 'application/javascript') && !script.tracyEvaluated) { let document = script.ownerDocument; let dolly = document.createElement('script'); - dolly.textContent = script.textContent; + dolly.textContent = Tracy.trusted.script(script.textContent); (document.body || document.documentElement).appendChild(dolly); dolly.remove(); script.tracyEvaluated = true; diff --git a/src/Tracy/BlueScreen/assets/bluescreen.js b/src/Tracy/BlueScreen/assets/bluescreen.js index 7249b0dd3..ec2a302ca 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.js +++ b/src/Tracy/BlueScreen/assets/bluescreen.js @@ -105,7 +105,7 @@ class BlueScreen { if (host) { host.remove(); } - document.body.insertAdjacentHTML('beforeend', content); + document.body.insertAdjacentHTML('beforeend', Tracy.trusted.html(content)); BlueScreen.init(true); } } diff --git a/src/Tracy/Dumper/assets/dumper.js b/src/Tracy/Dumper/assets/dumper.js index e6e792f3c..2e0fabdc1 100644 --- a/src/Tracy/Dumper/assets/dumper.js +++ b/src/Tracy/Dumper/assets/dumper.js @@ -341,7 +341,7 @@ function createEl(el, attrs, content) { } if (content && content.html !== undefined) { - el.innerHTML = content.html; + el.innerHTML = Tracy.trusted.html(content.html); return el; } diff --git a/src/Tracy/assets/helpers.js b/src/Tracy/assets/helpers.js index fdb161f2e..3aa715ae4 100644 --- a/src/Tracy/assets/helpers.js +++ b/src/Tracy/assets/helpers.js @@ -18,6 +18,26 @@ let Tracy = window.Tracy = window.Tracy || {}; // returns the real event target even when the event crossed a shadow DOM boundary Tracy.retarget = (e) => e.composedPath()[0] ?? e.target; +// passes Tracy's own markup and scripts to sinks that a page enforcing Trusted Types +// (require-trusted-types-for 'script') refuses plain strings at +Tracy.trusted = Tracy.trusted || (() => { + let policy; + try { + policy = window.trustedTypes?.createPolicy('tracy', { + createHTML: (s) => s, + createScript: (s) => s, + createScriptURL: (s) => s, + }); + } catch { + // the page's trusted-types directive doesn't allow the name + } + return { + html: (s) => policy ? policy.createHTML(s) : s, + script: (s) => policy ? policy.createScript(s) : s, + scriptURL: (s) => policy ? policy.createScriptURL(s) : s, + }; +})(); + // registry of CSS sources filled by the loader (DeferredContent, Dumper::renderAssets) Tracy.css = Tracy.css || {};