Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 19 Feb 2014 15:35:01 -0500
changeset 169942 8497ffecbacd85e047c8a8d0e525844f5f1c9594
parent 169921 968cda7c0e9550c55bd5f9db69cc326d34621423 (current diff)
parent 169941 47fddf9651fd5bbefa43e1fce0a24fbe30107a88 (diff)
child 169943 918802901b62670754197c4c6895359dd04af012
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge fx-team to m-c.
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
--- 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/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/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}"),
 
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/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/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 \