Skip to content

Commit

Permalink
Bug 1068412: Forward dynamically registered resource URI mappings to …
Browse files Browse the repository at this point in the history
…content processes. r=billm
  • Loading branch information
Mossop committed Oct 6, 2014
1 parent 5d7f504 commit c974094
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 9 deletions.
6 changes: 6 additions & 0 deletions chrome/RegistryMessageUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 13 additions & 9 deletions chrome/nsChromeRegistryChrome.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,17 +453,21 @@ nsChromeRegistryChrome::SendRegisteredChrome(
};
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);

Expand Down
4 changes: 4 additions & 0 deletions dom/ipc/ContentChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions dom/ipc/PContent.ipdl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ union ChromeRegistryItem
{
ChromePackage;
OverrideMapping;
ResourceMapping;
};

namespace mozilla {
Expand Down
33 changes: 33 additions & 0 deletions netwerk/protocol/res/nsResProtocolHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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<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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down
3 changes: 3 additions & 0 deletions netwerk/test/browser/browser.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[DEFAULT]
support-files =
dummy.html

[browser_NetUtil.js]
[browser_child_resource.js]
213 changes: 213 additions & 0 deletions netwerk/test/browser/browser_child_resource.js
Original file line number Diff line number Diff line change
@@ -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();
});
7 changes: 7 additions & 0 deletions netwerk/test/browser/dummy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>

<html>
<body>
<p>Dummy Page</p>
</body>
</html>

0 comments on commit c974094

Please sign in to comment.