Bug 1068412: Forward dynamically registered resource URI mappings to content processes. r=billm
☠☠ backed out by b1b103778f93 ☠ ☠
authorDave Townsend <dtownsend@oxymoronical.com>
Mon, 06 Oct 2014 12:42:12 -0700
changeset 232280 f4953072c20f75a7257538f990a232abfadc4354
parent 232279 8f1758ce53f05216f35ab9720d4db3782623225e
child 232281 b56f9bccea925bbf1189b3e218599eebfd094596
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1068412
milestone35.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 1068412: Forward dynamically registered resource URI mappings to content processes. r=billm
chrome/RegistryMessageUtils.h
chrome/nsChromeRegistryChrome.cpp
dom/ipc/ContentChild.cpp
dom/ipc/PContent.ipdl
netwerk/protocol/res/nsResProtocolHandler.cpp
netwerk/test/browser/browser.ini
netwerk/test/browser/browser_child_resource.js
netwerk/test/browser/dummy.html
--- a/chrome/RegistryMessageUtils.h
+++ b/chrome/RegistryMessageUtils.h
@@ -38,16 +38,22 @@ struct ChromePackage
            flags == rhs.flags;
   }
 };
 
 struct ResourceMapping
 {
   nsCString resource;
   SerializedURI resolvedURI;
+
+  bool operator ==(const ResourceMapping& rhs) const
+  {
+    return resource.Equals(rhs.resource) &&
+           resolvedURI == rhs.resolvedURI;
+  }
 };
 
 struct OverrideMapping
 {
   SerializedURI originalURI;
   SerializedURI overrideURI;
 
   bool operator==(const OverrideMapping& rhs) const
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -448,27 +448,31 @@ nsChromeRegistryChrome::SendRegisteredCh
   InfallibleTArray<ResourceMapping> resources;
   InfallibleTArray<OverrideMapping> overrides;
 
   EnumerationArgs args = {
     packages, mSelectedLocale, mSelectedSkin
   };
   mPackagesHash.EnumerateRead(CollectPackages, &args);
 
-  nsCOMPtr<nsIIOService> io (do_GetIOService());
-  NS_ENSURE_TRUE_VOID(io);
+  // If we were passed a parent then a new child process has been created and
+  // has requested all of the chrome so send it the resources too. Otherwise
+  // resource mappings are sent by the resource protocol handler dynamically.
+  if (aParent) {
+    nsCOMPtr<nsIIOService> io (do_GetIOService());
+    NS_ENSURE_TRUE_VOID(io);
 
-  nsCOMPtr<nsIProtocolHandler> ph;
-  nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
-  NS_ENSURE_SUCCESS_VOID(rv);
+    nsCOMPtr<nsIProtocolHandler> ph;
+    nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
+    NS_ENSURE_SUCCESS_VOID(rv);
 
-  //FIXME: Some substitutions are set up lazily and might not exist yet
-  nsCOMPtr<nsIResProtocolHandler> irph (do_QueryInterface(ph));
-  nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get());
-  rph->CollectSubstitutions(resources);
+    nsCOMPtr<nsIResProtocolHandler> irph (do_QueryInterface(ph));
+    nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get());
+    rph->CollectSubstitutions(resources);
+  }
 
   mOverrideTable.EnumerateRead(&EnumerateOverride, &overrides);
 
   if (aParent) {
     bool success = aParent->SendRegisterChrome(packages, resources, overrides,
                                                mSelectedLocale, false);
     NS_ENSURE_TRUE_VOID(success);
   } else {
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1569,16 +1569,20 @@ ContentChild::RecvRegisterChromeItem(con
         case ChromeRegistryItem::TChromePackage:
             chromeRegistry->RegisterPackage(item.get_ChromePackage());
             break;
 
         case ChromeRegistryItem::TOverrideMapping:
             chromeRegistry->RegisterOverride(item.get_OverrideMapping());
             break;
 
+        case ChromeRegistryItem::TResourceMapping:
+            chromeRegistry->RegisterResource(item.get_ResourceMapping());
+            break;
+
         default:
             MOZ_ASSERT(false, "bad chrome item");
             return false;
     }
 
     return true;
 }
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -66,16 +66,17 @@ using mozilla::dom::NativeThreadId from 
 using mozilla::dom::quota::PersistenceType from "mozilla/dom/quota/PersistenceType.h";
 using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h";
 using gfxIntSize from "nsSize.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
