Bug 1455458: De-XPIDLify the blocklist service. r?gijs draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 19 Apr 2018 16:01:24 -0700
changeset 785379 d2575784070c7761dd7ae7076a63cfc57f5e948b
parent 784795 84e73c51c90f199f4abf5b6dc9b6523b75647559
push id107208
push usermaglione.k@gmail.com
push dateThu, 19 Apr 2018 23:41:23 +0000
reviewersgijs
bugs1455458
milestone61.0a1
Bug 1455458: De-XPIDLify the blocklist service. r?gijs Going through XPConnect for JS-to-JS access in the blocklist service adds no benefit, but does add a lot of overhead and maintenance burden. MozReview-Commit-ID: Lf1mDK0b0B0
js/xpconnect/loader/XPCOMUtils.jsm
mobile/android/components/BlocklistPrompt.js
toolkit/modules/Services.jsm
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js
xpcom/system/nsIBlocklistService.idl
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -253,17 +253,20 @@ var XPCOMUtils = {
    * @param aInterfaceName
    *        The name of the interface to query the service to.
    */
   defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName,
                                                                  aContract,
                                                                  aInterfaceName)
   {
     this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
-      return Cc[aContract].getService(Ci[aInterfaceName]);
+      if (aInterfaceName) {
+        return Cc[aContract].getService(Ci[aInterfaceName]);
+      }
+      return Cc[aContract].getService().wrappedJSObject;
     });
   },
 
   /**
    * Defines a lazy service getter on a specified object for each
    * property in the given object.
    *
    * @param aObject
--- a/mobile/android/components/BlocklistPrompt.js
+++ b/mobile/android/components/BlocklistPrompt.js
@@ -5,17 +5,19 @@
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 // -----------------------------------------------------------------------
 // BlocklistPrompt Service
 // -----------------------------------------------------------------------
 
 
-function BlocklistPrompt() { }
+function BlocklistPrompt() {
+  this.wrappedJSObject = this;
+}
 
 BlocklistPrompt.prototype = {
   prompt: function(aAddons, aCount) {
     let win = Services.wm.getMostRecentWindow("navigator:browser");
     if (win.ExtensionsView.visible) {
       win.ExtensionsView.showRestart("blocked");
     } else {
       let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
@@ -44,12 +46,12 @@ BlocklistPrompt.prototype = {
     for (let i = 0; i < aAddons.length; i++) {
       if (aAddons[i].item instanceof Ci.nsIPluginTag)
         aAddons[i].item.disabled = true;
       else
         aAddons[i].item.userDisabled = true;
     }
   },
   classID: Components.ID("{4e6ea350-b09a-11df-94e2-0800200c9a66}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBlocklistPrompt])
+  QueryInterface: XPCOMUtils.generateQI([])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BlocklistPrompt]);
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -87,17 +87,17 @@ var initTable = {
   wm: ["@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"],
   ww: ["@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"],
   startup: ["@mozilla.org/toolkit/app-startup;1", "nsIAppStartup"],
   sysinfo: ["@mozilla.org/system-info;1", "nsIPropertyBag2"],
   clipboard: ["@mozilla.org/widget/clipboard;1", "nsIClipboard"],
   DOMRequest: ["@mozilla.org/dom/dom-request-service;1", "nsIDOMRequestService"],
   focus: ["@mozilla.org/focus-manager;1", "nsIFocusManager"],
   uriFixup: ["@mozilla.org/docshell/urifixup;1", "nsIURIFixup"],
-  blocklist: ["@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
+  blocklist: ["@mozilla.org/extensions/blocklist;1"],
   netUtils: ["@mozilla.org/network/util;1", "nsINetUtil"],
   loadContextInfo: ["@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
   qms: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
 };
 
 if (AppConstants.platform == "android") {
   initTable.androidBridge = ["@mozilla.org/android/bridge;1", "nsIAndroidBridge"];
 }
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -103,32 +103,46 @@ var MockAsyncShutdown = {
 AMscope.AsyncShutdown = MockAsyncShutdown;
 
 class MockBlocklist {
   constructor(addons) {
     if (ChromeUtils.getClassName(addons) === "Object") {
       addons = new Map(Object.entries(addons));
     }
     this.addons = addons;
+    this.wrappedJSObject = this;
+
+    // Copy blocklist constants.
+    for (let [k, v] of Object.entries(Ci.nsIBlocklistService)) {
+      if (typeof v === "number") {
+        this[k] = v;
+      }
+    }
   }
 
   get contractID() {
     return "@mozilla.org/extensions/blocklist;1";
   }
 
+  _reLazifyService() {
+    XPCOMUtils.defineLazyServiceGetter(Services, "blocklist", this.contractID);
+  }
+
   register() {
     this.originalCID = MockRegistrar.register(this.contractID, this);
+    this._reLazifyService();
   }
 
   unregister() {
     MockRegistrar.unregister(this.originalCID);
+    this._reLazifyService();
   }
 
   getAddonBlocklistState(addon, appVersion, toolkitVersion) {
-    return this.addons.get(addon.id, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    return this.addons.get(addon.id) || Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   }
 
   async getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
     await Promise.resolve();
     let state = this.getAddonBlocklistState(addon, appVersion, toolkitVersion);
     if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
       return {
         state,
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -38,17 +38,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   loadManifestFromFile: "resource://gre/modules/addons/XPIInstall.jsm",
   verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.jsm",
 });
 
 const {nsIBlocklistService} = Ci;
 
 XPCOMUtils.defineLazyServiceGetters(this, {
-  Blocklist: ["@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
   AddonPolicyService: ["@mozilla.org/addons/policy-service;1", "nsIAddonPolicyService"],
   aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
 });
 
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
 });
 
@@ -4498,41 +4497,41 @@ AddonInternal.prototype = {
     if (staticItem) {
       let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
       return {
         state: staticItem.level,
         url: url.replace(/%blockID%/g, staticItem.blockID)
       };
     }
 
-    return Blocklist.getAddonBlocklistEntry(this.wrapper);
+    return Services.blocklist.getAddonBlocklistEntry(this.wrapper);
   },
 
   async updateBlocklistState(options = {}) {
     let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options;
 
     if (oldAddon) {
       this.userDisabled = oldAddon.userDisabled;
       this.softDisabled = oldAddon.softDisabled;
       this.blocklistState = oldAddon.blocklistState;
     }
     let oldState = this.blocklistState;
 
     let entry = await this.findBlocklistEntry();
-    let newState = entry ? entry.state : Blocklist.STATE_NOT_BLOCKED;
+    let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED;
 
     this.blocklistState = newState;
     this.blocklistURL = entry && entry.url;
 
     let userDisabled, softDisabled;
     // After a blocklist update, the blocklist service manually applies
     // new soft blocks after displaying a UI, in which cases we need to
     // skip updating it here.
     if (applySoftBlock && oldState != newState) {
-      if (newState == Blocklist.STATE_SOFTBLOCKED) {
+      if (newState == Services.blocklist.STATE_SOFTBLOCKED) {
         if (this.type == "theme") {
           userDisabled = true;
         } else {
           softDisabled = !this.userDisabled;
         }
       } else {
         softDisabled = false;
       }
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -17,20 +17,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
   AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
   DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
-XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
-                                   "@mozilla.org/extensions/blocklist;1",
-                                   Ci.nsIBlocklistService);
-
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.xpi-utils";
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 // Create a new logger for use by the Addons XPI Provider Utils
 // (Requires AddonManager.jsm)
@@ -1541,17 +1537,17 @@ this.XPIDatabaseReconcile = {
 
           if (currentAddon.type == "webextension-theme")
             currentAddon.userDisabled = !isActive;
 
           // If the add-on wasn't active and it isn't already disabled in some way
           // then it was probably either softDisabled or userDisabled
           if (!isActive && !currentAddon.disabled) {
             // If the add-on is softblocked then assume it is softDisabled
-            if (currentAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
+            if (currentAddon.blocklistState == Services.blocklist.STATE_SOFTBLOCKED)
               currentAddon.softDisabled = true;
             else
               currentAddon.userDisabled = true;
           }
         } else {
           // This is a new install
           if (currentAddon.foreignInstall)
             AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -1,16 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 
 "use strict";
 
+/* eslint "valid-jsdoc": [2, {requireReturn: false}] */
+
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 try {
   // AddonManager.jsm doesn't allow itself to be imported in the child
   // process. We're used in the child process (for now), so guard against
   // this.
@@ -71,16 +73,41 @@ const VULNERABILITYSTATUS_UPDATE_AVAILAB
 const VULNERABILITYSTATUS_NO_UPDATE        = 2;
 
 const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
 
 var gLoggingEnabled = null;
 var gBlocklistEnabled = true;
 var gBlocklistLevel = DEFAULT_LEVEL;
 
+/**
+ * @class nsIBlocklistPrompt
+ *
+ * nsIBlocklistPrompt is used, if available, by the default implementation of
+ * nsIBlocklistService to display a confirmation UI to the user before blocking
+ * extensions/plugins.
+ */
+/**
+ * @method prompt
+ *
+ * Prompt the user about newly blocked addons. The prompt is then resposible
+ * for soft-blocking any addons that need to be afterwards
+ *
+ * @param {object[]} aAddons
+ *         An array of addons and plugins that are blocked. These are javascript
+ *         objects with properties:
+ *          name    - the plugin or extension name,
+ *          version - the version of the extension or plugin,
+ *          icon    - the plugin or extension icon,
+ *          disable - can be used by the nsIBlocklistPrompt to allows users to decide
+ *                    whether a soft-blocked add-on should be disabled,
+ *          blocked - true if the item is hard-blocked, false otherwise,
+ *          item    - the nsIPluginTag or Addon object
+ */
+
 // From appinfo in Services.jsm. It is not possible to use the one in
 // Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in
 // xpcshell tests due to other code calling Services.appinfo before the
 // nsIXULAppInfo is created by the tests.
 XPCOMUtils.defineLazyGetter(this, "gApp", function() {
   // eslint-disable-next-line mozilla/use-services
   let appinfo = Cc["@mozilla.org/xre/app-info;1"]
                   .getService(Ci.nsIXULRuntime);
@@ -131,18 +158,18 @@ XPCOMUtils.defineLazyGetter(this, "gOSVe
     }
     osVersion = encodeURIComponent(osVersion);
   }
   return osVersion;
 });
 
 /**
  * Logs a string to the error console.
- * @param   string
- *          The string to write to the error console..
+ * @param {string} string
+ *        The string to write to the error console..
  */
 function LOG(string) {
   if (gLoggingEnabled) {
     dump("*** " + string + "\n");
     Services.console.logStringMessage(string);
   }
 }
 
