Bug 609043 - Add support for Open Web Apps [r=mfinkle]
authorFabrice Desré <fabrice@mozilla.com>
Mon, 15 Aug 2011 12:16:50 -0700
changeset 78024 282cc7688390e0a56d36db81df1f3a154bdf662c
parent 78023 85ca334bad1dd34f93a76a7006e56be6ea7301d1
child 78025 d8a22be1e9c7e3552d674c66709a0dd9e2585db7
push id387
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 17:43:12 +0000
treeherdermozilla-aurora@ec885a01be07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs609043
milestone9.0a1
Bug 609043 - Add support for Open Web Apps [r=mfinkle]
mobile/chrome/content/PageActions.js
mobile/chrome/content/WebappsUI.js
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.xul
mobile/components/Makefile.in
mobile/components/MobileComponents.manifest
mobile/components/WebappsSupport.idl
mobile/components/WebappsSupport.js
mobile/installer/package-manifest.in
mobile/locales/en-US/chrome/browser.dtd
toolkit/Makefile.in
toolkit/mozapps/webapps/Makefile.in
toolkit/mozapps/webapps/OpenWebapps.idl
toolkit/mozapps/webapps/OpenWebapps.js
toolkit/mozapps/webapps/OpenWebapps.jsm
toolkit/mozapps/webapps/OpenWebapps.manifest
--- a/mobile/chrome/content/PageActions.js
+++ b/mobile/chrome/content/PageActions.js
@@ -47,17 +47,16 @@ var PageActions = {
 
     this.register("pageaction-reset", this.updatePagePermissions, this);
     this.register("pageaction-password", this.updateForgetPassword, this);
 #ifdef NS_PRINTING
     this.register("pageaction-saveas", this.updatePageSaveAs, this);
 #endif
     this.register("pageaction-share", this.updateShare, this);
     this.register("pageaction-search", BrowserSearch.updatePageSearchEngines, BrowserSearch);
-    this.register("pageaction-webapps-install", WebappsUI.updateWebappsInstall, WebappsUI);
 
     CharsetMenu.init();
   },
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "click":
         getIdentityHandler().hide();
