Merge m-c to fx-team
authorMatt Brubeck <mbrubeck@mozilla.com>
Thu, 08 Aug 2013 21:54:44 -0700
changeset 154759 e33c2011643e323aa29739ac67c28c588197cbb1
parent 154716 55b6b3b78d37f605600457bc55b1ac6ee52921fb (current diff)
parent 154758 e881258c3ac5c56a02c89fcecdd15fcb5559678d (diff)
child 154845 9bac3ba885ebd89156ee3a7e3cebf34f81747f16
child 154944 75c9ebb2ae8069b8e2c35118312ca0eaf85f1b7c
child 156424 d07b65be9f7a8d19552ea6443fc8dc3656da117a
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
first release with
nightly linux32
e33c2011643e / 26.0a1 / 20130809030203 / files
nightly linux64
e33c2011643e / 26.0a1 / 20130809030203 / files
nightly mac
e33c2011643e / 26.0a1 / 20130809030203 / files
nightly win32
e33c2011643e / 26.0a1 / 20130809030203 / files
nightly win64
e33c2011643e / 26.0a1 / 20130809030203 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
mobile/android/base/resources/drawable-mdpi/progress_spinner_1.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_10.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_11.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_12.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_2.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_3.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_4.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_5.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_6.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_7.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_8.png
mobile/android/base/resources/drawable-mdpi/progress_spinner_9.png
mobile/android/base/resources/drawable/progress_spinner.xml
--- a/addon-sdk/source/test/test-plain-text-console.js
+++ b/addon-sdk/source/test/test-plain-text-console.js
@@ -31,25 +31,25 @@ exports.testPlainTextConsole = function(
   prefs.reset(ADDON_LOG_LEVEL_PREF);
 
   var Console = require("sdk/console/plain-text").PlainTextConsole;
   var con = new Console(print);
 
   test.pass("PlainTextConsole instantiates");
 
   con.log('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, 1, Array [2,3,4]\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing 1 Array [2,3,4]\n",
                    "PlainTextConsole.log() must work.");
 
   con.info('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "console.info: " + name + ": testing, 1, Array [2,3,4]\n",
+  test.assertEqual(lastPrint(), "console.info: " + name + ": testing 1 Array [2,3,4]\n",
                    "PlainTextConsole.info() must work.");
 
   con.warn('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "console.warn: " + name + ": testing, 1, Array [2,3,4]\n",
+  test.assertEqual(lastPrint(), "console.warn: " + name + ": testing 1 Array [2,3,4]\n",
                    "PlainTextConsole.warn() must work.");
 
   con.error('testing', 1, [2, 3, 4]);
   test.assertEqual(prints[0], "console.error: " + name + ": \n",
                    "PlainTextConsole.error() must work.");
   test.assertEqual(prints[1], "  testing\n")
   test.assertEqual(prints[2], "  1\n")
   test.assertEqual(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
@@ -59,30 +59,30 @@ exports.testPlainTextConsole = function(
   test.assertEqual(prints[0], "console.debug: " + name + ": \n",
                    "PlainTextConsole.debug() must work.");
   test.assertEqual(prints[1], "  testing\n")
   test.assertEqual(prints[2], "  1\n")
   test.assertEqual(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
   prints = [];
 
   con.log('testing', undefined);
-  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, undefined\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing undefined\n",
                    "PlainTextConsole.log() must stringify undefined.");
 
   con.log('testing', null);
-  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, null\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing null\n",
                    "PlainTextConsole.log() must stringify null.");
 
   // TODO: Fix console.jsm to detect custom toString.
   con.log("testing", { toString: function() "obj.toString()" });
-  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing {}\n",
                    "PlainTextConsole.log() doesn't printify custom toString.");
 
   con.log("testing", { toString: function() { throw "fail!"; } });
-  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing {}\n",
                    "PlainTextConsole.log() must stringify custom bad toString.");
 
   
   con.exception(new Error("blah"));
 
   
   test.assertEqual(prints[0], "console.error: " + name + ": \n");
   let tbLines = prints[1].split("\n");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -751,17 +751,16 @@ var gBrowserInit = {
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
     messageManager.loadFrameScript("chrome://browser/content/content.js", true);
     messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
 
     // initialize observers and listeners
     // and give C++ access to gBrowser
-    gBrowser.init();
     XULBrowserWindow.init();
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(nsIWebNavigation)
           .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
           .QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIXULWindow)
           .XULBrowserWindow = window.XULBrowserWindow;
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2951,41 +2951,62 @@
           // Hook up the event listeners to the first browser
           var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
           const nsIWebProgress = Components.interfaces.nsIWebProgress;
           const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                    .createInstance(nsIWebProgress);
           filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
           this.mTabListeners[0] = tabListener;
           this.mTabFilters[0] = filter;
-          this.init();
+
+          try {
+            // We assume this can only fail because mCurrentBrowser's docShell
+            // hasn't been created, yet. This may be caused by code accessing
+            // gBrowser before the window has finished loading.
+            this._addProgressListenerForInitialTab();
+          } catch (e) {
+            // The binding was constructed too early, wait until the initial
+            // tab's document is ready, then add the progress listener.
+            this._waitForInitialContentDocument();
+          }
 
           this.style.backgroundColor =
             Services.prefs.getBoolPref("browser.display.use_system_colors") ?
               "-moz-default-background-color" :
               Services.prefs.getCharPref("browser.display.background_color");
 
           if (Services.prefs.getBoolPref("browser.tabs.remote")) {
             messageManager.addMessageListener("DOMTitleChanged", this);
             messageManager.addMessageListener("contextmenu", this);
           }
         ]]>
       </constructor>
 