@@ -160,16 +187,21 @@ function restartApp() {
   Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
 }
 
 /**
  * Checks whether this blocklist element is valid for the current OS and ABI.
  * If the element has an "os" attribute then the current OS must appear in
  * its comma separated list for the element to be valid. Similarly for the
  * xpcomabi attribute.
+ *
+ * @param {Element} blocklistElement
+ *        The blocklist element from an XML blocklist.
+ * @returns {bool}
+ *        Whether the entry matches the current OS.
  */
 function matchesOSABI(blocklistElement) {
   if (blocklistElement.hasAttribute("os")) {
     var choices = blocklistElement.getAttribute("os").split(",");
     if (choices.length > 0 && !choices.includes(gApp.OS))
       return false;
   }
 
@@ -181,34 +213,36 @@ function matchesOSABI(blocklistElement) 
 
   return true;
 }
 
 /**
  * Gets the current value of the locale.  It's possible for this preference to
  * be localized, so we have to do a little extra work here.  Similar code
  * exists in nsHttpHandler.cpp when building the UA string.
+ *
+ * @returns {string} The current requested locale.
  */
 function getLocale() {
   return Services.locale.getRequestedLocale();
 }
 
 /* Get the distribution pref values, from defaults only */
 function getDistributionPrefValue(aPrefName) {
   return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default");
 }
 
 /**
  * Parse a string representation of a regular expression. Needed because we
  * use the /pattern/flags form (because it's detectable), which is only
  * supported as a literal in JS.
  *
- * @param  aStr
+ * @param {string} aStr
  *         String representation of regexp
- * @return RegExp instance
+ * @return {RegExp} instance
  */
 function parseRegExp(aStr) {
   let lastSlash = aStr.lastIndexOf("/");
   let pattern = aStr.slice(1, lastSlash);
   let flags = aStr.slice(lastSlash + 1);
   return new RegExp(pattern, flags);
 }
 
