Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 19 Feb 2014 15:45:14 -0500
changeset 169945 a304d2065f65ee3bdbd9f428583cc28c48980ac7
parent 169906 71f4051ae6b392b59e005b77bf84bcb1f754a420 (current diff)
parent 169944 3762e1037b596a3f6f36bf42144aae70482f1ecf (diff)
child 169946 f13eb52f4eb036834de4bb6b28f19f6dd5a549e7
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge m-c to inbound.
browser/components/sessionstore/src/FormData.jsm
browser/components/sessionstore/src/ScrollPosition.jsm
browser/components/sessionstore/src/XPathGenerator.jsm
mobile/android/base/home/PanelGridItemView.java
mobile/android/base/home/PanelListRow.java
mobile/android/base/resources/layout/panel_grid_item_view.xml
mobile/android/base/resources/layout/panel_list_row.xml
new file mode 100644
--- /dev/null
+++ b/b2g/chrome/content/shell.css
@@ -0,0 +1,17 @@
+/* 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/. */
+
+html {
+  background: black;
+  overflow: hidden;
+  width: 100%;
+  height: 100%;
+  padding: 0 !important;
+}
+
+body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+}
--- a/b2g/chrome/content/shell.html
+++ b/b2g/chrome/content/shell.html
@@ -4,20 +4,20 @@
    - You can obtain one at http://mozilla.org/MPL/2.0/.  -->
 
 <html xmlns="http://www.w3.org/1999/xhtml "
       id="shell"
       windowtype="navigator:browser"
 #ifdef ANDROID
       sizemode="fullscreen"
 #endif
-      style="background: black; overflow: hidden; width:100%; height:100%; padding: 0px !important"
-      onunload="shell.stop();">
+      >
 
 <head>
+  <link rel="stylesheet" href="shell.css" type="text/css" media="all" />
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/settings.js"> </script>
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/shell.js"> </script>
 
 #ifndef MOZ_WIDGET_GONK
   <!-- various task that has to happen only on desktop -->
   <script type="application/javascript;version=1.8"
@@ -25,17 +25,17 @@
   <!-- this script handles the screen argument for desktop builds -->
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/screen.js"> </script>
   <!-- this script handles the "runapp" argument for desktop builds -->
   <script type="application/javascript;version=1.8"
           src="chrome://b2g/content/runapp.js"> </script>
 #endif
 </head>
-  <body id="container" style="margin: 0px; width:100%; height:100%;">
+  <body id="container">
 #ifdef MOZ_WIDGET_COCOA
     <!--
      If the document is empty at startup, we don't display the window
      at all on Mac OS...
     -->
     <h1 id="placeholder">wtf mac os!</h1>
 #endif
     <!-- The html:iframe containing the UI is created here. -->
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -310,16 +310,17 @@ var shell = {
     chromeEventHandler.addEventListener('keydown', this, true);
     chromeEventHandler.addEventListener('keypress', this, true);
     chromeEventHandler.addEventListener('keyup', this, true);
 
     window.addEventListener('MozApplicationManifest', this);
     window.addEventListener('mozfullscreenchange', this);
     window.addEventListener('MozAfterPaint', this);
     window.addEventListener('sizemodechange', this);
+    window.addEventListener('unload', this);
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
 
     CustomEventManager.init();
     WebappsHelper.init();
     UserAgentOverrides.init();
     IndexedDBPromptHelper.init();
     CaptivePortalLoginHelper.init();
 
@@ -333,16 +334,17 @@ var shell = {
     ppmm.addMessageListener("app-notification-send", AlertsHelper);
     ppmm.addMessageListener("file-picker", this);
     ppmm.addMessageListener("getProfD", function(message) {
       return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
     });
   },
 
   stop: function shell_stop() {
+    window.removeEventListener('unload', this);
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keypress', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('mozfullscreenchange', this);
     window.removeEventListener('sizemodechange', this);
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
     ppmm.removeMessageListener("content-handler", this);
@@ -532,16 +534,19 @@ var shell = {
         }
         break;
       case 'MozAfterPaint':
         window.removeEventListener('MozAfterPaint', this);
         this.sendChromeEvent({
           type: 'system-first-paint'
         });
         break;
+      case 'unload':
+        this.stop();
+        break;
     }
   },
 
   sendEvent: function shell_sendEvent(content, type, details) {
     let event = content.document.createEvent('CustomEvent');
     event.initCustomEvent(type, true, true, details ? details : {});
     content.dispatchEvent(event);
   },
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -8,16 +8,17 @@ chrome.jar:
 % content branding %content/branding/
 % content b2g %content/
 
   content/arrow.svg                     (content/arrow.svg)
 * content/dbg-browser-actors.js         (content/dbg-browser-actors.js)
 * content/settings.js                   (content/settings.js)
 * content/shell.html                    (content/shell.html)
 * content/shell.js                      (content/shell.js)
+  content/shell.css                     (content/shell.css)
   content/devtools.js                   (content/devtools.js)
 #ifndef ANDROID
   content/desktop.js                    (content/desktop.js)
   content/screen.js                     (content/screen.js)
   content/runapp.js                     (content/runapp.js)
 #endif
 * content/content.css                   (content/content.css)
   content/touchcontrols.css             (content/touchcontrols.css)
--- a/b2g/components/FxAccountsMgmtService.jsm
+++ b/b2g/components/FxAccountsMgmtService.jsm
@@ -124,16 +124,17 @@ this.FxAccountsMgmtService = {
           },
           reason => {
             self._onReject(msg.id, reason);
           }
         ).then(null, Components.utils.reportError);
         break;
       case "signIn":
       case "signUp":
+      case "refreshAuthentication":
         FxAccountsManager[data.method](data.accountId, data.password).then(
           user => {
             self._onFullfill(msg.id, user);
           },
           reason => {
             self._onReject(msg.id, reason);
           }
         ).then(null, Components.utils.reportError);
--- a/b2g/components/FxAccountsUIGlue.js
+++ b/b2g/components/FxAccountsUIGlue.js
@@ -17,17 +17,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 function FxAccountsUIGlue() {
 }
 
 FxAccountsUIGlue.prototype = {
 
   _browser: Services.wm.getMostRecentWindow("navigator:browser"),
 
-  signInFlow: function() {
+  _contentRequest: function(aEventName, aData) {
     let deferred = Promise.defer();
 
     let content = this._browser.getContentWindow();
     if (!content) {
       deferred.reject("InternalErrorNoContent");
       return;
     }
 
@@ -50,23 +50,34 @@ FxAccountsUIGlue.prototype = {
       } else {
         deferred.resolve(msg.result);
       }
       content.removeEventListener("mozFxAccountsRPContentEvent",
                                   onContentEvent);
     });
 
     let detail = {
-       eventName: "openFlow",
-       id: id
+       eventName: aEventName,
+       id: id,
+       data: aData
     };
     log.debug("Send chrome event " + JSON.stringify(detail));
     this._browser.shell.sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
 
     return deferred.promise;
   },
 
+  signInFlow: function() {
+    return this._contentRequest("openFlow");
+  },
+
+  refreshAuthentication: function(aAccountId) {
+    return this._contentRequest("refreshAuthentication", {
+      accountId: aAccountId
+    });
+  },
+
   classID: Components.ID("{51875c14-91d7-4b8c-b65d-3549e101228c}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFxAccountsUIGlue])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FxAccountsUIGlue]);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -10,18 +10,18 @@
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6e71ab4da1b08586ea0c758edb7aa199ee34cd2f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
-  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
+  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="022eadd5917615ff00c47eaaafa792b45e9c8a28"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -10,18 +10,18 @@
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6e71ab4da1b08586ea0c758edb7aa199ee34cd2f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="78b908b493bfe0b477e3d4f6edec8c46a2c0d096"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
-  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
+  <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="022eadd5917615ff00c47eaaafa792b45e9c8a28"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -14,21 +14,21 @@ let Ci = Components.interfaces;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Timer.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
-  "resource:///modules/sessionstore/FormData.jsm");
+  "resource://gre/modules/FormData.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
-  "resource:///modules/sessionstore/ScrollPosition.jsm");
+  "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 
 Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
 let gFrameTree = new FrameTree(this);
 
--- a/browser/components/sessionstore/src/ContentRestore.jsm
+++ b/browser/components/sessionstore/src/ContentRestore.jsm
@@ -9,21 +9,21 @@ this.EXPORTED_SYMBOLS = ["ContentRestore
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
   "resource:///modules/sessionstore/DocShellCapabilities.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormData",
-  "resource:///modules/sessionstore/FormData.jsm");
+  "resource://gre/modules/FormData.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
-  "resource:///modules/sessionstore/ScrollPosition.jsm");
+  "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource:///modules/sessionstore/Utils.jsm");
 
 /**
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -10,34 +10,31 @@ EXTRA_COMPONENTS += [
     'nsSessionStore.manifest',
 ]
 
 JS_MODULES_PATH = 'modules/sessionstore'
 
 EXTRA_JS_MODULES = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
-    'FormData.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
     'PageStyle.jsm',
     'PrivacyFilter.jsm',
     'PrivacyLevel.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
-    'ScrollPosition.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionHistory.jsm',
     'SessionMigration.jsm',
     'SessionStorage.jsm',
     'SessionWorker.js',
     'TabAttributes.jsm',
     'TabState.jsm',
     'TabStateCache.jsm',
     'Utils.jsm',
-    'XPathGenerator.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'SessionSaver.jsm',
     'SessionStore.jsm',
 ]
 
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -60,16 +60,19 @@ const COMPAT = {
   CATEGORY_CLASS_FRAGMENTS: [ "network", "cssparser", "exception", "console",
                               "input", "output", "security" ],
 
   // The fragment of a CSS class name that identifies each severity.
   SEVERITY_CLASS_FRAGMENTS: [ "error", "warn", "info", "log" ],
 
   // The indent of a console group in pixels.
   GROUP_INDENT: 12,
+
+  // The default indent in pixels, applied even without any groups.
+  GROUP_INDENT_DEFAULT: 6,
 };
 
 // A map from the console API call levels to the Web Console severities.
 const CONSOLE_API_LEVELS_TO_SEVERITIES = {
   error: "error",
   exception: "error",
   assert: "error",
   warn: "warning",
@@ -771,16 +774,22 @@ Messages.Simple.prototype = Heritage.ext
       return this;
     }
 
     let timestamp = new Widgets.MessageTimestamp(this, this.timestamp).render();
 
     let icon = this.document.createElementNS(XHTML_NS, "span");
     icon.className = "icon";
 
+    // Apply the current group by indenting appropriately.
+    // TODO: remove this once bug 778766 is fixed.
+    let iconMarginLeft = this._groupDepthCompat * COMPAT.GROUP_INDENT +
+                         COMPAT.GROUP_INDENT_DEFAULT;
+    icon.style.marginLeft = iconMarginLeft + "px";
+
     let body = this._renderBody();
     this._repeatID.textContent += "|" + body.textContent;
 
     let repeatNode = this._renderRepeatNode();
     let location = this._renderLocation();
 
     Messages.BaseMessage.prototype.render.call(this);
     if (this._className) {
@@ -1314,21 +1323,16 @@ Widgets.MessageTimestamp.prototype = Her
     if (this.element) {
       return this;
     }
 
     this.element = this.document.createElementNS(XHTML_NS, "span");
     this.element.className = "timestamp devtools-monospace";
     this.element.textContent = l10n.timestampString(this.timestamp) + " ";
 
-    // Apply the current group by indenting appropriately.
-    // TODO: remove this once bug 778766 is fixed.
-    this.element.style.marginRight = this.message._groupDepthCompat *
-                                     COMPAT.GROUP_INDENT + "px";
-
     return this;
   },
 }); // Widgets.MessageTimestamp.prototype
 
 
 /**
  * The JavaScript object widget.
  *
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -252,15 +252,16 @@ run-if = os == "mac"
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_split.js]
 [browser_webconsole_view_source.js]
 [browser_webconsole_reflow.js]
 [browser_webconsole_log_file_filter.js]
 [browser_webconsole_expandable_timestamps.js]
 [browser_webconsole_autocomplete_in_debugger_stackframe.js]
 [browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
+[browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js]
 [browser_webconsole_output_01.js]
 [browser_webconsole_output_02.js]
 [browser_webconsole_output_03.js]
 [browser_webconsole_output_04.js]
 [browser_webconsole_output_events.js]
 [browser_console_variables_view_highlighter.js]
 [browser_webconsole_console_trace_duplicates.js]
--- a/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
+++ b/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
@@ -86,17 +86,17 @@ function test()
           name: "console.trace output",
           consoleTrace: {
             file: "browser_console_consolejsm_output.js",
             fn: "onCachedMessage",
           },
         },
         {
           name: "console.dir output",
-          consoleDir: "XULDocument {",
+          consoleDir: /XULDocument .+ chrome:\/\/.+\/browser\.xul/,
         },
         {
           name: "console.time output",
           consoleTime: "foobarTimer",
         },
         {
           name: "console.timeEnd output",
           consoleTimeEnd: "foobarTimer",
--- a/browser/devtools/webconsole/test/browser_console_dead_objects.js
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -13,19 +13,24 @@
 // document. This is the dead object.
 
 const TEST_URI = "data:text/html;charset=utf8,<p>dead objects!";
 
 function test()
 {
   let hud = null;
 
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("devtools.chrome.enabled");
+  });
+
   Task.spawn(runner).then(finishTest);
 
   function* runner() {
+    Services.prefs.setBoolPref("devtools.chrome.enabled", true);
     let {tab} = yield loadTab(TEST_URI);
 
     info("open the browser console");
 
     hud = yield HUDService.toggleBrowserConsole();
     ok(hud, "browser console opened");
 
     hud.jsterm.clearOutput();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
@@ -0,0 +1,104 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/*
+ * Bug 922161 - hide Browser Console JS input field if devtools.chrome.enabled is false
+ * when devtools.chrome.enabled then
+ *   -browser console jsterm should be enabled
+ *   -browser console object inspector properties should be set.
+ *   -webconsole jsterm should be enabled
+ *   -webconsole object inspector properties should be set.
+ *
+ * when devtools.chrome.enabled == false then
+ *   -browser console jsterm should be disabled
+ *   -browser console object inspector properties should not be set.
+ *   -webconsole jsterm should be enabled
+ *   -webconsole object inspector properties should be set.
+ */
+
+function testObjectInspectorPropertiesAreNotSet(variablesView) {
+  is(variablesView.eval, null, "vview.eval is null");
+  is(variablesView.switch, null, "vview.switch is null");
+  is(variablesView.delete, null, "vview.delete is null");
+}
+
+function* getVariablesView(hud) {
+  function openVariablesView(event, vview) {
+    deferred.resolve(vview._variablesView);
+  }
+
+  let deferred = promise.defer();
+  hud.jsterm.clearOutput();
+  hud.jsterm.execute('new Object()');
+
+  let [message] = yield waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "object"
+    }],
+  })
+
+  hud.jsterm.once("variablesview-fetched", openVariablesView);
+
+  let anchor = [...message.matched][0].querySelector("a");
+
+  executeSoon(() =>
+    EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow)
+  );
+
+  return deferred.promise;
+}
+
+function testJSTermIsVisible(hud) {
+  let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+  isnot(inputContainer.style.display, "none", "input is visible");
+}
+
+function testObjectInspectorPropertiesAreSet(variablesView) {
+  isnot(variablesView.eval, null, "vview.eval is set");
+  isnot(variablesView.switch, null, "vview.switch is set");
+  isnot(variablesView.delete, null, "vview.delete is set");
+}
+
+function testJSTermIsNotVisible(hud) {
+  let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+  is(inputContainer.style.display, "none", "input is not visible");
+}
+
+function* testRunner() {
+  let browserConsole, webConsole, variablesView;
+
+  Services.prefs.setBoolPref("devtools.chrome.enabled", true);
+
+  browserConsole = yield HUDService.toggleBrowserConsole();
+  variablesView = yield getVariablesView(browserConsole);
+  testJSTermIsVisible(browserConsole);
+  testObjectInspectorPropertiesAreSet(variablesView);
+
+  let {tab: browserTab} = yield loadTab("data:text/html;charset=utf8,hello world");
+  webConsole = yield openConsole(browserTab);
+  variablesView = yield getVariablesView(webConsole);
+  testJSTermIsVisible(webConsole)
+  testObjectInspectorPropertiesAreSet(variablesView)
+  yield closeConsole(browserTab);
+
+  yield HUDService.toggleBrowserConsole();
+  Services.prefs.setBoolPref("devtools.chrome.enabled", false);
+
+  browserConsole = yield HUDService.toggleBrowserConsole();
+  variablesView = yield getVariablesView(browserConsole);
+  testJSTermIsNotVisible(browserConsole);
+  testObjectInspectorPropertiesAreNotSet(variablesView);
+
+  webConsole = yield openConsole(browserTab);
+  variablesView = yield getVariablesView(webConsole);
+  testJSTermIsVisible(webConsole)
+  testObjectInspectorPropertiesAreSet(variablesView)
+  yield closeConsole(browserTab);
+}
+
+function test() {
+  Task.spawn(testRunner).then(finishTest);
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
@@ -1,107 +1,79 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests that console.group/groupEnd works as intended.
-
-let testDriver, hud;
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 664131: Expand console object with group methods";
 
 function test() {
-  addTab("data:text/html;charset=utf-8,Web Console test for bug 664131: Expand console " +
-         "object with group methods");
-  browser.addEventListener("load", function onLoad(aEvent) {
-    browser.removeEventListener(aEvent.type, onLoad, true);
-    openConsole(null, function(aHud) {
-      hud = aHud;
-      testDriver = testGen();
-      testNext();
-    });
-  }, true);
-}
+  Task.spawn(runner).then(finishTest);
 
-function testNext() {
-  testDriver.next();
-}
+  function* runner() {
+    let {tab} = yield loadTab(TEST_URI);
+    let hud = yield openConsole(tab);
+    let outputNode = hud.outputNode;
 
-function testGen() {
-  outputNode = hud.outputNode;
+    hud.jsterm.clearOutput();
 
-  hud.jsterm.clearOutput();
-
-  content.console.group("bug664131a");
+    content.console.group("bug664131a");
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "bug664131a",
-      consoleGroup: 1,
-    }],
-  }).then(testNext);
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "bug664131a",
+        consoleGroup: 1,
+      }],
+    });
 
-  yield undefined;
-
-  content.console.log("bug664131a-inside");
+    content.console.log("bug664131a-inside");
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "bug664131a-inside",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-      groupDepth: 1,
-    }],
-  }).then(testNext);
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "bug664131a-inside",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+        groupDepth: 1,
+      }],
+    });
 
-  yield undefined;
+    content.console.groupEnd("bug664131a");
+    content.console.log("bug664131-outside");
 
-  content.console.groupEnd("bug664131a");
-  content.console.log("bug664131-outside");
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "bug664131-outside",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+        groupDepth: 0,
+      }],
+    });
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "bug664131-outside",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-      groupDepth: 0,
-    }],
-  }).then(testNext);
-
-  yield undefined;
-
-  content.console.groupCollapsed("bug664131b");
-
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "bug664131b",
-      consoleGroup: 1,
-    }],
-  }).then(testNext);
+    content.console.groupCollapsed("bug664131b");
 
-  yield undefined;
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "bug664131b",
+        consoleGroup: 1,
+      }],
+    });
 
-  // Test that clearing the console removes the indentation.
-  hud.jsterm.clearOutput();
-  content.console.log("bug664131-cleared");
+    // Test that clearing the console removes the indentation.
+    hud.jsterm.clearOutput();
+    content.console.log("bug664131-cleared");
 
-  waitForMessages({
-    webconsole: hud,
-    messages: [{
-      text: "bug664131-cleared",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-      groupDepth: 0,
-    }],
-  }).then(testNext);
-
-  yield undefined;
-
-  testDriver = hud = null;
-  finishTest();
-
-  yield undefined;
+    yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "bug664131-cleared",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+        groupDepth: 0,
+      }],
+    });
+  }
 }
-
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -29,16 +29,19 @@ const CATEGORY_SECURITY = 6;
 const SEVERITY_ERROR = 0;
 const SEVERITY_WARNING = 1;
 const SEVERITY_INFO = 2;
 const SEVERITY_LOG = 3;
 
 // The indent of a console group in pixels.
 const GROUP_INDENT = 12;
 