--- a/mobile/chrome/content/WebappsUI.js
+++ b/mobile/chrome/content/WebappsUI.js
@@ -34,140 +34,189 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 var WebappsUI = {
   _dialog: null,
   _manifest: null,
   _perms: [],
+  _application: null,
+  _browser: null,
+
+  init: function() {
+    Cu.import("resource://gre/modules/OpenWebapps.jsm");
+    this.messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+    this.messageManager.addMessageListener("OpenWebapps:Install", this);
+    this.messageManager.addMessageListener("OpenWebapps:GetInstalledBy", this);
+    this.messageManager.addMessageListener("OpenWebapps:AmInstalled", this);
+    this.messageManager.addMessageListener("OpenWebapps:MgmtLaunch", this);
+    this.messageManager.addMessageListener("OpenWebapps:MgmtList", this);
+    this.messageManager.addMessageListener("OpenWebapps:MgmtUninstall", this);
+  },
+
+  // converts a manifest to an application as expected by openwebapps.install()
+  convertManifest: function(aData) {
+    let app = {
+      manifest : JSON.parse(aData.manifest),
+      installData : aData.installData,
+      storeURI : aData.storeURI,
+      manifestURI : aData.manifestURI,
+      capabilities : [],
+      callbackID : aData.callbackID
+    }
+
+    let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).QueryInterface(Ci.nsIToolkitChromeRegistry);
+    let locale = chrome.getSelectedLocale("browser");
+
+    let localeRoot;
+    if (app.manifest.locales)
+      localeRoot = app.manifest.locales[locale];
+
+    if (!localeRoot)
+      localeRoot = app.manifest;
+
+    let baseURI = Services.io.newURI(aData.manifestURI, null, null);
+    app.title = localeRoot.name || app.manifest.name;
+
+    // choose the largest icon
+    let max = 0;
+    let icon;
+    for (let size in app.manifest.icons) {
+      let iSize = parseInt(size);
+      if (iSize > max) {
+        icon = baseURI.resolve(app.manifest.icons[size]);
+        max = iSize;
+      }
+    }
+    if (icon)
+      app.iconURI = icon;
+
+    let root = baseURI.resolve("/").toString();
+    app.appURI = root.substring(0, root.length - 1);
+
+    return app;
+  },
+
+  receiveMessage: function(aMessage) {
+    this._browser = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
+    switch(aMessage.name) {
+      case "OpenWebapps:Install":
+        WebappsUI.show(WebappsUI.convertManifest(aMessage.json));
+        break;
+      case "OpenWebapps:GetInstalledBy":
+        let apps = OpenWebapps.getInstalledBy(aMessage.json.storeURI);
+        this._browser.sendAsyncMessage("OpenWebapps:GetInstalledBy:Return",
+            { apps: apps, callbackID: aMessage.json.callbackID });
+        break;
+      case "OpenWebapps:AmInstalled":
+        let app = OpenWebapps.amInstalled(aMessage.json.appURI);
+        this._browser.sendAsyncMessage("OpenWebapps:AmInstalled:Return",
+            { installed: app != null, app: app, callbackID: aMessage.json.callbackID });
+        break;
+      case "OpenWebapps:MgmtList":
+        let list = OpenWebapps.mgmtList();
+        this._browser.sendAsyncMessage("OpenWebapps:MgmtList:Return",
+            { ok: true, apps: list, callbackID: aMessage.json.callbackID });
+        break;
+      case "OpenWebapps:MgmtLaunch":
+        let res = OpenWebapps.mgmtLaunch(aMessage.json.origin);
+        this._browser.sendAsyncMessage("OpenWebapps:MgmtLaunch:Return",
+            { ok: res, callbackID: aMessage.json.callbackID });
+        break;
+      case "OpenWebapps:MgmtUninstall":
+        app = OpenWebapps.amInstalled(aMessage.json.origin);
+        let uninstalled = OpenWebapps.mgmtUninstall(aMessage.json.origin);
+        this.messageManager.sendAsyncMessage("OpenWebapps:MgmtUninstall:Return",
+            { ok: uninstalled, app: app, callbackID: aMessage.json.callbackID });
+        break;
+    }
+  },
 
   checkBox: function(aEvent) {
     let elem = aEvent.originalTarget;
     let perm = elem.getAttribute("perm");
-    if (this._manifest.capabilities && this._manifest.capabilities.indexOf(perm) != -1) {
+    if (this._application.capabilities && this._application.capabilities.indexOf(perm) != -1) {
       if (elem.checked) {
         elem.classList.remove("webapps-noperm");
         elem.classList.add("webapps-perm");
       } else {
         elem.classList.remove("webapps-perm");
         elem.classList.add("webapps-noperm");
       }
     }
   },
 
-  show: function show(aManifest) {
-    if (!aManifest) {
-      // Try every way to get an icon
-      let browser = Browser.selectedBrowser;
-      let icon = browser.appIcon.href;
-      if (!icon)
-        icon = browser.mIconURL;
-      if (!icon)
-        icon = gFaviconService.getFaviconImageForPage(browser.currentURI).spec;
-
-      // Create a simple manifest
-      aManifest = {
-        uri: browser.currentURI.spec,
-        name: browser.contentTitle,
-        icon: icon,
-        capabilities: [],
-      };
-    }
-
-    this._manifest = aManifest;
+  show: function show(aApplication) {
+    this._application = aApplication;
     this._dialog = importDialog(window, "chrome://browser/content/webapps.xul", null);
 
-    if (aManifest.name)
-      document.getElementById("webapps-title").value = aManifest.name;
-    if (aManifest.icon)
-      document.getElementById("webapps-icon").src = aManifest.icon;
+    if (aApplication.title)
+      document.getElementById("webapps-title").value = aApplication.title;
+    if (aApplication.iconURI)
+      document.getElementById("webapps-icon").src = aApplication.iconURI;
 
-    let uri = Services.io.newURI(aManifest.uri, null, null);
+    let uri = Services.io.newURI(aApplication.appURI, null, null);
 
     let perms = [["offline", "offline-app"], ["geoloc", "geo"], ["notifications", "desktop-notification"]];
     let self = this;
     perms.forEach(function(tuple) {
       let elem = document.getElementById("webapps-" + tuple[0] + "-checkbox");
       let currentPerm = Services.perms.testExactPermission(uri, tuple[1]);
       self._perms[tuple[1]] = (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION);
-      if ((aManifest.capabilities && (aManifest.capabilities.indexOf(tuple[1]) != -1)) || (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION))
+      if ((aApplication.capabilities && (aApplication.capabilities.indexOf(tuple[1]) != -1)) || (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION))
         elem.checked = true;
       else
         elem.checked = (currentPerm == Ci.nsIPermissionManager.ALLOW_ACTION);
       elem.classList.remove("webapps-noperm");
       elem.classList.add("webapps-perm");
     });
 
     BrowserUI.pushPopup(this, this._dialog);
 
     // Force a modal dialog
     this._dialog.waitForClose();
   },
 
   hide: function hide() {
+    this.close();
+
+    this._browser.sendAsyncMessage("OpenWebapps:InstallAborted", { callbackID: this._application.callbackID });
+  },
+  
+  close: function close() {
     this._dialog.close();
     this._dialog = null;
     BrowserUI.popPopup(this);
   },
 
   _updatePermission: function updatePermission(aId, aPerm) {
     try {
-      let uri = Services.io.newURI(this._manifest.uri, null, null);
+      let uri = Services.io.newURI(this._application.appURI, null, null);
       let currentState = document.getElementById(aId).checked;
       if (currentState != this._perms[aPerm])
         Services.perms.add(uri, aPerm, currentState ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION);
     } catch(e) {
       Cu.reportError(e);
     }
   },
 
   launch: function launch() {
     let title = document.getElementById("webapps-title").value;
     if (!title)
       return;
 
+    this._application.title = title;
+
     this._updatePermission("webapps-offline-checkbox", "offline-app");
     this._updatePermission("webapps-geoloc-checkbox", "geo");
     this._updatePermission("webapps-notifications-checkbox", "desktop-notification");
 
-    this.hide();
-    this.install(this._manifest.uri, title, this._manifest.icon);
-  },
-
-  updateWebappsInstall: function updateWebappsInstall(aNode) {
-    if (document.getElementById("main-window").hasAttribute("webapp"))
-      return false;
-
-    let browser = Browser.selectedBrowser;
-
-    let webapp = Cc["@mozilla.org/webapps/support;1"].getService(Ci.nsIWebappsSupport);
-    return !(webapp && webapp.isApplicationInstalled(browser.currentURI.spec));
-  },
-
-  install: function(aURI, aTitle, aIcon) {
-    const kIconSize = 64;
-
-    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-    canvas.setAttribute("style", "display: none");
-
-    let self = this;
-    let image = new Image();
-    image.onload = function() {
-      canvas.width = canvas.height = kIconSize; // clears the canvas
-      let ctx = canvas.getContext("2d");
-      ctx.drawImage(image, 0, 0, kIconSize, kIconSize);
-      let data = canvas.toDataURL("image/png", "");
-      canvas = null;
-      try {
-        let webapp = Cc["@mozilla.org/webapps/support;1"].getService(Ci.nsIWebappsSupport);
-        webapp.installApplication(aTitle, aURI, aIcon, data);
-      } catch(e) {
-        Cu.reportError(e);
-      }
+    this.close();
+    try {
+      OpenWebapps.install(this._application);
+      let app = OpenWebapps.amInstalled(this._application.appURI);
+      this.messageManager.sendAsyncMessage("OpenWebapps:InstallDone", { app: app, callbackID: this._application.callbackID });
+    } catch(e) {
+      Cu.reportError(e);
     }
-    image.onerror = function() {
-      // can't load the icon (bad URI) : fallback to the default one from chrome
-      self.install(aURI, aTitle, "chrome://browser/skin/images/favicon-default-30.png");
-    }
-    image.src = aIcon;
   }
 };
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -485,16 +485,17 @@ var BrowserUI = {
       messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps);
 
       // Init helpers
       BadgeHandlers.register(BrowserUI._edit.popup);
       FormHelperUI.init();
       FindHelperUI.init();
       FullScreenVideo.init();
       NewTabPopup.init();
+      WebappsUI.init();
 
       // If some add-ons were disabled during during an application update, alert user
       let addonIDs = AddonManager.getStartupChanges("disabled");
       if (addonIDs.length > 0) {
         let disabledStrings = Strings.browser.GetStringFromName("alertAddonsDisabled");
         let label = PluralForm.get(addonIDs.length, disabledStrings).replace("#1", addonIDs.length);
         let image = "chrome://browser/skin/images/alert-addons-30.png";
 
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -361,17 +361,16 @@
           onclick="SharingUI.show(getBrowser().currentURI.spec, getBrowser().contentTitle);"/>
 #endif
         <pageaction id="pageaction-password" title="&pageactions.password.forget;"
           onclick="PageActions.forgetPassword(event);"/>
         <pageaction id="pageaction-reset" title="&pageactions.reset;"
           onclick="PageActions.clearPagePermissions(event);"/>
         <pageaction id="pageaction-search" title="&pageactions.search.addNew;"/>
         <pageaction id="pageaction-charset" title="&pageactions.charEncoding;" onclick="CharsetMenu.show();"/>
-        <pageaction id="pageaction-webapps-install" title="&pageactions.webapps.install;" onclick="WebappsUI.show();"/>
       </hbox>
     </arrowbox>
 
     <arrowbox id="bookmark-popup" hidden="true" align="stretch" offset="12" type="dialog">
       <hbox id="bookmark-popup-title">
         <label value="&bookmarkPopup.label;"/>
       </hbox>
       <richlistbox id="bookmark-popup-commands" class="action-buttons" onclick="BookmarkPopup.hide();" flex="1">
--- a/mobile/components/Makefile.in
+++ b/mobile/components/Makefile.in
@@ -44,17 +44,16 @@ VPATH      = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = MobileComponents
 XPIDL_MODULE = MobileComponents
 
 XPIDLSRCS = \
         SessionStore.idl \
         LoginManagerPrompter.idl \
-        WebappsSupport.idl \
         $(NULL)
 
 EXTRA_PP_COMPONENTS = \
         MobileComponents.manifest \
         AboutRedirector.js \
         BrowserCLH.js \
         BrowserStartup.js \
         DirectoryProvider.js\
@@ -71,17 +70,16 @@ EXTRA_COMPONENTS = \
         PromptService.js \
         ContentDispatchChooser.js \
         AutoCompleteCache.js \
         AddonUpdateService.js \
         FormAutoComplete.js \
         LoginManager.js \
         LoginManagerPrompter.js \
         BlocklistPrompt.js \
-        WebappsSupport.js \
         $(NULL)
 
 ifdef MOZ_SAFE_BROWSING
 EXTRA_COMPONENTS += SafeBrowsing.js
 endif
 
 ifdef MOZ_UPDATER
 EXTRA_COMPONENTS += UpdatePrompt.js
--- a/mobile/components/MobileComponents.manifest
+++ b/mobile/components/MobileComponents.manifest
@@ -115,12 +115,8 @@ contract @mozilla.org/safebrowsing/appli
 category app-startup SafeBrowsing service,@mozilla.org/safebrowsing/application;1
 #endif
 
 #ifdef MOZ_UPDATER
 # UpdatePrompt.js
 component {88b3eb21-d072-4e3b-886d-f89d8c49fe59} UpdatePrompt.js
 contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49fe59}
 #endif
-
-# webapps
-component {cb1107c1-1e15-4f11-99c8-27b9ec221a2a} WebappsSupport.js
-contract @mozilla.org/webapps/support;1 {cb1107c1-1e15-4f11-99c8-27b9ec221a2a}
deleted file mode 100644
--- a/mobile/components/WebappsSupport.idl
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Webapp code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Fabrice Desré <fabrice@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(adb91273-0cf1-4bbe-a37b-22e660192e2a)]
-interface nsIWebappsSupport : nsISupports
-{
-  /**
-   * This method installs a web app.
-   *
-   * @param title     the user-friendly name of the application.
-   * @param uri       the uri of the web app.
-   * @param iconData  a base64 encoded representation of the application's icon.
-   */
-  void installApplication(in wstring title, in wstring uri, in wstring iconUri, in wstring iconData);
-  
-  /**
-   * Checks is a web app is already installed
-   *
-   * @param uri the uri of the web app
-   * @return true if the web app is installed, false if it's not installed
-   */
-  boolean isApplicationInstalled(in wstring uri);
-};
-
deleted file mode 100644
--- a/mobile/components/WebappsSupport.js
+++ /dev/null
@@ -1,125 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Mobile Browser.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Fabrice Desré <fabrice@mozilla.com>
- *   Mark Finkle <mfinkle@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-const Cu = Components.utils; 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const DB_VERSION = 1;
-
-function WebappsSupport() {
-  this.init();
-}
-
-WebappsSupport.prototype = {
-  db: null,
-  
-  init: function() {
-    let file =  Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
-    file.append("webapps.sqlite");
-    this.db = Services.storage.openDatabase(file);
-    let version = this.db.schemaVersion;
-    
-    if (version == 0) {
-      this.db.executeSimpleSQL("CREATE TABLE webapps (title TEXT, uri TEXT PRIMARY KEY, icon TEXT)");
-      this.db.schemaVersion = DB_VERSION;
-    }
-
-    XPCOMUtils.defineLazyGetter(this, "_installQuery", function() {
-      return this.db.createAsyncStatement("INSERT INTO webapps (title, uri, icon) VALUES(:title, :uri, :icon)");
-    });
-
-    XPCOMUtils.defineLazyGetter(this, "_findQuery", function() {
-      return this.db.createStatement("SELECT uri FROM webapps where uri = :uri");
-    });
-
-    Services.obs.addObserver(this, "quit-application-granted", false);
-  },
- 
-  // entry point
-  installApplication: function(aTitle, aURI, aIconURI, aIconData) {
-    let stmt = this._installQuery;
-    stmt.params.title = aTitle;
-    stmt.params.uri = aURI;
-    stmt.params.icon = aIconData;
-    stmt.executeAsync();
-  },
- 
-  isApplicationInstalled: function(aURI) {
-    let stmt = this._findQuery;
-    let found = false;
-    try {
-      stmt.params.uri = aURI;
-      found = stmt.executeStep();
-    } finally {
-      stmt.reset();
-    }
-    return found;
-  },
-
-  // nsIObserver
-  observe: function(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(this, "quit-application-granted");
-
-    // Finalize the statements that we have used
-    let stmts = [
-      "_installQuery",
-      "_findQuery"
-    ];
-    for (let i = 0; i < stmts.length; i++) {
-      // We do not want to create any query we haven't already created, so
-      // see if it is a getter first.
-      if (Object.getOwnPropertyDescriptor(this, stmts[i]).value !== undefined) {
-        this[stmts[i]].finalize();
-      }
-    }
-
-    this.db.asyncClose();
-  },
-
-  // QI
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebappsSupport]),
-
-  // XPCOMUtils factory
-  classID: Components.ID("{cb1107c1-1e15-4f11-99c8-27b9ec221a2a}")
-};
-
-const NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsSupport]);
-
--- a/mobile/installer/package-manifest.in
+++ b/mobile/installer/package-manifest.in
@@ -266,17 +266,17 @@
 @BINPATH@/components/xpcom_threads.xpt
 @BINPATH@/components/xpcom_xpti.xpt
 @BINPATH@/components/xpconnect.xpt
 @BINPATH@/components/xulapp.xpt
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
-@BINPATH@/components/webapps.xpt
+@BINPATH@/components/openwebapps.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
@@ -322,16 +322,17 @@
 @BINPATH@/components/GPSDGeolocationProvider.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