@@ -226,16 +260,24 @@ function Blocklist() {
   gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                              MAX_BLOCK_LEVEL);
   Services.prefs.addObserver("extensions.blocklist.", this);
   Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
   this.wrappedJSObject = this;
 }
 
 Blocklist.prototype = {
+  STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+  STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
+  STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,
+  STATE_OUTDATED: Ci.nsIBlocklistService.STATE_OUTDATED,
+  STATE_VULNERABLE_UPDATE_AVAILABLE: Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE,
+  STATE_VULNERABLE_NO_UPDATE: Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE,
+
+
   /**
    * Extension ID -> array of Version Ranges
    * Each value in the version range array is a JS Object that has the
    * following properties:
    *   "minVersion"  The minimum version in a version range (default = 0)
    *   "maxVersion"  The maximum version in a version range (default = *)
    *   "targetApps"  Application ID -> array of Version Ranges
    *                 (default = current application ID)
@@ -282,41 +324,53 @@ Blocklist.prototype = {
                                      MAX_BLOCK_LEVEL);
           this._blocklistUpdated(null, null);
           break;
       }
       break;
     }
   },
 
-  /* See nsIBlocklistService */
+  /**
+   * Determine the blocklist state of an add-on
+   * @param {Addon} addon
+   *        The addon item to be checked.
+   * @param {string?} appVersion
+   *        The version of the application we are checking in the blocklist.
+   *        If this parameter is null, the version of the running application
+   *        is used.
+   * @param {string?} toolkitVersion
+   *        The version of the toolkit we are checking in the blocklist.
+   *        If this parameter is null, the version of the running toolkit
+   *        is used.
+   * @returns {integer} The STATE constant.
+   */
   getAddonBlocklistState(addon, appVersion, toolkitVersion) {
     if (!this.isLoaded)
       this._loadBlocklist();
     return this._getAddonBlocklistState(addon, this._addonEntries,
                                         appVersion, toolkitVersion);
   },
 
   /**
    * Returns a matching blocklist entry for the given add-on, if one
    * exists.
    *
-   * @param   id
-   *          The ID of the item to get the blocklist state for.
-   * @param   version
-   *          The version of the item to get the blocklist state for.
-   * @param   addonEntries
-   *          The add-on blocklist entries to compare against.
-   * @param   appVersion
-   *          The application version to compare to, will use the current
-   *          version if null.
-   * @param   toolkitVersion
-   *          The toolkit version to compare to, will use the current version if
-   *          null.
-   * @returns A blocklist entry for this item, with `state` and `url`
+   * @param {Addon} addon
+   *        The add-on object of the item to get the blocklist state for.
+   * @param {object[]} addonEntries
+   *        The add-on blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {object?}
+   *          A blocklist entry for this item, with `state` and `url`
    *          properties indicating the block state and URL, if there is
    *          a matching blocklist entry, or null otherwise.
    */
   _getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion) {
     if (!gBlocklistEnabled)
       return null;
 
     // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
@@ -339,53 +393,74 @@ Blocklist.prototype = {
                   Ci.nsIBlocklistService.STATE_BLOCKED : Ci.nsIBlocklistService.STATE_SOFTBLOCKED),
           url: blItem.blockID && this._createBlocklistURL(blItem.blockID),
         };
       }
     }
     return null;
   },
 
