☠☠ backed out by 77e960d65f64 ☠ ☠ | |
author | Rob Wu <rob@robwu.nl> |
Tue, 12 Jul 2016 13:55:14 -0700 | |
changeset 305522 | 4dd2466573eceb92c70f8436ad54bb400ae4110b |
parent 305521 | e8e2132fa883f916ed763f890c9cbdfddbf8cdf1 |
child 305523 | 99837fd9225866b6416caa2f7cd97e9632bad0ea |
push id | 30704 |
push user | wmccloskey@mozilla.com |
push date | Wed, 20 Jul 2016 00:23:31 +0000 |
treeherder | autoland@4dd2466573ec [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | billm |
bugs | 1234677, 1286057 |
milestone | 50.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/browser/components/extensions/test/browser/browser_ext_currentWindow.js +++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js @@ -1,18 +1,18 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; function genericChecker() { let kind = "background"; let path = window.location.pathname; - if (path.indexOf("popup") != -1) { + if (path.includes("/popup.html")) { kind = "popup"; - } else if (path.indexOf("page") != -1) { + } else if (path.includes("/page.html")) { kind = "page"; } browser.test.onMessage.addListener((msg, ...args) => { if (msg == kind + "-check-current1") { browser.tabs.query({ currentWindow: true, }, function(tabs) { @@ -29,17 +29,17 @@ function genericChecker() { browser.test.sendMessage("result", window.id); }); } else if (msg == kind + "-open-page") { browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("page.html")}); } else if (msg == kind + "-close-page") { browser.tabs.query({ windowId: args[0], }, tabs => { - let tab = tabs.find(tab => tab.url.indexOf("page.html") != -1); + let tab = tabs.find(tab => tab.url.includes("/page.html")); browser.tabs.remove(tab.id, () => { browser.test.sendMessage("closed"); }); }); } }); browser.test.sendMessage(kind + "-ready"); }
--- a/caps/nsIAddonPolicyService.idl +++ b/caps/nsIAddonPolicyService.idl @@ -29,16 +29,23 @@ interface nsIAddonPolicyService : nsISup /** * Returns the content security policy which applies to documents belonging * to the extension with the given ID. This may be either a custom policy, * if one was supplied, or the default policy if one was not. */ AString getAddonCSP(in AString aAddonId); /** + * Returns the generated background page as a data-URI, if any. If the addon + * does not have an auto-generated background page, an empty string is + * returned. + */ + ACString getGeneratedBackgroundPageUrl(in ACString aAddonId); + + /** * Returns true if unprivileged code associated with the given addon may load * data from |aURI|. */ boolean addonMayLoadURI(in AString aAddonId, in nsIURI aURI); /** * Returns true if a given extension:// URI is web-accessible. */
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp +++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp @@ -73,16 +73,49 @@ protected: virtual ~PipeCloser() {} private: nsCOMPtr<nsIOutputStream> mOutputStream; }; NS_IMPL_ISUPPORTS(PipeCloser, nsIRequestObserver) +bool +ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost, + const nsACString& aPath, + const nsACString& aPathname, + nsACString& aResult) +{ + // Create special moz-extension:-pages such as moz-extension://foo/_blank.html + // for all registered extensions. We can't just do this as a substitution + // because substitutions can only match on host. + if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) { + return false; + } + if (aPathname.EqualsLiteral("/_blank.html")) { + aResult.AssignLiteral("about:blank"); + return true; + } + if (aPathname.EqualsLiteral("/_generated_background_page.html")) { + nsCOMPtr<nsIAddonPolicyService> aps = + do_GetService("@mozilla.org/addons/policy-service;1"); + if (!aps) { + return false; + } + nsresult rv = aps->GetGeneratedBackgroundPageUrl(aHost, aResult); + NS_ENSURE_SUCCESS(rv, false); + if (!aResult.IsEmpty()) { + MOZ_RELEASE_ASSERT(Substring(aResult, 0, 5).Equals("data:")); + return true; + } + } + + return false; +} + nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** result) { nsresult rv; nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv); NS_ENSURE_SUCCESS(rv, rv);
--- a/netwerk/protocol/res/ExtensionProtocolHandler.h +++ b/netwerk/protocol/res/ExtensionProtocolHandler.h @@ -23,28 +23,20 @@ public: NS_FORWARD_NSIPROTOCOLHANDLER(SubstitutingProtocolHandler::) NS_FORWARD_NSISUBSTITUTINGPROTOCOLHANDLER(SubstitutingProtocolHandler::) ExtensionProtocolHandler() : SubstitutingProtocolHandler("moz-extension") {} protected: ~ExtensionProtocolHandler() {} - bool ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, nsACString& aResult) override - { - // Create a special about:blank-like moz-extension://foo/_blank.html for all - // registered extensions. We can't just do this as a substitution because - // substitutions can only match on host. - if (SubstitutingProtocolHandler::HasSubstitution(aHost) && aPath.EqualsLiteral("/_blank.html")) { - aResult.AssignLiteral("about:blank"); - return true; - } - - return false; - } + bool ResolveSpecialCases(const nsACString& aHost, + const nsACString& aPath, + const nsACString& aPathname, + nsACString& aResult) override; virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) override; }; } // namespace net } // namespace mozilla #endif /* ExtensionProtocolHandler_h___ */
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp +++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp @@ -338,43 +338,43 @@ SubstitutingProtocolHandler::HasSubstitu nsresult SubstitutingProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result) { nsresult rv; nsAutoCString host; nsAutoCString path; + nsAutoCString pathname; + + nsCOMPtr<nsIURL> url = do_QueryInterface(uri); + if (!url) { + return NS_ERROR_MALFORMED_URI; + } rv = uri->GetAsciiHost(host); if (NS_FAILED(rv)) return rv; rv = uri->GetPath(path); if (NS_FAILED(rv)) return rv; - if (ResolveSpecialCases(host, path, result)) { + rv = url->GetFilePath(pathname); + if (NS_FAILED(rv)) return rv; + + if (ResolveSpecialCases(host, path, pathname, result)) { return NS_OK; } nsCOMPtr<nsIURI> baseURI; rv = GetSubstitution(host, getter_AddRefs(baseURI)); if (NS_FAILED(rv)) return rv; // Unescape the path so we can perform some checks on it. - nsCOMPtr<nsIURL> url = do_QueryInterface(uri); - if (!url) { - return NS_ERROR_MALFORMED_URI; - } - - nsAutoCString unescapedPath; - rv = url->GetFilePath(unescapedPath); - if (NS_FAILED(rv)) return rv; - - NS_UnescapeURL(unescapedPath); - if (unescapedPath.FindChar('\\') != -1) { + NS_UnescapeURL(pathname); + if (pathname.FindChar('\\') != -1) { return NS_ERROR_MALFORMED_URI; } // Some code relies on an empty path resolving to a file rather than a // directory. NS_ASSERTION(path.CharAt(0) == '/', "Path must begin with '/'"); if (path.Length() == 1) { rv = baseURI->GetSpec(result);
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h +++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h @@ -50,17 +50,20 @@ protected: virtual nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) { *aResult = nullptr; return NS_ERROR_NOT_AVAILABLE; } // Override this in the subclass to check for special case when resolving URIs // _before_ checking substitutions. - virtual bool ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, nsACString& aResult) + virtual bool ResolveSpecialCases(const nsACString& aHost, + const nsACString& aPath, + const nsACString& aPathname, + nsACString& aResult) { return false; } // Override this in the subclass to check for special case when opening // channels. virtual nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) {
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp +++ b/netwerk/protocol/res/nsResProtocolHandler.cpp @@ -61,26 +61,27 @@ NS_IMPL_QUERY_INTERFACE(nsResProtocolHan NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler) NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler) nsresult nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result) { nsAutoCString uri; - if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), uri)) { + if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) { return NS_ERROR_NOT_AVAILABLE; } return NS_NewURI(result, uri); } bool nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, + const nsACString& aPathname, nsACString& aResult) { if (aHost.Equals("") || aHost.Equals(kAPP)) { aResult.Assign(mAppURI); } else if (aHost.Equals(kGRE)) { aResult.Assign(mGREURI); } else { return false;
--- a/netwerk/protocol/res/nsResProtocolHandler.h +++ b/netwerk/protocol/res/nsResProtocolHandler.h @@ -47,17 +47,19 @@ public: { return mozilla::SubstitutingProtocolHandler::ResolveURI(aResURI, aResult); } protected: nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override; virtual ~nsResProtocolHandler() {} - bool ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, + bool ResolveSpecialCases(const nsACString& aHost, + const nsACString& aPath, + const nsACString& aPathname, nsACString& aResult) override; private: nsCString mAppURI; nsCString mGREURI; }; #endif /* nsResProtocolHandler_h___ */
--- a/toolkit/components/extensions/ExtensionManagement.jsm +++ b/toolkit/components/extensions/ExtensionManagement.jsm @@ -156,25 +156,27 @@ var Service = { let handler = Services.io.getProtocolHandler("moz-extension"); handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); handler.setSubstitution(uuid, uri); this.uuidMap.set(uuid, extension); this.aps.setAddonLoadURICallback(extension.id, this.checkAddonMayLoad.bind(this, extension)); this.aps.setAddonLocalizeCallback(extension.id, extension.localize.bind(extension)); this.aps.setAddonCSP(extension.id, extension.manifest.content_security_policy); + this.aps.setBackgroundPageUrlCallback(uuid, this.generateBackgroundPageUrl.bind(this, extension)); }, // Called when an extension is unloaded. shutdownExtension(uuid) { let extension = this.uuidMap.get(uuid); this.uuidMap.delete(uuid); this.aps.setAddonLoadURICallback(extension.id, null); this.aps.setAddonLocalizeCallback(extension.id, null); this.aps.setAddonCSP(extension.id, null); + this.aps.setBackgroundPageUrlCallback(uuid, null); let handler = Services.io.getProtocolHandler("moz-extension"); handler.QueryInterface(Ci.nsISubstitutingProtocolHandler); handler.setSubstitution(uuid, null); }, // Return true if the given URI can be loaded from arbitrary web // content. The manifest.json |web_accessible_resources| directive @@ -195,16 +197,31 @@ var Service = { // Checks whether a given extension can load this URI (typically via // an XML HTTP request). The manifest.json |permissions| directive // determines this. checkAddonMayLoad(extension, uri) { return extension.whiteListedHosts.matchesIgnoringPath(uri); }, + generateBackgroundPageUrl(extension) { + let background_scripts = extension.manifest.background && + extension.manifest.background.scripts; + if (!background_scripts) { + return; + } + let html = "<!DOCTYPE html>\n<body>\n"; + for (let script of background_scripts) { + script = script.replace(/"/g, """); + html += `<script src="${script}"></script>\n`; + } + html += "</body>\n</html>\n"; + return "data:text/html;charset=utf-8," + encodeURIComponent(html); + }, + // Finds the add-on ID associated with a given moz-extension:// URI. // This is used to set the addonId on the originAttributes for the // nsIPrincipal attached to the URI. extensionURIToAddonID(uri) { let uuid = uri.host; let extension = this.uuidMap.get(uuid); return extension ? extension.id : undefined; },
--- a/toolkit/components/extensions/ext-backgroundPage.js +++ b/toolkit/components/extensions/ext-backgroundPage.js @@ -7,35 +7,34 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/AddonManager.jsm"); // WeakMap[Extension -> BackgroundPage] var backgroundPagesMap = new WeakMap(); // Responsible for the background_page section of the manifest. function BackgroundPage(options, extension) { this.extension = extension; - this.scripts = options.scripts || []; this.page = options.page || null; + this.isGenerated = !!options.scripts; this.contentWindow = null; this.chromeWebNav = null; this.webNav = null; this.context = null; } BackgroundPage.prototype = { build() { let chromeWebNav = Services.appShell.createWindowlessBrowser(true); this.chromeWebNav = chromeWebNav; let url; if (this.page) { url = this.extension.baseURI.resolve(this.page); - } else { - // TODO: Chrome uses "_generated_background_page.html" for this. - url = this.extension.baseURI.resolve("_blank.html"); + } else if (this.isGenerated) { + url = this.extension.baseURI.resolve("_generated_background_page.html"); } if (!this.extension.isExtensionURL(url)) { this.extension.manifestError("Background page must be a file within the extension"); url = this.extension.baseURI.resolve("_blank.html"); } let system = Services.scriptSecurityManager.getSystemPrincipal(); @@ -98,26 +97,16 @@ BackgroundPage.prototype = { Components.utils.exportFunction(alertOverwrite, window, { defineAs: "alert", }); if (event.target != window.document) { return; } event.currentTarget.removeEventListener("load", loadListener, true); - if (this.scripts) { - let doc = window.document; - for (let script of this.scripts) { - let tag = doc.createElement("script"); - tag.setAttribute("src", script); - tag.async = false; - doc.body.appendChild(tag); - } - } - if (this.extension.onStartup) { this.extension.onStartup(); } }; browser.addEventListener("load", loadListener, true); }, shutdown() {
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -82,16 +82,19 @@ skip-if = os == 'android' # Android does [test_ext_background_runtime_connect_params.html] [test_ext_cookies.html] [test_ext_bookmarks.html] skip-if = (os == 'android' || buildapp == 'b2g') # unimplemented api. Bug 1258975 on android. [test_ext_alarms.html] [test_ext_background_window_properties.html] [test_ext_background_sub_windows.html] [test_ext_background_api_injection.html] +[test_ext_background_generated_url.html] +[test_ext_background_generated_reload.html] +[test_ext_background_generated_load_events.html] [test_ext_i18n.html] skip-if = (os == 'android') # Bug 1258975 on android. [test_ext_web_accessible_resources.html] skip-if = (os == 'android') # Bug 1258975 on android. [test_ext_webrequest.html] skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android. [test_ext_webnavigation.html] skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_generated_load_events.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test load events in _generated_background_page.html</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + +<script> +"use strict"; + +/* eslint-disable mozilla/balanced-listeners */ + +add_task(function* test_DOMContentLoaded_in_generated_background_page() { + function backgroundScript() { + function reportListener(event) { + browser.test.sendMessage("eventname", event.type); + } + document.addEventListener("DOMContentLoaded", reportListener); + window.addEventListener("load", reportListener); + } + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + background: { + scripts: ["bg.js"], + }, + web_accessible_resources: ["_generated_background_page.html"], + }, + files: { + "bg.js": `(${backgroundScript})();`, + }, + }); + + yield extension.startup(); + is("DOMContentLoaded", yield extension.awaitMessage("eventname")); + is("load", yield extension.awaitMessage("eventname")); + + yield extension.unload(); +}); + +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_generated_reload.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test reload of _generated_background_page.html</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + +<script> +"use strict"; + +add_task(function* test_reload_generated_background_page() { + function backgroundScript() { + if (location.hash !== "#firstrun") { + browser.test.sendMessage("first run"); + location.hash = "#firstrun"; + browser.test.assertEq("#firstrun", location.hash); + location.reload(); + } else { + browser.test.notifyPass("second run"); + } + } + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + background: { + scripts: ["bg.js"], + }, + }, + files: { + "bg.js": `(${backgroundScript})();`, + }, + }); + + yield extension.startup(); + info("Waiting for first message"); + yield extension.awaitMessage("first run"); + info("Waiting for second message"); + yield extension.awaitFinish("second run"); + info("Received both messages"); + + yield extension.unload(); +}); + +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_background_generated_url.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test _generated_background_page.html</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + +<script> +"use strict"; + +add_task(function* test_url_of_generated_background_page() { + function backgroundScript() { + const EXPECTED_URL = browser.runtime.getURL("/_generated_background_page.html"); + browser.test.assertEq(EXPECTED_URL, location.href); + browser.test.sendMessage("script done", EXPECTED_URL); + } + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + background: { + scripts: ["bg.js"], + }, + web_accessible_resources: ["_generated_background_page.html"], + }, + files: { + "bg.js": `(${backgroundScript})();`, + }, + }); + + yield extension.startup(); + const EXPECTED_URL = yield extension.awaitMessage("script done"); + + let win = window.open(EXPECTED_URL); + ok(win, "Should open new tab at URL: " + EXPECTED_URL); + yield extension.awaitMessage("script done"); + win.close(); + + yield extension.unload(); +}); + +</script> +</body> +</html>
--- a/toolkit/components/utils/simpleServices.js +++ b/toolkit/components/utils/simpleServices.js @@ -23,16 +23,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); function AddonPolicyService() { this.wrappedJSObject = this; this.cspStrings = new Map(); + this.backgroundPageUrlCallbacks = new Map(); this.mayLoadURICallbacks = new Map(); this.localizeCallbacks = new Map(); XPCOMUtils.defineLazyPreferenceGetter( this, "baseCSP", "extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " + "object-src 'self' https://* moz-extension: blob: filesystem:;"); @@ -50,16 +51,26 @@ AddonPolicyService.prototype = { * to the extension with the given ID. This may be either a custom policy, * if one was supplied, or the default policy if one was not. */ getAddonCSP(aAddonId) { let csp = this.cspStrings.get(aAddonId); return csp || this.defaultCSP; }, + /** + * Returns the generated background page as a data-URI, if any. If the addon + * does not have an auto-generated background page, an empty string is + * returned. + */ + getGeneratedBackgroundPageUrl(aAddonId) { + let cb = this.backgroundPageUrlCallbacks.get(aAddonId); + return cb && cb(aAddonId) || ''; + }, + /* * Invokes a callback (if any) associated with the addon to determine whether * unprivileged code running within the addon is allowed to perform loads from * the given URI. * * @see nsIAddonPolicyService.addonMayLoadURI */ addonMayLoadURI(aAddonId, aURI) { @@ -129,16 +140,27 @@ AddonPolicyService.prototype = { setAddonCSP(aAddonId, aCSPString) { if (aCSPString) { this.cspStrings.set(aAddonId, aCSPString); } else { this.cspStrings.delete(aAddonId); } }, + /** + * Set the callback that generates a data-URL for the background page. + */ + setBackgroundPageUrlCallback(aAddonId, aCallback) { + if (aCallback) { + this.backgroundPageUrlCallbacks.set(aAddonId, aCallback); + } else { + this.backgroundPageUrlCallbacks.delete(aAddonId); + } + }, + /* * Sets the callbacks used by the stream converter service to localize * add-on resources. */ setAddonLocalizeCallback(aAddonId, aCallback) { if (aCallback) { this.localizeCallbacks.set(aAddonId, aCallback); } else {