Bug 1590526 - Temporarily allow node adoption across different docGroups for the content/content case r=smaug,zombie a=pascalc
authorSean Feng <sefeng@mozilla.com>
Thu, 24 Oct 2019 20:56:43 +0000
changeset 559741 1542e80327c2f1fe04d399f3da7c6b4948bd283a
parent 559740 6e24f27cc398f16f4ad43608e704083f798256d6
child 559742 8960870ce87122be50ded0dd8a964440a775309a
push id12210
push userrgurzau@mozilla.com
push dateSun, 27 Oct 2019 12:53:10 +0000
treeherdermozilla-beta@0dfa20097d27 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, zombie, pascalc
bugs1590526
milestone71.0
Bug 1590526 - Temporarily allow node adoption across different docGroups for the content/content case r=smaug,zombie a=pascalc As web extensions rely on this node adoption between content to content documents, we want to continue allowing this capability to work for now. Differential Revision: https://phabricator.services.mozilla.com/D50348
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_contentscript_cross_docGroup_adoption.js
browser/components/extensions/test/browser/browser_ext_contentscript_cross_docGroup_adoption_xhr.js
dom/base/nsNodeUtils.h
dom/tests/mochitest/general/chrome.ini
dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xul
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -295,8 +295,10 @@ tags = fullscreen
 [browser_ext_windows_incognito.js]
 [browser_ext_windows_remove.js]
 [browser_ext_windows_size.js]
 skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
 [browser_ext_windows_update.js]
 skip-if = (verify && (os == 'mac'))
 tags = fullscreen
 [browser_ext_contentscript_animate.js]
+[browser_ext_contentscript_cross_docGroup_adoption.js]
+[browser_ext_contentscript_cross_docGroup_adoption_xhr.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_cross_docGroup_adoption.js
@@ -0,0 +1,58 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function test_cross_docGroup_adoption() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser,
+    "http://example.com/"
+  );
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      content_scripts: [
+        {
+          matches: ["http://example.com/"],
+          js: ["content-script.js"],
+        },
+      ],
+    },
+
+    files: {
+      "current.html": "<html>data</html>",
+      "content-script.js": function() {
+        let iframe = document.createElement("iframe");
+        iframe.src = browser.extension.getURL("current.html");
+        document.body.appendChild(iframe);
+
+        iframe.addEventListener(
+          "load",
+          () => {
+            let parser = new DOMParser();
+            let bold = parser.parseFromString(
+              "<b>NodeAdopted</b>",
+              "text/html"
+            );
+            let doc = iframe.contentDocument;
+
+            let node = document.adoptNode(bold.documentElement);
+            doc.replaceChild(node, doc.documentElement);
+
+            const expected =
+              "<html><head></head><body><b>NodeAdopted</b></body></html>";
+            browser.test.assertEq(expected, doc.documentElement.outerHTML);
+
+            browser.test.notifyPass("nodeAdopted");
+          },
+          { once: true }
+        );
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("nodeAdopted");
+  await extension.unload();
+
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_cross_docGroup_adoption_xhr.js
@@ -0,0 +1,51 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function test_cross_docGroup_adoption() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser,
+    "http://example.com/"
+  );
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      content_scripts: [
+        {
+          matches: ["http://example.com/"],
+          js: ["content-script.js"],
+        },
+      ],
+    },
+
+    files: {
+      "blank.html": "<html>data</html>",
+      "content-script.js": function() {
+        let xhr = new XMLHttpRequest();
+        xhr.responseType = "document";
+        xhr.open("GET", browser.extension.getURL("blank.html"));
+
+        xhr.onload = function() {
+          let doc = xhr.response;
+          try {
+            let node = doc.body.cloneNode(true);
+            document.body.appendChild(node);
+            browser.test.notifyPass("nodeAdopted");
+          } catch (SecurityError) {
+            browser.test.assertTrue(
+              false,
+              "The above node adoption should not fail"
+            );
+          }
+        };
+        xhr.send();
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("nodeAdopted");
+  await extension.unload();
+
+  BrowserTestUtils.removeTab(tab);
+});
--- a/dom/base/nsNodeUtils.h
+++ b/dom/base/nsNodeUtils.h
@@ -8,16 +8,17 @@
 #define nsNodeUtils_h___
 
 #include "mozilla/Maybe.h"
 #include "nsIContent.h"  // for use in inline function (ParentChainChanged)
 #include "nsIMutationObserver.h"  // for use in inline function (ParentChainChanged)
 #include "mozilla/dom/Document.h"
 #include "js/TypeDecls.h"
 #include "nsCOMArray.h"
