Bug 1293721 - Handle options_ui properly when id isn't immediately available. r=kmag, a=sledru
authorAndrew Swan <aswan@mozilla.com>
Thu, 11 Aug 2016 09:59:03 -0700
changeset 349744 e6d0063c10d6ffcf56e2d1ae7ffb16116c845819
parent 349743 d4204af2ee53255b577907f4a1d9bf4ecb0f9476
child 349745 e1604afddd41b5757168a4fe1c9cdaa1ff7e8454
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag, sledru
bugs1293721
milestone50.0a2
Bug 1293721 - Handle options_ui properly when id isn't immediately available. r=kmag, a=sledru MozReview-Commit-ID: JQC8rZwUkXG
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/browser/addons/options_signed.xpi
toolkit/mozapps/extensions/test/browser/addons/options_signed/manifest.json
toolkit/mozapps/extensions/test/browser/addons/options_signed/options.html
toolkit/mozapps/extensions/test/browser/browser-common.ini
toolkit/mozapps/extensions/test/browser/browser_webext_options.js
--- 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, "getExtensionUUID", () => {
+  let {getExtensionUUID} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return getExtensionUUID;
+});
+
 /*
  * 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
@@ -80,16 +85,21 @@ var Frames = {
       case "Extension:RemoveTopWindowID":
         this.topWindowIds.delete(data.windowId);
         break;
     }
   },
 };
 Frames.init();
 
+function getURLForExtension(id, path = "") {
+  let uuid = getExtensionUUID(id);
+  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
@@ -272,13 +282,15 @@ function getAPILevelForWindow(window, ad
 
 this.ExtensionManagement = {
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   getFrameId: Frames.getId.bind(Frames),
   getParentFrameId: Frames.getParentId.bind(Frames),
 
+  getURLForExtension,
+
   // exported API Level Helpers
   getAddonIdForWindow,
   getAPILevelForWindow,
   API_LEVELS,
 };
--- 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");
@@ -79,16 +81,18 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "amIAddonPathService");
 
 XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
 
+Cu.importGlobalProperties(["URL"]);
+
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_INSTALL_CACHE              = "extensions.installCache";
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
@@ -920,17 +924,19 @@ var loadManifestFromWebManifest = Task.a
   addon.internalName = null;
   addon.updateURL = bss.update_url;
   addon.updateKey = null;
   addon.optionsURL = null;
   addon.optionsType = null;
   addon.aboutURL = null;
 
   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;
@@ -7142,21 +7148,34 @@ 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) {
+        // The internal object's optionsURL property comes from the addons
+        // DB and should be a relative URL.  However, extensions with
+        // options pages installed before bug 1293721 was fixed got absolute
+        // URLs in the addons db.  This code handles both cases.
+        let base = ExtensionManagement.getURLForExtension(addon.id);
+        return new URL(addon.optionsURL, base).href;
+      }
       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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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};
+  });
+});