Bug 892837 - Support permissions in desktop webrt. r=myk
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Fri, 16 Aug 2013 15:17:52 -0400
changeset 155914 4a6b9390ff5a2a8de95d91bbc1e8d44d75c91375
parent 155913 92b20164931fa8d38713af8d9a05c931bc020a17
child 155915 1cac9051df048e3cb65c5d4aee187c7871ecdea2
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmyk
bugs892837
milestone26.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 892837 - Support permissions in desktop webrt. r=myk
webapprt/CommandLineHandler.js
webapprt/Startup.jsm
webapprt/WebappRT.jsm
webapprt/WebappsHandler.jsm
webapprt/content/mochitest-shared.js
webapprt/content/mochitest.js
webapprt/content/mochitest.xul
webapprt/content/webapp.js
webapprt/jar.mn
webapprt/prefs.js
webapprt/test/chrome/head.js
--- a/webapprt/CommandLineHandler.js
+++ b/webapprt/CommandLineHandler.js
@@ -3,17 +3,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://webapprt/modules/WebappRT.jsm");
 
 function CommandLineHandler() {}
 
 CommandLineHandler.prototype = {
   classID: Components.ID("{6d69c782-40a3-469b-8bfd-3ee366105a4a}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 
@@ -25,22 +24,25 @@ CommandLineHandler.prototype = {
     if (inTestMode) {
       // Open the mochitest shim window, which configures the runtime for tests.
       Services.ww.openWindow(null,
                              "chrome://webapprt/content/mochitest.xul",
                              "_blank",
                              "chrome,dialog=no",
                              args);
     } else {
-      args.setProperty("url", WebappRT.launchURI);
-      Services.ww.openWindow(null,
-                             "chrome://webapprt/content/webapp.xul",
-                             "_blank",
-                             "chrome,dialog=no,resizable,scrollbars,centerscreen",
-                             args);
+      // We're opening the window here in order to show it as soon as possible.
+      let window = Services.ww.openWindow(null,
+                                          "chrome://webapprt/content/webapp.xul",
+                                          "_blank",
+                                          "chrome,dialog=no,resizable,scrollbars,centerscreen",
+                                          null);
+      // Load the module to start up the app
+      Cu.import("resource://webapprt/modules/Startup.jsm");
+      startup(window);
     }
   },
 
   _handleTestMode: function _handleTestMode(cmdLine, args) {
     // -test-mode [url]
     let idx = cmdLine.findFlag("test-mode", true);
     if (idx < 0)
       return false;
--- a/webapprt/Startup.jsm
+++ b/webapprt/Startup.jsm
@@ -1,32 +1,94 @@
 /* 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/. */
 
-/* This is imported by each new webapp window but is only evaluated the first
- * time it is imported.  Put stuff here that you want to happen once on startup
- * before the webapp is loaded.  But note that the "stuff" happens immediately
- * the first time this module is imported.  So only put stuff here that must
- * happen before the webapp is loaded. */
+/* This module is imported at the startup of an application.  It takes care of
+ * permissions installation, application url loading, security settings.  Put
+ * stuff here that you want to happen once on startup before the webapp is
+ * loaded.  */
 
-this.EXPORTED_SYMBOLS = [];
+this.EXPORTED_SYMBOLS = ["startup"];
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
-
 // Initialize DOMApplicationRegistry by importing Webapps.jsm.
 Cu.import("resource://gre/modules/Webapps.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 // Initialize window-independent handling of webapps- notifications.
 Cu.import("resource://webapprt/modules/WebappsHandler.jsm");
-WebappsHandler.init();
+Cu.import("resource://webapprt/modules/WebappRT.jsm");
+
+function isFirstRunOrUpdate() {
+  let savedBuildID = null;
+  try {
+    savedBuildID = Services.prefs.getCharPref("webapprt.buildID");
+  } catch (e) {}
+
+  let ourBuildID = Services.appinfo.platformBuildID;
+
+  if (ourBuildID != savedBuildID) {
+    Services.prefs.setCharPref("webapprt.buildID", ourBuildID);
+    return true;
+  }
+
+  return false;
+}
+
+// Observes all the events needed to actually launch an application.
+// It waits for XUL window and webapps registry loading.
+this.startup = function(window) {
+  return Task.spawn(function () {
+    // Observe registry loading.
+    let deferredRegistry = Promise.defer();
+    function observeRegistryLoading() {
+      Services.obs.removeObserver(observeRegistryLoading, "webapps-registry-start");
+      deferredRegistry.resolve();
+    }
+    Services.obs.addObserver(observeRegistryLoading, "webapps-registry-start", false);
 
-// On firstrun, set permissions to their default values.
-if (!Services.prefs.getBoolPref("webapprt.firstrun")) {
-  // Once we support packaged apps, set their permissions here on firstrun.
+    // Observe XUL window loading.
+    // For tests, it could be already loaded.
+    let deferredWindowLoad = Promise.defer();
+    if (window.document && window.document.getElementById("content")) {
+      deferredWindowLoad.resolve();
+    } else {
+      window.addEventListener("load", function onLoad() {
+        window.removeEventListener("load", onLoad, false);
+        deferredWindowLoad.resolve();
+      });
+    }
+
+    // Wait for webapps registry loading.
+    yield deferredRegistry.promise;
 
-  // Now that we've set the appropriate permissions, twiddle the firstrun
-  // flag so we don't try to do so again.
-  Services.prefs.setBoolPref("webapprt.firstrun", true);
+    // Install/update permissions and get the appID from the webapps registry.
+    let appID = Ci.nsIScriptSecurityManager.NO_APP_ID;
+    let manifestURL = WebappRT.config.app.manifestURL;
+    if (manifestURL) {
+      appID = DOMApplicationRegistry.getAppLocalIdByManifestURL(manifestURL);
+
+      // On firstrun, set permissions to their default values.
+      // When the webapp runtime is updated, update the permissions.
+      // TODO: Update the permissions when the application is updated.
+      if (isFirstRunOrUpdate(Services.prefs)) {
+        PermissionsInstaller.installPermissions(WebappRT.config.app, true);
+      }
+    }
+
+    // Wait for XUL window loading
+    yield deferredWindowLoad.promise;
+
+    // Get the <browser> element in the webapp.xul window.
+    let appBrowser = window.document.getElementById("content");
+
+    // Set the principal to the correct appID and launch the application.
+    appBrowser.docShell.setIsApp(appID);
+    appBrowser.setAttribute("src", WebappRT.launchURI);
+  }).then(null, Cu.reportError.bind(Cu));
 }
--- a/webapprt/WebappRT.jsm
+++ b/webapprt/WebappRT.jsm
@@ -19,24 +19,23 @@ XPCOMUtils.defineLazyGetter(this, "FileU
 
 this.WebappRT = {
   _config: null,
 
   get config() {
     if (this._config)
       return this._config;
 
-    let config;
     let webappFile = FileUtils.getFile("AppRegD", ["webapp.json"]);
 
     let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Ci.nsIFileInputStream);
     inputStream.init(webappFile, -1, 0, 0);
     let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
-    config = json.decodeFromStream(inputStream, webappFile.fileSize);
+    let config = json.decodeFromStream(inputStream, webappFile.fileSize);
 
     return this._config = config;
   },
 
   // This exists to support test mode, which installs webapps after startup.
   // Ideally we wouldn't have to have a setter, as tests can just delete
   // the getter and then set the property.  But the object to which they set it
   // will have a reference to its global object, so our reference to it
--- a/webapprt/WebappsHandler.jsm
+++ b/webapprt/WebappsHandler.jsm
@@ -5,29 +5,24 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["WebappsHandler"];
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/WebappsInstaller.jsm");
 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
 
 this.WebappsHandler = {
-  init: function() {
-    Services.obs.addObserver(this, "webapps-ask-install", false);
-    Services.obs.addObserver(this, "webapps-launch", false);
-    Services.obs.addObserver(this, "webapps-uninstall", false);
-  },
-
   observe: function(subject, topic, data) {
     data = JSON.parse(data);
     data.mm = subject;
 
     switch (topic) {
       case "webapps-ask-install":
         let chromeWin = Services.wm.getOuterWindowWithId(data.oid);
         if (chromeWin)
@@ -78,10 +73,17 @@ this.WebappsHandler = {
           }
         );
       } else {
         DOMApplicationRegistry.denyInstall(data);
       }
     } else {
       DOMApplicationRegistry.denyInstall(data);
     }
-  }
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference])
 };
+
+Services.obs.addObserver(WebappsHandler, "webapps-ask-install", false);
+Services.obs.addObserver(WebappsHandler, "webapps-launch", false);
+Services.obs.addObserver(WebappsHandler, "webapps-uninstall", false);
copy from webapprt/content/mochitest.js
copy to webapprt/content/mochitest-shared.js
--- a/webapprt/content/mochitest.js
+++ b/webapprt/content/mochitest-shared.js
@@ -1,19 +1,15 @@
 /* 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/. */
 
-/* Note: this script is loaded by both mochitest.xul and head.js, so make sure
+/* Note: this script is loaded by both mochitest.js and head.js, so make sure
  * the code you put here can be evaluated by both! */
 
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://webapprt/modules/WebappRT.jsm");
 
 // When WebappsHandler opens an install confirmation dialog for apps we install,
 // close it, which will be seen as the equivalent of cancelling the install.
 // This doesn't prevent us from installing those apps, as we listen for the same
 // notification as WebappsHandler and do the install ourselves.  It just
 // prevents the modal installation confirmation dialogs from hanging tests.
 Services.ww.registerNotification({
@@ -50,23 +46,45 @@ function becomeWebapp(manifestURL, param
     Services.obs.removeObserver(observeInstall, "webapps-ask-install");
 
     // Step 2: Configure the runtime session to represent the app.
     // We load DOMApplicationRegistry into a local scope to avoid appearing
     // to leak it.
 
     let scope = {};
     Cu.import("resource://gre/modules/Webapps.jsm", scope);
+    Cu.import("resource://webapprt/modules/Startup.jsm", scope);
     scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
 
     let installRecord = JSON.parse(data);
     installRecord.mm = subj;
     installRecord.registryDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
     WebappRT.config = installRecord;
 
-    onBecome();
+    let win = Services.wm.getMostRecentWindow("webapprt:webapp");
+    if (!win) {
+      win = Services.ww.openWindow(null,
+                                   "chrome://webapprt/content/webapp.xul",
+                                   "_blank",
+                                   "chrome,dialog=no,resizable,scrollbars,centerscreen",
+                                   null);
+    }
+
+    let promise = scope.startup(win);
+
+    // During chrome tests, we use the same window to load all the tests. We
+    // need to change the buildID so that the permissions for the currently
+    // tested application get installed.
+    Services.prefs.setCharPref("webapprt.buildID", WebappRT.config.app.manifestURL);
+
+    // During tests, the webapps registry is already loaded.
+    // The Startup module needs to be notified when the webapps registry
+    // gets loaded, so we do that now.
+    Services.obs.notifyObservers(this, "webapps-registry-start", null);
+
+    promise.then(onBecome);
   }
   Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
 
   // Step 1: Install the app at the URL specified by the manifest.
   let url = Services.io.newURI(manifestURL, null, null);
   navigator.mozApps.install(url.spec, parameters);
 }
--- a/webapprt/content/mochitest.js
+++ b/webapprt/content/mochitest.js
@@ -1,72 +1,38 @@
 /* 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/. */
 
-/* Note: this script is loaded by both mochitest.xul and head.js, so make sure
- * the code you put here can be evaluated by both! */
-
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://webapprt/modules/WebappRT.jsm");
+
+Services.scriptloader
+        .loadSubScript("chrome://webapprt/content/mochitest-shared.js", this);
+
+// In test mode, the runtime isn't configured until we tell it to become
+// an app, which requires us to use DOMApplicationRegistry to install one.
+// But DOMApplicationRegistry needs to know the location of its registry dir,
+// so we need to configure the runtime with at least that information.
+WebappRT.config = {
+  registryDir: Services.dirsvc.get("ProfD", Ci.nsIFile).path,
+};
+
 
-// When WebappsHandler opens an install confirmation dialog for apps we install,
-// close it, which will be seen as the equivalent of cancelling the install.
-// This doesn't prevent us from installing those apps, as we listen for the same
-// notification as WebappsHandler and do the install ourselves.  It just
-// prevents the modal installation confirmation dialogs from hanging tests.
-Services.ww.registerNotification({
-  observe: function(win, topic) {
-    if (topic == "domwindowopened") {
-      // Wait for load because the window is not yet sufficiently initialized.
-      win.addEventListener("load", function onLoadWindow() {
-        win.removeEventListener("load", onLoadWindow, false);
-        if (win.location == "chrome://global/content/commonDialog.xul" &&
-            win.opener == window) {
-          win.close();
-        }
-      }, false);
+Cu.import("resource://gre/modules/Webapps.jsm");
+
+DOMApplicationRegistry.allAppsLaunchable = true;
+
+becomeWebapp("http://mochi.test:8888/tests/webapprt/test/content/test.webapp",
+             undefined, function onBecome() {
+  if (window.arguments && window.arguments[0]) {
+    let testUrl = window.arguments[0].QueryInterface(Ci.nsIPropertyBag2).get("url");
+
+    if (testUrl) {
+      let win = Services.wm.getMostRecentWindow("webapprt:webapp");
+      win.document.getElementById("content").setAttribute("src", testUrl);
     }
   }
+
+  window.close();
 });
-
-/**
- * Transmogrify the runtime session into one for the given webapp.
- *
- * @param {String} manifestURL
- *        The URL of the webapp's manifest, relative to the base URL.
- *        Note that the base URL points to the *chrome* WebappRT mochitests,
- *        so you must supply an absolute URL to manifests elsewhere.
- * @param {Object} parameters
- *        The value to pass as the "parameters" argument to
- *        mozIDOMApplicationRegistry.install, e.g., { receipts: ... }.
- *        Use undefined to pass nothing.
- * @param {Function} onBecome
- *        The callback to call once the transmogrification is complete.
- */
-function becomeWebapp(manifestURL, parameters, onBecome) {
-  function observeInstall(subj, topic, data) {
-    Services.obs.removeObserver(observeInstall, "webapps-ask-install");
-
-    // Step 2: Configure the runtime session to represent the app.
-    // We load DOMApplicationRegistry into a local scope to avoid appearing
-    // to leak it.
-
-    let scope = {};
-    Cu.import("resource://gre/modules/Webapps.jsm", scope);
-    scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
-
-    let installRecord = JSON.parse(data);
-    installRecord.mm = subj;
-    installRecord.registryDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
-    WebappRT.config = installRecord;
-
-    onBecome();
-  }
-  Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
-
-  // Step 1: Install the app at the URL specified by the manifest.
-  let url = Services.io.newURI(manifestURL, null, null);
-  navigator.mozApps.install(url.spec, parameters);
-}
--- a/webapprt/content/mochitest.xul
+++ b/webapprt/content/mochitest.xul
@@ -6,39 +6,11 @@
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <window windowtype="webapprt:mochitest"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 <script type="application/javascript" src="chrome://webapprt/content/mochitest.js"/>
 
-<script type="application/javascript">
-  Cu.import("resource://webapprt/modules/WebappRT.jsm");
-
-  // In test mode, the runtime isn't configured until we tell it to become
-  // an app, which requires us to use DOMApplicationRegistry to install one.
-  // But DOMApplicationRegistry needs to know the location of its registry dir,
-  // so we need to configure the runtime with at least that information.
-  WebappRT.config = {
-    registryDir: Services.dirsvc.get("ProfD", Ci.nsIFile).path,
-  };
-
-  Cu.import("resource://gre/modules/Webapps.jsm");
-
-  DOMApplicationRegistry.allAppsLaunchable = true;
-
-  becomeWebapp("http://mochi.test:8888/tests/webapprt/test/content/test.webapp",
-               undefined, function onBecome() {
-    Services.ww.openWindow(
-      null,
-      "chrome://webapprt/content/webapp.xul",
-      "_blank",
-      "chrome,dialog=no,resizable,scrollbars,centerscreen",
-      window.arguments[0]
-    );
-    window.close();
-  });
-</script>
-
 <description value="WebappRT Test Shim"/>
 
 </window>
--- a/webapprt/content/webapp.js
+++ b/webapprt/content/webapp.js
@@ -1,17 +1,16 @@
 /* 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 Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-Cu.import("resource://webapprt/modules/Startup.jsm");
 Cu.import("resource://webapprt/modules/WebappRT.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gAppBrowser",
                             function() document.getElementById("content"));
 
 #ifdef MOZ_CRASHREPORTER
@@ -44,38 +43,26 @@ let progressListener = {
       updateCrashReportURL(aRequest.URI);
     }
   }
 };
 
 function onLoad() {
   window.removeEventListener("load", onLoad, false);
 
-  let args = window.arguments && window.arguments[0] ?
-             window.arguments[0].QueryInterface(Ci.nsIPropertyBag2) :
-             null;
-
   gAppBrowser.addProgressListener(progressListener,
                                   Ci.nsIWebProgress.NOTIFY_LOCATION |
                                   Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
 
   updateMenuItems();
 
   // Listen for clicks to redirect <a target="_blank"> to the browser.
   // This doesn't capture clicks so content can capture them itself and do
   // something different if it doesn't want the default behavior.
   gAppBrowser.addEventListener("click", onContentClick, false, true);
-
-  // This is not the only way that a URL gets loaded in the app browser.
-  // When content calls openWindow(), there are no window.arguments,
-  // but something in the platform loads the URL specified by the content.
-  if (args && args.hasKey("url")) {
-    gAppBrowser.setAttribute("src", args.get("url"));
-  }
-
 }
 window.addEventListener("load", onLoad, false);
 
 function onUnload() {
   gAppBrowser.removeProgressListener(progressListener);
 }
 window.addEventListener("unload", onUnload, false);
 
--- a/webapprt/jar.mn
+++ b/webapprt/jar.mn
@@ -1,10 +1,11 @@
 # 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/.
 
 webapprt.jar:
 % content webapprt %content/
 * content/webapp.js                     (content/webapp.js)
 * content/webapp.xul                    (content/webapp.xul)
+  content/mochitest-shared.js           (content/mochitest-shared.js)
   content/mochitest.js                  (content/mochitest.js)
   content/mochitest.xul                 (content/mochitest.xul)
--- a/webapprt/prefs.js
+++ b/webapprt/prefs.js
@@ -11,19 +11,16 @@ pref("extensions.enabledScopes", 1);
 pref("extensions.autoDisableScopes", 1);
 // Disable add-on installation via the web-exposed APIs
 pref("xpinstall.enabled", false);
 // Disable installation of distribution add-ons
 pref("extensions.installDistroAddons", false);
 // Disable the add-on compatibility dialog
 pref("extensions.showMismatchUI", false);
 
-// Whether or not we've ever run.  We use this to set permissions on firstrun.
-pref("webapprt.firstrun", false);
-
 // Set reportURL for crashes
 pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
 
 // Blocklist preferences
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
 // blocking them.
--- a/webapprt/test/chrome/head.js
+++ b/webapprt/test/chrome/head.js
@@ -1,18 +1,18 @@
 /* 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/. */
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 // Some of the code we want to provide to chrome mochitests is in another file
 // so we can share it with the mochitest shim window, thus we need to load it.
-Services.scriptloader.loadSubScript("chrome://webapprt/content/mochitest.js",
-                                    this);
+Services.scriptloader
+        .loadSubScript("chrome://webapprt/content/mochitest-shared.js", this);
 
 const MANIFEST_URL_BASE = Services.io.newURI(
   "http://test/webapprtChrome/webapprt/test/chrome/", null, null);
 
 /**
  * Load the webapp in the app browser.
  *
  * @param {String} manifestURL