-      <method name="init">
+      <method name="_addProgressListenerForInitialTab">
+        <body><![CDATA[
+          this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
+        ]]></body>
+      </method>
+
+      <method name="_waitForInitialContentDocument">
         <body><![CDATA[
-          if (!this._initialProgressListenerAdded) {
-            this._initialProgressListenerAdded = true;
-            try {
-              this.webProgress.addProgressListener(this.mTabFilters[0], Components.interfaces.nsIWebProgress.NOTIFY_ALL);
-            } catch (e) {
-              // The binding was constructed too early, need to try this again later. See bug 463384.
-              this._initialProgressListenerAdded = false;
+          let obs = (subject, topic) => {
+            if (this.browsers[0].contentWindow == subject) {
+              Services.obs.removeObserver(obs, topic);
+              this._addProgressListenerForInitialTab();
             }
-          }
+          };
+
+          // We use content-document-global-created as an approximation for
+          // "docShell is initialized". We can do this because in the
+          // mTabProgressListener we care most about the STATE_STOP notification
+          // that will reset mBlank. That means it's important to at least add
+          // the progress listener before the initial about:blank load stops
+          // if we can't do it before the load starts.
+          Services.obs.addObserver(obs, "content-document-global-created", false);
         ]]></body>
       </method>
 
       <destructor>
         <![CDATA[
           for (var i = 0; i < this.mTabListeners.length; ++i) {
             let browser = this.getBrowserAtIndex(i);
             if (browser.registeredOpenURI) {
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -179,16 +179,17 @@ MOCHITEST_BROWSER_FILES = \
                  browser_bug812562.js \
                  browser_bug816527.js \
                  browser_bug817947.js \
                  browser_bug818118.js \
                  browser_bug820497.js \
                  browser_bug822367.js \
                  browser_bug832435.js \
                  browser_bug839103.js \
+                 browser_bug880101.js \
                  browser_bug882977.js \
                  browser_bug887515.js \
                  browser_canonizeURL.js \
                  browser_clearplugindata_noage.html \
                  browser_clearplugindata.html \
                  browser_clearplugindata.js \
                  browser_contentAreaClick.js \
                  browser_contextSearchTabPosition.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_bug880101.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "about:robots";
+
+function test() {
+  let win;
+
+  let listener = {
+    onLocationChange: (webProgress, request, uri, flags) => {
+      ok(webProgress.isTopLevel, "Received onLocationChange from top frame");
+      is(uri.spec, URL, "Received onLocationChange for correct URL");
+      finish();
+    }
+  };
+
+  waitForExplicitFinish();
+
+  // Remove the listener and window when we're done.
+  registerCleanupFunction(() => {
+    win.gBrowser.removeProgressListener(listener);
+    win.close();
+  });
+
+  // Wait for the newly opened window.
+  whenNewWindowOpened(w => win = w);
+
+  // Open a link in a new window.
+  openLinkIn(URL, "window", {});
+
+  // On the next tick, but before the window has finished loading, access the
+  // window's gBrowser property to force the tabbrowser constructor early.
+  (function tryAddProgressListener() {
+    executeSoon(() => {
+      try {
+        win.gBrowser.addProgressListener(listener);
+      } catch (e) {
+        // win.gBrowser wasn't ready, yet. Try again in a tick.
+        tryAddProgressListener();
+      }
+    });
+  })();
+}
+
+function whenNewWindowOpened(cb) {
+  Services.obs.addObserver(function obs(win) {
+    Services.obs.removeObserver(obs, "domwindowopened");
+    cb(win);
+  }, "domwindowopened", false);
+}
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -306,19 +306,16 @@ let SessionStoreInternal = {
 
   // internal states for all open windows (data we need to associate,
   // but not write to disk)
   _internalWindows: {},
 
   // states for all recently closed windows
   _closedWindows: [],
 
-  // not-"dirty" windows usually don't need to have their data updated
-  _dirtyWindows: {},
-
   // collection of session states yet to be restored
   _statesToRestore: {},
 
   // counts the number of crashes since the last clean start
   _recentCrashes: 0,
 
   // whether the last window was closed and should be restored
   _restoreLastWindow: false,
@@ -483,18 +480,16 @@ let SessionStoreInternal = {
     }
 
     // at this point, we've as good as resumed the session, so we can
     // clear the resume_session_once flag, if it's set
     if (this._loadState != STATE_QUITTING &&
         this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
       this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
 
-    this._initEncoding();
-
     this._performUpgradeBackup();
 
     this._sessionInitialized = true;
   },
 
   /**
    * If this is the first time we launc this build of Firefox,
    * backup sessionstore.js.
@@ -519,23 +514,16 @@ let SessionStoreInternal = {
         yield _SessionFile.removeBackupCopy("-" + latestBackup);
       } catch (ex) {
         debug("Could not perform upgrade backup " + ex);
         debug(ex.stack);
       }
     }.bind(this));
   },
 
-  _initEncoding : function ssi_initEncoding() {
-    // The (UTF-8) encoder used to write to files.
-    XPCOMUtils.defineLazyGetter(this, "_writeFileEncoder", function () {
-      return new TextEncoder();
-    });
-  },
-
   _initPrefs : function() {
     this._prefBranch = Services.prefs.getBranch("browser.");
 
     gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
 
     Services.prefs.addObserver("browser.sessionstore.debug", () => {
       gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
     }, false);
@@ -995,17 +983,17 @@ let SessionStoreInternal = {
     this._forEachBrowserWindow(function(aWindow) {
       this._collectWindowData(aWindow);
     });
     // we must cache this because _getMostRecentBrowserWindow will always
     // return null by the time quit-application occurs
     var activeWindow = this._getMostRecentBrowserWindow();
     if (activeWindow)
       this.activeWindowSSiCache = activeWindow.__SSi || "";
-    this._dirtyWindows = [];
+    DirtyWindows.clear();
   },
 
   /**
    * On quit application granted
    */
   onQuitApplicationGranted: function ssi_onQuitApplicationGranted() {
     // freeze the data at what we've got (ignoring closing windows)
     this._loadState = STATE_QUITTING;
@@ -2539,24 +2527,24 @@ let SessionStoreInternal = {
 
     var activeWindow = this._getMostRecentBrowserWindow();
 
     if (this._loadState == STATE_RUNNING) {
       // update the data for all windows with activities since the last save operation
       this._forEachBrowserWindow(function(aWindow) {
         if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
           return;
-        if (aUpdateAll || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
+        if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
           this._collectWindowData(aWindow);
         }
         else { // always update the window features (whose change alone never triggers a save operation)
           this._updateWindowFeatures(aWindow);
         }
       });
-      this._dirtyWindows = [];
+      DirtyWindows.clear();
     }
 
     // collect the data for all windows
     var total = [], windows = {}, ids = [];
     var nonPopupCount = 0;
     var ix;
     for (ix in this._windows) {
       if (this._windows[ix]._restoring) // window data is still in _statesToRestore
@@ -2677,17 +2665,17 @@ let SessionStoreInternal = {
     this._updateWindowFeatures(aWindow);
 
     // Make sure we keep __SS_lastSessionWindowID around for cases like entering
     // or leaving PB mode.
     if (aWindow.__SS_lastSessionWindowID)
       this._windows[aWindow.__SSi].__lastSessionWindowID =
         aWindow.__SS_lastSessionWindowID;
 
-    this._dirtyWindows[aWindow.__SSi] = false;
+    DirtyWindows.remove(aWindow);
   },
 
   /* ........ Restoring Functionality .............. */
 
   /**
    * restore features to a single window
    * @param aWindow
    *        Window reference
@@ -3005,17 +2993,17 @@ let SessionStoreInternal = {
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
       delete this._windows[aWindow.__SSi]._restoring;
 
       // It's important to set the window state to dirty so that
       // we collect their data for the first time when saving state.
-      this._dirtyWindows[aWindow.__SSi] = true;
+      DirtyWindows.add(aWindow);
     }
 
     if (aTabs.length == 0) {
       // this is normally done in restoreHistory() but as we're returning early
       // here we need to take care of it.
       this._setWindowStateReady(aWindow);
       return;
     }
@@ -3696,17 +3684,17 @@ let SessionStoreInternal = {
    * marks window as dirty (i.e. data update can't be skipped)
    * @param aWindow
    *        Window reference
    * @param aDelay
    *        Milliseconds to delay
    */
   saveStateDelayed: function ssi_saveStateDelayed(aWindow = null, aDelay = 2000) {
     if (aWindow) {
-      this._dirtyWindows[aWindow.__SSi] = true;
+      DirtyWindows.add(aWindow);
     }
 
     if (!this._saveTimer) {
       // interval until the next disk operation is allowed
       var minimalDelay = this._lastSaveTime + this._interval - Date.now();
 
       // if we have to wait, set a timer, otherwise saveState directly
       aDelay = Math.max(minimalDelay, aDelay);
@@ -4684,16 +4672,38 @@ let DyingWindowCache = {
     this._data.set(window, data);
   },
 
   remove: function (window) {
     this._data.delete(window);
   }
 };
 
+// A weak set of dirty windows. We use it to determine which windows we need to
+// recollect data for when _getCurrentState() is called.
+let DirtyWindows = {
+  _data: new WeakMap(),
+
+  has: function (window) {
+    return this._data.has(window);
+  },
+
+  add: function (window) {
+    return this._data.set(window, true);
+  },
+
+  remove: function (window) {
+    this._data.delete(window);
+  },
+
+  clear: function (window) {
+    this._data.clear();
+  }
+};
+
 // A map storing the number of tabs last closed per windoow. This only
 // stores the most recent tab-close operation, and is used to undo
 // batch tab-closing operations.
 let NumberOfTabsClosedLastPerWindow = new WeakMap();
 
 // A set of tab attributes to persist. We will read a given list of tab
 // attributes when collecting tab data and will re-set those attributes when
 // the given tab data is restored to a new tab.
--- a/browser/components/tabview/groupitems.js
+++ b/browser/components/tabview/groupitems.js
@@ -1915,17 +1915,16 @@ let GroupItems = {
   _arrangePaused: false,
   _arrangesPending: [],
   _removingHiddenGroups: false,
   _delayedModUpdates: [],
   _autoclosePaused: false,
   minGroupHeight: 110,
   minGroupWidth: 125,
   _lastActiveList: null,
-  _lastGroupToUpdateTabBar: null,
 
   // ----------
   // Function: toString
   // Prints [GroupItems] for debug use
   toString: function GroupItems_toString() {
     return "[GroupItems count=" + this.groupItems.length + "]";
   },
 
@@ -2281,20 +2280,16 @@ let GroupItems = {
     if (groupItem == this._activeGroupItem)
       this._activeGroupItem = null;
 
     this._arrangesPending = this._arrangesPending.filter(function (pending) {
       return groupItem != pending.groupItem;
     });
 
     this._lastActiveList.remove(groupItem);
-
-    if (this._lastGroupToUpdateTabBar == groupItem)
-      this._lastGroupToUpdateTabBar = null;
-
     UI.updateTabButton();
   },
 
   // ----------
   // Function: groupItem
   // Given some sort of identifier, returns the appropriate groupItem.
   // Currently only supports groupItem ids.
   groupItem: function GroupItems_groupItem(a) {
@@ -2418,23 +2413,18 @@ let GroupItems = {
   // Function: _updateTabBar
   // Hides and shows tabs in the tab bar based on the active groupItem
   _updateTabBar: function GroupItems__updateTabBar() {
     if (!window.UI)
       return; // called too soon
 
     Utils.assert(this._activeGroupItem, "There must be something to show in the tab bar!");
 
-    // Update list of visible tabs only once after switching to another group.
-    if (this._activeGroupItem == this._lastGroupToUpdateTabBar)
-      return;
-
     let tabItems = this._activeGroupItem._children;
     gBrowser.showOnlyTheseTabs(tabItems.map(function(item) item.tab));
-    this._lastGroupToUpdateTabBar = this._activeGroupItem;
   },
 
   // ----------
   // Function: updateActiveGroupItemAndTabBar
   // Sets active TabItem and GroupItem, and updates tab bar appropriately.
   // Parameters:
   // tabItem - the tab item
   // options - is passed to UI.setActive() directly
@@ -2542,30 +2532,30 @@ let GroupItems = {
       return;
 
     Utils.assertThrow(tab._tabViewTabItem, "tab must be linked to a TabItem");
 
     // given tab is already contained in target group
     if (tab._tabViewTabItem.parent && tab._tabViewTabItem.parent.id == groupItemId)
       return;
 
-    let shouldHideTab = false;
+    let shouldUpdateTabBar = false;
     let shouldShowTabView = false;
     let groupItem;
 
     // switch to the appropriate tab first.
     if (tab.selected) {
       if (gBrowser.visibleTabs.length > 1) {
         gBrowser._blurTab(tab);
-        shouldHideTab = true;
+        shouldUpdateTabBar = true;
       } else {
         shouldShowTabView = true;
       }
     } else {
-      shouldHideTab = true;
+      shouldUpdateTabBar = true
     }
 
     // remove tab item from a groupItem
     if (tab._tabViewTabItem.parent)
       tab._tabViewTabItem.parent.remove(tab._tabViewTabItem);
 
     // add tab item to a groupItem
     if (groupItemId) {
@@ -2578,18 +2568,18 @@ let GroupItems = {
 
       let box = new Rect(pageBounds);
       box.width = 250;
       box.height = 200;
 
       new GroupItem([ tab._tabViewTabItem ], { bounds: box, immediately: true });
     }
 
-    if (shouldHideTab)
-      gBrowser.hideTab(tab);
+    if (shouldUpdateTabBar)
+      this._updateTabBar();
     else if (shouldShowTabView)
       UI.showTabView();
   },
 
   // ----------
   // Function: removeHiddenGroups
   // Removes all hidden groups' data and its browser tabs.
   removeHiddenGroups: function GroupItems_removeHiddenGroups() {
--- a/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js
+++ b/browser/components/tabview/test/browser_tabview_bug624265_perwindowpb.js
@@ -94,37 +94,73 @@ function test() {
             assertOneSingleGroupItem(aWindow);
             next(aWindow);
           }, aWindow);
         }, aWindow);
       }, aWindow);
     }, aWindow);
   }
 
-  function testOnWindow(aCallback) {
-    let win = OpenBrowserWindow({private: false});
+  // [624102] check state after return from private browsing
+  let testPrivateBrowsing = function (aWindow) {
+    aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#1', {inBackground: true});
+    aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#2', {inBackground: true});
+
+    let cw = getContentWindow(aWindow);
+    let box = new cw.Rect(20, 20, 250, 200);
+    let groupItem = new cw.GroupItem([], {bounds: box, immediately: true});
+    cw.UI.setActive(groupItem);
+
+    aWindow.gBrowser.selectedTab = aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#3', {inBackground: true});
+    aWindow.gBrowser.loadOneTab('http://mochi.test:8888/#4', {inBackground: true});
+
+    afterAllTabsLoaded(function () {
+      assertNumberOfVisibleTabs(aWindow, 2);
+
+      enterAndLeavePrivateBrowsing(function () {
+        assertNumberOfVisibleTabs(aWindow, 2);
+        aWindow.gBrowser.selectedTab = aWindow.gBrowser.tabs[0];
+        closeGroupItem(cw.GroupItems.groupItems[1], function() {
+          next(aWindow);
+        });
+      });
+    }, aWindow);
+  }
+
+  function testOnWindow(aIsPrivate, aCallback) {
+    let win = OpenBrowserWindow({private: aIsPrivate});
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad, false);
       executeSoon(function() { aCallback(win) });
     }, false);
   }
 
+  function enterAndLeavePrivateBrowsing(callback) {
+    testOnWindow(true, function (aWindow) {
+      aWindow.close();
+      callback();
+    });
+  }
+
   waitForExplicitFinish();
 
   // Tests for #624265
   tests.push(testUndoCloseTabs);
 
   // Tests for #623792
   tests.push(testDuplicateTab);
   tests.push(testBackForwardDuplicateTab);
 
-  testOnWindow(function(aWindow) {
+  // Tests for #624102
+  tests.push(testPrivateBrowsing);
+
+  testOnWindow(false, function(aWindow) {
     loadTabView(function() {
       next(aWindow);
     }, aWindow);
   });
 }
 
 function loadTabView(callback, aWindow) {
   showTabView(function () {
     hideTabView(callback, aWindow);
   }, aWindow);
-}
+}
\ No newline at end of file
--- a/browser/metro/base/content/WebProgress.js
+++ b/browser/metro/base/content/WebProgress.js
@@ -129,17 +129,16 @@ const WebProgress = {
     let browser = aTab.browser;
 
     aTab._firstPaint = false;
 
     browser.messageManager.addMessageListener("Browser:FirstPaint", function firstPaintListener(aMessage) {
       browser.messageManager.removeMessageListener(aMessage.name, arguments.callee);
       aTab._firstPaint = true;
       aTab.scrolledAreaChanged(true);
-      aTab.updateThumbnailSource();
     });
   },
 
   _networkStart: function _networkStart(aJson, aTab) {
     aTab.startLoading();
 
     if (aTab == Browser.selectedTab) {
       // NO_STARTUI_VISIBILITY since the current uri for the tab has not
--- a/browser/metro/base/content/bindings/tabs.xml
+++ b/browser/metro/base/content/bindings/tabs.xml
@@ -12,17 +12,17 @@
     xmlns="http://www.mozilla.org/xbl"
     xmlns:xbl="http://www.mozilla.org/xbl"
     xmlns:html="http://www.w3.org/1999/xhtml"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <binding id="documenttab">
     <content observes="bcast_urlbarState">
       <xul:stack class="documenttab-container">
-        <xul:box anonid="thumbnail" class="documenttab-thumbnail" />
+        <html:canvas anonid="thumbnail-canvas" class="documenttab-thumbnail" />
         <xul:image anonid="favicon" class="documenttab-favicon"
                    observes="bcast_urlbarState" width="26" height="26"/>
 
         <xul:label anonid="title" class="documenttab-title" bottom="0" start="0" end="0" crop="end"/>
         <xul:box anonid="selection" class="documenttab-crop"/>
         <xul:box anonid="selection" class="documenttab-selection"/>
         <xul:button anonid="close" class="documenttab-close" observes="bcast_urlbarState" end="0" top="0"
                     onclick="event.stopPropagation(); document.getBindingParent(this)._onClose()"
@@ -31,22 +31,29 @@
     </content>
 
     <handlers>
       <handler event="click" clickcount="1" action="this._onClick()"/>
       <handler event="dblclick" action="this._onDoubleClick(); event.stopPropagation();"/>
     </handlers>
 
     <implementation>
-      <field name="_thumbnail" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "thumbnail");</field>
+      <field name="thumbnailCanvas" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "thumbnail-canvas");</field>
       <field name="_close" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "close");</field>
       <field name="_title" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "title");</field>
       <field name="_favicon" readonly="true">document.getAnonymousElementByAttribute(this, "anonid", "favicon");</field>
       <field name="_container" readonly="true">this.parentNode;</field>
 
+      <constructor>
+        <![CDATA[
+          this.thumbnailCanvas.mozOpaque = true;
+          this.thumbnailCanvas.mozImageSmoothingEnabled = true;
+        ]]>
+      </constructor>
+
       <method name="_onClick">
         <body>
           <![CDATA[
             this._container.selectedTab = this;
             let selectFn = new Function("event", this._container.parentNode.getAttribute("onselect"));
             selectFn.call(this);
           ]]>
         </body>
@@ -85,24 +92,16 @@
         <parameter name="src"/>
         <body>
           <![CDATA[
             this._favicon.src = src;
           ]]>
         </body>
       </method>
 
-      <method name="updateThumbnailSource">
-        <parameter name="browser"/>
-        <body>
-          <![CDATA[
-            this._thumbnail.style.backgroundImage = "-moz-element(#" + browser.id + ")";
-          ]]>
-        </body>
-      </method>
     </implementation>
   </binding>
 
   <binding id="tablist">
     <content>
       <xul:arrowscrollbox anonid="tabs-scrollbox" class="tabs-scrollbox" flex="1" orient="horizontal"
         clicktoscroll="true" />
     </content>
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -3,24 +3,28 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
 
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+
 const kBrowserViewZoomLevelPrecision = 10000;
 
 // allow panning after this timeout on pages with registered touch listeners
 const kTouchTimeout = 300;
 const kSetInactiveStateTimeout = 100;
 
 const kDefaultMetadata = { autoSize: false, allowZoom: true, autoScale: true };
 
+const kTabThumbnailDelayCapture = 500;
+
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // Override sizeToContent in the main window. It breaks things (bug 565887)
 window.sizeToContent = function() {
   Cu.reportError("window.sizeToContent is not allowed in this window");
 }
 
 function getTabModalPromptBox(aWindow) {
@@ -1449,16 +1453,17 @@ function showDownloadManager(aWindowCont
 function Tab(aURI, aParams, aOwner) {
   this._id = null;
   this._browser = null;
   this._notification = null;
   this._loading = false;
   this._chromeTab = null;
   this._metadata = null;
   this._eventDeferred = null;
+  this._updateThumbnailTimeout = null;
 
   this.owner = aOwner || null;
 
   // Set to 0 since new tabs that have not been viewed yet are good tabs to
   // toss if app needs more memory.
   this.lastSelected = 0;
 
   // aParams is an object that contains some properties for the initial tab
@@ -1598,23 +1603,55 @@ Tab.prototype = {
     function onPageShowEvent(aEvent) {
       browser.removeEventListener("pageshow", onPageShowEvent);
       if (self._eventDeferred) {
         self._eventDeferred.resolve(self);
       }
       self._eventDeferred = null;
     }
     browser.addEventListener("pageshow", onPageShowEvent, true);
+    browser.messageManager.addMessageListener("Content:StateChange", this);
+    Services.obs.addObserver(this, "metro_viewstate_changed", false);
 
     if (aOwner)
       this._copyHistoryFrom(aOwner);
     this._loadUsingParams(browser, aURI, aParams);
   },
 
+  receiveMessage: function(aMessage) {
+    switch (aMessage.name) {
+      case "Content:StateChange":
+        // update the thumbnail now...
+        this.updateThumbnail();
+        // ...and in a little while to capture page after load.
+        if (aMessage.json.stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+          clearTimeout(this._updateThumbnailTimeout);
+          this._updateThumbnailTimeout = setTimeout(() => {
+            this.updateThumbnail();
+          }, kTabThumbnailDelayCapture);
+        }
+        break;
+    }
+  },
+
+  observe: function BrowserUI_observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "metro_viewstate_changed":
+        if (aData !== "snapped") {
+          this.updateThumbnail();
+        }
+        break;
+    }
+  },
+
   destroy: function destroy() {
+    this._browser.messageManager.removeMessageListener("Content:StateChange", this);
+    Services.obs.removeObserver(this, "metro_viewstate_changed", false);
+    clearTimeout(this._updateThumbnailTimeout);
+
     Elements.tabList.removeTab(this._chromeTab);
     this._chromeTab = null;
     this._destroyBrowser();
   },
 
   resurrect: function resurrect() {
     let dead = this._browser;
     let active = this.active;
@@ -1813,18 +1850,18 @@ Tab.prototype = {
     let screenW = aScreenWidth || this._browser.getBoundingClientRect().width;
     return screenW / browserW;
   },
 
   get allowZoom() {
     return this.metadata.allowZoom && !Util.isURLEmpty(this.browser.currentURI.spec);
   },
 
-  updateThumbnailSource: function updateThumbnailSource() {
-    this._chromeTab.updateThumbnailSource(this._browser);
+  updateThumbnail: function updateThumbnail() {
+    PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas);
   },
 
   updateFavicon: function updateFavicon() {
     this._chromeTab.updateFavicon(this._browser.mIconURL);
   },
 
   set active(aActive) {
     if (!this._browser)
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -223,20 +223,17 @@
             <vbox id="start-remotetabs" class="meta-section">
               <label class="meta-section-title wide-title" value="&remoteTabsHeader.label;"/>
               <html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
                 &narrowRemoteTabsHeader.label;
               </html:div>
               <richgrid id="start-remotetabs-grid" set-name="remoteTabs" seltype="multiple" flex="1"/>
             </vbox>
 
-            <!-- Spacer to take extra space in snapped mode. -->
-            <spacer flex="999"/>
           </scrollbox>
-
         </hbox>
       </vbox> <!-- end tray -->
 
       <!-- Content viewport -->
       <stack id="content-viewport">
         <deck id="browsers" flex="1" observes="bcast_preciseInput"/>
         <box id="vertical-scroller" class="scroller" orient="vertical" end="0" top="0"/>
         <box id="horizontal-scroller" class="scroller" orient="horizontal" left="0" bottom="0"/>
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -7,17 +7,17 @@
 const kPositionPadding = 10;
 
 var AutofillMenuUI = {
   _popupState: null,
   __menuPopup: null,
 
   get _panel() { return document.getElementById("autofill-container"); },
   get _popup() { return document.getElementById("autofill-popup"); },
-  get _commands() { return this._popup.childNodes[0]; },
+  get commands() { return this._popup.childNodes[0]; },
 
   get _menuPopup() {
     if (!this.__menuPopup) {
       this.__menuPopup = new MenuPopup(this._panel, this._popup);
       this.__menuPopup._wantTypeBehind = true;
       this.__menuPopup.controller = this;
     }
     return this.__menuPopup;
@@ -27,18 +27,18 @@ var AutofillMenuUI = {
     let menupopup = this._currentControl.menupopup;
     if (menupopup.hasAttribute(aEventName)) {
       let func = new Function("event", menupopup.getAttribute(aEventName));
       func.call(this);
     }
   },
 
   _emptyCommands: function _emptyCommands() {
-    while (this._commands.firstChild)
-      this._commands.removeChild(this._commands.firstChild);
+    while (this.commands.firstChild)
+      this.commands.removeChild(this.commands.firstChild);
   },
 
   _positionOptions: function _positionOptions() {
     return {
       bottomAligned: false,
       leftAligned: true,
       xPos: this._anchorRect.x,
       yPos: this._anchorRect.y + this._anchorRect.height,
@@ -52,25 +52,25 @@ var AutofillMenuUI = {
     this._anchorRect = aAnchorRect;
     this._emptyCommands();
     for (let idx = 0; idx < aSuggestionsList.length; idx++) {
       let item = document.createElement("richlistitem");
       let label = document.createElement("label");
       label.setAttribute("value", aSuggestionsList[idx].label);
       item.setAttribute("data", aSuggestionsList[idx].value);
       item.appendChild(label);
-      this._commands.appendChild(item);
+      this.commands.appendChild(item);
     }
 
     this._menuPopup.show(this._positionOptions());
   },
 
   selectByIndex: function mn_selectByIndex(aIndex) {
     this._menuPopup.hide();
-    FormHelperUI.doAutoComplete(this._commands.childNodes[aIndex].getAttribute("data"));
+    FormHelperUI.doAutoComplete(this.commands.childNodes[aIndex].getAttribute("data"));
   },
 
   hide: function hide () {
     this._menuPopup.hide();
   }
 };
 
 var ContextMenuUI = {
@@ -80,17 +80,17 @@ var ContextMenuUI = {
     bottomAligned: true,
     rightAligned: false,
     centerHorizontally: true,
     moveBelowToFit: true
   },
 
   get _panel() { return document.getElementById("context-container"); },
   get _popup() { return document.getElementById("context-popup"); },
-  get _commands() { return this._popup.childNodes[0]; },
+  get commands() { return this._popup.childNodes[0]; },
 
   get _menuPopup() {
     if (!this.__menuPopup) {
       this.__menuPopup = new MenuPopup(this._panel, this._popup);
       this.__menuPopup.controller = this;
     }
     return this.__menuPopup;
   },
@@ -148,22 +148,22 @@ var ContextMenuUI = {
     // links are displayed.
     let multipleMediaTypes = false;
     if (contentTypes.indexOf("link") != -1 &&
         (contentTypes.indexOf("image") != -1  ||
          contentTypes.indexOf("video") != -1 ||
          contentTypes.indexOf("selected-text") != -1))
       multipleMediaTypes = true;
 
-    for (let command of Array.slice(this._commands.childNodes)) {
+    for (let command of Array.slice(this.commands.childNodes)) {
       command.hidden = true;
     }
 
     let optionsAvailable = false;
-    for (let command of Array.slice(this._commands.childNodes)) {
+    for (let command of Array.slice(this.commands.childNodes)) {
       let types = command.getAttribute("type").split(",");
       let lowPriority = (command.hasAttribute("priority") &&
         command.getAttribute("priority") == "low");
       let searchTextItem = (command.id == "context-search");
 
       // filter low priority items if we have more than one media type.
       if (multipleMediaTypes && lowPriority)
         continue;
@@ -216,17 +216,17 @@ var ContextMenuUI = {
 };
 
 var MenuControlUI = {
   _currentControl: null,
   __menuPopup: null,
 
   get _panel() { return document.getElementById("menucontrol-container"); },
   get _popup() { return document.getElementById("menucontrol-popup"); },
-  get _commands() { return this._popup.childNodes[0]; },
+  get commands() { return this._popup.childNodes[0]; },
 
   get _menuPopup() {
     if (!this.__menuPopup) {
       this.__menuPopup = new MenuPopup(this._panel, this._popup);
       this.__menuPopup.controller = this;
     }
     return this.__menuPopup;
   },
@@ -235,18 +235,18 @@ var MenuControlUI = {
     let menupopup = this._currentControl.menupopup;
     if (menupopup.hasAttribute(aEventName)) {
       let func = new Function("event", menupopup.getAttribute(aEventName));
       func.call(this);
     }
   },
 
   _emptyCommands: function _emptyCommands() {
-    while (this._commands.firstChild)
-      this._commands.removeChild(this._commands.firstChild);
+    while (this.commands.firstChild)
+      this.commands.removeChild(this.commands.firstChild);
   },
 
   _positionOptions: function _positionOptions() {
     let position = this._currentControl.menupopup.position || "after_start";
     let rect = this._currentControl.getBoundingClientRect();
 
     let options = {};
 
@@ -309,17 +309,17 @@ var MenuControlUI = {
       let image = document.createElement("image");
       image.setAttribute("src", child.image || "");
       item.appendChild(image);
 
       let label = document.createElement("label");
       label.setAttribute("value", child.label);
       item.appendChild(label);
 
-      this._commands.appendChild(item);
+      this.commands.appendChild(item);
     }
 
     this._menuPopup.show(this._positionOptions());
   },
 
   selectByIndex: function mn_selectByIndex(aIndex) {
     this._currentControl.selectedIndex = aIndex;
 
@@ -333,94 +333,35 @@ var MenuControlUI = {
     this._menuPopup.hide();
   }
 };
 
 function MenuPopup(aPanel, aPopup) {
   this._panel = aPanel;
   this._popup = aPopup;
   this._wantTypeBehind = false;
-  this._willReshowPopup = false;
 
   window.addEventListener('MozAppbarShowing', this, false);
 }
 MenuPopup.prototype = {
-  get _visible() { return !this._panel.hidden; },
-  get _commands() { return this._popup.childNodes[0]; },
+  get visible() { return !this._panel.hidden; },
+  get commands() { return this._popup.childNodes[0]; },
 
   show: function (aPositionOptions) {
-    if (this._visible) {
-      this._willReshowPopup = true;
-      let self = this;
-      this._panel.addEventListener("transitionend", function () {
-        self._show(aPositionOptions);
-        self._panel.removeEventListener("transitionend", arguments.callee);
-      });
+    if (this.visible) {
+      this._animateHide().then(() => this._animateShow(aPositionOptions));
     } else {
-      this._show(aPositionOptions);
+      this._animateShow(aPositionOptions);
     }
   },
 
-  _show: function (aPositionOptions) {
-    window.addEventListener("keypress", this, true);
-    window.addEventListener("mousedown", this, true);
-    Elements.stack.addEventListener("PopupChanged", this, false);
-    Elements.browsers.addEventListener("PanBegin", this, false);
-
-    this._panel.hidden = false;
-    this._position(aPositionOptions || {});
-
-    let self = this;
-    this._panel.addEventListener("transitionend", function () {
-      self._panel.removeEventListener("transitionend", arguments.callee);
-      self._panel.removeAttribute("showingfrom");
-
-      let eventName = self._willReshowPopup ? "popupmoved" : "popupshown";
-      let event = document.createEvent("Events");
-      event.initEvent(eventName, true, false);
-      self._panel.dispatchEvent(event);
-
-      self._willReshowPopup = false;
-    });
-
-    let popupFrom = !aPositionOptions.bottomAligned ? "above" : "below";
-    this._panel.setAttribute("showingfrom", popupFrom);
-
-    // Ensure the panel actually gets shifted before getting animated
-    setTimeout(function () {
-      self._panel.setAttribute("showing", "true");
-    }, 0);
-  },
-
   hide: function () {
-    if (!this._visible)
-      return;
-
-    window.removeEventListener("keypress", this, true);
-    window.removeEventListener("mousedown", this, true);
-    Elements.stack.removeEventListener("PopupChanged", this, false);
-    Elements.browsers.removeEventListener("PanBegin", this, false);
-
-    let self = this;
-    this._panel.addEventListener("transitionend", function () {
-      self._panel.removeEventListener("transitionend", arguments.callee);
-      self._panel.removeAttribute("hiding");
-      self._panel.hidden = true;
-      self._popup.style.maxWidth = "none";
-      self._popup.style.maxHeight = "none";
-
-      if (!self._willReshowPopup) {
-        let event = document.createEvent("Events");
-        event.initEvent("popuphidden", true, false);
-        self._panel.dispatchEvent(event);
-      }
-    });
-
-    this._panel.setAttribute("hiding", "true");
-    setTimeout(()=>this._panel.removeAttribute("showing"), 0);
+    if (this.visible) {
+      this._animateHide();
+    }
   },
 
   _position: function _position(aPositionOptions) {
     let aX = aPositionOptions.xPos;
     let aY = aPositionOptions.yPos;
     let aSource = aPositionOptions.source;
 
     // Set these first so they are set when we do misc. calculations below.
@@ -435,17 +376,17 @@ MenuPopup.prototype = {
     let height = this._popup.boxObject.height;
     let halfWidth = width / 2;
     let screenWidth = ContentAreaObserver.width;
     let screenHeight = ContentAreaObserver.height;
 
     // Add padding on the side of the menu per the user's hand preference
     let leftHand = MetroUtils.handPreference == MetroUtils.handPreferenceLeft;
     if (aSource && aSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
-      this._commands.setAttribute("left-hand", leftHand);
+      this.commands.setAttribute("left-hand", leftHand);
     }
 
     if (aPositionOptions.rightAligned)
       aX -= width;
 
     if (aPositionOptions.bottomAligned)
       aY -= height;
 
@@ -481,28 +422,88 @@ MenuPopup.prototype = {
     }
 
     if (!aPositionOptions.maxWidth) {
       let popupWidth = Math.min(aX + width + kPositionPadding, screenWidth - aX - kPositionPadding);
       this._popup.style.maxWidth = popupWidth + "px";
     }
   },
 
+  _animateShow: function (aPositionOptions) {
+    let deferred = Promise.defer();
+
+    window.addEventListener("keypress", this, true);
+    window.addEventListener("click", this, true);
+    Elements.stack.addEventListener("PopupChanged", this, false);
+    Elements.browsers.addEventListener("PanBegin", this, false);
+
+    this._panel.hidden = false;
+    let popupFrom = !aPositionOptions.bottomAligned ? "above" : "below";
+    this._panel.setAttribute("showingfrom", popupFrom);
+
+    // This triggers a reflow, which sets transitionability.
+    // All animation/transition setup must happen before here.
+    this._position(aPositionOptions || {});
+
+    let self = this;
+    this._panel.addEventListener("transitionend", function popupshown () {
+      self._panel.removeEventListener("transitionend", popupshown);
+      self._panel.removeAttribute("showingfrom");
+
+      self._dispatch("popupshown");
+      deferred.resolve();
+    });
+
+    this._panel.setAttribute("showing", "true");
+    return deferred.promise;
+  },
+
+  _animateHide: function () {
+    let deferred = Promise.defer();
+
+    window.removeEventListener("keypress", this, true);
+    window.removeEventListener("click", this, true);
+    Elements.stack.removeEventListener("PopupChanged", this, false);
+    Elements.browsers.removeEventListener("PanBegin", this, false);
+
+    let self = this;
+    this._panel.addEventListener("transitionend", function popuphidden() {
+      self._panel.removeEventListener("transitionend", popuphidden);
+      self._panel.removeAttribute("hiding");
+      self._panel.hidden = true;
+      self._popup.style.maxWidth = "none";
+      self._popup.style.maxHeight = "none";
+
+      self._dispatch("popuphidden");
+      deferred.resolve();
+    });
+
+    this._panel.setAttribute("hiding", "true");
+    this._panel.removeAttribute("showing");
+    return deferred.promise;
+  },
+
+  _dispatch: function _dispatch(aName) {
+    let event = document.createEvent("Events");
+    event.initEvent(aName, true, false);
+    this._panel.dispatchEvent(event);
+  },
+
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "keypress":
         if (!this._wantTypeBehind) {
           // Hide the context menu so you can't type behind it.
           aEvent.stopPropagation();
           aEvent.preventDefault();
           if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE)
             this.hide();
         }
         break;
-      case "mousedown":
+      case "click":
         if (!this._popup.contains(aEvent.target)) {
           aEvent.stopPropagation();
           this.hide();
         }
         break;
       case "PopupChanged":
         if (aEvent.detail) {
           this.hide();
--- a/browser/metro/base/tests/mochitest/Makefile.in
+++ b/browser/metro/base/tests/mochitest/Makefile.in
@@ -5,29 +5,27 @@
 DEPTH     = @DEPTH@
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-# Disabled for intermittent failures 
-# Bug 880739
-#  browser_context_menu_tests.js \
-#  browser_context_menu_tests_01.html \
-#  browser_context_menu_tests_02.html \
-#  browser_context_menu_tests_03.html \
-
 MOCHITEST_METRO_FILES = \
   head.js \
   browser_urlbar.js \
   browser_bookmarks.js \
   browser_canonizeURL.js \
   browser_circular_progress_indicator.js \
+  browser_context_menu_tests.js \
+  browser_context_menu_tests_01.html \
+  browser_context_menu_tests_02.html \
+  browser_context_menu_tests_03.html \
+  browser_context_menu_tests_04.html \
   browser_context_ui.js \
   browser_downloads.js \
   browser_findbar.js \
   browser_findbar.html \
   browser_history.js \
   browser_onscreen_keyboard.js \
   browser_onscreen_keyboard.html \
   browser_prefs_ui.js \
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -53,17 +53,17 @@ gTests.push({
     yield waitForMs(0);
 
     // invoke selection context menu
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, span);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-copy",
                                       "context-search"]);
 
     let menuItem = document.getElementById("context-copy");
     promise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
@@ -86,17 +86,17 @@ gTests.push({
     // invoke selection with link context menu
     let link = win.document.getElementById("text2-link");
     win.getSelection().selectAllChildren(link);
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, link);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-copy",
                                       "context-search",
                                       "context-open-in-new-tab",
                                       "context-copy-link"]);
 
     promise = waitForEvent(document, "popuphidden");
@@ -108,17 +108,17 @@ gTests.push({
     // Context menu in content on a link
 
     link = win.document.getElementById("text2-link");
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, link);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-open-in-new-tab",
                                       "context-copy-link",
                                       "context-bookmark-link"]);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
@@ -130,17 +130,17 @@ gTests.push({
     emptyClipboard();
 
     let input = win.document.getElementById("text3-input");
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextUIMenuItemVisibility(["context-select",
                                       "context-select-all"]);
 
     // copy menu item should not exist when no text is selected
     let menuItem = document.getElementById("context-copy");
     ok(menuItem && menuItem.hidden, "menu item is not visible");
 
@@ -154,17 +154,17 @@ gTests.push({
     let input = win.document.getElementById("text3-input");
     input.value = "hello, I'm sorry but I must be going.";
     input.setSelectionRange(0, 5);
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextUIMenuItemVisibility(["context-cut",
                                       "context-copy"]);
 
     let menuItem = document.getElementById("context-copy");
     let popupPromise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
 
@@ -186,17 +186,17 @@ gTests.push({
 
     input = win.document.getElementById("text3-input");
     input.select();
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-cut",
                                       "context-copy"]);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
@@ -207,17 +207,17 @@ gTests.push({
     SpecialPowers.clipboardCopyString("foo");
     input = win.document.getElementById("text3-input");
     input.select();
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-cut",
                                       "context-copy",
                                       "context-paste"]);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
@@ -231,17 +231,17 @@ gTests.push({
     let input = win.document.getElementById("text3-input");
     input.value = "hello, I'm sorry but I must be going.";
     input.setSelectionRange(0, 5);
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextUIMenuItemVisibility(["context-cut",
                                       "context-copy"]);
 
     let menuItem = document.getElementById("context-cut");
     let popupPromise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
 
@@ -267,17 +267,17 @@ gTests.push({
     input = win.document.getElementById("text3-input");
     input.value = "";
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     // selected text context:
     checkContextUIMenuItemVisibility(["context-paste"]);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
@@ -290,17 +290,17 @@ gTests.push({
     input = win.document.getElementById("text3-input");
     input.value = "";
 
     promise = waitForEvent(Elements.tray, "transitionend");
     sendContextMenuClickToElement(win, input, 20);
     yield promise;
 
     // should *not* be visible
-    ok(!ContextMenuUI._menuPopup._visible, "is visible");
+    ok(!ContextMenuUI._menuPopup.visible, "is visible");
 
     // the test above will invoke the app bar
     yield hideContextUI();
 
     Browser.closeTab(Browser.selectedTab, { forceClose: true });
     purgeEventQueue();
   }
 });
@@ -331,32 +331,35 @@ gTests.push({
     browserwin.getSelection().selectAllChildren(span);
 
     // invoke selection context menu
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClick(225, 310);
     yield promise;
 
     // should be visible and at a specific position
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     let notificationBox = Browser.getNotificationBox();
     let notification = notificationBox.getNotificationWithValue("popup-blocked");
     let notificationHeight = notification.boxObject.height;
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 65, 80, notificationHeight +  155, notificationHeight + 180);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
     Browser.closeTab(Browser.selectedTab, { forceClose: true });
   }
 });
 
+/*
+XXX code used to diagnose bug 880739
+
 var observeLogger = {
   observe: function (aSubject, aTopic, aData) {
     info("observeLogger: " + aTopic);
   },
   QueryInterface: function (aIID) {
     if (!aIID.equals(Ci.nsIObserver) &&
         !aIID.equals(Ci.nsISupportsWeakReference) &&
         !aIID.equals(Ci.nsISupports)) {
@@ -378,25 +381,28 @@ var observeLogger = {
     Services.obs.removeObserver(observeLogger, "dl-done");
     Services.obs.removeObserver(observeLogger, "dl-failed");
     Services.obs.removeObserver(observeLogger, "dl-scanning");
     Services.obs.removeObserver(observeLogger, "dl-blocked");
     Services.obs.removeObserver(observeLogger, "dl-dirty");
     Services.obs.removeObserver(observeLogger, "dl-cancel");
   }
 }
+*/
 
 // Image context menu tests
 gTests.push({
   desc: "image context menu",
   setUp: function() {
-    observeLogger.init();
+    // XXX code used to diagnose bug 880739
+    //observeLogger.init();
   },
   tearDown: function() {
-    observeLogger.shutdown();
+    // XXX code used to diagnose bug 880739
+    //observeLogger.shutdown();
   },
   run: function test() {
     info(chromeRoot + "browser_context_menu_tests_01.html");
     yield addTab(chromeRoot + "browser_context_menu_tests_01.html");
 
     let win = Browser.selectedTab.browser.contentWindow;
 
     purgeEventQueue();
@@ -406,25 +412,25 @@ gTests.push({
     // If we don't do this, sometimes the first sendContextMenuClickToWindow
     // will trigger the app bar.
     yield waitForImageLoad(win, "image01");
 
     ////////////////////////////////////////////////////////////
     // Context menu options
     /*
     XXX disabled temporarily due to bug 880739
-    
+
     // image01 - 1x1x100x100
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToWindow(win, 10, 10);
     yield promise;
 
     purgeEventQueue();
 
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextUIMenuItemVisibility(["context-save-image-lib",
                                       "context-copy-image",
                                       "context-copy-image-loc",
                                       "context-open-image-tab"]);
 
     ////////////////////////////////////////////////////////////
     // Save to image library
@@ -460,17 +466,17 @@ gTests.push({
     ok(saveLocationPath.exists(), "image saved");
     */
     ////////////////////////////////////////////////////////////
     // Copy image
 
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToWindow(win, 20, 20);
     yield promise;
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     let menuItem = document.getElementById("context-copy-image");
     ok(menuItem, "menu item exists");
     ok(!menuItem.hidden, "menu item visible");
     let popupPromise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
     yield popupPromise;
 
@@ -481,17 +487,17 @@ gTests.push({
     ok(clip.hasDataMatchingFlavors(flavors, flavors.length, Ci.nsIClipboard.kGlobalClipboard), "clip has my png flavor");
 
     ////////////////////////////////////////////////////////////
     // Copy image location
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToWindow(win, 30, 30);
     yield promise;
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     menuItem = document.getElementById("context-copy-image-loc");
     ok(menuItem, "menu item exists");
     ok(!menuItem.hidden, "menu item visible");
     popupPromise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
     yield popupPromise;
 
@@ -514,17 +520,17 @@ gTests.push({
     ok(str == chromeRoot + "res/image01.png", "url copied");
 
     ////////////////////////////////////////////////////////////
     // Open image in new tab
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToWindow(win, 40, 40);
     yield promise;
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     menuItem = document.getElementById("context-open-image-tab");
     ok(menuItem, "menu item exists");
     ok(!menuItem.hidden, "menu item visible");
     let tabPromise = waitForEvent(document, "TabOpen");
     popupPromise = waitForEvent(document, "popuphidden");
     EventUtils.synthesizeMouse(menuItem, 10, 10, {}, win);
     yield popupPromise;
@@ -559,81 +565,152 @@ gTests.push({
     let frame1 = win.document.getElementById("frame1");
     let link1 = frame1.contentDocument.getElementById("link1");
 
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(frame1.contentDocument.defaultView, link1, 85, 10);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 175, 190);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
     frame1.contentDocument.defaultView.scrollBy(0, 200);
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(frame1.contentDocument.defaultView, link1, 85, 10);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 95, 110);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
     let rlink1 = win.document.getElementById("rlink1");
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, rlink1, 40, 10);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 295, 310, 540, 555);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
     win.scrollBy(0, 200);
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(win, rlink1, 40, 10);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 295, 310, 340, 355);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
 
     let link2 = frame1.contentDocument.getElementById("link2");
 
     promise = waitForEvent(document, "popupshown");
     sendContextMenuClickToElement(frame1.contentDocument.defaultView, link2, 85, 10);
     yield promise;
 
     // should be visible
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     checkContextMenuPositionRange(ContextMenuUI._panel, 265, 280, 110, 125);
 
     promise = waitForEvent(document, "popuphidden");
     ContextMenuUI.hide();
     yield promise;
+
+    Browser.closeTab(Browser.selectedTab, { forceClose: true });
   }
 });
 
+function reopenSetUp() {
+  info(chromeRoot + "browser_context_menu_tests_04.html");
+  yield addTab(chromeRoot + "browser_context_menu_tests_04.html");
+
+  // Sometimes the context UI won't actually show up.
+  // Since we're just normalizing, we don't want waitForCondition
+  // to cause an orange, so we're putting a try/catch here.
+  try {
+    yield waitForCondition(() => ContextUI.isVisible);
+    ContextUI.dismiss();
+  } catch(e) {}
+}
+
+function reopenTearDown() {
+  let promise = waitForEvent(document, "popuphidden")
+  ContextMenuUI.hide();
+  yield promise;
+  ok(!ContextMenuUI._menuPopup.visible, "popup is actually hidden");
+
+  Browser.closeTab(Browser.selectedTab, { forceClose: true });
+}
+
+function getReopenTest(aElementInputFn, aWindowInputFn) {
+  return function () {
+    let win = Browser.selectedTab.browser.contentWindow;
+    let panel = ContextMenuUI._menuPopup._panel;
+
+    let link1 = win.document.getElementById("text1-link");
+    let link2 = win.document.getElementById("text2-link");
+
+    // Show the menu on link 1
+    let showpromise = waitForEvent(panel, "popupshown");
+    aElementInputFn(win, link1);
+
+    ok((yield showpromise), "popupshown event fired");
+    ok(ContextMenuUI._menuPopup.visible, "initial popup is visible");
+
+    // Show the menu on link 2
+    let hidepromise = waitForEvent(panel, "popuphidden");
+    showpromise = waitForEvent(panel, "popupshown");
+    aElementInputFn(win, link2);
+
+    ok((yield hidepromise), "popuphidden event fired");
+    ok((yield showpromise), "popupshown event fired");
+    ok(ContextMenuUI._menuPopup.visible, "popup is still visible");
+
+    // Hide the menu
+    hidepromise = waitForEvent(panel, "popuphidden")
+    aWindowInputFn(win, 10, 10);
+
+    ok((yield hidepromise), "popuphidden event fired");
+    ok(!ContextMenuUI._menuPopup.visible, "popup is no longer visible");
+  }
+}
+
+gTests.push({
+  desc: "bug 856264 - mouse - context menu should reopen on other links",
+  setUp: reopenSetUp,
+  tearDown: reopenTearDown,
+  run: getReopenTest(sendContextMenuMouseClickToElement, sendMouseClick)
+});
+
+gTests.push({
+  desc: "bug 856264 - touch - context menu should reopen on other links",
+  setUp: reopenSetUp,
+  tearDown: reopenTearDown,
+  run: getReopenTest(sendContextMenuClickToElement, sendTap)
+});
+
 function test() {
   runTests();
 }
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests_04.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+    </style>
+  </head>
+<body style="padding: 10px; margin: 10px;">
+  <div style="margin: 0; padding: 200px 0;">
+    <span id="text1">hello, I'm sorry but I <a id="text1-link" href="#test">must be going</a>.</span>
+  </div>
+  <div style="margin: 0; padding: 200px 0;">
+    <span id="text2"><a id="text2-link" href="#test">hello, I'm sorry but</a> I must be going.</span>
+  </div>
+</body>
+</html>
\ No newline at end of file
--- a/browser/metro/base/tests/mochitest/browser_form_auto_complete.js
+++ b/browser/metro/base/tests/mochitest/browser_form_auto_complete.js
@@ -24,18 +24,18 @@ function setUp() {
 function tearDown() {
   PanelUI.hide();
 }
 
 function checkAutofillMenuItemContents(aItemList)
 {
   let errors = 0;
   let found = 0;
-  for (let idx = 0; idx < AutofillMenuUI._commands.childNodes.length; idx++) {
-    let item = AutofillMenuUI._commands.childNodes[idx];
+  for (let idx = 0; idx < AutofillMenuUI.commands.childNodes.length; idx++) {
+    let item = AutofillMenuUI.commands.childNodes[idx];
     let label = item.firstChild.getAttribute("value");
     let value = item.getAttribute("data");
     if (aItemList.indexOf(value) == -1) {
       errors++;
       info("unexpected entry:" + value);
     } else {
       found++;
     }
--- a/browser/metro/base/tests/mochitest/browser_selection_frame_content.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_frame_content.js
@@ -133,17 +133,17 @@ gTests.push({
     is(SelectionHelperUI.isActive, true, "selection active");
     is(getTrimmedSelection(gFrame).toString(), "started", "selection test");
 
     let promise = waitForEvent(document, "popupshown");
     sendContextMenuClick(527, 188);
 
     yield promise;
     ok(promise && !(promise instanceof Error), "promise error");
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
 
     let menuItem = document.getElementById("context-copy");
     ok(menuItem, "menu item exists");
     ok(!menuItem.hidden, "menu item visible");
     let popupPromise = waitForEvent(document, "popuphidden");
     sendElementTap(gWindow, menuItem);
     yield popupPromise;
     ok(popupPromise && !(popupPromise instanceof Error), "promise error");
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -73,17 +73,17 @@ gTests.push({
 
     yield showNavBar();
     let edit = document.getElementById("urlbar-edit");
 
     SpecialPowers.clipboardCopyString("mozilla");
     sendContextMenuClickToElement(window, edit);
     yield waitForEvent(document, "popupshown");
 
-    ok(ContextMenuUI._menuPopup._visible, "is visible");
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
     let paste = document.getElementById("context-paste");
     ok(!paste.hidden, "paste item is visible");
 
     sendElementTap(window, paste);
     ok(edit.popup.popupOpen, "bug: popup should be showing");
 
     clearSelection(edit);
   }
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -34,28 +34,28 @@ function setDevPixelEqualToPx()
   registerCleanupFunction(function () {
     SpecialPowers.clearUserPref("layout.css.devPixelsPerPx");
   });
 }
 
 function checkContextUIMenuItemCount(aCount)
 {
   let visibleCount = 0;
-  for (let idx = 0; idx < ContextMenuUI._commands.childNodes.length; idx++) {
-    if (!ContextMenuUI._commands.childNodes[idx].hidden)
+  for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+    if (!ContextMenuUI.commands.childNodes[idx].hidden)
       visibleCount++;
   }
   is(visibleCount, aCount, "command list count");
 }
 
 function checkContextUIMenuItemVisibility(aVisibleList)
 {
   let errors = 0;
-  for (let idx = 0; idx < ContextMenuUI._commands.childNodes.length; idx++) {
-    let item = ContextMenuUI._commands.childNodes[idx];
+  for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+    let item = ContextMenuUI.commands.childNodes[idx];
     if (aVisibleList.indexOf(item.id) != -1 && item.hidden) {
       // item should be visible
       errors++;
       info("should be visible:" + item.id);
     } else if (aVisibleList.indexOf(item.id) == -1 && !item.hidden) {
       // item should be hidden
       errors++;
       info("should be hidden:" + item.id);
@@ -546,16 +546,30 @@ function logicalCoordsForElement (aEleme
   let rect = aElement.getBoundingClientRect();
 
   coords.x = isNaN(aX) ? rect.left + (rect.width / 2) : rect.left + aX;
   coords.y = isNaN(aY) ? rect.top + (rect.height / 2) : rect.top + aY;
 
   return coords;
 }
 
+function sendContextMenuMouseClickToElement(aWindow, aElement, aX, aY) {
+  let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIDOMWindowUtils);
+  let coords = logicalCoordsForElement(aElement, aX, aY);
+
+  utils.sendMouseEventToWindow("mousedown", coords.x, coords.y, 2, 1, 0);
+  utils.sendMouseEventToWindow("mouseup", coords.x, coords.y, 2, 1, 0);
+  utils.sendMouseEventToWindow("contextmenu", coords.x, coords.y, 2, 1, 0);
+}
+
+function sendMouseClick(aWindow, aX, aY) {
+  EventUtils.synthesizeMouseAtPoint(aX, aY, {}, aWindow);
+}
+
 /*
  * sendContextMenuClick - simulates a press-hold touch input event. Event
  * is delivered to the main window of the application through the top-level
  * widget.
  *
  * @param aX, aY logical coordinates of the event.
  */
 function sendContextMenuClick(aX, aY) {
--- a/browser/metro/modules/colorUtils.jsm
+++ b/browser/metro/modules/colorUtils.jsm
@@ -45,18 +45,70 @@ let ColorUtils = {
 
     if (w3cContrastValue > 125) {
       // bright/light, use black text
       textColor = "rgb(0,0,0)";
     }
     return textColor;
   },
 
+  toCSSRgbColor: function toCSSRgbColor(r, g, b, a) {
+    var values = [Math.round(r), Math.round(g), Math.round(b)];
+    if(undefined !== a && a < 1) {
+      values.push(a);
+      return 'rgba('+values.join(',')+')';
+    }
+    return 'rgb('+values.join(',')+')';
+  },
+
   /**
-   * converts a decimal(base10) number into rgb string
+   * converts a decimal(base10) number into CSS rgb color value string
    */
   convertDecimalToRgbColor: function convertDecimalToRgbColor(aColor) {
-    let r = (aColor & 0xff0000) >> 16;
-    let g = (aColor & 0x00ff00) >> 8;
-    let b = (aColor & 0x0000ff);
-    return "rgb("+r+","+g+","+b+")";
+    let [r,g,b,a] = this.unpackDecimalColorWord(aColor);
+    return this.toCSSRgbColor(r,g,b,a);
+  },
+
+  /**
+   * unpack a decimal(base10) word for r,g,b,a values
+   */
+  unpackDecimalColorWord: function unpackDecimalColorWord(aColor) {
+    let a = (aColor & 0xff000000) >> 24;
+    let r = (aColor & 0x00ff0000) >> 16;
+    let g = (aColor & 0x0000ff00) >> 8;
+    let b = (aColor & 0x000000ff);
+    // NB: falsy alpha treated as undefined, fully opaque
+    return a ? [r,g,b,a/255] : [r,g,b];
+  },
+
+  /**
+   * create a decimal(base10) word for r,g,b values
+   */
+  createDecimalColorWord: function createDecimalColorWord(r, g, b, a) {
+    let rgb = 0;
+    rgb |= b;
+    rgb |= (g << 8);
+    rgb |= (r << 16);
+    // pack alpha value if one is given
+    if(undefined !== a && a < 1)
+      rgb |= (Math.round(a*255) << 24);
+    return rgb;
+  },
+
+  /**
+   * Add 2 rgb(a) colors to get a flat color
+   */
+  addRgbColors: function addRgbColors(color1, color2) {
+    let [r1, g1, b1] = this.unpackDecimalColorWord(color1);
+    let [r2, g2, b2, alpha] = this.unpackDecimalColorWord(color2);
+
+    let color = {};
+    // early return if 2nd color is opaque
+    if (!alpha || alpha >= 1)
+      return color2;
+
+    return this.createDecimalColorWord(
+      Math.min(255, alpha * r2 + (1 - alpha) * r1),
+      Math.min(255, alpha * g2 + (1 - alpha) * g1),
+      Math.min(255, alpha * b2 + (1 - alpha) * b1)
+    );
   }
 };
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -122,16 +122,17 @@ documenttab[closing] > .documenttab-cont
 .documenttab-favicon {
   visibility: collapse;
 }
 
 .documenttab-thumbnail {
   margin: @metro_spacing_normal@ @metro_spacing_snormal@;
   background: white none center top no-repeat;
   background-size: cover;
+  min-width: @thumbnail_width@;
   width: @thumbnail_width@;
   height: @thumbnail_height@;
 }
 #tray:not([expanded]) .documenttab-thumbnail {
   background-image: none!important;
 }
 
 .documenttab-title {
@@ -229,27 +230,28 @@ documenttab[selected] .documenttab-selec
 #start-container[viewstate="snapped"] #start-scrollbox {
   -moz-box-orient: vertical;
 }
 
 /*Formatting for the limited horizontal space of snapped*/
 #start-autocomplete[viewstate="snapped"] .richgrid-item-content {
   -moz-box-orient: horizontal;
 }
+
 #start-container,
 #start-autocomplete {
   padding-left: 0;
   padding-right: 0;
 }
+
 #start-container[viewstate="snapped"] #start-scrollbox > .meta-section {
-  margin: 0;
+  margin: 0 @metro_spacing_xnormal@;
   min-width: @grid_double_column_width@;
-  -moz-box-flex: 1;
-  -moz-box-align: center;
 }
+
 #start-container[viewstate="snapped"] richgrid {
   visibility: collapse;
 }
 
 #start-container[viewstate="snapped"] .meta-section[expanded] > richgrid {
   visibility: visible;
 }
 
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -576,16 +576,26 @@ arrowbox {
 
 .meta-section-title {
   font-size: @metro_font_large@;
   font-weight: 100;
   display: none;
   cursor: default;
 }
 
+#start-container[viewstate="snapped"] {
+  padding-top: 0;
+}
+
+#start-container[viewstate="snapped"] .meta-section-title,
+#start-container[viewstate="snapped"] richgrid {
+  margin-top: @metro_spacing_xnormal@;
+  padding: 0;
+}
+
 #start-container[viewstate="snapped"] .meta-section-title.narrow-title,
 #start-container:not([viewstate="snapped"]) .meta-section-title.wide-title {
   display: block;
 }
 
 .meta-section:not([expanded]) > .meta-section-title.narrow-title:-moz-locale-dir(ltr):after {
   content: ">";
 }
--- a/browser/metro/theme/tiles.css
+++ b/browser/metro/theme/tiles.css
@@ -277,9 +277,18 @@ richgriditem[bending] > .tile-content {
     background: #fff;
     padding: 8px;
   }
   .tile-icon-box {
     padding: 2px;
     background: #fff;
     opacity: 1.0;
   }
+
+  .tile-content {
+    left: 0;
+    right: 0;
+  }
+
+  richgriditem {
+    width: auto;
+  }
 }
--- a/browser/modules/BrowserNewTabPreloader.jsm
+++ b/browser/modules/BrowserNewTabPreloader.jsm
@@ -285,24 +285,17 @@ let HiddenBrowsers = {
     }
 
     return sizes;
   }
 };
 
 function HiddenBrowser(width, height) {
   this.resize(width, height);
-
-  HostFrame.get().then(aFrame => {
-    let doc = aFrame.document;
-    this._browser = doc.createElementNS(XUL_NS, "browser");
-    this._browser.setAttribute("type", "content");
-    this._browser.setAttribute("src", NEWTAB_URL);
-    doc.getElementById("win").appendChild(this._browser);
-  });
+  this._createBrowser();
 }
 
 HiddenBrowser.prototype = {
   _width: null,
   _height: null,
   _timer: null,
 
   get isPreloaded() {
@@ -312,55 +305,78 @@ HiddenBrowser.prototype = {
            this._browser.currentURI.spec === NEWTAB_URL;
   },
 
   swapWithNewTab: function (aTab) {
     if (!this.isPreloaded || this._timer) {
       return false;
     }
 
-    let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
+    let win = aTab.ownerDocument.defaultView;
+    let tabbrowser = win.gBrowser;
+
     if (!tabbrowser) {
       return false;
     }
 
     // Swap docShells.
     tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
 
+    // Load all default frame scripts attached to the target window.
+    let mm = aTab.linkedBrowser.messageManager;
+    let scripts = win.messageManager.getDelayedFrameScripts();
+    Array.forEach(scripts, script => mm.loadFrameScript(script, true));
+
+    // Remove the browser, it will be recreated by a timer.
+    this._removeBrowser();
+
     // Start a timer that will kick off preloading the next newtab page.
     this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
 
     // Signal that we swapped docShells.
     return true;
   },
 
   observe: function () {
     this._timer = null;
 
     // Start pre-loading the new tab page.
-    this._browser.loadURI(NEWTAB_URL);
+    this._createBrowser();
   },
 
   resize: function (width, height) {
     if (this._browser) {
       this._browser.style.width = width + "px";
       this._browser.style.height = height + "px";
     } else {
       this._width = width;
       this._height = height;
     }
   },
 
   destroy: function () {
+    this._removeBrowser();
+    this._timer = clearTimer(this._timer);
+  },
+
+  _createBrowser: function () {
+    HostFrame.get().then(aFrame => {
+      let doc = aFrame.document;
+      this._browser = doc.createElementNS(XUL_NS, "browser");
+      this._browser.setAttribute("type", "content");
+      this._browser.setAttribute("src", NEWTAB_URL);
+      doc.getElementById("win").appendChild(this._browser);
+    });
+  },
+
+  _removeBrowser: function () {
     if (this._browser) {
       this._browser.remove();
       this._browser = null;
     }
-
-    this._timer = clearTimer(this._timer);
   }
 };
 
 let HostFrame = {
   _frame: null,
   _deferred: null,
 
   get hiddenDOMDocument() {
--- a/content/base/public/nsIMessageManager.idl
+++ b/content/base/public/nsIMessageManager.idl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
+interface nsIDOMDOMStringList;
 interface nsIDOMWindow;
 interface nsIDocShell;
 interface nsIContent;
 
 /**
  * Message managers provide a way for chrome-privileged JS code to
  * communicate with each other, even across process boundaries.
  *
@@ -315,32 +316,38 @@ interface nsIContentFrameMessageManager 
 };
 
 [uuid(a2325927-9c0c-437d-9215-749c79235031)]
 interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
 {
   [notxpcom] nsIContent getOwnerContent();
 };
 
-[scriptable, builtinclass, uuid(a54acd34-4141-46f5-b71b-e2ca32879b08)]
+[scriptable, builtinclass, uuid(ecebfb8c-ff51-11e2-9d65-7af553959281)]
 interface nsIFrameScriptLoader : nsISupports
 {
   /**
    * Load a script in the (remote) frame. aURL must be the absolute URL.
    * data: URLs are also supported. For example data:,dump("foo\n");
    * If aAllowDelayedLoad is true, script will be loaded when the
    * remote frame becomes available. Otherwise the script will be loaded
    * only if the frame is already available.
    */
   void loadFrameScript(in AString aURL, in boolean aAllowDelayedLoad);
 
   /**
    * Removes aURL from the list of scripts which support delayed load.
    */
   void removeDelayedFrameScript(in AString aURL);
+
+  /**
+   * Returns a list of all delayed scripts that will be loaded once
+   * a (remote) frame becomes available.
+   */
+  nsIDOMDOMStringList getDelayedFrameScripts();
 };
 
 [scriptable, builtinclass, uuid(b37821ff-df79-44d4-821c-6d6ec4dfe1e9)]
 interface nsIProcessChecker : nsISupports
 {
 
   /**
    * Return true iff the "remote" process has |aPermission|.  This is
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -28,16 +28,17 @@
 #include "nsIJSRuntimeService.h"
 #include "nsIDOMClassInfo.h"
 #include "nsIDOMFile.h"
 #include "xpcpublic.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/StructuredCloneUtils.h"
 #include "JavaScriptChild.h"
 #include "JavaScriptParent.h"
+#include "nsDOMLists.h"
 #include <algorithm>
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 #ifdef XP_WIN
 #include <windows.h>
 #endif
@@ -311,16 +312,38 @@ nsFrameMessageManager::LoadFrameScript(c
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL)
 {
   mPendingScripts.RemoveElement(aURL);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameMessageManager::GetDelayedFrameScripts(nsIDOMDOMStringList** aList)
+{
+  // Frame message managers may return an incomplete list because scripts
+  // that were loaded after it was connected are not added to the list.
+  if (!IsGlobal() && !IsWindowLevel()) {
+    NS_WARNING("Cannot retrieve list of pending frame scripts for frame"
+               "message managers as it may be incomplete");
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  nsRefPtr<nsDOMStringList> scripts = new nsDOMStringList();
+
+  for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+    scripts->Add(mPendingScripts[i]);
+  }
+
+  scripts.forget(aList);
+
+  return NS_OK;
+}
+
 static JSBool
 JSONCreator(const jschar* aBuf, uint32_t aLen, void* aData)
 {
   nsAString* result = static_cast<nsAString*>(aData);
   result->Append(static_cast<const PRUnichar*>(aBuf),
                  static_cast<uint32_t>(aLen));
   return true;
 }
--- a/content/base/test/test_ipc_messagemanager_blob.html
+++ b/content/base/test/test_ipc_messagemanager_blob.html
@@ -91,15 +91,15 @@
     addEventListener("load", function() {
       info("Got load event.");
 
       SpecialPowers.addPermission("browser", true, document);
       SpecialPowers.pushPrefEnv({
         "set": [
           ["dom.ipc.browser_frames.oop_by_default", true],
           ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", false]
+          ["browser.pagethumbnails.capturing_disabled", true]
         ]
       }, runTests);
     });
   </script>
 </body>
 </html>
--- a/content/media/webspeech/synth/ipc/test/test_ipc.html
+++ b/content/media/webspeech/synth/ipc/test/test_ipc.html
@@ -176,16 +176,16 @@
       SpecialPowers.addPermission("browser", true, document);
       SpecialPowers.pushPrefEnv({
         "set": [
           // TODO: remove this as part of bug 820712
           ["network.disable.ipc.security", true],
 
           ["dom.ipc.browser_frames.oop_by_default", true],
           ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", false]
+          ["browser.pagethumbnails.capturing_disabled", true]
         ]
       }, runTests);
     });
 
   </script>
 </body>
 </html>
--- a/dom/browser-element/mochitest/browserElementTestHelpers.js
+++ b/dom/browser-element/mochitest/browserElementTestHelpers.js
@@ -201,17 +201,17 @@ function expectMozbrowserEvent(iframe, e
     iframe.removeEventListener('mozbrowser' + eventName, handler);
     deferred.resolve(e);
   });
   return deferred.promise;
 }
 
 // Set some prefs:
 //
-//  * browser.pagethumbnails.capturing_disabled: false
+//  * browser.pagethumbnails.capturing_disabled: true
 //
 //    Disable tab view; it seriously messes us up.
 //
 //  * dom.ipc.browser_frames.oop_by_default
 //
 //    Enable or disable OOP-by-default depending on the test's filename.  You
 //    can still force OOP on or off with <iframe mozbrowser remote=true/false>,
 //    at least until bug 756376 lands.
@@ -239,17 +239,17 @@ function expectMozbrowserEvent(iframe, e
 //    Disable mixed active content blocking, so that tests can confirm that mixed
 //    content results in a broken security state.
 
 (function() {
   var oop = location.pathname.indexOf('_inproc_') == -1;
 
   browserElementTestHelpers.lockTestReady();
   SpecialPowers.setBoolPref("network.disable.ipc.security", true);
-  SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", false],
+  SpecialPowers.pushPrefEnv({set: [["browser.pagethumbnails.capturing_disabled", true],
                                    ["dom.ipc.browser_frames.oop_by_default", oop],
                                    ["dom.ipc.tabs.disabled", false],
                                    ["security.mixed_content.block_active_content", false]]},
                             browserElementTestHelpers.unlockTestReady.bind(browserElementTestHelpers));
 })();
 
 addEventListener('unload', function() {
   browserElementTestHelpers.cleanUp();
--- a/dom/devicestorage/ipc/test_ipc.html
+++ b/dom/devicestorage/ipc/test_ipc.html
@@ -153,16 +153,16 @@
           ["device.storage.testing", true],
           ["device.storage.prompt.testing", true],
 
           // TODO: remove this as part of bug 820712
           ["network.disable.ipc.security", true],
 
           ["dom.ipc.browser_frames.oop_by_default", true],
           ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", false]
+          ["browser.pagethumbnails.capturing_disabled", true]
         ]
       }, runTests);
     });
 
   </script>
 </body>
 </html>
--- a/layout/tools/reftest/reftest-cmdline.js
+++ b/layout/tools/reftest/reftest-cmdline.js
@@ -101,17 +101,17 @@ RefTestCmdLineHandler.prototype =
     // Make url-classifier updates so rare that they won't affect tests
     branch.setIntPref("urlclassifier.updateinterval", 172800);
     // Disable high-quality downscaling, since it makes reftests more difficult.
     branch.setBoolPref("image.high_quality_downscaling.enabled", false);
     // Checking whether two files are the same is slow on Windows.
     // Setting this pref makes tests run much faster there.
     branch.setBoolPref("security.fileuri.strict_origin_policy", false);
     // Disable the thumbnailing service
-    branch.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+    branch.setBoolPref("browser.pagethumbnails.capturing_disabled", true);
 
     var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                            .getService(nsIWindowWatcher);
     wwatch.openWindow(null, "chrome://reftest/content/reftest.xul", "_blank",
                       "chrome,dialog=no,all", args);
     cmdLine.preventDefault = true;
   },
 
--- a/mach
+++ b/mach
@@ -28,18 +28,17 @@ for dir_path in ancestors(os.getcwd()):
         import json
         info = json.load(open(mozinfo_path))
         if "mozconfig" in info and "MOZCONFIG" not in os.environ:
             # If the MOZCONFIG environment variable is not already set, set it
             # to the value from mozinfo.json.  This will tell the build system
             # to look for a config file at the path in $MOZCONFIG rather than
             # its default locations.
             #
-            # Note: subprocess requires native strings in os.environ Python
-            # 2.7.2 and earlier on Windows.
+            # Note: subprocess requires native strings in os.environ on Windows
             os.environ[b"MOZCONFIG"] = str(info["mozconfig"])
 
         if "topsrcdir" in info:
             # Continue searching for mach_bootstrap in the source directory.
             dir_path = info["topsrcdir"]
 
     # If we find the mach bootstrap module, we are in the srcdir.
     mach_path = os.path.join(dir_path, "build/mach_bootstrap.py")
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -473,20 +473,18 @@ pref("app.faqURL", "http://www.mozilla.c
 #endif
 pref("app.marketplaceURL", "https://marketplace.firefox.com/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 pref("security.warn_viewing_mixed", false); // Warning is disabled.  See Bug 616712.
 
-#ifdef NIGHTLY_BUILD
 // Block insecure active content on https pages
 pref("security.mixed_content.block_active_content", true);
-#endif
 
 // Override some named colors to avoid inverse OS themes
 pref("ui.-moz-dialog", "#efebe7");
 pref("ui.-moz-dialogtext", "#101010");
 pref("ui.-moz-field", "#fff");
 pref("ui.-moz-fieldtext", "#1a1a1a");
 pref("ui.-moz-buttonhoverface", "#f3f0ed");
 pref("ui.-moz-buttonhovertext", "#101010");
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -131,9 +131,18 @@ public class AppConstants {
 #endif
 
     public static final boolean MOZ_ANDROID_BEAM =
 #ifdef MOZ_ANDROID_BEAM
     true;
 #else
     false;
 #endif
+
+    // See this wiki page for more details about channel specific build defines:
+    // https://wiki.mozilla.org/Platform/Channel-specific_build_defines
+    public static final boolean RELEASE_BUILD =
+#ifdef RELEASE_BUILD
+    true;
+#else
+    false;
+#endif
 }
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1983,18 +1983,17 @@ abstract public class BrowserApp extends
      * Google Play Store.  If updating is enabled, Aurora, Nightly, and custom
      * builds open about:, which provides an update interface.
      *
      * If updating is not enabled, this simply logs an error.
      *
      * @return true if update UI was launched.
      */
     protected boolean handleUpdaterLaunch() {
-        if ("release".equals(AppConstants.MOZ_UPDATE_CHANNEL) ||
-            "beta".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
+        if (AppConstants.RELEASE_BUILD) {
             Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setData(Uri.parse("market://details?id=" + getPackageName()));
             startActivity(intent);
             return true;
         }
 
         if (AppConstants.MOZ_UPDATER) {
             Tabs.getInstance().loadUrlInTab("about:");
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -45,16 +45,17 @@ import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.Window;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.TranslateAnimation;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -84,30 +85,31 @@ public class BrowserToolbar extends Geck
     private boolean mSwitchingTabs;
     private ShapedButton mTabs;
     private ImageButton mBack;
     private ImageButton mForward;
     public ImageButton mFavicon;
     public ImageButton mStop;
     public ImageButton mSiteSecurity;
     public PageActionLayout mPageActionLayout;
-    private AnimationDrawable mProgressSpinner;
+    private Animation mProgressSpinner;
     private TabCounter mTabsCounter;
     private ImageView mShadow;
     private GeckoImageButton mMenu;
     private GeckoImageView mMenuIcon;
     private LinearLayout mActionItemBar;
     private MenuPopup mMenuPopup;
     private List<? extends View> mFocusOrder;
 
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
     private boolean mShowSiteSecurity;
     private boolean mShowReader;
+    private boolean mSpinnerVisible;
 
     private boolean mAnimatingEntry;
 
     private AlphaAnimation mLockFadeIn;
     private TranslateAnimation mTitleSlideLeft;
     private TranslateAnimation mTitleSlideRight;
 
     private int mAddressBarViewOffset;
@@ -216,17 +218,17 @@ public class BrowserToolbar extends Geck
         if (Build.VERSION.SDK_INT >= 16)
             mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
 
         mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
         mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
         mActivity.getSiteIdentityPopup().setAnchor(mSiteSecurity);
 
-        mProgressSpinner = (AnimationDrawable) res.getDrawable(R.drawable.progress_spinner);
+        mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
 
         mStop = (ImageButton) findViewById(R.id.stop);
         mShadow = (ImageView) findViewById(R.id.shadow);
         mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
 
         if (Build.VERSION.SDK_INT >= 16) {
             mShadow.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
         }
@@ -782,26 +784,36 @@ public class BrowserToolbar extends Geck
                                     mActivity.getString(R.string.one_tab));
     }
 
     public void setProgressVisibility(boolean visible) {
         // The "Throbber start" and "Throbber stop" log messages in this method
         // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
         // See discussion in Bug 804457. Bug 805124 tracks paring these down.
         if (visible) {
-            mFavicon.setImageDrawable(mProgressSpinner);
-            mProgressSpinner.start();
-            setPageActionVisibility(true);
+            mFavicon.setImageResource(R.drawable.progress_spinner);
+            //To stop the glitch caused by mutiple start() calls.
+            if (!mSpinnerVisible) {
+                setPageActionVisibility(true);
+                mFavicon.setAnimation(mProgressSpinner);
+                mProgressSpinner.start();
+                mSpinnerVisible = true;
+            }
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
         } else {
-            mProgressSpinner.stop();
-            setPageActionVisibility(false);
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
             if (selectedTab != null)
                 setFavicon(selectedTab.getFavicon());
+
+            if (mSpinnerVisible) {
+                setPageActionVisibility(false);
+                mFavicon.setAnimation(null);
+                mProgressSpinner.cancel();
+                mSpinnerVisible = false;
+            }
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
         }
     }
 
     public void setPageActionVisibility(boolean isLoading) {
         // Handle the loading mode page actions
         mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE);
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -716,16 +716,31 @@ abstract public class GeckoApp
                 } else if (!message.isNull("phone")) {
                     Uri contactUri = Uri.parse(message.getString("phone"));       
                     Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
                     startActivity(i);
                 } else {
                     // something went wrong.
                     Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
                 }                
+            } else if (event.equals("Intent:GetHandlers")) {
+                Intent intent = GeckoAppShell.getOpenURIIntent(sAppContext, message.optString("url"),
+                    message.optString("mime"), message.optString("action"), message.optString("title"));
+                String[] handlers = GeckoAppShell.getHandlersForIntent(intent);
+                ArrayList<String> appList = new ArrayList<String>(handlers.length);
+                for (int i = 0; i < handlers.length; i++) {
+                    appList.add(handlers[i]);
+                }
+                JSONObject handlersJSON = new JSONObject();
+                handlersJSON.put("apps", new JSONArray(appList));
+                mCurrentResponse = handlersJSON.toString();
+            } else if (event.equals("Intent:Open")) {
+                GeckoAppShell.openUriExternal(message.optString("url"),
+                    message.optString("mime"), message.optString("packageName"),
+                    message.optString("className"), message.optString("action"), message.optString("title"));
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     public String getResponse(JSONObject origMessage) {
         String res = mCurrentResponse;
@@ -1502,16 +1517,18 @@ abstract public class GeckoApp
         registerEventListener("Share:Image");
         registerEventListener("Image:SetAs");
         registerEventListener("Sanitize:ClearHistory");
         registerEventListener("Update:Check");
         registerEventListener("Update:Download");
         registerEventListener("Update:Install");
         registerEventListener("PrivateBrowsing:Data");
         registerEventListener("Contact:Add");
+        registerEventListener("Intent:Open");
+        registerEventListener("Intent:GetHandlers");
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
 
         mPromptService = new PromptService(this);
@@ -2056,16 +2073,18 @@ abstract public class GeckoApp
         unregisterEventListener("Share:Image");
         unregisterEventListener("Image:SetAs");
         unregisterEventListener("Sanitize:ClearHistory");
         unregisterEventListener("Update:Check");
         unregisterEventListener("Update:Download");
         unregisterEventListener("Update:Install");
         unregisterEventListener("PrivateBrowsing:Data");
         unregisterEventListener("Contact:Add");
+        unregisterEventListener("Intent:Open");
+        unregisterEventListener("Intent:GetHandlers");
 
         deleteTempFiles();
 
         if (mLayerView != null)
             mLayerView.destroy();
         if (mDoorHangerPopup != null)
             mDoorHangerPopup.destroy();
         if (mFormAssistPopup != null)
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -592,16 +592,17 @@ RES_XML_V11 = \
 RES_ANIM = \
   res/anim/awesomebar_fade_in.xml \
   res/anim/popup_show.xml \
   res/anim/popup_hide.xml \
   res/anim/awesomebar_fade_out.xml \
   res/anim/awesomebar_hold_still.xml \
   res/anim/grow_fade_in.xml \
   res/anim/grow_fade_in_center.xml \
+  res/anim/progress_spinner.xml \
   res/anim/shrink_fade_out.xml \
   $(NULL)
 
 RES_DRAWABLE_MDPI = \
   $(SYNC_RES_DRAWABLE_MDPI) \
   res/drawable-mdpi/blank.png \
   res/drawable-mdpi/favicon.png \
   res/drawable-mdpi/folder.png \
@@ -642,28 +643,17 @@ RES_DRAWABLE_MDPI = \
   res/drawable-mdpi/ic_menu_character_encoding.png \
   res/drawable-mdpi/ic_menu_close_all_tabs.png \
   res/drawable-mdpi/ic_menu_forward.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/icon_pageaction.png \
-  res/drawable-mdpi/progress_spinner_1.png \
-  res/drawable-mdpi/progress_spinner_2.png \
-  res/drawable-mdpi/progress_spinner_3.png \
-  res/drawable-mdpi/progress_spinner_4.png \
-  res/drawable-mdpi/progress_spinner_5.png \
-  res/drawable-mdpi/progress_spinner_6.png \
-  res/drawable-mdpi/progress_spinner_7.png \
-  res/drawable-mdpi/progress_spinner_8.png \
-  res/drawable-mdpi/progress_spinner_9.png \
-  res/drawable-mdpi/progress_spinner_10.png \
-  res/drawable-mdpi/progress_spinner_11.png \
-  res/drawable-mdpi/progress_spinner_12.png \
+  res/drawable-mdpi/progress_spinner.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 \
@@ -1113,17 +1103,16 @@ RES_DRAWABLE += \
   res/drawable/handle_end_level.xml                   \
   res/drawable/handle_start_level.xml                 \
   res/drawable/ic_menu_back.xml                       \
   res/drawable/ic_menu_desktop_mode_off.xml           \
   res/drawable/ic_menu_desktop_mode_on.xml            \
   res/drawable/ic_menu_quit.xml                       \
   res/drawable/menu_item_state.xml                    \
   res/drawable/menu_level.xml                         \
-  res/drawable/progress_spinner.xml                   \
   res/drawable/remote_tabs_child_divider.xml          \
   res/drawable/shaped_button.xml                      \
   res/drawable/site_security_level.xml                \
   res/drawable/spinner.xml                            \
   res/drawable/suggestion_selector.xml                \
   res/drawable/tab_new_level.xml                      \
   res/drawable/tab_row.xml                            \
   res/drawable/tab_thumbnail.xml                      \
--- a/mobile/android/base/UpdateService.java
+++ b/mobile/android/base/UpdateService.java
@@ -133,18 +133,17 @@ public class UpdateService extends Inten
         resultIntent.putExtra("result", result.toString());
         sendBroadcast(resultIntent);
     }
 
     private int getUpdateInterval(boolean isRetry) {
         int interval;
         if (isRetry) {
             interval = INTERVAL_RETRY;
-        } else if (AppConstants.MOZ_UPDATE_CHANNEL.equals("nightly") ||
-                   AppConstants.MOZ_UPDATE_CHANNEL.equals("aurora")) {
+        } else if (!AppConstants.RELEASE_BUILD) {
             interval = INTERVAL_SHORT;
         } else {
             interval = INTERVAL_LONG;
         }
 
         return interval;
     }
 
--- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
+++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
@@ -15,16 +15,17 @@ import org.json.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.Cursor;
 import android.database.SQLException;
+import android.database.sqlite.SQLiteConstraintException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Build;
 import android.util.SparseArray;
 
 /**
  * <code>HealthReportDatabaseStorage</code> provides an interface on top of
  * SQLite storage for Health Report data. It exposes methods for management of
@@ -181,17 +182,17 @@ public class HealthReportDatabaseStorage
     this.fields.clear();
     this.envs.clear();
     this.measurementVersions.clear();
   }
 
   protected final HealthReportSQLiteOpenHelper helper;
 
   public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
-    public static final int CURRENT_VERSION = 4;
+    public static final int CURRENT_VERSION = 5;
     public static final String LOG_TAG = "HealthReportSQL";
 
     /**
      * A little helper to avoid SQLiteOpenHelper misbehaving on Android 2.1.
      * Partly cribbed from
      * <http://stackoverflow.com/questions/5332328/sqliteopenhelper-problem-with-fully-qualified-db-path-name>.
      */
     public static class AbsolutePathContext extends ContextWrapper {
@@ -222,21 +223,26 @@ public class HealthReportDatabaseStorage
     }
 
     public static String getAbsolutePath(File parent, String name) {
       return parent.getAbsolutePath() + File.separator + name;
     }
 
     public static boolean CAN_USE_ABSOLUTE_DB_PATH = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);
     public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name) {
+      this(context, profileDirectory, name, CURRENT_VERSION);
+    }
+
+    // For testing DBs of different versions.
+    public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name, int version) {
       super(
           (CAN_USE_ABSOLUTE_DB_PATH ? context : new AbsolutePathContext(context, profileDirectory)),
           (CAN_USE_ABSOLUTE_DB_PATH ? getAbsolutePath(profileDirectory, name) : name),
           null,
-          CURRENT_VERSION);
+          version);
 
       if (CAN_USE_ABSOLUTE_DB_PATH) {
         Logger.pii(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
       }
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
@@ -342,16 +348,23 @@ public class HealthReportDatabaseStorage
         createAddonsEnvironmentsView(db);
 
         db.setTransactionSuccessful();
       } finally {
         db.endTransaction();
       }
     }
 
+    @Override
+    public void onOpen(SQLiteDatabase db) {
+      if (!db.isReadOnly()) {
+        db.execSQL("PRAGMA foreign_keys=ON;");
+      }
+    }
+
     private void createAddonsEnvironmentsView(SQLiteDatabase db) {
       db.execSQL("CREATE VIEW environments_with_addons AS " +
           "SELECT e.id AS id, " +
           "       e.hash AS hash, " +
           "       e.profileCreation AS profileCreation, " +
           "       e.cpuCount AS cpuCount, " +
           "       e.memoryMB AS memoryMB, " +
           "       e.isBlocklistEnabled AS isBlocklistEnabled, " +
@@ -389,30 +402,48 @@ public class HealthReportDatabaseStorage
     }
 
     private void upgradeDatabaseFrom3To4(SQLiteDatabase db) {
       // Update search measurements to use a different type.
       db.execSQL("UPDATE OR IGNORE fields SET flags = " + Field.TYPE_COUNTED_STRING_DISCRETE +
                  " WHERE measurement IN (SELECT id FROM measurements WHERE name = 'org.mozilla.searches.counts')");
     }
 
+    private void upgradeDatabaseFrom4to5(SQLiteDatabase db) {
+      // Delete NULL in addons.body, which appeared as a result of Bug 886156. Note that the
+      // foreign key constraint, "ON DELETE RESTRICT", may be violated, but since onOpen() is
+      // called after this method, foreign keys are not yet enabled and constraints can be broken.
+      db.delete("addons", "body IS NULL", null);
+
+      // Purge any data inconsistent with foreign key references (which may have appeared before
+      // foreign keys were enabled in Bug 900289).
+      db.delete("fields", "measurement NOT IN (SELECT id FROM measurements)", null);
+      db.delete("environments", "addonsID NOT IN (SELECT id from addons)", null);
+      db.delete(EVENTS_INTEGER, "env NOT IN (SELECT id FROM environments)", null);
+      db.delete(EVENTS_TEXTUAL, "env NOT IN (SELECT id FROM environments)", null);
+      db.delete(EVENTS_INTEGER, "field NOT IN (SELECT id FROM fields)", null);
+      db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null);
+    }
+
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       if (oldVersion >= newVersion) {
         return;
       }
 
       Logger.info(LOG_TAG, "onUpgrade: from " + oldVersion + " to " + newVersion + ".");
       try {
         db.beginTransaction();
         switch (oldVersion) {
         case 2:
           upgradeDatabaseFrom2To3(db);
         case 3:
           upgradeDatabaseFrom3To4(db);
+        case 4:
+          upgradeDatabaseFrom4to5(db);
         }
         db.setTransactionSuccessful();
       } catch (Exception e) {
         Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
         throw new RuntimeException(e);
       } finally {
         db.endTransaction();
       }
@@ -1026,17 +1057,21 @@ public class HealthReportDatabaseStorage
     // UNIQUE(env, field, day) constraint for daily-last values, then we could
     // use INSERT OR REPLACE.
     final int updated = db.update(table, v, WHERE_DATE_AND_ENV_AND_FIELD,
                                   new String[] {dayString, envString, fieldIDString});
     if (0 == updated) {
       v.put("env", env);
       v.put("field", field);
       v.put("date", day);
-      db.insert(table, null, v);
+      try {
+        db.insertOrThrow(table, null, v);
+      } catch (SQLiteConstraintException e) {
+        throw new IllegalStateException("Event did not reference existing an environment or field.", e);
+      }
     }
   }
 
   @Override
   public void recordDailyLast(int env, int day, int field, JSONObject value) {
     this.recordDailyLast(env, day, field, value == null ? "null" : value.toString(), EVENTS_TEXTUAL);
   }
 
@@ -1058,17 +1093,21 @@ public class HealthReportDatabaseStorage
 
     final ContentValues v = new ContentValues();
     v.put("env", env);
     v.put("field", field);
     v.put("date", day);
 
     final SQLiteDatabase db = this.helper.getWritableDatabase();
     putValue(v, value);
-    db.insert(table, null, v);
+    try {
+      db.insertOrThrow(table, null, v);
+    } catch (SQLiteConstraintException e) {
+      throw new IllegalStateException("Event did not reference existing an environment or field.", e);
+    }
   }
 
   @Override
   public void recordDailyDiscrete(int env, int day, int field, JSONObject value) {
     this.recordDailyDiscrete(env, day, field, value == null ? "null" : value.toString(), EVENTS_TEXTUAL);
   }
 
   @Override
@@ -1128,17 +1167,21 @@ public class HealthReportDatabaseStorage
                  WHERE_DATE_AND_ENV_AND_FIELD,
                  args);
     } else {
       final ContentValues v = new ContentValues();
       v.put("env", env);
       v.put("value", by);
       v.put("field", field);
       v.put("date", day);
-      db.insert(EVENTS_INTEGER, null, v);
+      try {
+        db.insertOrThrow(EVENTS_INTEGER, null, v);
+      } catch (SQLiteConstraintException e) {
+        throw new IllegalStateException("Event did not reference existing an environment or field.", e);
+      }
     }
   }
 
   @Override
   public void incrementDailyCount(int env, int day, int field) {
     this.incrementDailyCount(env, day, field, 1);
   }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/anim/progress_spinner.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:interpolator="@android:anim/linear_interpolator">
+
+    <rotate android:duration="1200"
+            android:fromDegrees="0"
+            android:pivotX="50%"
+            android:pivotY="50%"
+            android:repeatCount="infinite"
+            android:toDegrees="360"/>
+
+</set>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..91bdec6342d1aeb4ca2b65bd90a6f3d8367685ee
GIT binary patch
literal 1998
zc$@*w2Qm1GP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU(a7jc#RCwBqS6ggcR~237+{es##_<Cu
zek5t^hPX*uD2P%}Nq`j5rWNo3^ot6KKYplGsZv4WBY%7#9zP&NeyZ}tA4*k3d9+Z0
zLJ0wt%1x7!l+@1Cv14P;+?o40hjq@mbLU}?#aPGpdVJ5`Yp=cbKKlxM^9<}qV~39b
z_Us-(oIVAG6VUP~GVF&^!>Gt#<JiNeQS$!F2hF;}bsN8YDaGy`jpmly0Ja4|#}ng6
z5$o>&rIYZg6@)bbuOuKt0j(q&cO>NcuW;hyFLrIrxUO;i8?U`ohaL>X@Ap@L7$2`d
ziSHq;d=1s{0O5#)U(t|0pYjCEkK%K(0<5<l=Uq1dk9H0~=~*0m;tB$P0k57>+X0YU
zIidaC6NjPv*HM}JI>rwA*tHL+i~?baraiMuTd-z#Ku|$-t7Y?q5M;-jN0NRHpE^10
zBHRgpaXvBeNvP5p?3$gW;|xQ)6nsxxr^uJcujY3W5t#bZw!uk0NwNrn=*0;<^7*j>
zL~c#?IN!792z2>HIv>F>6%>XjoV6WlCNz$L79BU%`2XeK;Guh8=u2ehH`QW6_<jXK
zeiom2w1)}b-CO5F$b!=t-d{s)T-lAzvAbqUq1DKsqQ3&om-*En5vWBxG~49=%UmN|
z(}>kxgfg@HMFGQrFeu8%vQOjTM_<9;UQLZS{UZ8IoI-W-0IFk(5;BCVf~vShs@L9t
zXg-g-cNdWI?~E*{6u5ec_esKv*nses@RcVnaOa2UbS7s&6ii6Q5(J}YH6NtU^9F!)
z)KAVM82u`$6U?_Xj+NE8A-&a5NN)WEt2e%g8;c7pI}&i0DcB&ex&xwBr;g9PwuyKC
zah8lN<Z5Zl)q(MSvf&@WBaaO`YA94_?-|rSAeeKVKk7ilO@(y%`&hko3ehUTu+OAT
z6EXK>aP{3)<GZ=7No}lC8yC*MgEV~;%9f<aRpHX&2P`FVnBwFYGt(pRhd+Y~)h?}L
zlRxG<%aE-61uM6Ih-jUTBj?mO6a%`70i^g$v%#LoJs19Y5ovNAmT6lR?#J^C^9OL>
zhf5Nvev+jYk{Vr5Z?3Hkh8ca^MxC`fG!Js#-FD5hF`3%r-X#7zg$K)o3o(ZwL#~5F
z;vZupY5&VDO~t1hujBU3_X_8t%eg@B-@OgUEt){9Wmwomk}TQK+Ld8{m}@EL*j~$X
z@Ix25Tcn=3*UjJY>J0Te?`NVNz3u~I_C~akCX2lHaxuzW3r36+KD=;}rIGa*R5AZb
z&)G5)x?@=Mof$GecDf_)VS@m&)aYWFY48xpPP1Z<q!z{qz%TJOEt-Y{K;7z_PILpP
zZhtpogtx<aYx1wrAPCb0x&fF9EVC2Jw)#76B*RuJC_)kgoSUm5%iBQ{#zqn|`m&ds
z)17lj2m;Xcyd{gyVIvHEQ~YLai_`9VIbe?H2clGs5BYtGVz)K4=<H_Rc3u4&A^Ow=
z#1X4hF~GSPp#J`48=aoyRl;%QmMP0jt&#lXGR39}=N15~Li)#T??zwgVH>*jHeiZ<
zmg)Ow%Y!@A-PI&MOIIj_>?{wL_8IShl*$TW^<$Wx-e=Px2GA>ZaIEtO=8ulSFC8MQ
z61qqly`)^(6#~$|Wt)w<`T!Xa97IOUB=PJP7Ix}}+eHuYBm)Za<J~b}=uZ<<X%{4J
z-Pk}{|1%Gc4giva%5cV6@FSR-e!9@YPR{$IH+w#K_&Cb7d4#M0QRJW2Im{;UWn8(`
z<c&Gm(ihO&jEfYQYryg}$8%-uX-v(0g9Ziu-;Z%VH~$#Q)sv{yc;pG0=U>=siz!dp
z=;DOsrwHKY_4hCnyo{v!1%yF+{!=KIGRP@2(>=vh9D-0k!}V+L4}OUt#<_!2eEv(Q
zjUGg8gn<N|an5|w%*+_)@$TEJOalSflPfY+_)|n{55k|Av5m-8g=YRVMkf_YrTbA|
z`vLae^Cq#KBcQiveAV_4m2ud_9J}fRoRSZrwo7BEmf2uLSI02iMvZ0C{|7raC`0%&
zXB=haA>f|5S%jlMLUpu~j}gj(FnvO^FSjBNDRmxJ5m%g<M#3t|*wa%2B*;oiI|eM7
z6HZIWk9T7ws@Kt~pT@<7WjiwvfM2M_oZm}}|G_@_T^c>bDySA9Y^$chAsRP8b&{Ww
z`74j4K1{XvzLA<JCPCymx6T_4I{yVOUtH!)i1yo(?44xTm9M?OfO!2m)K`{lW$3>5
z0Us`}<q{W%;c{7^#PL1!VV$FE{4&tvOmHw+L388hRQ)1ZaK3r~6d-tXHaE${)vHK0
zzlHjpR}gJxNR{sE<Nf!&cLr*z1R|!azIGPT#*cCN(rq+oJ5AC3uIMz-(`f9Q4Btgf
zbR1mw&g_Q_zD#j_)ShU8?^0q!D)an0d2x=Uq7_x1agI+~|6)MD$Kq8w-f$^a*_OWD
z0nlINHm5w1L%w{Lh76D@T^;A_{{YX5{S@9Pfrbu%YOt5AFgliK_BL(3f$K|=6`Fa2
gN|S*z<^Kd20Q9d}Qt55Z&Hw-a07*qoM6N<$f*oDm-T(jq
deleted file mode 100644
index 6b543a96b659294219ebdc552a41f4ff27129028..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 91bdec6342d1aeb4ca2b65bd90a6f3d8367685ee..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 7f3d12ddeb9224f2bb2f672c725d42e9d4ba59b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c7b271592af6f20109018d00108ebe92c2578f5c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 709c9fa2f21cabe78b993ef51ea888315d48f5dd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d5ded1e253f1e5bd8754d4138e9c35a690b7e78e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f3e1dd9b9b8fdafbe828a904b232209ec674fc82..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f2283efbf97760b5245004800e8b71ead26090de..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 413fe939e5f5ce7f7907820bfb586865defb8713..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5386d592a9de25503b04b90f5e90ae512c2264a9..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index db7d794f8ba500db245024d50ddfaabb1513469d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 617a77c05bc046c33c90af45a2181013d007b3de..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/progress_spinner.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
-                android:oneshot="false">
-
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_1"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_2"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_3"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_4"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_5"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_6"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_7"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_8"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_9"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_10"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_11"/>
-    <item android:duration="100" android:drawable="@drawable/progress_spinner_12"/>
-
-</animation-list>
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -70,18 +70,19 @@
                   android:layout_toRightOf="@id/back"
                   android:layout_toLeftOf="@id/menu_items">
 
         <ImageButton android:id="@+id/favicon"
                      style="@style/AddressBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_favicon_size"
                      android:layout_height="fill_parent"
                      android:scaleType="fitCenter"
-                     android:paddingLeft="8dip"
-                     android:layout_marginRight="4dip"
+                     android:layout_marginLeft="4dip"
+                     android:paddingLeft="4dip"
+                     android:paddingRight="4dip"
                      android:layout_gravity="center_vertical"
                      android:src="@drawable/favicon"/>
 
         <ImageButton android:id="@+id/site_security"
                      style="@style/AddressBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -96,18 +96,19 @@
                   android:layout_marginRight="-24dp"
                   android:orientation="horizontal">
 
         <ImageButton android:id="@+id/favicon"
                      style="@style/AddressBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_favicon_size"
                      android:layout_height="fill_parent"
                      android:scaleType="fitCenter"
-                     android:paddingLeft="12dip"
-                     android:layout_marginRight="4dip"
+                     android:layout_marginLeft="8dip"
+                     android:paddingLeft="4dip"
+                     android:paddingRight="4dip"
                      android:layout_gravity="center_vertical"
                      android:src="@drawable/favicon"/>
 
         <ImageButton android:id="@+id/site_security"
                      style="@style/AddressBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -25,17 +25,17 @@
     <dimen name="awesomebar_row_height">48dp</dimen>
     <dimen name="awesomebar_row_favicon_size_small">16dp</dimen>
     <dimen name="awesomebar_row_favicon_size_large">32dp</dimen>
     <dimen name="awesomebar_tab_transparency_height">38dp</dimen>
     <dimen name="browser_toolbar_height">48dp</dimen>
     <dimen name="browser_toolbar_button_padding">12dp</dimen>
     <dimen name="browser_toolbar_icon_width">48dp</dimen>
     <dimen name="browser_toolbar_lock_width">20dp</dimen>
-    <dimen name="browser_toolbar_favicon_size">29.33dip</dimen>
+    <dimen name="browser_toolbar_favicon_size">25.33dip</dimen>
     <dimen name="favicon_bg">32dp</dimen>
     <dimen name="favicon_bg_radius">1dp</dimen>
 
     <!-- Max width of arrow popups on tablets -->
     <dimen name="arrow_popup_width">400dp</dimen>
 
     <!-- Padding at the top of the site identity popup, when no identity data is available. -->
     <dimen name="identity_padding_top">5dp</dimen>
--- a/mobile/android/chrome/content/HelperApps.js
+++ b/mobile/android/chrome/content/HelperApps.js
@@ -1,13 +1,26 @@
 /* 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";
 
+XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
+  let ContentAreaUtils = {};
+  Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
+  return ContentAreaUtils;
+});
+
+function getBridge() {
+  return Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge);
+}
+
+function sendMessageToJava(aMessage) {
+  return getBridge().handleGeckoMessage(JSON.stringify(aMessage));
+}
 
 var HelperApps =  {
   get defaultHttpHandlers() {
     let protoHandlers = this.getAppsForProtocol("http");
 
     var results = {};
     for (var i = 0; i < protoHandlers.length; i++) {
       try {
@@ -29,38 +42,64 @@ var HelperApps =  {
     delete this.urlHandlerService;
     return this.urlHandlerService = Cc["@mozilla.org/uriloader/external-url-handler-service;1"].getService(Ci.nsIExternalURLHandlerService);
   },
 
   getAppsForProtocol: function getAppsForProtocol(uri) {
     let handlerInfoProto = this.protoSvc.getProtocolHandlerInfoFromOS(uri, {});
     return handlerInfoProto.possibleApplicationHandlers;
   },
-  
+
   getAppsForUri: function getAppsFor(uri) {
     let found = [];
-    let handlerInfoProto = this.urlHandlerService.getURLHandlerInfoFromOS(uri, {});
-    let urlHandlers = handlerInfoProto.possibleApplicationHandlers;
-    for (var i = 0; i < urlHandlers.length; i++) {
-      let urlApp = urlHandlers.queryElementAt(i, Ci.nsIHandlerApp);
-      if (!this.defaultHttpHandlers[urlApp.name]) {
-        found.push(urlApp);
-      }
+    let mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || "";
+    // empty action string defaults to android.intent.action.VIEW
+    let msg = {
+      type: "Intent:GetHandlers",
+      mime: mimeType,
+      action: "",
+      url: uri.spec,
+      packageName: "",
+      className: ""
+    };
+    let apps = this._parseApps(JSON.parse(sendMessageToJava(msg)));
+    for (let i = 0; i < apps.length; i++) {
+      let appName = apps[i].name;
+      if (appName.length > 0 && !this.defaultHttpHandlers[appName])
+        found.push(apps[i]);
     }
     return found;
   },
-  
+
   openUriInApp: function openUriInApp(uri) {
-    var possibleHandlers = this.getAppsForUri(uri);
-    if (possibleHandlers.length == 1) {
-      possibleHandlers[0].launchWithURI(uri);
-    } else if (possibleHandlers.length > 0) {
-      let handlerInfoProto = this.urlHandlerService.getURLHandlerInfoFromOS(uri, {});
-      handlerInfoProto.preferredApplicationHandler.launchWithURI(uri);
+    let mimeType = ContentAreaUtils.getMIMETypeForURI(uri) || "";
+    let msg = {
+      type: "Intent:Open",
+      mime: mimeType,
+      action: "",
+      url: uri.spec,
+      packageName: "",
+      className: ""
+    };
+    sendMessageToJava(msg);
+  },
+
+  _parseApps: function _parseApps(aJSON) {
+    // aJSON -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]}
+    // see GeckoAppShell.java getHandlersForIntent function for details
+    let appInfo = aJSON.apps;
+    const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name.
+    let apps = [];
+    for (let i = 0; i < appInfo.length; i += numAttr) {
+      apps.push({"name" : appInfo[i],
+                 "isDefault" : appInfo[i+1],
+                 "packageName" : appInfo[i+2],
+                 "activityName" : appInfo[i+3]});
     }
+    return apps;
   },
 
   showDoorhanger: function showDoorhanger(aUri, aCallback) {
     let permValue = Services.perms.testPermission(aUri, "native-intent");
     if (permValue != Services.perms.UNKNOWN_ACTION) {
       if (permValue == Services.perms.ALLOW_ACTION) {
         if (aCallback)
           aCallback(aUri);
@@ -75,17 +114,17 @@ var HelperApps =  {
     let apps = this.getAppsForUri(aUri);
     let strings = Strings.browser;
 
     let message = "";
     if (apps.length == 1)
       message = strings.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1);
     else
       message = strings.GetStringFromName("helperapps.openWithList2");
-  
+
     let buttons = [{
       label: strings.GetStringFromName("helperapps.open"),
       callback: function(aChecked) {
         if (aChecked)
           Services.perms.add(aUri, "native-intent", Ci.nsIPermissionManager.ALLOW_ACTION);
         if (aCallback)
           aCallback(aUri);
         else
--- a/mobile/android/chrome/content/aboutAddons.js
+++ b/mobile/android/chrome/content/aboutAddons.js
@@ -36,17 +36,16 @@ var ContextMenus = {
     while (!this.target.hasAttribute("contextmenu")) {
       this.target = this.target.parentNode;
     }
 
     if (!this.target) {
       document.getElementById("contextmenu-enable").setAttribute("hidden", "true");
       document.getElementById("contextmenu-disable").setAttribute("hidden", "true");
       document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true");
-      document.getElementById("contextmenu-default").setAttribute("hidden", "true");
       return;
     }
 
     let addon = this.target.addon;
     if (addon.scope == AddonManager.SCOPE_APPLICATION) {
       document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true");
     } else {
       document.getElementById("contextmenu-uninstall").removeAttribute("hidden");
@@ -55,18 +54,16 @@ var ContextMenus = {
     let enabled = this.target.getAttribute("isDisabled") != "true";
     if (enabled) {
       document.getElementById("contextmenu-enable").setAttribute("hidden", "true");
       document.getElementById("contextmenu-disable").removeAttribute("hidden");
     } else {
       document.getElementById("contextmenu-enable").removeAttribute("hidden");
       document.getElementById("contextmenu-disable").setAttribute("hidden", "true");
     }
-
-    document.getElementById("contextmenu-default").setAttribute("hidden", "true");
   },
 
   enable: function(event) {
     Addons.setEnabled(true, this.target.addon);
     this.target = null;
   },
   
   disable: function (event) {
@@ -248,47 +245,16 @@ var Addons = {
       let list = document.getElementById("addons-list");
       list.innerHTML = "";
 
       for (let i=0; i<aAddons.length; i++) {
         let item = self._createItemForAddon(aAddons[i]);
         list.appendChild(item);
       }
 
-      // Load the search engines
-      let defaults = Services.search.getDefaultEngines({ }).map(function (e) e.name);
-      function isDefault(aEngine)
-        defaults.indexOf(aEngine.name) != -1
-
-      let defaultDescription = gStringBundle.GetStringFromName("addonsSearchEngine.description");
-
-      let engines = Services.search.getEngines({ });
-      for (let e = 0; e < engines.length; e++) {
-        let engine = engines[e];
-        let addon = {};
-        addon.id = engine.name;
-        addon.type = "search";
-        addon.name = engine.name;
-        addon.version = "";
-        addon.description = engine.description || defaultDescription;
-        addon.iconURL = engine.iconURI ? engine.iconURI.spec : "";
-        addon.optionsURL = "";
-        addon.appDisabled = false;
-        addon.scope = isDefault(engine) ? AddonManager.SCOPE_APPLICATION : AddonManager.SCOPE_PROFILE;
-        addon.engine = engine;
-
-        let item = self._createItem(addon);
-        item.setAttribute("isDisabled", engine.hidden);
-        item.setAttribute("updateable", "false");
-        item.setAttribute("opType", "");
-        item.setAttribute("optionsURL", "");
-        item.addon = addon;
-        list.appendChild(item);
-      }
-
       // Add a "Browse all Firefox Add-ons" item to the bottom of the list.
       let browseItem = self._createBrowseItem();
       list.appendChild(browseItem);
     });
   },
 
   _getOpTypeForOperations: function _getOpTypeForOperations(aOperations) {
     if (aOperations & AddonManager.PENDING_UNINSTALL)
@@ -343,19 +309,16 @@ var Addons = {
       enableBtn.removeAttribute("disabled");
 
     let uninstallBtn = document.getElementById("uninstall-btn");
     if (addon.scope == AddonManager.SCOPE_APPLICATION)
       uninstallBtn.setAttribute("disabled", "true");
     else
       uninstallBtn.removeAttribute("disabled");
 
-    let defaultButton = document.getElementById("default-btn");
-    defaultButton.setAttribute("hidden", "true");
-
     let box = document.querySelector("#addons-details > .addon-item .options-box");
     box.innerHTML = "";
 
     // Retrieve the extensions preferences
     try {
       let optionsURL = aListItem.getAttribute("optionsURL");
       let xhr = new XMLHttpRequest();
       xhr.open("GET", optionsURL, false);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -7626,22 +7626,36 @@ let Reader = {
       this.log("Database upgrade done: " + this.DB_VERSION);
     }.bind(this);
   }
 };
 
 var ExternalApps = {
   _contextMenuId: -1,
 
+  // extend _getLink to pickup html5 media links.
+  _getMediaLink: function(aElement) {
+    let uri = NativeWindow.contextmenus._getLink(aElement);
+    if (uri == null) {
+      if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && (aElement instanceof Ci.nsIDOMHTMLMediaElement && mediaSrc)) {
+        try {
+          let mediaSrc = aElement.currentSrc || aElement.src;
+          uri = ContentAreaUtils.makeURI(mediaSrc, null, null);
+        } catch (e) {}
+      }
+    }
+    return uri;
+  },
+
   init: function helper_init() {
     this._contextMenuId = NativeWindow.contextmenus.add(function(aElement) {
       let uri = null;
       var node = aElement;
       while (node && !uri) {
-        uri = NativeWindow.contextmenus._getLink(node);
+        uri = ExternalApps._getMediaLink(node);
         node = node.parentNode;
       }
       let apps = [];
       if (uri)
         apps = HelperApps.getAppsForUri(uri);
 
       return apps.length == 1 ? Strings.browser.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1) :
                                 Strings.browser.GetStringFromName("helperapps.openWithList2");
@@ -7649,27 +7663,27 @@ var ExternalApps = {
   },
 
   uninit: function helper_uninit() {
     NativeWindow.contextmenus.remove(this._contextMenuId);
   },
 
   filter: {
     matches: function(aElement) {
-      let uri = NativeWindow.contextmenus._getLink(aElement);
+      let uri = ExternalApps._getMediaLink(aElement);
       let apps = [];
       if (uri) {
         apps = HelperApps.getAppsForUri(uri);
       }
       return apps.length > 0;
     }
   },
 
   openExternal: function(aElement) {
-    let uri = NativeWindow.contextmenus._getLink(aElement);
+    let uri = ExternalApps._getMediaLink(aElement);
     HelperApps.openUriInApp(uri);
   }
 };
 
 var Distribution = {
   // File used to store campaign data
   _file: null,
 
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -54,17 +54,18 @@ class MachCommands(MachCommandBase):
     @Command('python', category='devenv',
         allow_all_args=True,
         description='Run Python.')
     @CommandArgument('args', nargs=argparse.REMAINDER)
     def python(self, args):
         return self.run_process([self.python_executable] + args,
             pass_thru=True, # Allow user to run Python interactively.
             ensure_exit_code=False, # Don't throw on non-zero exit code.
-            append_env={'PYTHONDONTWRITEBYTECODE': '1'})
+            # Note: subprocess requires native strings in os.environ on Windows
+            append_env={b'PYTHONDONTWRITEBYTECODE': str('1')})
 
     @Command('python-test', category='testing',
         description='Run Python unit tests.')
     @CommandArgument('--verbose',
         default=False,
         action='store_true',
         help='Verbose output.')
     @CommandArgument('--stop',
@@ -104,17 +105,18 @@ class MachCommands(MachCommandBase):
             def _line_handler(line):
                 if not file_displayed_test and line.startswith('TEST-'):
                     file_displayed_test.append(True)
 
             inner_return_code = self.run_process(
                 [self.python_executable, file],
                 ensure_exit_code=False, # Don't throw on non-zero exit code.
                 log_name='python-test',
-                append_env={'PYTHONDONTWRITEBYTECODE': '1'},
+                # subprocess requires native strings in os.environ on Windows
+                append_env={b'PYTHONDONTWRITEBYTECODE': str('1')},
                 line_handler=_line_handler)
             return_code += inner_return_code
 
             if not file_displayed_test:
                 self.log(logging.WARN, 'python-test', {'file': file},
                          'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}')
 
             if verbose:
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -144,17 +144,16 @@ class MochitestRunner(MozbuildObject):
             test_root = runner.getTestRoot(options)
             test_root_file = mozpack.path.join(mochitest_dir, test_root, test_path)
             if not os.path.exists(test_root_file):
                 print('Specified test path does not exist: %s' % test_root_file)
                 print('You may need to run |mach build| to build the test files.')
                 return 1
 
             options.testPath = test_path
-            env = {'TEST_PATH': test_path}
 
         if rerun_failures:
             options.testManifest = failure_file_path
 
         if debugger:
             options.debugger = debugger
 
         if debugger_args:
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -142,9 +142,9 @@ user_pref("media.webaudio.legacy.PannerN
 user_pref("media.webaudio.legacy.OscillatorNode", true);
 
 // Always use network provider for geolocation tests
 // so we bypass the OSX dialog raised by the corelocation provider
 user_pref("geo.provider.testing", true);
 
 // Background thumbnails in particular cause grief, and disabling thumbnails
 // in general can't hurt - we re-enable them when tests need them.
-user_pref("browser.pagethumbnails.capturing_disabled", false);
+user_pref("browser.pagethumbnails.capturing_disabled", true);
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -460,17 +460,17 @@ this.PageThumbs = {
       this._thumbnailWidth = Math.round(width.value / 3);
       this._thumbnailHeight = Math.round(height.value / 3);
     }
     return [this._thumbnailWidth, this._thumbnailHeight];
   },
 
   _prefEnabled: function PageThumbs_prefEnabled() {
     try {
-      return Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+      return !Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
     }
     catch (e) {
       return true;
     }
   },
 };
 
 this.PageThumbsStorage = {
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -6,17 +6,17 @@ Cu.import("resource://gre/modules/PageTh
 Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
 Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
 Cu.import("resource://gre/modules/osfile.jsm", tmp);
 let {PageThumbs, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 let oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
-Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", true);
+Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
 
 registerCleanupFunction(function () {
   while (gBrowser.tabs.length > 1)
     gBrowser.removeTab(gBrowser.tabs[1]);
   Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref)
 });
 
 /**
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -106,21 +106,22 @@ function getCtorName(aObj) {
   return Object.prototype.toString.call(aObj).slice(8, -1);
 }
 
 /**
  * A single line stringification of an object designed for use by humans
  *
  * @param {any} aThing
  *        The object to be stringified
+ * @param {boolean} aAllowNewLines
  * @return {string}
  *        A single line representation of aThing, which will generally be at
  *        most 80 chars long
  */
-function stringify(aThing) {
+function stringify(aThing, aAllowNewLines) {
   if (aThing === undefined) {
     return "undefined";
   }
 
   if (aThing === null) {
     return "null";
   }
 
@@ -140,17 +141,20 @@ function stringify(aThing) {
     }
     return type + json;
   }
 
   if (typeof aThing == "function") {
     return aThing.toString().replace(/\s+/g, " ");
   }
 
-  let str = aThing.toString().replace(/\n/g, "|");
+  let str = aThing.toString();
+  if (!aAllowNewLines) {
+    str = str.replace(/\n/g, "|");
+  }
   return str;
 }
 
 /**
  * Create a simple debug representation of a given element.
  *
  * @param {nsIDOMElement} aElement
  *        The element to debug
@@ -460,19 +464,19 @@ function createDumper(aLevel) {
   return function() {
     if (!shouldLog(aLevel, this.maxLogLevel)) {
       return;
     }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     sendConsoleAPIMessage(aLevel, frame, args);
     let data = args.map(function(arg) {
-      return stringify(arg);
+      return stringify(arg, true);
     });
-    dumpMessage(this, aLevel, data.join(", "));
+    dumpMessage(this, aLevel, data.join(" "));
   };
 }
 
 /**
  * Create a function which will output more detailed level of output when
  * used as a logging function
  *
  * @param {string} aLevel