+    ResourceMapping;
 };
 
 namespace mozilla {
 namespace dom {
 
 struct FontListEntry {
     nsString  familyName;
     nsString  faceName;
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -1,24 +1,29 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/chrome/RegistryMessageUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/unused.h"
 
 #include "nsResProtocolHandler.h"
 #include "nsIIOService.h"
 #include "nsIFile.h"
 #include "nsNetUtil.h"
 #include "nsURLHelper.h"
 #include "nsEscape.h"
 
 #include "mozilla/Omnijar.h"
 
+using mozilla::dom::ContentParent;
+using mozilla::unused;
+
 static NS_DEFINE_CID(kResURLCID, NS_RESURL_CID);
 
 static nsResProtocolHandler *gResHandler = nullptr;
 
 #if defined(PR_LOGGING)
 //
 // Log module for Resource Protocol logging...
 //
@@ -299,44 +304,72 @@ nsResProtocolHandler::AllowPort(int32_t 
     *_retval = false;
     return NS_OK;
 }
 
 //----------------------------------------------------------------------------
 // nsResProtocolHandler::nsIResProtocolHandler
 //----------------------------------------------------------------------------
 
+static void
+SendResourceSubstitution(const nsACString& root, nsIURI* baseURI)
+{
+    if (GeckoProcessType_Content == XRE_GetProcessType()) {
+        return;
+    }
+
+    ResourceMapping resourceMapping;
+    resourceMapping.resource = root;
+    if (baseURI) {
+        baseURI->GetSpec(resourceMapping.resolvedURI.spec);
+        baseURI->GetOriginCharset(resourceMapping.resolvedURI.charset);
+    }
+
+    nsTArray<ContentParent*> parents;
+    ContentParent::GetAll(parents);
+    if (!parents.Length()) {
+        return;
+    }
+
+    for (uint32_t i = 0; i < parents.Length(); i++) {
+        unused << parents[i]->SendRegisterChromeItem(resourceMapping);
+    }
+}
+
 NS_IMETHODIMP
 nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
 {
     if (!baseURI) {
         mSubstitutions.Remove(root);
+        SendResourceSubstitution(root, baseURI);
         return NS_OK;
     }
 
     // If baseURI isn't a resource URI, we can set the substitution immediately.
     nsAutoCString scheme;
     nsresult rv = baseURI->GetScheme(scheme);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!scheme.EqualsLiteral("resource")) {
         mSubstitutions.Put(root, baseURI);
+        SendResourceSubstitution(root, baseURI);
         return NS_OK;
     }
 
     // baseURI is a resource URI, let's resolve it first.
     nsAutoCString newBase;
     rv = ResolveURI(baseURI, newBase);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> newBaseURI;
     rv = mIOService->NewURI(newBase, nullptr, nullptr,
                             getter_AddRefs(newBaseURI));
     NS_ENSURE_SUCCESS(rv, rv);
 
     mSubstitutions.Put(root, newBaseURI);
+    SendResourceSubstitution(root, newBaseURI);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsResProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
 {
     NS_ENSURE_ARG_POINTER(result);
 
--- a/netwerk/test/browser/browser.ini
+++ b/netwerk/test/browser/browser.ini
@@ -1,3 +1,6 @@
 [DEFAULT]
+support-files =
+  dummy.html
 
 [browser_NetUtil.js]
+[browser_child_resource.js]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,213 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+// This must be loaded in the remote process for this test to be useful
+const TEST_URL = "http://example.com/browser/netwerk/test/browser/dummy.html";
+
+const expectedRemote = gMultiProcessBrowser ? "true" : "";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"]
+                        .getService(Ci.nsIResProtocolHandler);
+
+function frameScript() {
+  Components.utils.import("resource://gre/modules/Services.jsm");
+  let resProtocol = Components.classes["@mozilla.org/network/protocol;1?name=resource"]
+                              .getService(Components.interfaces.nsIResProtocolHandler);
+
+  addMessageListener("Test:ResolveURI", function({ data: uri }) {
+    uri = Services.io.newURI(uri, null, null);
+    try {
+      let resolved = resProtocol.resolveURI(uri);
+      sendAsyncMessage("Test:ResolvedURI", resolved);
+    }
+    catch (e) {
+      sendAsyncMessage("Test:ResolvedURI", null);
+    }
+  });
+
+  addMessageListener("Test:Crash", function() {
+    dump("Crashing\n");
+    privateNoteIntentionalCrash();
+    Components.utils.import("resource://gre/modules/ctypes.jsm");
+    let zero = new ctypes.intptr_t(8);
+    let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+    badptr.contents
+  });
+}
+
+function waitForEvent(obj, name, capturing, chromeEvent) {
+  info("Waiting for " + name);
+  return new Promise((resolve) => {
+    function listener(event) {
+      info("Saw " + name);
+      obj.removeEventListener(name, listener, capturing, chromeEvent);
+      resolve(event);
+    }
+
+    obj.addEventListener(name, listener, capturing, chromeEvent);
+  });
+}
+
+function resolveURI(uri) {
+  uri = Services.io.newURI(uri, null, null);
+  try {
+    return resProtocol.resolveURI(uri);
+  }
+  catch (e) {
+    return null;
+  }
+}
+
+function remoteResolveURI(uri) {
+  return new Promise((resolve) => {
+    let manager = gBrowser.selectedBrowser.messageManager;
+
+    function listener({ data: resolved }) {
+      manager.removeMessageListener("Test:ResolvedURI", listener);
+      resolve(resolved);
+    }
+
+    manager.addMessageListener("Test:ResolvedURI", listener);
+    manager.sendAsyncMessage("Test:ResolveURI", uri);
+  });
+}
+
+let loadTestTab = Task.async(function*() {
+  gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+  let browser = gBrowser.selectedBrowser;
+  yield waitForEvent(browser, "load", true);
+  browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+  return browser;
+});
+
+// Restarts the child process by crashing it then reloading the tab
+let restart = Task.async(function*() {
+  let browser = gBrowser.selectedBrowser;
+  // If the tab isn't remote this would crash the main process so skip it
+  if (browser.getAttribute("remote") != "true")
+    return browser;
+
+  browser.messageManager.sendAsyncMessage("Test:Crash");
+  yield waitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+  browser.reload();
+
+  yield waitForEvent(browser, "load", true);
+  is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+  browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+  return browser;
+});
+
+// Sanity check that this test is going to be useful
+add_task(function*() {
+  let browser = yield loadTestTab();
+
+  // This must be loaded in the remote process for this test to be useful
+  is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+
+  let local = resolveURI("resource://gre/modules/Services.jsm");
+  let remote = yield remoteResolveURI("resource://gre/modules/Services.jsm");
+  is(local, remote, "Services.jsm should resolve in both processes");
+
+  gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, update it then remove it
+add_task(function*() {
+  let browser = yield loadTestTab();
+
+  info("Set");
+  resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+  let local = resolveURI("resource://testing/test.js");
+  let remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, "chrome://global/content/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+  info("Change");
+  resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+  local = resolveURI("resource://testing/test.js");
+  remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+  info("Clear");
+  resProtocol.setSubstitution("testing", null);
+  local = resolveURI("resource://testing/test.js");
+  remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, null, "Shouldn't resolve in main process");
+  is(remote, null, "Shouldn't resolve in child process");
+
+  gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, restart the child process then check it is still there
+add_task(function*() {
+  let browser = yield loadTestTab();
+
+  info("Set");
+  resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+  let local = resolveURI("resource://testing/test.js");
+  let remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, "chrome://global/content/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+  yield restart();
+
+  local = resolveURI("resource://testing/test.js");
+  remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, "chrome://global/content/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+  info("Change");
+  resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+
+  yield restart();
+
+  local = resolveURI("resource://testing/test.js");
+  remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+  info("Clear");
+  resProtocol.setSubstitution("testing", null);
+
+  yield restart();
+
+  local = resolveURI("resource://testing/test.js");
+  remote = yield remoteResolveURI("resource://testing/test.js");
+  is(local, null, "Shouldn't resolve in main process");
+  is(remote, null, "Shouldn't resolve in child process");
+
+  gBrowser.removeCurrentTab();
+});
+
+// Adding a mapping to a resource URI should work
+add_task(function*() {
+  let browser = yield loadTestTab();
+
+  info("Set");
+  resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+  resProtocol.setSubstitution("testing2", Services.io.newURI("resource://testing", null, null));
+  let local = resolveURI("resource://testing2/test.js");
+  let remote = yield remoteResolveURI("resource://testing2/test.js");
+  is(local, "chrome://global/content/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+  info("Clear");
+  resProtocol.setSubstitution("testing", null);
+  local = resolveURI("resource://testing2/test.js");
+  remote = yield remoteResolveURI("resource://testing2/test.js");
+  is(local, "chrome://global/content/test.js", "Should resolve in main process");
+  is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+  resProtocol.setSubstitution("testing2", null);
+  local = resolveURI("resource://testing2/test.js");
+  remote = yield remoteResolveURI("resource://testing2/test.js");
+  is(local, null, "Shouldn't resolve in main process");
+  is(remote, null, "Shouldn't resolve in child process");
+
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+  <p>Dummy Page</p>
+</body>
+</html>