Bug 1596458 part 2. Fix the innerHTML setter for shadow DOM cases in XML to supply the right context. r=emilio
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 15 Nov 2019 12:45:40 +0000
changeset 503057 4460bd1e6ccb6e487271e8cb6881a06410e798d3
parent 503056 29897d3c1b7f3e2e30ff3b6bf996a6903032ced8
child 503058 426df2f3f4a8211300aa3a94470e5328d0f1f056
push id36825
push usercbrindusan@mozilla.com
push dateWed, 20 Nov 2019 21:52:17 +0000
treeherdermozilla-central@e76dbab2aea8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1596458
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1596458 part 2. Fix the innerHTML setter for shadow DOM cases in XML to supply the right context. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D53077
dom/base/FragmentOrElement.cpp
dom/webidl/ShadowRoot.webidl
testing/web-platform/tests/shadow-dom/innerHTML-setter.xhtml
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -1956,44 +1956,45 @@ void FragmentOrElement::SetInnerHTMLInte
   nsAutoMutationBatch mb(target, true, false);
   while (target->HasChildren()) {
     target->RemoveChildNode(target->GetFirstChild(), true);
   }
   mb.RemovalDone();
 
   nsAutoScriptLoaderDisabler sld(doc);
 
-  nsAtom* contextLocalName = NodeInfo()->NameAtom();
-  int32_t contextNameSpaceID = GetNameSpaceID();
-
+  FragmentOrElement* parseContext = this;
   if (ShadowRoot* shadowRoot = ShadowRoot::FromNode(this)) {
-    // Fix up the context to be the host of the ShadowRoot.
-    contextLocalName = shadowRoot->GetHost()->NodeInfo()->NameAtom();
-    contextNameSpaceID = shadowRoot->GetHost()->GetNameSpaceID();
+    // Fix up the context to be the host of the ShadowRoot.  See
+    // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml setter step 1.
+    parseContext = shadowRoot->GetHost();
   }
 
   if (doc->IsHTMLDocument()) {
+    nsAtom* contextLocalName = parseContext->NodeInfo()->NameAtom();
+    int32_t contextNameSpaceID = parseContext->GetNameSpaceID();
+
     int32_t oldChildCount = target->GetChildCount();
     aError = nsContentUtils::ParseFragmentHTML(
         aInnerHTML, target, contextLocalName, contextNameSpaceID,
         doc->GetCompatibilityMode() == eCompatibility_NavQuirks, true);
     mb.NodesAdded();
     // HTML5 parser has notified, but not fired mutation events.
     nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
                                                        oldChildCount);
   } else {
     RefPtr<DocumentFragment> df = nsContentUtils::CreateContextualFragment(
-        target, aInnerHTML, true, aError);
+        parseContext, aInnerHTML, true, aError);
     if (!aError.Failed()) {
       // Suppress assertion about node removal mutation events that can't have
       // listeners anyway, because no one has had the chance to register
       // mutation listeners on the fragment that comes from the parser.
       nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
 
-      static_cast<nsINode*>(target)->AppendChild(*df, aError);
+      target->AppendChild(*df, aError);
       mb.NodesAdded();
     }
   }
 }
 
 void FragmentOrElement::FireNodeRemovedForChildren() {
   Document* doc = OwnerDoc();
   // Optimize the common case
--- a/dom/webidl/ShadowRoot.webidl
+++ b/dom/webidl/ShadowRoot.webidl
@@ -21,16 +21,17 @@ enum ShadowRootMode {
 interface ShadowRoot : DocumentFragment
 {
   // Shadow DOM v1
   readonly attribute ShadowRootMode mode;
   readonly attribute Element host;
 
   Element? getElementById(DOMString elementId);
 
+  // https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin
   [CEReactions, SetterThrows]
   attribute [TreatNullAs=EmptyString] DOMString innerHTML;
 
   // When JS invokes importNode or createElement, the binding code needs to
   // create a reflector, and so invoking those methods directly on the content
   // document would cause the reflector to be created in the content scope,
   // at which point it would be difficult to move into the UA Widget scope.
   // As such, these methods allow UA widget code to simultaneously create nodes
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/innerHTML-setter.xhtml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:bar="bar">
+  <head>
+    <title>Test for Shadow DOM innerHTML setter in XML</title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script>
+      <![CDATA[
+        // We define our custom elements lazily so we don't mess
+        // with the DOM during parsing.
+        customElements.define("custom-el-1",
+          class extends HTMLElement {
+            constructor() {
+              super();
+              this.attachShadow({ mode: "open" });
+              try { this.shadowRoot.innerHTML = "<span/>"; } catch (e) {}
+            }
+          });
+        function defineElements() {
+          // We define our custom elements whose behavior involves
+          // ancestors of our parent lazily, because otherwise the
+          // constructor runs before the element is in the DOM and has
+          // the ancestors set up.
+          customElements.define("custom-el-2",
+            class extends HTMLElement {
+              constructor() {
+                super();
+                this.attachShadow({ mode: "open" });
+                try { this.shadowRoot.innerHTML = "<span/>"; } catch (e) {}
+              }
+            });
+          customElements.define("custom-el-with-prefix",
+            class extends HTMLElement {
+              constructor() {
+                super();
+                this.attachShadow({ mode: "open" });
+                try {
+                  this.shadowRoot.innerHTML = "<bar:span/>";
+                } catch (e) {
+                  // Test will fail due to us not having the kid
+                }
+              }
+            });
+        }
+      ]]>
+    </script>
+  </head>
+  <body>
+    <custom-el-1 id="htmlDefault"/>
+    <span xmlns="foo" xmlns:html="http://www.w3.org/1999/xhtml">
+      <html:custom-el-2 id="fooDefault"/>
+    </span>
+    <custom-el-with-prefix id="prefixTest"/>
+    <script>
+      <![CDATA[
+        const htmlNS = "http://www.w3.org/1999/xhtml";
+        test(() => {
+          var el = document.getElementById("htmlDefault");
+          var kid = el.shadowRoot.firstChild;
+          assert_equals(kid.namespaceURI, htmlNS,
+                        "Kid's namespace should be our default");
+        }, "InnerHTML behavior on custom element in default XHTML namespace");
+
+        test(defineElements, "Setting up the custom elements");
+        test(() => {
+          var el = document.getElementById("fooDefault");
+          var kid = el.shadowRoot.firstChild;
+          assert_equals(kid.namespaceURI, "foo",
+                        "Kid's namespace should be our default");
+        }, "InnerHTML behavior on custom element in default 'foo' namespace");
+
+        test(() => {
+          var el = document.getElementById("prefixTest");
+          var kid = el.shadowRoot.firstChild;
+          assert_equals(kid.namespaceURI, "bar",
+                        "Kid's namespace should be based on ancestor prefix");
+        }, "InnerHTML behavior with prefixes on custom element");
+      ]]>
+    </script>
+  </body>
+</html>