Bug 1118138 - Add support to bootstrap b2g from a manifest url r=vingtetun
authorFabrice Desré <fabrice@mozilla.com>
Fri, 23 Jan 2015 10:23:45 -0800
changeset 252519 c636c1f0e07be3fe6b3a5ec8eb8c86cdffea1445
parent 252518 2fb840ec39621d78b74a62d8b80b172a6d380b21
child 252520 1cd10eb14ddc008e510204bcb13f6ffa558d3d8c
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun
bugs1118138
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1118138 - Add support to bootstrap b2g from a manifest url r=vingtetun
b2g/chrome/content/settings.js
b2g/chrome/content/shell.js
b2g/components/B2GComponents.manifest
b2g/components/BootstrapCommandLine.js
b2g/components/Bootstraper.jsm
b2g/components/moz.build
b2g/installer/package-manifest.in
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -103,17 +103,17 @@ SettingsListener.observe('language.curre
   if (!((new RegExp('^' + value + '[^a-z-_] *[,;]?', 'i')).test(intl))) {
     value = value + ', ' + intl;
   } else {
     value = intl;
   }
   Services.prefs.setCharPref(prefName, value);
 
   if (shell.hasStarted() == false) {
-    shell.start();
+    shell.bootstrap();
   }
 });
 
 // =================== RIL ====================
 (function RILSettingsToPrefs() {
   // DSDS default service IDs
   ['mms', 'sms', 'telephony', 'voicemail'].forEach(function(key) {
     SettingsListener.observe('ril.' + key + '.defaultServiceId', 0,
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -217,16 +217,30 @@ var shell = {
     return Services.prefs.getCharPref('b2g.system_manifest_url');
   },
 
   _started: false,
   hasStarted: function shell_hasStarted() {
     return this._started;
   },
 
+  bootstrap: function() {
+    let startManifestURL =
+      Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
+        .getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
+    if (startManifestURL) {
+      Cu.import('resource://gre/modules/Bootstraper.jsm');
+      Bootstraper.ensureSystemAppInstall(startManifestURL)
+                 .then(this.start.bind(this))
+                 .catch(Bootstraper.bailout);
+    } else {
+      this.start();
+    }
+  },
+
   start: function shell_start() {
     this._started = true;
 
     // This forces the initialization of the cookie service before we hit the
     // network.
     // See bug 810209
     let cookies = Cc["@mozilla.org/cookieService;1"];
 
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -95,16 +95,21 @@ component {e30b0e13-2d12-4cb0-bc4c-4e617
 contract @mozilla.org/commandlinehandler/general-startup;1?type=b2goop {e30b0e13-2d12-4cb0-bc4c-4e617a1bf76e}
 category command-line-handler m-b2goop @mozilla.org/commandlinehandler/general-startup;1?type=b2goop
 
 component {385993fe-8710-4621-9fb1-00a09d8bec37} CommandLine.js
 contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds {385993fe-8710-4621-9fb1-00a09d8bec37}
 category command-line-handler m-b2gcmds @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds
 #endif
 
+# BootstrapCommandLine.js
+component {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44} BootstrapCommandLine.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}
+category command-line-handler m-b2gbootstrap @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap
+
 # MobileIdentityUIGlue.js
 component {83dbe26a-81f3-4a75-9541-3d0b7ca496b5} MobileIdentityUIGlue.js
 contract @mozilla.org/services/mobileid-ui-glue;1 {83dbe26a-81f3-4a75-9541-3d0b7ca496b5}
 
 # B2GAppMigrator.js
 component {7211ece0-b458-4635-9afc-f8d7f376ee95} B2GAppMigrator.js
 contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
 
new file mode 100644
--- /dev/null
+++ b/b2g/components/BootstrapCommandLine.js
@@ -0,0 +1,52 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+function BootstrapCommandlineHandler() {
+  this.wrappedJSObject = this;
+  this.startManifestURL = null;
+}
+
+BootstrapCommandlineHandler.prototype = {
+    bailout: function(aMsg) {
+      dump("************************************************************\n");
+      dump("* /!\\ " + aMsg + "\n");
+      dump("************************************************************\n");
+      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                         .getService(Ci.nsIAppStartup);
+      appStartup.quit(appStartup.eForceQuit);
+    },
+
+    handle: function(aCmdLine) {
+      this.startManifestURL = null;
+
+      try {
+        // Returns null if the argument was not specified.  Throws
+        // NS_ERROR_INVALID_ARG if there is no parameter specified (because
+        // it was the last argument or the next argument starts with '-').
+        // However, someone could still explicitly pass an empty argument!
+        this.startManifestURL = aCmdLine.handleFlagWithParam("start-manifest", false);
+      } catch(e) {
+        return;
+      }
+
+      if (!this.startManifestURL) {
+        return;
+      }
+
+      if (!isAbsoluteURI(this.startManifestURL)) {
+        this.bailout("The start manifest url must be absolute.");
+        return;
+      }
+    },
+
+    helpInfo: "--start-manifest=manifest_url",
+    classID: Components.ID("{fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}"),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BootstrapCommandlineHandler]);
new file mode 100644
--- /dev/null
+++ b/b2g/components/Bootstraper.jsm
@@ -0,0 +1,147 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["Bootstraper"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Webapps.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+function debug(aMsg) {
+  //dump("-*- Bootstraper: " + aMsg + "\n");
+}
+
+/**
+  * This module loads the manifest for app from the --start-url enpoint and
+  * ensures that it's installed as the system app.
+  */
+this.Bootstraper = {
+  _manifestURL: null,
+  _startupURL: null,
+
+  bailout: function(aMsg) {
+    dump("************************************************************\n");
+    dump("* /!\\ " + aMsg + "\n");
+    dump("************************************************************\n");
+    let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                       .getService(Ci.nsIAppStartup);
+    appStartup.quit(appStartup.eForceQuit);
+  },
+
+  installSystemApp: function(aManifest) {
+    // Get the appropriate startup url from the manifest launch_path.
+    let base = Services.io.newURI(this._manifestURL, null, null);
+    let origin = base.prePath;
+    let helper = new ManifestHelper(aManifest, origin, this._manifestURL);
+    this._startupURL = helper.fullLaunchPath();
+
+    return new Promise((aResolve, aReject) => {
+      debug("Origin is " + origin);
+      let appData = {
+        app: {
+          installOrigin: origin,
+          origin: origin,
+          manifest: aManifest,
+          manifestURL: this._manifestURL,
+          manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest)),
+          appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED
+        },
+        appId: 1,
+        isBrowser: false,
+        isPackage: false
+      };
+
+      DOMApplicationRegistry.confirmInstall(appData, null, aResolve);
+    });
+  },
+
+  /**
+    * Resolves to a json manifest.
+    */
+  loadManifest: function() {
+    return new Promise((aResolve, aReject) => {
+      debug("Loading manifest " + this._manifestURL);
+
+      let xhr =  Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                 .createInstance(Ci.nsIXMLHttpRequest);
+      xhr.mozBackgroundRequest = true;
+      xhr.open("GET", this._manifestURL);
+      xhr.responseType = "json";
+      xhr.addEventListener("load", () => {
+        if (xhr.status >= 200 && xhr.status < 400) {
+          debug("Success loading " + this._manifestURL);
+          aResolve(xhr.response);
+        } else {
+          aReject("Error loading " + this._manifestURL);
+        }
+      });
+      xhr.addEventListener("error", () => {
+        aReject("Error loading " + this._manifestURL);
+      });
+      xhr.send(null);
+    });
+  },
+
+  configure: function() {
+    debug("Setting startup prefs... " + this._startupURL);
+    Services.prefs.setCharPref("b2g.system_manifest_url", this._manifestURL);
+    Services.prefs.setCharPref("b2g.system_startup_url", this._startupURL);
+    return Promise.resolve();
+  },
+
+  /**
+    * If a system app is already installed, uninstall it so that we can
+    * cleanly replace it by the current one.
+    */
+  uninstallPreviousSystemApp: function() {
+    let oldManifestURL;
+    try{
+      oldManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
+    } catch(e) {
+      // No preference set, so nothing to uninstall.
+      return Promise.resolve();
+    }
+
+    let id = DOMApplicationRegistry.getAppLocalIdByManifestURL(oldManifestURL);
+    if (id == Ci.nsIScriptSecurityManager.NO_APP_ID) {
+      return Promise.resolve();
+    }
+    debug("Uninstalling " + oldManifestURL);
+    return DOMApplicationRegistry.uninstall(oldManifestURL);
+  },
+
+  /**
+    * Resolves once we have installed the app.
+    */
+  ensureSystemAppInstall: function(aManifestURL) {
+    this._manifestURL = aManifestURL;
+    debug("Installing app from " + this._manifestURL);
+
+    // Check if we are already configured to run from this manifest url, and
+    // skip reinstall if that's the case.
+    try {
+      if (Services.prefs.getCharPref("b2g.system_manifest_url") == this._manifestURL) {
+        debug("Already configured for " + this._manifestURL);
+        return Promise.resolve();
+      }
+    } catch(e) { }
+
+    return new Promise((aResolve, aReject) => {
+      DOMApplicationRegistry.registryReady
+          .then(this.uninstallPreviousSystemApp.bind(this))
+          .then(this.loadManifest.bind(this))
+          .then(this.installSystemApp.bind(this))
+          .then(this.configure.bind(this))
+          .then(aResolve)
+          .catch(aReject);
+    });
+  }
+};
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -7,16 +7,17 @@
 DIRS += ['test']
 
 EXTRA_COMPONENTS += [
     'ActivitiesGlue.js',
     'AlertsService.js',
     'B2GAboutRedirector.js',
     'B2GAppMigrator.js',
     'B2GPresentationDevicePrompt.js',
+    'BootstrapCommandLine.js',
     'ContentPermissionPrompt.js',
     'FilePicker.js',
     'FxAccountsUIGlue.js',
     'HelperAppDialog.js',
     'InterAppCommUIGlue.js',
     'MailtoProtocolHandler.js',
     'MobileIdentityUIGlue.js',
     'OMAContentHandler.js',
@@ -44,16 +45,17 @@ EXTRA_PP_COMPONENTS += [
 
 if CONFIG['MOZ_UPDATER']:
     EXTRA_PP_COMPONENTS += [
         'UpdatePrompt.js',
     ]
 
 EXTRA_JS_MODULES += [
     'AlertsHelper.jsm',
+    'Bootstraper.jsm',
     'ContentRequestHelper.jsm',
     'DebuggerActors.js',
     'ErrorPage.jsm',
     'Frames.jsm',
     'FxAccountsMgmtService.jsm',
     'LogCapture.jsm',
     'LogParser.jsm',
     'LogShake.jsm',
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -472,16 +472,17 @@
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amInstallTrigger.js
 @BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
 @BINPATH@/components/OopCommandLine.js
 @BINPATH@/components/CommandLine.js
 #endif
+@BINPATH@/components/BootstrapCommandLine.js
 
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
 #endif
 @BINPATH@/components/nsUpdateTimerManager.manifest
 @BINPATH@/components/nsUpdateTimerManager.js