+// The default indent in pixels, applied even without any groups.
+const GROUP_INDENT_DEFAULT = 6;
+
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
@@ -1129,20 +1132,20 @@ function waitForMessages(aOptions)
     if ("repeats" in aRule) {
       let repeats = aElement.querySelector(".message-repeats");
       if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
         return false;
       }
     }
 
     if ("groupDepth" in aRule) {
-      let timestamp = aElement.querySelector(".timestamp");
-      let indent = (GROUP_INDENT * aRule.groupDepth) + "px";
-      if (!timestamp || timestamp.style.marginRight != indent) {
-        is(timestamp.style.marginRight, indent,
+      let icon = aElement.querySelector(".icon");
+      let indent = (GROUP_INDENT * aRule.groupDepth + GROUP_INDENT_DEFAULT) + "px";
+      if (!icon || icon.style.marginLeft != indent) {
+        is(icon.style.marginLeft, indent,
            "group depth check failed for message rule: " + displayRule(aRule));
         return false;
       }
     }
 
     if ("longString" in aRule) {
       let longStrings = aElement.querySelectorAll(".longStringEllipsis");
       if (aRule.longString != !!longStrings[0]) {
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -140,16 +140,19 @@ const MAX_HTTP_ERROR_CODE = 599;
 
 // Constants used for defining the direction of JSTerm input history navigation.
 const HISTORY_BACK = -1;
 const HISTORY_FORWARD = 1;
 
 // The indent of a console group in pixels.
 const GROUP_INDENT = 12;
 
+// The default indent in pixels, applied even without any groups.
+const GROUP_INDENT_DEFAULT = 6;
+
 // The number of messages to display in a single display update. If we display
 // too many messages at once we slow the Firefox UI too much.
 const MESSAGES_IN_INTERVAL = DEFAULT_LOG_LIMIT;
 
 // The delay between display updates - tells how often we should *try* to push
 // new messages to screen. This value is optimistic, updates won't always
 // happen. Keep this low so the Web Console output feels live.
 const OUTPUT_INTERVAL = 50; // milliseconds
@@ -2433,16 +2436,20 @@ WebConsoleFrame.prototype = {
     }
 
     // Make the icon container, which is a vertical box. Its purpose is to
     // ensure that the icon stays anchored at the top of the message even for
     // long multi-line messages.
     let iconContainer = this.document.createElementNS(XHTML_NS, "span");
     iconContainer.className = "icon";
 
+    // Apply the current group by indenting appropriately.
+    let iconMarginLeft = this.groupDepth * GROUP_INDENT + GROUP_INDENT_DEFAULT;
+    iconContainer.style.marginLeft = iconMarginLeft + "px";
+
     // Create the message body, which contains the actual text of the message.
     let bodyNode = this.document.createElementNS(XHTML_NS, "span");
     bodyNode.className = "body devtools-monospace";
 
     // Store the body text, since it is needed later for the variables view.
     let body = aBody;
     // If a string was supplied for the body, turn it into a DOM node and an
     // associated clipboard string now.
@@ -2490,18 +2497,16 @@ WebConsoleFrame.prototype = {
       repeatNode.textContent = 1;
       repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel,
                          aSourceURL, aSourceLine].join(":");
     }
 
     // Create the timestamp.
     let timestampNode = this.document.createElementNS(XHTML_NS, "span");
     timestampNode.className = "timestamp devtools-monospace";
-    // Apply the current group by indenting appropriately.
-    timestampNode.style.marginRight = this.groupDepth * GROUP_INDENT + "px";
 
     let timestampString = l10n.timestampString(timestamp);
     timestampNode.textContent = timestampString + " ";
 
     // Create the source location (e.g. www.example.com:6) that sits on the
     // right side of the message, if applicable.
     let locationNode;
     if (aSourceURL && IGNORED_SOURCE_URLS.indexOf(aSourceURL) == -1) {
@@ -3097,24 +3102,32 @@ JSTerm.prototype = {
       theme: "auto",
       direction: "ltr",
       autoSelect: true
     };
     this.autocompletePopup = new AutocompletePopup(this.hud.document,
                                                    autocompleteOptions);
 
     let doc = this.hud.document;
+    let inputContainer = doc.querySelector(".jsterm-input-container");
     this.completeNode = doc.querySelector(".jsterm-complete-node");
     this.inputNode = doc.querySelector(".jsterm-input-node");
-    this.inputNode.addEventListener("keypress", this._keyPress, false);
-    this.inputNode.addEventListener("input", this._inputEventHandler, false);
-    this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
-    this.inputNode.addEventListener("focus", this._focusEventHandler, false);
+
+    if (this.hud.owner._browserConsole &&
+        !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
+      inputContainer.style.display = "none";
+    }
+    else {
+      this.inputNode.addEventListener("keypress", this._keyPress, false);
+      this.inputNode.addEventListener("input", this._inputEventHandler, false);
+      this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
+      this.inputNode.addEventListener("focus", this._focusEventHandler, false);
+    }
+
     this.hud.window.addEventListener("blur", this._blurEventHandler, false);
-
     this.lastInputValue && this.setInputValue(this.lastInputValue);
   },
 
   /**
    * The JavaScript evaluation response handler.
    *
    * @private
    * @param object [aAfterMessage]
@@ -3529,17 +3542,19 @@ JSTerm.prototype = {
     view.empty();
 
     // We need to avoid pruning the object inspection starting point.
     // That one is pruned when the console message is removed.
     view.controller.releaseActors(aActor => {
       return view._consoleLastObjectActor != aActor;
     });
 
-    if (aOptions.objectActor) {
+    if (aOptions.objectActor &&
+        (!this.hud.owner._browserConsole ||
+         Services.prefs.getBoolPref("devtools.chrome.enabled"))) {
       // Make sure eval works in the correct context.
       view.eval = this._variablesViewEvaluate.bind(this, aOptions);
       view.switch = this._variablesViewSwitch.bind(this, aOptions);
       view.delete = this._variablesViewDelete.bind(this, aOptions);
     }
     else {
       view.eval = null;
       view.switch = null;
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -496,18 +496,16 @@
 @BINPATH@/components/servicesComponents.manifest
 @BINPATH@/components/cryptoComponents.manifest
 @BINPATH@/components/TelemetryStartup.js
 @BINPATH@/components/TelemetryStartup.manifest
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
-@BINPATH@/components/SettingsService.js
-@BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 @BINPATH@/components/nsDOMIdentity.js
 @BINPATH@/components/nsIDService.js
 @BINPATH@/components/Identity.manifest
 @BINPATH@/components/recording-cmdline.js
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -373,17 +373,19 @@ Section "-Application" APP_IDX
     StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
     ${If} $AddDesktopSC == 1
     ${OrIf} $AddStartMenuSC == 1
       WriteRegDWORD HKCU "$0" "IconsVisible" 1
     ${Else}
       WriteRegDWORD HKCU "$0" "IconsVisible" 0
     ${EndIf}
 !ifdef MOZ_METRO
-    ${CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID}
+    ${CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID} \
+                                        "FirefoxURL" \
+                                        "FirefoxHTML"
     ${AddMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID} \
                                     "$INSTDIR\CommandExecuteHandler.exe" \
                                     $AppUserModelID \
                                     "FirefoxURL" \
                                     "FirefoxHTML"
 !endif
   ${EndIf}
 
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -5,17 +5,19 @@
 ; The registration ID of the COM server which is used for choosing wether
 ; to launch the Win8 metro browser or desktop browser.
 !define DELEGATE_EXECUTE_HANDLER_ID {5100FEC1-212B-4BF5-9BF8-3E650FD794A3}
 
 ; Does metro registration for the command execute handler
 Function RegisterCEH
 !ifdef MOZ_METRO
   ${If} ${AtLeastWin8}
-    ${CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID}
+    ${CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID} \
+                                        "FirefoxURL" \
+                                        "FirefoxHTML"
     ${AddMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID} \
                                     "$INSTDIR\CommandExecuteHandler.exe" \
                                     $AppUserModelID \
                                     "FirefoxURL" \
                                     "FirefoxHTML"
   ${EndIf}
 !endif
 FunctionEnd
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -289,17 +289,19 @@ Section "Uninstall"
     ${un.RegCleanMain} "Software\Mozilla"
     ${un.RegCleanUninstall}
     ${un.DeleteShortcuts}
     ${un.SetAppLSPCategories}
   ${EndIf}
 
 !ifdef MOZ_METRO
   ${If} ${AtLeastWin8}
-    ${un.CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID}
+    ${un.CleanupMetroBrowserHandlerValues} ${DELEGATE_EXECUTE_HANDLER_ID} \
+                                           "FirefoxURL" \
+                                           "FirefoxHTML"
   ${EndIf}
   ${ResetWin8PromptKeys}
   ${ResetWin8MetroSplash}
 !endif
 
   ${un.RegCleanAppHandler} "FirefoxURL"
   ${un.RegCleanAppHandler} "FirefoxHTML"
   ${un.RegCleanProtocolHandler} "ftp"
--- a/browser/metro/base/content/bindings/bindings.xml
+++ b/browser/metro/base/content/bindings/bindings.xml
@@ -290,17 +290,17 @@
       </method>
     </implementation>
     <handlers>
       <handler event="click" phase="capturing">
         <![CDATA[
           if (event.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
             if (typeof SelectionHelperUI != 'undefined') {
               SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
-                  event.clientX, event.clientY, this);
+                  event.clientX, event.clientY, event.target);
             } else {
               // If we don't have access to SelectionHelperUI then we are using this
               // binding for browser content (e.g. about:config)
               Services.obs.notifyObservers(event, "attach_edit_session_to_content", "");
             }
           }
         ]]>
       </handler>
--- a/browser/metro/base/content/bindings/browser.js
+++ b/browser/metro/base/content/bindings/browser.js
@@ -3,16 +3,19 @@
  * 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/. */
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FormData.jsm");
+Cu.import("resource://gre/modules/ScrollPosition.jsm");
+Cu.import("resource://gre/modules/Timer.jsm", this);
 
 let WebProgressListener = {
   _lastLocation: null,
   _firstPaint: false,
 
   init: function() {
     let flags = Ci.nsIWebProgress.NOTIFY_LOCATION |
                 Ci.nsIWebProgress.NOTIFY_SECURITY |
@@ -436,26 +439,47 @@ let WebNavigation =  {
     return entry;
   }
 };
 
 WebNavigation.init();
 
 
 let DOMEvents =  {
+  _timeout: null,
+  _sessionEvents: new Set(),
+  _sessionEventMap: {"SessionStore:collectFormdata" : FormData.collect,
+                     "SessionStore:collectScrollPosition" : ScrollPosition.collect},
+
   init: function() {
     addEventListener("DOMContentLoaded", this, false);
     addEventListener("DOMTitleChanged", this, false);
     addEventListener("DOMLinkAdded", this, false);
     addEventListener("DOMWillOpenModalDialog", this, false);
     addEventListener("DOMModalDialogClosed", this, true);
     addEventListener("DOMWindowClose", this, false);
     addEventListener("DOMPopupBlocked", this, false);
     addEventListener("pageshow", this, false);
     addEventListener("pagehide", this, false);
+
+    addEventListener("input", this, true);
+    addEventListener("change", this, true);
+    addEventListener("scroll", this, true);
+    addMessageListener("SessionStore:restoreSessionTabData", this);
+  },
+
+  receiveMessage: function(message) {
+    switch (message.name) {
+      case "SessionStore:restoreSessionTabData":
+        if (message.json.formdata)
+          FormData.restore(content, message.json.formdata);
+        if (message.json.scroll)
+          ScrollPosition.restore(content, message.json.scroll.scroll);
+        break;
+    }
   },
 
   handleEvent: function(aEvent) {
     let document = content.document;
     switch (aEvent.type) {
       case "DOMContentLoaded":
         if (document.documentURIObject.spec == "about:blank")
           return;
@@ -542,16 +566,41 @@ let DOMEvents =  {
         let retvals = sendSyncMessage(aEvent.type, { });
         for (let i in retvals) {
           if (retvals[i].preventDefault) {
             aEvent.preventDefault();
             break;
           }
         }
         break;
+      case "input":
+      case "change":
+        this._sessionEvents.add("SessionStore:collectFormdata");
+        this._sendUpdates();
+        break;
+      case "scroll":
+        this._sessionEvents.add("SessionStore:collectScrollPosition");
+        this._sendUpdates();
+        break;
+    }
+  },
+
+  _sendUpdates: function() {
+    if (!this._timeout) {
+      // Wait a little before sending the message to batch multiple changes.
+      this._timeout = setTimeout(function() {
+        for (let eventType of this._sessionEvents) {
+          sendAsyncMessage(eventType, {
+            data: this._sessionEventMap[eventType](content)
+          });
+        }
+        this._sessionEvents.clear();
+        clearTimeout(this._timeout);
+        this._timeout = null;
+      }.bind(this), 1000);
     }
   }
 };
 
 DOMEvents.init();
 
 let ContentScroll =  {
   // The most recent offset set by APZC for the root scroll frame
--- a/browser/metro/base/content/bindings/urlbar.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -288,17 +288,17 @@
               this.focus();
 
             this._clearFormatting();
             this.select();
 
             if (aShouldDismiss)
               ContextUI.dismissTabs();
 
-            if (!InputSourceHelper.isPrecise && this.textLength) {
+            if (!InputSourceHelper.isPrecise) {
               let inputRectangle = this.inputField.getBoundingClientRect();
               SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
                   inputRectangle.left, inputRectangle.top, this);
             }
           ]]>
         </body>
       </method>
 
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -1453,17 +1453,17 @@ Tab.prototype = {
     // the input overlay we use to shade content from input events when
     // we're intercepting touch input.
     let notification = this._notification = document.createElement("notificationbox");
 
     let browser = this._browser = document.createElement("browser");
     browser.id = "browser-" + this._id;
     this._chromeTab.linkedBrowser = browser;
 
-    browser.setAttribute("type", "content");
+    browser.setAttribute("type", "content-targetable");
 
     let useRemote = Services.appinfo.browserTabsRemote;
     let useLocal = Util.isLocalScheme(aURI);
     browser.setAttribute("remote", (!useLocal && useRemote) ? "true" : "false");
 
     // Append the browser to the document, which should start the page load
     let stack = document.createElementNS(XUL_NS, "stack");
     stack.className = "browserStack";
@@ -1527,17 +1527,17 @@ Tab.prototype = {
       browser.setAttribute("type", "content-primary");
       Elements.browsers.selectedPanel = notification;
       browser.active = true;
       Elements.tabList.selectedTab = this._chromeTab;
       browser.focus();
     } else {
       notification.classList.remove("active-tab-notificationbox");
       browser.messageManager.sendAsyncMessage("Browser:Blur", { });
-      browser.setAttribute("type", "content");
+      browser.setAttribute("type", "content-targetable");
       browser.active = false;
     }
   },
 
   get active() {
     if (!this._browser)
       return false;
     return this._browser.getAttribute("type") == "content-primary";
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -1,16 +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/. */
 
 /*
  * Selection handler for chrome text inputs
  */
 
+let Ci = Components.interfaces;
+
 const kCaretMode = 1;
 const kSelectionMode = 2;
 
 var ChromeSelectionHandler = {
   _mode: kSelectionMode,
 
   /*************************************************
    * Messaging wrapper
@@ -29,28 +31,34 @@ var ChromeSelectionHandler = {
 
   /*
    * General selection start method for both caret and selection mode.
    */
   _onSelectionAttach: function _onSelectionAttach(aJson) {
     this._domWinUtils = Util.getWindowUtils(window);
     this._contentWindow = window;
     this._targetElement = aJson.target;
-    this._targetIsEditable = this._targetElement instanceof Components.interfaces.nsIDOMXULTextBoxElement;
+    this._targetIsEditable = Util.isTextInput(this._targetElement) ||
+        this._targetElement instanceof Ci.nsIDOMXULTextBoxElement;
     if (!this._targetIsEditable) {
       this._onFail("not an editable?", this._targetElement);
       return;
     }
 
     let selection = this._getSelection();
     if (!selection) {
       this._onFail("no selection.");
       return;
     }
 
+    if (!this._getTargetElementValue()) {
+      this._onFail("Target element does not contain any content to select.");
+      return;
+    }
+
     if (!selection.isCollapsed) {
       this._mode = kSelectionMode;
       this._updateSelectionUI("start", true, true);
     } else {
       this._mode = kCaretMode;
       this._updateSelectionUI("caret", false, false, true);
     }
 
@@ -375,26 +383,43 @@ var ChromeSelectionHandler = {
     }
   },
 
   /*************************************************
    * Utilities
    */
 
   _getSelection: function _getSelection() {
+    let targetElementEditor = this._getTargetElementEditor();
+
+    return targetElementEditor ? targetElementEditor.selection : null;
+  },
+
+  _getTargetElementValue: function _getTargetElementValue() {
     if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
-      return this._targetElement
-                 .QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
-                 .editor.selection;
+      return this._targetElement.inputField.value;
+    } else if (Util.isTextInput(this._targetElement)) {
+      return this._targetElement.value;
     }
     return null;
   },
 
   _getSelectController: function _getSelectController() {
-    return this._targetElement
-                .QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
-                .editor.selectionController;
+    let targetElementEditor = this._getTargetElementEditor();
+
+    return targetElementEditor ? targetElementEditor.selectionController : null;
   },
+
+  _getTargetElementEditor: function() {
+    if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
+      return this._targetElement.QueryInterface(Ci.nsIDOMXULTextBoxElement)
+          .editor;
+    } else if (Util.isTextInput(this._targetElement)) {
+      return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement)
+          .editor;
+    }
+    return null;
+  }
 };
 
 ChromeSelectionHandler.__proto__ = new SelectionPrototype();
 ChromeSelectionHandler.type = 1; // kChromeSelector
 
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -245,15 +245,48 @@ gTests.push({
     sendTap(window, editRectangle.left + 50, editRectangle.top - 2);
 
     yield waitForCondition(function () {
       return SelectionHelperUI.isSelectionUIVisible;
     });
   }
 });
 
+gTests.push({
+  desc: "Bug 972428 - grippers not appearing under the URL field when adding " +
+        "text.",
+  run: function() {
+    let inputField = document.getElementById("urlbar-edit").inputField;
+    let inputFieldRectangle = inputField.getBoundingClientRect();
+
+    let chromeHandlerSpy = spyOnMethod(ChromeSelectionHandler, "msgHandler");
+
+    // Reset URL to empty string
+    inputField.value = "";
+    inputField.blur();
+
+    // Activate URL input
+    sendTap(window, inputFieldRectangle.left + 50, inputFieldRectangle.top + 5);
+
+    // Wait until ChromeSelectionHandler tries to attach selection
+    yield waitForCondition(() => chromeHandlerSpy.argsForCall.some(
+        (args) => args[0] == "Browser:SelectionAttach"));
+
+    ok(!SelectHelperUI.isSelectionUIVisible && !SelectHelperUI.isCaretUIVisible,
+        "Neither CaretUI nor SelectionUI is visible on empty input.");
+
+    inputField.value = "Test text";
+
+    sendTap(window, inputFieldRectangle.left + 10, inputFieldRectangle.top + 5);
+
+    yield waitForCondition(() => SelectionHelperUI.isCaretUIVisible);
+
+    chromeHandlerSpy.restore();
+  }
+});
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   runTests();
 }
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -1028,21 +1028,24 @@ function runTests() {
   });
 }
 
 // wrap a method with a spy that records how and how many times it gets called
 // the spy is returned; use spy.restore() to put the original back
 function spyOnMethod(aObj, aMethod) {
   let origFunc = aObj[aMethod];
   let spy = function() {
-    spy.calledWith = Array.slice(arguments);
+    let callArguments = Array.slice(arguments);
     spy.callCount++;
+    spy.calledWith = callArguments;
+    spy.argsForCall.push(callArguments);
     return (spy.returnValue = origFunc.apply(aObj, arguments));
   };
   spy.callCount = 0;
+  spy.argsForCall = [];
   spy.restore = function() {
     return (aObj[aMethod] = origFunc);
   };
   return (aObj[aMethod] = spy);
 }
 
 // replace a method with a stub that records how and how many times it gets called
 // the stub is returned; use stub.restore() to put the original back
