Bug 1378397 - Introduce shim for managing upcoming DevTools addon. r=jdescottes
MozReview-Commit-ID: GUJEwwR2rd6
--- a/devtools/shim/DevToolsShim.jsm
+++ b/devtools/shim/DevToolsShim.jsm
@@ -1,22 +1,37 @@
/* 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";
const Cu = Components.utils;
const Ci = Components.interfaces;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "console", () => {
+ let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
+ return new ConsoleAPI({
+ maxLogLevelPref: "devtools.addon.install-log",
+ prefix: "Devtools-Addon-Install",
+ });
+});
+
this.EXPORTED_SYMBOLS = [
"DevToolsShim",
];
+// ID defined in add-on's install.rdf file
+const ADDON_ID = "devtools@mozilla.org";
+
function removeItem(array, callback) {
let index = array.findIndex(callback);
if (index >= 0) {
array.splice(index, 1);
}
}
/**
@@ -255,16 +270,93 @@ this.DevToolsShim = {
for (let theme of this.themes) {
this._gDevTools.registerTheme(theme);
}
this.listeners = [];
this.tools = [];
this.themes = [];
},
+
+ async install(onProgress = function () {}) {
+ // First check if the add-on is disabled,
+ // and only re-enable it if that's the case.
+ // Also, do not do anything if the add-on is already insatlled.
+ let addon = await new Promise(resolve => {
+ AddonManager.getAddonByID(ADDON_ID, resolve);
+ });
+ if (addon) {
+ if (addon.userDisabled) {
+ addon.userDisabled = false;
+ }
+ onProgress(1);
+ return Promise.resolve();
+ }
+ return new Promise((resolve, reject) => {
+ let installFailureHandler = (install, message) => {
+ console.error("Install failure: " + message);
+ reject(message);
+ };
+ let listener = {
+ onDownloadStarted() {
+ this.status = "downloading";
+ console.log("Downloading");
+ onProgress(0);
+ },
+
+ onInstallStarted() {
+ this.status = "installing";
+ console.log("Installing");
+ },
+
+ onDownloadProgress(install) {
+ let progress = install.maxProgress == -1 ? -1 :
+ install.progress / install.maxProgress;
+ console.log("Progress: " + progress);
+ onProgress(progress);
+ },
+
+ onInstallEnded(event) {
+ event.addon.userDisabled = false;
+ console.log("Installed!");
+ resolve();
+ onProgress(1);
+ },
+
+ onDownloadEnded(install) {
+ // Assert that the addon we are trying to install has the right ID
+ if (install.addon.id != ADDON_ID) {
+ console.error("Install cancelled as addon id doesn't match devtools one");
+ install.cancel();
+ }
+ },
+ onDownloadCancelled(install) {
+ installFailureHandler(install, "Download cancelled");
+ },
+ onDownloadFailed(install) {
+ installFailureHandler(install, "Download failed");
+ },
+ onInstallCancelled(install) {
+ installFailureHandler(install, "Install cancelled");
+ },
+ onInstallFailed(install) {
+ installFailureHandler(install, "Install failed");
+ },
+ };
+ AddonManager.getInstallForURL(this.addonURL, install => {
+ install.addListener(listener);
+ install.install();
+ }, "application/x-xpinstall");
+ });
+ },
+
+ // Returns the xpi file URL
+ get addonURL() {
+ return Services.prefs.getCharPref("devtools.addon.install-url");
+ },
};
/**
* Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
*
* The methods below are used by classes and tests from addon-sdk/
* If DevTools are not installed when calling one of them, the call will throw.
*/
--- a/devtools/shim/devtools-startup-prefs.js
+++ b/devtools/shim/devtools-startup-prefs.js
@@ -12,8 +12,21 @@
pref("devtools.jsonview.enabled", true);
// Default theme ("dark" or "light")
#ifdef MOZ_DEV_EDITION
sticky_pref("devtools.theme", "dark");
#else
sticky_pref("devtools.theme", "light");
#endif
+
+// DevTools add-on is automatically installed on startup if:
+// - they were opened once,
+// - any DevTools command line argument is passed,
+// This pref allow to disable that.
+pref("devtools.addon.auto-install", true);
+
+// DevTools add-on install URL.
+pref("devtools.addon.install-url", "https://archive.mozilla.org/pub/labs/devtools/master/devtools.xpi");
+
+// Log level of /devtools/shim/devtools-startup.js managing addon installation
+// (Use "log" to see everything)
+pref("devtools.addon.install-log", "error");
--- a/devtools/shim/devtools-startup.js
+++ b/devtools/shim/devtools-startup.js
@@ -16,27 +16,29 @@
*
* Only once any of these entry point is fired, this module ensures starting
* core modules like 'devtools-browser.js' that hooks the browser windows
* and ensure setting up tools.
**/
"use strict";
-const { interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
"resource:///modules/CustomizableWidgets.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DevToolsShim",
+ "chrome://devtools-shim/content/DevToolsShim.jsm");
XPCOMUtils.defineLazyGetter(this, "Bundle", function () {
const kUrl = "chrome://devtools/locale/key-shortcuts.properties";
return Services.strings.createBundle(kUrl);
});
XPCOMUtils.defineLazyGetter(this, "KeyShortcuts", function () {
const isMac = AppConstants.platform == "macosx";
@@ -170,17 +172,49 @@ DevToolsStartup.prototype = {
*/
developerToggleCreated: false,
// Check if any command line argument specific to DevTools has been given
hasAnyDevToolsFlag(cmdLine) {
return DevToolsFlags.some(flag => cmdLine.findFlag(flag, false) != -1);
},
- handle: function (cmdLine) {
+ // Returns true if DevTools toolbox has been opened at least once for this profile
+ get hasDevToolsEverBeenOpened() {
+ // This telemetry pref is updated whenever we open the toolbox
+ return Services.prefs.prefHasUserValue("devtools.telemetry.tools.opened.version");
+ },
+
+ // Returns true if we should try to automatically install the add-on
+ get autoInstallEnabled() {
+ return Services.prefs.getBoolPref("devtools.addon.auto-install");
+ },
+
+ // Check if DevTools is packaged in this profile, either builtin in the omni.jar,
+ // or via a system-addon or via a regular add-on.
+ // No matter which packaging method is used, it will automatically register
+ // resource://devtools/ to expose Loader.jsm
+ get isInstalled() {
+ let resProto = Cc["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Ci.nsIResProtocolHandler);
+ return resProto.hasSubstitution("devtools");
+ },
+
+ async handle(cmdLine) {
+ // Firefox builds that still ship DevTools (Nightly, Dev-edition) will have
+ // isInstalled=true and skip that path.
+ if (!this.isInstalled &&
+ this.autoInstallEnabled && (this.hasAnyDevToolsFlag(cmdLine) ||
+ this.hasDevToolsEverBeenOpened)) {
+ await DevToolsShim.install();
+
+ // Only try to auto-install the add-on once
+ Services.prefs.setBoolPref("devtools.addon.auto-install", false);
+ }
+
// Defer command line implementation to a dedicated module to reduce
// this module size
if (this.hasAnyDevToolsFlag(cmdLine)) {
let require = this.initDevTools("CommandLine");
let CommandLine = require("devtools/client/framework/command-line");
CommandLine.handle(cmdLine);
}