diff --git a/test/web_interface/test_style_editor_takeover.py b/test/web_interface/test_style_editor_takeover.py index 4831fb97..00ea9791 100644 --- a/test/web_interface/test_style_editor_takeover.py +++ b/test/web_interface/test_style_editor_takeover.py @@ -88,6 +88,16 @@ class TestItTakesOverOnlyItsOwnBlocks: entirely styling looks exactly as it did before.""" assert "!fallback.querySelector('[data-child-key]')" in source + def test_the_owned_key_is_escaped_before_it_becomes_a_selector(self, source): + """ownedKeys comes back off a dataset attribute, not a literal -- + splicing it into '[data-child-key="' + k + '"]' unescaped breaks (or + is steerable) on a key containing a quote or backslash. The rest of + this codebase (plugin-file-manager.js, app-shell.js) always routes a + dynamic value through CSS.escape() before it lands in a selector.""" + assert "CSS.escape(k)" in source, ( + "an owned key must be CSS.escape()'d before being interpolated " + "into the data-child-key attribute selector") + class TestTheSchemaSaysWhichBlocksAreStyling: def test_adopted_blocks_are_marked_and_others_are_not(self): diff --git a/web_interface/templates/v3/partials/plugin_config.html b/web_interface/templates/v3/partials/plugin_config.html index 8d4752a1..ae3263f4 100644 --- a/web_interface/templates/v3/partials/plugin_config.html +++ b/web_interface/templates/v3/partials/plugin_config.html @@ -147,8 +147,13 @@ var owned = (container.dataset.ownedKeys || '') .split(',').filter(function (k) { return k; }); owned.forEach(function (k) { + // CSS.escape: k is a schema property key read + // back off dataset.ownedKeys, not a literal -- + // building the attribute selector by raw + // concatenation would break (or be steerable) + // on a key containing a quote or backslash. var node = fallback.querySelector( - '[data-child-key="' + k + '"]'); + '[data-child-key="' + CSS.escape(k) + '"]'); if (node) { node.remove(); } }); // Nothing left but the section heading, or a widget