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 143007 4a6b9390ff5a2a8de95d91bbc1e8d44d75c91375
parent 143006 92b20164931fa8d38713af8d9a05c931bc020a17
child 143008 1cac9051df048e3cb65c5d4aee187c7871ecdea2
push idunknown
push userunknown
push dateunknown
reviewersmyk
bugs892837
milestone26.0a1
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