Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Tue, 24 Sep 2013 20:06:37 -0700
changeset 162361 68d279364a8b985754e847828eacd0e361f89aeb
parent 162336 c65a4d588933af505d60b5c2e4121ec350272d6e (current diff)
parent 162360 4e9ccedf993c2c27c0564d829fee0b29155a70fe (diff)
child 162362 681a8e611edeb5ab9a613ca5539a2f4410c74f5e
child 162372 fa526250d6ef960f9b44533f1401c024d04a8109
child 162404 e3be443ea09325e1655a7a9a9fd2e360c31b56d6
child 162448 6095527a89147d6cd98a2dbe9d0d253220520ac6
child 170357 d6406f5ecc5d573a9f87341aba12f44d9f5c73b7
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
68d279364a8b / 27.0a1 / 20130925030203 / files
nightly linux64
68d279364a8b / 27.0a1 / 20130925030203 / files
nightly mac
68d279364a8b / 27.0a1 / 20130925030203 / files
nightly win32
68d279364a8b / 27.0a1 / 20130925030203 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Merge fx-team to m-c
browser/devtools/inspector/inspector-panel.js
browser/metro/profile/metro.js
mobile/android/base/resources/drawable-hdpi/ic_menu_close_all_tabs.png
mobile/android/base/resources/drawable-mdpi/ic_menu_close_all_tabs.png
mobile/android/base/resources/drawable-xhdpi/ic_menu_close_all_tabs.png
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4899,27 +4899,26 @@
               this._calcMouseTargetRect();
               break;
           }
         ]]></body>
       </method>
 
       <method name="_calcMouseTargetRect">
         <body><![CDATA[
-          let alignRight = false;
-
-          if (getComputedStyle(document.documentElement).direction == "rtl")
-            alignRight = !alignRight;
-
-          let rect = this.getBoundingClientRect();
+          let container = this.parentNode;
+          let alignRight = (getComputedStyle(container).direction == "rtl");
+          let panelRect = this.getBoundingClientRect();
+          let containerRect = container.getBoundingClientRect();
+
           this._mouseTargetRect = {
-            top:    rect.top,
-            bottom: rect.bottom,
-            left:   alignRight ? window.innerWidth - rect.width : 0,
-            right:  alignRight ? window.innerWidth : rect.width
+            top:    panelRect.top,
+            bottom: panelRect.bottom,
+            left:   alignRight ? containerRect.right - panelRect.width : containerRect.left,
+            right:  alignRight ? containerRect.right : containerRect.left + panelRect.width
           };
         ]]></body>
       </method>
 
       <method name="_mirror">
         <body>
           if (this.hasAttribute("mirror"))
             this.removeAttribute("mirror");
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1122,17 +1122,17 @@ var ViewMenu = {
     }
     event.stopPropagation();
   },
 
   /**
    * Set up the content of the view menu.
    */
   populateSortMenu: function VM_populateSortMenu(event) {
-    this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
+    this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.1.");
 
     var sortColumn = this._getSortColumn();
     var viewSortAscending = document.getElementById("viewSortAscending");
     var viewSortDescending = document.getElementById("viewSortDescending");
     // We need to remove an existing checked attribute because the unsorted
     // menu item is not rebuilt every time we open the menu like the others.
     var viewUnsorted = document.getElementById("viewUnsorted");
     if (!sortColumn) {
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -379,17 +379,17 @@
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
-            <treecol label="&col.lastvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
+            <treecol label="&col.mostrecentvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.keyword.label;" id="placesContentKeyword" anonid="keyword" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -2580,17 +2580,16 @@ let SessionStoreInternal = {
    *        Hash for ensuring unique frame IDs
    * @param aRestoreImmediately
    *        Flag to indicate whether the given set of tabs aTabs should be
    *        restored/loaded immediately even if restore_on_demand = true
    */
   restoreHistory:
     function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
                                 aRestoreImmediately) {
-    var _this = this;
     // if the tab got removed before being completely restored, then skip it
     while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) {
       aTabs.shift();
       aTabData.shift();
     }
     if (aTabs.length == 0) {
       // At this point we're essentially ready for consumers to read/write data
       // via the sessionstore API so we'll send the SSWindowStateReady event.
@@ -2649,19 +2648,21 @@ let SessionStoreInternal = {
       SessionStorage.deserialize(browser.docShell, tabData.storage);
 
     // notify the tabbrowser that the tab chrome has been restored
     var event = aWindow.document.createEvent("Events");
     event.initEvent("SSTabRestoring", true, false);
     tab.dispatchEvent(event);
 
     // Restore the history in the next tab
-    aWindow.setTimeout(function(){
-      _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
-                           aRestoreImmediately);
+    aWindow.setTimeout(() => {
+      if (!aWindow.closed) {
+        this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
+                            aRestoreImmediately);
+      }
     }, 0);
 
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
     if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) {
       this.restoreTab(tab);
     }
     else {
--- a/browser/devtools/app-manager/content/connection-footer.js
+++ b/browser/devtools/app-manager/content/connection-footer.js
@@ -56,16 +56,19 @@ let UI = {
     pre.scrollTop = pre.scrollTopMax;
     this.connection.on(Connection.Events.NEW_LOG, (event, str) => {
       pre.textContent += "\n" + str;
       pre.scrollTop = pre.scrollTopMax;
     });
 
     this.template = new Template(document.body, this.store, Utils.l10n);
     this.template.start();
+
+    this._onSimulatorConnected = this._onSimulatorConnected.bind(this);
+    this._onSimulatorDisconnected = this._onSimulatorDisconnected.bind(this);
   },
 
   useFloatingScrollbarsIfNeeded: function() {
     if (Services.appinfo.OS == "Darwin") {
       return;
     }
     let scrollbarsUrl = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars-light.css", null, null);
     let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
@@ -131,26 +134,35 @@ let UI = {
     this.connection.log("Starting simulator...");
 
     this.simulator = simulator;
     this.simulator.launch({ port: port })
       .then(() => {
         this.connection.log("Simulator ready. Connecting.");
         this.connection.port = port;
         this.connection.host = "localhost";
-        this.connection.once("connected", () => {
-          this.connection.log("Connected to simulator.");
-          this.connection.keepConnecting = false;
-        });
+        this.connection.once("connected",
+                             this._onSimulatorConnected);
+        this.connection.once("disconnected",
+                             this._onSimulatorDisconnected);
         this.connection.keepConnecting = true;
         this.connection.connect();
       });
     document.body.classList.remove("show-simulators");
   },
 
+  _onSimulatorConnected: function() {
+    this.connection.log("Connected to simulator.");
+    this.connection.keepConnecting = false;
+  },
+
+  _onSimulatorDisconnected: function() {
+    this.connection.off("connected", this._onSimulatorConnected);
+  },
+
   connectToAdbDevice: function(name) {
     let device = Devices.getByName(name);
     device.connect().then((port) => {
       this.connection.host = "localhost";
       this.connection.port = port;
       this.connect();
     });
   },
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-03.js
@@ -41,17 +41,17 @@ function checkInitialSource() {
     "The debugger's editor should have the original source displayed, " +
     "not the whitespace stripped minified version.");
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
 
   gDebugger.gThreadClient.interrupt(aResponse => {
-    gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 30, column: 10 }, aResponse => {
+    gDebugger.gThreadClient.setBreakpoint({ url: JS_URL, line: 30, column: 21 }, aResponse => {
       ok(!aResponse.error,
         "Should be able to set a breakpoint in a js file.");
       ok(!aResponse.actualLocation,
         "Should be able to set a breakpoint on line 30 and column 10.");
 
       deferred.resolve();
     });
   });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/devtools-clhandler.js
@@ -0,0 +1,40 @@
+/* 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/XPCOMUtils.jsm");
+
+function devtoolsCommandlineHandler() {
+}
+devtoolsCommandlineHandler.prototype = {
+  handle: function(cmdLine) {
+    if (!cmdLine.handleFlag("jsconsole", false)) {
+      return;
+    }
+
+    Cu.import("resource://gre/modules/Services.jsm");
+    let window = Services.wm.getMostRecentWindow("devtools:webconsole");
+    if (!window) {
+      let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+      // Load the browser devtools main module as the loader's main module.
+      devtools.main("main");
+      let hudservice = devtools.require("devtools/webconsole/hudservice");
+      let console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
+      hudservice.toggleBrowserConsole().then(null, console.error);
+    } else {
+      window.focus(); // the Browser Console was already open
+    }
+
+    if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
+      cmdLine.preventDefault = true;
+    }
+  },
+
+  helpInfo : "  -jsconsole         Open the Browser Console.\n",
+
+  classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([devtoolsCommandlineHandler]);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/devtools-clhandler.manifest
@@ -0,0 +1,2 @@
+component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-clhandler.js
+contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -163,17 +163,17 @@ InspectorPanel.prototype = {
     }
     let walker = this.walker;
     let rootNode = null;
 
     // If available, set either the previously selected node or the body
     // as default selected, else set documentElement
     return walker.getRootNode().then(aRootNode => {
       rootNode = aRootNode;
-      return walker.querySelector(aRootNode, this.selectionCssSelector);
+      return walker.querySelector(rootNode, this.selectionCssSelector);
     }).then(front => {
       if (front) {
         return front;
       }
       return walker.querySelector(rootNode, "body");
     }).then(front => {
       if (front) {
         return front;
--- a/browser/devtools/inspector/test/Makefile.in
+++ b/browser/devtools/inspector/test/Makefile.in
@@ -38,10 +38,12 @@ MOCHITEST_BROWSER_FILES := \
 		browser_inspector_bug_835722_infobar_reappears.js \
 		browser_inspector_bug_840156_destroy_after_navigation.js \
 		browser_inspector_reload.js \
 		browser_inspector_navigation.js \
 		browser_inspector_select_last_selected.js \
 		browser_inspector_select_last_selected.html \
 		browser_inspector_select_last_selected2.html \
 		browser_inspector_basic_highlighter.js \
+    browser_inspector_dead_node_exception.html \
+    browser_inspector_dead_node_exception.js \
 		head.js \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_dead_node_exception.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>iframe creation/deletion test</title>
+</head>
+<body>
+  <div id="yay"></div>
+  <script type="text/javascript">
+      var yay = document.querySelector("#yay");
+      yay.textContent = "nothing";
+
+      var iframe = document.createElement('iframe');
+      document.body.appendChild(iframe);
+      iframe.parentNode.removeChild(iframe);
+
+      yay.textContent = "before events";
+
+      document.addEventListener("DOMContentLoaded", function() {
+        var iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        iframe.parentNode.removeChild(iframe);
+
+        yay.textContent = "DOMContentLoaded";
+      });
+      window.addEventListener("load", function() {
+        var iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        iframe.parentNode.removeChild(iframe);
+
+        yay.textContent = "load";
+      });
+  </script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_dead_node_exception.js
@@ -0,0 +1,56 @@
+/* Any copyright", " is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  let contentTab, contentDoc, inspector;
+
+  waitForExplicitFinish();
+
+  // Create a tab
+  contentTab = gBrowser.selectedTab = gBrowser.addTab();
+
+  // Open the toolbox's inspector first
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    inspector = toolbox.getCurrentPanel();
+
+    inspector.selection.setNode(content.document.querySelector("body"));
+    inspector.once("inspector-updated", () => {
+      is(inspector.selection.node.tagName, "BODY", "Inspector displayed");
+
+      // Then load our test page
+      gBrowser.selectedBrowser.addEventListener("load", function onload() {
+        gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+        contentDoc = content.document;
+
+        // The content doc contains a script that creates an iframe and deletes
+        // it immediately after. This is what used to make the inspector go
+        // blank when navigating to that page.
+
+        var iframe = contentDoc.createElement("iframe");
+        contentDoc.body.appendChild(iframe);
+        iframe.parentNode.removeChild(iframe);
+
+        is(contentDoc.querySelector("iframe"), null, "Iframe has been removed");
+
+        inspector.once("markuploaded", () => {
+          // Assert that the markup-view is displayed and works
+          is(contentDoc.querySelector("iframe"), null, "Iframe has been removed");
+          is(contentDoc.getElementById("yay").textContent, "load", "Load event fired");
+
+          inspector.selection.setNode(contentDoc.getElementById("yay"));
+          inspector.once("inspector-updated", () => {
+            ok(inspector.selection.node, "Inspector still displayed");
+
+            // Clean up and go
+            contentTab, contentDoc, inspector = null;
+            gBrowser.removeTab(contentTab);
+            finish();
+          });
+        });
+      }, true);
+      content.location = "http://mochi.test:8888/browser/browser/devtools/" +
+       "inspector/test/browser_inspector_dead_node_exception.html";
+    });
+  });
+}
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -19,8 +19,13 @@ DIRS += [
     'layoutview',
     'shared',
     'responsivedesign',
     'framework',
     'profiler',
     'fontinspector',
     'app-manager',
 ]
+
+EXTRA_COMPONENTS += [
+    'devtools-clhandler.js',
+    'devtools-clhandler.manifest',
+]
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -249,23 +249,24 @@ HUD_SERVICE.prototype =
         root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
 
         deferred.resolve(win);
       });
 
       return deferred.promise;
     }
 
-    connect().then(getTarget).then(openWindow).then((aWindow) =>
+    connect().then(getTarget).then(openWindow).then((aWindow) => {
       this.openBrowserConsole(target, aWindow, aWindow)
         .then((aBrowserConsole) => {
           this._browserConsoleID = aBrowserConsole.hudId;
           this._browserConsoleDefer.resolve(aBrowserConsole);
           this._browserConsoleDefer = null;
-        }));
+        })
+    }, console.error);
 
     return this._browserConsoleDefer.promise;
   },
 
   /**
    * Get the Browser Console instance, if open.
    *
    * @return object|null
--- a/browser/devtools/webconsole/test/browser_console.js
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -57,17 +57,17 @@ function consoleOpened(hud)
   let xhrRequest = false;
 
   let output = hud.outputNode;
   function performChecks()
   {
     let text = output.textContent;
     chromeConsole = text.indexOf("bug587757a");
     contentConsole = text.indexOf("bug587757b");
-    execValue = text.indexOf("browser.xul");
+    execValue = text.indexOf("webconsole.xul");
     exception = text.indexOf("foobarExceptionBug587757");
     xhrRequest = text.indexOf("test-console.html");
   }
 
   function showResults()
   {
     isnot(chromeConsole, -1, "chrome window console.log() is displayed");
     isnot(contentConsole, -1, "content window console.log() is displayed");
--- a/browser/devtools/webconsole/test/browser_console_dead_objects.js
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -19,17 +19,20 @@ function test()
   }, true);
 
   function onBrowserConsoleOpen(aHud)
   {
     hud = aHud;
     ok(hud, "browser console opened");
 
     hud.jsterm.clearOutput();
-    hud.jsterm.execute("foobarzTezt = content.document", onAddVariable);
+    hud.jsterm.execute("Cu = Components.utils;" +
+                       "Cu.import('resource://gre/modules/Services.jsm');" +
+                       "chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');" +
+                       "foobarzTezt = chromeWindow.content.document", onAddVariable);
   }
 
   function onAddVariable()
   {
     gBrowser.removeCurrentTab();
 
     hud.jsterm.execute("foobarzTezt", onReadVariable);
   }
--- a/browser/locales/en-US/chrome/browser/places/places.dtd
+++ b/browser/locales/en-US/chrome/browser/places/places.dtd
@@ -76,17 +76,17 @@
 <!ENTITY cmd.reloadLivebookmark.accesskey  "R">
 
 <!ENTITY cmd.moveBookmarks.label                  "Move…">
 <!ENTITY cmd.moveBookmarks.accesskey              "M">
 
 <!ENTITY col.name.label          "Name">
 <!ENTITY col.tags.label          "Tags">
 <!ENTITY col.url.label           "Location">
-<!ENTITY col.lastvisit.label     "Visit Date">
+<!ENTITY col.mostrecentvisit.label "Most Recent Visit">
 <!ENTITY col.visitcount.label    "Visit Count">
 <!ENTITY col.keyword.label       "Keyword">
 <!ENTITY col.description.label   "Description">
 <!ENTITY col.dateadded.label     "Added">
 <!ENTITY col.lastmodified.label  "Last Modified">
 
 <!ENTITY search.label                              "Search:">
 <!ENTITY search.accesskey                          "S">
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -20,34 +20,38 @@ bookmarksRestoreParseError=Unable to pro
 
 bookmarksLivemarkLoading=Live Bookmark loading…
 bookmarksLivemarkFailed=Live Bookmark feed failed to load.
 
 menuOpenLivemarkOrigin.label=Open "%S"
 
 sortByName=Sort '%S' by Name
 sortByNameGeneric=Sort by Name
-view.sortBy.name.label=Sort by Name
-view.sortBy.name.accesskey=N
-view.sortBy.url.label=Sort by Location
-view.sortBy.url.accesskey=L
-view.sortBy.date.label=Sort by Visit Date
-view.sortBy.date.accesskey=V
-view.sortBy.visitCount.label=Sort by Visit Count
-view.sortBy.visitCount.accesskey=C
-view.sortBy.keyword.label=Sort by Keyword
-view.sortBy.keyword.accesskey=K
-view.sortBy.description.label=Sort by Description
-view.sortBy.description.accesskey=D
-view.sortBy.dateAdded.label=Sort by Added
-view.sortBy.dateAdded.accesskey=e
-view.sortBy.lastModified.label=Sort by Last Modified
-view.sortBy.lastModified.accesskey=M
-view.sortBy.tags.label=Sort by Tags
-view.sortBy.tags.accesskey=T
+# LOCALIZATION NOTE (view.sortBy.1.name.label): sortBy properties are versioned.
+# When any of these changes, all of the properties must be bumped, and the
+# change must be annotated here.  Both label and accesskey must be updated.
+# - version 1: changed view.sortBy.1.date.
+view.sortBy.1.name.label=Sort by Name
+view.sortBy.1.name.accesskey=N
+view.sortBy.1.url.label=Sort by Location
+view.sortBy.1.url.accesskey=L
+view.sortBy.1.date.label=Sort by Most Recent Visit
+view.sortBy.1.date.accesskey=V
+view.sortBy.1.visitCount.label=Sort by Visit Count
+view.sortBy.1.visitCount.accesskey=C
+view.sortBy.1.keyword.label=Sort by Keyword
+view.sortBy.1.keyword.accesskey=K
+view.sortBy.1.description.label=Sort by Description
+view.sortBy.1.description.accesskey=D
+view.sortBy.1.dateAdded.label=Sort by Added
+view.sortBy.1.dateAdded.accesskey=e
+view.sortBy.1.lastModified.label=Sort by Last Modified
+view.sortBy.1.lastModified.accesskey=M
+view.sortBy.1.tags.label=Sort by Tags
+view.sortBy.1.tags.accesskey=T
 
 searchBookmarks=Search Bookmarks
 searchHistory=Search History
 searchDownloads=Search Downloads
 
 tabs.openWarningTitle=Confirm open
 tabs.openWarningMultipleBranded=You are about to open %S tabs.  This might slow down %S while the pages are loading.  Are you sure you want to continue?
 tabs.openButtonMultiple=Open tabs
--- a/browser/metro/base/content/ContentAreaObserver.js
+++ b/browser/metro/base/content/ContentAreaObserver.js
@@ -72,16 +72,23 @@ var ContentAreaObserver = {
   get isKeyboardOpened() {
     return Services.metro.keyboardVisible;
   },
 
   get isKeyboardTransitioning() {
     return this._deckTransitioning;
   },
 
+  get viewstate() {
+    if (this.width < Services.prefs.getIntPref("browser.ui.snapped.maxWidth")) {
+      return "snapped";
+    }
+    return (this.height > this.width) ? "portrait" : "landscape";
+  },
+
   /*
    * Public apis
    */
 
   init: function init() {
     window.addEventListener("resize", this, false);
 
     // Message manager msgs we listen for
@@ -117,16 +124,18 @@ var ContentAreaObserver = {
     if (newHeight == oldHeight && newWidth == oldWidth)
       return;
 
     this.styles["window-width"].width = newWidth + "px";
     this.styles["window-width"].maxWidth = newWidth + "px";
     this.styles["window-height"].height = newHeight + "px";
     this.styles["window-height"].maxHeight = newHeight + "px";
 
+    this._updateViewState();
+
     this.updateContentArea(newWidth, this._getContentHeightForWindow(newHeight));
     this._disatchBrowserEvent("SizeChanged");
   },
 
   updateContentArea: function cao_updateContentArea (width, height) {
     let oldHeight = parseInt(this.styles["content-height"].height);
     let oldWidth = parseInt(this.styles["content-width"].width);
 
@@ -275,16 +284,25 @@ var ContentAreaObserver = {
         break;
     }
   },
 
   /*
    * Internal helpers
    */
 
+  _updateViewState: function (aState) {
+    let oldViewstate = Elements.windowState.getAttribute("viewstate");
+    let viewstate = aState || this.viewstate;
+    if (viewstate != oldViewstate) {
+      Elements.windowState.setAttribute("viewstate", viewstate);
+      Services.obs.notifyObservers(null, "metro_viewstate_changed", viewstate);
+    }
+  },
+
   _shiftBrowserDeck: function _shiftBrowserDeck(aAmount) {
     if (aAmount == 0) {
       this._deckTransitioning = false;
       this._dispatchWindowEvent("KeyboardChanged", this.isKeyboardOpened);
     }
 
     if (this._shiftAmount == aAmount)
       return;
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -97,29 +97,25 @@ var BrowserUI = {
 
     // listening escape to dismiss dialog on VK_ESCAPE
     window.addEventListener("keypress", this, true);
 
     window.addEventListener("MozPrecisePointer", this, true);
     window.addEventListener("MozImprecisePointer", this, true);
 
     Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
-    Services.obs.addObserver(this, "metro_viewstate_changed", false);
 
     // Init core UI modules
     ContextUI.init();
     PanelUI.init();
     FlyoutPanelsUI.init();
     PageThumbs.init();
     SettingsCharm.init();
     NavButtonSlider.init();
 
-    // show the right toolbars, awesomescreen, etc for the os viewstate
-    BrowserUI._adjustDOMforViewState();
-
     // We can delay some initialization until after startup.  We wait until
     // the first page is shown, then dispatch a UIReadyDelayed event.
     messageManager.addMessageListener("pageshow", function onPageShow() {
       if (getBrowser().currentURI.spec == "about:blank")
         return;
 
       messageManager.removeMessageListener("pageshow", onPageShow);
 
@@ -173,16 +169,17 @@ var BrowserUI = {
     }, 3000);
 #endif
   },
 
   uninit: function() {
     messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
 
     PanelUI.uninit();
+    FlyoutPanelsUI.uninit();
     Downloads.uninit();
     SettingsCharm.uninit();
     messageManager.removeMessageListener("Content:StateChange", this);
     PageThumbs.uninit();
     this.stopDebugServer();
   },
 
   /************************************
@@ -588,23 +585,16 @@ var BrowserUI = {
               this.stopDebugServer();
             }
             break;
           case debugServerPortChanged:
             this.changeDebugPort(Services.prefs.getIntPref(aData));
             break;
         }
         break;
-      case "metro_viewstate_changed":
-        this._adjustDOMforViewState(aData);
-        if (aData == "snapped") {
-          FlyoutPanelsUI.hide();
-        }
-
-        break;
     }
   },
 
   /*********************************
    * Internal utils
    */
 
   /**
@@ -633,38 +623,16 @@ var BrowserUI = {
     }
     let registry = Cc["@mozilla.org/windows-registry-key;1"].
                    createInstance(Ci.nsIWindowsRegKey);
     pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_INT, "setIntPref");
     pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_BOOL, "setBoolPref");
     pullDesktopControlledPrefType(Ci.nsIPrefBranch.PREF_STRING, "setCharPref");
   },
 
-  _adjustDOMforViewState: function(aState) {
-    let currViewState = aState;
-    if (!currViewState && Services.metro.immersive) {
-      switch (Services.metro.snappedState) {
-        case Ci.nsIWinMetroUtils.fullScreenLandscape:
-          currViewState = "landscape";
-          break;
-        case Ci.nsIWinMetroUtils.fullScreenPortrait:
-          currViewState = "portrait";
-          break;
-        case Ci.nsIWinMetroUtils.filled:
-          currViewState = "filled";
-          break;
-        case Ci.nsIWinMetroUtils.snapped:
-          currViewState = "snapped";
-          break;
-      }
-    }
-
-    Elements.windowState.setAttribute("viewstate", currViewState);
-  },
-
   _titleChanged: function(aBrowser) {
     let url = this.getDisplayURI(aBrowser);
 
     let tabCaption;
     if (aBrowser.contentTitle) {
       tabCaption = aBrowser.contentTitle;
     } else if (!Util.isURLEmpty(aBrowser.userTypedValue)) {
       tabCaption = aBrowser.userTypedValue;
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -125,24 +125,17 @@ var Browser = {
     }
     window.addEventListener("mozfullscreenchange", fullscreenHandler, true);
 
     BrowserUI.init();
 
     window.controllers.appendController(this);
     window.controllers.appendController(BrowserUI);
 
-    let os = Services.obs;
-    os.addObserver(SessionHistoryObserver, "browser:purge-session-history", false);
-    os.addObserver(ActivityObserver, "application-background", false);
-    os.addObserver(ActivityObserver, "application-foreground", false);
-    os.addObserver(ActivityObserver, "system-active", false);
-    os.addObserver(ActivityObserver, "system-idle", false);
-    os.addObserver(ActivityObserver, "system-display-on", false);
-    os.addObserver(ActivityObserver, "system-display-off", false);
+    Services.obs.addObserver(SessionHistoryObserver, "browser:purge-session-history", false);
 
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
 
     Elements.browsers.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
 
     // Make sure we're online before attempting to load
     Util.forceOnline();
 
@@ -242,24 +235,17 @@ var Browser = {
     messageManager.removeMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.removeMessageListener("scroll", this);
     messageManager.removeMessageListener("Browser:CertException", this);
     messageManager.removeMessageListener("Browser:BlockedSite", this);
     messageManager.removeMessageListener("Browser:ErrorPage", this);
     messageManager.removeMessageListener("Browser:PluginClickToPlayClicked", this);
     messageManager.removeMessageListener("Browser:TapOnSelection", this);
 
-    var os = Services.obs;
-    os.removeObserver(SessionHistoryObserver, "browser:purge-session-history");
-    os.removeObserver(ActivityObserver, "application-background");
-    os.removeObserver(ActivityObserver, "application-foreground");
-    os.removeObserver(ActivityObserver, "system-active");
-    os.removeObserver(ActivityObserver, "system-idle");
-    os.removeObserver(ActivityObserver, "system-display-on");
-    os.removeObserver(ActivityObserver, "system-display-off");
+    Services.obs.removeObserver(SessionHistoryObserver, "browser:purge-session-history");
 
     window.controllers.removeController(this);
     window.controllers.removeController(BrowserUI);
   },
 
   getHomePage: function getHomePage(aOptions) {
     aOptions = aOptions || { useDefault: false };
 
@@ -1389,47 +1375,16 @@ var SessionHistoryObserver = {
     let urlbar = document.getElementById("urlbar-edit");
     if (urlbar) {
       // Clear undo history of the URL bar
       urlbar.editor.transactionManager.clear();
     }
   }
 };
 
-var ActivityObserver = {
-  _inBackground : false,
-  _notActive : false,
-  _isDisplayOff : false,
-  _timeoutID: 0,
-  observe: function ao_observe(aSubject, aTopic, aData) {
-    if (aTopic == "application-background") {
-      this._inBackground = true;
-    } else if (aTopic == "application-foreground") {
-      this._inBackground = false;
-    } else if (aTopic == "system-idle") {
-      this._notActive = true;
-    } else if (aTopic == "system-active") {
-      this._notActive = false;
-    } else if (aTopic == "system-display-on") {
-      this._isDisplayOff = false;
-    } else if (aTopic == "system-display-off") {
-      this._isDisplayOff = true;
-    }
-    let activeTabState = !this._inBackground && !this._notActive && !this._isDisplayOff;
-    if (this._timeoutID)
-      clearTimeout(this._timeoutID);
-    if (Browser.selectedTab.active != activeTabState) {
-      // On Maemo all backgrounded applications getting portrait orientation
-      // so if browser had landscape mode then we need timeout in order
-      // to finish last rotate/paint operation and have nice lookine browser in TS
-      this._timeoutID = setTimeout(function() { Browser.selectedTab.active = activeTabState; }, activeTabState ? 0 : kSetInactiveStateTimeout);
-    }
-  }
-};
-
 function getNotificationBox(aBrowser) {
   return Browser.getNotificationBox(aBrowser);
 }
 
 function showDownloadManager(aWindowContext, aID, aReason) {
   // TODO: Bug 883962: Toggle the downloads infobar as our current "download manager".
 }
 
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -167,16 +167,17 @@
     <key id="key_selectTab6" oncommand="BrowserUI.selectTabAtIndex(5);" key="6" modifiers="accel"/>
     <key id="key_selectTab7" oncommand="BrowserUI.selectTabAtIndex(6);" key="7" modifiers="accel"/>
     <key id="key_selectTab8" oncommand="BrowserUI.selectTabAtIndex(7);" key="8" modifiers="accel"/>
     <key id="key_selectLastTab" oncommand="BrowserUI.selectTabAtIndex(-1);" key="9" modifiers="accel"/>
   </keyset>
 
   <stack id="stack" flex="1">
     <observes element="bcast_urlbarState" attribute="*"/>
+    <observes element="bcast_windowState" attribute="*"/>
     <!-- Page Area -->
     <vbox id="page">
       <vbox id="tray" class="tray-toolbar" observes="bcast_windowState" >
         <!-- Tabs -->
         <hbox id="tabs-container" observes="bcast_windowState">
           <box id="tabs" flex="1"
                 observes="bcast_preciseInput"
                 onselect="BrowserUI.selectTabAndDismiss(this);"
--- a/browser/metro/base/content/flyoutpanels/FlyoutPanelsUI.js
+++ b/browser/metro/base/content/flyoutpanels/FlyoutPanelsUI.js
@@ -31,16 +31,22 @@ let FlyoutPanelsUI = {
       let [name, script] = aScript;
       XPCOMUtils.defineLazyGetter(FlyoutPanelsUI, name, function() {
         let sandbox = {};
         Services.scriptloader.loadSubScript(script, sandbox);
         sandbox[name].init();
         return sandbox[name];
       });
     });
+
+    Services.obs.addObserver(this, "metro_viewstate_changed", false);
+  },
+
+  uninit: function () {
+    Services.obs.removeObserver(this, "metro_viewstate_changed");
   },
 
   show: function(aToShow) {
     if (!this[aToShow]) {
       throw("FlyoutPanelsUI asked to show '" + aToShow + "' which does not exist");
     }
 
     if (this._currentFlyout) {
@@ -68,16 +74,26 @@ let FlyoutPanelsUI = {
       Services.metro.showSettingsFlyout();
     }
   },
 
   get isVisible() {
     return this._currentFlyout ? true : false;
   },
 
+  observe: function (aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "metro_viewstate_changed":
+        if (aData == "snapped") {
+          this.hide();
+        }
+        break;
+    }
+  },
+
   dispatchEvent: function(aEvent) {
     if (this._currentFlyout) {
       this._currentFlyout._topmostElement.dispatchEvent(aEvent);
     }
   },
 
   hide: function() {
     if (this._currentFlyout) {
--- a/browser/metro/base/content/startui/BookmarksView.js
+++ b/browser/metro/base/content/startui/BookmarksView.js
@@ -8,34 +8,32 @@
  * Wraps a list/grid control implementing nsIDOMXULSelectControlElement and
  * fills it with the user's bookmarks.
  *
  * @param           aSet    Control implementing nsIDOMXULSelectControlElement.
  * @param {Number}  aLimit  Maximum number of items to show in the view.
  * @param           aRoot   Bookmark root to show in the view.
  */
 function BookmarksView(aSet, aLimit, aRoot, aFilterUnpinned) {
-  this._set = aSet;
-  this._set.controller = this;
+  View.call(this, aSet);
+
   this._inBatch = false; // batch up grid updates to avoid redundant arrangeItems calls
 
   this._limit = aLimit;
   this._filterUnpinned = aFilterUnpinned;
   this._bookmarkService = PlacesUtils.bookmarks;
   this._navHistoryService = gHistSvc;
 
   this._changes = new BookmarkChangeListener(this);
   this._pinHelper = new ItemPinHelper("metro.bookmarks.unpinned");
   this._bookmarkService.addObserver(this._changes, false);
-  Services.obs.addObserver(this, "metro_viewstate_changed", false);
   StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
   StartUI.chromeWin.addEventListener('BookmarksNeedsRefresh', this, false);
   window.addEventListener("TabClose", this, true);
 
-  this._adjustDOMforViewState();
   this.root = aRoot;
 }
 
 BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
   _limit: null,
   _set: null,
   _changes: null,
   _root: null,
@@ -57,21 +55,21 @@ BookmarksView.prototype = Util.extend(Ob
   },
 
   set root(aRoot) {
     this._root = aRoot;
   },
 
   destruct: function bv_destruct() {
     this._bookmarkService.removeObserver(this._changes);
-    Services.obs.removeObserver(this, "metro_viewstate_changed");
     if (StartUI.chromeWin) {
       StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
       StartUI.chromeWin.removeEventListener('BookmarksNeedsRefresh', this, false);
     }
+    View.prototype.destruct.call(this);
   },
 
   handleItemClick: function bv_handleItemClick(aItem) {
     let url = aItem.getAttribute("value");
     StartUI.goToURI(url);
   },
 
   _getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
@@ -270,25 +268,16 @@ BookmarksView.prototype = Util.extend(Ob
       default:
         return;
     }
 
     // Send refresh event so all view are in sync.
     this._sendNeedsRefresh();
   },
 
-  // nsIObservers
-  observe: function (aSubject, aTopic, aState) {
-    switch(aTopic) {
-      case "metro_viewstate_changed":
-        this.onViewStateChange(aState);
-        break;
-    }
-  },
-
   handleEvent: function bv_handleEvent(aEvent) {
     switch (aEvent.type){
       case "MozAppbarDismissing":
         // If undo wasn't pressed, time to do definitive actions.
         if (this._toRemove) {
           for (let bookmarkId of this._toRemove) {
             this._bookmarkService.removeItem(bookmarkId);
           }
--- a/browser/metro/base/content/startui/HistoryView.js
+++ b/browser/metro/base/content/startui/HistoryView.js
@@ -1,45 +1,42 @@
 /* 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';
 
 function HistoryView(aSet, aLimit, aFilterUnpinned) {
-  this._set = aSet;
-  this._set.controller = this;
+  View.call(this, aSet);
+
   this._inBatch = 0;
 
   this._limit = aLimit;
   this._filterUnpinned = aFilterUnpinned;
   this._historyService = PlacesUtils.history;
   this._navHistoryService = gHistSvc;
 
   this._pinHelper = new ItemPinHelper("metro.history.unpinned");
   this._historyService.addObserver(this, false);
-  Services.obs.addObserver(this, "metro_viewstate_changed", false);
   StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
   StartUI.chromeWin.addEventListener('HistoryNeedsRefresh', this, false);
   window.addEventListener("TabClose", this, true);
-
-  this._adjustDOMforViewState();
 }
 
 HistoryView.prototype = Util.extend(Object.create(View.prototype), {
   _set: null,
   _toRemove: null,
 
   destruct: function destruct() {
     this._historyService.removeObserver(this);
-    Services.obs.removeObserver(this, "metro_viewstate_changed");
     if (StartUI.chromeWin) {
       StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
       StartUI.chromeWin.removeEventListener('HistoryNeedsRefresh', this, false);
     }
+    View.prototype.destruct.call(this);
   },
 
   handleItemClick: function tabview_handleItemClick(aItem) {
     let url = aItem.getAttribute("value");
     StartUI.goToURI(url);
   },
 
   populateGrid: function populateGrid(aRefresh) {
@@ -218,25 +215,16 @@ HistoryView.prototype = Util.extend(Obje
       case "TabClose":
         // Flush any pending actions - appbar will call us back
         // before this returns with 'MozAppbarDismissing' above.
         StartUI.chromeWin.ContextUI.dismissContextAppbar();
       break;
     }
   },
 
-  // nsIObservers
-  observe: function (aSubject, aTopic, aState) {
-    switch(aTopic) {
-      case "metro_viewstate_changed":
-        this.onViewStateChange(aState);
-        break;
-    }
-  },
-
   // nsINavHistoryObserver & helpers
 
   onBeginUpdateBatch: function() {
     // Avoid heavy grid redraws while a batch is in process
     this._inBatch++;
   },
 
   onEndUpdateBatch: function() {
--- a/browser/metro/base/content/startui/RemoteTabsView.js
+++ b/browser/metro/base/content/startui/RemoteTabsView.js
@@ -15,50 +15,44 @@ Components.utils.import("resource://serv
  * early during startup.
  *
  * @param    aSet         Control implementing nsIDOMXULSelectControlElement.
  * @param    aSetUIAccess The UI element that should be hidden when Sync is
  *                          disabled. Must sanely support 'hidden' attribute.
  *                          You may only have one UI access point at this time.
  */
 function RemoteTabsView(aSet, aSetUIAccessList) {
-  this._set = aSet;
-  this._set.controller = this;
+  View.call(this, aSet);
+
   this._uiAccessElements = aSetUIAccessList;
 
   // Sync uses special voodoo observers.
   // If you want to change this code, talk to the fx-si team
   Weave.Svc.Obs.add("weave:service:sync:finish", this);
   Weave.Svc.Obs.add("weave:service:start-over", this);
 
-  Services.obs.addObserver(this, "metro_viewstate_changed", false);
-
   if (this.isSyncEnabled() ) {
     this.populateGrid();
   }
   else {
     this.setUIAccessVisible(false);
   }
-  this._adjustDOMforViewState();
 }
 
 RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
   _set: null,
   _uiAccessElements: [],
 
   handleItemClick: function tabview_handleItemClick(aItem) {
     let url = aItem.getAttribute("value");
     StartUI.goToURI(url);
   },
 
   observe: function(subject, topic, data) {
     switch (topic) {
-      case "metro_viewstate_changed":
-        this.onViewStateChange(data);
-        break;
       case "weave:service:sync:finish":
         this.populateGrid();
         break;
       case "weave:service:start-over":
         this.setUIAccessVisible(false);
         break;
     }
   },
@@ -97,19 +91,19 @@ RemoteTabsView.prototype = Util.extend(O
 
       }, this);
     }
     this.setUIAccessVisible(show);
     this._set.arrangeItems();
   },
 
   destruct: function destruct() {
-    Services.obs.removeObserver(this, "metro_viewstate_changed");
     Weave.Svc.Obs.remove("weave:engine:sync:finish", this);
     Weave.Svc.Obs.remove("weave:service:logout:start-over", this);
+    View.prototype.destruct.call(this);
   },
 
   isSyncEnabled: function isSyncEnabled() {
     return (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED);
   }
 
 });
 
--- a/browser/metro/base/content/startui/StartUI.js
+++ b/browser/metro/base/content/startui/StartUI.js
@@ -21,17 +21,17 @@ var StartUI = {
   init: function init() {
     this.startUI.addEventListener("click", this, false);
     this.startUI.addEventListener("MozMousePixelScroll", this, false);
 
     // Update the input type on our local broadcaster
     document.getElementById("bcast_preciseInput").setAttribute("input",
       this.chromeWin.InputSourceHelper.isPrecise ? "precise" : "imprecise");
 
-    this._adjustDOMforViewState();
+    this._adjustDOMforViewState(this.chromeWin.ContentAreaObserver.viewstate);
 
     TopSitesStartView.init();
     BookmarksStartView.init();
     HistoryStartView.init();
     RemoteTabsStartView.init();
 
     this.chromeWin.addEventListener("MozPrecisePointer", this, true);
     this.chromeWin.addEventListener("MozImprecisePointer", this, true);
@@ -76,16 +76,20 @@ var StartUI = {
       return;
 
     for (let expandedSection of this.startUI.querySelectorAll(".meta-section[expanded]"))
       expandedSection.removeAttribute("expanded")
 
     section.setAttribute("expanded", "true");
   },
 
+  _adjustDOMforViewState: function(aState) {
+    document.getElementById("bcast_windowState").setAttribute("viewstate", aState);
+  },
+
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "MozPrecisePointer":
         document.getElementById("bcast_preciseInput").setAttribute("input", "precise");
         break;
       case "MozImprecisePointer":
         document.getElementById("bcast_preciseInput").setAttribute("input", "imprecise");
         break;
@@ -101,43 +105,16 @@ var StartUI = {
         }
 
         aEvent.preventDefault();
         aEvent.stopPropagation();
         break;
     }
   },
 
-  _adjustDOMforViewState: function(aState) {
-    let currViewState = aState;
-    if (!currViewState && Services.metro.immersive) {
-      switch (Services.metro.snappedState) {
-        case Ci.nsIWinMetroUtils.fullScreenLandscape:
-          currViewState = "landscape";
-          break;
-        case Ci.nsIWinMetroUtils.fullScreenPortrait:
-          currViewState = "portrait";
-          break;
-        case Ci.nsIWinMetroUtils.filled:
-          currViewState = "filled";
-          break;
-        case Ci.nsIWinMetroUtils.snapped:
-          currViewState = "snapped";
-          break;
-      }
-    }
-
-    document.getElementById("bcast_windowState").setAttribute("viewstate", currViewState);
-    if (currViewState == "snapped") {
-      document.getElementById("start-topsites-grid").removeAttribute("tiletype");
-    } else {
-      document.getElementById("start-topsites-grid").setAttribute("tiletype", "thumbnail");
-    }
-  },
-
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
       case "metro_viewstate_changed":
         this._adjustDOMforViewState(aData);
         break;
     }
   }
 };
--- a/browser/metro/base/content/startui/TopSitesView.js
+++ b/browser/metro/base/content/startui/TopSitesView.js
@@ -6,31 +6,28 @@
 
 let prefs = Components.classes["@mozilla.org/preferences-service;1"].
       getService(Components.interfaces.nsIPrefBranch);
 
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource:///modules/colorUtils.jsm");
 
 function TopSitesView(aGrid, aMaxSites) {
-  this._set = aGrid;
-  this._set.controller = this;
+  View.call(this, aGrid);
+
   this._topSitesMax = aMaxSites;
 
   // clean up state when the appbar closes
   StartUI.chromeWin.addEventListener('MozAppbarDismissing', this, false);
   let history = Cc["@mozilla.org/browser/nav-history-service;1"].
                 getService(Ci.nsINavHistoryService);
   history.addObserver(this, false);
 
   PageThumbs.addExpirationFilter(this);
   Services.obs.addObserver(this, "Metro:RefreshTopsiteThumbnail", false);
-  Services.obs.addObserver(this, "metro_viewstate_changed", false);
-
-  this._adjustDOMforViewState();
 
   NewTabUtils.allPages.register(this);
   TopSites.prepareCache().then(function(){
     this.populateGrid();
   }.bind(this));
 }
 
 TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
@@ -38,22 +35,22 @@ TopSitesView.prototype = Util.extend(Obj
   _topSitesMax: null,
   // _lastSelectedSites used to temporarily store blocked/removed sites for undo/restore-ing
   _lastSelectedSites: null,
   // isUpdating used only for testing currently
   isUpdating: false,
 
   destruct: function destruct() {
     Services.obs.removeObserver(this, "Metro:RefreshTopsiteThumbnail");
-    Services.obs.removeObserver(this, "metro_viewstate_changed");
     PageThumbs.removeExpirationFilter(this);
     NewTabUtils.allPages.unregister(this);
     if (StartUI.chromeWin) {
       StartUI.chromeWin.removeEventListener('MozAppbarDismissing', this, false);
     }
+    View.prototype.destruct.call(this);
   },
 
   handleItemClick: function tabview_handleItemClick(aItem) {
     let url = aItem.getAttribute("value");
     StartUI.goToURI(url);
   },
 
   doActionOnSelectedTiles: function(aActionName, aEvent) {
@@ -132,16 +129,17 @@ TopSitesView.prototype = Util.extend(Obj
     }
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type){
       case "MozAppbarDismissing":
         // clean up when the context appbar is dismissed - we don't remember selections
         this._lastSelectedSites = null;
+        break;
     }
   },
 
   update: function() {
     // called by the NewTabUtils.allPages.update, notifying us of data-change in topsites
     let grid = this._set,
         dirtySites = TopSites.dirty();
 
@@ -229,16 +227,23 @@ TopSitesView.prototype = Util.extend(Obj
   _adjustDOMforViewState: function _adjustDOMforViewState(aState) {
     if (!this._set)
       return;
     if (!aState)
       aState = this._set.getAttribute("viewstate");
 
     View.prototype._adjustDOMforViewState.call(this, aState);
 
+    // Don't show thumbnails in snapped view.
+    if (aState == "snapped") {
+      document.getElementById("start-topsites-grid").removeAttribute("tiletype");
+    } else {
+      document.getElementById("start-topsites-grid").setAttribute("tiletype", "thumbnail");
+    }
+
     // propogate tiletype changes down to tile children
     let tileType = this._set.getAttribute("tiletype");
     for (let item of this._set.children) {
       if (tileType) {
         item.setAttribute("tiletype", tileType);
       } else {
         item.removeAttribute("tiletype");
       }
@@ -246,19 +251,16 @@ TopSitesView.prototype = Util.extend(Obj
   },
 
   // nsIObservers
   observe: function (aSubject, aTopic, aState) {
     switch(aTopic) {
       case "Metro:RefreshTopsiteThumbnail":
         this.forceReloadOfThumbnail(aState);
         break;
-      case "metro_viewstate_changed":
-        this.onViewStateChange(aState);
-        break;
     }
   },
 
   // nsINavHistoryObserver
   onBeginUpdateBatch: function() {
   },
 
   onEndUpdateBatch: function() {
--- a/browser/metro/base/tests/mochitest/browser_snappedState.js
+++ b/browser/metro/base/tests/mochitest/browser_snappedState.js
@@ -106,17 +106,17 @@ gTests.push({
     yield setUpSnapped();
   },
   run: function() {
     ok(Browser.selectedBrowser.contentWindow.scrollMaxY !== 0, "Snapped scrolls vertically");
     ok(Browser.selectedBrowser.contentWindow.scrollMaxX === 0, "Snapped does not scroll horizontally");
   },
   tearDown: function() {
     BookmarksTestHelper.restore();
-    restoreViewstate();
+    yield restoreViewstate();
   }
 });
 
 gTests.push({
   desc: "Navbar contextual buttons are not shown in snapped",
   setUp: setUpSnapped,
   run: function() {
     let toolbarContextual = document.getElementById("toolbar-contextual");
@@ -155,11 +155,11 @@ gTests.push({
   },
   run: function() {
     ok(Browser.selectedBrowser.contentWindow.scrollMaxY !== 0, "Portrait scrolls vertically");
     ok(Browser.selectedBrowser.contentWindow.scrollMaxX === 0, "Portrait does not scroll horizontally");
   },
   tearDown: function() {
     BookmarksTestHelper.restore();
     HistoryTestHelper.restore();
-    restoreViewstate();
+    yield restoreViewstate();
   }
 });
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -42,17 +42,17 @@ const mochitestPath = splitPath.join('/'
 }, this);
 
 /*=============================================================================
   Metro ui helpers
 =============================================================================*/
 
 function isLandscapeMode()
 {
-  return (Services.metro.snappedState == Ci.nsIWinMetroUtils.fullScreenLandscape);
+  return Elements.windowState.getAttribute("viewstate") == "landscape";
 }
 
 function setDevPixelEqualToPx()
 {
   todo(false, "test depends on devPixelsPerPx set to 1.0 - see bugs 886624 and 859742");
   SpecialPowers.setCharPref("layout.css.devPixelsPerPx", "1.0");
   registerCleanupFunction(function () {
     SpecialPowers.clearUserPref("layout.css.devPixelsPerPx");
--- a/browser/metro/base/tests/mochitest/helpers/ViewStateHelper.js
+++ b/browser/metro/base/tests/mochitest/helpers/ViewStateHelper.js
@@ -15,39 +15,38 @@ function setSnappedViewstate() {
 
   // Reduce browser width to simulate small screen size.
   let fullWidth = browser.clientWidth;
   let padding = fullWidth - snappedSize;
 
   browser.style.borderRight = padding + "px solid gray";
 
   // Communicate viewstate change
-  Services.obs.notifyObservers(null, 'metro_viewstate_changed', 'snapped');
+  ContentAreaObserver._updateViewState("snapped");
 
   // Make sure it renders the new mode properly
   yield waitForMs(0);
 }
 
 function setPortraitViewstate() {
   ok(isLandscapeMode(), "setPortraitViewstate expects landscape mode to work.");
 
   let browser = Browser.selectedBrowser;
 
   let fullWidth = browser.clientWidth;
   let padding = fullWidth - portraitSize;
 
   browser.style.borderRight = padding + "px solid gray";
 
-  Services.obs.notifyObservers(null, 'metro_viewstate_changed', 'portrait');
+  ContentAreaObserver._updateViewState("portrait");
 
   // Make sure it renders the new mode properly
   yield waitForMs(0);
 }
 
 function restoreViewstate() {
-  ok(isLandscapeMode(), "restoreViewstate expects landscape mode to work.");
-
-  Services.obs.notifyObservers(null, 'metro_viewstate_changed', 'landscape');
+  ContentAreaObserver._updateViewState("landscape");
+  ok(isLandscapeMode(), "restoreViewstate should restore landscape mode.");
 
   Browser.selectedBrowser.style.removeProperty("border-right");
 
   yield waitForMs(0);
 }
--- a/browser/metro/locales/generic/profile/bookmarks.json.in
+++ b/browser/metro/locales/generic/profile/bookmarks.json.in
@@ -1,17 +1,14 @@
 #filter substitution
 {"type":"text/x-moz-place-container","root":"placesRoot","children":
   [{"type":"text/x-moz-place-container","title":"@bookmarks_title@","annos":[{"name":"metro/bookmarksRoot","expires":4,"type":1,"value":1}],
     "children":
      [
        {"index":1,"title":"@firefox_about@", "type":"text/x-moz-place", "uri":"http://www.mozilla.org/@AB_CD@/about/",
         "iconUri":"chrome://branding/content/favicon32.png"
        },
-       {"index":2,"title":"@getting_started@",  "type":"text/x-moz-place", "uri":"http://www.mozilla.org/@AB_CD@/firefox/central/",
-        "iconUri":"chrome://branding/content/favicon32.png"
-       },
-       {"index":3,"title":"@firefox_community@",  "type":"text/x-moz-place", "uri":"http://www.mozilla.org/@AB_CD@/contribute/",
+       {"index":2,"title":"@firefox_community@",  "type":"text/x-moz-place", "uri":"http://www.mozilla.org/@AB_CD@/contribute/",
         "iconUri":"chrome://branding/content/favicon32.png"
        }
      ]
   }]
 }
--- a/browser/metro/modules/View.jsm
+++ b/browser/metro/modules/View.jsm
@@ -18,20 +18,33 @@ function makeURI(aURL, aOriginCharset, a
 }
 
 // --------------------------------
 
 
 // --------------------------------
 // View prototype for shared functionality
 
-function View() {
+function View(aSet) {
+  this._set = aSet;
+  this._set.controller = this;
+
+  this.viewStateObserver = {
+    observe: (aSubject, aTopic, aData) => this._adjustDOMforViewState(aData)
+  };
+  Services.obs.addObserver(this.viewStateObserver, "metro_viewstate_changed", false);
+
+  this._adjustDOMforViewState();
 }
 
 View.prototype = {
+  destruct: function () {
+    Services.obs.removeObserver(this.viewStateObserver, "metro_viewstate_changed");
+  },
+
   _adjustDOMforViewState: function _adjustDOMforViewState(aState) {
     if (this._set) {
       if (undefined == aState)
         aState = this._set.getAttribute("viewstate");
 
       this._set.setAttribute("suppressonselect", (aState == "snapped"));
 
       if (aState == "portrait") {
@@ -39,20 +52,16 @@ View.prototype = {
       } else {
         this._set.removeAttribute("vertical");
       }
 
       this._set.arrangeItems();
     }
   },
 
-  onViewStateChange: function (aState) {
-    this._adjustDOMforViewState(aState);
-  },
-
   _updateFavicon: function pv__updateFavicon(aItem, aUri) {
     if ("string" == typeof aUri) {
       aUri = makeURI(aUri);
     }
     PlacesUtils.favicons.getFaviconURLForPage(aUri, this._gotIcon.bind(this, aItem));
   },
 
   _gotIcon: function pv__gotIcon(aItem, aIconUri) {
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -372,16 +372,19 @@ pref("privacy.sanitize.migrateFx3Prefs",
 
 // enable geo
 pref("geo.enabled", true);
 pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
 
 // JS error console
 pref("devtools.errorconsole.enabled", false);
 
+// snapped view
+pref("browser.ui.snapped.maxWidth", 600);
+
 // kinetic tweakables
 pref("browser.ui.kinetic.updateInterval", 16);
 pref("browser.ui.kinetic.exponentialC", 1400);
 pref("browser.ui.kinetic.polynomialC", 100);
 pref("browser.ui.kinetic.swipeLength", 160);
 pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation
 
 // pinch gesture
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -370,16 +370,17 @@ documenttab[selected] .documenttab-selec
 }
 
 #overlay-plus:-moz-locale-dir(ltr),
 #overlay-back:-moz-locale-dir(rtl) {
   right: -70px;
   background-position: left 6px center;
 }
 
+#stack[viewstate="snapped"] > .overlay-button,
 #stack[keyboardVisible] > .overlay-button,
 #stack[autocomplete] > .overlay-button,
 #stack[fullscreen] > .overlay-button,
 #appbar[visible] ~ .overlay-button,
 .overlay-button[disabled] {
   box-shadow: none;
   visibility: collapse;
 }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -176,19 +176,16 @@ abstract public class GeckoApp
     private PromptService mPromptService;
     private TextSelection mTextSelection;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
     protected TabsPanel mTabsPanel;
     protected ButtonToast mToast;
 
-    // Handles notification messages from javascript
-    protected NotificationHelper mNotificationHelper;
-
     protected LayerView mLayerView;
     private AbsoluteLayout mPluginContainer;
 
     private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
@@ -1230,17 +1227,16 @@ abstract public class GeckoApp
         setContentView(getLayout());
 
         // Set up Gecko layout.
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
 
         // Set up tabs panel.
         mTabsPanel = (TabsPanel) findViewById(R.id.tabs_panel);
-        mNotificationHelper = new NotificationHelper(this);
         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);
 
@@ -1791,21 +1787,16 @@ abstract public class GeckoApp
             alertName = data.getQueryParameter("name");
             if (alertName == null)
                 alertName = "";
             alertCookie = data.getQueryParameter("cookie");
             if (alertCookie == null)
                 alertCookie = "";
         }
         handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
-
-        if (intent.hasExtra(NotificationHelper.NOTIFICATION_ID)) {
-            String id = intent.getStringExtra(NotificationHelper.NOTIFICATION_ID);
-            mNotificationHelper.hideNotification(id);
-        }
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
             // We're exiting and shouldn't try to do anything else just incase
             // we're hung for some reason we'll force the process to exit
             System.exit(0);
@@ -2040,18 +2031,16 @@ abstract public class GeckoApp
         if (mFormAssistPopup != null)
             mFormAssistPopup.destroy();
         if (mContactService != null)
             mContactService.destroy();
         if (mPromptService != null)
             mPromptService.destroy();
         if (mTextSelection != null)
             mTextSelection.destroy();
-        if (mNotificationHelper != null)
-            mNotificationHelper.destroy();
 
         if (SmsManager.getInstance() != null) {
             SmsManager.getInstance().stop();
             if (isFinishing())
                 SmsManager.getInstance().shutdown();
         }
 
         final BrowserHealthRecorder rec = mHealthRecorder;
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1325,22 +1325,16 @@ public class GeckoAppShell
         if (GeckoApp.ACTION_ALERT_CALLBACK.equals(aAction)) {
             callObserver(aAlertName, "alertclickcallback", aAlertCookie);
 
             if (sNotificationClient.isOngoing(notificationID)) {
                 // When clicked, keep the notification if it displays progress
                 return;
             }
         }
-
-        // Also send a notification to the observer service
-        // New listeners should register for these notifications since they will be called even if
-        // Gecko has been killed and restared between when your notification was shown and when the
-        // user clicked on it.
-        sendEventToGecko(GeckoEvent.createBroadcastEvent("Notification:Clicked", aAlertCookie));
         closeNotification(aAlertName);
     }
 
     @GeneratableAndroidBridgeTarget(stubName = "GetDpiWrapper")
     public static int getDpi() {
         if (sDensityDpi == 0) {
             sDensityDpi = getContext().getResources().getDisplayMetrics().densityDpi;
         }
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -93,16 +93,17 @@ public class GeckoApplication extends Ap
     protected boolean needsRestart() {
         return mNeedsRestart;
     }
 
     @Override
     public void onCreate() {
         HardwareUtils.init(getApplicationContext());
         Clipboard.init(getApplicationContext());
+        NotificationHelper.init(getApplicationContext());
         GeckoLoader.loadMozGlue();
         super.onCreate();
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -644,17 +644,17 @@ RES_DRAWABLE_MDPI = \
   res/drawable-mdpi/bookmark_folder_closed.png \
   res/drawable-mdpi/bookmark_folder_opened.png \
   res/drawable-mdpi/desktop_notification.png \
   res/drawable-mdpi/home_tab_menu_strip.9.png \
   res/drawable-mdpi/ic_menu_addons_filler.png \
   res/drawable-mdpi/ic_menu_bookmark_add.png \
   res/drawable-mdpi/ic_menu_bookmark_remove.png \
   res/drawable-mdpi/ic_menu_character_encoding.png \
-  res/drawable-mdpi/ic_menu_close_all_tabs.png \
+  res/drawable-mdpi/close.png \
   res/drawable-mdpi/ic_menu_forward.png \
   res/drawable-mdpi/ic_menu_guest.png \
   res/drawable-mdpi/ic_menu_new_private_tab.png \
   res/drawable-mdpi/ic_menu_new_tab.png \
   res/drawable-mdpi/ic_menu_reload.png \
   res/drawable-mdpi/ic_status_logo.png \
   res/drawable-mdpi/ic_url_bar_go.png \
   res/drawable-mdpi/ic_url_bar_reader.png \
@@ -666,16 +666,18 @@ RES_DRAWABLE_MDPI = \
   res/drawable-mdpi/icon_most_recent.png \
   res/drawable-mdpi/icon_most_recent_empty.png \
   res/drawable-mdpi/icon_most_visited.png \
   res/drawable-mdpi/icon_most_visited_empty.png \
   res/drawable-mdpi/icon_openinapp.png \
   res/drawable-mdpi/icon_pageaction.png \
   res/drawable-mdpi/icon_reading_list_empty.png \
   res/drawable-mdpi/progress_spinner.png \
+  res/drawable-mdpi/play.png \
+  res/drawable-mdpi/pause.png \
   res/drawable-mdpi/tab_indicator_divider.9.png \
   res/drawable-mdpi/tab_indicator_selected.9.png \
   res/drawable-mdpi/tab_indicator_selected_focused.9.png \
   res/drawable-mdpi/spinner_default.9.png \
   res/drawable-mdpi/spinner_focused.9.png \
   res/drawable-mdpi/spinner_pressed.9.png \
   res/drawable-mdpi/tab_new.png \
   res/drawable-mdpi/tab_new_pb.png \
@@ -758,17 +760,17 @@ RES_DRAWABLE_HDPI = \
   res/drawable-hdpi/alert_mic.png \
   res/drawable-hdpi/alert_mic_camera.png \
   res/drawable-hdpi/arrow_popup_bg.9.png \
   res/drawable-hdpi/home_tab_menu_strip.9.png \
   res/drawable-hdpi/ic_menu_addons_filler.png \
   res/drawable-hdpi/ic_menu_bookmark_add.png \
   res/drawable-hdpi/ic_menu_bookmark_remove.png \
   res/drawable-hdpi/ic_menu_character_encoding.png \
-  res/drawable-hdpi/ic_menu_close_all_tabs.png \
+  res/drawable-hdpi/close.png \
   res/drawable-hdpi/ic_menu_forward.png \
   res/drawable-hdpi/ic_menu_guest.png \
   res/drawable-hdpi/ic_menu_new_private_tab.png \
   res/drawable-hdpi/ic_menu_new_tab.png \
   res/drawable-hdpi/ic_menu_reload.png \
   res/drawable-hdpi/ic_status_logo.png \
   res/drawable-hdpi/ic_url_bar_go.png \
   res/drawable-hdpi/ic_url_bar_reader.png \
@@ -813,16 +815,18 @@ RES_DRAWABLE_HDPI = \
   res/drawable-hdpi/menu_panel_bg.9.png \
   res/drawable-hdpi/menu_popup_bg.9.png \
   res/drawable-hdpi/menu_popup_arrow_bottom.png \
   res/drawable-hdpi/menu_popup_arrow_top.png \
   res/drawable-hdpi/menu_item_check.png \
   res/drawable-hdpi/menu_item_more.png \
   res/drawable-hdpi/menu_item_uncheck.png \
   res/drawable-hdpi/pin.png \
+  res/drawable-hdpi/play.png \
+  res/drawable-hdpi/pause.png \
   res/drawable-hdpi/shield.png \
   res/drawable-hdpi/shield_doorhanger.png \
   res/drawable-hdpi/tabs_normal.png \
   res/drawable-hdpi/tabs_private.png \
   res/drawable-hdpi/tabs_synced.png \
   res/drawable-hdpi/top_site_add.png \
   res/drawable-hdpi/urlbar_stop.png \
   res/drawable-hdpi/reader.png \
@@ -857,17 +861,17 @@ RES_DRAWABLE_XHDPI = \
   res/drawable-xhdpi/alert_camera.png \
   res/drawable-xhdpi/alert_mic.png \
   res/drawable-xhdpi/alert_mic_camera.png \
   res/drawable-xhdpi/arrow_popup_bg.9.png \
   res/drawable-xhdpi/home_tab_menu_strip.9.png \
   res/drawable-xhdpi/ic_menu_addons_filler.png \
   res/drawable-xhdpi/ic_menu_bookmark_add.png \
   res/drawable-xhdpi/ic_menu_bookmark_remove.png \
-  res/drawable-xhdpi/ic_menu_close_all_tabs.png \
+  res/drawable-xhdpi/close.png \
   res/drawable-xhdpi/ic_menu_character_encoding.png \
   res/drawable-xhdpi/ic_menu_forward.png \
   res/drawable-xhdpi/ic_menu_guest.png \
   res/drawable-xhdpi/ic_menu_new_private_tab.png \
   res/drawable-xhdpi/ic_menu_new_tab.png \
   res/drawable-xhdpi/ic_menu_reload.png \
   res/drawable-xhdpi/ic_status_logo.png \
   res/drawable-xhdpi/ic_url_bar_go.png \
@@ -912,16 +916,18 @@ RES_DRAWABLE_XHDPI = \
   res/drawable-xhdpi/menu_panel_bg.9.png \
   res/drawable-xhdpi/menu_popup_bg.9.png \
   res/drawable-xhdpi/menu_popup_arrow_bottom.png \
   res/drawable-xhdpi/menu_popup_arrow_top.png \
   res/drawable-xhdpi/menu_item_check.png \
   res/drawable-xhdpi/menu_item_more.png \
   res/drawable-xhdpi/menu_item_uncheck.png \
   res/drawable-xhdpi/pin.png \
+  res/drawable-xhdpi/play.png \
+  res/drawable-xhdpi/pause.png \
   res/drawable-xhdpi/shield.png \
   res/drawable-xhdpi/shield_doorhanger.png \
   res/drawable-xhdpi/tab_indicator_divider.9.png \
   res/drawable-xhdpi/tab_indicator_selected.9.png \
   res/drawable-xhdpi/tab_indicator_selected_focused.9.png \
   res/drawable-xhdpi/tabs_normal.png \
   res/drawable-xhdpi/tabs_private.png \
   res/drawable-xhdpi/tabs_synced.png \
--- a/mobile/android/base/NotificationHelper.java
+++ b/mobile/android/base/NotificationHelper.java
@@ -9,107 +9,248 @@ import org.mozilla.gecko.gfx.BitmapUtils
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
 import android.util.Log;
 
 import java.util.Set;
 import java.util.HashSet;
 
-public class NotificationHelper implements GeckoEventListener {
+public final class NotificationHelper implements GeckoEventListener {
     public static final String NOTIFICATION_ID = "NotificationHelper_ID";
     private static final String LOGTAG = "GeckoNotificationManager";
-    private Context mContext;
-    private Set<String> mShowing;
+    private static final String HELPER_NOTIFICATION = "helperNotif";
+    private static final String HELPER_BROADCAST_ACTION = "helperBroadcastAction";
+
+    // Attributes mandatory to be used while sending a notification from js.
+    private static final String TITLE_ATTR = "title";
+    private static final String TEXT_ATTR = "text";
+    private static final String ID_ATTR = "id";
+    private static final String SMALLICON_ATTR = "smallIcon";
 
-    public NotificationHelper(Context context) {
+    // Attributes that can be used while sending a notification from js.
+    private static final String PROGRESS_VALUE_ATTR = "progress_value";
+    private static final String PROGRESS_MAX_ATTR = "progress_max";
+    private static final String LIGHT_ATTR = "light";
+    private static final String ONGOING_ATTR = "ongoing";
+    private static final String WHEN_ATTR = "when";
+    private static final String LARGE_ICON_ATTR = "largeIcon";
+    private static final String COOKIE_ATTR = "cookie";
+    private static final String EVENT_TYPE_ATTR = "eventType";
+    private static final String ACTIONS_ATTR = "actions";
+    private static final String ACTION_ATTR = "actionKind";
+    private static final String ACTION_TITLE_ATTR = "title";
+    private static final String ACTION_ICON_ATTR = "icon";
+
+    private static final String NOTIFICATION_SCHEME = "moz-notification";
+
+    private static final String CLICK_EVENT = "notification-clicked";
+    private static final String CLEARED_EVENT = "notification-cleared";
+
+    private static Context mContext;
+    private static Set<String> mShowing;
+    private static BroadcastReceiver mReceiver;
+    private static NotificationHelper mInstance;
+
+    private NotificationHelper() {
+    }
+
+    public static void init(Context context) {
+        if (mInstance != null) {
+            Log.w(LOGTAG, "NotificationHelper.init() called twice!");
+        }
+        mInstance = new NotificationHelper();
         mContext = context;
         mShowing = new HashSet<String>();
         registerEventListener("Notification:Show");
         registerEventListener("Notification:Hide");
+        registerReceiver(context);
     }
 
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
+    private static void registerEventListener(String event) {
+        GeckoAppShell.getEventDispatcher().registerEventListener(event, mInstance);
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         if (event.equals("Notification:Show")) {
             showNotification(message);
         } else if (event.equals("Notification:Hide")) {
             hideNotification(message);
         }
     }
 
+    public boolean isHelperIntent(Intent i) {
+        return i.getBooleanExtra(HELPER_NOTIFICATION, false);
+    }
+
+    private static void registerReceiver(Context context) {
+        IntentFilter filter = new IntentFilter(HELPER_BROADCAST_ACTION);
+        // Scheme is needed, otherwise only broadcast with no data will be catched.
+        filter.addDataScheme(NOTIFICATION_SCHEME);
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mInstance.handleNotificationIntent(intent);
+            }
+        };
+        context.registerReceiver(mReceiver, filter);
+    }
+
+
+    private void handleNotificationIntent(Intent i) {
+        final Uri data = i.getData();
+        if (data == null) {
+            Log.w(LOGTAG, "handleNotificationEvent: empty data");
+            return;
+        }
+        final String id = data.getQueryParameter(ID_ATTR);
+        final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
+        if (id == null || notificationType == null) {
+            Log.w(LOGTAG, "handleNotificationEvent: invalid intent parameters");
+            return;
+        }
+
+        // In case the user swiped out the notification, we empty the id
+        // set.
+        if (CLEARED_EVENT.equals(notificationType)) {
+            mShowing.remove(id);
+        }
+
+        // If the notification was clicked, we are closing it.
+        if (CLICK_EVENT.equals(notificationType) && !i.getBooleanExtra(ONGOING_ATTR, false)) {
+            hideNotification(id);
+        }
+        String cookie = data.getQueryParameter(COOKIE_ATTR);
+
+        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+            JSONObject args = new JSONObject();
+            try {
+                args.put(NOTIFICATION_ID, id);
+                if (cookie != null) {
+                    args.put(COOKIE_ATTR, cookie);
+                }
+                args.put(EVENT_TYPE_ATTR, notificationType);
+                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Notification:Event", args.toString()));
+            } catch (JSONException e) {
+                Log.w(LOGTAG, "Error building JSON notification arguments.", e);
+            }
+        }
+    }
+
+    private PendingIntent buildNotificationPendingIntent(JSONObject message, String type) {
+        Intent notificationIntent = new Intent(HELPER_BROADCAST_ACTION);
+        final boolean ongoing = message.optBoolean(ONGOING_ATTR);
+        notificationIntent.putExtra(ONGOING_ATTR, ongoing);
+
+        Uri.Builder b = new Uri.Builder();
+        b.scheme(NOTIFICATION_SCHEME).appendQueryParameter(EVENT_TYPE_ATTR, type);
+
+        try {
+            final String id = message.getString(ID_ATTR);
+            b.appendQueryParameter(ID_ATTR, id);
+            if (message.has(COOKIE_ATTR)) {
+                b.appendQueryParameter(COOKIE_ATTR,
+                                       message.getString(COOKIE_ATTR));
+            }
+        } catch (JSONException ex) {
+            Log.i(LOGTAG, "buildNotificationPendingIntent, error parsing", ex);
+        }
+        final Uri dataUri = b.build();
+        notificationIntent.setData(dataUri);
+        notificationIntent.putExtra(HELPER_NOTIFICATION, true);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return pi;
+    }
+
     private void showNotification(JSONObject message) {
         NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
 
         // These attributes are required
         final String id;
         try {
-            builder.setContentTitle(message.getString("title"));
-            builder.setContentText(message.getString("text"));
-            id = message.getString("id");
+            builder.setContentTitle(message.getString(TITLE_ATTR));
+            builder.setContentText(message.getString(TEXT_ATTR));
+            id = message.getString(ID_ATTR);
         } catch (JSONException ex) {
             Log.i(LOGTAG, "Error parsing", ex);
             return;
         }
 
-        Uri imageUri = Uri.parse(message.optString("smallicon"));
+        Uri imageUri = Uri.parse(message.optString(SMALLICON_ATTR));
         builder.setSmallIcon(BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo));
 
-        JSONArray light = message.optJSONArray("light");
+        JSONArray light = message.optJSONArray(LIGHT_ATTR);
         if (light != null && light.length() == 3) {
             try {
                 builder.setLights(light.getInt(0),
                                   light.getInt(1),
                                   light.getInt(2));
             } catch (JSONException ex) {
                 Log.i(LOGTAG, "Error parsing", ex);
             }
         }
 
-        boolean ongoing = message.optBoolean("ongoing");
+        boolean ongoing = message.optBoolean(ONGOING_ATTR);
         builder.setOngoing(ongoing);
 
-        if (message.has("when")) {
-            int when = message.optInt("when");
+        if (message.has(WHEN_ATTR)) {
+            int when = message.optInt(WHEN_ATTR);
             builder.setWhen(when);
         }
 
-        if (message.has("largeicon")) {
-            Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString("largeicon"));
+        if (message.has(LARGE_ICON_ATTR)) {
+            Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString(LARGE_ICON_ATTR));
             builder.setLargeIcon(b);
         }
 
-        // We currently don't support a callback when these are clicked.
-        // Instead we just open fennec.
-        Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
-        String app = mContext.getClass().getName();
-        notificationIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, app);
-        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        // if this isn't an ongoing notification, add the id to the intent so that we
-        // can remove the notification from our list of active notifications if its clicked
-        if (!ongoing) {
-            notificationIntent.putExtra(NOTIFICATION_ID, id);
+        if (message.has(PROGRESS_VALUE_ATTR) &&
+            message.has(PROGRESS_MAX_ATTR)) {
+            try {
+                final int progress = message.getInt(PROGRESS_VALUE_ATTR);
+                final int progressMax = message.getInt(PROGRESS_MAX_ATTR);
+                builder.setProgress(progressMax, progress, false);
+            } catch (JSONException ex) {
+                Log.i(LOGTAG, "Error parsing", ex);
+            }
         }
 
-        PendingIntent pi = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
+        JSONArray actions = message.optJSONArray(ACTIONS_ATTR);
+        if (actions != null) {
+            try {
+                for (int i = 0; i < actions.length(); i++) {
+                    JSONObject action = actions.getJSONObject(i);
+                    final PendingIntent pending = buildNotificationPendingIntent(message, action.getString(ACTION_ATTR));
+                    final String actionTitle = action.getString(ACTION_TITLE_ATTR);
+                    final Uri actionImage = Uri.parse(action.optString(ACTION_ICON_ATTR));
+                    builder.addAction(BitmapUtils.getResource(actionImage, R.drawable.ic_status_logo),
+                                      actionTitle,
+                                      pending);
+                }
+            } catch (JSONException ex) {
+                Log.i(LOGTAG, "Error parsing", ex);
+            }
+        }
+
+        PendingIntent pi = buildNotificationPendingIntent(message, CLICK_EVENT);
         builder.setContentIntent(pi);
+        PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
+        builder.setDeleteIntent(deletePendingIntent);
 
         GeckoAppShell.sNotificationClient.add(id.hashCode(), builder.build());
         if (!mShowing.contains(id)) {
             mShowing.add(id);
         }
     }
 
     private void hideNotification(JSONObject message) {
@@ -129,13 +270,9 @@ public class NotificationHelper implemen
         mShowing.remove(id);
     }
 
     private void clearAll() {
         for (String id : mShowing) {
             hideNotification(id);
         }
     }
-
-    public void destroy() {
-        clearAll();
-    }
 }
rename from mobile/android/base/resources/drawable-hdpi/ic_menu_close_all_tabs.png
rename to mobile/android/base/resources/drawable-hdpi/close.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b1bcd06079dc098921ce1501daf086a839a050ac
GIT binary patch
literal 763
zc$@+I0tEeuP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000!j000!j0TpmfLI3~&8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10(eP8K~!jg?U}J^8$lFB@1{$wN}xs}LLU<KLKuSy#^hI27z{29
z217_bC4b;Pq;%mp4*rEmJ}G7gEz3%NZ)T-+ur5qFZ|)iGT<yFq0R7K;*R_FM9RRx4
z0ibKWS%AKzsidMM9|K9fl!++-LrJ%izDxS5e2AH(J4rt!P295q&lmv@z>>AT0EM$o
z9CP3=uqtc*0mhyhp2a=@ziZ~BZW7ENm9<utI!oZOT&q=paap*@eAKnW{Bc?9xuMRo
z+^A7o029JbyUl#mm7(4VVb`0?S0NsNVJ<)qcru!gy4+Illfiry;vVSd01Q|kGLJ4A
zYMnaFS2fK65HL%GIIq+=D~)^2f8&LOvmUxRn-ASKEr8F7=1uWz1rY1l^BN&x&W**q
z7eIvgOL>kx^2D(lK-gk1RhAge1`xhDv}#-YwhkadET$aJCN;(G&H+SvJXTeW+r0yb
z5R<7|<gVYwjck}P_bz+&%pEw-*!}qEo#lAQF@KR^z7BD^Z-C1d%vT{+glOIiz*URp
z>kucq2DsjV`8ve$E&vL`3zpYV3dEA|;@BEML3p+G8hIxCM=TN{=4}8JgiA$=`D#3R
z<90>3M9p#mrU~X(G{>Shb|b_f2cS>5SX(ajmN@jp?~>3^=;Z*2(AubX<2ISM#GoaH
zW0!97Qax)5jo93ki-*n=f7{|XcIQkjM(ctiwEODax!BV*$6jSL#d7S*wcc8cW6Zis
z7Y~^!?ixlto?|!8)KW|Z5aGFe!WeS~h#1rN7+Q(-Q9#mn;+~}Ul73~s&xh{|A0_>e
t^yr?P2=Hq3X1|fW8XW+-)&Zbv{R1#$Nns?;kY)e?002ovPDHLkV1gB0Q;7fo
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5f635fc10aba60c7360e69170c69507a1f4df38c
GIT binary patch
literal 362
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmSQK*5Dp-y;YjHK@;M7UB8wRq
zL=J;6qiBJz6HrjH#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWIa~o-U3d
z8I5meI&vK{5OGmYTB-g~>XEHY%id1w{0F8k{-H<GVobOXs!iA>!Sq>y#pus7`&)UF
z9E5Z?2bzD-7Yj&cG(F3m6MjX8QHJX$$Ad}F^2->`WuE)UP_X`0Tguyti9IUI&nTQs
zT$tw(xlV6cdidAP&v;8R`DWTMy%4|ld&*S1+v_IiUixF7z-S6K4G*#TS&3DW-OO`$
z&*y!pHfLZ6K9#ggkD=na*=Dx~ED7-^+!?O7ewBWv{O!`b^S9EP^miSNaOh>dVxs?n
ZQ7LXG$Haem)<7>ac)I$ztaD0e0ssbzf=~be
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ac69ea59a43370a3d777570c7e8d5a882a32cf13
GIT binary patch
literal 670
zc$@*A0%84$P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00004b3#c}2nYxW
zd<bNS00009a7bBm000!j000!j0TpmfLI3~&8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10vkz0K~!jg?b*+Z%~2S~@z<Rzl!daf@-O(YP$b#0kj9TlvB3g6
zTUjVuODPMnku3`~Op&r-WBgpBP!>WIW$s*yGZ)<%&79|ZzV~}4^R8QWbDrn(>3Qxs
z&pEf%?RG6>Mw%8-l|=(hvuL1cA_ku00+trm5~3B@Mtch;un(__D#|2Mfu4c2c#6?-
za`LJ28)#z%ZsI(a6j78(q=h<WH~tX&@wTd{9ICWXKO6B3JK}QkDQTdMl^DahVC-xn
zE%ej;`u#o*;zMqN97<N8pRLlFu`Q5e-qdNJja9gZ(`W^9%}Jz%eqdQ3*KHie$H3Zu
zGRRqIyR=JfDjBrS7Md;BYCOQHV2l|=TIf5L7s!1BNARUekU>_UpHV!)hAKgtG%$iS
zc!c9sg5*;28KW&7ikEOr8V<8)+5msfheMKyEQZ0r-!Wh0E^K6m@1ryQIf*N|iz5a~
zR-l9T(vCC{3D9&4jbojlc$zWLnT~N5JMpPXutB%bB)&=aggX`D{AQ2_y3+k%58ec0
z%wQ1HXwUFvY*tV_^;cjLQ#gUEfn0M^r-3QF!)|G4tA#pSXbkJsD4z1$Xm5)=gI)L>
z5tT#97V1cslLNRLR+LF816@4EUJLALvG@HVwivK@s#TyPz0w`S^<s)Lsbb)@G)2BF
zrY1n-RpOTPGI11Niz~_`VqmEFFaM<+Di#ei&4M@Z3!9jND^Z?P5&!@I07*qoM6N<$
Ef_wTT0ssI2
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..22853226316794982f1f88cf71659489bdbec03a
GIT binary patch
literal 450
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}Y)RhkE)4%c
zaKYZ?lYt_f1s;*b3=9&pL736Aw0R0pkiEpy*OmP)r?@z~<~9T22%wN;iEBiObAE1a
zYF-J0b5UwyNotBhd1gt5g1e`0KzJjcIM6CtPZ!6KjC*fyZOl3pAad;E{;TX8lswo4
zxm@2o3*KSzl~YD$OYn5A+O|B|J<j??A4O|;)C<=;O~2;4U8&>cg2_izq?`rzF-M+z
z=>3U%XJnqyyyt~yOAk+-&X5`Y?ux_eyS=53>At%fYNs~ZeW<uFpGo$C`V+-FOwRNF
z$bWdYMpBjc2aA;5-|7Tzo7N}sjn5|DTyXW>J<Dt}&vlJa2iC|b#&gaPZ48>#{X^*a
znODYI4Gc^i3OI-usSO-6YF$sPXG+U{V4iuv_`}=F^BMI8KfQNfAid!Enn-nCiOsj4
zYstv)A5hVGvt}l9==_>{k)I1s^*%}3Qzy5<#(dBH)|g|l%#wfKC_3!<#eR90snoHI
S^RIxR$>8bg=d#Wzp$PzZfvT$j
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..157964a742816e04bce47cb2e4e4844a9fad6bf4
GIT binary patch
literal 611
zc$@)a0-XJcP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L02rG902rGANp*vi00007bV*G`2i*x5
z7Y7~=jkZ?+000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0005LNkl<Z
zXx{Cbv1%1j5Qe|ii-Lj(c@rBOix|NarVvsD#A1qpIK{QQP0~p~2-pN<nn4aC1O-7+
zBq%8?tZY(Q3MoV(hzeptf?S(CfJt`u?!ZpH=imAE?3wuoPBk8n$sDIh9z8%0&=mkt
zyC-T3nRnnWur6wgnSY@Qa8Vm(J_8l7CTgSXhj>-gma|u(6;WHsUPhNiZ8duz7>U}N
zs8yL8+3>JJRapT57J)~ib~>|_5a4lL5R>vkw}7Xjb|$l#n+KkY+S$x*bPjkaYUeWB
z=~>{lsGZL|4_pN{MD0T6f#wSER@5$Lp2#i(o1!+Ad8E7qY-FE_U;XccgAOLYRKo9P
z1Q-LO68_*Qiv}G^eptdE9(7?C0R94ZN_ZS+@t_0AO9_8+T!Bdf900dV__LFUObTEh
zSSaByPAW7BfIVQogukgntZjflz|9i=t}ekg0e%B>CH#GzqHO{E0&bM>4|NN-0q_Hu
zE#V)V5N{e_2e@9sx0-rElK|g<YbE?slV7MQJN*TiZaVp<c3>NrDdC^ns19`k>;m5p
xlizNuHrEL-1AHyvKilk#Re#l>2j~HE$3GD^cUyWx>%{;7002ovPDHLkV1lt(?b-kU
rename from mobile/android/base/resources/drawable-mdpi/ic_menu_close_all_tabs.png
rename to mobile/android/base/resources/drawable-mdpi/close.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..64e7c9a3c200c669ee311ddc1452453e46c9020c
GIT binary patch
literal 497
zc$@+80S^9&P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10d7e|K~z|U?Uzk%!Y~kphpu~!h^VLsh(M6!{G1_2Xh4K@zAlV~
zq0Wq5Dn*s@B=78bU+m|ftN@u)Z?n2D0RIJ`5|K_s+Kjq|h#W<vF?#}9+W`Yk15g|N
z1y}=LzzAG{gVtXwbhs4=H2`~MOc|JaU2h+f1<*vyLx@<x51@ZC564tF0LO_!UKd~+
z^xr7vUR@4A!Oy9TK~%sea5DN1K$`>b93~CooM2v0`B;fAv8Ps(6<QO(3YVq}O8RpE
ze8{YIel`z7`Z9pv!gfwHk8ZhG%ef2QCGF@QMdX@XHIa0C`?3^`xaHdX)e`J7%tMGC
z7vPrw_S`R{TzQD{w*a+aKH@^D&CZBB%+|R8ZG!m;w==3lh$07|GYa-RhRh1-h`ZpD
z0V?7%poMEUF$Ub#-sAxIg5jIMPo);BSZLK(k+lo_tH=yf&}tr^aq4}Lat<`O^^1mR
nqP4gjnMd*pmS3|4U|xL#Wo(iwv{AY?00000NkvXXu0mjf#~;iT
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81712cabed27c3aba37a0789a41f0196af80459d
GIT binary patch
literal 252
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q
zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt+|FE{-7)
zt#7Xzay1z6v_9PbYRS5}ReksVn_N10)N#&~T^3Uoy;vafkkv@VW>)^%PQ##0s}@~n
z{lTrk@Y?gY%42<_hrA7}uhxhkP?aoy^Jz{k^M_1Mg$4#D4lr@S=ENC?`ER2VJf3mB
mXwW^~Ti!2SdzM9m_XgvIUy_Znsh%MJF?hQAxvX<aXaWEb+gQT@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..77f87b387b96f89fb54374c8452fcdc121a05e20
GIT binary patch
literal 463
zc$@*x0WkiFP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10Zd6mK~z|U?bo|5L{S*U@!z09MB*8gXq2K95g|f})=P-m18C@V
zB7#EV0VdHYTneH=Bs7XMhD;d6G0hZapUXHQR=xLL|MRVHUrwoBuPag=g}VO(1Ok+?
z)GmNhKoh>Bie0SW?WX~1==y@V#T0Jx0!Y#W-5A6v=86DljPJuXHpG?5q$CkE>jhVs
z#(i3VEZeXLLpaAo5dhGMe(YgA&3aiPXjTPhn8jl#K%OUIFGg{R@ge|-D>Z;4tfACu
zLY@&ctAb<9e>bSzm(fM50U=+O^u<y~gk|jK0gyyhJYW*nArtb9AQCs<aHx4GKu81;
zR<R&HqJxz+f*PJNgOfB1WNE@kd=JdvDUCVldmzFV#<G}`Jc1AL0keq1EXGLy8WFtW
zPQ1l_{7LqL1B~QpPFMxX*uiF=#tnsBMs3xv_%r(30H3RcRM*L2<_rJ;002ovPDHLk
FV1mZ@xbXl0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..00daa8355ada830aed3c42af0c2b525a08d306bc
GIT binary patch
literal 380
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Y)RhkE)4%c
zaKYZ?lYt_f1s;*b3=9&pL736Aw0R0pkiEpy*OmP)r?@z~$yKxZMxc;niEBiObAE1a
zYF-J0b5UwyNotBhd1gt5g1e`0KzJjcI8f1bPZ!6Kh{JEE8*&{o5OIj_=1}5`J9sdA
zhqa|u*@c6`FBCc^KVs#&w2-~<1H(BdcE|MCkQYi4(|PzZF9}_ETF>#MK5o+VlwXZO
zT){4jIqfE@6>6>vYkJ9$YrCIOoc%s))GsE>2}?cy=ruVXPm_MYBJ;(9*_eAj(<c3Y
z967n{J6M#Qb2A?PZo75BeL-BIZ@|~t&i`bT6qr!Z1EC#KS^nJfrL)TC-z(tJX<T+r
z{=k((=NnlsT=>Zy@tfmKV_d8F-Isy8togcbeU3lM?&PoG`%_qF6VQtcp00i_>zopr
E05AuHR{#J2
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bd04a5c0303183b2917c7d1e6a801d0b262f592f
GIT binary patch
literal 479
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Y)RhkE)4%c
zaKYZ?lYt_f1s;*b3=9&pL736Aw0R0pkiEpy*OmP)r?@z~<?4238=#P6iEBiObAE1a
zYF-J0b5UwyNotBhd1gt5g1e`0KzJjcIM6C9PZ!6Kh{H#(ulJUBkYIn{&fX*zX&{ki
zG^-@F+fV%mw`0pqy_lW*UpVN+MBGY9HAr<9^vRmkdN4x3P?K9pqUB@k&wtEKAL90X
zkU9Q)-~93v1Bnis#r`|z6j}Q3T+`0@L27=GUUwja#PRD>PT$sG@Ocn@Dpiq-Ve$?C
zoo8Gb6S(dN>unBXm~kxdRH_;m!}@|5MKk-D4!rqby=)PKL3{I*(<veh&)TmV3bHns
zA5JrsV{LG)@!Q!0)XFO#tf#+-VMFs?4~9KGEPoEPYaHO*R<*icu)#RzOPIXUst4*}
z)vL<|Yaf_Tt6E*JZ1qs}-q)~vrL=>l)2dfr7rgvI?0xm>dZkYX?tXZ>UrFeqZCKT6
zalx4%7-#RjdbuOyPsiJPuL_UEec<1{_iA&;*%w?ZxBq&&Gpk&s%Jx6MYh&aE3zjdN
QfkDmS>FVdQ&MBb@0FyktK>z>%
rename from mobile/android/base/resources/drawable-xhdpi/ic_menu_close_all_tabs.png
rename to mobile/android/base/resources/drawable-xhdpi/close.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f00e43782fcc7b279bcc8dd7ffedbf495c55045b
GIT binary patch
literal 1261
zc$@+41QPp+P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00004b3#c}2nYxW
zd<bNS00009a7bBm000}W000}W0bUxB8~^|S8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H11Zqh{K~#90?V8JrTtyVde?1ddGGnIa#S9Z>Cn6F=#I^CU%t8<)
zD#1WdaFdOHM79nDg9*{lF(E`EAxqzjVVI0DCcbsZN|XpjP~64&T%0cK?&-&^s?$?Q
z`h$XL=H7G8_uZ;n=bkz>N!-mCD;+>-j07khKxvEwC>=m)j07khKxvEwC>=m)cnL58
zG=NLMvXjkTI0766W?ZaE${mxER`nllO4{RKx7%20@pE0$bgs?ZfbNsDrtR32wB$&<
zCh1ty_Ew9ZYm(}v1lTQUy$kt(pCm0hkw4yre85#nQ#&odl%$P5<O6<?w2(>uL?7}2
z8?pzoBLdWAs#nE#lI9cqj7d6GfqcNaY)KAPfN9yfi^VsR<|4_TjvybfCVMFlMSvOE
zn>Q6-Nt&(TXIxUFc~1=afK^Eo75qdCa5N#9P7K<*#wDF?UYkNbpwY)i+lWboehsNZ
zM%u9#_z<Xf`QI+!EO4Odqn6=kvhtNxm1>TVh~zvSY5Uf-OVR}=+czaGt0JdZ0Z{jZ
zBn4sXx<}H*Oj`k)ip)Z5QuK_j0dE4Yl0Q|i0Jnhs88!kofqlTwF*l_M07m*P;4t|j
zc;H8j{4cRLrhW|ycm;SHBcJnP%srU+9(bGt@{m3Q0>&^A4pL?&d<#5*Eu$pl)&sVH
zL%<pGXF@E##-@ETdGINSYymF==h!(BZeUOTw_HQHOQ03wSR(VBm%$$R5_k&u-NTq?
zP4FM!Mc_S0o1ORqcoHY_;9nHIgQc?XJKF{L9DDM&J&g{dDtrfc9=P26?XkwL08ayd
zIM`~#u*%~10`twkbGF7H-YwwotcZGG7y?WI9{`Uz*b3t92hL+(jIQt&ppJd+KI&{2
z;2CVIbrr!=fN9_(;1Nfgop=^_7u#}O;3dE;@G<bPlf7OLmS;3@62U`&IgI>X2U~*>
zGR*_PJ2=FjYhWJu1b8SnkO%}3Ucx@avN+`2ya0R(JeXl4HAe^{90X31vDKyuu!xa=
zAk}dbIXOZQ;Sg4wNwSq9z!LBoaDR%!4qV2vyi^1c4r67ycoAX>h|PlIrc(j)qO2gg
zTh-1P`ymu)k14Xq8*}lptmNzw=ml9RJlLL6+<9X^+bbH90GFHsJg;{0Vbqm&?%2)8
zkuLVLPlo=CUSBOM4O^e*fETbY#!lSEK6h_q_zCZTh3b)2p8*qUZ-t3{Dh=7$-ylD;
zvV+uAT$df>4&`KMQr%l(dLfWMEj#kA>Ss=Nq#lcFitHPT6#!WiLci9SPV%QB$%ip7
zYp$q@t4in>N)Z4J-ns($6S6j!R6h%{Hk(1%P-34@YGA1M))mMf&m<qlqO9Sn7uJ>7
zACxNq+P-zQCL|u@!&p-H1Z+(SeL<cAAbaVww$4N)A`xSc+7q!=MfL-A8(s$^Gr;Ru
z7s(qQ276!`Siy3V*MPrr4SJpo{Vy@<7B3w@X^aFY9YAS}1SlOqX^aFY9YAT^^#c40
X@w81DKldN}00000NkvXXu0mjfR9Gk5
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bc3930fcc219c390aab75ba2d659264d19254c05
GIT binary patch
literal 389
zc%17D@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf<Z~8yL>4nJ
zNUsNB#yF{oGC)De64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<_&nLJ$_
zLn`LHon^?^<RHQ-AJ_HxMedgGOjUfgQ#Njil$I7&T_cdjYuqV1d;jE49g&iyQ`XGn
z)pbZ_h-x^-u!W(QF@sTx`2v$kG0P2gP38LSDt`~G`L{Lg&7Qv@Kl9qcAKqeouCiPI
zx}WE^sh@Y9`kG(C4OGmwfHk9o!MXj~66Uw!f7qcSHZ0TcZ7}6=;JzTyAhzJ6=7ukA
zYZJlt9GEB9q>I4>+V0=D)I5h5WIr3jw=V)YoM7gR#A8d7-rAlleDKTk<l>}z3zOcu
sKl>5#ZT&|vpfxZH3>kDCHveMqmr>R5lze6;0Sqz*Pgg&ebxsLQ095geRsaA1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2ce52f803941cc582a296542acddb3c94c9ccc7b
GIT binary patch
literal 853
zc$@)M1FHOqP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF00004b3#c}2nYxW
zd<bNS00009a7bBm000}W000}W0bUxB8~^|S8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H10@6uDK~#90?b<(Rl~)+Y@lUMP(n`g}$*Dr2LWdL-aVUt0OP91*
zgfvJjROlp5E{=i@4&tJlLzXzGv|U;RrBs4cY(T+91W{5d4o0FvqPY$y7cWtZ$@`r5
zyf5DGk_+Mfp6C2{e&@b_&OoQr@jkPnbOWX78&JA|()0}|-9Tyj29$1~G!X{;fxqy1
zfL84c1Y8Jp1!nLg&Q=wylZXlE8n6cAIEhsOy0s870rMDG_}p3e{5N*vZdLKzL{0#&
zjM$8S@J)bzR-z5)WgULOQG5_kCkqh~c<${B>_*~G?7_cPi$#-j0(cdPuQ7=o0rf)3
zZ9p&UrE7-6L3Kljh`=m9T;g|r#}9Z|wRlzPoPb_-Nxu@?0_s<ylmV~-mvJzlhh-BH
zfoJ%rqQ-fQV!CQGOQk8B(Gad<u<pVoBe0myFoFF+dhbM&41m=*g)w{*sQXr$5`o1G
zOE*-Tau;ga1oZNSWWsPJeKgwu_*ja=X(=AzG&KT?`AOO!H%1FLeFAzJlzu9QB1PL|
z0DOvbIBx0pA8#b$N^n|gtZpXoJ?>W#i7?<1)>ai+CO7eA6@j)zU@;R_1=?!B3mg(~
z*}SrPFSQ%+pY(k3a)el04Cp>wy&`RVQ&GZgiNF9(V7tJ@(~b#vf_*Z*b8NZ+xA5J<
z-z+pa0^`^s<Kk(i0dqJY?esIj+ON^H2uw-4{FOk>|D{P2a7nu3t8MW#!+;KsVJ99`
zZ)a&lL}0$ccOPRfE(G+kEK2SHH}EZP2lTLPN=0A{TNPeB^&2pUpD}``0ktbrX9Vs^
z2dAzE)C?o{1a$v*5npN8;;APBN2K!r#ScTgiJTFbmQI2E7EmJ_xeS<;?&9tQ=w~HI
z1kOlDUmCx7Vhxy;9#W1<r$$;qL<D}7Oc=qn0KHm?crnsy+VXZd8+`*xH&B|s0i_!#
fP2Ygh4V303iw~YqV&68P00000NkvXXu0mjfz*lU%
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e13b1c303dbbb27e86adb3f5887897e1145b8e8d
GIT binary patch
literal 467
zc%17D@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=85ksHgD|6KY4a4IAbW|YuPggqPH}NI)n#+@jDSLtC9V-A&iT2y
zsd*&~&PAz-C8;S2<(VZJ3hti10pX2&;y|nPJY5_^DsH{Kz0vD%fJECvbFI9!3;OO&
z>~3yOZqInTYW9K!;ulOsj9bp}>D`;Sm_tji>BvDIMt;-Y<t`_F>q`F9SAVED$46cB
z^d!6V-g)i~`<Qb|K3)j4`g!@U>%7164S`mX?CTm0H%PCrVJd#G>cr;rs?+6}j1%M~
zSQzd%uqVVkxtSdve}K1OW5T}hvy<bi7Wy<UX8r7FTF-uI-M5CzZF?t+Wy`z0Tg#HL
zJ28Lx(Oc0ItN*dG2skh>l0z(roTvQ#Rb}U$XwCyVMeFD7KCxH6voP(&rd@Kvb|zn0
z?p&)n{bbJjVn_A^X$k)r?=Yw5#@l^-v1axH2Bt5T@$Rp*<PY{Vp9BUkgQu&X%Q~lo
FCIF4arRe|w
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..db491e74d47849edf5803efecb656d49e0ce7b6e
GIT binary patch
literal 550
zc%17D@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=85ksHgD|6KY4a4IAbW|YuPggqPH}NIEA6St$ALnUC9V-A&iT2y
zsd*&~&PAz-C8;S2<(VZJ3hti10pX2&;y|k!JzX3_DsH{Ky|MSO0T1hi`3gsQ1r<)n
zt;`b=%`$O)qgm3*b+)K|_M@ox4^8elDE*X_n;pXVNj_<|(!<O9-gm^Lv#js_%I~q0
zr*{ALQ{RD%m|(sB6VnfvH_V@M+F0wr-ybnI1Wodqu5d*8Ge)tTJ+7eIR3`7xo^X*P
zP%)yk$%JFtS_9UmuaOCiA9{Ea`VJIHnK&m{@y%A)aF+Rspai?pDvmY6il!{jCeGku
z`t1`SbMSCLh{(ayArU+WFGs|%9K0QuVAXW|=!p=P^|Kdda?A@)4i?z+o~^xI>-`29
zS*MEWZ$C^b@|?)X@Icw<z^&-NU0fT!g-u`ay<y>pdyLl>i*wv$_u+gmnh;gd&k%gL
znt6uYJ>dqA3Vnv8hqeqIZRzX+{O5TV2+b39(6v!+$f_`7`1-+zLG58A!<j?I3^Urc
zvq{LMRj@7K$Y*sBzQ@#{wa1Jhw!)X;)Q2dBEe};0|Jk&#Ke;FKm!;tK+J8-ZTo|fW
lOE>WHz54S1Tg8VeM&_@%pA)@xcLQUL!PC{xWt~$(69CqF(%k?6
--- a/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
@@ -57,31 +57,31 @@
             <item android:id="@+id/addons"
                   android:icon="@drawable/ic_menu_addons"
                   android:title="@string/addons"/>
 
             <item android:id="@+id/apps"
                   android:icon="@drawable/ic_menu_apps"
                   android:title="@string/apps"/>
 
+            <item android:id="@+id/new_guest_session"
+                  android:icon="@drawable/ic_menu_guest"
+                  android:visible="false"
+                  android:title="@string/new_guest_session"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
 
-    <item android:id="@+id/new_guest_session"
-          android:icon="@drawable/ic_menu_guest"
-          android:visible="false"
-          android:title="@string/new_guest_session"/>
-
     <item android:id="@+id/exit_guest_session"
           android:icon="@drawable/ic_menu_guest"
           android:visible="false"
           android:title="@string/exit_guest_session"/>
 </menu>
--- a/mobile/android/base/resources/menu-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-v11/browser_app_menu.xml
@@ -58,31 +58,31 @@
             <item android:id="@+id/addons"
                   android:icon="@drawable/ic_menu_addons"
                   android:title="@string/addons"/>
 
             <item android:id="@+id/apps"
                   android:icon="@drawable/ic_menu_apps"
                   android:title="@string/apps"/>
 
+            <item android:id="@+id/new_guest_session"
+                  android:icon="@drawable/ic_menu_guest"
+                  android:visible="false"
+                  android:title="@string/new_guest_session"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
 
-    <item android:id="@+id/new_guest_session"
-          android:icon="@drawable/ic_menu_guest"
-          android:visible="false"
-          android:title="@string/new_guest_session"/>
-
     <item android:id="@+id/exit_guest_session"
           android:icon="@drawable/ic_menu_guest"
           android:visible="false"
           android:title="@string/exit_guest_session"/>
 </menu>
--- a/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
+++ b/mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
@@ -58,31 +58,31 @@
             <item android:id="@+id/addons"
                   android:icon="@drawable/ic_menu_addons"
                   android:title="@string/addons"/>
 
             <item android:id="@+id/apps"
                   android:icon="@drawable/ic_menu_apps"
                   android:title="@string/apps"/>
 
+            <item android:id="@+id/new_guest_session"
+                  android:icon="@drawable/ic_menu_guest"
+                  android:visible="false"
+                  android:title="@string/new_guest_session"/>
+
         </menu>
 
     </item>
 
     <item android:id="@+id/char_encoding"
           android:icon="@drawable/ic_menu_character_encoding"
           android:visible="false"
           android:title="@string/char_encoding"/>
 
     <item android:id="@+id/settings"
           android:icon="@drawable/ic_menu_settings"
           android:title="@string/settings" />
 
-    <item android:id="@+id/new_guest_session"
-          android:icon="@drawable/ic_menu_guest"
-          android:visible="false"
-          android:title="@string/new_guest_session"/>
-
     <item android:id="@+id/exit_guest_session"
           android:icon="@drawable/ic_menu_guest"
           android:visible="false"
           android:title="@string/exit_guest_session"/>
 </menu>
--- a/mobile/android/base/tests/PixelTest.java.in
+++ b/mobile/android/base/tests/PixelTest.java.in
@@ -1,20 +1,25 @@
 #filter substitution
 package @ANDROID_PACKAGE_NAME@.tests;
 
 import @ANDROID_PACKAGE_NAME@.*;
+import android.os.Build;
 
 abstract class PixelTest extends BaseTest {
     private static final long PAINT_CLEAR_DELAY = 10000; // milliseconds
 
     protected final PaintedSurface loadAndGetPainted(String url) {
         Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();
         loadUrl(url);
-        verifyHomePagerHidden();
+        // Skip this on the Tegras (Android 2.2) since they are too slow,
+        // which results in too many intermittent failures.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+            verifyHomePagerHidden();
+        }
         paintExpecter.blockUntilClear(PAINT_CLEAR_DELAY);
         paintExpecter.unregisterListener();
         PaintedSurface p = mDriver.getPaintedSurface();
         if (p == null) {
             mAsserter.ok(p != null, "checking that painted surface loaded", 
                  "painted surface loaded");
         }
         return p;
--- a/mobile/android/base/tests/testSettingsMenuItems.java.in
+++ b/mobile/android/base/tests/testSettingsMenuItems.java.in
@@ -138,22 +138,28 @@ public class testSettingsMenuItems exten
      * Check for conditions for building certain settings, and add them to be tested
      * if they are present.
      */
     public void addConditionalSettings(Map<String, List<String[]>> settingsMap) {
         try {
             ClassLoader classLoader = getActivity().getClassLoader();
             Class appConstants = classLoader.loadClass("org.mozilla.gecko.AppConstants");
 
-            // Text reflow
-            Field textReflowField = appConstants.getField("RELEASE_BUILD");
-            boolean textReflow = textReflowField.getBoolean(appConstants);
-            if (textReflow) {
+            // Preferences dependent on RELEASE_BUILD
+            Field releaseBuildField = appConstants.getField("RELEASE_BUILD");
+            boolean releaseBuild = releaseBuildField.getBoolean(appConstants);
+            if (!releaseBuild) {
+                // Text reflow - only built if *not* release build
                 String[] textReflowUi = { "Text reflow" };
                 settingsMap.get("Display").add(textReflowUi);
+
+                // Anonymous cell tower/wifi collection - only built if *not* release build
+                String[] networkReportingUi = { "Mozilla location services", "Help improve geolocation services for the Open Web by letting " + BRAND_NAME + " collect and send anonymous cellular tower data" };
+                settingsMap.get("Mozilla").add(networkReportingUi);
+
             }
 
             // Automatic updates
             Field autoUpdateField = appConstants.getField("MOZ_UPDATER");
             boolean autoUpdate = autoUpdateField.getBoolean(appConstants);
             if (autoUpdate) {
                 String[] autoUpdateUi = { "Automatic updates", "Only over Wi-Fi", "Enabled", "Only over Wi-Fi", "Disabled" };
                 settingsMap.get("Customize").add(autoUpdateUi);
@@ -162,24 +168,16 @@ public class testSettingsMenuItems exten
             // Crash reporter
             Field crashReportingField = appConstants.getField("MOZ_CRASHREPORTER");
             boolean crashReporter = crashReportingField.getBoolean(appConstants);
             if (crashReporter) {
                 String[] crashReporterUi = { "Crash Reporter", BRAND_NAME + " submits crash reports to help Mozilla make your browser more stable and secure" };
                 settingsMap.get("Mozilla").add(crashReporterUi);
             }
 
-            // Anonymous cell tower/wifi collection; built if *not* a RELEASE_BUILD
-            Field releaseBuildField = appConstants.getField("RELEASE_BUILD");
-            boolean releaseBuild = releaseBuildField.getBoolean(appConstants);
-            if (!releaseBuild) {
-                String[] networkReportingUi = { "Mozilla location services", "Help improve geolocation services for the Open Web by letting " + BRAND_NAME + " collect and send anonymous cellular tower data" };
-                settingsMap.get("Mozilla").add(networkReportingUi);
-            }
-
             // Telemetry
             Field telemetryField = appConstants.getField("MOZ_TELEMETRY_REPORTING");
             boolean telemetry = telemetryField.getBoolean(appConstants);
             if (telemetry) {
                 String[] telemetryUi = { "Telemetry", "Shares performance, usage, hardware and customization data about your browser with Mozilla to help us make " + BRAND_NAME + " better" };
                 settingsMap.get("Mozilla").add(telemetryUi);
             }
         } catch (ClassNotFoundException e) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5131,20 +5131,26 @@ var FormAssistant = {
           this._hideFormAssistPopup();
         };
 
         this._showAutoCompleteSuggestions(currentElement, checkResultsInput);
         break;
 
       // Reset invalid submit state on each pageshow
       case "pageshow":
-        let target = aEvent.originalTarget;
-        let selectedDocument = BrowserApp.selectedBrowser.contentDocument;
-        if (target == selectedDocument || target.ownerDocument == selectedDocument)
-          this._invalidSubmit = false;
+        if (!this._invalidSubmit)
+          return;
+
+        let selectedBrowser = BrowserApp.selectedBrowser;
+        if (selectedBrowser) {
+          let selectedDocument = selectedBrowser.contentDocument;
+          let target = aEvent.originalTarget;
+          if (target == selectedDocument || target.ownerDocument == selectedDocument)
+            this._invalidSubmit = false;
+        }
     }
   },
 
   // We only want to show autocomplete suggestions for certain elements
   _isAutoComplete: function _isAutoComplete(aElement) {
     if (!(aElement instanceof HTMLInputElement) || aElement.readOnly ||
         (aElement.getAttribute("type") == "password") ||
         (aElement.hasAttribute("autocomplete") &&
--- a/mobile/android/chrome/content/downloads.js
+++ b/mobile/android/chrome/content/downloads.js
@@ -5,24 +5,46 @@
 
 "use strict";
 
 function dump(a) {
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
 }
 
 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
+const URI_PAUSE_ICON = "drawable://pause";
+const URI_CANCEL_ICON = "drawable://close";
+const URI_RESUME_ICON = "drawable://play";
+
+const PAUSE_ACTION = {
+  actionKind : "pause",
+  title : Strings.browser.GetStringFromName("alertDownloadsPause"),
+  icon : URI_PAUSE_ICON
+};
+
+const CANCEL_ACTION = {
+  actionKind : "cancel",
+  title : Strings.browser.GetStringFromName("alertDownloadsCancel"),
+  icon : URI_CANCEL_ICON
+};
+
+const RESUME_ACTION = {
+  actionKind : "resume",
+  title : Strings.browser.GetStringFromName("alertDownloadsResume"),
+  icon : URI_RESUME_ICON
+};
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 var Downloads = {
   _initialized: false,
   _dlmgr: null,
   _progressAlert: null,
   _privateDownloads: [],
+  _showingPrompt: false,
 
   _getLocalFile: function dl__getLocalFile(aFileURI) {
     // if this is a URL, get the file from that
     // XXX it's possible that using a null char-set here is bad
     const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL);
     return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
   },
 
@@ -31,16 +53,17 @@ var Downloads = {
       return;
     this._initialized = true;
 
     // Monitor downloads and display alerts
     this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
     this._progressAlert = new AlertDownloadProgressListener();
     this._dlmgr.addPrivacyAwareListener(this._progressAlert);
     Services.obs.addObserver(this, "last-pb-context-exited", true);
+    Services.obs.addObserver(this, "Notification:Event", true);
   },
 
   openDownload: function dl_openDownload(aDownload) {
     let fileUri = aDownload.target.spec;
     let guid = aDownload.guid;
     let f = this._getLocalFile(fileUri);
     try {
       f.launch();
@@ -55,81 +78,139 @@ var Downloads = {
     aDownload.cancel();
     
     let fileURI = aDownload.target.spec;
     let f = this._getLocalFile(fileURI);
 
     OS.File.remove(f.path);
   },
 
-  showAlert: function dl_showAlert(aDownload, aMessage, aTitle, aIcon) { 
-    let self = this;
-
-    // Use this flag to make sure we only show one prompt at a time
-    let cancelPrompt = false;
+  getNotificationIdFromDownload: function dl_getNotificationIdFromDownload(aDownload) {
+    return aDownload.target.spec.replace("file:", "download:");
+  },
 
-    // Callback for tapping on the alert popup
-    let observer = {
-      observe: function (aSubject, aTopic, aData) {
-        if (aTopic == "alertclickcallback") {
-          if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
-            // Only open the downloaded file if the download is complete
-            self.openDownload(aDownload);
-          } else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING &&
-                     !cancelPrompt) {
-            cancelPrompt = true;
-            // Open a prompt that offers a choice to cancel the download
-            let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
-            let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
-            let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
-                        Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
-
-            let choice = Services.prompt.confirmEx(null, title, message, flags,
-                                                   null, null, null, null, {});
-            if (choice == 0)
-              self.cancelDownload(aDownload);
-            cancelPrompt = false;
-          }
-        }
-      }
+  showNotification: function dl_showNotification(aDownload, aMessage, aTitle, aOptions) {
+    let msg = {
+        type: "Notification:Show",
+        id: this.getNotificationIdFromDownload(aDownload),
+        title: aTitle,
+        smallIcon: URI_GENERIC_ICON_DOWNLOAD,
+        text: aMessage,
+        ongoing: false,
+        cookie: aDownload.guid
     };
 
-    if (!aIcon)
-      aIcon = URI_GENERIC_ICON_DOWNLOAD;
-
-    if (aDownload.isPrivate) {
-      this._privateDownloads.push(aDownload);
+    if (aOptions && aOptions.icon) {
+      msg.smallIcon = aOptions.icon;
     }
 
-    var notifier = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    notifier.showAlertNotification(aIcon, aTitle, aMessage, true, "", observer,
-                                   aDownload.target.spec.replace("file:", "download:"));
+    if (aOptions && aOptions.percentComplete) {
+      msg.progress_value = aOptions.percentComplete;
+      msg.progress_max = 100;
+    }
+    if (aOptions && aOptions.actions) {
+      msg.actions = aOptions.actions;
+    }
+    if (aOptions && aOptions.ongoing) {
+      msg.ongoing = aOptions.ongoing;
+    }
+    this._bridge.handleGeckoMessage(JSON.stringify(msg));
+  },
+
+  removeNotification: function dl_removeNotification(aDownload) {
+    let msg = {
+        type: "Notification:Hide",
+        id: this.getNotificationIdFromDownload(aDownload)
+    };
+    this._bridge.handleGeckoMessage(JSON.stringify(msg));
+  },
+
+  showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) {
+    if (this._showingPrompt)
+      return;
+    this._showingPrompt = true;
+    // Open a prompt that offers a choice to cancel the download
+    let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle");
+    let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage");
+    let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
+                Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO;
+    let choice = Services.prompt.confirmEx(null, title, message, flags,
+                                           null, null, null, null, {});
+    if (choice == 0)
+      this.cancelDownload(aDownload);
+    this._showingPrompt = false;
+  },
+
+  handleClickEvent: function dl_handleClickEvent(aDownload) {
+    // Only open the downloaded file if the download is complete
+    if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED)
+      this.openDownload(aDownload);
+    else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING ||
+                aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED)
+      this.showCancelConfirmPrompt(aDownload);
+  },
+
+  handleNotificationEvent: function dl_handleNotificationEvent(aNotifData, aDownload) {
+    switch (aNotifData.eventType) {
+      case "notification-clicked":
+        this.handleClickEvent(aDownload);
+        break;
+      case "cancel":
+        this.cancelDownload(aDownload);
+        break;
+      case "pause":
+        aDownload.pause();
+        break;
+      case "resume":
+        aDownload.resume();
+        break;
+      case "notification-cleared":
+        // notification cleared by the user
+        break;
+    }
   },
 
   // observer for last-pb-context-exited
   observe: function dl_observe(aSubject, aTopic, aData) {
-    let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-    let download;
-    while ((download = this._privateDownloads.pop())) {
-      try {
-        let notificationName = download.target.spec.replace("file:", "download:");
-        progressListener.onCancel(notificationName);
-      } catch (e) {
-        dump("Error removing private download: " + e);
+    switch (aTopic) {
+      case "Notification:Event": {
+        let data = JSON.parse(aData);
+        let guid = data.cookie;
+        this._dlmgr.getDownloadByGUID(guid, (function(status, download) {
+          if (Components.isSuccessCode(status))
+            this.handleNotificationEvent(data, download);
+        }).bind(this));
+        break;
+      }
+      case "last-pb-context-exited": {
+        let download;
+        while ((download = this._privateDownloads.pop())) {
+          try {
+            Downloads.removeNotification(download);
+          } catch (e) {
+            dump("Error removing private download: " + e);
+          }
+        }
+        break;
       }
     }
   },
 
   QueryInterface: function (aIID) {
     if (!aIID.equals(Ci.nsISupports) &&
         !aIID.equals(Ci.nsIObserver) &&
         !aIID.equals(Ci.nsISupportsWeakReference))
       throw Components.results.NS_ERROR_NO_INTERFACE;
     return this;
+  },
+
+  get _bridge() {
+    delete this._bridge;
+    return this._bridge = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
+
   }
 };
 
 // AlertDownloadProgressListener is used to display progress in the alert notifications.
 function AlertDownloadProgressListener() { }
 
 AlertDownloadProgressListener.prototype = {
   //////////////////////////////////////////////////////////////////////////////
@@ -138,59 +219,61 @@ AlertDownloadProgressListener.prototype 
     let strings = Strings.browser;
     let availableSpace = -1;
     try {
       // diskSpaceAvailable is not implemented on all systems
       let availableSpace = aDownload.targetFile.diskSpaceAvailable;
     } catch(ex) { }
     let contentLength = aDownload.size;
     if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) {
-      Downloads.showAlert(aDownload, strings.GetStringFromName("alertDownloadsNoSpace"),
+      Downloads.showNotification(aDownload, strings.GetStringFromName("alertDownloadsNoSpace"),
                                      strings.GetStringFromName("alertDownloadsSize"));
-
       aDownload.cancel();
     }
 
     if (aDownload.percentComplete == -1) {
       // Undetermined progress is not supported yet
       return;
     }
-    let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-    let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-    let notificationName = aDownload.target.spec.replace("file:", "download:");
-    progressListener.onProgress(notificationName, aDownload.percentComplete, 100);
+    Downloads.showNotification(aDownload, aDownload.percentComplete + "%",
+                            aDownload.displayName, { percentComplete: aDownload.percentComplete,
+                                                     ongoing: true,
+                                                     actions: [PAUSE_ACTION, CANCEL_ACTION] });
+
   },
 
   onDownloadStateChange: function(aState, aDownload) {
     let state = aDownload.state;
     switch (state) {
       case Ci.nsIDownloadManager.DOWNLOAD_QUEUED:
         NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long");
-        Downloads.showAlert(aDownload, Strings.browser.GetStringFromName("alertDownloadsStart2"),
+        Downloads.showNotification(aDownload, Strings.browser.GetStringFromName("alertDownloadsStart2"),
                             aDownload.displayName);
         break;
+      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
+        Downloads.showNotification(aDownload, aDownload.percentComplete + "%",
+                            aDownload.displayName, { percentComplete: aDownload.percentComplete,
+                                                     ongoing: true,
+                                                     actions: [RESUME_ACTION, CANCEL_ACTION] });
+        break;
       case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
       case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
       case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
       case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
       case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: {
-        let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
-        let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener);
-        let notificationName = aDownload.target.spec.replace("file:", "download:");
-        progressListener.onCancel(notificationName);
-
+        Downloads.removeNotification(aDownload);
         if (aDownload.isPrivate) {
           let index = this._privateDownloads.indexOf(aDownload);
           if (index != -1) {
             this._privateDownloads.splice(index, 1);
           }
         }
 
         if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) {
-          Downloads.showAlert(aDownload, Strings.browser.GetStringFromName("alertDownloadsDone2"),
+          Downloads.showNotification(aDownload, Strings.browser.GetStringFromName("alertDownloadsDone2"),
                               aDownload.displayName);
         }
         break;
       }
     }
   },
 
   onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { },
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -11,16 +11,19 @@ alertAddonsInstalledNoRestart=Installati
 alertAddonsFail=Installation failed
 
 alertDownloadsStart2=Download starting
 alertDownloadsDone2=Download complete
 alertCantOpenDownload=Can't open file. Tap to save it.
 alertDownloadsSize=Download too big
 alertDownloadsNoSpace=Not enough storage space
 alertDownloadsToast=Download started…
+alertDownloadsPause=Pause
+alertDownloadsResume=Resume
+alertDownloadsCancel=Cancel
 
 alertFullScreenToast=Press BACK to leave full-screen mode
 
 downloadCancelPromptTitle=Cancel Download
 downloadCancelPromptMessage=Do you want to cancel this download?
 
 # LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
 # #1 is the add-on name, #2 is the add-on host, #3 is the application name
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -4,19 +4,18 @@
  * 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/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
-                                  "resource://gre/modules/FormHistory.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+Cu.import("resource://gre/modules/FormHistory.jsm");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 function sendMessageToJava(aMessage) {
   return Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
 }
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -308,17 +308,17 @@ this.DownloadIntegration = {
   _downloadsDirectory: null,
 
   /**
    * Returns the user downloads directory asynchronously.
    *
    * @return {Promise}
    * @resolves The nsIFile of downloads directory.
    */
-  getUserDownloadsDirectory: function DI_getUserDownloadsDirectory() {
+  getPreferredDownloadsDirectory: function DI_getPreferredDownloadsDirectory() {
     return Task.spawn(function() {
       let directory = null;
       let prefValue = 1;
 
       try {
         prefValue = Services.prefs.getIntPref("browser.download.folderList");
       } catch(e) {}
 
@@ -351,17 +351,17 @@ this.DownloadIntegration = {
    *
    * @return {Promise}
    * @resolves The nsIFile of downloads directory.
    */
   getTemporaryDownloadsDirectory: function DI_getTemporaryDownloadsDirectory() {
     return Task.spawn(function() {
       let directory = null;
 #ifdef XP_MACOSX
-      directory = yield this.getUserDownloadsDirectory();
+      directory = yield this.getPreferredDownloadsDirectory();
 #elifdef ANDROID
       directory = yield this.getSystemDownloadsDirectory();
 #else
       // For Metro mode on Windows 8,  we want searchability for documents
       // that the user chose to open with an external application.
       if (this._isImmersiveProcess()) {
         directory = yield this.getSystemDownloadsDirectory();
       } else {
--- a/toolkit/components/jsdownloads/src/Downloads.jsm
+++ b/toolkit/components/jsdownloads/src/Downloads.jsm
@@ -268,18 +268,18 @@ this.Downloads = {
 
   /**
    * Returns the preferred downloads directory based on the user preferences
    * in the current profile asynchronously.
    *
    * @return {Promise}
    * @resolves The nsIFile of downloads directory.
    */
-  getUserDownloadsDirectory: function D_getUserDownloadsDirectory() {
-    return DownloadIntegration.getUserDownloadsDirectory();
+  getPreferredDownloadsDirectory: function D_getPreferredDownloadsDirectory() {
+    return DownloadIntegration.getPreferredDownloadsDirectory();
   },
 
   /**
    * Returns the temporary directory where downloads are placed before the
    * final location is chosen, or while the document is opened temporarily
    * with an external application. This may or may not be the system temporary
    * directory, based on the platform asynchronously.
    *
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadIntegration.js
@@ -111,90 +111,90 @@ add_task(function test_getSystemDownload
 
   let downloadDirBefore = yield DownloadIntegration.getSystemDownloadsDirectory();
   cleanup();
   let downloadDirAfter = yield DownloadIntegration.getSystemDownloadsDirectory();
   do_check_false(downloadDirBefore.equals(downloadDirAfter));
 });
 
 /**
- * Tests that the getUserDownloadsDirectory returns a valid nsFile
+ * Tests that the getPreferredDownloadsDirectory returns a valid nsFile
  * download directory object.
  */
-add_task(function test_getUserDownloadsDirectory()
+add_task(function test_getPreferredDownloadsDirectory()
 {
   let folderListPrefName = "browser.download.folderList";
   let dirPrefName = "browser.download.dir";
   function cleanup() {
     Services.prefs.clearUserPref(folderListPrefName);
     Services.prefs.clearUserPref(dirPrefName);
   }
   do_register_cleanup(cleanup);
 
   // Should return the system downloads directory.
   Services.prefs.setIntPref(folderListPrefName, 1);
   let systemDir = yield DownloadIntegration.getSystemDownloadsDirectory();
-  let downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  let downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
   do_check_eq(downloadDir.path, systemDir.path);
 
   // Should return the desktop directory.
   Services.prefs.setIntPref(folderListPrefName, 0);
-  downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
   do_check_eq(downloadDir.path, Services.dirsvc.get("Desk", Ci.nsIFile).path);
 
   // Should return the system downloads directory because the dir preference
   // is not set.
   Services.prefs.setIntPref(folderListPrefName, 2);
-  let downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  let downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
   do_check_eq(downloadDir.path, systemDir.path);
 
   // Should return the directory which is listed in the dir preference.
   let time = (new Date()).getTime();
   let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
   tempDir.append(time);
   Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, tempDir);
-  downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
   do_check_eq(downloadDir.path,  tempDir.path);
   do_check_true(yield OS.File.exists(downloadDir.path));
   yield OS.File.removeEmptyDir(tempDir.path);
 
   // Should return the system downloads directory beacause the path is invalid
   // in the dir preference.
   tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
   tempDir.append("dir_not_exist");
   tempDir.append(time);
   Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, tempDir);
-  downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_eq(downloadDir.path, systemDir.path);
 
   // Should return the system downloads directory because the folderList
   // preference is invalid
   Services.prefs.setIntPref(folderListPrefName, 999);
-  let downloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
+  let downloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
   do_check_eq(downloadDir.path, systemDir.path);
 
   cleanup();
 });
 
 /**
  * Tests that the getTemporaryDownloadsDirectory returns a valid nsFile 
  * download directory object.
  */
 add_task(function test_getTemporaryDownloadsDirectory()
 {
   let downloadDir = yield DownloadIntegration.getTemporaryDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
 
   if ("nsILocalFileMac" in Ci) {
-    let userDownloadDir = yield DownloadIntegration.getUserDownloadsDirectory();
-    do_check_eq(downloadDir.path, userDownloadDir.path);
+    let preferredDownloadDir = yield DownloadIntegration.getPreferredDownloadsDirectory();
+    do_check_eq(downloadDir.path, preferredDownloadDir.path);
   } else {
     let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
     do_check_eq(downloadDir.path, tempDir.path);
   }
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests DownloadObserver
--- a/toolkit/components/jsdownloads/test/unit/test_Downloads.js
+++ b/toolkit/components/jsdownloads/test/unit/test_Downloads.js
@@ -141,22 +141,22 @@ add_task(function test_getSummary()
  */
 add_task(function test_getSystemDownloadsDirectory()
 {
   let downloadDir = yield Downloads.getSystemDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
 });
 
 /**
- * Tests that the getUserDownloadsDirectory returns a valid nsFile
+ * Tests that the getPreferredDownloadsDirectory returns a valid nsFile
  * download directory object.
  */
-add_task(function test_getUserDownloadsDirectory()
+add_task(function test_getPreferredDownloadsDirectory()
 {
-  let downloadDir = yield Downloads.getUserDownloadsDirectory();
+  let downloadDir = yield Downloads.getPreferredDownloadsDirectory();
   do_check_true(downloadDir instanceof Ci.nsIFile);
 });
 
 /**
  * Tests that the getTemporaryDownloadsDirectory returns a valid nsFile
  * download directory object.
  */
 add_task(function test_getTemporaryDownloadsDirectory()
--- a/toolkit/components/osfile/modules/ospath_win.jsm
+++ b/toolkit/components/osfile/modules/ospath_win.jsm
@@ -209,28 +209,30 @@ exports.winIsAbsolute = winIsAbsolute;
 
 /**
  * Normalize a path by removing any unneeded ".", "..", "\\".
  * Also convert any "/" to a "\\".
  */
 let normalize = function(path) {
   let stack = [];
 
+  if (!path.startsWith("\\\\")) {
+    // Normalize "/" to "\\"
+    path = path.replace(/\//g, "\\");
+  }
+
   // Remove the drive (we will put it back at the end)
   let root = this.winGetDrive(path);
   if (root) {
     path = path.slice(root.length);
   }
 
   // Remember whether we need to restore a leading "\\" or drive name.
   let absolute = this.winIsAbsolute(path);
 
-  // Normalize "/" to "\\"
-  path = path.replace("/", "\\");
-
   // And now, fill |stack| from the components,
   // popping whenever there is a ".."
   path.split("\\").forEach(function loop(v) {
     switch (v) {
     case "":  case ".": // Ignore
       break;
     case "..":
       if (stack.length == 0) {
--- a/toolkit/components/osfile/tests/xpcshell/test_path.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_path.js
@@ -83,16 +83,31 @@ function run_test()
   do_check_eq(Win.join("c:\\tmp", "c:foo", "bar"), "c:foo\\bar", "join c:\\tmp,c:foo,bar");
   do_check_eq(Win.winGetDrive("c:"), "c:");
   do_check_eq(Win.winGetDrive("c:\\"), "c:");
   do_check_eq(Win.winGetDrive("c:abc"), "c:");
   do_check_eq(Win.winGetDrive("c:abc\\d\\e\\f\\g"), "c:");
   do_check_eq(Win.winGetDrive("c:\\abc"), "c:");
   do_check_eq(Win.winGetDrive("c:\\abc\\d\\e\\f\\g"), "c:");
 
+  do_print("Forwardslash-separated, no drive");
+  do_check_eq(Win.normalize("/a/b/c"), "\\a\\b\\c");
+  do_check_eq(Win.normalize("/a/b////c"), "\\a\\b\\c");
+  do_check_eq(Win.normalize("/a/b/c///"), "\\a\\b\\c");
+  do_check_eq(Win.normalize("/a/b/c/../../../d/e/f"), "\\d\\e\\f");
+  do_check_eq(Win.normalize("a/b/c/../../../d/e/f"), "d\\e\\f");
+
+  do_print("Forwardslash-separated, with a drive");
+  do_check_eq(Win.normalize("c:/a/b/c"), "c:\\a\\b\\c");
+  do_check_eq(Win.normalize("c:/a/b////c"), "c:\\a\\b\\c");
+  do_check_eq(Win.normalize("c:////a/b/c"), "c:\\a\\b\\c");
+  do_check_eq(Win.normalize("c:/a/b/c///"), "c:\\a\\b\\c");
+  do_check_eq(Win.normalize("c:/a/b/c/../../../d/e/f"), "c:\\d\\e\\f");
+  do_check_eq(Win.normalize("c:a/b/c/../../../d/e/f"), "c:d\\e\\f");
+
   do_print("Backslash-separated, UNC-style");
   do_check_eq(Win.basename("\\\\a\\b"), "b");
   do_check_eq(Win.basename("\\\\a\\b\\"), "");
   do_check_eq(Win.basename("\\\\abc"), "");
   do_check_eq(Win.dirname("\\\\a\\b"), "\\\\a");
   do_check_eq(Win.dirname("\\\\a\\b\\"), "\\\\a\\b");
   do_check_eq(Win.dirname("\\\\a\\\\\\\\b"), "\\\\a");
   do_check_eq(Win.dirname("\\\\abc"), "\\\\abc");
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -14,17 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/Services.jsm");
 
 this.EXPORTED_SYMBOLS = ["LayoutHelpers"];
 
 this.LayoutHelpers = LayoutHelpers = function(aTopLevelWindow) {
   this._topDocShell = aTopLevelWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                      .getInterface(Ci.nsIWebNavigation)
                                      .QueryInterface(Ci.nsIDocShell);
-}
+};
 
 LayoutHelpers.prototype = {
 
   /**
    * Compute the position and the dimensions for the visible portion
    * of a node, relativalely to the root window.
    *
    * @param nsIDOMNode aNode
@@ -361,33 +361,44 @@ LayoutHelpers.prototype = {
       return parentDocShell.contentViewer.DOMDocument.defaultView;
     } else {
       return win.parent;
     }
   },
 
   /**
    * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
+   *
+   * @param DOMWindow win The window to get the frame for
+   * @return DOMElement The element(only <iframe> for now) in which the window
+   * is embedded. A null return from this function does not imply that it is a
+   * top level window. Use isTopLevelWindow(win) if needed.
    */
   getFrameElement: function LH_getFrameElement(win) {
     if (this.isTopLevelWindow(win)) {
       return null;
     }
 
+    // Get the docShell for that window
     let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShell);
 
     if (docShell.isBrowserOrApp) {
+      // Get the docShell's same-type parent ignoring mozBrowser and mozApp
+      // boundaries
       let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
+      // Once we have a parent, get all its iframes and loop through them
+      // to find `win`. If we do, it means win is a nested iframe
       let parentDoc = parentDocShell.contentViewer.DOMDocument;
       let allIframes = parentDoc.querySelectorAll("iframe");
       for (let f of allIframes) {
         if (f.contentWindow === win) {
           return f;
         }
       }
+
       return null;
     } else {
       return win.frameElement;
     }
   },
 };
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -719,35 +719,36 @@ var ProgressListener = Class({
     try {
       this.webProgress.removeProgressListener(this);
     } catch(ex) {
       // This can throw during browser shutdown.
     }
     this.webProgress = null;
   },
 
-  onStateChange: makeInfallible(function stateChange(progress, request, flag, status) {
+  onStateChange: makeInfallible(function stateChange(progress, request, flags, status) {
     if (!this.webProgress) {
       console.warn("got an onStateChange after destruction");
       return;
     }
 
-    let isWindow = flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-    let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+    let isWindow = flags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+    let isDocument = flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     if (!(isWindow || isDocument)) {
       return;
     }
 
-    if (isDocument && (flag & Ci.nsIWebProgressListener.STATE_START)) {
+    if (isDocument && (flags & Ci.nsIWebProgressListener.STATE_START)) {
       events.emit(this, "windowchange-start", progress.DOMWindow);
     }
-    if (isWindow && (flag & Ci.nsIWebProgressListener.STATE_STOP)) {
+    if (isWindow && (flags & Ci.nsIWebProgressListener.STATE_STOP)) {
       events.emit(this, "windowchange-stop", progress.DOMWindow);
     }
   }),
+
   onProgressChange: function() {},
   onSecurityChange: function() {},
   onStatusChange: function() {},
   onLocationChange: function() {},
 });
 
 /**
  * Server side of the DOM walker.
@@ -1731,17 +1732,18 @@ var WalkerActor = protocol.ActorClass({
         mutation.added = addedActors;
       }
       this.queueMutation(mutation);
     }
   },
 
   onFrameLoad: function(window) {
     let frame = this.layoutHelpers.getFrameElement(window);
-    if (!frame && !this.rootDoc) {
+    let isTopLevel = this.layoutHelpers.isTopLevelWindow(window);
+    if (!frame && !this.rootDoc && isTopLevel) {
       this.rootDoc = window.document;
       this.rootNode = this.document();
       this.queueMutation({
         type: "newRoot",
         target: this.rootNode.form()
       });
     }
     let frameActor = this._refMap.get(frame);
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2441,62 +2441,62 @@ SourceActor.prototype = {
     });
   },
 
   /**
    * Invert a source map. So if a source map maps from a to b, return a new
    * source map from b to a. We need to do this because the source map we get
    * from _generatePrettyCodeAndMap goes the opposite way we want it to for
    * debugging.
+   *
+   * Note that the source map is modified in place.
    */
   _invertSourceMap: function SA__invertSourceMap({ code, map }) {
-    const smc = new SourceMapConsumer(map.toJSON());
-    const invertedMap = new SourceMapGenerator({
-      file: this._url
+    // XXX bug 918802: Monkey punch the source map consumer, because iterating
+    // over all mappings and inverting each of them, and then creating a new
+    // SourceMapConsumer is *way* too slow.
+
+    map.setSourceContent(this._url, code);
+    const consumer = new SourceMapConsumer.fromSourceMap(map);
+    const getOrigPos = consumer.originalPositionFor.bind(consumer);
+    const getGenPos = consumer.generatedPositionFor.bind(consumer);
+
+    consumer.originalPositionFor = ({ line, column }) => {
+      const location = getGenPos({
+        line: line,
+        column: column,
+        source: this._url
+      });
+      location.source = this._url;
+      return location;
+    };
+
+    consumer.generatedPositionFor = ({ line, column }) => getOrigPos({
+      line: line,
+      column: column
     });
 
-    smc.eachMapping(m => {
-      if (!m.originalLine || !m.originalColumn) {
-        return;
-      }
-      const invertedMapping = {
-        source: m.source,
-        name: m.name,
-        original: {
-          line: m.generatedLine,
-          column: m.generatedColumn
-        },
-        generated: {
-          line: m.originalLine,
-          column: m.originalColumn
-        }
-      };
-      invertedMap.addMapping(invertedMapping);
-    });
-
-    invertedMap.setSourceContent(this._url, code);
-
     return {
       code: code,
-      map: new SourceMapConsumer(invertedMap.toJSON())
+      map: consumer
     };
   },
 
   /**
    * Save the source map back to our thread's ThreadSources object so that
    * stepping, breakpoints, debugger statements, etc can use it. If we are
    * pretty printing a source mapped source, we need to compose the existing
    * source map with our new one.
    */
   _saveMap: function SA__saveMap({ map }) {
     if (this._sourceMap) {
       // Compose the source maps
       this._sourceMap = SourceMapGenerator.fromSourceMap(this._sourceMap);
       this._sourceMap.applySourceMap(map, this._url);
-      this._sourceMap = new SourceMapConsumer(this._sourceMap.toJSON());
+      this._sourceMap = SourceMapConsumer.fromSourceMap(this._sourceMap);
       this._threadActor.sources.saveSourceMap(this._sourceMap,
                                               this._generatedSource);
     } else {
       this._sourceMap = map;
       this._threadActor.sources.saveSourceMap(this._sourceMap, this._url);
     }
   },
 
--- a/toolkit/devtools/server/actors/styleeditor.js
+++ b/toolkit/devtools/server/actors/styleeditor.js
@@ -617,23 +617,20 @@ StyleSheetActor.prototype = {
           this._onSourceLoad(LOAD_ERROR);
           return;
         }
         let source = chunks.join("");
         this._onSourceLoad(null, source, channelCharset);
       }
     };
 
-    if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
-      let loadContext = this.window
-                            .QueryInterface(Ci.nsIInterfaceRequestor)
+    channel.loadGroup = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIWebNavigation)
-                            .QueryInterface(Ci.nsILoadContext);
-      channel.setPrivate(loadContext.usePrivateBrowsing);
-    }
+                            .QueryInterface(Ci.nsIDocumentLoader)
+                            .loadGroup;
     channel.loadFlags = channel.LOAD_FROM_CACHE;
     channel.asyncOpen(streamListener, null);
   },
 
   /**
    * Update the style sheet in place with new text
    *
    * @param  {object} request
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -180,18 +180,28 @@ function BrowserTabList(aConnection)
 
   /* True if we're testing, and should throw if consistency checks fail. */
   this._testing = false;
 }
 
 BrowserTabList.prototype.constructor = BrowserTabList;
 
 
+/**
+ * Get the selected browser for the given navigator:browser window.
+ * @private
+ * @param aWindow nsIChromeWindow
+ *        The navigator:browser window for which you want the selected browser.
+ * @return nsIDOMElement|null
+ *         The currently selected xul:browser element, if any. Note that the
+ *         browser window might not be loaded yet - the function will return
+ *         |null| in such cases.
+ */
 BrowserTabList.prototype._getSelectedBrowser = function(aWindow) {
-  return aWindow.gBrowser.selectedBrowser;
+  return aWindow.gBrowser ? aWindow.gBrowser.selectedBrowser : null;
 };
 
 BrowserTabList.prototype._getChildren = function(aWindow) {
   return aWindow.gBrowser.browsers;
 };
 
 BrowserTabList.prototype.getList = function() {
   let topXULWindow = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
@@ -204,16 +214,19 @@ BrowserTabList.prototype.getList = funct
   // To avoid mysterious behavior if tabs are closed or opened mid-iteration,
   // we update the map first, and then make a second pass over it to yield
   // the actors. Thus, the sequence yielded is always a snapshot of the
   // actors that were live when we began the iteration.
 
   // Iterate over all navigator:browser XUL windows.
   for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
     let selectedBrowser = this._getSelectedBrowser(win);
+    if (!selectedBrowser) {
+      continue;
+    }
 
     // For each tab in this XUL window, ensure that we have an actor for
     // it, reusing existing actors where possible. We actually iterate
     // over 'browser' XUL elements, and BrowserTabActor uses
     // browser.contentWindow.wrappedJSObject as the debuggee global.
     for (let browser of this._getChildren(win)) {
       // Do we have an existing actor for this browser? If not, create one.
       let actor = this._actorByBrowser.get(browser);
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -113,20 +113,28 @@ WebConsoleActor.prototype =
 
   /**
    * The debugger server connection instance.
    * @type object
    */
   conn: null,
 
   /**
-   * The content window we work with.
+   * The window we work with.
    * @type nsIDOMWindow
    */
-  get window() this.parentActor.window,
+  get window() {
+    if (this.parentActor.isRootActor) {
+      // Try to find the Browser Console window, otherwise use the window of
+      // the root actor.
+      let window = Services.wm.getMostRecentWindow("devtools:webconsole");
+      return window || this.parentActor.window;
+    }
+    return this.parentActor.window;
+  },
 
   /**
    * The ConsoleServiceListener instance.
    * @type object
    */
   consoleServiceListener: null,
 
   /**
--- a/toolkit/devtools/sourcemap/SourceMap.jsm
+++ b/toolkit/devtools/sourcemap/SourceMap.jsm
@@ -113,16 +113,42 @@ define('source-map/source-map-consumer',
     //
     // `this._originalMappings` is ordered by the original positions.
     this._generatedMappings = [];
     this._originalMappings = [];
     this._parseMappings(mappings, sourceRoot);
   }
 
   /**
+   * Create a SourceMapConsumer from a SourceMapGenerator.
+   *
+   * @param SourceMapGenerator aSourceMap
+   *        The source map that will be consumed.
+   * @returns SourceMapConsumer
+   */
+  SourceMapConsumer.fromSourceMap =
+    function SourceMapConsumer_fromSourceMap(aSourceMap) {
+      var smc = Object.create(SourceMapConsumer.prototype);
+
+      smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+      smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+      smc.sourceRoot = aSourceMap._sourceRoot;
+      smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+                                                              smc.sourceRoot);
+      smc.file = aSourceMap._file;
+
+      smc._generatedMappings = aSourceMap._mappings.slice()
+        .sort(util.compareByGeneratedPositions);
+      smc._originalMappings = aSourceMap._mappings.slice()
+        .sort(util.compareByOriginalPositions);
+
+      return smc;
+    };
+
+  /**
    * The version of the source mapping spec that we are consuming.
    */
   SourceMapConsumer.prototype._version = 3;
 
   /**
    * The list of original sources.
    */
   Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
@@ -207,47 +233,17 @@ define('source-map/source-map-consumer',
 
           this._generatedMappings.push(mapping);
           if (typeof mapping.originalLine === 'number') {
             this._originalMappings.push(mapping);
           }
         }
       }
 
-      this._originalMappings.sort(this._compareOriginalPositions);
-    };
-
-  /**
-   * Comparator between two mappings where the original positions are compared.
-   */
-  SourceMapConsumer.prototype._compareOriginalPositions =
-    function SourceMapConsumer_compareOriginalPositions(mappingA, mappingB) {
-      if (mappingA.source > mappingB.source) {
-        return 1;
-      }
-      else if (mappingA.source < mappingB.source) {
-        return -1;
-      }
-      else {
-        var cmp = mappingA.originalLine - mappingB.originalLine;
-        return cmp === 0
-          ? mappingA.originalColumn - mappingB.originalColumn
-          : cmp;
-      }
-    };
-
-  /**
-   * Comparator between two mappings where the generated positions are compared.
-   */
-  SourceMapConsumer.prototype._compareGeneratedPositions =
-    function SourceMapConsumer_compareGeneratedPositions(mappingA, mappingB) {
-      var cmp = mappingA.generatedLine - mappingB.generatedLine;
-      return cmp === 0
-        ? mappingA.generatedColumn - mappingB.generatedColumn
-        : cmp;
+      this._originalMappings.sort(util.compareByOriginalPositions);
     };
 
   /**
    * Find the mapping that best matches the hypothetical "needle" mapping that
    * we are searching for in the given "haystack" of mappings.
    */
   SourceMapConsumer.prototype._findMapping =
     function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
@@ -290,17 +286,17 @@ define('source-map/source-map-consumer',
         generatedLine: util.getArg(aArgs, 'line'),
         generatedColumn: util.getArg(aArgs, 'column')
       };
 
       var mapping = this._findMapping(needle,
                                       this._generatedMappings,
                                       "generatedLine",
                                       "generatedColumn",
-                                      this._compareGeneratedPositions);
+                                      util.compareByGeneratedPositions);
 
       if (mapping) {
         var source = util.getArg(mapping, 'source', null);
         if (source && this.sourceRoot) {
           source = util.join(this.sourceRoot, source);
         }
         return {
           source: source,
@@ -384,17 +380,17 @@ define('source-map/source-map-consumer',
       if (this.sourceRoot) {
         needle.source = util.relative(this.sourceRoot, needle.source);
       }
 
       var mapping = this._findMapping(needle,
                                       this._originalMappings,
                                       "originalLine",
                                       "originalColumn",
-                                      this._compareOriginalPositions);
+                                      util.compareByOriginalPositions);
 
       if (mapping) {
         return {
           line: util.getArg(mapping, 'generatedLine', null),
           column: util.getArg(mapping, 'generatedColumn', null)
         };
       }
 
@@ -569,16 +565,103 @@ define('source-map/util', ['require', 'e
     }
 
     return aPath.indexOf(aRoot + '/') === 0
       ? aPath.substr(aRoot.length + 1)
       : aPath;
   }
   exports.relative = relative;
 
+  function strcmp(aStr1, aStr2) {
+    var s1 = aStr1 || "";
+    var s2 = aStr2 || "";
+    return (s1 > s2) - (s1 < s2);
+  }
+
+  /**
+   * Comparator between two mappings where the original positions are compared.
+   *
+   * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+   * mappings with the same original source/line/column, but different generated
+   * line and column the same. Useful when searching for a mapping with a
+   * stubbed out mapping.
+   */
+  function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+    var cmp;
+
+    cmp = strcmp(mappingA.source, mappingB.source);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalLine - mappingB.originalLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalColumn - mappingB.originalColumn;
+    if (cmp || onlyCompareOriginal) {
+      return cmp;
+    }
+
+    cmp = strcmp(mappingA.name, mappingB.name);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.generatedLine - mappingB.generatedLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    return mappingA.generatedColumn - mappingB.generatedColumn;
+  };
+  exports.compareByOriginalPositions = compareByOriginalPositions;
+
+  /**
+   * Comparator between two mappings where the generated positions are
+   * compared.
+   *
+   * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+   * mappings with the same generated line and column, but different
+   * source/name/original line and column the same. Useful when searching for a
+   * mapping with a stubbed out mapping.
+   */
+  function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) {
+    var cmp;
+
+    cmp = mappingA.generatedLine - mappingB.generatedLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+    if (cmp || onlyCompareGenerated) {
+      return cmp;
+    }
+
+    cmp = strcmp(mappingA.source, mappingB.source);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalLine - mappingB.originalLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalColumn - mappingB.originalColumn;
+    if (cmp) {
+      return cmp;
+    }
+
+    return strcmp(mappingA.name, mappingB.name);
+  };
+  exports.compareByGeneratedPositions = compareByGeneratedPositions;
+
 });
 /* -*- Mode: js; js-indent-level: 2; -*- */
 /*
  * Copyright 2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 define('source-map/binary-search', ['require', 'exports', 'module' , ], function(require, exports, module) {
@@ -599,17 +682,17 @@ define('source-map/binary-search', ['req
     //
     //   2. We did not find the exact element, but we can return the next
     //      closest element that is less than that element.
     //
     //   3. We did not find the exact element, and there is no next-closest
     //      element which is less than the one we are searching for, so we
     //      return null.
     var mid = Math.floor((aHigh - aLow) / 2) + aLow;
-    var cmp = aCompare(aNeedle, aHaystack[mid]);
+    var cmp = aCompare(aNeedle, aHaystack[mid], true);
     if (cmp === 0) {
       // Found the element we are looking for.
       return aHaystack[mid];
     }
     else if (cmp > 0) {
       // aHaystack[mid] is greater than our needle.
       if (aHigh - mid > 1) {
         // The element is in the upper half.
@@ -1028,18 +1111,20 @@ define('source-map/source-map-generator'
         this._sources.add(source);
       }
 
       if (name && !this._names.has(name)) {
         this._names.add(name);
       }
 
       this._mappings.push({
-        generated: generated,
-        original: original,
+        generatedLine: generated.line,
+        generatedColumn: generated.column,
+        originalLine: original != null && original.line,
+        originalColumn: original != null && original.column,
         source: source,
         name: name
       });
     };
 
   /**
    * Set the source content for a source file.
    */
@@ -1078,46 +1163,43 @@ define('source-map/source-map-generator'
    *        If omitted, SourceMapConsumer's file property will be used.
    */
   SourceMapGenerator.prototype.applySourceMap =
     function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
       // If aSourceFile is omitted, we will use the file property of the SourceMap
       if (!aSourceFile) {
         aSourceFile = aSourceMapConsumer.file;
       }
-
       var sourceRoot = this._sourceRoot;
       // Make "aSourceFile" relative if an absolute Url is passed.
       if (sourceRoot) {
         aSourceFile = util.relative(sourceRoot, aSourceFile);
       }
-
       // Applying the SourceMap can add and remove items from the sources and
       // the names array.
       var newSources = new ArraySet();
       var newNames = new ArraySet();
 
       // Find mappings for the "aSourceFile"
       this._mappings.forEach(function (mapping) {
-        if (mapping.source === aSourceFile && mapping.original) {
+        if (mapping.source === aSourceFile && mapping.originalLine) {
           // Check if it can be mapped by the source map, then update the mapping.
           var original = aSourceMapConsumer.originalPositionFor({
-            line: mapping.original.line,
-            column: mapping.original.column
+            line: mapping.originalLine,
+            column: mapping.originalColumn
           });
-
           if (original.source !== null) {
             // Copy mapping
             if (sourceRoot) {
               mapping.source = util.relative(sourceRoot, original.source);
             } else {
               mapping.source = original.source;
             }
-            mapping.original.line = original.line;
-            mapping.original.column = original.column;
+            mapping.originalLine = original.line;
+            mapping.originalColumn = original.column;
             if (original.name !== null && mapping.name !== null) {
               // Only use the identifier name if it's an identifier
               // in both SourceMaps
               mapping.name = original.name;
             }
           }
         }
 
@@ -1175,34 +1257,16 @@ define('source-map/source-map-generator'
         // Cases 2 and 3.
         return;
       }
       else {
         throw new Error('Invalid mapping.');
       }
     };
 
-  function cmpLocation(loc1, loc2) {
-    var cmp = (loc1 && loc1.line) - (loc2 && loc2.line);
-    return cmp ? cmp : (loc1 && loc1.column) - (loc2 && loc2.column);
-  }
-
-  function strcmp(str1, str2) {
-    str1 = str1 || '';
-    str2 = str2 || '';
-    return (str1 > str2) - (str1 < str2);
-  }
-
-  function cmpMapping(mappingA, mappingB) {
-    return cmpLocation(mappingA.generated, mappingB.generated) ||
-      cmpLocation(mappingA.original, mappingB.original) ||
-      strcmp(mappingA.source, mappingB.source) ||
-      strcmp(mappingA.name, mappingB.name);
-  }
-
   /**
    * Serialize the accumulated mappings in to the stream of base 64 VLQs
    * specified by the source map format.
    */
   SourceMapGenerator.prototype._serializeMappings =
     function SourceMapGenerator_serializeMappings() {
       var previousGeneratedColumn = 0;
       var previousGeneratedLine = 1;
@@ -1213,92 +1277,99 @@ define('source-map/source-map-generator'
       var result = '';
       var mapping;
 
       // The mappings must be guaranteed to be in sorted order before we start
       // serializing them or else the generated line numbers (which are defined
       // via the ';' separators) will be all messed up. Note: it might be more
       // performant to maintain the sorting as we insert them, rather than as we
       // serialize them, but the big O is the same either way.
-      this._mappings.sort(cmpMapping);
+      this._mappings.sort(util.compareByGeneratedPositions);
 
       for (var i = 0, len = this._mappings.length; i < len; i++) {
         mapping = this._mappings[i];
 
-        if (mapping.generated.line !== previousGeneratedLine) {
+        if (mapping.generatedLine !== previousGeneratedLine) {
           previousGeneratedColumn = 0;
-          while (mapping.generated.line !== previousGeneratedLine) {
+          while (mapping.generatedLine !== previousGeneratedLine) {
             result += ';';
             previousGeneratedLine++;
           }
         }
         else {
           if (i > 0) {
-            if (!cmpMapping(mapping, this._mappings[i - 1])) {
+            if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) {
               continue;
             }
             result += ',';
           }
         }
 
-        result += base64VLQ.encode(mapping.generated.column
+        result += base64VLQ.encode(mapping.generatedColumn
                                    - previousGeneratedColumn);
-        previousGeneratedColumn = mapping.generated.column;
+        previousGeneratedColumn = mapping.generatedColumn;
 
-        if (mapping.source && mapping.original) {
+        if (mapping.source) {
           result += base64VLQ.encode(this._sources.indexOf(mapping.source)
                                      - previousSource);
           previousSource = this._sources.indexOf(mapping.source);
 
           // lines are stored 0-based in SourceMap spec version 3
-          result += base64VLQ.encode(mapping.original.line - 1
+          result += base64VLQ.encode(mapping.originalLine - 1
                                      - previousOriginalLine);
-          previousOriginalLine = mapping.original.line - 1;
+          previousOriginalLine = mapping.originalLine - 1;
 
-          result += base64VLQ.encode(mapping.original.column
+          result += base64VLQ.encode(mapping.originalColumn
                                      - previousOriginalColumn);
-          previousOriginalColumn = mapping.original.column;
+          previousOriginalColumn = mapping.originalColumn;
 
           if (mapping.name) {
             result += base64VLQ.encode(this._names.indexOf(mapping.name)
                                        - previousName);
             previousName = this._names.indexOf(mapping.name);
           }
         }
       }
 
       return result;
     };
 
+  SourceMapGenerator.prototype._generateSourcesContent =
+    function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+      return aSources.map(function (source) {
+        if (aSourceRoot) {
+          source = util.relative(aSourceRoot, source);
+        }
+        var key = util.toSetString(source);
+        return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+                                                    key)
+          ? this._sourcesContents[key]
+          : null;
+      }, this);
+    };
+
   /**
    * Externalize the source map.
    */
   SourceMapGenerator.prototype.toJSON =
     function SourceMapGenerator_toJSON() {
       var map = {
         version: this._version,
         file: this._file,
         sources: this._sources.toArray(),
         names: this._names.toArray(),
         mappings: this._serializeMappings()
       };
       if (this._sourceRoot) {
         map.sourceRoot = this._sourceRoot;
       }
       if (this._sourcesContents) {
-        map.sourcesContent = map.sources.map(function (source) {
-          if (map.sourceRoot) {
-            source = util.relative(map.sourceRoot, source);
-          }
-          return Object.prototype.hasOwnProperty.call(
-            this._sourcesContents, util.toSetString(source))
-            ? this._sourcesContents[util.toSetString(source)]
-            : null;
-        }, this);
+        map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
       }
+
       return map;
     };
 
   /**
    * Render the source map being generated to a string.
    */
   SourceMapGenerator.prototype.toString =
     function SourceMapGenerator_toString() {
@@ -1495,29 +1566,31 @@ define('source-map/source-node', ['requi
   /**
    * Walk over the tree of JS snippets in this node and its children. The
    * walking function is called once for each snippet of JS and is passed that
    * snippet and the its original associated source's line/column location.
    *
    * @param aFn The traversal function.
    */
   SourceNode.prototype.walk = function SourceNode_walk(aFn) {
-    this.children.forEach(function (chunk) {
+    var chunk;
+    for (var i = 0, len = this.children.length; i < len; i++) {
+      chunk = this.children[i];
       if (chunk instanceof SourceNode) {
         chunk.walk(aFn);
       }
       else {
         if (chunk !== '') {
           aFn(chunk, { source: this.source,
                        line: this.line,
                        column: this.column,
                        name: this.name });
         }
       }
-    }, this);
+    }
   };
 
   /**
    * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
    * each of `this.children`.
    *
    * @param aSep The separator.
    */
@@ -1573,24 +1646,26 @@ define('source-map/source-node', ['requi
   /**
    * Walk over the tree of SourceNodes. The walking function is called for each
    * source file content and is passed the filename and source content.
    *
    * @param aFn The traversal function.
    */
   SourceNode.prototype.walkSourceContents =
     function SourceNode_walkSourceContents(aFn) {
-      this.children.forEach(function (chunk) {
-        if (chunk instanceof SourceNode) {
-          chunk.walkSourceContents(aFn);
+      for (var i = 0, len = this.children.length; i < len; i++) {
+        if (this.children[i] instanceof SourceNode) {
+          this.children[i].walkSourceContents(aFn);
         }
-      }, this);
-      Object.keys(this.sourceContents).forEach(function (sourceFileKey) {
-        aFn(util.fromSetString(sourceFileKey), this.sourceContents[sourceFileKey]);
-      }, this);
+      }
+
+      var sources = Object.keys(this.sourceContents);
+      for (var i = 0, len = sources.length; i < len; i++) {
+        aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+      }
     };
 
   /**
    * Return the string representation of this source node. Walks over the tree
    * and concatenates all the various snippets together to one string.
    */
   SourceNode.prototype.toString = function SourceNode_toString() {
     var str = "";
--- a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
+++ b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
@@ -209,17 +209,18 @@ define('test/source-map/util', ['require
                    expectedMap.sources[i],
                    "sources[" + i + "] length mismatch: " +
                    actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
     }
     assert.equal(actualMap.sourceRoot,
                  expectedMap.sourceRoot,
                  "sourceRoot mismatch: " +
                    actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
-    assert.equal(actualMap.mappings, expectedMap.mappings, "mappings mismatch");
+    assert.equal(actualMap.mappings, expectedMap.mappings,
+                 "mappings mismatch:\nActual:   " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
     if (actualMap.sourcesContent) {
       assert.equal(actualMap.sourcesContent.length,
                    expectedMap.sourcesContent.length,
                    "sourcesContent length mismatch");
       for (var i = 0; i < actualMap.sourcesContent.length; i++) {
         assert.equal(actualMap.sourcesContent[i],
                      expectedMap.sourcesContent[i],
                      "sourcesContent[" + i + "] mismatch");
@@ -338,16 +339,103 @@ define('lib/source-map/util', ['require'
     }
 
     return aPath.indexOf(aRoot + '/') === 0
       ? aPath.substr(aRoot.length + 1)
       : aPath;
   }
   exports.relative = relative;
 
+  function strcmp(aStr1, aStr2) {
+    var s1 = aStr1 || "";
+    var s2 = aStr2 || "";
+    return (s1 > s2) - (s1 < s2);
+  }
+
+  /**
+   * Comparator between two mappings where the original positions are compared.
+   *
+   * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+   * mappings with the same original source/line/column, but different generated
+   * line and column the same. Useful when searching for a mapping with a
+   * stubbed out mapping.
+   */
+  function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+    var cmp;
+
+    cmp = strcmp(mappingA.source, mappingB.source);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalLine - mappingB.originalLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalColumn - mappingB.originalColumn;
+    if (cmp || onlyCompareOriginal) {
+      return cmp;
+    }
+
+    cmp = strcmp(mappingA.name, mappingB.name);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.generatedLine - mappingB.generatedLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    return mappingA.generatedColumn - mappingB.generatedColumn;
+  };
+  exports.compareByOriginalPositions = compareByOriginalPositions;
+
+  /**
+   * Comparator between two mappings where the generated positions are
+   * compared.
+   *
+   * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+   * mappings with the same generated line and column, but different
+   * source/name/original line and column the same. Useful when searching for a
+   * mapping with a stubbed out mapping.
+   */
+  function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) {
+    var cmp;
+
+    cmp = mappingA.generatedLine - mappingB.generatedLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+    if (cmp || onlyCompareGenerated) {
+      return cmp;
+    }
+
+    cmp = strcmp(mappingA.source, mappingB.source);
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalLine - mappingB.originalLine;
+    if (cmp) {
+      return cmp;
+    }
+
+    cmp = mappingA.originalColumn - mappingB.originalColumn;
+    if (cmp) {
+      return cmp;
+    }
+
+    return strcmp(mappingA.name, mappingB.name);
+  };
+  exports.compareByGeneratedPositions = compareByGeneratedPositions;
+
 });
 /* -*- Mode: js; js-indent-level: 2; -*- */
 /*
  * Copyright 2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 function runSourceMapTests(modName, do_throw) {
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
@@ -388,12 +388,72 @@ define("test/source-map/test-source-map-
       line: 6,
       column: 6
     });
     assert.equal(pos.name, 'name3');
     assert.equal(pos.line, 5);
     assert.equal(pos.column, 5);
   };
 
+  exports['test SourceMapConsumer.fromSourceMap'] = function (assert, util) {
+    var smg = new SourceMapGenerator({
+      sourceRoot: 'http://example.com/',
+      file: 'foo.js'
+    });
+    smg.addMapping({
+      original: { line: 1, column: 1 },
+      generated: { line: 2, column: 2 },
+      source: 'bar.js'
+    });
+    smg.addMapping({
+      original: { line: 2, column: 2 },
+      generated: { line: 4, column: 4 },
+      source: 'baz.js',
+      name: 'dirtMcGirt'
+    });
+    smg.setSourceContent('baz.js', 'baz.js content');
+
+    var smc = SourceMapConsumer.fromSourceMap(smg);
+    assert.equal(smc.file, 'foo.js');
+    assert.equal(smc.sourceRoot, 'http://example.com/');
+    assert.equal(smc.sources.length, 2);
+    assert.equal(smc.sources[0], 'http://example.com/bar.js');
+    assert.equal(smc.sources[1], 'http://example.com/baz.js');
+    assert.equal(smc.sourceContentFor('baz.js'), 'baz.js content');
+
+    var pos = smc.originalPositionFor({
+      line: 2,
+      column: 2
+    });
+    assert.equal(pos.line, 1);
+    assert.equal(pos.column, 1);
+    assert.equal(pos.source, 'http://example.com/bar.js');
+    assert.equal(pos.name, null);
+
+    pos = smc.generatedPositionFor({
+      line: 1,
+      column: 1,
+      source: 'http://example.com/bar.js'
+    });
+    assert.equal(pos.line, 2);
+    assert.equal(pos.column, 2);
+
+    pos = smc.originalPositionFor({
+      line: 4,
+      column: 4
+    });
+    assert.equal(pos.line, 2);
+    assert.equal(pos.column, 2);
+    assert.equal(pos.source, 'http://example.com/baz.js');
+    assert.equal(pos.name, 'dirtMcGirt');
+
+    pos = smc.generatedPositionFor({
+      line: 2,
+      column: 2,
+      source: 'http://example.com/baz.js'
+    });
+    assert.equal(pos.line, 4);
+    assert.equal(pos.column, 4);
+  };
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-source-map-consumer', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
@@ -257,21 +257,27 @@ define("test/source-map/test-source-node
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     var inputMap = input.map.toJSON();
     util.assertEqualMaps(assert, map, inputMap);
   };
 
   exports['test .fromStringWithSourceMap() merging duplicate mappings'] = function (assert, util) {
     var input = new SourceNode(null, null, null, [
-      new SourceNode(1, 0, "a.js", "(function"), new SourceNode(1, 0, "a.js", "() {\n"),
-        "  ", new SourceNode(1, 0, "a.js", "var Test = "), new SourceNode(1, 0, "b.js", "{};\n"),
-        new SourceNode(2, 0, "b.js", "Test"), new SourceNode(2, 0, "b.js", ".A", "A"), new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
-        "}());\n",
-        "/* Generated Source */"]);
+      new SourceNode(1, 0, "a.js", "(function"),
+      new SourceNode(1, 0, "a.js", "() {\n"),
+      "  ",
+      new SourceNode(1, 0, "a.js", "var Test = "),
+      new SourceNode(1, 0, "b.js", "{};\n"),
+      new SourceNode(2, 0, "b.js", "Test"),
+      new SourceNode(2, 0, "b.js", ".A", "A"),
+      new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
+      "}());\n",
+      "/* Generated Source */"
+    ]);
     input = input.toStringWithSourceMap({
       file: 'foo.js'
     });
 
     var correctMap = new SourceMapGenerator({
       file: 'foo.js'
     });
     correctMap.addMapping({
--- a/webapprt/content/dbg-webapp-actors.js
+++ b/webapprt/content/dbg-webapp-actors.js
@@ -47,17 +47,17 @@ function WebappTabList(connection)
   BrowserTabList.call(this, connection);
 }
 
 WebappTabList.prototype = Object.create(BrowserTabList.prototype);
 
 WebappTabList.prototype.constructor = WebappTabList;
 
 WebappTabList.prototype.getList = function() {
-  let topXULWindow = windowMediator.getMostRecentWindow(this._windowType);
+  let topXULWindow = Services.wm.getMostRecentWindow(this._windowType);
 
   // As a sanity check, make sure all the actors presently in our map get
   // picked up when we iterate over all windows.
   let initialMapSize = this._actorByBrowser.size;
   let foundCount = 0;
 
   // To avoid mysterious behavior if windows are closed or opened mid-iteration,
   // we update the map first, and then make a second pass over it to yield
--- a/widget/nsIWinMetroUtils.idl
+++ b/widget/nsIWinMetroUtils.idl
@@ -7,59 +7,39 @@
 
 /**
  * Integration with the "Metro"/"Modern" UI environment in Windows 8.
  *
  * Note: browser/metro/base/content/browser-scripts.js contains a stub
  * implementation of this interface for non-Windows systems, for testing and
  * development purposes only.
  */
-[scriptable, uuid(c5a654c8-2443-47ce-9322-d150af3ca526)]
+[scriptable, uuid(fa6750a2-f0fe-411c-af23-1cd6d2fdeceb)]
 interface nsIWinMetroUtils : nsISupports
 {
-  /* Fullscreen landscape orientation */
-  const long fullScreenLandscape = 0;
-  /* Larger snapped state */
-  const long filled = 1;
-  /* Smaller snapped state */
-  const long snapped = 2;
-  /* Fullscreen portrait orientation */
-  const long fullScreenPortrait = 3;
-
   /* return constants for the handPreference property */
   const long handPreferenceLeft = 0;
   const long handPreferenceRight = 1;
 
   /**
-   * Determines the current snapped state.
-   */
-  readonly attribute long snappedState;
-
-  /**
    * Determine if the current browser is running in the metro immersive
    * environment.
    */
   readonly attribute boolean immersive;
 
   /**
    * Determine if the user prefers left handed or right handed input.
    */
   readonly attribute long handPreference;
 
   /**
    * Determine the activation URI
    */
   readonly attribute AString activationURI;
 
-  /**
-   * Attempts to unsnap the application from snapped state to filled state
-   */
-   void unsnap();
-
-
    /**
     * Show the settings flyout
     */
    void showSettingsFlyout();
 
   /**
    * Launches the specified application with the specified arguments and
    * switches to Desktop mode if in metro mode.
--- a/widget/windows/winrt/FrameworkView.cpp
+++ b/widget/windows/winrt/FrameworkView.cpp
@@ -104,17 +104,16 @@ FrameworkView::Run()
 }
 
 HRESULT
 FrameworkView::ActivateView()
 {
   LogFunction();
 
   UpdateWidgetSizeAndPosition();
-  MetroUtils::GetViewState(mViewState);
 
   nsIntRegion region(nsIntRect(0, 0, mWindowBounds.width, mWindowBounds.height));
   mWidget->Paint(region);
 
   // Activate the window, this kills the splash screen
   mWindow->Activate();
 
   ProcessLaunchArguments();
@@ -381,62 +380,31 @@ FrameworkView::OnSoftkeyboardShown(IInpu
 
 HRESULT
 FrameworkView::OnWindowClosed(ICoreWindow* aSender, ICoreWindowEventArgs* aArgs)
 {
   // this doesn't seem very reliable
   return S_OK;
 }
 
-void
-FrameworkView::FireViewStateObservers()
-{
-  ApplicationViewState state;
-  MetroUtils::GetViewState(state);
-  if (state == mViewState) {
-    return;
-  }
-  mViewState = state;
-  nsAutoString name;
-  switch (mViewState) {
-    case ApplicationViewState_FullScreenLandscape:
-      name.AssignLiteral("landscape");
-    break;
-    case ApplicationViewState_Filled:
-      name.AssignLiteral("filled");
-    break;
-    case ApplicationViewState_Snapped:
-      name.AssignLiteral("snapped");
-    break;
-    case ApplicationViewState_FullScreenPortrait:
-      name.AssignLiteral("portrait");
-    break;
-    default:
-      NS_WARNING("Unknown view state");
-    return;
-  };
-  MetroUtils::FireObserver("metro_viewstate_changed", name.get());
-}
-
 HRESULT
 FrameworkView::OnWindowSizeChanged(ICoreWindow* aSender, IWindowSizeChangedEventArgs* aArgs)
 {
   LogFunction();
 
   if (mShuttingDown) {
     return S_OK;
   }
 
   NS_ASSERTION(mWindow, "SetWindow must be called before OnWindowSizeChanged!");
   Rect logicalBounds;
   mWindow->get_Bounds(&logicalBounds);
   mWindowBounds = MetroUtils::LogToPhys(logicalBounds);
 
   UpdateWidgetSizeAndPosition();
-  FireViewStateObservers();
   return S_OK;
 }
 
 HRESULT
 FrameworkView::OnWindowActivated(ICoreWindow* aSender, IWindowActivatedEventArgs* aArgs)
 {
   LogFunction();
   if (mShuttingDown || !mWidget)
--- a/widget/windows/winrt/FrameworkView.h
+++ b/widget/windows/winrt/FrameworkView.h
@@ -190,12 +190,11 @@ private:
   Microsoft::WRL::ComPtr<MetroApp> mMetroApp;
   Microsoft::WRL::ComPtr<ICoreWindow> mWindow;
   Microsoft::WRL::ComPtr<MetroWidget> mWidget;
   Microsoft::WRL::ComPtr<MetroInput> mMetroInput;
   static bool sKeyboardIsVisible;
   static Rect sKeyboardRect;
   bool mWinVisible;
   bool mWinActiveState;
-  ApplicationViewState mViewState;
 };
 
 } } }
--- a/widget/windows/winrt/nsWinMetroUtils.cpp
+++ b/widget/windows/winrt/nsWinMetroUtils.cpp
@@ -359,42 +359,16 @@ nsWinMetroUtils::ShowNativeToast(const n
   HSTRING msg = HStringReference(aMessage.BeginReading()).Get();
   HSTRING imagePath = HStringReference(anImage.BeginReading()).Get();
   notification_handler->DisplayNotification(title, msg, imagePath);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsWinMetroUtils::GetSnappedState(int32_t *aSnappedState)
-{
-  if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop) {
-    NS_WARNING("GetSnappedState can't be called on the desktop.");
-    return NS_ERROR_FAILURE;
-  }
-  NS_ENSURE_ARG_POINTER(aSnappedState);
-  ApplicationViewState viewState;
-  AssertRetHRESULT(MetroUtils::GetViewState(viewState), NS_ERROR_UNEXPECTED);
-  *aSnappedState = (int32_t) viewState;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsWinMetroUtils::Unsnap()
-{
-  if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop) {
-    NS_WARNING("Unsnap can't be called on the desktop.");
-    return NS_ERROR_FAILURE;
-  }
-
-  HRESULT hr = MetroUtils::TryUnsnap();
-  return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
 nsWinMetroUtils::ShowSettingsFlyout()
 {
   if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop) {
     NS_WARNING("Settings flyout can't be shown on the desktop.");
     return NS_ERROR_FAILURE;
   }
 
   HRESULT hr = MetroUtils::ShowSettingsFlyout();