+  /**
+   * Returns a promise that resolves to the blocklist entry.
+   * The blocklist entry is an object with `state` and `url`
+   * properties, if a blocklist entry for the add-on exists, or null
+   * otherwise.
+
+   * @param {Addon} addon
+   *          The addon object to match.
+   * @param {string?} appVersion
+   *        The version of the application we are checking in the blocklist.
+   *        If this parameter is null, the version of the running application
+   *        is used.
+   * @param {string?} toolkitVersion
+   *        The version of the toolkit we are checking in the blocklist.
+   *        If this parameter is null, the version of the running toolkit
+   *        is used.
+   * @returns {Promise<object?>}
+   *        The blocklist entry for the add-on, if one exists, or null
+   *        otherwise.
+   */
   async getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
     await this.loadBlocklistAsync();
     return this._getAddonBlocklistEntry(addon, this._addonEntries,
                                         appVersion, toolkitVersion);
   },
 
   /**
    * Private version of getAddonBlocklistState that allows the caller to pass in
    * the add-on blocklist entries to compare against.
    *
-   * @param   id
-   *          The ID of the item to get the blocklist state for.
-   * @param   version
-   *          The version of the item to get the blocklist state for.
-   * @param   addonEntries
-   *          The add-on blocklist entries to compare against.
-   * @param   appVersion
-   *          The application version to compare to, will use the current
-   *          version if null.
-   * @param   toolkitVersion
-   *          The toolkit version to compare to, will use the current version if
-   *          null.
-   * @returns The blocklist state for the item, one of the STATE constants as
-   *          defined in nsIBlocklistService.
+   * @param {Addon} addon
+   *        The add-on object of the item to get the blocklist state for.
+   * @param {object[]} addonEntries
+   *        The add-on blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {integer}
+   *        The blocklist state for the item, one of the STATE constants as
+   *        defined in nsIBlocklistService.
    */
   _getAddonBlocklistState(addon, addonEntries, appVersion, toolkitVersion) {
     let entry = this._getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion);
     if (entry)
       return entry.state;
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
   /**
    * Returns the set of prefs of the add-on stored in the blocklist file
    * (probably to revert them on disabling).
-   * @param addon
+   * @param {Addon} addon
    *        The add-on whose to-be-reset prefs are to be found.
+   * @returns {string[]}
+   *        An array of preference names.
    */
   _getAddonPrefs(addon) {
     let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
     return entry.prefs.slice(0);
   },
 
   _findMatchingAddonEntry(aAddonEntries, aAddon) {
     if (!aAddon)
@@ -751,28 +826,34 @@ Blocklist.prototype = {
         } catch (ex) {}
       }
     }
 
     if (text)
       this._loadBlocklistFromString(text);
   },
 
