Bug 1293721 Handle options_ui properly when id isn't immediately available r?kmag
MozReview-Commit-ID: JQC8rZwUkXG
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -10,16 +10,21 @@ const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
const Cr = Components.results;
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.
*/
// Keep track of frame IDs for content windows. Mostly we can just use
// the outer window ID as the frame ID. However, the API specifies
// that top-level windows have a frame ID of 0. So we need to keep
@@ -100,16 +105,25 @@ var APIs = {
if (!this.apis.has(namespace)) {
throw new Error(`API namespace does not exist: ${namespace}`);
}
this.apis.delete(namespace);
},
};
+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.
//
// moz-extension URIs have the form moz-extension://uuid/path. Each
// extension has its own UUID, unique to the machine it's installed
// on. This is easier and more secure than using the extension ID,
// since it makes it slightly harder to fingerprint for extensions if
@@ -295,15 +309,17 @@ this.ExtensionManagement = {
shutdownExtension: Service.shutdownExtension.bind(Service),
registerAPI: APIs.register.bind(APIs),
unregisterAPI: APIs.unregister.bind(APIs),
getFrameId: Frames.getId.bind(Frames),
getParentFrameId: Frames.getParentId.bind(Frames),
+ getURLForExtension,
+
// exported API Level Helpers
getAddonIdForWindow,
getAPILevelForWindow,
API_LEVELS,
APIs,
};
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -24,16 +24,18 @@ Cu.import("resource://gre/modules/Prefer
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
"resource://gre/modules/ChromeManifestParser.jsm");
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",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
"resource://gre/modules/ZipUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
@@ -953,17 +955,19 @@ var loadManifestFromWebManifest = Task.a
addon.updateURL = bss.update_url;
addon.updateKey = null;
addon.optionsURL = null;
addon.optionsType = null;
addon.aboutURL = null;
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
addon.optionsType = AddonManager.OPTIONS_TYPE_INLINE_BROWSER;
}
// WebExtensions don't use iconURLs
addon.iconURL = null;
@@ -7231,21 +7235,29 @@ AddonWrapper.prototype = {
return addonFor(this)._installLocation == TemporaryInstallLocation;
},
get aboutURL() {
return this.isActive ? addonFor(this)["aboutURL"] : null;
},
get optionsURL() {
+ if (!this.isActive) {
+ return null;
+ }
+
let addon = addonFor(this);
- if (this.isActive && addon.optionsURL)
+ if (addon.optionsURL) {
+ if (this.isWebExtension) {
+ return ExtensionManagement.getURLForExtension(addon.id, addon.optionsURL);
+ }
return addon.optionsURL;
-
- if (this.isActive && this.hasResource("options.xul"))
+ }
+
+ if (this.hasResource("options.xul"))
return this.getResourceURI("options.xul").spec;
return null;
},
get optionsType() {
if (!this.isActive)
return null;
new file mode 100644
index 0000000000000000000000000000000000000000..a063fd1c43d3cfa9ae7b2df388e29bf12494970b
GIT binary patch
literal 4560
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
XXRHpnhx2Cw&fVjC_gR@ge-Yq6B>)_M
new file mode 100644
--- /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"
+ }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ </head>
+ <body>
+ <div id="options-test-panel" />
+ </body>
+</html>
--- a/toolkit/mozapps/extensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini
@@ -57,8 +57,9 @@ skip-if = os == 'win' # Disabled on Wind
[browser_inlinesettings_browser.js]
[browser_inlinesettings_custom.js]
[browser_inlinesettings_info.js]
[browser_tabsettings.js]
[browser_pluginprefs.js]
skip-if = buildapp == 'mulet'
[browser_CTP_plugins.js]
skip-if = buildapp == 'mulet'
+[browser_webext_options.js]
new file mode 100644
--- /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};
+ });
+});