fix(ui): make mega menus work under strict CSP
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.8.33",
|
||||
"version": "0.8.34",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -90,7 +90,7 @@ const CONTROLLER_LOADER = String.raw`
|
||||
if (!componentControllerPromise) {
|
||||
componentControllerPromise = new Promise(function (resolve, reject) {
|
||||
var script = document.createElement("script");
|
||||
script.src = componentControllerUrl;
|
||||
script.src = trustedScriptURL(componentControllerUrl);
|
||||
script.defer = true;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
|
||||
@@ -26,6 +26,40 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
var behaviorObserver;
|
||||
var clientModuleCache = new Map();
|
||||
|
||||
/*
|
||||
* Production enables require-trusted-types-for 'script'. Every HTML write
|
||||
* below uses compiler-emitted template markup (or the explicit data-html
|
||||
* escape hatch), and controller URLs are generated by the framework. Route
|
||||
* those known sinks through the policy allowed by the default security
|
||||
* preset instead of letting the browser reject hydration midway through a
|
||||
* state update.
|
||||
*/
|
||||
var wrnexusTrustedTypesPolicy = null;
|
||||
if (window.trustedTypes && typeof window.trustedTypes.createPolicy === "function") {
|
||||
try {
|
||||
wrnexusTrustedTypesPolicy = window.trustedTypes.createPolicy("wrnexus", {
|
||||
createHTML: function (value) { return value; },
|
||||
createScriptURL: function (value) { return value; }
|
||||
});
|
||||
} catch (_) {
|
||||
wrnexusTrustedTypesPolicy = window.trustedTypes.defaultPolicy || null;
|
||||
}
|
||||
}
|
||||
|
||||
function trustedHTML(value) {
|
||||
var source = value == null ? "" : String(value);
|
||||
return wrnexusTrustedTypesPolicy
|
||||
? wrnexusTrustedTypesPolicy.createHTML(source)
|
||||
: source;
|
||||
}
|
||||
|
||||
function trustedScriptURL(value) {
|
||||
var source = value == null ? "" : String(value);
|
||||
return wrnexusTrustedTypesPolicy
|
||||
? wrnexusTrustedTypesPolicy.createScriptURL(source)
|
||||
: source;
|
||||
}
|
||||
|
||||
/*
|
||||
* Two builtin chains the runtime reaches for constantly. Aliasing them is
|
||||
* not only shorter: hasOwn keeps prototype keys from reading as data, and
|
||||
@@ -1818,7 +1852,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
var next = markup == null ? "" : String(markup);
|
||||
|
||||
if (htmlNode.innerHTML !== next) {
|
||||
htmlNode.innerHTML = next;
|
||||
htmlNode.innerHTML = trustedHTML(next);
|
||||
}
|
||||
});
|
||||
runHtml();
|
||||
@@ -2002,7 +2036,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
}
|
||||
|
||||
function clearControlContent() {
|
||||
if (!rangeEnd) { block.innerHTML = ""; return; }
|
||||
if (!rangeEnd) { block.innerHTML = trustedHTML(""); return; }
|
||||
while (block.nextSibling && block.nextSibling !== rangeEnd) {
|
||||
block.parentNode.removeChild(block.nextSibling);
|
||||
}
|
||||
@@ -2010,7 +2044,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
|
||||
function appendControlContent(markup, locals) {
|
||||
var template = document.createElement("template");
|
||||
template.innerHTML = markup || "";
|
||||
template.innerHTML = trustedHTML(markup || "");
|
||||
var fragment = template.content;
|
||||
var elements = toArray(fragment.childNodes).filter(function (node) {
|
||||
return node.nodeType === 1;
|
||||
@@ -2399,7 +2433,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
var markup = value == null ? "" : String(value);
|
||||
|
||||
if (node.innerHTML !== markup) {
|
||||
node.innerHTML = markup;
|
||||
node.innerHTML = trustedHTML(markup);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,13 @@ test("split runtime hydrates a controller only from the controller asset", () =>
|
||||
expect(win.document.activeElement).toBe(buttons[1]);
|
||||
});
|
||||
|
||||
test("production hydration uses Trusted Types for HTML and controller URLs", () => {
|
||||
const runtime = getReactiveRuntime();
|
||||
expect(runtime).toContain('createPolicy("wrnexus"');
|
||||
expect(runtime).toContain('template.innerHTML = trustedHTML(markup || "")');
|
||||
expect(runtime).toContain("script.src = trustedScriptURL(componentControllerUrl)");
|
||||
});
|
||||
|
||||
test("a page without controller markers does not request the controller asset", () => {
|
||||
const win = mount(
|
||||
`<main data-scope="count: 1"><span>{count}</span></main>`,
|
||||
|
||||
Reference in New Issue
Block a user