Bug 1439153 - Make WebExtensions work with Shadow DOM/WebComponents, r=kmag
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 14 Jul 2018 05:26:15 +0300
changeset 426664 62cb6ad78b9b66055c325e43924dc6b5b9818d36
parent 426663 a7c110500e40481df5db2a47db9c7408c96f8359
child 426665 2bc6298fa815fd289d272f7081e586f9279db08e
push id34278
push useraciure@mozilla.com
push dateSun, 15 Jul 2018 09:53:15 +0000
treeherdermozilla-central@2a8f94a45fd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1439153
milestone63.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 1439153 - Make WebExtensions work with Shadow DOM/WebComponents, r=kmag
caps/BasePrincipal.cpp
caps/BasePrincipal.h
caps/nsIPrincipal.idl
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/webidl/Element.webidl
toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html
toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js
toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -278,16 +278,23 @@ BasePrincipal::GetIsExpandedPrincipal(bo
 NS_IMETHODIMP
 BasePrincipal::GetIsSystemPrincipal(bool* aResult)
 {
   *aResult = Kind() == eSystemPrincipal;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult)
+{
+  *aResult = AddonPolicy() || ContentScriptAddonPolicy();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 BasePrincipal::GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
 {
   if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -74,16 +74,17 @@ public:
   NS_IMETHOD EnsureCSP(nsIDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetPreloadCsp(nsIContentSecurityPolicy** aPreloadCSP) override;
   NS_IMETHOD EnsurePreloadCSP(nsIDocument* aDocument, nsIContentSecurityPolicy** aCSP) override;
   NS_IMETHOD GetCspJSON(nsAString& outCSPinJSON) override;
   NS_IMETHOD GetIsNullPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override;
   NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
+  NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
   NS_IMETHOD GetAppId(uint32_t* aAppId) final;
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final;
   NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
   NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
 
   virtual bool AddonHasPermission(const nsAtom* aPerm);
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -315,16 +315,22 @@ interface nsIPrincipal : nsISerializable
      * Returns true iff this is an expanded principal.
      */
     [infallible] readonly attribute boolean isExpandedPrincipal;
 
     /**
      * Returns true iff this is the system principal.
      */
     [infallible] readonly attribute boolean isSystemPrincipal;
+
+    /**
+     * Returns true iff the principal is either an addon principal or
+     * an expanded principal, which contains at least one addon principal.
+     */
+    [infallible] readonly attribute boolean isAddonOrExpandedAddonPrincipal;
 };
 
 /**
  * If SystemPrincipal is too risky to use, but we want a principal to access
  * more than one origin, ExpandedPrincipals letting us define an array of
  * principals it subsumes. So script with an ExpandedPrincipals will gain
  * same origin access when at least one of its principals it contains gained
  * sameorigin acccess. An ExpandedPrincipal will be subsumed by the system
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2607,16 +2607,31 @@ nsDocument::IsShadowDOMEnabled(JSContext
   nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
   if (!doc) {
     return false;
   }
 
   return doc->IsShadowDOMEnabled();
 }
 
+// static
+bool
+nsDocument::IsShadowDOMEnabledAndCallerIsChromeOrAddon(JSContext* aCx,
+                                                       JSObject* aObject)
+{
+  if (IsShadowDOMEnabled(aCx, aObject)) {
+    nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
+    return principal &&
+      (nsContentUtils::IsSystemPrincipal(principal) ||
+       principal->GetIsAddonOrExpandedAddonPrincipal());
+  }
+
+  return false;
+}
+
 bool
 nsDocument::IsShadowDOMEnabled(const nsINode* aNode)
 {
   return aNode->OwnerDoc()->IsShadowDOMEnabled();
 }
 
 nsresult
 nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -191,16 +191,18 @@ public:
 
   // for radio group
   nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
   nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
 
   // Check whether shadow DOM is enabled for aGlobal.
   static bool IsShadowDOMEnabled(JSContext* aCx, JSObject* aGlobal);
   // Check whether shadow DOM is enabled for the document this node belongs to.
+  // Same as above, but also checks that the caller is either chrome or some addon.
+  static bool IsShadowDOMEnabledAndCallerIsChromeOrAddon(JSContext* aCx, JSObject* aObject);
   static bool IsShadowDOMEnabled(const nsINode* aNode);
 
 public:
   using mozilla::dom::DocumentOrShadowRoot::GetElementById;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagName;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagNameNS;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByClassName;
 
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -256,17 +256,17 @@ dictionary ShadowRootInit {
 // https://dom.spec.whatwg.org/#element
 partial interface Element {
   // Shadow DOM v1
   [Throws, Func="nsDocument::IsShadowDOMEnabled"]
   ShadowRoot attachShadow(ShadowRootInit shadowRootInitDict);
   [BinaryName="shadowRootByMode", Func="nsDocument::IsShadowDOMEnabled"]
   readonly attribute ShadowRoot? shadowRoot;
 
-  [ChromeOnly, Func="nsDocument::IsShadowDOMEnabled", BinaryName="shadowRoot"]
+  [Func="nsDocument::IsShadowDOMEnabledAndCallerIsChromeOrAddon", BinaryName="shadowRoot"]
   readonly attribute ShadowRoot? openOrClosedShadowRoot;
 
   [BinaryName="assignedSlotByMode", Func="nsDocument::IsShadowDOMEnabled"]
   readonly attribute HTMLSlotElement? assignedSlot;
 
   [ChromeOnly, BinaryName="assignedSlot", Func="nsDocument::IsShadowDOMEnabled"]
   readonly attribute HTMLSlotElement? openOrClosedAssignedSlot;
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/data/file_shadowdom.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<div id="host">host</div>
+<script>
+  document.getElementById("host").attachShadow({mode: "closed"});
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_shadowdom.js
@@ -0,0 +1,65 @@
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "Preferences",
+                               "resource://gre/modules/Preferences.jsm");
+
+// ExtensionContent.jsm needs to know when it's running from xpcshell,
+// to use the right timeout for content scripts executed at document_idle.
+ExtensionTestUtils.mockAppInfo();
+
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
+
+add_task(async function test_contentscript_shadowDOM() {
+  const PREFS = {
+    "dom.webcomponents.shadowdom.enabled": true
+  };
+
+  // Set prefs to our initial values.
+  for (let pref in PREFS) {
+    Preferences.set(pref, PREFS[pref]);
+  }
+
+  registerCleanupFunction(() => {
+    // Reset the prefs.
+    for (let pref in PREFS) {
+      Preferences.reset(pref);
+    }
+  });
+
+  function backgroundScript() {
+    browser.test.assertTrue("openOrClosedShadowRoot" in document.documentElement,
+                            "Should have openOrClosedShadowRoot in Element in background script.");
+  }
+
+  function contentScript() {
+    var host = document.getElementById("host");
+    browser.test.assertTrue("openOrClosedShadowRoot" in host, "Should have openOrClosedShadowRoot in Element.");
+    var shadowRoot = host.openOrClosedShadowRoot;
+    browser.test.assertEq(shadowRoot.mode, "closed", "Should have closed ShadowRoot.");
+    browser.test.sendMessage("contentScript");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      content_scripts: [{
+        "matches": ["http://*/*/file_shadowdom.html"],
+        "js": ["content_script.js"],
+      }],
+    },
+    background: backgroundScript,
+    files: {
+      "content_script.js": contentScript,
+    },
+  });
+
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_shadowdom.html`);
+  await extension.awaitMessage("contentScript");
+
+  await contentPage.close();
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-content.ini
@@ -5,8 +5,9 @@ skip-if = os == "android" || (os == "win
 [test_ext_contentscript_scriptCreated.js]
 skip-if = debug # Bug 1407501
 [test_ext_contentscript_triggeringPrincipal.js]
 skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 1438796
 [test_ext_contentscript_xrays.js]
 [test_ext_contentScripts_register.js]
 skip-if = os == "android"
 [test_ext_adoption_with_xrays.js]
+[test_ext_shadowdom.js]