author | Haik Aftandilian <haftandilian@mozilla.com> |
Wed, 21 Jun 2017 16:13:23 -0700 | |
changeset 366107 | 3358bef7a047d347e576bbb83d1461b3776fc5fb |
parent 366106 | 69cb0b4c0b2be646cfd682876fc65d2ddc8ecc6e |
child 366108 | 4b9a7375ccb6f6f21678cfdc117edf7faf0d727a |
push id | 45500 |
push user | haftandilian@mozilla.com |
push date | Tue, 27 Jun 2017 05:35:56 +0000 |
treeherder | autoland@3358bef7a047 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jimm |
bugs | 1334550 |
milestone | 56.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
|
--- a/caps/tests/mochitest/test_extensionURL.html +++ b/caps/tests/mochitest/test_extensionURL.html @@ -13,51 +13,166 @@ https://bugzilla.mozilla.org/show_bug.cg /** Test for Bug 1161831 **/ SimpleTest.waitForExplicitFinish(); let module = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {}); var {MatchGlob, MatchPatternSet, WebExtensionPolicy} = module; var policy1, policy2; + var XPCOMUtils = SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils; var resourceHandler = SpecialPowers.Services.io.getProtocolHandler("resource") .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler); var extensionHandler = SpecialPowers.Services.io.getProtocolHandler("moz-extension") .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler); + var fileHandler = SpecialPowers.Cc["@mozilla.org/network/protocol;1?name=file"] + .getService(SpecialPowers.Ci.nsIFileProtocolHandler); + + // Chrome script that adds handles for inserting substitutions and + // resolving symlinked paths in the parent process. + var script = SpecialPowers.loadChromeScript(() => { + const Ci = Components.interfaces; + const Cc = Components.classes; + const Cu = Components.utils; + Cu.import("resource://gre/modules/Services.jsm"); + + // Sets up a substitution in the parent process + this.addMessageListener("SetSubstitution", ({from, to}) => { + // Convert the passed |to| string to a URI. + // A null |to| value clears the substitution + if (to != null) { + var uri = Services.io.newURI(to); + } + Services.io.getProtocolHandler("moz-extension") + .QueryInterface(Ci.nsISubstitutingProtocolHandler) + .setSubstitution(from, uri); + }); + + // Gets a normalized (de-symlinked) path in the parent process + this.addMessageListener("ResolvePath", (path) => { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + file.initWithPath(path); + file.normalize(); + return file.path; + }); + }); + + // An array of objects each containing a callback and a host + // for a substitution that has been set in either the parent + // or local child process and for which we are waiting for the + // observer notification in the child process. + var pendingSubstitutions = []; + + // Adds a new callback to |pendingSubstitutions|. + function pushSubstitutionCallback(callback, host) { + let entry = {callback, host}; + pendingSubstitutions.push(entry); + } + + // Invoke the first callback found in |pendingSubstitutions| + // with a matching host. + function popSubstitutionCallback(host) { + for (let i = 0; i < pendingSubstitutions.length; i++) { + let entry = pendingSubstitutions[i]; + if (host === entry.host) { + entry.callback(); + pendingSubstitutions.splice(i, 1); + return; + } + } + // This indicates we installed a mapping in either the + // parent or the local child process, but never received an + // observer notification in the child for a mapping with + // a matching host. + ok(false, `popSubstitutionCallback(${host}) no match found`); + } + + // Define an implementation of nsISubstitutionObserver and add it + // to this process' ExtensionProtocolHandler to observe substitutions + // as they are propagated to the child. + function SubstitutionObserver() {} + SubstitutionObserver.prototype = { + onSetSubstitution: SpecialPowers.wrapCallback(function(root, uri) { + popSubstitutionCallback(root); + }), + QueryInterface: + XPCOMUtils.generateQI([SpecialPowers.Ci.nsISupports, + SpecialPowers.Ci.nsISubstitutionObserver]), + }; + var observer = new SubstitutionObserver(); + var wrappedObserver = SpecialPowers.wrap(observer); + extensionHandler.addObserver(wrappedObserver); + + // Set a substitution in the parent. The parent + // propagates all substitutions to child processes. + function globalSetSubstitution(chromeScript, from, to) { + var p = new Promise(function(resolve, reject) { + pushSubstitutionCallback(resolve, from); + chromeScript.sendSyncMessage("SetSubstitution", {from, to}); + }); + return p; + } SimpleTest.registerCleanupFunction(function() { + extensionHandler.removeObserver(wrappedObserver); policy1.active = false; policy2.active = false; + script.sendSyncMessage("SetSubstitution", {from: "cherise", to: null}); + script.sendSyncMessage("SetSubstitution", {from: "liebchen", to: null}); }); addLoadEvent(function() { // First, get a file:// URI to something - open to suggestions on how to do // this more easily. var resURI = SpecialPowers.Services.io.newURI("resource://testing-common/resource_test_file.html"); var filePath = resourceHandler.resolveURI(resURI); ok(filePath.startsWith("file://"), "resource:// URI resolves where we expect: " + filePath); - var fileURI = SpecialPowers.Services.io.newURI(filePath); + var resFile = fileHandler.getFileFromURLSpec(filePath); + + // Get normalized path to the test file. We already have a file object + // |resFile|, but its underlying path may contain a symlink we can't + // resolve in the child process. + let resolvedPath = script.sendSyncMessage("ResolvePath", resFile.path); + let file = SpecialPowers.Cc["@mozilla.org/file/local;1"] + .createInstance(SpecialPowers.Ci.nsILocalFile); + info(`resolved test file path: ${resolvedPath}`); + file.initWithPath(resolvedPath); + + // Setup the base directory URI string and a URI string to refer to + // the test file within that directory. + let cheriseURIStr = "moz-extension://cherise/" + file.leafName; + let liebchenURIStr = "moz-extension://liebchen/" + file.leafName; + let cheriseBaseDirURIStr = "file://" + file.parent.path + "/"; + info(`cheriseURIStr: ${cheriseURIStr}`); + info(`liebchenURIStr: ${liebchenURIStr}`); + info(`cheriseBaseDirURIStr: ${cheriseBaseDirURIStr}`); function StubPolicy(id, accessible) { let policy = new WebExtensionPolicy(SpecialPowers.Cu.cloneInto({ id: `imaginaryaddon-${id[0]}`, mozExtensionHostname: id, - baseURL: fileURI.spec, + baseURL: cheriseBaseDirURIStr, allowedOrigins: SpecialPowers.unwrap(new MatchPatternSet([])), webAccessibleResources: accessible ? [SpecialPowers.unwrap(new MatchGlob("*"))] : [], localizeCallback(string) {}, }, module, {cloneFunctions: true, wrapReflectors: true})); + // Activating the policy results in a substitution being added, + // which triggers SubstitutionObserver.onSetSubstitution(). + // All observer notifications must be accounted for (in this + // test to validate they are working correctly) so ignore this + // substitution when the observer gets notified. + pushSubstitutionCallback(() => {}, id); policy.active = true; return policy; } - // Register a moz-extension:// URI. + // Register a moz-extension:// URI locally. policy1 = StubPolicy("cherise", false); policy2 = StubPolicy("liebchen", false); // // Make sure that non-file:// URIs don't work. // // resource:// @@ -84,17 +199,20 @@ https://bugzilla.mozilla.org/show_bug.cg SpecialPowers.wrap(ifr).contentWindow .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) .getInterface(SpecialPowers.Ci.nsIWebNavigation) .loadURI(url, 0, null, null, null); } function setWhitelistCallback(paths) { + pushSubstitutionCallback(() => {}, policy1.mozExtensionHostname); policy1.active = false; + + pushSubstitutionCallback(() => {}, policy2.mozExtensionHostname); policy2.active = false; policy1 = StubPolicy("cherise", paths.includes("cherise")); policy2 = StubPolicy("liebchen", paths.includes("liebchen")); } function testLoad(url, navigate, shouldThrow) { var ifr = document.createElement("iframe"); @@ -142,29 +260,31 @@ https://bugzilla.mozilla.org/show_bug.cg xhr.open("GET", url, true); xhr.send(); }); } // // Perform some loads and make sure they work correctly. // - testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithLocation)() - .then(testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithWebNav)) - .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation, /* shouldThrow = */ true)) - .then(testXHR.bind(null, "moz-extension://cherise", /* shouldError = */ true)) + globalSetSubstitution(script, "cherise", cheriseBaseDirURIStr) + .then(testLoad.bind(null, cheriseURIStr, navigateFromChromeWithLocation)) + .then(testLoad.bind(null, cheriseURIStr, navigateFromChromeWithWebNav)) + .then(testLoad.bind(null, cheriseURIStr, navigateWithLocation, /* shouldThrow = */ true)) + .then(testXHR.bind(null, cheriseURIStr, /* shouldError = */ true)) .then(setWhitelistCallback.bind(null, ["cherise"])) - .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation)) - .then(testXHR.bind(null, "moz-extension://cherise")) - .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation, /* shouldThrow = */ true)) - .then(testXHR.bind(null, "moz-extension://liebchen", /* shouldError = */ true)) + .then(testLoad.bind(null, cheriseURIStr, navigateWithLocation)) + .then(testXHR.bind(null, cheriseURIStr)) + .then(globalSetSubstitution(script, "liebchen", "moz-extension://cherise")) + .then(testLoad.bind(null, liebchenURIStr, navigateWithLocation, /* shouldThrow = */ true)) + .then(testXHR.bind(null, liebchenURIStr, /* shouldError = */ true)) .then(setWhitelistCallback.bind(null, ["cherise", "liebchen"])) - .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation)) - .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithSrc)) - .then(testLoad.bind(null, "moz-extension://cherise", navigateWithSrc)) + .then(testLoad.bind(null, liebchenURIStr, navigateWithLocation)) + .then(testLoad.bind(null, liebchenURIStr, navigateWithSrc)) + .then(testLoad.bind(null, cheriseURIStr, navigateWithSrc)) .then(testLoad.bind(null, "moz-extension://cherise/_blank.html", navigateWithSrc)) .then(SimpleTest.finish.bind(SimpleTest), function(e) { ok(false, "rejected promise: " + e); SimpleTest.finish() } ); }); </script> </head>
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp +++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp @@ -292,44 +292,47 @@ SubstitutingProtocolHandler::AllowPort(i // nsISubstitutingProtocolHandler //---------------------------------------------------------------------------- nsresult SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI) { if (!baseURI) { mSubstitutions.Remove(root); + NotifyObservers(root, baseURI); return SendSubstitution(root, baseURI); } // If baseURI isn't a same-scheme URI, we can set the substitution immediately. nsAutoCString scheme; nsresult rv = baseURI->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); if (!scheme.Equals(mScheme)) { if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar") && !scheme.EqualsLiteral("app")) { NS_WARNING("Refusing to create substituting URI to non-file:// target"); return NS_ERROR_INVALID_ARG; } mSubstitutions.Put(root, baseURI); + NotifyObservers(root, baseURI); return SendSubstitution(root, baseURI); } // baseURI is a same-type substituting 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); + NotifyObservers(root, baseURI); return SendSubstitution(root, newBaseURI); } nsresult SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result) { NS_ENSURE_ARG_POINTER(result); @@ -403,10 +406,43 @@ SubstitutingProtocolHandler::ResolveURI( if (MOZ_LOG_TEST(gResLog, LogLevel::Debug)) { nsAutoCString spec; uri->GetAsciiSpec(spec); MOZ_LOG(gResLog, LogLevel::Debug, ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get())); } return rv; } +nsresult +SubstitutingProtocolHandler::AddObserver(nsISubstitutionObserver* aObserver) +{ + NS_ENSURE_ARG(aObserver); + if (mObservers.Contains(aObserver)) { + return NS_ERROR_DUPLICATE_HANDLE; + } + + mObservers.AppendElement(aObserver); + return NS_OK; +} + +nsresult +SubstitutingProtocolHandler::RemoveObserver(nsISubstitutionObserver* aObserver) +{ + NS_ENSURE_ARG(aObserver); + if (!mObservers.Contains(aObserver)) { + return NS_ERROR_INVALID_ARG; + } + + mObservers.RemoveElement(aObserver); + return NS_OK; +} + +void +SubstitutingProtocolHandler::NotifyObservers(const nsACString& aRoot, + nsIURI* aBaseURI) +{ + for (size_t i = 0; i < mObservers.Length(); ++i) { + mObservers[i]->OnSetSubstitution(aRoot, aBaseURI); + } +} + } // namespace net } // namespace mozilla
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h +++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h @@ -6,16 +6,17 @@ #ifndef SubstitutingProtocolHandler_h___ #define SubstitutingProtocolHandler_h___ #include "nsISubstitutingProtocolHandler.h" #include "nsInterfaceHashtable.h" #include "nsIOService.h" +#include "nsISubstitutionObserver.h" #include "nsStandardURL.h" #include "mozilla/chrome/RegistryMessageUtils.h" #include "mozilla/Maybe.h" class nsIIOService; namespace mozilla { namespace net { @@ -68,21 +69,29 @@ protected: virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) { return NS_OK; } nsIIOService* IOService() { return mIOService; } private: + // Notifies all observers that a new substitution from |aRoot| to + // |aBaseURI| has been set/installed for this protocol handler. + void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI); + nsCString mScheme; Maybe<uint32_t> mFlags; nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions; nsCOMPtr<nsIIOService> mIOService; + // The list of observers added with AddObserver that will be + // notified when substitutions are set or unset. + nsTArray<nsCOMPtr<nsISubstitutionObserver>> mObservers; + // In general, we expect the principal of a document loaded from a // substituting URI to be a codebase principal for that URI (rather than // a principal for whatever is underneath). However, this only works if // the protocol handler for the underlying URI doesn't set an explicit // owner (which chrome:// does, for example). So we want to require that // substituting URIs only map to other URIs of the same type, or to // file:// and jar:// URIs. //
--- a/netwerk/protocol/res/moz.build +++ b/netwerk/protocol/res/moz.build @@ -2,16 +2,17 @@ # vim: set filetype=python: # 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/. XPIDL_SOURCES += [ 'nsIResProtocolHandler.idl', 'nsISubstitutingProtocolHandler.idl', + 'nsISubstitutionObserver.idl', ] XPIDL_MODULE = 'necko_res' EXPORTS.mozilla.net += [ 'ExtensionProtocolHandler.h', 'SubstitutingProtocolHandler.h', ]
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl +++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl @@ -1,15 +1,17 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsIProtocolHandler.idl" +interface nsISubstitutionObserver; + /** * Protocol handler superinterface for a protocol which performs substitutions * from URIs of its scheme to URIs of another scheme. */ [scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)] interface nsISubstitutingProtocolHandler : nsIProtocolHandler { /** @@ -38,9 +40,23 @@ interface nsISubstitutingProtocolHandler /** * Utility function to resolve a substituted URI. A resolved URI is not * guaranteed to reference a resource that exists (ie. opening a channel to * the resolved URI may fail). * * @throws NS_ERROR_NOT_AVAILABLE if resURI.host() is an unknown root key. */ [must_use] AUTF8String resolveURI(in nsIURI resURI); + + /** + * Adds an observer that will be notified on the main thread whenever a + * substitition is set or unset. Notifications are not sent for substitutions + * that were set prior to the observer being added. Holds an owning reference + * to the observer until removeObserver is called or the protocol handler is + * destroyed. + */ + [must_use] void addObserver(in nsISubstitutionObserver observer); + + /** + * Removes the observer. + */ + [must_use] void removeObserver(in nsISubstitutionObserver observer); };
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/res/nsISubstitutionObserver.idl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsISupports.idl" + +interface nsIURI; + +/** + * An observer of substitutions being set or unset on a + * SubstitutingProtocolHandler. Useful for receiving asynchronous notification + * in a child process after a substitution has been set in the parent process + * and is propagated to the child. + */ +[scriptable, uuid(492c9192-3803-4e2b-8373-d25fe55f5588)] +interface nsISubstitutionObserver : nsISupports +{ + /** + * To be called when a substition has been set or unset on a protocol + * handler. Unset operations are identified by a null URI argument. + * + * @param aRoot the root key of the mapping + * @param aBaseURI the base URI to be substituted for the root key by the + * protocol handler. For notifications triggered by unset + * operations (i.e., when is a substitution is removed from the + * protocol handler) this argument is null. + */ + void onSetSubstitution(in ACString aRoot, in nsIURI aBaseURI); +};
--- a/netwerk/protocol/res/nsResProtocolHandler.h +++ b/netwerk/protocol/res/nsResProtocolHandler.h @@ -8,16 +8,18 @@ #include "SubstitutingProtocolHandler.h" #include "nsIResProtocolHandler.h" #include "nsInterfaceHashtable.h" #include "nsWeakReference.h" #include "nsStandardURL.h" +class nsISubstitutionObserver; + struct SubstitutionMapping; class nsResProtocolHandler final : public nsIResProtocolHandler, public mozilla::SubstitutingProtocolHandler, public nsSupportsWeakReference { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRESPROTOCOLHANDLER @@ -43,16 +45,26 @@ public: return mozilla::SubstitutingProtocolHandler::HasSubstitution(aRoot, aResult); } NS_IMETHOD ResolveURI(nsIURI *aResURI, nsACString& aResult) override { return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult); } + NS_IMETHOD AddObserver(nsISubstitutionObserver *aObserver) override + { + return mozilla::SubstitutingProtocolHandler::AddObserver(aObserver); + } + + NS_IMETHOD RemoveObserver(nsISubstitutionObserver *aObserver) override + { + return mozilla::SubstitutingProtocolHandler::RemoveObserver(aObserver); + } + protected: MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override; virtual ~nsResProtocolHandler() {} MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, const nsACString& aPathname, nsACString& aResult) override;