+  /**
+   * Whether or not we've finished loading the blocklist.
+   */
   get isLoaded() {
     return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   /* Used for testing */
   _clear() {
     this._addonEntries = null;
     this._gfxEntries = null;
     this._pluginEntries = null;
     delete this._preloadPromise;
   },
 
+  /**
+   * Trigger loading the blocklist content asynchronously.
+   */
   async loadBlocklistAsync() {
     if (this.isLoaded) {
       return;
     }
     if (!this._preloadPromise) {
       this._preloadPromise = this._loadBlocklistAsyncInternal();
     }
     await this._preloadPromise;
@@ -1072,28 +1153,29 @@ Blocklist.prototype = {
     return this._getPluginBlocklistState(plugin, this._pluginEntries,
                                          appVersion, toolkitVersion);
   },
 
   /**
    * Private helper to get the blocklist entry for a plugin given a set of
    * blocklist entries and versions.
    *
-   * @param   plugin
-   *          The nsIPluginTag to get the blocklist state for.
-   * @param   pluginEntries
-   *          The plugin blocklist entries to compare against.
-   * @param   appVersion
-   *          The application version to compare to, will use the current
-   *          version if null.
-   * @param   toolkitVersion
-   *          The toolkit version to compare to, will use the current version if
-   *          null.
-   * @returns {entry: blocklistEntry, version: blocklistEntryVersion},
-   *          or null if there is no matching entry.
+   * @param {nsIPluginTag} plugin
+   *        The nsIPluginTag to get the blocklist state for.
+   * @param {object[]} pluginEntries
+   *        The plugin blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {object?}
+   *        {entry: blocklistEntry, version: blocklistEntryVersion},
+   *        or null if there is no matching entry.
    */
   _getPluginBlocklistEntry(plugin, pluginEntries, appVersion, toolkitVersion) {
     if (!gBlocklistEnabled)
       return null;
 
     // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
     if (!appVersion && !gApp.version)
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
@@ -1133,28 +1215,29 @@ Blocklist.prototype = {
 
     return null;
   },
 
   /**
    * Private version of getPluginBlocklistState that allows the caller to pass in
    * the plugin blocklist entries.
    *
-   * @param   plugin
-   *          The nsIPluginTag to get the blocklist state for.
-   * @param   pluginEntries
-   *          The plugin blocklist entries to compare against.
-   * @param   appVersion
-   *          The application version to compare to, will use the current
-   *          version if null.
-   * @param   toolkitVersion
-   *          The toolkit version to compare to, will use the current version if
-   *          null.
-   * @returns The blocklist state for the item, one of the STATE constants as
-   *          defined in nsIBlocklistService.
+   * @param {nsIPluginTag} plugin
+   *        The nsIPluginTag to get the blocklist state for.
+   * @param {object[]} pluginEntries
+   *        The plugin blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {integer}
+   *        The blocklist state for the item, one of the STATE constants as
+   *        defined in nsIBlocklistService.
    */
   _getPluginBlocklistState(plugin, pluginEntries, appVersion, toolkitVersion) {
 
     let r = this._getPluginBlocklistEntry(plugin, pluginEntries,
                                           appVersion, toolkitVersion);
     if (!r) {
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
     }
@@ -1345,17 +1428,17 @@ Blocklist.prototype = {
     if (addonList.length == 0) {
       this._notifyObserversBlocklistUpdated();
       return;
     }
 
     if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
       try {
         let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
-                               .getService(Ci.nsIBlocklistPrompt);
+                               .getService().wrappedJSObject;
         blockedPrompter.prompt(addonList);
       } catch (e) {
         LOG(e);
       }
       this._notifyObserversBlocklistUpdated();
       return;
     }
 
@@ -1414,17 +1497,17 @@ Blocklist.prototype = {
   },
 
   classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIBlocklistService,
                                          Ci.nsITimerCallback]),
 };
 
-/**
+/*
  * Helper for constructing a blocklist.
  */
 function BlocklistItemData(versionRangeElement) {
   var versionRange = this.getBlocklistVersionRange(versionRangeElement);
   this.minVersion = versionRange.minVersion;
   this.maxVersion = versionRange.maxVersion;
   if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
     this.severity = versionRangeElement.getAttribute("severity");
@@ -1456,24 +1539,25 @@ function BlocklistItemData(versionRangeE
     this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
 }
 
 BlocklistItemData.prototype = {
   /**
    * Tests if a version of an item is included in the version range and target
    * application information represented by this BlocklistItemData using the
    * provided application and toolkit versions.
-   * @param   version
-   *          The version of the item being tested.
-   * @param   appVersion
-   *          The application version to test with.
-   * @param   toolkitVersion
-   *          The toolkit version to test with.
-   * @returns True if the version range covers the item version and application
-   *          or toolkit version.
+   * @param {string} version
+   *        The version of the item being tested.
+   * @param {string} appVersion
+   *        The application version to test with.
+   * @param {string} toolkitVersion
+   *        The toolkit version to test with.
+   * @returns {boolean}
+   *        True if the version range covers the item version and application
+   *        or toolkit version.
    */
   includesItem(version, appVersion, toolkitVersion) {
     // Some platforms have no version for plugins, these don't match if there
     // was a min/maxVersion provided
     if (!version && (this.minVersion || this.maxVersion))
       return false;
 
     // Check if the item version matches
@@ -1486,41 +1570,44 @@ BlocklistItemData.prototype = {
 
     // Check if the toolkit version matches
     return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
   },
 
   /**
    * Checks if a version is higher than or equal to the minVersion (if provided)
    * and lower than or equal to the maxVersion (if provided).
-   * @param   version
-   *          The version to test.
-   * @param   minVersion
-   *          The minimum version. If null it is assumed that version is always
-   *          larger.
-   * @param   maxVersion
-   *          The maximum version. If null it is assumed that version is always
-   *          smaller.
+   * @param {string} version
+   *        The version to test.
+   * @param {string?} minVersion
+   *        The minimum version. If null it is assumed that version is always
+   *        larger.
+   * @param {string?} maxVersion
+   *        The maximum version. If null it is assumed that version is always
+   *        smaller.
+   * @returns {boolean}
+   *        Whether the item matches the range.
    */
   matchesRange(version, minVersion, maxVersion) {
     if (minVersion && Services.vc.compare(version, minVersion) < 0)
       return false;
     if (maxVersion && Services.vc.compare(version, maxVersion) > 0)
       return false;
     return true;
   },
 
   /**
    * Tests if there is a matching range for the given target application id and
    * version.
-   * @param   appID
-   *          The application ID to test for, may be for an application or toolkit
-   * @param   appVersion
-   *          The version of the application to test for.
-   * @returns True if this version range covers the application version given.
+   * @param {string} appID
+   *        The application ID to test for, may be for an application or toolkit
+   * @param {string} appVersion
+   *        The version of the application to test for.
+   * @returns {boolean}
+   *        True if this version range covers the application version given.
    */
   matchesTargetRange(appID, appVersion) {
     var blTargetApp = this.targetApps[appID];
     if (!blTargetApp)
       return false;
 
     for (let app of blTargetApp) {
       if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
@@ -1528,19 +1615,20 @@ BlocklistItemData.prototype = {
     }
 
     return false;
   },
 
   /**
    * Retrieves a version range (e.g. minVersion and maxVersion) for a
    * blocklist item's targetApplication element.
-   * @param   targetAppElement
-   *          A targetApplication blocklist element.
-   * @returns An array of JS objects with the following properties:
+   * @param {Element} targetAppElement
+   *        A targetApplication blocklist element.
+   * @returns {object[]}
+   *        An array of JS objects with the following properties:
    *          "minVersion"  The minimum version in a version range (default = null).
    *          "maxVersion"  The maximum version in a version range (default = null).
    */
   getBlocklistAppVersions(targetAppElement) {
     var appVersions = [ ];
 
     if (targetAppElement) {
       for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
@@ -1556,19 +1644,22 @@ BlocklistItemData.prototype = {
     if (appVersions.length == 0)
       appVersions.push(this.getBlocklistVersionRange(null));
     return appVersions;
   },
 
   /**
    * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
    * versionRange element.
-   * @param   versionRangeElement
-   *          The versionRange blocklist element.
-   * @returns A JS object with the following properties:
+   *
+   * @param {Element} versionRangeElement
+   *        The versionRange blocklist element.
+   *
+   * @returns {Object}
+   *        A JS object with the following properties:
    *          "minVersion"  The minimum version in a version range (default = null).
    *          "maxVersion"  The maximum version in a version range (default = null).
    */
   getBlocklistVersionRange(versionRangeElement) {
     var minVersion = null;
     var maxVersion = null;
     if (!versionRangeElement)
       return { minVersion, maxVersion };
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
@@ -234,21 +234,23 @@ var PluginHost = {
     countRef.value = PLUGINS.length;
     return PLUGINS;
   },
 
   QueryInterface: XPCOMUtils.generateQI(["nsIPluginHost"]),
 };
 
 var BlocklistPrompt = {
+  get wrappedJSObject() { return this; },
+
   prompt(list) {
     gNewBlocks = list.map(item => `${item.name} ${item.version}`);
   },
 
-  QueryInterface: XPCOMUtils.generateQI(["nsIBlocklistPrompt"]),
+  QueryInterface: XPCOMUtils.generateQI([]),
 };
 
 
 async function loadBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
@@ -15,23 +15,21 @@ function get_test_plugintag() {
   return null;
 }
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
   copyBlocklistToProfile(do_get_file("data/test_bug514327_2.xml"));
 
-  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
-
   Services.prefs.setBoolPref("plugin.load_flash_only", false);
 
   var plugin = get_test_plugintag();
   if (!plugin)
     do_throw("Plugin tag not found");
 
   // run the code after the blocklist is closed
   Services.obs.notifyObservers(null, "addon-blocklist-closed");
   executeSoon(function() {
     // should be marked as outdated by the blocklist
-    Assert.ok(blocklist.getPluginBlocklistState(plugin, "1", "1.9") == nsIBLS.STATE_OUTDATED);
+    Assert.ok(Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9") == nsIBLS.STATE_OUTDATED);
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
@@ -41,26 +41,28 @@ var PluginHost = {
     countRef.value = PLUGINS.length;
     return PLUGINS;
   },
 
   QueryInterface: XPCOMUtils.generateQI(["nsIPluginHost"]),
 };
 
 var BlocklistPrompt = {
+  get wrappedJSObject() { return this; },
+
   prompt(list) {
     // Should only include one item
     Assert.equal(list.length, 1);
     // And that item should be the blocked plugin, not the outdated one
     var item = list[0];
     Assert.ok(item.item instanceof Ci.nsIPluginTag);
     Assert.notEqual(item.name, "test_bug514327_outdated");
   },
 
-  QueryInterface: XPCOMUtils.generateQI(["nsIBlocklistPrompt"]),
+  QueryInterface: XPCOMUtils.generateQI([]),
 };
 
 
 async function loadBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
@@ -50,17 +50,17 @@ var PLUGINS = [{
   // not in the blocklist -> not blocked
   name: "test_plugin_5",
   version: "5",
   disabled: false,
   blocklisted: false
 }];
 
 function test_basic() {
-  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
+  var {blocklist} = Services;
 
   Assert.ok(blocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9") == nsIBLS.STATE_OUTDATED);
 
   Assert.ok(blocklist.getPluginBlocklistState(PLUGINS[1], "1", "1.9") == nsIBLS.STATE_VULNERABLE_UPDATE_AVAILABLE);
 
   Assert.ok(blocklist.getPluginBlocklistState(PLUGINS[2], "1", "1.9") == nsIBLS.STATE_VULNERABLE_NO_UPDATE);
 
   Assert.ok(blocklist.getPluginBlocklistState(PLUGINS[3], "1", "1.9") == nsIBLS.STATE_BLOCKED);
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js
@@ -41,17 +41,19 @@ var servicesASTParser = {
     if (node.declarations.length === 1 &&
         node.declarations[0].id &&
         helpers.getIsGlobalScope(parents) &&
         node.declarations[0].init.type === "ObjectExpression") {
       let name = node.declarations[0].id.name;
       let interfaces = {};
 
       for (let property of node.declarations[0].init.properties) {
-        interfaces[property.key.name] = property.value.elements[1].value;
+        if (property.value.elements.length > 1) {
+          interfaces[property.key.name] = property.value.elements[1].value;
+        }
       }
 
       this.identifiers[name] = interfaces;
     }
   },
 
   /**
    * This looks for any additions to the global variable declarations, and adds
--- a/xpcom/system/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -23,34 +23,16 @@ interface nsIBlocklistService : nsISuppo
   // update available.
   const unsigned long STATE_OUTDATED    = 3;
   // Indicates that the item is vulnerable and there is an update.
   const unsigned long STATE_VULNERABLE_UPDATE_AVAILABLE = 4;
   // Indicates that the item is vulnerable and there is no update.
   const unsigned long STATE_VULNERABLE_NO_UPDATE = 5;
 
   /**
-   * Determine the blocklist state of an add-on
-   * @param   id
-   *          The addon item to be checked.
-   * @param   appVersion
-   *          The version of the application we are checking in the blocklist.
-   *          If this parameter is null, the version of the running application
-   *          is used.
-   * @param   toolkitVersion
-   *          The version of the toolkit we are checking in the blocklist.
-   *          If this parameter is null, the version of the running toolkit
-   *          is used.
-   * @returns The STATE constant.
-   */
-  unsigned long getAddonBlocklistState(in jsval addon,
-                                       [optional] in AString appVersion,
-                                       [optional] in AString toolkitVersion);
-
-  /**
    * Determine the blocklist state of a plugin
    * @param   plugin
    *          The plugin to get the state for
    * @param   appVersion
    *          The version of the application we are checking in the blocklist.
    *          If this parameter is null, the version of the running application
    *          is used.
    * @param   toolkitVersion
@@ -58,85 +40,10 @@ interface nsIBlocklistService : nsISuppo
    *          If this parameter is null, the version of the running toolkit
    *          is used.
    * @returns The STATE constant.
    */
   unsigned long getPluginBlocklistState(in nsIPluginTag plugin,
                                         [optional] in AString appVersion,
                                         [optional] in AString toolkitVersion);
 
-  /**
-   * Returns a promise that resolves to the blocklist entry.
-   * The blocklist entry is an object with `state` and `url`
-   * properties, if a blocklist entry for the add-on exists, or null
-   * otherwise.
-
-   * @param   addon
-   *          The addon object to match.
-   * @param   appVersion
-   *          The version of the application we are checking in the blocklist.
-   *          If this parameter is null, the version of the running application
-   *          is used.
-   * @param   toolkitVersion
-   *          The version of the toolkit we are checking in the blocklist.
-   *          If this parameter is null, the version of the running toolkit
-   *          is used.
-   */
-  jsval getAddonBlocklistEntry(in jsval addon,
-                               [optional] in AString appVersion,
-                               [optional] in AString toolkitVersion);
-
-  /**
-   * Determine the blocklist web page of a plugin.
-   * @param   plugin
-   *          The blocked plugin that we are determining the web page for.
-   * @returns The URL of the description page.
-   */
-  AString getPluginBlocklistURL(in nsIPluginTag plugin);
-
-  /**
-   * Determine the blocklist infoURL of a plugin.
-   * @param   plugin
-   *          The blocked plugin that we are determining the infoURL for.
-   * @returns The preferred URL to present the user, or |null| if
-   *          it is not available.
-   */
-  AString getPluginInfoURL(in nsIPluginTag plugin);
-
-  /**
-   * Whether or not we've finished loading the blocklist.
-   */
   readonly attribute boolean isLoaded;
-
-  /**
-   * Trigger loading the blocklist content asynchronously.
-   */
-  void loadBlocklistAsync();
 };
-
-/**
- * nsIBlocklistPrompt is used, if available, by the default implementation of 
- * nsIBlocklistService to display a confirmation UI to the user before blocking
- * extensions/plugins.
- */
-[scriptable, uuid(ba915921-b9c0-400d-8e4f-ca1b80c5699a)]
-interface nsIBlocklistPrompt : nsISupports
-{
-  /**
-   * Prompt the user about newly blocked addons. The prompt is then resposible
-   * for soft-blocking any addons that need to be afterwards
-   *
-   * @param  aAddons
-   *         An array of addons and plugins that are blocked. These are javascript
-   *         objects with properties:
-   *          name    - the plugin or extension name,
-   *          version - the version of the extension or plugin,
-   *          icon    - the plugin or extension icon,
-   *          disable - can be used by the nsIBlocklistPrompt to allows users to decide
-   *                    whether a soft-blocked add-on should be disabled,
-   *          blocked - true if the item is hard-blocked, false otherwise,
-   *          item    - the nsIPluginTag or Addon object
-   * @param  aCount
-   *         The number of addons
-   */
-  void prompt([array, size_is(aCount)] in nsIVariant aAddons,
-              [optional] in uint32_t aCount);
-};