diff --git a/toolkit/components/extensions/ExtensionManagement.jsm b/toolkit/components/extensions/ExtensionManagement.jsm index 2c7a3a323f22..0f8feb602941 100644 --- a/toolkit/components/extensions/ExtensionManagement.jsm +++ b/toolkit/components/extensions/ExtensionManagement.jsm @@ -15,6 +15,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/AppConstants.jsm"); +XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => { + let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {}); + return UUIDMap; +}); + /* * This file should be kept short and simple since it's loaded even * when no extensions are running. @@ -105,6 +110,15 @@ var APIs = { }, }; +function getURLForExtension(id, path = "") { + let uuid = UUIDMap.get(id, false); + if (!uuid) { + Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`); + return null; + } + return `moz-extension://${uuid}/${path}`; +} + // This object manages various platform-level issues related to // moz-extension:// URIs. It lives here so that it can be used in both // the parent and child processes. @@ -300,6 +314,8 @@ this.ExtensionManagement = { getFrameId: Frames.getId.bind(Frames), getParentFrameId: Frames.getParentId.bind(Frames), + getURLForExtension, + // exported API Level Helpers getAddonIdForWindow, getAPILevelForWindow, diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index b14a431dfec4..fe21b98b2926 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -29,6 +29,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData", "resource://gre/modules/Extension.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", + "resource://gre/modules/ExtensionManagement.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Locale", "resource://gre/modules/Locale.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", @@ -958,7 +960,9 @@ var loadManifestFromWebManifest = Task.async(function*(aUri) { addon.dependencies = Object.freeze(Array.from(extension.dependencies)); if (manifest.options_ui) { - addon.optionsURL = extension.getURL(manifest.options_ui.page); + // Store just the relative path here, the AddonWrapper getURL + // wrapper maps this to a full URL. + addon.optionsURL = manifest.options_ui.page; if (manifest.options_ui.open_in_tab) addon.optionsType = AddonManager.OPTIONS_TYPE_TAB; else @@ -7236,11 +7240,19 @@ AddonWrapper.prototype = { }, get optionsURL() { - let addon = addonFor(this); - if (this.isActive && addon.optionsURL) - return addon.optionsURL; + if (!this.isActive) { + return null; + } - if (this.isActive && this.hasResource("options.xul")) + let addon = addonFor(this); + if (addon.optionsURL) { + if (this.isWebExtension) { + return ExtensionManagement.getURLForExtension(addon.id, addon.optionsURL); + } + return addon.optionsURL; + } + + if (this.hasResource("options.xul")) return this.getResourceURI("options.xul").spec; return null; diff --git a/toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi b/toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi new file mode 100644 index 000000000000..a063fd1c43d3 Binary files /dev/null and b/toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi differ diff --git a/toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json b/toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json new file mode 100644 index 000000000000..e808cd5ab639 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json @@ -0,0 +1,11 @@ +{ + "manifest_version": 2, + + "name": "Test options_ui", + "description": "Test add-ons manager handling options_ui with no id in manifest.json", + "version": "1.2", + + "options_ui": { + "page": "options.html" + } +} diff --git a/toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html b/toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html new file mode 100644 index 000000000000..ea804601b5e8 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html @@ -0,0 +1,9 @@ + + + + + + +
+ + diff --git a/toolkit/mozapps/extensions/test/browser/browser-common.ini b/toolkit/mozapps/extensions/test/browser/browser-common.ini index 9c846663f318..bb9f8d0df695 100644 --- a/toolkit/mozapps/extensions/test/browser/browser-common.ini +++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini @@ -62,3 +62,4 @@ skip-if = os == 'win' # Disabled on Windows due to intermittent failures (bug 11 skip-if = buildapp == 'mulet' [browser_CTP_plugins.js] skip-if = buildapp == 'mulet' +[browser_webext_options.js] diff --git a/toolkit/mozapps/extensions/test/browser/browser_webext_options.js b/toolkit/mozapps/extensions/test/browser/browser_webext_options.js new file mode 100644 index 000000000000..543012ecc564 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_webext_options.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Wrapper to run a test that consists of: +// 1. opening the add-ons manager viewing the list of extensions +// 2. installing an extension (using the provider installer callable) +// 3. opening the preferences panel for the new extension and verifying +// that it opens cleanly +function* runTest(installer) { + let mgrWindow = yield open_manager("addons://list/extension"); + + let {addon, id} = yield installer(); + isnot(addon, null, "Extension is installed"); + + let element = get_addon_element(mgrWindow, id); + element.parentNode.ensureElementIsVisible(element); + + let button = mgrWindow.document.getAnonymousElementByAttribute(element, "anonid", "preferences-btn"); + is_element_visible(button, "Preferences button should be visible"); + + EventUtils.synthesizeMouseAtCenter(button, {clickCount: 1}, mgrWindow); + + yield TestUtils.topicObserved(AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, + (subject, data) => data == id); + + is(mgrWindow.gViewController.currentViewId, + `addons://detail/${encodeURIComponent(id)}/preferences`, + "Current view should scroll to preferences"); + + var browser = mgrWindow.document.querySelector("#detail-grid > rows > .inline-options-browser"); + var rows = browser.parentNode; + + ok(browser, "Grid should have a browser child"); + is(browser.localName, "browser", "Grid should have a browser child"); + is(browser.currentURI.spec, element.mAddon.optionsURL, "Browser has the expected options URL loaded") + + is(browser.clientWidth, rows.clientWidth, + "Browser should be the same width as its parent node"); + + button = mgrWindow.document.getElementById("detail-prefs-btn"); + is_element_hidden(button, "Preferences button should not be visible"); + + yield close_manager(mgrWindow); + + addon.uninstall(); +} + +// Test that deferred handling of optionsURL works for a signed webextension +add_task(function test_options_signed() { + return runTest(function*() { + // The extension in-tree is signed with this ID: + const ID = "{9792932b-32b2-4567-998c-e7bf6c4c5e35}"; + + yield install_addon("addons/options_signed.xpi"); + let addon = yield promiseAddonByID(ID); + + return {addon, id: ID}; + }); +}); + +add_task(function* test_options_temporary() { + return runTest(function*() { + let dir = get_addon_file_url("options_signed").file; + let addon = yield AddonManager.installTemporaryAddon(dir); + isnot(addon, null, "Extension is installed (temporarily)"); + + return {addon, id: addon.id}; + }); +});