+#include "nsContentUtils.h"
 
 struct CharacterDataChangeInfo;
 template <class E>
 class nsCOMArray;
 class nsCycleCollectionTraversalCallback;
 namespace mozilla {
 struct NonOwningAnimationTarget;
 class ErrorResult;
@@ -201,23 +202,30 @@ class nsNodeUtils {
    *                             descendants) with properties.
    * @param aError The error, if any.
    */
   static void Adopt(nsINode* aNode, nsNodeInfoManager* aNewNodeInfoManager,
                     JS::Handle<JSObject*> aReparentScope,
                     nsCOMArray<nsINode>& aNodesWithProperties,
                     mozilla::ErrorResult& aError) {
     if (aNode && aNewNodeInfoManager) {
-      mozilla::dom::Document* newDoc = aNewNodeInfoManager->GetDocument();
-      mozilla::dom::Document* oldDoc = aNode->OwnerDoc();
-      if (newDoc && oldDoc &&
-          (oldDoc->GetDocGroup() != newDoc->GetDocGroup())) {
-        MOZ_ASSERT(false, "Cross docGroup adoption is not allowed");
-        aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
-        return;
+      mozilla::dom::Document* afterAdoptDoc =
+          aNewNodeInfoManager->GetDocument();
+      mozilla::dom::Document* beforeAdoptDoc = aNode->OwnerDoc();
+
+      if (afterAdoptDoc && beforeAdoptDoc &&
+          (afterAdoptDoc->GetDocGroup() != beforeAdoptDoc->GetDocGroup())) {
+        // This is a temporary solution for Bug 1590526 to only limit
+        // the restriction to chrome level documents because web extensions
+        // rely on content to content node adoption.
+        if (nsContentUtils::IsChromeDoc(afterAdoptDoc) ||
+            nsContentUtils::IsChromeDoc(beforeAdoptDoc)) {
+          aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+          return;
+        }
       }
     }
 
     // Just need to store the return value of CloneAndAdopt in a
     // temporary nsCOMPtr to make sure we release it.
     nsCOMPtr<nsINode> node =
         CloneAndAdopt(aNode, false, true, aNewNodeInfoManager, aReparentScope,
                       &aNodesWithProperties, nullptr, aError);
--- a/dom/tests/mochitest/general/chrome.ini
+++ b/dom/tests/mochitest/general/chrome.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 skip-if = os == 'android'
 
 [test_innerScreen.xul]
 [test_offsets.xul]
 support-files = test_offsets.css test_offsets.js
 skip-if = (os == "mac" && debug) #leaks Bug 1571583
 [test_spacetopagedown.html]
+[test_nodeAdoption_chrome_boundary.xul]
 [test_focusrings.xul]
 support-files = file_focusrings.html
 skip-if = toolkit == 'android' #TIMED_OUT
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_nodeAdoption_chrome_boundary.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Cross chrome and content node adoption test"
+  onload="setTimeout(runTest, 0);"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <browser xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="content" type="content" src="about:blank"/>
+
+<script>
+
+SimpleTest.waitForExplicitFinish();
+function runTest()
+{
+  let browserElement = document.getElementById("content");
+  try {
+    document.adoptNode(browserElement.contentDocument.documentElement);
+    SimpleTest.ok(false, "Cross chrome and content node adoption should fail");
+  } catch (SecurityError) {
+    SimpleTest.ok(true, "Cross chrome and content node adoption fails as expected");
+  }
+  SimpleTest.finish();
+}
+</script>
+</window>
+