--- a/browser/metro/components/SessionStore.js
+++ b/browser/metro/components/SessionStore.js
@@ -350,18 +350,29 @@ SessionStore.prototype = {
       if (this._currTabCount > this._maxTabsOpen) {
         this._maxTabsOpen = this._currTabCount;
       }
   },
 
   handleEvent: function ss_handleEvent(aEvent) {
     let window = aEvent.currentTarget.ownerDocument.defaultView;
     switch (aEvent.type) {
+      case "load":
+        browser = aEvent.currentTarget;
+        if (aEvent.target == browser.contentDocument && browser.__SS_tabFormData) {
+          browser.messageManager.sendAsyncMessage("SessionStore:restoreSessionTabData", {
+            formdata: browser.__SS_tabFormData.formdata,
+            scroll: browser.__SS_tabFormData.scroll
+          });
+        }
+        break;
       case "TabOpen":
         this.updateTabTelemetryVars(window);
+        let browser = aEvent.originalTarget.linkedBrowser;
+        browser.addEventListener("load", this, true);
       case "TabClose": {
         let browser = aEvent.originalTarget.linkedBrowser;
         if (aEvent.type == "TabOpen") {
           this.onTabAdd(window, browser);
         }
         else {
           this.onTabClose(window, browser);
           this.onTabRemove(window, browser);
@@ -375,18 +386,29 @@ SessionStore.prototype = {
         let browser = aEvent.originalTarget.linkedBrowser;
         this.onTabSelect(window, browser);
         break;
       }
     }
   },
 
   receiveMessage: function ss_receiveMessage(aMessage) {
-    let window = aMessage.target.ownerDocument.defaultView;
-    this.onTabLoad(window, aMessage.target, aMessage);
+    let browser = aMessage.target;
+    switch (aMessage.name) {
+      case "SessionStore:collectFormdata":
+        browser.__SS_data.formdata = aMessage.json.data;
+        break;
+      case "SessionStore:collectScrollPosition":
+        browser.__SS_data.scroll = aMessage.json.data;
+        break;
+      default:
+        let window = aMessage.target.ownerDocument.defaultView;
+        this.onTabLoad(window, aMessage.target, aMessage);
+        break;
+    }
   },
 
   onWindowOpen: function ss_onWindowOpen(aWindow) {
     // Return if window has already been initialized
     if (aWindow && aWindow.__SSID && this._windows[aWindow.__SSID])
       return;
 
     // Ignore non-browser windows and windows opened while shutting down
@@ -450,25 +472,29 @@ SessionStore.prototype = {
       this.onTabRemove(aWindow, tabs[i].browser, true);
 
     delete aWindow.__SSID;
   },
 
   onTabAdd: function ss_onTabAdd(aWindow, aBrowser, aNoNotification) {
     aBrowser.messageManager.addMessageListener("pageshow", this);
     aBrowser.messageManager.addMessageListener("Content:SessionHistory", this);
+    aBrowser.messageManager.addMessageListener("SessionStore:collectFormdata", this);
+    aBrowser.messageManager.addMessageListener("SessionStore:collectScrollPosition", this);
 
     if (!aNoNotification)
       this.saveStateDelayed();
     this._updateCrashReportURL(aWindow);
   },
 
   onTabRemove: function ss_onTabRemove(aWindow, aBrowser, aNoNotification) {
     aBrowser.messageManager.removeMessageListener("pageshow", this);
     aBrowser.messageManager.removeMessageListener("Content:SessionHistory", this);
+    aBrowser.messageManager.removeMessageListener("SessionStore:collectFormdata", this);
+    aBrowser.messageManager.removeMessageListener("SessionStore:collectScrollPosition", this);
 
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
     delete aBrowser.__SS_data;
 
     if (!aNoNotification)
@@ -907,16 +933,17 @@ SessionStore.prototype = {
             // Make sure the browser has its session data for the delay reload
             tab.browser.__SS_data = tabData;
             tab.browser.__SS_restore = true;
 
             // Restore current title
             tab.chromeTab.updateTitle(tabData.entries[tabData.index - 1].title);
           }
 
+          tab.browser.__SS_tabFormData = tabData
           tab.browser.__SS_extdata = tabData.extData;
         }
 
         notifyObservers();
       }.bind(this));
     } catch (ex) {
       Cu.reportError("SessionStore: Could not read from sessionstore.bak file: " + ex);
       notifyObservers("fail");
--- a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
+++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
@@ -21,17 +21,17 @@
 #include <io.h>
 #include <shellapi.h>
 
 #ifdef SHOW_CONSOLE
 #define DEBUG_DELAY_SHUTDOWN 1
 #endif
 
 // Heartbeat timer duration used while waiting for an incoming request.
-#define HEARTBEAT_MSEC 1000
+#define HEARTBEAT_MSEC 250
 // Total number of heartbeats we wait before giving up and shutting down.
 #define REQUEST_WAIT_TIMEOUT 30
 // Pulled from desktop browser's shell
 #define APP_REG_NAME L"Firefox"
 
 const WCHAR* kFirefoxExe = L"firefox.exe";
 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
 static const WCHAR* kMetroRestartCmdLine = L"--metro-restart";
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -189,18 +189,18 @@ toolbaritem[cui-areatype="menu-panel"][s
 .panel-customization-placeholder-child {
   -moz-appearance: none;
   -moz-box-orient: vertical;
   width: calc(@menuPanelButtonWidth@);
   height: calc(40px + 4em);
 }
 
 /* Help SDK buttons fit in. */
-toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
-#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
+toolbarpaletteitem[place="palette"] > toolbarbutton[sdk-button="true"] > .toolbarbutton-icon,
+toolbarbutton[sdk-button="true"][cui-areatype="menu-panel"] > .toolbarbutton-icon {
   height: 32px;
   width: 32px;
 }
 
 .customization-palette .toolbarbutton-1 {
   -moz-appearance: none;
   -moz-box-orient: vertical;
 }
--- a/build/docs/androideclipse.rst
+++ b/build/docs/androideclipse.rst
@@ -76,14 +76,15 @@ itself.
 In future, we'd like to expand this documentation to include some of
 the technical details of how the Eclipse integration works, and how to
 add additional Android Eclipse projects using the ``moz.build``
 system.
 
 Tested Versions
 ===============
 
-============    ====================================    =================
-OS              Version                                 Working as of
-============    ====================================    =================
-Mac OS X        Luna (Build id: 20130919-0819)          February 2014
-Mac OS X        Kepler (Build id: 20131219-0014)        February 2014
-============    ====================================    =================
+===============    ====================================    =================
+OS                 Version                                 Working as of
+===============    ====================================    =================
+Mac OS X           Luna (Build id: 20130919-0819)          February 2014
+Mac OS X           Kepler (Build id: 20131219-0014)        February 2014
+Mac OS X 10.8.5    Kepler (Build id: 20130919-0819)        February 2014
+===============    ====================================    =================
--- a/content/media/MediaRecorder.cpp
+++ b/content/media/MediaRecorder.cpp
@@ -230,25 +230,29 @@ public:
 
     mStopIssued = true;
     CleanupStreams();
     nsContentUtils::UnregisterShutdownObserver(this);
   }
 
   nsresult Pause()
   {
-    NS_ENSURE_TRUE(NS_IsMainThread() && mTrackUnionStream, NS_ERROR_FAILURE);
+    MOZ_ASSERT(NS_IsMainThread());
+
+    NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
     mTrackUnionStream->ChangeExplicitBlockerCount(-1);
 
     return NS_OK;
   }
 
   nsresult Resume()
   {
-    NS_ENSURE_TRUE(NS_IsMainThread() && mTrackUnionStream, NS_ERROR_FAILURE);
+    MOZ_ASSERT(NS_IsMainThread());
+
+    NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
     mTrackUnionStream->ChangeExplicitBlockerCount(1);
 
     return NS_OK;
   }
 
   already_AddRefed<nsIDOMBlob> GetEncodedData()
   {
     return mEncodedBufferCache->ExtractBlob(mMimeType);
--- a/content/media/encoder/EncodedFrameContainer.h
+++ b/content/media/encoder/EncodedFrameContainer.h
@@ -40,38 +40,38 @@ private:
 // Represent one encoded frame
 class EncodedFrame
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EncodedFrame)
 public:
   EncodedFrame() :
     mTimeStamp(0),
     mDuration(0),
-    mFrameType(UNKNOW)
+    mFrameType(UNKNOWN)
   {}
   enum FrameType {
     I_FRAME,      // intraframe
     P_FRAME,      // predicted frame
     B_FRAME,      // bidirectionally predicted frame
     AUDIO_FRAME,  // audio frame
     AAC_CSD,      // AAC codec specific data
     AVC_CSD,      // AVC codec specific data
-    UNKNOW        // FrameType not set
+    UNKNOWN       // FrameType not set
   };
   nsresult SwapInFrameData(nsTArray<uint8_t>& aData)
   {
     mFrameData.SwapElements(aData);
     return NS_OK;
   }
   nsresult SwapOutFrameData(nsTArray<uint8_t>& aData)
   {
-    if (mFrameType != UNKNOW) {
-      // Reset this frame type to UNKNOW once the data is swapped out.
+    if (mFrameType != UNKNOWN) {
+      // Reset this frame type to UNKNOWN once the data is swapped out.
       mFrameData.SwapElements(aData);
-      mFrameType = UNKNOW;
+      mFrameType = UNKNOWN;
       return NS_OK;
     }
     return NS_ERROR_FAILURE;
   }
   const nsTArray<uint8_t>& GetFrameData() const
   {
     return mFrameData;
   }
--- a/content/media/encoder/fmp4_muxer/AVCBox.cpp
+++ b/content/media/encoder/fmp4_muxer/AVCBox.cpp
@@ -6,86 +6,53 @@
 #include <climits>
 #include "ISOControl.h"
 #include "ISOMediaBoxes.h"
 #include "AVCBox.h"
 
 namespace mozilla {
 
 nsresult
-VisualSampleEntry::Generate(uint32_t* aBoxSize)
+AVCSampleEntry::Generate(uint32_t* aBoxSize)
 {
-  // both fields occupy 16 bits defined in 14496-2 6.2.3.
-  width = mMeta.mVidMeta->Width;
-  height = mMeta.mVidMeta->Height;
-
   uint32_t avc_box_size = 0;
   nsresult rv;
   rv = avcConfigBox->Generate(&avc_box_size);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  size += avc_box_size +
-          sizeof(reserved) +
-          sizeof(width) +
-          sizeof(height) +
-          sizeof(horizresolution) +
-          sizeof(vertresolution) +
-          sizeof(reserved2) +
-          sizeof(frame_count) +
-          sizeof(compressorName) +
-          sizeof(depth) +
-          sizeof(pre_defined);
+  size += avc_box_size;
 
   *aBoxSize = size;
 
   return NS_OK;
 }
 
 nsresult
-VisualSampleEntry::Write()
+AVCSampleEntry::Write()
 {
   BoxSizeChecker checker(mControl, size);
-  SampleEntryBox::Write();
-
-  mControl->Write(reserved, sizeof(reserved));
-  mControl->Write(width);
-  mControl->Write(height);
-  mControl->Write(horizresolution);
-  mControl->Write(vertresolution);
-  mControl->Write(reserved2);
-  mControl->Write(frame_count);
-  mControl->Write(compressorName, sizeof(compressorName));
-  mControl->Write(depth);
-  mControl->Write(pre_defined);
-  nsresult rv = avcConfigBox->Write();
+  nsresult rv;
+  rv = VisualSampleEntry::Write();
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = avcConfigBox->Write();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-VisualSampleEntry::VisualSampleEntry(ISOControl* aControl)
-  : SampleEntryBox(NS_LITERAL_CSTRING("avc1"), Video_Track, aControl)
-  , width(0)
-  , height(0)
-  , horizresolution(resolution_72_dpi)
-  , vertresolution(resolution_72_dpi)
-  , reserved2(0)
-  , frame_count(1)
-  , depth(video_depth)
-  , pre_defined(-1)
+AVCSampleEntry::AVCSampleEntry(ISOControl* aControl)
+  : VisualSampleEntry(NS_LITERAL_CSTRING("avc1"), aControl)
 {
-  memset(reserved, 0 , sizeof(reserved));
-  memset(compressorName, 0 , sizeof(compressorName));
   avcConfigBox = new AVCConfigurationBox(aControl);
-  MOZ_COUNT_CTOR(VisualSampleEntry);
+  MOZ_COUNT_CTOR(AVCSampleEntry);
 }
 
-VisualSampleEntry::~VisualSampleEntry()
+AVCSampleEntry::~AVCSampleEntry()
 {
-  MOZ_COUNT_DTOR(VisualSampleEntry);
+  MOZ_COUNT_DTOR(AVCSampleEntry);
 }
 
 AVCConfigurationBox::AVCConfigurationBox(ISOControl* aControl)
   : Box(NS_LITERAL_CSTRING("avcC"), aControl)
 {
   MOZ_COUNT_CTOR(AVCConfigurationBox);
 }
 
--- a/content/media/encoder/fmp4_muxer/AVCBox.h
+++ b/content/media/encoder/fmp4_muxer/AVCBox.h
@@ -36,38 +36,25 @@ public:
 
   // AVCConfigurationBox methods
   AVCConfigurationBox(ISOControl* aControl);
   ~AVCConfigurationBox();
 };
 
 // 14496-15 5.3.4.1 'Sample description name and format'
 // Box type: 'avc1'
-class VisualSampleEntry : public SampleEntryBox {
+class AVCSampleEntry : public VisualSampleEntry {
 public:
   // ISO BMFF members
-  uint8_t reserved[16];
-  uint16_t width;
-  uint16_t height;
-
-  uint32_t horizresolution; // 72 dpi
-  uint32_t vertresolution;  // 72 dpi
-  uint32_t reserved2;
-  uint16_t frame_count;     // 1, defined in 14496-12 8.5.2.2
-
-  uint8_t compressorName[32];
-  uint16_t depth;       // 0x0018, defined in 14496-12 8.5.2.2;
-  uint16_t pre_defined; // -1, defined in 14496-12 8.5.2.2;
-
   nsRefPtr<AVCConfigurationBox> avcConfigBox;
 
   // MuxerOperation methods
   nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
   nsresult Write() MOZ_OVERRIDE;
 
   // VisualSampleEntry methods
-  VisualSampleEntry(ISOControl* aControl);
-  ~VisualSampleEntry();
+  AVCSampleEntry(ISOControl* aControl);
+  ~AVCSampleEntry();
 };
 
 }
 
 #endif // AVCBox_h_
--- a/content/media/encoder/fmp4_muxer/ISOControl.cpp
+++ b/content/media/encoder/fmp4_muxer/ISOControl.cpp
@@ -126,18 +126,19 @@ FragmentBuffer::GetFirstFragmentSampleSi
   uint32_t size = 0;
   uint32_t len = mFragArray.ElementAt(0).Length();
   for (uint32_t i = 0; i < len; i++) {
     size += mFragArray.ElementAt(0).ElementAt(i)->GetFrameData().Length();
   }
   return size;
 }
 
-ISOControl::ISOControl()
-  : mAudioFragmentBuffer(nullptr)
+ISOControl::ISOControl(uint32_t aMuxingType)
+  : mMuxingType(aMuxingType)
+  , mAudioFragmentBuffer(nullptr)
   , mVideoFragmentBuffer(nullptr)
   , mFragNum(0)
   , mOutputSize(0)
   , mBitCount(0)
   , mBit(0)
 {
   // Create a data array for first mp4 Box, ftyp.
   mOutBuffers.SetLength(1);
--- a/content/media/encoder/fmp4_muxer/ISOControl.h
+++ b/content/media/encoder/fmp4_muxer/ISOControl.h
@@ -131,17 +131,17 @@ private:
  *    writing methods for different kind of data; they are Write, WriteArray,
  *    WriteBits...etc.
  */
 class ISOControl {
 
 friend class Box;
 
 public:
-  ISOControl();
+  ISOControl(uint32_t aMuxingType);
   ~ISOControl();
 
   nsresult GenerateFtyp();
   nsresult GenerateMoov();
   nsresult GenerateMoof(uint32_t aTrackType);
 
   // Swap elementary stream pointer to output buffers.
   uint32_t WriteAVData(nsTArray<uint8_t>& aArray);
@@ -183,33 +183,39 @@ public:
   uint32_t GetTime();
 
   // current fragment number
   uint32_t GetCurFragmentNumber() { return mFragNum; }
 
   nsresult SetFragment(FragmentBuffer* aFragment);
   FragmentBuffer* GetFragment(uint32_t aType);
 
+  uint32_t GetMuxingType() { return mMuxingType; }
+
   nsresult SetMetadata(TrackMetadataBase* aTrackMeta);
   nsresult GetAudioMetadata(nsRefPtr<AACTrackMetadata>& aAudMeta);
   nsresult GetVideoMetadata(nsRefPtr<AVCTrackMetadata>& aVidMeta);
 
   // Track ID is the Metadata index in mMetaArray.
   uint32_t GetTrackID(uint32_t aTrackType);
   uint32_t GetNextTrackID();
 
   bool HasAudioTrack();
   bool HasVideoTrack();
 
 private:
   uint32_t GetBufPos();
   nsresult FlushBuf();
 
+  // One of value in TYPE_XXX, defined in ISOMediaWriter.
+  uint32_t mMuxingType;
+
   // Audio and video fragments are owned by ISOMediaWriter.
-  // They don't need to worry about pointer going stale.
+  // They don't need to worry about pointer going stale because ISOMediaWriter's
+  // lifetime is longer than ISOControl.
   FragmentBuffer* mAudioFragmentBuffer;
   FragmentBuffer* mVideoFragmentBuffer;
 
   // Generated fragment number
   uint32_t mFragNum;
 
   // The (index + 1) will be the track ID.
   nsTArray<nsRefPtr<TrackMetadataBase>> mMetaArray;
--- a/content/media/encoder/fmp4_muxer/ISOMediaBoxes.cpp
+++ b/content/media/encoder/fmp4_muxer/ISOMediaBoxes.cpp
@@ -2,30 +2,32 @@
 /* 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/. */
 
 #include <climits>
 #include "TrackMetadataBase.h"
 #include "ISOMediaBoxes.h"
 #include "ISOControl.h"
+#include "ISOMediaWriter.h"
 #include "EncodedFrameContainer.h"
 #include "ISOTrackMetadata.h"
 #include "MP4ESDS.h"
 #include "AVCBox.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
 // 14496-12 6.2.2 'Data Types and fields'
 const uint32_t iso_matrix[] = { 0x00010000, 0,          0,
                                 0,          0x00010000, 0,
                                 0,          0,          0x40000000 };
 
-uint32_t set_sample_flags(bool aSync)
+uint32_t
+set_sample_flags(bool aSync)
 {
   std::bitset<32> flags;
   flags.set(16, !aSync);
   return flags.to_ulong();
 }
 
 Box::BoxSizeChecker::BoxSizeChecker(ISOControl* aControl, uint32_t aSize)
 {
@@ -635,17 +637,17 @@ SampleDescriptionBox::SampleDescriptionB
 
   switch (mTrackType) {
   case Audio_Track:
     {
       sample_entry_box = new MP4AudioSampleEntry(aControl);
     } break;
   case Video_Track:
     {
-      sample_entry_box = new VisualSampleEntry(aControl);
+      sample_entry_box = new AVCSampleEntry(aControl);
     } break;
   }
   MOZ_COUNT_CTOR(SampleDescriptionBox);
 }
 
 SampleDescriptionBox::~SampleDescriptionBox()
 {
   MOZ_COUNT_DTOR(SampleDescriptionBox);
@@ -1185,24 +1187,38 @@ TrackHeaderBox::Write()
   mControl->Write(height);
 
   return NS_OK;
 }
 
 nsresult
 FileTypeBox::Generate(uint32_t* aBoxSize)
 {
-  if (!mControl->HasVideoTrack() && mControl->HasAudioTrack()) {
-    major_brand = "M4A ";
+  minor_version = 0;
+
+  if (mControl->GetMuxingType() == ISOMediaWriter::TYPE_FRAG_MP4) {
+    if (!mControl->HasVideoTrack() && mControl->HasAudioTrack()) {
+      major_brand = "M4A ";
+    } else {
+      major_brand = "MP42";
+    }
+    compatible_brands.AppendElement("mp42");
+    compatible_brands.AppendElement("isom");
+  } else if (mControl->GetMuxingType() == ISOMediaWriter::TYPE_FRAG_3GP) {
+    major_brand = "3gp9";
+    // According to 3GPP TS 26.244 V12.2.0, section 5.3.4, it's recommended to
+    // list all compatible brands here. 3GP spec supports fragment from '3gp6'.
+    compatible_brands.AppendElement("3gp9");
+    compatible_brands.AppendElement("3gp8");
+    compatible_brands.AppendElement("3gp7");
+    compatible_brands.AppendElement("3gp6");
+    compatible_brands.AppendElement("isom");
   } else {
-    major_brand = "MP42";
+    MOZ_ASSERT(0);
   }
-  minor_version = 0;
-  compatible_brands.AppendElement("isom");
-  compatible_brands.AppendElement("mp42");
 
   size += major_brand.Length() +
           sizeof(minor_version) +
           compatible_brands.Length() * 4;
 
   *aBoxSize = size;
 
   return NS_OK;
@@ -1362,21 +1378,19 @@ TrackBox::TrackBox(uint32_t aTrackType, 
   MOZ_COUNT_CTOR(TrackBox);
 }
 
 TrackBox::~TrackBox()
 {
   MOZ_COUNT_DTOR(TrackBox);
 }
 
-SampleEntryBox::SampleEntryBox(const nsACString& aFormat, uint32_t aTrackType,
-                               ISOControl* aControl)
+SampleEntryBox::SampleEntryBox(const nsACString& aFormat, ISOControl* aControl)
   : Box(aFormat, aControl)
   , data_reference_index(0)
-  , mTrackType(aTrackType)
 {
   data_reference_index = 1; // There is only one data reference in each track.
   size += sizeof(reserved) +
           sizeof(data_reference_index);
   mMeta.Init(aControl);
   memset(reserved, 0, sizeof(reserved));
 }
 
@@ -1384,9 +1398,109 @@ nsresult
 SampleEntryBox::Write()
 {
   Box::Write();
   mControl->Write(reserved, sizeof(reserved));
   mControl->Write(data_reference_index);
   return NS_OK;
 }
 
+nsresult
+AudioSampleEntry::Write()
+{
+  SampleEntryBox::Write();
+  mControl->Write(sound_version);
+  mControl->Write(reserved2, sizeof(reserved2));
+  mControl->Write(channels);
+  mControl->Write(sample_size);
+  mControl->Write(compressionId);
+  mControl->Write(packet_size);
+  mControl->Write(timeScale);
+  return NS_OK;
 }
+
+AudioSampleEntry::AudioSampleEntry(const nsACString& aFormat, ISOControl* aControl)
+  : SampleEntryBox(aFormat, aControl)
+  , sound_version(0)
+  , channels(2)
+  , sample_size(16)
+  , compressionId(0)
+  , packet_size(0)
+  , timeScale(0)
+{
+  mMeta.Init(mControl);
+  memset(reserved2, 0 , sizeof(reserved2));
+  channels = mMeta.mAudMeta->Channels;
+  timeScale = mMeta.mAudMeta->SampleRate << 16;
+
+  size += sizeof(sound_version) +
+          sizeof(reserved2) +
+          sizeof(sample_size) +
+          sizeof(channels) +
+          sizeof(packet_size) +
+          sizeof(compressionId) +
+          sizeof(timeScale);
+
+  MOZ_COUNT_CTOR(AudioSampleEntry);
+}
+
+AudioSampleEntry::~AudioSampleEntry()
+{
+  MOZ_COUNT_DTOR(AudioSampleEntry);
+}
+
+nsresult
+VisualSampleEntry::Write()
+{
+  SampleEntryBox::Write();
+
+  mControl->Write(reserved, sizeof(reserved));
+  mControl->Write(width);
+  mControl->Write(height);
+  mControl->Write(horizresolution);
+  mControl->Write(vertresolution);
+  mControl->Write(reserved2);
+  mControl->Write(frame_count);
+  mControl->Write(compressorName, sizeof(compressorName));
+  mControl->Write(depth);
+  mControl->Write(pre_defined);
+
+  return NS_OK;
+}
+
+VisualSampleEntry::VisualSampleEntry(const nsACString& aFormat, ISOControl* aControl)
+  : SampleEntryBox(aFormat, aControl)
+  , width(0)
+  , height(0)
+  , horizresolution(resolution_72_dpi)
+  , vertresolution(resolution_72_dpi)
+  , reserved2(0)
+  , frame_count(1)
+  , depth(video_depth)
+  , pre_defined(-1)
+{
+  memset(reserved, 0 , sizeof(reserved));
+  memset(compressorName, 0 , sizeof(compressorName));
+
+  // both fields occupy 16 bits defined in 14496-2 6.2.3.
+  width = mMeta.mVidMeta->Width;
+  height = mMeta.mVidMeta->Height;
+
+  size += sizeof(reserved) +
+          sizeof(width) +
+          sizeof(height) +
+          sizeof(horizresolution) +
+          sizeof(vertresolution) +
+          sizeof(reserved2) +
+          sizeof(frame_count) +
+          sizeof(compressorName) +
+          sizeof(depth) +
+          sizeof(pre_defined);
+
+  MOZ_COUNT_CTOR(VisualSampleEntry);
+}
+
+VisualSampleEntry::~VisualSampleEntry()
+{
+  MOZ_COUNT_DTOR(VisualSampleEntry);
+}
+
+}
--- a/content/media/encoder/fmp4_muxer/ISOMediaBoxes.h
+++ b/content/media/encoder/fmp4_muxer/ISOMediaBoxes.h
@@ -491,37 +491,48 @@ public:
   ~TimeToSampleBox();
 
 protected:
   uint32_t mTrackType;
 };
 
 /**
  * 14496-12 8.5.2 'Sample Description Box'
- * This is the base class for VisualSampleEntry and MP4AudioSampleEntry.
+ * This is the base class for VisualSampleEntry and AudioSampleEntry.
  *
  * This class is for inherited only, it shouldn't be instanced directly.
+ *
+ * The inhertied tree of a codec box should be:
+ *
+ *                                            +--> AVCSampleEntry
+ *                  +--> VisualSampleEntryBox +
+ *                  |                         +--> ...
+ *   SampleEntryBox +
+ *                  |                         +--> MP4AudioSampleEntry
+ *                  +--> AudioSampleEntryBox  +
+ *                                            +--> AMRSampleEntry
+ *                                            +
+ *                                            +--> ...
+ *
  */
 class SampleEntryBox : public Box {
 public:
   // ISO BMFF members
   uint8_t reserved[6];
   uint16_t data_reference_index;
 
-  // SampleEntryBox methods
-  SampleEntryBox(const nsACString& aFormat, uint32_t aTrackType,
-                 ISOControl* aControl);
+  // sampleentrybox methods
+  SampleEntryBox(const nsACString& aFormat, ISOControl* aControl);
 
   // MuxerOperation methods
   nsresult Write() MOZ_OVERRIDE;
 
 protected:
   SampleEntryBox() MOZ_DELETE;
 
-  uint32_t mTrackType;
   MetaHelper mMeta;
 };
 
 // 14496-12 8.5.2 'Sample Description Box'
 // Box type: 'stsd'
 class SampleDescriptionBox : public FullBox {
 public:
   // ISO BMFF members
@@ -535,16 +546,68 @@ public:
   // SampleDescriptionBox methods
   SampleDescriptionBox(uint32_t aType, ISOControl* aControl);
   ~SampleDescriptionBox();
 
 protected:
   uint32_t mTrackType;
 };
 
+// 14496-12 8.5.2.2
+// The base class for audio codec box.
+// This class is for inherited only, it shouldn't be instanced directly.
+class AudioSampleEntry : public SampleEntryBox {
+public:
+  // ISO BMFF members
+  uint16_t sound_version;
+  uint8_t reserved2[6];
+  uint16_t channels;
+  uint16_t sample_size;
+  uint16_t compressionId;
+  uint16_t packet_size;
+  uint32_t timeScale;  // (sample rate of media) <<16
+
+  // MuxerOperation methods
+  nsresult Write() MOZ_OVERRIDE;
+
+  ~AudioSampleEntry();
+
+protected:
+  AudioSampleEntry(const nsACString& aFormat, ISOControl* aControl);
+};
+
+// 14496-12 8.5.2.2
+// The base class for video codec box.
+// This class is for inherited only, it shouldn't be instanced directly.
+class VisualSampleEntry : public SampleEntryBox {
+public:
+  // ISO BMFF members
+  uint8_t reserved[16];
+  uint16_t width;
+  uint16_t height;
+
+  uint32_t horizresolution; // 72 dpi
+  uint32_t vertresolution;  // 72 dpi
+  uint32_t reserved2;
+  uint16_t frame_count;     // 1, defined in 14496-12 8.5.2.2
+
+  uint8_t compressorName[32];
+  uint16_t depth;       // 0x0018, defined in 14496-12 8.5.2.2;
+  uint16_t pre_defined; // -1, defined in 14496-12 8.5.2.2;
+
+  // MuxerOperation methods
+  nsresult Write() MOZ_OVERRIDE;
+
+  // VisualSampleEntry methods
+  ~VisualSampleEntry();
+
+protected:
+  VisualSampleEntry(const nsACString& aFormat, ISOControl* aControl);
+};
+
 // 14496-12 8.7.3.2 'Sample Size Box'
 // Box type: 'stsz'
 class SampleSizeBox : public FullBox {
 public:
   // ISO BMFF members
   uint32_t sample_size;
   uint32_t sample_count;
 
--- a/content/media/encoder/fmp4_muxer/ISOMediaWriter.cpp
+++ b/content/media/encoder/fmp4_muxer/ISOMediaWriter.cpp
@@ -18,29 +18,29 @@
 #else
 #define LOG(args, ...)
 #endif
 
 namespace mozilla {
 
 const static uint32_t FRAG_DURATION = 2 * USECS_PER_S;    // microsecond per unit
 
-ISOMediaWriter::ISOMediaWriter(uint32_t aType)
+ISOMediaWriter::ISOMediaWriter(uint32_t aType, uint32_t aHint)
   : ContainerWriter()
   , mState(MUXING_HEAD)
   , mBlobReady(false)
   , mType(0)
 {
   if (aType & CREATE_AUDIO_TRACK) {
     mType |= Audio_Track;
   }
   if (aType & CREATE_VIDEO_TRACK) {
     mType |= Video_Track;
   }
-  mControl = new ISOControl();
+  mControl = new ISOControl(aHint);
   MOZ_COUNT_CTOR(ISOMediaWriter);
 }
 
 ISOMediaWriter::~ISOMediaWriter()
 {
   MOZ_COUNT_DTOR(ISOMediaWriter);
 }
 
--- a/content/media/encoder/fmp4_muxer/ISOMediaWriter.h
+++ b/content/media/encoder/fmp4_muxer/ISOMediaWriter.h
@@ -15,27 +15,43 @@ class ISOControl;
 class FragmentBuffer;
 class AACTrackMetadata;
 class AVCTrackMetadata;
 class ISOMediaWriterRunnable;
 
 class ISOMediaWriter : public ContainerWriter
 {
 public:
+  // Generate an fragmented MP4 stream, ISO/IEC 14496-12.
+  // Brand names in 'ftyp' box are 'isom' and 'mp42'.
+  const static uint32_t TYPE_FRAG_MP4 = 1 << 0;
+
+  // Generate an fragmented 3GP stream, 3GPP TS 26.244,
+  // '5.4.3 Basic profile'.
+  // Brand names in 'ftyp' box are '3gp9' and 'isom'.
+  const static uint32_t TYPE_FRAG_3GP = 1 << 1;
+
+  // aType is the combination of CREATE_AUDIO_TRACK and CREATE_VIDEO_TRACK.
+  // It is a hint to muxer that the output streaming contains audio, video
+  // or both.
+  //
+  // aHint is one of the value in TYPE_XXXXXXXX. It is a hint to muxer what kind
+  // of ISO format should be generated.
+  ISOMediaWriter(uint32_t aType, uint32_t aHint = TYPE_FRAG_MP4);
+  ~ISOMediaWriter();
+
+  // ContainerWriter methods
   nsresult WriteEncodedTrack(const EncodedFrameContainer &aData,
                              uint32_t aFlags = 0) MOZ_OVERRIDE;
 
   nsresult GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs,
                             uint32_t aFlags = 0) MOZ_OVERRIDE;
 
   nsresult SetMetadata(TrackMetadataBase* aMetadata) MOZ_OVERRIDE;
 
-  ISOMediaWriter(uint32_t aType);
-  ~ISOMediaWriter();
-
 protected:
   /**
    * The state of each state will generate one or more blob.
    * Each blob will be a moov, moof, moof... until receiving EOS.
    * The generated sequence is:
    *
    *   moov -> moof -> moof -> ... -> moof -> moof
    *
--- a/content/media/encoder/fmp4_muxer/MP4ESDS.cpp
+++ b/content/media/encoder/fmp4_muxer/MP4ESDS.cpp
@@ -8,72 +8,42 @@
 #include "ISOMediaBoxes.h"
 #include "MP4ESDS.h"
 
 namespace mozilla {
 
 nsresult
 MP4AudioSampleEntry::Generate(uint32_t* aBoxSize)
 {
-  sound_version = 0;
-  // step reserved2
-  sample_size = 16;
-  channels = mMeta.mAudMeta->Channels;
-  compressionId = 0;
-  packet_size = 0;
-  timeScale = mMeta.mAudMeta->SampleRate << 16;
-
-  size += sizeof(sound_version) +
-          sizeof(reserved2) +
-          sizeof(sample_size) +
-          sizeof(channels) +
-          sizeof(packet_size) +
-          sizeof(compressionId) +
-          sizeof(timeScale);
-
   uint32_t box_size;
   nsresult rv = es->Generate(&box_size);
   NS_ENSURE_SUCCESS(rv, rv);
   size += box_size;
 
   *aBoxSize = size;
   return NS_OK;
 }
 
 nsresult
 MP4AudioSampleEntry::Write()
 {
   BoxSizeChecker checker(mControl, size);
-  SampleEntryBox::Write();
-  mControl->Write(sound_version);
-  mControl->Write(reserved2, sizeof(reserved2));
-  mControl->Write(channels);
-  mControl->Write(sample_size);
-  mControl->Write(compressionId);
-  mControl->Write(packet_size);
-  mControl->Write(timeScale);
-
-  nsresult rv = es->Write();
+  nsresult rv;
+  rv = AudioSampleEntry::Write();
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = es->Write();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 MP4AudioSampleEntry::MP4AudioSampleEntry(ISOControl* aControl)
-  : SampleEntryBox(NS_LITERAL_CSTRING("mp4a"), Audio_Track, aControl)
-  , sound_version(0)
-  , channels(2)
-  , sample_size(16)
-  , compressionId(0)
-  , packet_size(0)
-  , timeScale(0)
+  : AudioSampleEntry(NS_LITERAL_CSTRING("mp4a"), aControl)
 {
   es = new ESDBox(aControl);
-  mMeta.Init(mControl);
-  memset(reserved2, 0 , sizeof(reserved2));
   MOZ_COUNT_CTOR(MP4AudioSampleEntry);
 }
 
 MP4AudioSampleEntry::~MP4AudioSampleEntry()
 {
   MOZ_COUNT_DTOR(MP4AudioSampleEntry);
 }
 
--- a/content/media/encoder/fmp4_muxer/MP4ESDS.h
+++ b/content/media/encoder/fmp4_muxer/MP4ESDS.h
@@ -63,36 +63,25 @@ public:
 
   // ESDBox methods
   ESDBox(ISOControl* aControl);
   ~ESDBox();
 };
 
 // 14496-14 5.6 'Sample Description Boxes'
 // Box type: 'mp4a'
-class MP4AudioSampleEntry : public SampleEntryBox {
+class MP4AudioSampleEntry : public AudioSampleEntry {
 public:
   // ISO BMFF members
-  uint16_t sound_version;
-  uint8_t reserved2[6];
-  uint16_t channels;
-  uint16_t sample_size;
-  uint16_t compressionId;
-  uint16_t packet_size;
-  uint32_t timeScale;
   nsRefPtr<ESDBox> es;
 
   // MuxerOperation methods
   nsresult Generate(uint32_t* aBoxSize) MOZ_OVERRIDE;
   nsresult Write() MOZ_OVERRIDE;
 
   // MP4AudioSampleEntry methods
   MP4AudioSampleEntry(ISOControl* aControl);
   ~MP4AudioSampleEntry();
-
-protected:
-  uint32_t mTrackType;
-  MetaHelper mMeta;
 };
 
 }
 
 #endif // MP4ESDS_h_
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -94,16 +94,17 @@ public:
   void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> >& aFiles,
                             PRTime aSince, nsAString& aRootPath);
 
   void AccumDiskUsage(uint64_t* aPicturesSoFar, uint64_t* aVideosSoFar,
                       uint64_t* aMusicSoFar, uint64_t* aTotalSoFar);
 
   void GetDiskFreeSpace(int64_t* aSoFar);
   void GetStatus(nsAString& aStatus);
+  void GetStorageStatus(nsAString& aStatus);
   void DoFormat(nsAString& aStatus);
   static void GetRootDirectoryForType(const nsAString& aStorageType,
                                       const nsAString& aStorageName,
                                       nsIFile** aFile);
 
   nsresult CalculateSizeAndModifiedDate();
   nsresult CalculateMimeType();
   nsresult CreateFileDescriptor(mozilla::ipc::FileDescriptor& aFileDescriptor);
@@ -239,16 +240,17 @@ public:
   already_AddRefed<DOMCursor>
   EnumerateEditable(const nsAString& aPath,
                     const EnumerationParameters& aOptions, ErrorResult& aRv);
 
   already_AddRefed<DOMRequest> FreeSpace(ErrorResult& aRv);
   already_AddRefed<DOMRequest> UsedSpace(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Available(ErrorResult& aRv);
   already_AddRefed<DOMRequest> Format(ErrorResult& aRv);
+  already_AddRefed<DOMRequest> StorageStatus(ErrorResult& aRv);
 
   bool Default();
 
   // Uses XPCOM GetStorageName
 
   static void
   CreateDeviceStorageFor(nsPIDOMWindow* aWin,
                          const nsAString& aType,
--- a/dom/devicestorage/DeviceStorageRequestChild.cpp
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -131,16 +131,26 @@ DeviceStorageRequestChild::
       AvailableStorageResponse r = aValue;
       AutoJSContext cx;
       JS::Rooted<JS::Value> result(
         cx, StringToJsval(mRequest->GetOwner(), r.mountState()));
       mRequest->FireSuccess(result);
       break;
     }
 
+    case DeviceStorageResponseValue::TStorageStatusResponse:
+    {
+      StorageStatusResponse r = aValue;
+      AutoJSContext cx;
+      JS::Rooted<JS::Value> result(
+        cx, StringToJsval(mRequest->GetOwner(), r.storageStatus()));
+      mRequest->FireSuccess(result);
+      break;
+    }
+
     case DeviceStorageResponseValue::TFormatStorageResponse:
     {
       FormatStorageResponse r = aValue;
       AutoJSContext cx;
       JS::Rooted<JS::Value> result(
         cx, StringToJsval(mRequest->GetOwner(), r.mountState()));
       mRequest->FireSuccess(result);
       break;
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -143,16 +143,29 @@ DeviceStorageRequestParent::Dispatch()
         new DeviceStorageFile(p.type(), p.storageName());
       nsRefPtr<PostAvailableResultEvent> r
         = new PostAvailableResultEvent(this, dsf);
       DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       break;
     }
 
+    case DeviceStorageParams::TDeviceStorageStatusParams:
+    {
+      DeviceStorageStatusParams p = mParams;
+
+      nsRefPtr<DeviceStorageFile> dsf =
+        new DeviceStorageFile(p.type(), p.storageName());
+      nsRefPtr<PostStatusResultEvent> r
+        = new PostStatusResultEvent(this, dsf);
+      DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+      break;
+    }
+
     case DeviceStorageParams::TDeviceStorageFormatParams:
     {
       DeviceStorageFormatParams p = mParams;
 
       nsRefPtr<DeviceStorageFile> dsf =
         new DeviceStorageFile(p.type(), p.storageName());
       nsRefPtr<PostFormatResultEvent> r
         = new PostFormatResultEvent(this, dsf);
@@ -248,16 +261,24 @@ DeviceStorageRequestParent::EnsureRequir
     case DeviceStorageParams::TDeviceStorageAvailableParams:
     {
       DeviceStorageAvailableParams p = mParams;
       type = p.type();
       requestType = DEVICE_STORAGE_REQUEST_AVAILABLE;
       break;
     }
 
+    case DeviceStorageParams::TDeviceStorageStatusParams:
+    {
+      DeviceStorageStatusParams p = mParams;
+      type = p.type();
+      requestType = DEVICE_STORAGE_REQUEST_STATUS;
+      break;
+    }
+
     case DeviceStorageParams::TDeviceStorageFormatParams:
     {
       DeviceStorageFormatParams p = mParams;
       type = p.type();
       requestType = DEVICE_STORAGE_REQUEST_FORMAT;
       break;
     }
 
@@ -818,16 +839,44 @@ DeviceStorageRequestParent::PostAvailabl
     mFile->GetStatus(state);
   }
 
   AvailableStorageResponse response(state);
   unused << mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
+DeviceStorageRequestParent::PostStatusResultEvent::
+  PostStatusResultEvent(DeviceStorageRequestParent* aParent,
+                           DeviceStorageFile* aFile)
+  : CancelableRunnable(aParent)
+  , mFile(aFile)
+{
+}
+
+DeviceStorageRequestParent::PostStatusResultEvent::
+  ~PostStatusResultEvent()
+{
+}
+
+nsresult
+DeviceStorageRequestParent::PostStatusResultEvent::CancelableRun()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsString state = NS_LITERAL_STRING("undefined");
+  if (mFile) {
+    mFile->GetStorageStatus(state);
+  }
+
+  StorageStatusResponse response(state);
+  unused << mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
 DeviceStorageRequestParent::PostFormatResultEvent::
   PostFormatResultEvent(DeviceStorageRequestParent* aParent,
                            DeviceStorageFile* aFile)
   : CancelableRunnable(aParent)
   , mFile(aFile)
 {
 }
 
--- a/dom/devicestorage/DeviceStorageRequestParent.h
+++ b/dom/devicestorage/DeviceStorageRequestParent.h
@@ -244,16 +244,26 @@ private:
     public:
       PostAvailableResultEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
       virtual ~PostAvailableResultEvent();
       virtual nsresult CancelableRun();
     private:
       nsRefPtr<DeviceStorageFile> mFile;
  };
 
+ class PostStatusResultEvent : public CancelableRunnable
+ {
+    public:
+      PostStatusResultEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      virtual ~PostStatusResultEvent();
+      virtual nsresult CancelableRun();
+    private:
+      nsRefPtr<DeviceStorageFile> mFile;
+ };
+
  class PostFormatResultEvent : public CancelableRunnable
  {
     public:
       PostFormatResultEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
       virtual ~PostFormatResultEvent();
       virtual nsresult CancelableRun();
     private:
       nsRefPtr<DeviceStorageFile> mFile;
--- a/dom/devicestorage/PDeviceStorageRequest.ipdl
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -53,31 +53,37 @@ struct UsedSpaceStorageResponse
   uint64_t usedBytes;
 };
 
 struct AvailableStorageResponse
 {
   nsString mountState;
 };
 
+struct StorageStatusResponse
+{
+  nsString storageStatus;
+};
+
 struct FormatStorageResponse
 {
   nsString mountState;
 };
 
 union DeviceStorageResponseValue
 {
   ErrorResponse;
   SuccessResponse;
   FileDescriptorResponse;
   BlobResponse;
   EnumerationResponse;
   FreeSpaceStorageResponse;
   UsedSpaceStorageResponse;
   AvailableStorageResponse;
+  StorageStatusResponse;
   FormatStorageResponse;
 };
 
 sync protocol PDeviceStorageRequest {
     manager PContent;
 child:
     __delete__(DeviceStorageResponseValue response);
 };
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -385,16 +385,17 @@ DeviceStorageTypeChecker::GetAccessForRe
   const DeviceStorageRequestType aRequestType, nsACString& aAccessResult)
 {
   switch(aRequestType) {
     case DEVICE_STORAGE_REQUEST_READ:
     case DEVICE_STORAGE_REQUEST_WATCH:
     case DEVICE_STORAGE_REQUEST_FREE_SPACE:
     case DEVICE_STORAGE_REQUEST_USED_SPACE:
     case DEVICE_STORAGE_REQUEST_AVAILABLE:
+    case DEVICE_STORAGE_REQUEST_STATUS:
       aAccessResult.AssignLiteral("read");
       break;
     case DEVICE_STORAGE_REQUEST_WRITE:
     case DEVICE_STORAGE_REQUEST_DELETE:
     case DEVICE_STORAGE_REQUEST_FORMAT:
       aAccessResult.AssignLiteral("write");
       break;
     case DEVICE_STORAGE_REQUEST_CREATE:
@@ -1397,16 +1398,48 @@ DeviceStorageFile::GetStatus(nsAString& 
   rv = vol->GetState(&volState);
   NS_ENSURE_SUCCESS_VOID(rv);
   if (volState == nsIVolume::STATE_MOUNTED) {
     aStatus.AssignLiteral("available");
   }
 #endif
 }
 
+void
+DeviceStorageFile::GetStorageStatus(nsAString& aStatus)
+{
+  DeviceStorageTypeChecker* typeChecker
+    = DeviceStorageTypeChecker::CreateOrGet();
+  if (!typeChecker) {
+    return;
+  }
+  if (!typeChecker->IsVolumeBased(mStorageType)) {
+    aStatus.AssignLiteral("available");
+    return;
+  }
+
+  aStatus.AssignLiteral("undefined");
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+  NS_ENSURE_TRUE_VOID(vs);
+
+  nsCOMPtr<nsIVolume> vol;
+  nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (!vol) {
+    return;
+  }
+
+  int32_t volState;
+  rv = vol->GetState(&volState);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  aStatus.AssignASCII(mozilla::system::NS_VolumeStateStr(volState));
+#endif
+}
+
 NS_IMPL_ISUPPORTS0(DeviceStorageFile)
 
 static void
 RegisterForSDCardChanges(nsIObserver* aObserver)
 {
 #ifdef MOZ_WIDGET_GONK
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false);
@@ -1916,16 +1949,50 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
+class PostStatusResultEvent : public nsRunnable
+{
+public:
+  PostStatusResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
+    : mFile(aFile)
+    , mRequest(aRequest)
+  {
+    MOZ_ASSERT(mRequest);
+  }
+
+  ~PostStatusResultEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsString state = NS_LITERAL_STRING("undefined");
+    if (mFile) {
+      mFile->GetStorageStatus(state);
+    }
+
+    AutoJSContext cx;
+    JS::Rooted<JS::Value> result(cx,
+                                 StringToJsval(mRequest->GetOwner(), state));
+    mRequest->FireSuccess(result);
+    mRequest = nullptr;
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
 class PostFormatResultEvent : public nsRunnable
 {
 public:
   PostFormatResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
     : mFile(aFile)
     , mRequest(aRequest)
   {
     MOZ_ASSERT(mRequest);
@@ -2651,16 +2718,31 @@ public:
           ContentChild::GetSingleton()
             ->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new PostAvailableResultEvent(mFile, mRequest);
         return NS_DispatchToCurrentThread(r);
       }
 
+      case DEVICE_STORAGE_REQUEST_STATUS:
+      {
+        if (XRE_GetProcessType() != GeckoProcessType_Default) {
+          PDeviceStorageRequestChild* child
+            = new DeviceStorageRequestChild(mRequest, mFile);
+          DeviceStorageStatusParams params(mFile->mStorageType,
+                                              mFile->mStorageName);
+          ContentChild::GetSingleton()
+            ->SendPDeviceStorageRequestConstructor(child, params);
+          return NS_OK;
+        }
+        r = new PostStatusResultEvent(mFile, mRequest);
+        return NS_DispatchToCurrentThread(r);
+      }
+
       case DEVICE_STORAGE_REQUEST_WATCH:
       {
         mDeviceStorage->mAllowedToWatchFile = true;
         return NS_OK;
       }
 
       case DEVICE_STORAGE_REQUEST_FORMAT:
       {
@@ -3374,16 +3456,41 @@ nsDOMDeviceStorage::Available(ErrorResul
   nsresult rv = NS_DispatchToCurrentThread(r);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
   return request.forget();
 }
 
 already_AddRefed<DOMRequest>
+nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
+  if (!win) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
+                                                          mStorageName);
+  nsCOMPtr<nsIRunnable> r
+    = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_STATUS,
+                               win, mPrincipal, dsf, request);
+  nsresult rv = NS_DispatchToCurrentThread(r);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+  return request.forget();
+}
+
+already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::Format(ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -47,16 +47,17 @@ enum DeviceStorageRequestType {
     DEVICE_STORAGE_REQUEST_READ,
     DEVICE_STORAGE_REQUEST_WRITE,
     DEVICE_STORAGE_REQUEST_CREATE,
     DEVICE_STORAGE_REQUEST_DELETE,
     DEVICE_STORAGE_REQUEST_WATCH,
     DEVICE_STORAGE_REQUEST_FREE_SPACE,
     DEVICE_STORAGE_REQUEST_USED_SPACE,
     DEVICE_STORAGE_REQUEST_AVAILABLE,
+    DEVICE_STORAGE_REQUEST_STATUS,
     DEVICE_STORAGE_REQUEST_FORMAT,
     DEVICE_STORAGE_REQUEST_CREATEFD
 };
 
 class DeviceStorageUsedSpaceCache MOZ_FINAL
 {
 public:
   static DeviceStorageUsedSpaceCache* CreateOrGet();
--- a/dom/downloads/src/DownloadsAPI.jsm
+++ b/dom/downloads/src/DownloadsAPI.jsm
@@ -143,24 +143,35 @@ let DownloadsAPI = {
 
   receiveMessage: function(aMessage) {
     if (!aMessage.target.assertPermission("downloads")) {
       debug("No 'downloads' permission!");
       return;
     }
 
     debug("message: " + aMessage.name);
-    // Removing 'Downloads:' and turning first letter to lower case to
-    // build the function name from the message name.
-    let c = aMessage.name[10].toLowerCase();
-    let methodName = c + aMessage.name.substring(11);
-    if (this[methodName] && typeof this[methodName] === "function") {
-      this[methodName](aMessage.data, aMessage.target);
-    } else {
-      debug("Unimplemented method:  " + methodName);
+
+    switch (aMessage.name) {
+    case "Downloads:GetList":
+      this.getList(aMessage.data, aMessage.target);
+      break;
+    case "Downloads:ClearAllDone":
+      this.clearAllDone(aMessage.data, aMessage.target);
+      break;
+    case "Downloads:Remove":
+      this.remove(aMessage.data, aMessage.target);
+      break;
+    case "Downloads:Pause":
+      this.pause(aMessage.data, aMessage.target);
+      break;
+    case "Downloads:Resume":
+      this.resume(aMessage.data, aMessage.target);
+      break;
+    default:
+      debug("Invalid message: " + aMessage.name);
     }
   },
 
   getList: function(aData, aMm) {
     debug("getList called!");
     let self = this;
     Task.spawn(function () {
       let list = yield Downloads.getList(Downloads.ALL);
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -75,16 +75,22 @@ struct DeviceStorageUsedSpaceParams
 };
 
 struct DeviceStorageAvailableParams
 {
   nsString type;
   nsString storageName;
 };
 
+struct DeviceStorageStatusParams
+{
+  nsString type;
+  nsString storageName;
+};
+
 struct DeviceStorageFormatParams
 {
   nsString type;
   nsString storageName;
 };
 
 struct DeviceStorageAddParams
 {
@@ -129,16 +135,17 @@ union DeviceStorageParams
   DeviceStorageAddParams;
   DeviceStorageCreateFdParams;
   DeviceStorageGetParams;
   DeviceStorageDeleteParams;
   DeviceStorageEnumerationParams;
   DeviceStorageFreeSpaceParams;
   DeviceStorageUsedSpaceParams;
   DeviceStorageAvailableParams;
+  DeviceStorageStatusParams;
   DeviceStorageFormatParams;
 };
 
 struct FMRadioRequestEnableParams
 {
   double frequency;
 };
 
--- a/dom/network/src/NetUtils.cpp
+++ b/dom/network/src/NetUtils.cpp
@@ -169,13 +169,22 @@ int32_t NetUtils::do_dhcp_do_request(con
     // JB 4.3
     // http://androidxref.com/4.3_r2.1/xref/system/core/libnetutils/dhcp_utils.c#181
     DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*,  uint32_t*, char**, char*, uint32_t*, char*, char*)
     USE_DLFUNC(dhcp_do_request)
     char *dns[3] = {dns1, dns2, nullptr};
     char domains[PROPERTY_VALUE_MAX];
     ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns,
                           server, lease, vendorinfo, domains);
+  } else if (sdkVersion == 19) {
+    // JB 4.4
+    // http://androidxref.com/4.4_r1/xref/system/core/libnetutils/dhcp_utils.c#18
+    DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*,  uint32_t*, char**, char*, uint32_t*, char*, char*, char*)
+    USE_DLFUNC(dhcp_do_request)
+    char *dns[3] = {dns1, dns2, nullptr};
+    char domains[PROPERTY_VALUE_MAX];
+    char mtu[PROPERTY_VALUE_MAX];
+    ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns, server, lease, vendorinfo, domains, mtu);
   } else {
     NS_WARNING("Unable to perform do_dhcp_request: unsupported sdk version!");
   }
   return ret;
 }
--- a/dom/settings/moz.build
+++ b/dom/settings/moz.build
@@ -4,17 +4,21 @@
 # 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/.
 
 TEST_DIRS += ['tests']
 
 EXTRA_COMPONENTS += [
     'SettingsManager.js',
     'SettingsManager.manifest',
-    'SettingsService.js',
-    'SettingsService.manifest',
 ]
 
+if CONFIG['MOZ_B2G']:
+    EXTRA_COMPONENTS += [
+        'SettingsService.js',
+        'SettingsService.manifest',
+    ]
+
 EXTRA_JS_MODULES += [
     'SettingsChangeNotifier.jsm',
     'SettingsDB.jsm',
     'SettingsQueue.jsm',
 ]
--- a/dom/settings/tests/moz.build
+++ b/dom/settings/tests/moz.build
@@ -1,10 +1,11 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 MOCHITEST_MANIFESTS += ['mochitest.ini']
 
-MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
+if CONFIG['MOZ_B2G']:
+  MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
 
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -13936,26 +13936,24 @@ ICCContactHelperObject.prototype = {
    * @param appType       One of CARD_APPTYPE_*.
    * @param contactType   "adn" or "fdn".
    * @param contact       The contact will be added.
    * @param pin2          PIN2 is required for FDN.
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
-    let ICCContactHelper = this.context.ICCContactHelper;
-
-    let foundFreeCb = function foundFreeCb(pbrIndex, recordId) {
+    let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
       contact.pbrIndex = pbrIndex;
       contact.recordId = recordId;
-      ICCContactHelper.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
-    };
+      this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
+    }).bind(this);
 
     // Find free record first.
-    ICCContactHelper.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
+    this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
   },
 
   /**
    * Helper function to update ICC contact.
    *
    * @param appType       One of CARD_APPTYPE_*.
    * @param contactType   "adn" or "fdn".
    * @param contact       The contact will be updated.
@@ -14056,30 +14054,29 @@ ICCContactHelperObject.prototype = {
    * Read supported Phonebook fields.
    *
    * @param pbr         Phone Book Reference file.
    * @param contacts    Contacts stored on ICC.
    * @param onsuccess   Callback to be called when success.
    * @param onerror     Callback to be called when error.
    */
   readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
-    let ICCContactHelper = this.context.ICCContactHelper;
     let fieldIndex = 0;
     (function readField() {
       let field = USIM_PBR_FIELDS[fieldIndex];
       fieldIndex += 1;
       if (!field) {
         if (onsuccess) {
           onsuccess(contacts);
         }
         return;
       }
 
-      ICCContactHelper.readPhonebookField(pbr, contacts, field, readField, onerror);
-    })();
+      this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
+    }).call(this);
   },
 
   /**
    * Read Phonebook field.
    *
    * @param pbr           The phonebook reference file.
    * @param contacts      Contacts stored on ICC.
    * @param field         Phonebook field to be retrieved.
@@ -14089,30 +14086,29 @@ ICCContactHelperObject.prototype = {
   readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
     if (!pbr[field]) {
       if (onsuccess) {
         onsuccess(contacts);
       }
       return;
     }
 
-    let ICCContactHelper = this.context.ICCContactHelper;
     (function doReadContactField(n) {
       if (n >= contacts.length) {
         // All contact's fields are read.
         if (onsuccess) {
           onsuccess(contacts);
         }
         return;
       }
 
       // get n-th contact's field.
-      ICCContactHelper.readContactField(
-        pbr, contacts[n], field, doReadContactField.bind(this, n + 1), onerror);
-    })(0);
+      this.readContactField(pbr, contacts[n], field,
+                            doReadContactField.bind(this, n + 1), onerror);
+    }).call(this, 0);
   },
 
   /**
    * Read contact's field from USIM.
    *
    * @param pbr           The phonebook reference file.
    * @param contact       The contact needs to get field.
    * @param field         Phonebook field to be retrieved.
@@ -14253,36 +14249,35 @@ ICCContactHelperObject.prototype = {
    * Update supported Phonebook fields.
    *
    * @param pbr         Phone Book Reference file.
    * @param contact     Contact to be updated.
    * @param onsuccess   Callback to be called when success.
    * @param onerror     Callback to be called when error.
    */
   updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
-    let ICCContactHelper = this.context.ICCContactHelper;
     let fieldIndex = 0;
     (function updateField() {
       let field = USIM_PBR_FIELDS[fieldIndex];
       fieldIndex += 1;
       if (!field) {
         if (onsuccess) {
           onsuccess();
         }
         return;
       }
 
       // Check if PBR has this field.
       if (!pbr[field]) {
-        updateField();
+        updateField.call(this);
         return;
       }
 
-      ICCContactHelper.updateContactField(pbr, contact, field, updateField, onerror);
-    })();
+      this.updateContactField(pbr, contact, field, updateField.bind(this), onerror);
+    }).call(this);
   },
 
   /**
    * Update contact's field from USIM.
    *
    * @param pbr           The phonebook reference file.
    * @param contact       The contact needs to be updated.
    * @param field         Phonebook field to be updated.
--- a/dom/system/gonk/tests/test_ril_worker_icc.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -669,30 +669,30 @@ add_test(function test_icc_get_card_lock
   buf.sendParcel = function() {
     // Request Type.
     do_check_eq(this.readInt32(), REQUEST_QUERY_FACILITY_LOCK)
 
     // Token : we don't care.
     this.readInt32();
 
     // String Array Length.
-    do_check_eq(this.readInt32(), worker.RILQUIRKS_V5_LEGACY ? 3 : 4);
+    do_check_eq(this.readInt32(), ril.v5Legacy ? 3 : 4);
 
     // Facility.
     do_check_eq(this.readString(), ICC_CB_FACILITY_FDN);
 
     // Password.
     do_check_eq(this.readString(), "");
 
     // Service class.
     do_check_eq(this.readString(), (ICC_SERVICE_CLASS_VOICE |
                                     ICC_SERVICE_CLASS_DATA  |
                                     ICC_SERVICE_CLASS_FAX).toString());
 
-    if (!worker.RILQUIRKS_V5_LEGACY) {
+    if (!ril.v5Legacy) {
       // AID. Ignore because it's from modem.
       this.readInt32();
     }
 
     run_next_test();
   };
 
   ril.iccGetCardLockState({lockType: "fdn"});
@@ -1115,17 +1115,17 @@ add_test(function test_update_email() {
         do_check_eq(pduHelper.readHexOctet(), expectedAdnRecordId);
       }
       this.readStringDelimiter(strLen);
       do_check_eq(email, expectedEmail);
 
       // pin2.
       do_check_eq(this.readString(), null);
 
-      if (!worker.RILQUIRKS_V5_LEGACY) {
+      if (!ril.v5Legacy) {
         // AID. Ignore because it's from modem.
         this.readInt32();
       }
 
       if (count == NUM_TESTS) {
         run_next_test();
       }
     };
@@ -1261,17 +1261,17 @@ add_test(function test_update_anr() {
         do_check_eq(pduHelper.readHexOctet(), pbr.adn.sfi);
         do_check_eq(pduHelper.readHexOctet(), expectedAdnRecordId);
       }
       this.readStringDelimiter(strLen);
 
       // pin2.
       do_check_eq(this.readString(), null);
 
-      if (!worker.RILQUIRKS_V5_LEGACY) {
+      if (!ril.v5Legacy) {
         // AID. Ignore because it's from modem.
         this.readInt32();
       }
 
       if (count == NUM_TESTS) {
         run_next_test();
       }
     };
@@ -1398,17 +1398,17 @@ add_test(function test_update_iap() {
       for (let i = 0; i < recordSize; i++) {
         do_check_eq(expectedIAP[i], pduHelper.readHexOctet());
       }
       this.readStringDelimiter(strLen);
 
       // pin2.
       do_check_eq(this.readString(), null);
 
-      if (!worker.RILQUIRKS_V5_LEGACY) {
+      if (!ril.v5Legacy) {
         // AID. Ignore because it's from modem.
         this.readInt32();
       }
 
       run_next_test();
     };
     recordHelper.updateIAP(fileId, recordNumber, expectedIAP);
   }
@@ -1474,17 +1474,17 @@ add_test(function test_update_adn_like()
 
     // pin2.
     if (fileId == ICC_EF_ADN) {
       do_check_eq(this.readString(), null);
     } else {
       do_check_eq(this.readString(), "1111");
     }
 
-    if (!worker.RILQUIRKS_V5_LEGACY) {
+    if (!ril.v5Legacy) {
       // AID. Ignore because it's from modem.
       this.readInt32();
     }
 
     if (fileId == ICC_EF_FDN) {
       run_next_test();
     }
   };
@@ -2222,20 +2222,20 @@ add_test(function test_icc_permanent_blo
 });
 
 /**
  * Verify iccSetCardLock - Facility Lock.
  */
 add_test(function test_set_icc_card_lock_facility_lock() {
   let worker = newUint8Worker();
   let context = worker.ContextPool._contexts[0];
-  worker.RILQUIRKS_V5_LEGACY = false;
   let aid = "123456789";
   let ril = context.RIL;
   ril.aid = aid;
+  ril.v5Legacy = false;
   let buf = context.Buf;
 
   let GECKO_CARDLOCK_TO_FACILITIY_LOCK = {};
   GECKO_CARDLOCK_TO_FACILITIY_LOCK[GECKO_CARDLOCK_PIN] = ICC_CB_FACILITY_SIM;
   GECKO_CARDLOCK_TO_FACILITIY_LOCK[GECKO_CARDLOCK_FDN] = ICC_CB_FACILITY_FDN;
 
   let GECKO_CARDLOCK_TO_PASSWORD_TYPE = {};
   GECKO_CARDLOCK_TO_PASSWORD_TYPE[GECKO_CARDLOCK_PIN] = "pin";
@@ -2723,17 +2723,17 @@ add_test(function test_update_mwis() {
       for (let i = 0; i < recordSize; i++) {
         do_check_eq(expectedMwis[i], pduHelper.readHexOctet());
       }
       this.readStringDelimiter(strLen);
 
       // pin2.
       do_check_eq(this.readString(), null);
 
-      if (!worker.RILQUIRKS_V5_LEGACY) {
+      if (!ril.v5Legacy) {
         // AID. Ignore because it's from modem.
         this.readInt32();
       }
     };
 
     do_check_false(isUpdated);
 
     recordHelper.updateMWIS({ active: isActive,
--- a/dom/webidl/DeviceStorage.webidl
+++ b/dom/webidl/DeviceStorage.webidl
@@ -35,16 +35,18 @@ interface DeviceStorage : EventTarget {
 
   [Throws]
   DOMRequest freeSpace();
   [Throws]
   DOMRequest usedSpace();
   [Throws]
   DOMRequest available();
   [Throws]
+  DOMRequest storageStatus();
+  [Throws]
   DOMRequest format();
 
   // Note that the storageName is just a name (like sdcard), and doesn't
   // include any path information.
   readonly attribute DOMString storageName;
 
   // Determines if this storage area is the one which will be used by default
   // for storing new files.
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -8,31 +8,34 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.favicons.LoadFaviconTask;
 import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
+import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerMarginsAnimator;
 import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.gecko.health.BrowserHealthReporter;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.home.BrowserSearch;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.SearchEngine;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.prompts.Prompt;
+import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.toolbar.AutocompleteHandler;
 import org.mozilla.gecko.toolbar.BrowserToolbar;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.StringUtils;
@@ -540,16 +543,18 @@ abstract public class BrowserApp extends
         registerEventListener("Feedback:OpenPlayStore");
         registerEventListener("Feedback:MaybeLater");
         registerEventListener("Telemetry:Gather");
         registerEventListener("Settings:Show");
         registerEventListener("Updater:Launch");
         registerEventListener("Menu:Add");
         registerEventListener("Menu:Remove");
         registerEventListener("Menu:Update");
+        registerEventListener("Accounts:Create");
+        registerEventListener("Accounts:Exist");
 
         Distribution.init(this);
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
         mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
@@ -857,16 +862,18 @@ abstract public class BrowserApp extends
         unregisterEventListener("Feedback:OpenPlayStore");
         unregisterEventListener("Feedback:MaybeLater");
         unregisterEventListener("Telemetry:Gather");
         unregisterEventListener("Settings:Show");
         unregisterEventListener("Updater:Launch");
         unregisterEventListener("Menu:Add");
         unregisterEventListener("Menu:Remove");
         unregisterEventListener("Menu:Update");
+        unregisterEventListener("Accounts:Create");
+        unregisterEventListener("Accounts:Exist");
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
                 // automatically on API 14+
                 nfc.setNdefPushMessageCallback(null, this);
@@ -1241,16 +1248,40 @@ abstract public class BrowserApp extends
             } else if (event.equals("Updater:Launch")) {
                 handleUpdaterLaunch();
             } else if (event.equals("Prompt:ShowTop")) {
                 // Bring this activity to front so the prompt is visible..
                 Intent bringToFrontIntent = new Intent();
                 bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS);
                 bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
                 startActivity(bringToFrontIntent);
+            } else if (event.equals("Accounts:Create")) {
+                // Do exactly the same thing as if you tapped 'Sync'
+                // in Settings.
+                final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                getContext().startActivity(intent);
+            } else if (event.equals("Accounts:Exist")) {
+                final String kind = message.getString("kind");
+                final JSONObject response = new JSONObject();
+
+                if ("any".equals(kind)) {
+                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()) ||
+                                           FirefoxAccounts.firefoxAccountsExist(getContext()));
+                    EventDispatcher.sendResponse(message, response);
+                } else if ("fxa".equals(kind)) {
+                    response.put("exists", FirefoxAccounts.firefoxAccountsExist(getContext()));
+                    EventDispatcher.sendResponse(message, response);
+                } else if ("sync11".equals(kind)) {
+                    response.put("exists", SyncAccounts.syncAccountsExist(getContext()));
+                    EventDispatcher.sendResponse(message, response);
+                } else {
+                    response.put("error", "Unknown kind");
+                    EventDispatcher.sendError(message, response);
+                }
             } else {
                 super.handleMessage(event, message);
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
@@ -1397,17 +1428,16 @@ abstract public class BrowserApp extends
         mTabsPanel.finishTabsAnimation();
 
         mMainLayoutAnimator = null;
     }
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        mToast.onSaveInstanceState(outState);
         outState.putBoolean(STATE_DYNAMIC_TOOLBAR_ENABLED, mDynamicToolbarEnabled);
         outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomePagerContainer.getPaddingTop());
     }
 
     /**
      * Attempts to switch to an open tab with the given URL.
      *
      * @return true if we successfully switched to a tab, false otherwise.
@@ -1892,17 +1922,17 @@ abstract public class BrowserApp extends
     }
 
     /**
      * Add the provided item to the provided menu, which should be
      * the root (mMenu).
      */
     private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
         info.added = true;
-
+        
         final Menu destination;
         if (info.parent == 0) {
             destination = menu;
         } else if (info.parent == GECKO_TOOLS_MENU) {
             MenuItem tools = menu.findItem(R.id.tools);
             destination = tools != null ? tools.getSubMenu() : menu;
         } else {
             MenuItem parent = menu.findItem(info.parent);
@@ -2056,21 +2086,18 @@ abstract public class BrowserApp extends
             share.setActionProvider(provider);
         }
 
         return true;
     }
 
     @Override
     public void openOptionsMenu() {
-        // Disable menu access in edge cases only accessible to hardware menu buttons.
-        if ((!hasTabsSideBar() && areTabsShown()) ||
-                mBrowserToolbar.isEditing()) {
+        if (!hasTabsSideBar() && areTabsShown())
             return;
-        }
 
         // Scroll custom menu to the top
         if (mMenuPanel != null)
             mMenuPanel.scrollTo(0, 0);
 
         if (!mBrowserToolbar.openOptionsMenu())
             super.openOptionsMenu();
 
@@ -2259,17 +2286,17 @@ abstract public class BrowserApp extends
             tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 if (item.isChecked()) {
                     tab.removeBookmark();
                     Toast.makeText(this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
                     item.setIcon(R.drawable.ic_menu_bookmark_add);
                 } else {
                     tab.addBookmark();
-                    mToast.show(false,
+                    getButtonToast().show(false,
                         getResources().getString(R.string.bookmark_added),
                         getResources().getString(R.string.bookmark_options),
                         null,
                         new ButtonToast.ToastListener() {
                             @Override
                             public void onButtonClicked() {
                                 showBookmarkDialog();
                             }
@@ -2545,17 +2572,17 @@ abstract public class BrowserApp extends
             }
         }).execute();
     }
 
     // HomePager.OnNewTabsListener
     @Override
     public void onNewTabs(String[] urls) {
         final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
-
+ 
         for (String url : urls) {
             if (!maybeSwitchToTab(url, flags)) {
                 openUrlAndStopEditing(url, true);
             }
         }
     }
 
     // HomePager.OnUrlOpenListener
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -86,16 +86,17 @@ import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.AbsoluteLayout;
 import android.widget.FrameLayout;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
@@ -394,17 +395,17 @@ public abstract class GeckoApp
                 mMenuPanel = new MenuPanel(this, null);
             } else {
                 // Prepare the panel everytime before showing the menu.
                 onPreparePanel(featureId, mMenuPanel, mMenu);
             }
 
             return mMenuPanel; 
         }
