Bug 697309 - Add support for the Open Web Apps API - part 2 : about:apps r=mbrubeck, fabrice
☠☠ backed out by 2989466dba9e ☠ ☠
authorMark Finkle <mfinkle@mozilla.com>
Tue, 27 Mar 2012 17:44:28 -0400
changeset 93772 add087add91aa8a02999eb6789a56ac492eb42d7
parent 93771 5f20ab3487dba4159ae56ea9f1bbd06d56d839e3
child 93773 967263f4d02053b8a475a94141a95a8c59611738
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck, fabrice
bugs697309
milestone14.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 697309 - Add support for the Open Web Apps API - part 2 : about:apps r=mbrubeck, fabrice
mobile/android/app/mobile.js
mobile/android/chrome/content/aboutApps.js
mobile/android/chrome/content/aboutApps.xhtml
mobile/android/chrome/content/browser.js
mobile/android/chrome/jar.mn
mobile/android/components/AboutRedirector.js
mobile/android/components/MobileComponents.manifest
mobile/android/locales/en-US/chrome/aboutApps.dtd
mobile/android/locales/en-US/chrome/aboutApps.properties
mobile/android/locales/jar.mn
mobile/android/themes/core/aboutApps.css
mobile/android/themes/core/jar.mn
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -480,16 +480,17 @@ pref("app.releaseNotesURL", "http://www.
 #endif
 #if MOZ_UPDATE_CHANNEL == beta
 pref("app.featuresURL", "http://www.mozilla.com/%LOCALE%/mobile/beta/features/");
 pref("app.faqURL", "http://www.mozilla.com/%LOCALE%/mobile/beta/faq/");
 #else
 pref("app.featuresURL", "http://www.mozilla.com/%LOCALE%/mobile/features/");
 pref("app.faqURL", "http://www.mozilla.com/%LOCALE%/mobile/faq/");
 #endif
+pref("app.marketplaceURL", "https://marketplace.mozilla.org/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 pref("security.warn_viewing_mixed", false); // Warning is disabled.  See Bug 616712.
 
 // Override some named colors to avoid inverse OS themes
 pref("ui.-moz-dialog", "#efebe7");
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/aboutApps.js
@@ -0,0 +1,160 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * 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/.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm")
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Webapps.jsm");
+
+let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutApps.properties");
+
+XPCOMUtils.defineLazyGetter(window, "gChromeWin", function()
+  window.QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIWebNavigation)
+    .QueryInterface(Ci.nsIDocShellTreeItem)
+    .rootTreeItem
+    .QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIDOMWindow)
+    .QueryInterface(Ci.nsIDOMChromeWindow));
+
+var AppsUI = {
+  uninstall: null,
+  shortcut: null
+};
+
+function onLoad(aEvent) {
+  try {
+    let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
+    let link = document.getElementById("marketplaceURL");
+    let url = formatter.formatURLPref(link.getAttribute("pref"));
+    link.setAttribute("href", url);
+  } catch (e) {}
+
+  navigator.mozApps.mgmt.oninstall = onInstall;
+  navigator.mozApps.mgmt.onuninstall = onUninstall;
+  updateList();
+
+  let contextmenus = gChromeWin.NativeWindow.contextmenus;
+  AppsUI.shortcut = contextmenus.add(gStrings.GetStringFromName("appsContext.shortcut"), contextmenus.SelectorContext("div[mozApp]"),
+    function(aTarget) {
+      let manifest = aTarget.manifest;
+      createShortcut(manifest.name, manifest.fullLaunchPath(), manifest.iconURLForSize("64"), "webapp");
+    });
+  AppsUI.uninstall = contextmenus.add(gStrings.GetStringFromName("appsContext.uninstall"), contextmenus.SelectorContext("div[mozApp]"),
+    function(aTarget) {
+      aTarget.app.uninstall();
+    });
+}
+
+function onUnload(aEvent) {
+  let contextmenus = gChromeWin.NativeWindow.contextmenus;
+  if (AppsUI.shortcut)
+    contextmenus.remove(AppsUI.shortcut);
+  if (AppsUI.uninstall)
+    contextmenus.remove(AppsUI.uninstall);
+}
+
+function updateList() {
+  let grid = document.getElementById("appgrid");
+  while (grid.lastChild) {
+    grid.removeChild(grid.lastChild);
+  }
+
+  let request = navigator.mozApps.mgmt.getAll();
+  request.onsuccess = function() {
+    for (let i = 0; i < request.result.length; i++)
+      addApplication(request.result[i]);
+    if (!request.result.length)
+      document.getElementById("noapps").className = "";
+  }
+}
+
+function addApplication(aApp) {
+  let list = document.getElementById("appgrid");
+  let manifest = new DOMApplicationManifest(aApp.manifest, aApp.origin);
+
+  let container = document.createElement("div");
+  container.className = "app";
+  container.setAttribute("id", "app-" + aApp.origin);
+  container.setAttribute("mozApp", aApp.origin);
+  container.setAttribute("title", manifest.name);
+
+  let img = document.createElement("img");
+  img.src = manifest.iconURLForSize("64");
+  img.setAttribute("title", manifest.name);
+
+  let title = document.createElement("div");
+  title.appendChild(document.createTextNode(manifest.name));
+
+  container.appendChild(img);
+  container.appendChild(title);
+  list.appendChild(container);
+
+  container.addEventListener("click", function(aEvent) {
+    aApp.launch();
+  }, false);
+  container.app = aApp;
+  container.manifest = manifest;
+}
+
+function onInstall(aEvent) {
+  let node = document.getElementById("app-" + aEvent.application.origin);
+  if (node)
+    return;
+
+  addApplication(aEvent.application);
+  document.getElementById("noapps").className = "hidden";
+}
+
+function onUninstall(aEvent) {
+  let node = document.getElementById("app-" + aEvent.application.origin);
+  if (node) {
+    let parent = node.parentNode;
+    parent.removeChild(node);
+    if (!parent.firstChild)
+      document.getElementById("noapps").className = "";
+  }
+}
+
+function createShortcut(aTitle, aURL, aIconURL, aType) {
+  // The background images are 72px, but Android will resize as needed.
+  // Bigger is better than too small.
+  const kIconSize = 72;
+  const kOverlaySize = 32;
+  const kOffset = 20;
+
+  let canvas = document.createElement("canvas");
+
+  function _createShortcut() {
+    let icon = canvas.toDataURL("image/png", "");
+    canvas = null;
+    try {
+      let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
+      shell.createShortcut(aTitle, aURL, icon, aType);
+    } catch(e) {
+      Cu.reportError(e);
+    }
+  }
+
+  canvas.width = canvas.height = kIconSize;
+  let ctx = canvas.getContext("2d");
+
+  let favicon = new Image();
+  favicon.onload = function() {
+    // Center the favicon and overlay it on the background
+    ctx.drawImage(favicon, kOffset, kOffset, kOverlaySize, kOverlaySize);
+    _createShortcut();
+  }
+
+  favicon.onerror = function() {
+    Cu.reportError("CreateShortcut: favicon image load error");
+  }
+
+  favicon.src = aIconURL;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/content/aboutApps.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+# ***** BEGIN LICENSE BLOCK *****
+#
+# 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/.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+  %globalDTD;
+  <!ENTITY % aboutAppsDTD SYSTEM "chrome://browser/locale/aboutApps.dtd">
+  %aboutAppsDTD;
+  <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+  %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>&aboutApps.title;</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
+    <link rel="icon" type="image/png" href="chrome://branding/content/favicon32.png" />
+    <link rel="stylesheet" type="text/css" href="chrome://browser/skin/aboutApps.css" media="all" />
+    <script type="text/javascript;version=1.8" src="chrome://browser/content/aboutApps.js"></script>
+  </head>
+
+  <body dir="&locale.dir;" onload="onLoad(event)" onunload="onUnload(event)">
+    <div id="main-container">
+      <div>&aboutApps.title;</div>
+      <div id="noapps" class="hidden">
+        &aboutApps.noApps.pre;<a id="marketplaceURL" pref="app.marketplaceURL">&aboutApps.noApps.middle;</a>&aboutApps.noApps.post;
+      </div>
+      <div>
+        <div class="spacer" id="spacer1"> </div>
+        <div id="appgrid"/>
+        <div class="spacer" id="spacer1"> </div>
+      </div>
+    </div>
+  </body>
+</html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1283,17 +1283,19 @@ var NativeWindow = {
     },
 
     _show: function(aEvent) {
       if (aEvent.defaultPrevented)
         return;
 
       let popupNode = aEvent.originalTarget;
       let title = "";
-      if ((popupNode instanceof Ci.nsIDOMHTMLAnchorElement && popupNode.href) ||
+      if (popupNode.hasAttribute("title")) {
+        title = popupNode.getAttribute("title")
+      } else if ((popupNode instanceof Ci.nsIDOMHTMLAnchorElement && popupNode.href) ||
               (popupNode instanceof Ci.nsIDOMHTMLAreaElement && popupNode.href)) {
         title = this._getLinkURL(popupNode);
       } else if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
         title = popupNode.currentURI.spec;
       } else if (popupNode instanceof Ci.nsIDOMHTMLMediaElement) {
         title = (popupNode.currentSrc || popupNode.src);
       }
 
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -5,16 +5,18 @@ chrome.jar:
 
 * content/about.xhtml                  (content/about.xhtml)
   content/config.xhtml                 (content/config.xhtml)
   content/aboutAddons.xhtml            (content/aboutAddons.xhtml)
   content/aboutAddons.js               (content/aboutAddons.js)
   content/aboutCertError.xhtml         (content/aboutCertError.xhtml)
   content/aboutHome.xhtml              (content/aboutHome.xhtml)
 * content/aboutRights.xhtml            (content/aboutRights.xhtml)
+* content/aboutApps.xhtml              (content/aboutApps.xhtml)
+  content/aboutApps.js                 (content/aboutApps.js)
   content/blockedSite.xhtml            (content/blockedSite.xhtml)
   content/languages.properties         (content/languages.properties)
 * content/browser.xul                  (content/browser.xul)
 * content/browser.js                   (content/browser.js)
 * content/bindings.xml                 (content/bindings.xml)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/settings.xml        (content/bindings/settings.xml)
   content/bindings/dialog.xml          (content/bindings/dialog.xml)
--- a/mobile/android/components/AboutRedirector.js
+++ b/mobile/android/components/AboutRedirector.js
@@ -80,16 +80,20 @@ let modules = {
   certerror: {
     uri: "chrome://browser/content/aboutCertError.xhtml",
     privileged: true,
     hide: true
   },
   home: {
     uri: "chrome://browser/content/aboutHome.xhtml",
     privileged: true
+  },
+  apps: {
+    uri: "chrome://browser/content/aboutApps.xhtml",
+    privileged: true
   }
 }
 
 function AboutRedirector() {}
 AboutRedirector.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
   classID: Components.ID("{322ba47e-7047-4f71-aebf-cb7d69325cd9}"),
 
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -2,16 +2,17 @@
 component {322ba47e-7047-4f71-aebf-cb7d69325cd9} AboutRedirector.js
 contract @mozilla.org/network/protocol/about;1?what= {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=fennec {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=firefox {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=empty {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=rights {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=certerror {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 contract @mozilla.org/network/protocol/about;1?what=home {322ba47e-7047-4f71-aebf-cb7d69325cd9}
+contract @mozilla.org/network/protocol/about;1?what=apps {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 #ifdef MOZ_SAFE_BROWSING
 contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-aebf-cb7d69325cd9}
 #endif
 
 # DirectoryProvider.js
 component {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b} DirectoryProvider.js
 contract @mozilla.org/browser/directory-provider;1 {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}
 category xpcom-directory-providers browser-directory-provider @mozilla.org/browser/directory-provider;1
new file mode 100644
--- /dev/null
+++ b/mobile/android/locales/en-US/chrome/aboutApps.dtd
@@ -0,0 +1,8 @@
+<!ENTITY aboutApps.title "Your Apps">
+
+<!-- LOCALIZATION NOTE (aboutApps.noApps.pre): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (aboutApps.noApps.middle): avoid leading/trailing spaces, this text is a link -->
+<!-- LOCALIZATION NOTE (aboutApps.noApps.post): include a starting space as needed -->
+<!ENTITY aboutApps.noApps.pre "No web apps installed. Get some from the ">
+<!ENTITY aboutApps.noApps.middle "app store">
+<!ENTITY aboutApps.noApps.post ".">
new file mode 100644
--- /dev/null
+++ b/mobile/android/locales/en-US/chrome/aboutApps.properties
@@ -0,0 +1,2 @@
+appsContext.uninstall=Uninstall
+appsContext.shortcut=Add to Home Screen
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -1,15 +1,17 @@
 #filter substitution
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/@AB_CD@/browser/
   locale/@AB_CD@/browser/about.dtd                (%chrome/about.dtd)
   locale/@AB_CD@/browser/aboutAddons.dtd          (%chrome/aboutAddons.dtd)
   locale/@AB_CD@/browser/aboutAddons.properties   (%chrome/aboutAddons.properties)
+  locale/@AB_CD@/browser/aboutApps.dtd            (%chrome/aboutApps.dtd)
+  locale/@AB_CD@/browser/aboutApps.properties     (%chrome/aboutApps.properties)
   locale/@AB_CD@/browser/aboutCertError.dtd       (%chrome/aboutCertError.dtd)
   locale/@AB_CD@/browser/browser.properties       (%chrome/browser.properties)
   locale/@AB_CD@/browser/config.dtd               (%chrome/config.dtd)
   locale/@AB_CD@/browser/config.properties        (%chrome/config.properties)
   locale/@AB_CD@/browser/localepicker.properties  (%chrome/localepicker.properties)
   locale/@AB_CD@/browser/aboutHome.dtd            (%chrome/aboutHome.dtd)
   locale/@AB_CD@/browser/checkbox.dtd             (%chrome/checkbox.dtd)
   locale/@AB_CD@/browser/notification.dtd         (%chrome/notification.dtd)
new file mode 100644
--- /dev/null
+++ b/mobile/android/themes/core/aboutApps.css
@@ -0,0 +1,43 @@
+
+html {
+  font-family: "Droid Sans",helvetica,arial,clean,sans-serif;
+  font-size: 18px;
+  background-image: url("chrome://browser/skin/images/about-bg-lightblue.png");
+  -moz-text-size-adjust: none;
+}
+
+#main-container {
+  margin: 1em;
+  padding: 1em;
+  border-radius: 10px;
+  border: 1px solid grey;
+  background-color: white;
+}
+
+.spacer {
+  clear: both;
+}
+
+.app {
+  float: left;
+  cursor: pointer;
+  text-align: center;
+  margin: 1em;
+  width: 70px;
+  height: 85px;
+  font-size: 10px;
+}
+
+.app img {
+  width: 64px;
+  height: 64px;
+}
+
+#noapps {
+  padding: 1em;
+  text-align: center;
+}
+
+.hidden {
+  display: none;
+}
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -1,15 +1,16 @@
 #filter substitution
 
 chrome.jar:
 % skin browser classic/1.0 %skin/
   skin/aboutPage.css                        (aboutPage.css)
   skin/about.css                            (about.css)
   skin/aboutAddons.css                      (aboutAddons.css)
+  skin/aboutApps.css                        (aboutApps.css)
 * skin/browser.css                          (browser.css)
 * skin/content.css                          (content.css)
   skin/config.css                           (config.css)
   skin/touchcontrols.css                    (touchcontrols.css)
   skin/netError.css                         (netError.css)
 % override chrome://global/skin/about.css chrome://browser/skin/about.css
 % override chrome://global/skin/media/videocontrols.css chrome://browser/skin/touchcontrols.css
 % override chrome://global/skin/netError.css chrome://browser/skin/netError.css