+@BINPATH@/components/OpenWebapps.manifest
 
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
 #endif
 @BINPATH@/components/nsUpdateTimerManager.manifest
 @BINPATH@/components/nsUpdateTimerManager.js
@@ -599,18 +600,18 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/HelperAppDialog.js
 @BINPATH@/components/LoginManager.js
 @BINPATH@/components/LoginManagerPrompter.js
 @BINPATH@/components/MobileComponents.manifest
 @BINPATH@/components/MobileComponents.xpt
 @BINPATH@/components/PromptService.js
 @BINPATH@/components/SessionStore.js
 @BINPATH@/components/Sidebar.js
+@BINPATH@/components/OpenWebapps.js
 #ifdef MOZ_SAFE_BROWSING
 @BINPATH@/components/SafeBrowsing.js
 #endif
 #ifdef MOZ_UPDATER
 @BINPATH@/components/UpdatePrompt.js
 #endif
-@BINPATH@/components/WebappsSupport.js
 @BINPATH@/components/XPIDialogService.js
 @BINPATH@/components/browsercomps.xpt
 @BINPATH@/extensions/feedback@mobile.mozilla.org.xpi
--- a/mobile/locales/en-US/chrome/browser.dtd
+++ b/mobile/locales/en-US/chrome/browser.dtd
@@ -113,11 +113,10 @@
 <!ENTITY pageactions.saveas.pdf      "Save As PDF">
 <!ENTITY pageactions.share.page      "Share Page">
 <!ENTITY pageactions.password.forget "Forget Password">
 <!ENTITY pageactions.quit            "Quit">
 <!ENTITY pageactions.reset           "Clear Site Preferences">
 <!ENTITY pageactions.findInPage      "Find In Page">
 <!ENTITY pageactions.search.addNew   "Add Search Engine">
 <!ENTITY pageactions.charEncoding    "Character Encoding">