-
+  
         return super.onCreatePanelView(featureId);
     }
 
     @Override
     public boolean onCreatePanelMenu(int featureId, Menu menu) {
         if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
             if (mMenuPanel == null) {
                 mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
@@ -486,16 +487,20 @@ public abstract class GeckoApp
 
         return super.onKeyDown(keyCode, event);
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
+        if (mToast != null) {
+            mToast.onSaveInstanceState(outState);
+        }
+
         outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
         outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
     }
 
     void handleFaviconRequest(final String url) {
         (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
             @Override
             public String doInBackground(Void... params) {
@@ -808,25 +813,36 @@ public abstract class GeckoApp
                 } else {
                     toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_SHORT);
                 }
                 toast.show();
             }
         });
     }
 
+    protected ButtonToast getButtonToast() {
+        if (mToast != null) {
+            return mToast;
+        }
+
+        ViewStub toastStub = (ViewStub) findViewById(R.id.toast_stub);
+        mToast = new ButtonToast(toastStub.inflate());
+
+        return mToast;
+    }
+
     void showButtonToast(final String message, final String buttonText,
                          final String buttonIcon, final String buttonId) {
         BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() {
             public void onBitmapFound(final Drawable d) {
 
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        mToast.show(false, message, buttonText, d, new ButtonToast.ToastListener() {
+                        getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() {
                             @Override
                             public void onButtonClicked() {
                                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId));
                             }
 
                             @Override
                             public void onToastHidden(ButtonToast.ReasonHidden reason) {
                                 if (reason == ButtonToast.ReasonHidden.TIMEOUT) {
@@ -1235,18 +1251,16 @@ public abstract class GeckoApp
         mOrientation = getResources().getConfiguration().orientation;
 
         setContentView(getLayout());
 
         // Set up Gecko layout.
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
 
-        mToast = new ButtonToast(findViewById(R.id.toast));
-
         // Determine whether we should restore tabs.
         mShouldRestore = getSessionRestoreState(savedInstanceState);
         if (mShouldRestore && savedInstanceState != null) {
             boolean wasInBackground =
                 savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
 
             // Don't log OOM-kills if only one activity was destroyed. (For example
             // from "Don't keep activities" on ICS)
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -464,16 +464,68 @@ public final class HomeConfig {
 
             @Override
             public ViewType[] newArray(final int size) {
                 return new ViewType[size];
             }
         };
     }
 
+    public static enum ItemType implements Parcelable {
+        ARTICLE("article"),
+        IMAGE("image");
+
+        private final String mId;
+
+        ItemType(String id) {
+            mId = id;
+        }
+
+        public static ItemType fromId(String id) {
+            if (id == null) {
+                throw new IllegalArgumentException("Could not convert null String to ItemType");
+            }
+
+            for (ItemType itemType : ItemType.values()) {
+                if (TextUtils.equals(itemType.mId, id.toLowerCase())) {
+                    return itemType;
+                }
+            }
+
+            throw new IllegalArgumentException("Could not convert String id to ItemType");
+        }
+
+        @Override
+        public String toString() {
+            return mId;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(ordinal());
+        }
+
+        public static final Creator<ItemType> CREATOR = new Creator<ItemType>() {
+            @Override
+            public ItemType createFromParcel(final Parcel source) {
+                return ItemType.values()[source.readInt()];
+            }
+
+            @Override
+            public ItemType[] newArray(final int size) {
+                return new ItemType[size];
+            }
+        };
+    }
+
     public static enum ItemHandler implements Parcelable {
         BROWSER("browser"),
         INTENT("intent");
 
         private final String mId;
 
         ItemHandler(String id) {
             mId = id;
@@ -519,100 +571,116 @@ public final class HomeConfig {
                 return new ItemHandler[size];
             }
         };
     }
 
     public static class ViewConfig implements Parcelable {
         private final ViewType mType;
         private final String mDatasetId;
+        private final ItemType mItemType;
         private final ItemHandler mItemHandler;
 
         private static final String JSON_KEY_TYPE = "type";
         private static final String JSON_KEY_DATASET = "dataset";
+        private static final String JSON_KEY_ITEM_TYPE = "itemType";
         private static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
 
         public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
             mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
             mDatasetId = json.getString(JSON_KEY_DATASET);
+            mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
             mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
 
             validate();
         }
 
         @SuppressWarnings("unchecked")
         public ViewConfig(Parcel in) {
             mType = (ViewType) in.readParcelable(getClass().getClassLoader());
             mDatasetId = in.readString();
+            mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
             mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
 
             validate();
         }
 
         public ViewConfig(ViewConfig viewConfig) {
             mType = viewConfig.mType;
             mDatasetId = viewConfig.mDatasetId;
+            mItemType = viewConfig.mItemType;
             mItemHandler = viewConfig.mItemHandler;
 
             validate();
         }
 
-        public ViewConfig(ViewType type, String datasetId, ItemHandler itemHandler) {
+        public ViewConfig(ViewType type, String datasetId, ItemType itemType, ItemHandler itemHandler) {
             mType = type;
             mDatasetId = datasetId;
+            mItemType = itemType;
             mItemHandler = itemHandler;
 
             validate();
         }
 
         private void validate() {
             if (mType == null) {
                 throw new IllegalArgumentException("Can't create ViewConfig with null type");
             }
 
             if (TextUtils.isEmpty(mDatasetId)) {
                 throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
             }
 
+            if (mItemType == null) {
+                throw new IllegalArgumentException("Can't create ViewConfig with null item type");
+            }
+
             if (mItemHandler == null) {
                 throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
             }
         }
 
         public ViewType getType() {
             return mType;
         }
 
         public String getDatasetId() {
             return mDatasetId;
         }
 
+        public ItemType getItemType() {
+            return mItemType;
+        }
+
         public ItemHandler getItemHandler() {
             return mItemHandler;
         }
 
         public JSONObject toJSON() throws JSONException {
             final JSONObject json = new JSONObject();
 
             json.put(JSON_KEY_TYPE, mType.toString());
             json.put(JSON_KEY_DATASET, mDatasetId);
+            json.put(JSON_KEY_ITEM_TYPE, mItemType.toString());
             json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
 
             return json;
         }
 
         @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeParcelable(mType, 0);
             dest.writeString(mDatasetId);
+            dest.writeParcelable(mItemType, 0);
             dest.writeParcelable(mItemHandler, 0);
         }
 
         public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
             @Override
             public ViewConfig createFromParcel(final Parcel in) {
                 return new ViewConfig(in);
             }
deleted file mode 100644
--- a/mobile/android/base/home/PanelGridItemView.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.home;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.favicons.Favicons;
-import org.mozilla.gecko.R;
-
-import com.squareup.picasso.Picasso;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-public class PanelGridItemView extends FrameLayout {
-    private static final String LOGTAG = "GeckoPanelGridItemView";
-
-    private final ImageView mThumbnailView;
-
-    public PanelGridItemView(Context context) {
-        this(context, null);
-    }
-
-    public PanelGridItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.panelGridItemViewStyle);
-    }
-
-    public PanelGridItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        LayoutInflater.from(context).inflate(R.layout.panel_grid_item_view, this);
-        mThumbnailView = (ImageView) findViewById(R.id.image);
-    }
-
-    public void updateFromCursor(Cursor cursor) {
-        int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
-        final String imageUrl = cursor.getString(imageIndex);
-
-        Picasso.with(getContext())
-               .load(imageUrl)
-               .into(mThumbnailView);
-    }
-}
--- a/mobile/android/base/home/PanelGridView.java
+++ b/mobile/android/base/home/PanelGridView.java
@@ -24,23 +24,23 @@ import android.widget.GridView;
 
 import java.util.EnumSet;
 
 public class PanelGridView extends GridView
                            implements DatasetBacked, PanelView {
     private static final String LOGTAG = "GeckoPanelGridView";
 
     private final ViewConfig mViewConfig;
-    private final PanelGridViewAdapter mAdapter;
+    private final PanelViewAdapter mAdapter;
     protected OnUrlOpenListener mUrlOpenListener;
 
     public PanelGridView(Context context, ViewConfig viewConfig) {
         super(context, null, R.attr.panelGridViewStyle);
         mViewConfig = viewConfig;
-        mAdapter = new PanelGridViewAdapter(context);
+        mAdapter = new PanelViewAdapter(context, viewConfig.getItemType());
         setAdapter(mAdapter);
         setOnItemClickListener(new PanelGridItemClickListener());
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mUrlOpenListener = null;
@@ -51,34 +51,16 @@ public class PanelGridView extends GridV
         mAdapter.swapCursor(cursor);
     }
 
     @Override
     public void setOnUrlOpenListener(OnUrlOpenListener listener) {
         mUrlOpenListener = listener;
     }
 
-    private class PanelGridViewAdapter extends CursorAdapter {
-
-        public PanelGridViewAdapter(Context context) {
-            super(context, null, 0);
-        }
-
-        @Override
-        public void bindView(View bindView, Context context, Cursor cursor) {
-            final PanelGridItemView item = (PanelGridItemView) bindView;
-            item.updateFromCursor(cursor);
-        }
-
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            return new PanelGridItemView(context);
-        }
-    }
-
     private class PanelGridItemClickListener implements AdapterView.OnItemClickListener {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             Cursor cursor = mAdapter.getCursor();
             if (cursor == null || !cursor.moveToPosition(position)) {
                 throw new IllegalStateException("Couldn't move cursor to position " + position);
             }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/PanelItemView.java
@@ -0,0 +1,104 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.home;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract.HomeItems;
+import org.mozilla.gecko.home.HomeConfig.ItemType;
+
+import com.squareup.picasso.Picasso;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+
+class PanelItemView extends LinearLayout {
+    private final TextView mTitle;
+    private final TextView mDescription;
+    private final ImageView mImage;
+    private final LinearLayout mTitleDescContainer;
+
+    private PanelItemView(Context context, int layoutId) {
+        super(context);
+
+        LayoutInflater.from(context).inflate(layoutId, this);
+        mTitle = (TextView) findViewById(R.id.title);
+        mDescription = (TextView) findViewById(R.id.description);
+        mImage = (ImageView) findViewById(R.id.image);
+        mTitleDescContainer = (LinearLayout) findViewById(R.id.title_desc_container);
+    }
+
+    public void updateFromCursor(Cursor cursor) {
+        int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
+        final String title = cursor.getString(titleIndex);
+
+        // Only show title if the item has one
+        final boolean hasTitle = !TextUtils.isEmpty(title);
+        mTitleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
+        if (hasTitle) {
+            mTitle.setText(title);
+
+            int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
+            final String description = cursor.getString(descriptionIndex);
+
+            final boolean hasDescription = !TextUtils.isEmpty(description);
+            mDescription.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
+            if (hasDescription) {
+                mDescription.setText(description);
+            }
+        }
+
+        int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
+        final String imageUrl = cursor.getString(imageIndex);
+
+        // Only try to load the image if the item has define image URL
+        final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
+        mImage.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
+
+        if (hasImageUrl) {
+            Picasso.with(getContext())
+                   .load(imageUrl)
+                   .error(R.drawable.favicon)
+                   .into(mImage);
+        }
+    }
+
+    private static class ArticleItemView extends PanelItemView {
+        private ArticleItemView(Context context) {
+            super(context, R.layout.panel_article_item);
+            setOrientation(LinearLayout.HORIZONTAL);
+        }
+    }
+
+    private static class ImageItemView extends PanelItemView {
+        private ImageItemView(Context context) {
+            super(context, R.layout.panel_image_item);
+            setOrientation(LinearLayout.VERTICAL);
+        }
+    }
+
+    public static PanelItemView create(Context context, ItemType itemType) {
+        switch(itemType) {
+            case ARTICLE:
+                return new ArticleItemView(context);
+
+            case IMAGE:
+                return new ImageItemView(context);
+
+            default:
+                throw new IllegalArgumentException("Could not create panel item view from " + itemType);
+        }
+    }
+}
deleted file mode 100644
--- a/mobile/android/base/home/PanelListRow.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-
-import com.squareup.picasso.Picasso;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ImageView;
-
-public class PanelListRow extends TwoLineRow {
-
-    private final ImageView mIcon;
-
-    public PanelListRow(Context context) {
-        this(context, null);
-    }
-
-    public PanelListRow(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mIcon = (ImageView) findViewById(R.id.icon);
-    }
-
-    @Override
-    public void updateFromCursor(Cursor cursor) {
-        if (cursor == null) {
-            return;
-        }
-
-        // XXX: This will have to be updated once we come up with the
-        // final schema for Panel datasets (see bug 942288).
-
-        int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
-        final String title = cursor.getString(titleIndex);
-        setTitle(title);
-
-        int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
-        final String description = cursor.getString(descriptionIndex);
-        setDescription(description);
-
-        int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
-        final String imageUrl = cursor.getString(imageIndex);
-
-        final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
-        mIcon.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
-
-        if (hasImageUrl) {
-            Picasso.with(getContext())
-                   .load(imageUrl)
-                   .error(R.drawable.favicon)
-                   .into(mIcon);
-        }
-    }
-}
--- a/mobile/android/base/home/PanelListView.java
+++ b/mobile/android/base/home/PanelListView.java
@@ -24,50 +24,33 @@ import android.widget.AdapterView;
 
 import java.util.EnumSet;
 
 public class PanelListView extends HomeListView
                            implements DatasetBacked, PanelView {
 
     private static final String LOGTAG = "GeckoPanelListView";
 
-    private final PanelListAdapter mAdapter;
+    private final PanelViewAdapter mAdapter;
     private final ViewConfig mViewConfig;
 
     public PanelListView(Context context, ViewConfig viewConfig) {
         super(context);
         mViewConfig = viewConfig;
-        mAdapter = new PanelListAdapter(context);
+        mAdapter = new PanelViewAdapter(context, viewConfig.getItemType());
         setAdapter(mAdapter);
         setOnItemClickListener(new PanelListItemClickListener());
     }
 
     @Override
     public void setDataset(Cursor cursor) {
         Log.d(LOGTAG, "Setting dataset: " + mViewConfig.getDatasetId());
         mAdapter.swapCursor(cursor);
     }
 
-    private class PanelListAdapter extends CursorAdapter {
-        public PanelListAdapter(Context context) {
-            super(context, null, 0);
-        }
-
-        @Override
-        public void bindView(View view, Context context, Cursor cursor) {
-            final PanelListRow row = (PanelListRow) view;
-            row.updateFromCursor(cursor);
-        }
-
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            return LayoutInflater.from(parent.getContext()).inflate(R.layout.panel_list_row, parent, false);
-        }
-    }
-
     private class PanelListItemClickListener implements AdapterView.OnItemClickListener {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             Cursor cursor = mAdapter.getCursor();
             if (cursor == null || !cursor.moveToPosition(position)) {
                 throw new IllegalStateException("Couldn't move cursor to position " + position);
             }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/PanelViewAdapter.java
@@ -0,0 +1,37 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.home;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.home.HomeConfig.ItemType;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+
+class PanelViewAdapter extends CursorAdapter {
+	private final Context mContext;
+	private final ItemType mItemType;
+
+    public PanelViewAdapter(Context context, ItemType itemType) {
+        super(context, null, 0);
+        mContext = context;
+        mItemType = itemType;
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        final PanelItemView item = (PanelItemView) view;
+        item.updateFromCursor(cursor);
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return PanelItemView.create(mContext, mItemType);
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -228,22 +228,22 @@ gbjar.sources += [
     'home/HomeContextMenuInfo.java',
     'home/HomeFragment.java',
     'home/HomeListView.java',
     'home/HomePager.java',
     'home/HomePagerTabStrip.java',
     'home/LastTabsPanel.java',
     'home/MostRecentPanel.java',
     'home/MultiTypeCursorAdapter.java',
-    'home/PanelGridItemView.java',
     'home/PanelGridView.java',
+    'home/PanelItemView.java',
     'home/PanelLayout.java',
-    'home/PanelListRow.java',
     'home/PanelListView.java',
     'home/PanelManager.java',
+    'home/PanelViewAdapter.java',
     'home/PinSiteDialog.java',
     'home/ReadingListPanel.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SuggestClient.java',
     'home/TabMenuStrip.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/button_toast.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/toast"
+              style="@style/Toast">
+
+    <TextView android:id="@+id/toast_message"
+              style="@style/ToastMessage" />
+
+    <ImageView android:id="@+id/toast_divider"
+               style="@style/ToastDivider" />
+
+    <Button android:id="@+id/toast_button"
+            style="@style/ToastButton" />
+
+</LinearLayout>
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -93,23 +93,13 @@
                                                     android:layout_height="fill_parent"
                                                     android:layout_width="fill_parent"
                                                     style="@style/GeckoActionBar"/>
 
         </org.mozilla.gecko.widget.GeckoViewFlipper>
 
     </view>
 
-    <LinearLayout android:id="@+id/toast"
-                  style="@style/Toast">
-
-        <TextView android:id="@+id/toast_message"
-                  style="@style/ToastMessage" />
-
-        <ImageView android:id="@+id/toast_divider"
-                   style="@style/ToastDivider" />
-
-        <Button android:id="@+id/toast_button"
-                style="@style/ToastButton" />
-
-    </LinearLayout>
+    <ViewStub android:id="@+id/toast_stub"
+              android:layout="@layout/button_toast"
+              style="@style/Toast"/>
 
 </RelativeLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/panel_article_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <ImageView android:id="@+id/image"
+               android:layout_width="54dp"
+               android:layout_height="44dp"
+               android:layout_marginTop="10dip"
+               android:layout_marginLeft="10dip"
+               android:scaleType="centerCrop"/>
+
+    <LinearLayout android:id="@+id/title_desc_container"
+                  android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:paddingTop="5dip"
+                  android:paddingBottom="5dip"
+                  android:paddingLeft="10dip"
+                  android:paddingRight="10dip"
+                  android:minHeight="@dimen/page_row_height"
+                  android:orientation="vertical">
+
+        <TextView android:id="@+id/title"
+                  style="@style/Widget.PanelItemView.Title"
+                  android:layout_width="fill_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="1"
+                  android:gravity="center_vertical"/>
+
+        <TextView android:id="@+id/description"
+                  style="@style/Widget.PanelItemView.Description"
+                  android:layout_width="fill_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="2"
+                  android:maxLength="1024"/>
+
+    </LinearLayout>
+
+</merge>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/panel_grid_item_view.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
-    <org.mozilla.gecko.widget.SquaredImageView android:id="@+id/image"
-               style="@style/Widget.PanelGridItemImageView" />
-
-</merge>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/panel_image_item.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <org.mozilla.gecko.widget.SquaredImageView
+        android:id="@+id/image"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        android:background="@color/panel_image_item_background"/>
+
+    <LinearLayout android:id="@+id/title_desc_container"
+                  android:layout_width="fill_parent"
+                  android:layout_height="@dimen/page_row_height"
+                  android:paddingTop="7dip"
+                  android:paddingBottom="7dip"
+                  android:paddingLeft="5dip"
+                  android:paddingRight="5dip"
+                  android:orientation="vertical">
+
+        <TextView android:id="@+id/title"
+                  style="@style/Widget.PanelItemView.Title"
+                  android:layout_width="fill_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="1"
+                  android:gravity="center_vertical"
+                  android:singleLine="true"/>
+
+        <TextView android:id="@+id/description"
+                  style="@style/Widget.PanelItemView.Description"
+                  android:layout_width="fill_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="1"
+                  android:layout_marginTop="3dp"
+                  android:singleLine="true"
+                  android:maxLength="1024"/>
+
+    </LinearLayout>
+
+</merge>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/panel_list_row.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<org.mozilla.gecko.home.PanelListRow xmlns:android="http://schemas.android.com/apk/res/android"
-                                     android:layout_width="fill_parent"
-                                     android:layout_height="@dimen/page_row_height"
-                                     android:paddingLeft="10dip"
-                                     android:minHeight="@dimen/page_row_height"/>
--- a/mobile/android/base/resources/layout/web_app.xml
+++ b/mobile/android/base/resources/layout/web_app.xml
@@ -48,23 +48,13 @@
                          android:layout_alignParentBottom="true"
                          android:paddingBottom="30dip"
                          android:visibility="gone"/>
 
         </RelativeLayout>
 
     </RelativeLayout>
 
-    <LinearLayout android:id="@+id/toast"
-                  style="@style/Toast">
-
-        <TextView android:id="@+id/toast_message"
-                  style="@style/ToastMessage" />
-
-        <ImageView android:id="@+id/toast_divider"
-                   style="@style/ToastDivider" />
-
-        <Button android:id="@+id/toast_button"
-                style="@style/ToastButton" />
-
-    </LinearLayout>
+    <ViewStub android:id="@+id/toast_stub"
+              android:layout="@layout/button_toast"
+              style="@style/Toast"/>
 
 </RelativeLayout>
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ b/mobile/android/base/resources/values-v11/themes.xml
@@ -43,17 +43,16 @@
         <item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
         <item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="panelGridViewStyle">@style/Widget.PanelGridView</item>
-        <item name="panelGridItemViewStyle">@style/Widget.PanelGridItemView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
         <item name="android:actionModeStyle">@style/GeckoActionBar</item>
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
         <item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
--- a/mobile/android/base/resources/values-v16/styles.xml
+++ b/mobile/android/base/resources/values-v16/styles.xml
@@ -4,17 +4,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="TextAppearance.EmptyMessage" parent="TextAppearance.Large">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
-    <style name="TextAppearance.Widget.TwoLineRow.Title" parent="TextAppearance.Medium">
+    <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.PageTitle" parent="TextAppearance.Medium">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
 </resources>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -36,19 +36,16 @@
         <attr name="topSitesGridItemViewStyle" format="reference" />
 
         <!-- Default style for the HomeGridView -->
         <attr name="homeGridViewStyle" format="reference" />
 
         <!-- Style for the PanelGridView -->
         <attr name="panelGridViewStyle" format="reference" />
 
-        <!-- Default style for the PanelGridItemView -->
-        <attr name="panelGridItemViewStyle" format="reference" />
-
         <!-- Default style for the TopSitesGridView -->
         <attr name="topSitesGridViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesThumbnailView -->
         <attr name="topSitesThumbnailViewStyle" format="reference" />
 
         <!-- Default style for the HomeListView -->
         <attr name="homeListViewStyle" format="reference" />
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -85,11 +85,11 @@
   <color name="url_bar_urltext">#A6A6A6</color>
   <color name="url_bar_domaintext">#000</color>
   <color name="url_bar_domaintext_private">#FFF</color>
   <color name="url_bar_blockedtext">#b14646</color>
   <color name="url_bar_shadow">#12000000</color>
 
   <color name="home_last_tab_bar_bg">#FFF5F7F9</color>
 
-  <color name="panel_grid_item_image_background">#D1D9E1</color>
+  <color name="panel_image_item_background">#D1D9E1</color>
 </resources>
 
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -107,34 +107,49 @@
         <item name="android:textAppearance">@style/TextAppearance</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">middle</item>
     </style>
 
     <style name="Widget.TwoLineRow" />
 
     <style name="Widget.TwoLineRow.Title">
-        <item name="android:textAppearance">@style/TextAppearance.Widget.TwoLineRow.Title</item>
+        <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">none</item>
     </style>
 
     <style name="Widget.TwoLineRow.Description">
-        <item name="android:textAppearance">@style/TextAppearance.Widget.TwoLineRow.Description</item>
+        <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
         <item name="android:includeFontPadding">false</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">middle</item>
     </style>
 
     <style name="Widget.BookmarkFolderView" parent="Widget.TwoLineRow.Title">
         <item name="android:paddingLeft">10dip</item>
         <item name="android:drawablePadding">10dip</item>
         <item name="android:drawableLeft">@drawable/bookmark_folder</item>
     </style>
 
+    <style name="Widget.PanelItemView" />
+
+    <style name="Widget.PanelItemView.Title">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.PanelItemView.Description">
+        <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
     <style name="Widget.HomeGridView" parent="Widget.GridView">
         <item name="android:padding">7dp</item>
         <item name="android:horizontalSpacing">0dp</item>
         <item name="android:verticalSpacing">7dp</item>
     </style>
 
     <style name="Widget.TopSitesGridView" parent="Widget.HomeGridView" />
 
@@ -151,29 +166,16 @@
         <item name="android:paddingTop">0dp</item>
         <item name="android:stretchMode">columnWidth</item>
         <item name="android:numColumns">auto_fit</item>
         <item name="android:columnWidth">@dimen/panel_grid_view_column_width</item>
         <item name="android:horizontalSpacing">2dp</item>
         <item name="android:verticalSpacing">2dp</item>
     </style>
 
-    <style name="Widget.PanelGridItemView">
-      <item name="android:layout_width">fill_parent</item>
-      <item name="android:layout_height">wrap_content</item>
-    </style>
-
-    <style name="Widget.PanelGridItemImageView">
-      <item name="android:layout_width">fill_parent</item>
-      <item name="android:layout_height">wrap_content</item>
-      <item name="android:scaleType">centerCrop</item>
-      <item name="android:adjustViewBounds">true</item>
-      <item name="android:background">@color/panel_grid_item_image_background</item>
-    </style>
-
     <style name="Widget.BookmarkItemView" parent="Widget.TwoLineRow"/>
 
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
 
     <style name="Widget.TopSitesThumbnailView">
       <item name="android:padding">0dip</item>
       <item name="android:scaleType">centerCrop</item>
     </style>
@@ -338,40 +340,38 @@
         <item name="android:textColor">?android:attr/textColorHint</item>
     </style>
 
     <style name="TextAppearance.Widget.HomePagerTabMenuStrip" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorHint</item>
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="TextAppearance.Widget.TwoLineRow" />
-
-    <style name="TextAppearance.Widget.TwoLineRow.Title" parent="TextAppearance.Medium"/>
-
-    <style name="TextAppearance.Widget.TwoLineRow.Description" parent="TextAppearance.Micro">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
-    </style>
-
     <style name="TextAppearance.Widget.SuggestionsPrompt" parent="TextAppearance.Small">
         <item name="android:textColor">@color/url_bar_title</item>
     </style>
 
     <style name="TextAppearance.Widget.Home" />
 
     <style name="TextAppearance.Widget.Home.Header" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.PageTitle" parent="TextAppearance.Medium" />
 
     <style name="TextAppearance.Widget.Home.PageAction" parent="TextAppearance.Small">
         <item name="android:textColor">#00ACFF</item>
     </style>
 
+    <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium"/>
+
+    <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
     <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorHint</item>
     </style>
 
     <!-- BrowserToolbar -->
     <style name="BrowserToolbar">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">@dimen/browser_toolbar_height</item>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -76,17 +76,16 @@
         <item name="android:editTextStyle">@style/Widget.EditText</item>
         <item name="android:gridViewStyle">@style/Widget.GridView</item>
         <item name="android:textViewStyle">@style/Widget.TextView</item>
         <item name="android:spinnerStyle">@style/Widget.Spinner</item>
         <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="panelGridViewStyle">@style/Widget.PanelGridView</item>
-        <item name="panelGridItemViewStyle">@style/Widget.PanelGridItemView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
     </style>
 
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -72,16 +72,17 @@ skip-if = processor == "x86"
 [testSystemPages]
 # disabled on x86 only; bug 907383
 skip-if = processor == "x86"
 # [testThumbnails] # see bug 813107
 [testTitleBar]
 # [testVkbOverlap] # see bug 907274
 
 # Using JavascriptTest
+[testAccounts]
 [testBrowserDiscovery]
 [testDeviceSearchEngine]
 [testJNI]
 # [testMozPay] # see bug 945675
 [testOrderedBroadcast]
 [testSharedPreferences]
 [testSimpleDiscovery]
 [testUITelemetry]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAccounts.java
@@ -0,0 +1,28 @@
+/* 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/. */
+
+package org.mozilla.gecko.tests;
+
+import org.mozilla.gecko.*;
+import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+
+public class testAccounts extends JavascriptTest {
+    public testAccounts() {
+        super("testAccounts.js");
+    }
+
+    @Override
+    public void testJavascript() throws Exception {
+        super.testJavascript();
+
+        // Rather than waiting for the JS call to message
+        // Java and wait for the Activity to launch, we just
+        // don't test these.
+        /*
+        android.app.Activity activity = mSolo.getCurrentActivity();
+        System.out.println("Current activity: " + activity);
+        mAsserter.ok(activity instanceof FxAccountGetStartedActivity, "checking activity", "setup activity launched");
+        */
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAccounts.js
@@ -0,0 +1,24 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Accounts.jsm");
+
+add_task(function test_Accounts() {
+  let syncExists = yield Accounts.syncAccountsExist();
+  dump("Sync account exists? " + syncExists + "\n");
+  let firefoxExists = yield Accounts.firefoxAccountsExist();
+  dump("Firefox account exists? " + firefoxExists + "\n");
+  let anyExists = yield Accounts.anySyncAccountsExist();
+  dump("Any accounts exist? " + anyExists + "\n");
+
+  // Only one account should exist.
+  do_check_true(!syncExists || !firefoxExists);
+  do_check_eq(anyExists, firefoxExists || syncExists);
+
+  dump("Launching setup.\n");
+  Accounts.launchSetup();
+});
+
+run_next_test();
--- a/mobile/android/components/Snippets.js
+++ b/mobile/android/components/Snippets.js
@@ -1,14 +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/. */
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/Accounts.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Home", "resource://gre/modules/Home.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gEncoder", function() { return new gChromeWin.TextEncoder(); });
@@ -287,35 +288,39 @@ function _httpGetRequest(url, callback) 
     if (callback) {
       callback(xhr.responseText);
     }
   }
   xhr.send(null);
 }
 
 function loadSyncPromoBanner() {
-  // XXX: Use Accounts.jsm to check if a sync account exists (bug 917942).
-  let syncAccountExists = false;
-  if (syncAccountExists) {
-    // Don't show the promo banner if a sync account already exists.
-    return;
-  }
+  Accounts.anySyncAccountsExist().then(
+    (exist) => {
+      // Don't show the banner if sync accounts exist.
+      if (exist) {
+        return;
+      }
+
+      let stringBundle = Services.strings.createBundle("chrome://browser/locale/sync.properties");
+      let text = stringBundle.GetStringFromName("promoBanner.message.text");
+      let link = stringBundle.GetStringFromName("promoBanner.message.link");
 
-  let stringBundle = Services.strings.createBundle("chrome://browser/locale/sync.properties");
-  let text = stringBundle.GetStringFromName("promoBanner.message.text");
-  let link = stringBundle.GetStringFromName("promoBanner.message.link");
-
-  Home.banner.add({
-    text: text + "<a href=\"#\">" + link + "</a>",
-    icon: "drawable://sync_promo",
-    onclick: function() {
-      // XXX: Use Accounts.jsm to launch sync set-up activity (bug 917942).
-      gChromeWin.alert("Launch sync set-up activity!");
+      Home.banner.add({
+        text: text + "<a href=\"#\">" + link + "</a>",
+        icon: "drawable://sync_promo",
+        onclick: function() {
+          Accounts.launchSetup();
+        }
+      });
+    },
+    (err) => {
+      Cu.reportError("Error checking whether sync account exists: " + err);
     }
-  });
+  );
 }
 
 function Snippets() {}
 
 Snippets.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsITimerCallback]),
   classID: Components.ID("{a78d7e59-b558-4321-a3d6-dffe2f1e76dd}"),
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -283,18 +283,16 @@
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
 @BINPATH@/components/NotificationStorage.js
 @BINPATH@/components/NotificationStorage.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
-@BINPATH@/components/SettingsService.js
-@BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/BrowserFeeds.manifest
 @BINPATH@/components/FeedConverter.js
 @BINPATH@/components/FeedWriter.js
 @BINPATH@/components/PermissionSettings.js
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/Accounts.jsm
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Accounts"];
+
+const { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+/**
+ * A promise-based API for querying the existence of Sync accounts,
+ * and accessing the Sync setup wizard.
+ *
+ * Usage:
+ *
+ *   Cu.import("resource://gre/modules/Accounts.jsm");
+ *   Accounts.anySyncAccountsExist().then(
+ *     (exist) => {
+ *       console.log("Accounts exist? " + exist);
+ *       if (!exist) {
+ *         Accounts.launchSetup();
+ *       }
+ *     },
+ *     (err) => {
+ *       console.log("We failed so hard.");
+ *     }
+ *   );
+ */
+let Accounts = Object.freeze({
+  _accountsExist: function (kind) {
+    let deferred = Promise.defer();
+
+    sendMessageToJava({
+      type: "Accounts:Exist",
+      kind: kind,
+    }, (data, error) => {
+      if (error) {
+        deferred.reject(error);
+      } else {
+        deferred.resolve(JSON.parse(data).exists);
+      }
+    });
+
+    return deferred.promise;
+  },
+
+  firefoxAccountsExist: function () {
+    return this._accountsExist("fxa");
+  },
+
+  syncAccountsExist: function () {
+    return this._accountsExist("sync11");
+  },
+
+  anySyncAccountsExist: function () {
+    return this._accountsExist("any");
+  },
+
+  /**
+   * Fire-and-forget: open the Firefox accounts activity, which
+   * will be the Getting Started screen if FxA isn't yet set up.
+   *
+   * There is no return value from this method.
+   */
+  launchSetup: function () {
+    sendMessageToJava({
+      type: "Accounts:Create",
+    });
+  },
+});
+
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -223,16 +223,22 @@ let HomePanels = (function () {
     }),
 
     // Valid actions for a panel.
     Action: Object.freeze({
       INSTALL: "install",
       REFRESH: "refresh"
     }),
 
+    // Valid item types for a panel view.
+    Item: Object.freeze({
+      ARTICLE: "article",
+      IMAGE: "image"
+    }),
+
     // Valid item handlers for a panel view.
     ItemHandler: Object.freeze({
       BROWSER: "browser",
       INTENT: "intent"
     }),
 
     add: function(options) {
       let panel = new Panel(options);
@@ -252,16 +258,28 @@ let HomePanels = (function () {
         throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
       }
 
       for (let view of panel.views) {
         if (!_valueExists(this.View, view.type)) {
           throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
         }
 
+        if (!view.itemType) {
+          if (view.type == this.View.LIST) {
+            // Use ARTICLE item type by default in LIST views
+            view.itemType = this.Item.ARTICLE;
+          } else if (view.type == this.View.GRID) {
+            // Use IMAGE item type by default in GRID views
+            view.itemType = this.Item.IMAGE;
+          }
+        } else if (!_valueExists(this.Item, view.itemType)) {
+          throw "Home.panels: Invalid item type: panel.id = " + panel.id + ", view.itemType = " + view.itemType;
+        }
+
         if (!view.itemHandler) {
           // Use BROWSER item handler by default
           view.itemHandler = this.ItemHandler.BROWSER;
         } else if (!_valueExists(this.ItemHandler, view.itemHandler)) {
           throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
         }
 
         if (!view.dataset) {
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_JS_MODULES += [
+    'Accounts.jsm',
     'ContactService.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
     'JNI.jsm',
     'LightweightThemeConsumer.jsm',
     'Messaging.jsm',
     'Notifications.jsm',
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -168,16 +168,18 @@ FxAccountsInternal.prototype = {
    *        The promise resolves to the credentials object of the signed-in user:
    *        {
    *          email: The user's email address
    *          uid: The user's unique id
    *          sessionToken: Session for the FxA server
    *          kA: An encryption key from the FxA server
    *          kB: An encryption key derived from the user's FxA password
    *          verified: email verification status
+   *          authAt: The time (seconds since epoch) that this record was
+   *                  authenticated
    *        }
    *        or null if no user is signed in.
    */
   getSignedInUser: function getSignedInUser() {
     return this.getUserAccountData().then(data => {
       if (!data) {
         return null;
       }
@@ -198,16 +200,18 @@ FxAccountsInternal.prototype = {
    *        The credentials object obtained by logging in or creating
    *        an account on the FxA server:
    *        {
    *          email: The users email address
    *          uid: The user's unique id
    *          sessionToken: Session for the FxA server
    *          keyFetchToken: an unused keyFetchToken
    *          verified: true/false
+   *          authAt: The time (seconds since epoch) that this record was
+   *                  authenticated
    *        }
    * @return Promise
    *         The promise resolves to null when the data is saved
    *         successfully and is rejected on error.
    */
   setSignedInUser: function setSignedInUser(credentials) {
     log.debug("setSignedInUser - aborting any existing flows");
     this.abortExistingFlow();
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -41,16 +41,20 @@ this.KEY_LIFETIME       = 1000 * 3600 * 
 this.POLL_SESSION       = 1000 * 60 * 5;    // 5 minutes
 this.POLL_STEP          = 1000 * 3;         // 3 seconds
 
 // Observer notifications.
 this.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
 this.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
 this.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
 
+// UI Requests.
+this.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
+this.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
+
 // Server errno.
 // From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
 this.ERRNO_ACCOUNT_ALREADY_EXISTS     = 101;
 this.ERRNO_ACCOUNT_DOES_NOT_EXIST     = 102;
 this.ERRNO_INCORRECT_PASSWORD         = 103;
 this.ERRNO_UNVERIFIED_ACCOUNT         = 104;
 this.ERRNO_INVALID_VERIFICATION_CODE  = 105;
 this.ERRNO_NOT_VALID_JSON_BODY        = 106;
@@ -73,28 +77,30 @@ this.ERROR_ALREADY_SIGNED_IN_USER     = 
 this.ERROR_INVALID_ACCOUNTID          = "INVALID_ACCOUNTID";
 this.ERROR_INVALID_AUDIENCE           = "INVALID_AUDIENCE";
 this.ERROR_INVALID_AUTH_TOKEN         = "INVALID_AUTH_TOKEN";
 this.ERROR_INVALID_AUTH_TIMESTAMP     = "INVALID_AUTH_TIMESTAMP";
 this.ERROR_INVALID_AUTH_NONCE         = "INVALID_AUTH_NONCE";
 this.ERROR_INVALID_BODY_PARAMETERS    = "INVALID_BODY_PARAMETERS";
 this.ERROR_INVALID_PASSWORD           = "INVALID_PASSWORD";
 this.ERROR_INVALID_VERIFICATION_CODE  = "INVALID_VERIFICATION_CODE";
+this.ERROR_INVALID_REFRESH_AUTH_VALUE = "INVALID_REFRESH_AUTH_VALUE";
 this.ERROR_INVALID_REQUEST_SIGNATURE  = "INVALID_REQUEST_SIGNATURE";
 this.ERROR_INTERNAL_INVALID_USER      = "INTERNAL_ERROR_INVALID_USER";
 this.ERROR_MISSING_BODY_PARAMETERS    = "MISSING_BODY_PARAMETERS";
 this.ERROR_MISSING_CONTENT_LENGTH     = "MISSING_CONTENT_LENGTH";
 this.ERROR_NO_TOKEN_SESSION           = "NO_TOKEN_SESSION";
 this.ERROR_NOT_VALID_JSON_BODY        = "NOT_VALID_JSON_BODY";
 this.ERROR_OFFLINE                    = "OFFLINE";
 this.ERROR_REQUEST_BODY_TOO_LARGE     = "REQUEST_BODY_TOO_LARGE";
 this.ERROR_SERVER_ERROR               = "SERVER_ERROR";
 this.ERROR_TOO_MANY_CLIENT_REQUESTS   = "TOO_MANY_CLIENT_REQUESTS";
 this.ERROR_SERVICE_TEMP_UNAVAILABLE   = "SERVICE_TEMPORARY_UNAVAILABLE";
 this.ERROR_UI_ERROR                   = "UI_ERROR";
+this.ERROR_UI_REQUEST                 = "UI_REQUEST";
 this.ERROR_UNKNOWN                    = "UNKNOWN_ERROR";
 this.ERROR_UNVERIFIED_ACCOUNT         = "UNVERIFIED_ACCOUNT";
 
 // Error matching.
 this.SERVER_ERRNO_TO_ERROR = {};
 SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS]     = ERROR_ACCOUNT_ALREADY_EXISTS;
 SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXIST]     = ERROR_ACCOUNT_DOES_NOT_EXIST;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD]         = ERROR_INVALID_PASSWORD;
--- a/services/fxaccounts/FxAccountsManager.jsm
+++ b/services/fxaccounts/FxAccountsManager.jsm
@@ -54,96 +54,82 @@ this.FxAccountsManager = {
     }
 
     return {
       accountId: this._activeSession.email,
       verified: this._activeSession.verified
     }
   },
 
+  _error: function(aError, aDetails) {
+    log.error(aError);
+    let reason = {
+      error: aError
+    };
+    if (aDetails) {
+      reason.details = aDetails;
+    }
+    return Promise.reject(reason);
+  },
+
   _getError: function(aServerResponse) {
     if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
       return;
     }
     let error = SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
-    log.error(error);
     return error;
   },
 
   _serverError: function(aServerResponse) {
     let error = this._getError({ error: aServerResponse });
-    return Promise.reject({
-      error: error ? error : ERROR_SERVER_ERROR,
-      details: aServerResponse
-    });
+    return this._error(error ? error : ERROR_SERVER_ERROR, aServerResponse);
   },
 
   // As we do with _fxAccounts, we don't really need this factory, but this way
   // we allow tests to mock FxAccountsClient.
   _createFxAccountsClient: function() {
     return new FxAccountsClient();
   },
 
   _signInSignUp: function(aMethod, aAccountId, aPassword) {
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     if (!aAccountId) {
-      log.error(ERROR_INVALID_ACCOUNTID);
-      return Promise.reject({
-        error: ERROR_INVALID_ACCOUNTID
-      });
+      return this._error(ERROR_INVALID_ACCOUNTID);
     }
 
     if (!aPassword) {
-      log.error(ERROR_INVALID_PASSWORD);
-      return Promise.reject({
-        error: ERROR_INVALID_PASSWORD
-      });
+      return this._error(ERROR_INVALID_PASSWORD);
     }
 
     // Check that there is no signed in account first.
     if (this._activeSession) {
-      log.error(ERROR_ALREADY_SIGNED_IN_USER);
-      return Promise.reject({
-        error: ERROR_ALREADY_SIGNED_IN_USER,
-        details: {
-          user: this._user
-        }
+      return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
+        user: this._user
       });
     }
 
     let client = this._createFxAccountsClient();
     return this._fxAccounts.getSignedInUser().then(
       user => {
         if (user) {
-          log.error(ERROR_ALREADY_SIGNED_IN_USER);
-          return Promise.reject({
-            error: ERROR_ALREADY_SIGNED_IN_USER,
-            details: {
-              user: user
-            }
+          return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
+            user: this._user
           });
         }
         return client[aMethod](aAccountId, aPassword);
       }
     ).then(
       user => {
         let error = this._getError(user);
         if (!user || !user.uid || !user.sessionToken || error) {
-          log.error(error ? error : ERROR_INTERNAL_INVALID_USER);
-          return Promise.reject({
-            error: error ? error : ERROR_INTERNAL_INVALID_USER,
-            details: {
-              user: user
-            }
+          return this._error(error ? error : ERROR_INTERNAL_INVALID_USER, {
+            user: user
           });
         }
 
         // Save the credentials of the signed in user.
         user.email = aAccountId;
         return this._fxAccounts.setSignedInUser(user, false).then(
           () => {
             this._activeSession = user;
@@ -185,32 +171,58 @@ this.FxAccountsManager = {
           return Promise.resolve();
         }
         // Otherwise, we try to remove the remote session.
         let client = this._createFxAccountsClient();
         return client.signOut(sessionToken).then(
           result => {
             let error = this._getError(result);
             if (error) {
-              return Promise.reject({
-                error: error,
-                details: result
-              });
+              return this._error(error, result);
             }
             log.debug("Signed out");
             return Promise.resolve();
           },
           reason => {
             return this._serverError(reason);
           }
         );
       }
     );
   },
 
+  _uiRequest: function(aRequest, aAudience, aParams) {
+    let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
+               .createInstance(Ci.nsIFxAccountsUIGlue);
+    if (!ui[aRequest]) {
+      return this._error(ERROR_UI_REQUEST);
+    }
+
+    if (!aParams || !Array.isArray(aParams)) {
+      aParams = [aParams];
+    }
+
+    return ui[aRequest].apply(this, aParams).then(
+      result => {
+        // Even if we get a successful result from the UI, the account will
+        // most likely be unverified, so we cannot get an assertion.
+        if (result && result.verified) {
+          return this._getAssertion(aAudience);
+        }
+
+        return this._error(ERROR_UNVERIFIED_ACCOUNT, {
+          user: result
+        });
+      },
+      error => {
+        return this._error(ERROR_UI_ERROR, error);
+      }
+    );
+  },
+
   // -- API --
 
   signIn: function(aAccountId, aPassword) {
     return this._signInSignUp("signIn", aAccountId, aPassword);
   },
 
   signUp: function(aAccountId, aPassword) {
     return this._signInSignUp("signUp", aAccountId, aPassword);
@@ -267,83 +279,65 @@ this.FxAccountsManager = {
         return Promise.resolve(this._user);
       }
     );
   },
 
   queryAccount: function(aAccountId) {
     log.debug("queryAccount " + aAccountId);
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     let deferred = Promise.defer();
 
     if (!aAccountId) {
-      log.error(ERROR_INVALID_ACCOUNTID);
-      return Promise.reject({
-        error: ERROR_INVALID_ACCOUNTID
-      });
+      return this._error(ERROR_INVALID_ACCOUNTID);
     }
 
     let client = this._createFxAccountsClient();
     return client.accountExists(aAccountId).then(
       result => {
         log.debug("Account " + result ? "" : "does not" + " exists");
         let error = this._getError(result);
         if (error) {
-          return Promise.reject({
-            error: error,
-            details: result
-          });
+          return this._error(error, result);
         }
 
         return Promise.resolve({
           registered: result
         });
       },
       reason => { this._serverError(reason); }
     );
   },
 
   verificationStatus: function() {
     log.debug("verificationStatus");
     if (!this._activeSession || !this._activeSession.sessionToken) {
-      log.error(ERROR_NO_TOKEN_SESSION);
-      return Promise.reject({
-        error: ERROR_NO_TOKEN_SESSION
-      });
+      return this._error(ERROR_NO_TOKEN_SESSION);
     }
 
     // There is no way to unverify an already verified account, so we just
     // return the account details of a verified account
     if (this._activeSession.verified) {
       log.debug("Account already verified");
       return Promise.resolve(this._user);
     }
 
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     let client = this._createFxAccountsClient();
     return client.recoveryEmailStatus(this._activeSession.sessionToken).then(
       data => {
         let error = this._getError(data);
         if (error) {
-          return Promise.reject({
-            error: error,
-            details: data
-          });
+          return this._error(error, data);
         }
 
         // If the verification status is different from the one that we have
         // stored, we update it and return the session data. If not, we simply
         // return the session data.
         if (this._activeSession.verified != data.verified) {
           this._activeSession.verified = data.verified;
           return this._fxAccounts.setSignedInUser(this._activeSession).then(
@@ -355,76 +349,63 @@ this.FxAccountsManager = {
         }
         log.debug(JSON.stringify(this._user));
         return Promise.resolve(this._user);
       },
       reason => { return this._serverError(reason); }
     );
   },
 
-  getAssertion: function(aAudience) {
-    log.debug("getAssertion " + aAudience);
+  getAssertion: function(aAudience, aOptions) {
+    log.debug("getAssertion " + aAudience + JSON.stringify(aOptions));
     if (!aAudience) {
-      log.error(ERROR_INVALID_AUDIENCE);
-      return Promise.reject({
-        error: ERROR_INVALID_AUDIENCE
-      });
+      return this._error(ERROR_INVALID_AUDIENCE);
     }
 
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     return this.getAccount().then(
       user => {
         if (user) {
           // We cannot get assertions for unverified accounts.
-          if (user.verified) {
-            return this._getAssertion(aAudience);
+          if (!user.verified) {
+            return this._error(ERROR_UNVERIFIED_ACCOUNT, {
+              user: user
+            });
           }
 
-          log.error(ERROR_UNVERIFIED_ACCOUNT);
-          return Promise.reject({
-            error: ERROR_UNVERIFIED_ACCOUNT,
-            details: {
-              user: user
+          // RPs might require an authentication refresh.
+          if (aOptions &&
+              aOptions.refreshAuthentication) {
+            let gracePeriod = aOptions.refreshAuthentication;
+            if (typeof gracePeriod != 'number' || isNaN(gracePeriod)) {
+              return this._error(ERROR_INVALID_REFRESH_AUTH_VALUE);
             }
-          });
+
+            if ((Date.now() / 1000) - this._activeSession.authAt > gracePeriod) {
+              // Grace period expired, so we sign out and request the user to
+              // authenticate herself again. If the authentication succeeds, we
+              // will return the assertion. Otherwise, we will return an error.
+              return this._signOut().then(
+                () => {
+                  return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
+                                         aAudience, user.accountId);
+                }
+              );
+            }
+          }
+
+          return this._getAssertion(aAudience);
         }
 
         log.debug("No signed in user");
         // If there is no currently signed in user, we trigger the signIn UI
         // flow.
-        let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
-                   .createInstance(Ci.nsIFxAccountsUIGlue);
-        return ui.signInFlow().then(
-          result => {
-            // Even if we get a successful result from the UI, the account will
-            // most likely be unverified, so we cannot get an assertion.
-            if (result && result.verified) {
-              return this._getAssertion(aAudience);
-            }
-
-            log.error(ERROR_UNVERIFIED_ACCOUNT);
-            return Promise.reject({
-              error: ERROR_UNVERIFIED_ACCOUNT,
-              details: {
-                user: result
-              }
-            });
-          },
-          error => {
-            log.error(ERROR_UI_ERROR + " " + error);
-            return Promise.reject({
-              error: ERROR_UI_ERROR,
-              details: error
-            });
-          }
-        );
+        return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);
       }
     );
   }
+
 };
 
 FxAccountsManager.init();
--- a/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
+++ b/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
@@ -1,12 +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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(5805ac8b-7cbe-4fbd-97ad-d3ae8cd29f79)]
+[scriptable, uuid(ab8d0700-9577-11e3-a5e2-0800200c9a66)]
 interface nsIFxAccountsUIGlue : nsISupports
 {
   // Returns a Promise.
   jsval signInFlow();
+
+  // Returns a Promise.
+  jsval refreshAuthentication(in DOMString accountId);
 };
--- a/services/fxaccounts/tests/xpcshell/test_manager.js
+++ b/services/fxaccounts/tests/xpcshell/test_manager.js
@@ -30,44 +30,56 @@ let fakeFxAccountsUIGlueFactory = {
 // FxAccountsUIGlue fake component.
 let FxAccountsUIGlue = {
   _reject: false,
 
   _error: 'error',
 
   _signInFlowCalled: false,
 
+  _refreshAuthCalled: false,
+
+  _activeSession: null,
+
   _reset: function() {
     this._reject = false;
     this._error = 'error';
     this._signInFlowCalled = false;
+    this._refreshAuthCalled = false;
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFxAccountsUIGlue]),
 
-  getUserPermission: function() {},
-
-  signInFlow: function() {
-    this._signInFlowCalled = true;
+  _promise: function() {
     let deferred = Promise.defer();
 
     if (this._reject) {
       deferred.reject(this._error);
     } else {
-      FxAccountsManager._activeSession = {
+      FxAccountsManager._activeSession = this._activeSession || {
         email: "user@domain.org",
         verified: false,
         sessionToken: "1234"
       };
       FxAccountsManager._fxAccounts
                        .setSignedInUser(FxAccountsManager._activeSession);
-      deferred.resolve();
+      deferred.resolve(FxAccountsManager._activeSession);
     }
 
     return deferred.promise;
+  },
+
+  signInFlow: function() {
+    this._signInFlowCalled = true;
+    return this._promise();
+  },
+
+  refreshAuthentication: function() {
+    this._refreshAuthCalled = true;
+    return this._promise();
   }
 };
 
 (function registerFakeFxAccountsUIGlue() {
   Cm.QueryInterface(Ci.nsIComponentRegistrar)
     .registerFactory(Components.ID(kFxAccountsUIGlueUUID),
                      "FxAccountsUIGlue",
                      kFxAccountsUIGlueContractID,
@@ -219,132 +231,201 @@ do_register_cleanup(function() {
 
 // === Tests ===
 
 function run_test() {
   run_next_test();
 }
 
 add_test(function test_initial_state() {
-  do_print("= Test 0 | Initial state =");
+  do_print("= Initial state =");
   do_check_neq(FxAccountsManager, undefined);
   do_check_null(FxAccountsManager._activeSession);
   do_check_null(FxAccountsManager._user);
   run_next_test();
 });
 
 add_test(function(test_getAccount_no_session) {
-  do_print("= Test 1 | getAccount no session =");
+  do_print("= getAccount no session =");
   FxAccountsManager.getAccount().then(
     result => {
       do_check_null(result);
       do_check_null(FxAccountsManager._activeSession);
       do_check_null(FxAccountsManager._user);
       do_check_true(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       FxAccountsManager._fxAccounts._reset();
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAssertion_no_audience) {
-  do_print("= Test 2 | getAssertion no audience =");
+  do_print("= getAssertion no audience =");
   FxAccountsManager.getAssertion().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_AUDIENCE);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_no_session_ui_error) {
-  do_print("= Test 3 | getAssertion no session, UI error =");
+  do_print("= getAssertion no session, UI error =");
   FxAccountsUIGlue._reject = true;
   FxAccountsManager.getAssertion("audience").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_UI_ERROR);
       do_check_eq(error.details, "error");
       FxAccountsUIGlue._reset();
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_no_session_ui_success) {
-  do_print("= Test 4 | getAssertion no session, UI success =");
+  do_print("= getAssertion no session, UI success =");
   FxAccountsManager.getAssertion("audience").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_true(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(error.error, ERROR_UNVERIFIED_ACCOUNT);
       FxAccountsUIGlue._reset();
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_active_session_unverified_account) {
-  do_print("= Test 5 | getAssertion active session, unverified account =");
+  do_print("= getAssertion active session, unverified account =");
   FxAccountsManager.getAssertion("audience").then(
     result => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_false(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(error.error, ERROR_UNVERIFIED_ACCOUNT);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_active_session_verified_account) {
-  do_print("= Test 6 | getAssertion active session, verified account =");
+  do_print("= getAssertion active session, verified account =");
   FxAccountsManager._fxAccounts._signedInUser.verified = true;
   FxAccountsManager._activeSession.verified = true;
   FxAccountsManager.getAssertion("audience").then(
     result => {
       do_check_false(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(result, "assertion");
       FxAccountsManager._fxAccounts._reset();
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
+add_test(function(test_getAssertion_refreshAuth) {
+  do_print("= getAssertion refreshAuth =");
+  let gracePeriod = 1200;
+  FxAccountsUIGlue._activeSession = {
+    email: "user@domain.org",
+    verified: true,
+    sessionToken: "1234"
+  };
+  FxAccountsManager._fxAccounts._signedInUser.verified = true;
+  FxAccountsManager._activeSession.verified = true;
+  FxAccountsManager._activeSession.authAt =
+    (Date.now() / 1000) - gracePeriod;
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": gracePeriod
+  }).then(
+    result => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_true(FxAccountsUIGlue._refreshAuthCalled);
+      do_check_eq(result, "assertion");
+      FxAccountsManager._fxAccounts._reset();
+      FxAccountsUIGlue._reset();
+      run_next_test();
+    },
+    error => {
+      do_throw("Unexpected error: " + error);
+    }
+  );
+});
+
+add_test(function(test_getAssertion_refreshAuth_NaN) {
+  do_print("= getAssertion refreshAuth NaN=");
+  let gracePeriod = "NaN";
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": gracePeriod
+  }).then(
+    result => {
+      do_throw("Unexpected success");
+    },
+    error => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_false(FxAccountsUIGlue._refreshAuthCalled);
+      do_check_eq(error.error, ERROR_INVALID_REFRESH_AUTH_VALUE);
+      FxAccountsManager._fxAccounts._reset();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function(test_getAssertion_refresh_auth_no_refresh) {
+  do_print("= getAssertion refreshAuth no refresh =");
+  FxAccountsManager._fxAccounts._signedInUser.verified = true;
+  FxAccountsManager._activeSession.verified = true;
+  FxAccountsManager._activeSession.authAt =
+    (Date.now() / 1000) + 10000;
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": 1
+  }).then(
+    result => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_eq(result, "assertion");
+      FxAccountsManager._fxAccounts._reset();
+      run_next_test();
+    },
+    error => {
+      do_throw("Unexpected error: " + error);
+    }
+  );
+});
+
 add_test(function(test_getAccount_existing_verified_session) {
-  do_print("= Test 7 | getAccount, existing verified session =");
+  do_print("= getAccount, existing verified session =");
   FxAccountsManager.getAccount().then(
     result => {
       do_check_false(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
       do_check_eq(result.verified, FxAccountsManager._user.verified);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAccount_existing_unverified_session_unverified_user) {
-  do_print("= Test 8 | getAccount, existing unverified session, unverified user =");
+  do_print("= getAccount, existing unverified session, unverified user =");
   FxAccountsManager._activeSession.verified = false;
   FxAccountsManager._fxAccounts._signedInUser.verified = false;
   FxAccountsManager.getAccount().then(
     result => {
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_false(result.verified);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
       FakeFxAccountsClient._reset();
@@ -352,17 +433,17 @@ add_test(function(test_getAccount_existi
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAccount_existing_unverified_session_verified_user) {
-  do_print("= Test 8 | getAccount, existing unverified session, verified user =");
+  do_print("= getAccount, existing unverified session, verified user =");
   FxAccountsManager._activeSession.verified = false;
   FxAccountsManager._fxAccounts._signedInUser.verified = false;
   FakeFxAccountsClient._verified = true;
   FxAccountsManager.getAccount().then(
     result => {
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_true(result.verified);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
@@ -371,99 +452,99 @@ add_test(function(test_getAccount_existi
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_signOut) {
-  do_print("= Test 9 | signOut =");
+  do_print("= signOut =");
   do_check_true(FxAccountsManager._activeSession != null);
   FxAccountsManager.signOut().then(
     result => {
       do_check_null(result);
       do_check_null(FxAccountsManager._activeSession);
       do_check_true(FakeFxAccountsClient._signOutCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_verificationStatus_no_token_session) {
-  do_print("= Test 10 | verificationStatus, no token session =");
+  do_print("= verificationStatus, no token session =");
   do_check_null(FxAccountsManager._activeSession);
   FxAccountsManager.verificationStatus().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_NO_TOKEN_SESSION);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp_no_accountId) {
-  do_print("= Test 11 | signUp, no accountId=");
+  do_print("= signUp, no accountId=");
   FxAccountsManager.signUp().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_no_accountId) {
-  do_print("= Test 12 | signIn, no accountId=");
+  do_print("= signIn, no accountId=");
   FxAccountsManager.signIn().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp_no_password) {
-  do_print("= Test 13 | signUp, no accountId=");
+  do_print("= signUp, no accountId=");
   FxAccountsManager.signUp("user@domain.org").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_PASSWORD);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_no_accountId) {
-  do_print("= Test 14 | signIn, no accountId=");
+  do_print("= signIn, no accountId=");
   FxAccountsManager.signIn("user@domain.org").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_PASSWORD);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp) {
-  do_print("= Test 15 | signUp =");
+  do_print("= signUp =");
   FakeFxAccountsClient._verified = false;
   FxAccountsManager.signUp("user@domain.org", "password").then(
     result => {
       do_check_true(FakeFxAccountsClient._signInCalled);
       do_check_true(FakeFxAccountsClient._signUpCalled);
       do_check_true(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       do_check_eq(FxAccountsManager._fxAccounts._signedInUser.email, "user@domain.org");
       do_check_eq(FakeFxAccountsClient._password, "password");
@@ -476,119 +557,119 @@ add_test(function(test_signUp) {
     },
     error => {
       do_throw("Unexpected error: " + error.error);
     }
   );
 });
 
 add_test(function(test_signUp_already_signed_user) {
-  do_print("= Test 16 | signUp, already signed user =");
+  do_print("= signUp, already signed user =");
   FxAccountsManager.signUp("user@domain.org", "password").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_false(FakeFxAccountsClient._signInCalled);
       do_check_eq(error.error, ERROR_ALREADY_SIGNED_IN_USER);
       do_check_eq(error.details.user.accountId, "user@domain.org");
       do_check_false(error.details.user.verified);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_already_signed_user) {
-  do_print("= Test 17 | signIn, already signed user =");
+  do_print("= signIn, already signed user =");
   FxAccountsManager.signIn("user@domain.org", "password").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_ALREADY_SIGNED_IN_USER);
       do_check_eq(error.details.user.accountId, "user@domain.org");
       do_check_false(error.details.user.verified);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_verificationStatus_unverified_session_unverified_user) {
-  do_print("= Test 18 | verificationStatus unverified session and user =");
+  do_print("= verificationStatus unverified session and user =");
   FakeFxAccountsClient._verified = false;
   FxAccountsManager.verificationStatus().then(
     user => {
       do_check_false(user.verified);
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_false(FxAccountsManager._fxAccounts._setSignedInUserCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_verificationStatus_unverified_session_verified_user) {
-  do_print("= Test 19 | verificationStatus unverified session, verified user =");
+  do_print("= verificationStatus unverified session, verified user =");
   FakeFxAccountsClient._verified = true;
   FxAccountsManager.verificationStatus().then(
     user => {
       do_check_true(user.verified);
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_true(FxAccountsManager._fxAccounts._setSignedInUserCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_no_exists) {
-  do_print("= Test 20 | queryAccount, no exists =");
+  do_print("= queryAccount, no exists =");
   FxAccountsManager.queryAccount("user@domain.org").then(
     result => {
       do_check_false(result.registered);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_exists) {
-  do_print("= Test 21 | queryAccount, exists =");
+  do_print("= queryAccount, exists =");
   FakeFxAccountsClient._accountExists = true;
   FxAccountsManager.queryAccount("user@domain.org").then(
     result => {
       do_check_true(result.registered);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_no_accountId) {
-  do_print("= Test 22 | queryAccount, no accountId =");
+  do_print("= queryAccount, no accountId =");
   FxAccountsManager.queryAccount().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function() {
-  do_print("= Test 23 | fxaccounts:onlogout notification =");
+  do_print("= fxaccounts:onlogout notification =");
   do_check_true(FxAccountsManager._activeSession != null);
   Services.obs.notifyObservers(null, ONLOGOUT_NOTIFICATION, null);
   do_execute_soon(function() {
     do_check_null(FxAccountsManager._activeSession);
     run_next_test();
   });
 });
--- a/testing/mochitest/b2g-debug.json
+++ b/testing/mochitest/b2g-debug.json
@@ -7,16 +7,17 @@
     "dom": "",
     "layout": "",
     "toolkit/devtools/apps": ""
   },
 "excludetests": {
     "content/xul":"tests that use xul",
     "layout/xul" : "",
     "dom/apps":"bug 972927, nearly perma-fail",
+    "dom/datastore":"bug 974270, frequent failures",
     "dom/datastore/tests/test_changes.html":"intermittent failures, bug 961021",
     "dom/tests/mochitest/general/test_focusrings.xul":"",
     "layout/base/tests/test_bug465448.xul":"",
 
     "layout/forms/test/test_bug478219.xhtml":"window.closed not working, bug 907795",
     "content/media/test":"bug 918299",
     "content/media/test/test_bug448534.html": "Timed out, bug 894922? Bug 902677 is for the timing out of a lot of media tests",
     "content/media/mediasource/test/test_MediaSource.html": " ReferenceError: MediaSource is not defined",
--- a/testing/mochitest/b2g.json
+++ b/testing/mochitest/b2g.json
@@ -7,16 +7,17 @@
     "dom": "",
     "layout": "",
     "toolkit/devtools/apps": ""
   },
 "excludetests": {
     "content/xul":"tests that use xul",
     "layout/xul" : "",
     "dom/apps":"bug 972927, nearly perma-fail",
+    "dom/datastore":"bug 974270, frequent failures",
     "dom/tests/mochitest/general/test_focusrings.xul":"",
     "layout/base/tests/test_bug465448.xul":"",
 
     "layout/forms/test/test_bug478219.xhtml":"window.closed not working, bug 907795",
     "content/media/test/test_bug448534.html": "Timed out, bug 894922? Bug 902677 is for the timing out of a lot of media tests",
     "content/media/mediasource/test/test_MediaSource.html": " ReferenceError: MediaSource is not defined",
     "content/media/test/test_autoplay_contentEditable.html": "bug 899074 - timeouts",
     "content/media/test/test_bug465498.html":"",
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -255,11 +255,16 @@ this.hasSafeGetter = function hasSafeGet
  *         True if it is safe to read properties from aObj, or false otherwise.
  */
 this.isSafeJSObject = function isSafeJSObject(aObj) {
   if (Cu.getGlobalForObject(aObj) ==
       Cu.getGlobalForObject(isSafeJSObject)) {
     return true; // aObj is not a cross-compartment wrapper.
   }
 
+  let principal = Services.scriptSecurityManager.getObjectPrincipal(aObj);
+  if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
+    return true; // allow chrome objects
+  }
+
   return Cu.isXrayWrapper(aObj);
 };
 
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -3651,17 +3651,17 @@ DebuggerServer.ObjectActorPreviewers.Obj
       let value = aRawObj.getPropertyValue(prop);
       entries.push([prop, threadActor.createValueGrip(value)]);
     }
 
     return true;
   },
 
   function DOMNode({obj, threadActor}, aGrip, aRawObj) {
-    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
+    if (obj.class == "Object" || !aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
       return false;
     }
 
     let preview = aGrip.preview = {
       kind: "DOMNode",
       nodeType: aRawObj.nodeType,
       nodeName: aRawObj.nodeName,
     };
--- a/toolkit/identity/FirefoxAccounts.jsm
+++ b/toolkit/identity/FirefoxAccounts.jsm
@@ -121,17 +121,17 @@ FxAccountsService.prototype = {
       return;
     }
 
     let options = makeMessageObject(rp);
     objectCopy(aOptions, options);
 
     log.debug("get assertion for " + rp.audience);
 
-    this.fxAccountsManager.getAssertion(rp.audience).then(
+    this.fxAccountsManager.getAssertion(rp.audience, options).then(
       data => {
         log.debug("got assertion: " + JSON.stringify(data));
         this.doLogin(aRPId, data);
       },
       error => {
         log.error("get assertion failed: " + JSON.stringify(error));
       }
     );
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -36,16 +36,19 @@ EXTRA_JS_MODULES += [
     'RemoteAddonsParent.jsm',
     'RemoteController.jsm',
     'RemoteFinder.jsm',
     'RemoteSecurityUI.jsm',
     'RemoteWebNavigation.jsm',
     'RemoteWebProgress.jsm',
     'SelectContentHelper.jsm',
     'SelectParentHelper.jsm',
+    'sessionstore/FormData.jsm',
+    'sessionstore/ScrollPosition.jsm',
+    'sessionstore/XPathGenerator.jsm',
     'ShortcutUtils.jsm',
     'Sntp.jsm',
     'SpatialNavigation.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'TelemetryTimestamps.jsm',
     'Timer.jsm',
 ]
rename from browser/components/sessionstore/src/FormData.jsm
rename to toolkit/modules/sessionstore/FormData.jsm
--- a/browser/components/sessionstore/src/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -5,17 +5,17 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormData"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
+Cu.import("resource://gre/modules/XPathGenerator.jsm");
 
 /**
  * Returns whether the given URL very likely has input
  * fields that contain serialized session store data.
  */
 function isRestorationPage(url) {
   return url == "about:sessionrestore" || url == "about:welcomeback";
 }
rename from browser/components/sessionstore/src/ScrollPosition.jsm
rename to toolkit/modules/sessionstore/ScrollPosition.jsm
rename from browser/components/sessionstore/src/XPathGenerator.jsm
rename to toolkit/modules/sessionstore/XPathGenerator.jsm
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -2079,27 +2079,33 @@ var XPIProvider = {
                                                AddonManager.checkCompatibility);
         } catch (e) { }
         this.addAddonsToCrashReporter();
       }
 
       try {
         AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
         for (let id in this.bootstrappedAddons) {
-          let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-          file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
-          let reason = BOOTSTRAP_REASONS.APP_STARTUP;
-          // Eventually set INSTALLED reason when a bootstrap addon
-          // is dropped in profile folder and automatically installed
-          if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
-                          .indexOf(id) !== -1)
-            reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
-          this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
-                                   this.bootstrappedAddons[id].type, file,
-                                   "startup", reason);
+          try {
+            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+            file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
+            let reason = BOOTSTRAP_REASONS.APP_STARTUP;
+            // Eventually set INSTALLED reason when a bootstrap addon
+            // is dropped in profile folder and automatically installed
+            if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
+                            .indexOf(id) !== -1)
+              reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+            this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
+                                     this.bootstrappedAddons[id].type, file,
+                                     "startup", reason);
+          }
+          catch (e) {
+            ERROR("Failed to load bootstrap addon " + id + " from " +
+                  this.bootstrappedAddons[id].descriptor, e);
+          }
         }
         AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
       }
       catch (e) {
         ERROR("bootstrap startup failed", e);
         AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
       }
 
@@ -2156,18 +2162,18 @@ var XPIProvider = {
     this.allAppGlobal = true;
 
     this.inactiveAddonIDs = [];
 
     // If there are pending operations then we must update the list of active
     // add-ons
     if (Prefs.getBoolPref(PREF_PENDING_OPERATIONS, false)) {
       XPIDatabase.updateActiveAddons();
-      XPIDatabase.writeAddonsList();
-      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
+      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+                                 !XPIDatabase.writeAddonsList());
     }
 
     this.installs = null;
     this.installLocations = null;
     this.installLocationsByName = null;
 
     // This is needed to allow xpcshell tests to simulate a restart
     this.extensionsActive = false;
@@ -2239,18 +2245,18 @@ var XPIProvider = {
       // This *must* be modal as it has to block startup.
       var features = "chrome,centerscreen,dialog,titlebar,modal";
       var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
                getService(Ci.nsIWindowWatcher);
       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
     }
 
     // Ensure any changes to the add-ons list are flushed to disk
-    XPIDatabase.writeAddonsList();
-    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
+    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+                               !XPIDatabase.writeAddonsList());
   },
 
   /**
    * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref).
    */
   persistBootstrappedAddons: function XPI_persistBootstrappedAddons() {
     Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                JSON.stringify(this.bootstrappedAddons));
@@ -2415,34 +2421,34 @@ var XPIProvider = {
 
           if (addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
             let targetDir = stagingDir.clone();
             targetDir.append(addon.id);
             try {
               targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
             }
             catch (e) {
-              ERROR("Failed to create staging directory for add-on " + id, e);
+              ERROR("Failed to create staging directory for add-on " + addon.id, e);
               continue;
             }
 
             try {
               extractFiles(stagedXPI, targetDir);
             }
             catch (e) {
-              ERROR("Failed to extract staged XPI for add-on " + id + " in " +
+              ERROR("Failed to extract staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
           else {
             try {
               stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
             }
             catch (e) {
-              ERROR("Failed to move staged XPI for add-on " + id + " in " +
+              ERROR("Failed to move staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
         }
         entries.close();
       }
 
       if (stagedXPIDir.exists()) {
@@ -2450,18 +2456,24 @@ var XPIProvider = {
           recursiveRemove(stagedXPIDir);
         }
         catch (e) {
           // Non-critical, just saves some perf on startup if we clean this up.
           LOG("Error removing XPI staging dir " + stagedXPIDir.path, e);
         }
       }
 
-      if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
+      try {
+        if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
+          return;
+      }
+      catch (e) {
+        WARN("Failed to find staging directory", e);
         return;
+      }
 
       let seenFiles = [];
       // Use a snapshot of the directory contents to avoid possible issues with
       // iterating over a directory while removing files from it (the YAFFS2
       // embedded filesystem has this issue, see bug 772238), and to remove
       // normal files before their resource forks on OSX (see bug 733436).
       let stagingDirEntries = getDirectoryEntries(stagingDir, true);
       for (let stageDirEntry of stagingDirEntries) {
@@ -3582,18 +3594,18 @@ var XPIProvider = {
         }
       }
 
       // If the application crashed before completing any pending operations then
       // we should perform them now.
       if (extensionListChanged || hasPendingChanges) {
         LOG("Updating database with changes to installed add-ons");
         XPIDatabase.updateActiveAddons();
-        XPIDatabase.writeAddonsList();
-        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
+        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+                                   !XPIDatabase.writeAddonsList());
         Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
                                    JSON.stringify(this.bootstrappedAddons));
         return true;
       }
 
       LOG("No changes found");
     }
     catch (e) {
--- a/toolkit/mozapps/extensions/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/XPIProviderUtils.js
@@ -1404,16 +1404,17 @@ this.XPIDatabase = {
         addon.active = newActive;
         this.saveChanges();
       }
     }
   },
 
   /**
    * Writes out the XPI add-ons list for the platform to read.
+   * @return true if the file was successfully updated, false otherwise
    */
   writeAddonsList: function XPIDB_writeAddonsList() {
     if (!this.addonDB) {
       // force the DB to load
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_writeList",
           XPIProvider.runPhase);
       this.syncLoadDB(true);
     }
@@ -1468,27 +1469,41 @@ this.XPIDatabase = {
                            encodeURIComponent(row.version));
       }
       fullCount += count;
     }
 
     if (fullCount > 0) {
       LOG("Writing add-ons list");
 
-      let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
-                                            true);
-      var fos = FileUtils.openFileOutputStream(addonsListTmp);
-      fos.write(text, text.length);
-      fos.close();
-      addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
+      try {
+        let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
+                                              true);
+        var fos = FileUtils.openFileOutputStream(addonsListTmp);
+        fos.write(text, text.length);
+        fos.close();
+        addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
 
-      Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
+        Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
+      }
+      catch (e) {
+        ERROR("Failed to write add-ons list to " + addonsListTmp.parent + "/" +
+              FILE_XPI_ADDONS_LIST, e);
+        return false;
+      }
     }
     else {
       if (addonsList.exists()) {
         LOG("Deleting add-ons list");
-        addonsList.remove(false);
+        try {
+          addonsList.remove(false);
+        }
+        catch (e) {
+          ERROR("Failed to remove " + addonsList.path, e);
+          return false;
+        }
       }
 
       Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
     }
+    return true;
   }
 };
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -7690,69 +7690,91 @@
 
   Pop $1
   Exch $0 ; return elapsed seconds
 !macroend
 
 !ifdef MOZ_METRO
 ; Removes the CEH registration if it's set to our installation directory.
 ; If it's set to some other installation directory, then it should be removed
-; by that installation. 
+; by that installation.
 !macro RemoveDEHRegistrationIfMatchingCall un
+
   Function ${un}RemoveDEHRegistrationIfMatchingCall
-    ; Move the old $R0 on the stack and set it to DEH ID
-    Exch $R0
-    ; Backup the old values of R8 and R7 on the stack
-    Push $R8
-    Push $R7
+    ; Retrieve DEH ID from the stack into $R9
+    Exch $R9
+    Exch 1
+
+    ; Retrieve Protocol Activation ID from stack into $R8
+    Exch $R8
+    Exch 2
+
+    ; Retrieve File Activation ID from stack into $R7
+    Exch $R7
+
+    ; Backup the old values of R6 and R5 on the stack
+    Push $R6
+    Push $R5
 
     ; Conditionally remove the DEH as long as we are the default (HKCU)
-    ReadRegStr $R8 HKCU "Software\Classes\CLSID\$R0\LocalServer32" ""
-    ${${un}GetLongPath} "$INSTDIR" $R7
-    StrCmp "$R8" "" next +1
-    IfFileExists "$R8" +1 clearHKCU
-    ${${un}GetParent} "$R8" $R8
-    ${${un}GetLongPath} "$R8" $R8
-    StrCmp "$R7" "$R8" clearHKCU next
+    ReadRegStr $R6 HKCU "Software\Classes\CLSID\$R9\LocalServer32" ""
+    ${${un}GetLongPath} "$INSTDIR" $R5
+    StrCmp "$R6" "" next +1
+    IfFileExists "$R6" +1 clearHKCU
+    ${${un}GetParent} "$R6" $R6
+    ${${un}GetLongPath} "$R6" $R6
+    StrCmp "$R5" "$R6" clearHKCU next
     clearHKCU:
-    DeleteRegKey HKCU "Software\Classes\CLSID\$R0"
+    DeleteRegKey HKCU "Software\Classes\CLSID\$R9"
+    DeleteRegValue HKCU "Software\Classes\$R8\shell\open\command" "DelegateExecute"
+    DeleteRegValue HKCU "Software\Classes\$R7\shell\open\command" "DelegateExecute"
     next:
 
     ; Conditionally remove the DEH as long as we are the default (HKLM)
-    ReadRegStr $R8 HKLM "Software\Classes\CLSID\$R0\LocalServer32" ""
-    ${${un}GetLongPath} "$INSTDIR" $R7
-    StrCmp "$R8" "" done +1
-    IfFileExists "$R8" +1 clearHKLM
-    ${${un}GetParent} "$R8" $R8
-    ${${un}GetLongPath} "$R8" $R8
-    StrCmp "$R7" "$R8" clearHKLM done
+    ReadRegStr $R6 HKLM "Software\Classes\CLSID\$R9\LocalServer32" ""
+    ${${un}GetLongPath} "$INSTDIR" $R5
+    StrCmp "$R6" "" done +1
+    IfFileExists "$R6" +1 clearHKLM
+    ${${un}GetParent} "$R6" $R6
+    ${${un}GetLongPath} "$R6" $R6
+    StrCmp "$R5" "$R6" clearHKLM done
     clearHKLM:
-    DeleteRegKey HKLM "Software\Classes\CLSID\$R0"
+    DeleteRegKey HKLM "Software\Classes\CLSID\$R9"
+    DeleteRegValue HKLM "Software\Classes\$R8\shell\open\command" "DelegateExecute"
+    DeleteRegValue HKLM "Software\Classes\$R7\shell\open\command" "DelegateExecute"
     done:
 
     ; Always remove the AppUserModelID keys for this installation
     DeleteRegKey HKCU "Software\Classes\$AppUserModelID"
     DeleteRegKey HKLM "Software\Classes\$AppUserModelID"
 
-    ; Restore the stack back to its original state
-    Pop $R7
-    Pop $R8
-    Pop $R0
+    ; Restore the registers back to their original state
+    Pop $R5
+    Pop $R6
+    Exch $R7
+    Exch 2
+    Exch $R8
+    Exch 1
+    Exch $R9
   FunctionEnd
 !macroend
 
 !macro RemoveDEHRegistrationIfMatching
   !insertmacro RemoveDEHRegistrationIfMatchingCall ""
 !macroend
 
 !macro un.RemoveDEHRegistrationIfMatching
   !insertmacro RemoveDEHRegistrationIfMatchingCall "un."
 !macroend
 
-!macro CleanupMetroBrowserHandlerValues un DELEGATE_EXECUTE_HANDLER_ID
+!macro CleanupMetroBrowserHandlerValues un DELEGATE_EXECUTE_HANDLER_ID \
+                                           PROTOCOL_ACTIVATION_ID \
+                                           FILE_ACTIVATION_ID
+  Push ${FILE_ACTIVATION_ID}
+  Push ${PROTOCOL_ACTIVATION_ID}
   Push ${DELEGATE_EXECUTE_HANDLER_ID}
   Call ${un}RemoveDEHRegistrationIfMatchingCall
 !macroend
 !define CleanupMetroBrowserHandlerValues '!insertmacro CleanupMetroBrowserHandlerValues ""'
 !define un.CleanupMetroBrowserHandlerValues '!insertmacro CleanupMetroBrowserHandlerValues "un."'
 
 !macro AddMetroBrowserHandlerValues DELEGATE_EXECUTE_HANDLER_ID \
                                     DELEGATE_EXECUTE_HANDLER_PATH \