From 88fec784afd7bb7ac5883c05fc5925342a81fec4 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Mon, 6 Oct 2014 12:42:12 -0700 Subject: [PATCH] Bug 1068412: Forward dynamically registered resource URI mappings to content processes. r=billm --- chrome/RegistryMessageUtils.h | 6 + chrome/nsChromeRegistryChrome.cpp | 22 +- dom/ipc/ContentChild.cpp | 4 + dom/ipc/PContent.ipdl | 1 + netwerk/protocol/res/nsResProtocolHandler.cpp | 33 +++ netwerk/test/browser/browser.ini | 3 + .../test/browser/browser_child_resource.js | 213 ++++++++++++++++++ netwerk/test/browser/dummy.html | 7 + 8 files changed, 280 insertions(+), 9 deletions(-) create mode 100644 netwerk/test/browser/browser_child_resource.js create mode 100644 netwerk/test/browser/dummy.html diff --git a/chrome/RegistryMessageUtils.h b/chrome/RegistryMessageUtils.h index 31b015c526b..72b3243dbb9 100644 --- a/chrome/RegistryMessageUtils.h +++ b/chrome/RegistryMessageUtils.h @@ -43,6 +43,12 @@ struct ResourceMapping { nsCString resource; SerializedURI resolvedURI; + + bool operator ==(const ResourceMapping& rhs) const + { + return resource.Equals(rhs.resource) && + resolvedURI == rhs.resolvedURI; + } }; struct OverrideMapping diff --git a/chrome/nsChromeRegistryChrome.cpp b/chrome/nsChromeRegistryChrome.cpp index c52502de3cb..15e077c8a16 100644 --- a/chrome/nsChromeRegistryChrome.cpp +++ b/chrome/nsChromeRegistryChrome.cpp @@ -453,17 +453,21 @@ nsChromeRegistryChrome::SendRegisteredChrome( }; mPackagesHash.EnumerateRead(CollectPackages, &args); - nsCOMPtr 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 io (do_GetIOService()); + NS_ENSURE_TRUE_VOID(io); - nsCOMPtr ph; - nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph)); - NS_ENSURE_SUCCESS_VOID(rv); + nsCOMPtr 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 irph (do_QueryInterface(ph)); - nsResProtocolHandler* rph = static_cast(irph.get()); - rph->CollectSubstitutions(resources); + nsCOMPtr irph (do_QueryInterface(ph)); + nsResProtocolHandler* rph = static_cast(irph.get()); + rph->CollectSubstitutions(resources); + } mOverrideTable.EnumerateRead(&EnumerateOverride, &overrides); diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 3872140ece2..96a805273c6 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1574,6 +1574,10 @@ ContentChild::RecvRegisterChromeItem(const ChromeRegistryItem& item) chromeRegistry->RegisterOverride(item.get_OverrideMapping()); break; + case ChromeRegistryItem::TResourceMapping: + chromeRegistry->RegisterResource(item.get_ResourceMapping()); + break; + default: MOZ_ASSERT(false, "bad chrome item"); return false; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 07ba1a3f628..484e0f17f75 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -71,6 +71,7 @@ union ChromeRegistryItem { ChromePackage; OverrideMapping; + ResourceMapping; }; namespace mozilla { diff --git a/netwerk/protocol/res/nsResProtocolHandler.cpp b/netwerk/protocol/res/nsResProtocolHandler.cpp index 94849a7b0a7..a8af2ceaa5c 100644 --- a/netwerk/protocol/res/nsResProtocolHandler.cpp +++ b/netwerk/protocol/res/nsResProtocolHandler.cpp @@ -4,6 +4,8 @@ * 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" @@ -14,6 +16,9 @@ #include "mozilla/Omnijar.h" +using mozilla::dom::ContentParent; +using mozilla::unused; + static NS_DEFINE_CID(kResURLCID, NS_RESURL_CID); static nsResProtocolHandler *gResHandler = nullptr; @@ -304,11 +309,37 @@ nsResProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) // 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 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; } @@ -318,6 +349,7 @@ nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI) NS_ENSURE_SUCCESS(rv, rv); if (!scheme.EqualsLiteral("resource")) { mSubstitutions.Put(root, baseURI); + SendResourceSubstitution(root, baseURI); return NS_OK; } @@ -332,6 +364,7 @@ nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI) NS_ENSURE_SUCCESS(rv, rv); mSubstitutions.Put(root, newBaseURI); + SendResourceSubstitution(root, newBaseURI); return NS_OK; } diff --git a/netwerk/test/browser/browser.ini b/netwerk/test/browser/browser.ini index a5f31f9ef8a..685771eba17 100644 --- 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] diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js new file mode 100644 index 00000000000..1de2c39fa62 --- /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(); +}); diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html new file mode 100644 index 00000000000..6b28a248fbd --- /dev/null +++ b/netwerk/test/browser/dummy.html @@ -0,0 +1,7 @@ + + + + +

Dummy Page

+ +