-<!ENTITY pageactions.webapps.install "Install as App">
 
 <!ENTITY appMenu.siteOptions         "Site Options">
--- a/toolkit/Makefile.in
+++ b/toolkit/Makefile.in
@@ -52,16 +52,17 @@ PARALLEL_DIRS = \
   mozapps/downloads \
   mozapps/extensions \
   mozapps/handling \
   mozapps/preferences \
   mozapps/plugins \
   mozapps/shared \
   mozapps/update \
   mozapps/xpinstall \
+  mozapps/webapps \
   obsolete \
   profile \
   themes \
   $(NULL)
 
 ifneq (,$(filter gtk2 qt,$(MOZ_WIDGET_TOOLKIT)))
 PARALLEL_DIRS += system/unixproxy
 endif
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/webapps/Makefile.in
@@ -0,0 +1,57 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Open Web Apps.
+#
+# The Initial Developer of the Original Code is Ben Goodger.
+# Portions created by the Initial Developer are Copyright (C) 2004
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Fabrice Desré <fabrice@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = openwebapps
+
+XPIDLSRCS = OpenWebapps.idl
+
+EXTRA_COMPONENTS += \
+  OpenWebapps.js \
+  OpenWebapps.manifest \
+  $(NULL)
+
+EXTRA_JS_MODULES = \
+  OpenWebapps.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/webapps/OpenWebapps.idl
@@ -0,0 +1,118 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Open Web Apps.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Fabrice Desré <fabrice@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// IDL for https://developer.mozilla.org/en/OpenWebApps/The_JavaScript_API
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(3b937eb5-679b-41e9-aefa-543849fa61dd)]
+interface nsIOpenWebappsApplication : nsISupports {
+    attribute jsval manifest;
+    attribute DOMString origin;
+    attribute jsval install_data;
+    attribute DOMString install_origin;
+    attribute unsigned long install_time;
+};
+
+[scriptable, function, uuid(fa3ac1bb-ad7d-44d7-8585-9ecdf3782d65)]
+interface nsIOpenWebappsSuccessInstalled : nsISupports {
+    void handle(in nsIOpenWebappsApplication application);
+};
+
+[scriptable, function, uuid(a8a83f45-4cbe-4806-b867-017554e30bd4)]
+interface nsIOpenWebappsSuccessList : nsISupports {
+    void handle([array, size_is(count)] in nsIOpenWebappsApplication apps,
+                in unsigned long count);
+};
+
+[scriptable, function, uuid(75e44e3f-ccda-4497-af68-8abd3f5e1d7b)]
+interface nsIOpenWebappsError : nsISupports {
+    attribute DOMString code;
+    attribute DOMString message;
+};
+
+[scriptable, function, uuid(8b29495e-a5e4-4e76-9af8-0f6fe97b8959)]
+interface nsIOpenWebappsErrorCB : nsISupports {
+    void handle(in nsIOpenWebappsError error);
+};
+
+[scriptable, function, uuid(b86669ab-6a36-4ceb-a4bf-a980dd496144)]
+interface nsIOpenWebappsSuccessEmpty : nsISupports {
+    void handle();
+};
+
+[scriptable, function, uuid(a458afcf-eee9-42fb-bd90-75d5e41c0d9e)]
+interface nsIOpenWebappsChangeCallback : nsISupports {
+    // what is either "add" when new apps are added to the repository, or
+    // "remove" when they are deleted.
+    void update(in DOMString what, [array, size_is(count)] in nsIOpenWebappsApplication apps,
+		in unsigned long count);
+};
+
+[scriptable, uuid(f3ec76a6-abca-4d90-b8c9-e221033068ef)]
+interface nsIOpenWebappsMgmt : nsISupports {
+    void launch(in DOMString origin,
+                [optional] in nsIOpenWebappsSuccessEmpty onsuccess,
+                [optional] in nsIOpenWebappsErrorCB onerror);
+    
+    void list(in nsIOpenWebappsSuccessList onsuccess,
+              [optional] in nsIOpenWebappsErrorCB onerror);
+    
+    void uninstall(in DOMString origin,
+                   in nsIOpenWebappsSuccessEmpty onsuccess,
+                   [optional] in nsIOpenWebappsErrorCB onerror);
+
+    long watchUpdates(in nsIOpenWebappsChangeCallback callback);
+
+    void clearWatch(in long watchId);
+};
+
+[scriptable, uuid(cecd9de7-ea4e-45fd-8a01-a5861d9109ab)]
+interface nsIOpenWebapps : nsISupports {
+    void install(in DOMString manifestURI,
+                 [optional] in jsval install_data,
+                 [optional] in nsIOpenWebappsSuccessEmpty onsuccess,
+                 [optional] in nsIOpenWebappsErrorCB onerror);
+    
+    void amInstalled(in nsIOpenWebappsSuccessInstalled onsuccess,
+                     [optional] in nsIOpenWebappsErrorCB onerror);
+    
+    void getInstalledBy(in nsIOpenWebappsSuccessList onsuccess,
+                        [optional] in nsIOpenWebappsErrorCB onerror);
+    
+    readonly attribute nsIOpenWebappsMgmt mgmt;
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/webapps/OpenWebapps.js
@@ -0,0 +1,296 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Open Web Apps.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Fabrice Desré <fabrice@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function OpenWebapps() {
+  this.messages = ["OpenWebapps:InstallDone", "OpenWebapps:InstallAborted", "OpenWebapps:GetInstalledBy:Return",
+                   "OpenWebapps:AmInstalled:Return", "OpenWebapps:MgmtLaunch:Return", "OpenWebapps:MgmtList:Return", 
+                   "OpenWebapps:MgmtUninstall:Return"];
+
+  this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
+
+  this.messages.forEach((function(msgName) {
+    this.mm.addMessageListener(msgName, this);
+  }).bind(this));
+
+  this._callbacks = [];
+  this._window = null;
+  this._watchId = 0;
+}
+
+OpenWebapps.prototype = {
+  
+  /** from https://developer.mozilla.org/en/OpenWebApps/The_Manifest
+   * only the name property is mandatory
+   */
+  checkManifest: function(aManifest) {
+    return ("name" in aManifest);
+  },
+  
+  getCallbackId: function(aCallback) {
+    let id = "id" + this._getRandomId();
+    this._callbacks[id] = aCallback;
+    return id;
+  },
+  
+  getCallback: function(aId) {
+    return this._callbacks[aId];
+  },
+
+  removeCallback: function(aId) {
+    if (this._callbacks[aId])
+      delete this._callbacks[aId];
+  },
+  
+  _getRandomId: function() {
+    return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+  },
+
+  _convertAppsArray: function(aApps) {
+    let apps = new Array();
+    for (let i = 0; i < aApps.length; i++) {
+      let app = aApps[i];
+      let xapp = Cc["@mozilla.org/openwebapps/application;1"].createInstance(Ci.nsIOpenWebappsApplication);
+      xapp.origin = app.origin;
+      xapp.manifest = app.manifest;
+      xapp.install_data = app.install_data;
+      xapp.install_origin = app.install_origin;
+      xapp.install_time = app.install_time;
+      apps.push(xapp);
+    }
+    return apps;
+  },
+
+  receiveMessage: function(aMessage) {
+    let msg = aMessage.json;
+    let callbacks = this.getCallback(msg.callbackID);
+
+    // if we have no such callback and this is not a broadcast message, bail out
+    if (!callbacks && aMessage.name != "OpenWebapps:InstallDone"
+                   && aMessage.name != "OpenWebapps:MgmtUninstall:Return")
+      return;
+
+    switch(aMessage.name) {
+      case "OpenWebapps:InstallAborted" :
+        if (callbacks.error)
+          callbacks.error.handle({ code: "denied", message: "User denied installation" });
+        break;
+      case "OpenWebapps:InstallDone" :
+        if (callbacks && callbacks.success)
+          callbacks.success.handle();
+        this._onInstalled([msg.app]);
+        break;
+      case "OpenWebapps:GetInstalledBy:Return":
+        if (callbacks && callbacks.success) {
+          let apps = this._convertAppsArray(msg.apps);
+          callbacks.success.handle(apps, apps.length);
+        }
+        break;
+      case "OpenWebapps:AmInstalled:Return":
+        if (callbacks.success)
+          callbacks.success.handle(msg.installed ? msg.app : null);
+        break;
+      case "OpenWebapps:MgmtLaunch:Return":
+        if (msg.ok && callbacks && callbacks.success)
+          callbacks.success.handle();
+        else if (!msg.ok && callbacks.error)
+          callbacks.error.handle({ code: "noSuchApp", message: "Unable to launch application"});
+        break;
+      case "OpenWebapps:MgmtList:Return":
+        if (msg.ok && callbacks && callbacks.success) {
+          let apps = this._convertAppsArray(msg.apps);
+          callbacks.success.handle(apps, apps.length);
+        }
+        else if (!msg.ok && callbacks && callbacks.error) {
+          callbacks.error.handle({ code: "noAppList", message: "Unable to get application list"});
+        }
+        break;
+      case "OpenWebapps:MgmtUninstall:Return":
+        if (msg.ok) {
+          if (callbacks && callbacks.success)
+            callbacks.success.handle();
+          this._onUninstalled([msg.app]);
+        }
+        else if (!msg.ok && callbacks.error)
+          callbacks.error.handle({ code: "noSuchApp", message: "Unable to uninstall application"});
+        break;
+    }
+    this.removeCallback(msg.callbackID);
+  },
+  
+  // nsIOpenWebapps implementation
+  
+  install: function(aURL, aInstallData, aSuccess, aError) {
+    let self = this;
+
+    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+    xhr.open("GET", aURL, true);
+
+    xhr.onload = function() {
+      if (xhr.status == 200) {
+        try {
+          let manifest = JSON.parse(xhr.responseText);
+          if (!self.checkManifest(manifest)) {
+            if (aError)
+              aError.handle({ code: "invalidManifest", message: "Invalid manifest" });
+          } else {
+            self.mm.sendAsyncMessage("OpenWebapps:Install", { storeURI: self._window.location.href, manifestURI: aURL, manifest: xhr.responseText,
+                             installData: aInstallData, callbackID: self.getCallbackId({ success: aSuccess, error: aError }) });
+          }
+        } catch(e) {
+          if (aError)
+            aError.handle({ code: "manifestParseError", message: "Unable to parse the manifest" });
+        }
+      }
+      else if (aError) {
+        aError.handle({ code: "networkError", message: "Unable to retrieve manifest" });
+      }      
+    }
+
+    xhr.onerror = function() {
+      if (aError)
+        aError.handle({ code: "networkError", message: "Unable to retrieve manifest" });
+    }
+
+    xhr.send(null);
+  },
+  
+  amInstalled: function(aSuccess, aError) {
+    this.mm.sendAsyncMessage("OpenWebapps:AmInstalled", { appURI: this._window.location.href, callbackID:  this.getCallbackId({ success: aSuccess, error: aError }) });
+  },
+  
+  getInstalledBy: function(aSuccess, aError) {
+    this.mm.sendAsyncMessage("OpenWebapps:GetInstalledBy", { storeURI: this._window.location.href, callbackID:  this.getCallbackId({ success: aSuccess, error: aError }) });
+  },
+  
+  // nsIOpenWebappsMgmt implementation
+  launch: function(aOrigin, aSuccess, aError) {
+    this.mm.sendAsyncMessage("OpenWebapps:MgmtLaunch", { origin: aOrigin, callbackID:  this.getCallbackId({ success: aSuccess, error: aError }) });
+  },
+  
+  list: function(aSuccess, aError) {
+    this.mm.sendAsyncMessage("OpenWebapps:MgmtList", { callbackID:  this.getCallbackId({ success: aSuccess, error: aError }) });
+  },
+  
+  uninstall: function(aOrigin, aSuccess, aError) {
+    this.mm.sendAsyncMessage("OpenWebapps:MgmtUninstall", { origin: aOrigin, callbackID:  this.getCallbackId({ success: aSuccess, error: aError }) });
+  },
+
+  _onRepoChange: function(aWhat, aApps) {
+    for (let prop in this._callbacks) {
+      if (this._callbacks[prop].isWatch) {
+        let apps = this._convertAppsArray(aApps);
+        this._callbacks[prop].callback.update(aWhat, apps, apps.length);
+      }
+    }
+  },
+
+  _onInstalled: function(aApps) {
+    this._onRepoChange("add", aApps);
+  },
+
+  _onUninstalled: function(aApps) {
+    this._onRepoChange("remove", aApps);
+  },
+
+  watchUpdates: function(aCallback) {
+    this._watchId++;
+    this._callbacks["_watch" + this._getRandomId()] = { isWatch: true, callback: aCallback };
+    return this._watchId;
+  },
+
+  clearWatch: function(aWatchId) {
+    this.removeCallback("_watch" + aWatchId);
+  },
+
+  handleEvent: function(aEvent) {
+    if (aEvent.type == "unload") {
+      // remove all callbacks so we don't call anything on a cleared scope
+      this._callbacks = [];
+    }
+  },
+  
+  // nsIDOMGlobalPropertyInitializer implementation
+  init: function(aWindow) {
+    this._window = aWindow;
+    this._window.addEventListener("unload", this, false);
+  },
+  
+  get mgmt() {
+    return this.QueryInterface(Ci.nsIOpenWebappsMgmt);
+  },
+  
+  classID: Components.ID("{d8fd4d63-27ea-47b9-a931-481214bb8b5b}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIOpenWebapps, Ci.nsIOpenWebappsMgmt, Ci.nsIDOMGlobalPropertyInitializer]),
+  
+  classInfo: XPCOMUtils.generateCI({classID: Components.ID("{d8fd4d63-27ea-47b9-a931-481214bb8b5b}"),
+                                    contractID: "@mozilla.org/openwebapps;1",
+                                    interfaces: [Ci.nsIOpenWebapps],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
+                                    classDescription: "OpenWebapps"})
+}
+
+function OpenWebappsApplication() {
+}
+
+OpenWebappsApplication.prototype = {
+  origin: null,
+  manifest: null,
+  install_data: null,
+  install_origin: null,
+  install_time: 0,
+
+  classID: Components.ID("{34456347-0792-45a4-8eb1-7b5f94f2d700}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIOpenWebappsApplication]),
+
+  classInfo: XPCOMUtils.generateCI({classID: Components.ID("{34456347-0792-45a4-8eb1-7b5f94f2d700}"),
+                                    contractID: "@mozilla.org/openwebapps/application;1",
+                                    interfaces: [Ci.nsIOpenWebappsApplication],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT,
+                                    classDescription: "OpenWebapps Application"})
+}
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([OpenWebapps, OpenWebappsApplication]);
+
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/webapps/OpenWebapps.jsm
@@ -0,0 +1,251 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Fabrice Desré <fabrice@mozilla.com>
+ *   Mark Finkle <mfinkle@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cu = Components.utils; 
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let EXPORTED_SYMBOLS = ["OpenWebapps"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+  Cu.import("resource://gre/modules/NetUtil.jsm");
+  return NetUtil;
+});
+
+let OpenWebapps = {
+  appsDir: null,
+  appsFile: null,
+  webapps: { },
+
+  init: function() {
+    let file =  Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+    file.append("webapps");
+    if (!file.exists() || !file.isDirectory()) {
+      file.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+    }
+    this.appsDir = file;
+    this.appsFile = file.clone();
+    this.appsFile.append("webapps.json");
+    if (!this.appsFile.exists())
+      return;
+    
+    try {
+      let channel = NetUtil.newChannel(this.appsFile);
+      channel.contentType = "application/json";
+      let self = this;
+      NetUtil.asyncFetch(channel, function(aStream, aResult) {
+        if (!Components.isSuccessCode(aResult)) {
+          Cu.reportError("OpenWebappsSupport: Could not read from json file " + this.appsFile.path);
+          return;
+        }
+
+        // Read json file into a string
+        let data = null;
+        try {
+          self.webapps = JSON.parse(NetUtil.readInputStreamToString(aStream, aStream.available()) || "");
+          aStream.close();
+        } catch (ex) {
+          Cu.reportError("OpenWebsappsStore: Could not parse JSON: " + ex);
+        }
+      });
+    } catch (ex) {
+      Cu.reportError("OpenWebappsSupport: Could not read from " + aFile.path + " : " + ex);
+    }
+  },
+
+  _writeFile: function ss_writeFile(aFile, aData) {
+    // Initialize the file output stream.
+    let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+    ostream.init(aFile, 0x02 | 0x08 | 0x20, 0600, ostream.DEFER_OPEN);
+
+    // Obtain a converter to convert our data to a UTF-8 encoded input stream.
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+
+    // Asynchronously copy the data to the file.
+    let istream = converter.convertToInputStream(aData);
+    NetUtil.asyncCopy(istream, ostream, function(rc) {
+      // nothing to do
+    });
+  },
+  
+  install: function(aApplication) {
+    let id = this._appId(aApplication.appURI);
+
+    // install an application again is considered as an update
+    if (id) {
+      let dir = this.appsDir.clone();
+      dir.append(id);
+      try {
+        dir.remove(true);
+      } catch(e) {
+      }
+    }
+    else {
+      let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+      id = uuidGenerator.generateUUID().toString();
+    }
+
+    let dir = this.appsDir.clone();
+    dir.append(id);
+    dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+    
+    let manFile = dir.clone();
+    manFile.append("manifest.json");
+    this._writeFile(manFile, JSON.stringify(aApplication.manifest));
+    
+    this.webapps[id] = {
+      title: aApplication.title,
+      storeURI: aApplication.storeURI,
+      appURI: aApplication.appURI,
+      installData: aApplication.installData,
+      installTime: (new Date()).getTime()
+    };
+    this._writeFile(this.appsFile, JSON.stringify(this.webapps));
+  },
+ 
+  _appId: function(aURI) {
+    for (let id in this.webapps) {
+      if (this.webapps[id].appURI == aURI)
+        return id;
+    }
+    return null;
+  },
+
+  _readManifest: function(aId) {
+    let file = this.appsDir.clone();
+    file.append(aId);
+    file.append("manifest.json");
+    let data = "";  
+    let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+    var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
+    fstream.init(file, -1, 0, 0);
+    cstream.init(fstream, "UTF-8", 0, 0);
+    let (str = {}) {  
+      let read = 0;  
+      do {   
+        read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value  
+        data += str.value;  
+      } while (read != 0);  
+    }  
+    cstream.close(); // this closes fstream  
+    try {
+      return JSON.parse(data);
+    } catch(e) {
+      return null;
+    }
+  },
+
+  amInstalled: function(aURI) {
+    for (let id in this.webapps) {
+      let app = this.webapps[id];
+      if (app.appURI == aURI) {
+        return { origin: app.appURI,
+                 install_origin: app.storeURI,
+                 install_data: app.installData,
+                 install_time: app.installTime,
+                 manifest: this._readManifest(id) };
+      }
+    }
+    return null;
+  },
+
+  getInstalledBy: function(aStoreURI) {
+    let res = [];
+    for (let id in this.webapps) {
+      let app = this.webapps[id];
+      if (app.storeURI == aStoreURI)
+        res.push({ origin: app.appURI,
+                   install_origin: app.storeURI,
+                   install_data: app.installData,
+                   install_time: app.installTime,
+                   manifest: this._readManifest(id) });
+    }
+    return res;
+  },
+  
+  mgmtList: function() {
+    let res = new Array();
+    for (let id in this.webapps) {
+      let app = this.webapps[id];
+      res.push({ origin: app.appURI,
+                 install_origin: app.storeURI,
+                 install_data: app.installData,
+                 install_time: app.installTime,
+                 manifest: this._readManifest(id) });
+    }
+    return res;
+  },
+  
+  mgmtLaunch: function(aOrigin) {
+    for (let id in this.webapps) {
+      let app = this.webapps[id];
+      app.manifest = this._readManifest(id);
+      if (app.appURI == aOrigin) {
+        let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
+        let uri = Services.io.newURI(aOrigin + (app.manifest.launch_path ? app.manifest.launch_path : ""), null, null);
+        browserWin.browserDOMWindow.openURI(uri, null, browserWin.OPEN_APPTAB, Ci.nsIBrowserDOMWindow.OPEN_NEW);
+        return true;
+      }
+    }
+    return false;
+  },
+  
+  mgmtUninstall: function(aOrigin) {
+    for (let id in this.webapps) {
+      let app = this.webapps[id];
+      if (app.appURI == aOrigin) {
+        delete this.webapps[id];
+        this._writeFile(this.appsFile, JSON.stringify(this.webapps));
+        let dir = this.appsDir.clone();
+        dir.append(id);
+        try {
+          dir.remove(true);
+        } catch (e) {
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+};
+
+OpenWebapps.init();
+
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/webapps/OpenWebapps.manifest
@@ -0,0 +1,8 @@
+# OpenWebapps.js
+component {d8fd4d63-27ea-47b9-a931-481214bb8b5b} OpenWebapps.js
+contract @mozilla.org/openwebapps;1 {d8fd4d63-27ea-47b9-a931-481214bb8b5b}
+category JavaScript-navigator-property mozApps @mozilla.org/openwebapps;1
+
+component {34456347-0792-45a4-8eb1-7b5f94f2d700} OpenWebapps.js
+contract @mozilla.org/openwebapps/application;1 {34456347-0792-45a4-8eb1-7b5f94f2d700}
+