Merge mozilla-central and fx-team
authorEd Morley <emorley@mozilla.com>
Tue, 01 Oct 2013 10:23:54 +0100
changeset 149354 a55240c523be90da13c0ec2134a5731f05afdbd4
parent 149342 0d9f5e4bd5b8789fe2f516972844d76c520c8077 (current diff)
parent 149353 132dbcea6676aa0bd73ee984d13ae83b217b607a (diff)
child 149410 6856c45f3688d817181cd0b9c99a0533dcb5965a
push id25385
push useremorley@mozilla.com
push dateTue, 01 Oct 2013 09:25:32 +0000
treeherdermozilla-central@a55240c523be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central and fx-team
browser/base/content/test/social/Makefile.in
browser/components/downloads/content/indicator.css
browser/metro/base/content/contenthandlers/PluginCTPHandler.js
browser/metro/base/content/contenthandlers/ViewportHandler.js
toolkit/devtools/server/tests/unit/test_pretty_print-02.js
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -154,20 +154,21 @@ SocialUI = {
         case "social:ambient-notification-changed":
           SocialStatus.updateNotification(data);
           if (this._matchesCurrentProvider(data)) {
             SocialToolbar.updateButton();
             SocialMenu.populate();
           }
           break;
         case "social:profile-changed":
+          // make sure anything that happens here only affects the provider for
+          // which the profile is changing, and that anything we call actually
+          // needs to change based on profile data.
           if (this._matchesCurrentProvider(data)) {
             SocialToolbar.updateProvider();
-            SocialMarks.update();
-            SocialChatBar.update();
           }
           break;
         case "social:frameworker-error":
           if (this.enabled && Social.provider.origin == data) {
             SocialSidebar.setSidebarErrorMessage();
           }
           break;
 
@@ -1441,19 +1442,29 @@ SocialStatus = {
       return;
     let tbh = this._toolbarHelper;
     tbh.removePersistence(tbh.idFromOrgin(origin));
   },
 
   removeProvider: function(origin) {
     if (!Social.allowMultipleWorkers)
       return;
+    this._removeFrame(origin);
     this._toolbarHelper.removeProviderButton(origin);
   },
 
+  _removeFrame: function(origin) {
+    let notificationFrameId = "social-status-" + origin;
+    let frame = document.getElementById(notificationFrameId);
+    if (frame) {
+      SharedFrame.forgetGroup(frame.id);
+      frame.parentNode.removeChild(frame);
+    }
+  },
+
   get _toolbarHelper() {
     delete this._toolbarHelper;
     this._toolbarHelper = new ToolbarHelper("social-status-button", this._createButton.bind(this));
     return this._toolbarHelper;
   },
 
   get _dynamicResizer() {
     delete this._dynamicResizer;
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -679,20 +679,59 @@ chatbox[minimized="true"] {
 chatbar {
   -moz-binding: url("chrome://browser/content/socialchat.xml#chatbar");
   height: 0;
   max-height: 0;
 }
 
 /** See bug 872317 for why the following rule is necessary. */
 
-#downloads-indicator {
+#downloads-button {
   -moz-binding: url("chrome://browser/content/downloads/download.xml#download-toolbarbutton");
 }
 
+/*** Visibility of downloads indicator controls ***/
+
+#downloads-button[indicator] > image.toolbarbutton-icon {
+  display: none;
+}
+
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > image.toolbarbutton-icon {
+  display: -moz-box;
+}
+
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > stack.toolbarbutton-icon {
+  display: none;
+}
+
+#downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
+#downloads-button:not(:-moz-any([progress], [counter], [paused]))
+                                                   #downloads-indicator-progress-area
+{
+  visibility: hidden;
+}
+
+/* Hacks for toolbar full and text modes, until bug 573329 removes them */
+
+toolbar[mode="text"] > #downloads-button {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  -moz-box-pack: center;
+}
+
+toolbar[mode="text"] > #downloads-button > .toolbarbutton-text {
+  -moz-box-ordinal-group: 1;
+}
+
+toolbar[mode="text"] > #downloads-button > .toolbarbutton-icon {
+  display: -moz-box;
+  -moz-box-ordinal-group: 2;
+  visibility: collapse;
+}
+
 /* full screen chat window support */
 chatbar:-moz-full-screen-ancestor,
 chatbox:-moz-full-screen-ancestor  {
   border: none;
   position: fixed !important;
   top: 0 !important;
   left: 0 !important;
   right: 0 !important;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -907,18 +907,26 @@
       <!-- This is a placeholder for the Downloads Indicator.  It is visible
            during the customization of the toolbar, in the palette, and before
            the Downloads Indicator overlay is loaded. -->
       <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      oncommand="DownloadsIndicatorView.onCommand(event);"
                      ondrop="DownloadsIndicatorView.onDrop(event);"
                      ondragover="DownloadsIndicatorView.onDragOver(event);"
                      ondragenter="DownloadsIndicatorView.onDragOver(event);"
+                     xmlns:xbl="http://www.mozilla.org/xbl"
                      label="&downloads.label;"
-                     tooltiptext="&downloads.tooltip;"/>
+                     tooltiptext="&downloads.tooltip;">
+        <!-- We need a different binding for the notification and progress bar, which means
+             we don't get these for free. Adding them in our binding loses them when dragging
+             to the customize window, so we add them in here as 'real' children -->
+        <image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+        <label class="toolbarbutton-text" crop="right" flex="1"
+               xbl:inherits="value=label,accesskey,crop"/>
+      </toolbarbutton>
 
       <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      observes="viewHistorySidebar" label="&historyButton.label;"
                      tooltiptext="&historyButton.tooltip;"/>
 
       <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      observes="viewBookmarksSidebar"
                      tooltiptext="&bookmarksButton.tooltip;"
deleted file mode 100644
--- a/browser/base/content/test/social/Makefile.in
+++ /dev/null
@@ -1,4 +0,0 @@
-# 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/.
-
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -6,16 +6,17 @@ support-files =
   share.html
   social_activate.html
   social_activate_iframe.html
   social_chat.html
   social_flyout.html
   social_mark.html
   social_panel.html
   social_sidebar.html
+  social_sidebar_empty.html
   social_window.html
   social_worker.js
   unchecked.jpg
 
 [browser_addons.js]
 [browser_blocklist.js]
 [browser_chat_tearoff.js]
 [browser_defaults.js]
--- a/browser/base/content/test/social/browser_social_toolbar.js
+++ b/browser/base/content/test/social/browser_social_toolbar.js
@@ -1,24 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-let manifest = { // normal provider
+let manifests = [{
   name: "provider 1",
   origin: "https://example.com",
-  workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+  sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
   iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
-};
+}, { // used for testing install
+  name: "provider test1",
+  origin: "https://test1.example.com",
+  statusURL: "https://test1.example.com/browser/browser/base/content/test/social/social_panel.html",
+  iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
+}];
 
 function test() {
   waitForExplicitFinish();
 
-  runSocialTestWithProvider(manifest, function (finishcb) {
-    runSocialTests(tests, undefined, undefined, finishcb);
+  // required to test status button in combination with the toolbaritem
+  Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
+
+  // Preset the currentSet so the statusbutton is in the toolbar on addition. We
+  // bypass the SocialStatus class here since it requires the manifest already
+  // be installed.
+  let tbh = SocialStatus._toolbarHelper;
+  tbh.setPersistentPosition(tbh.idFromOrgin(manifests[1].origin));
+
+  runSocialTestWithProvider(manifests, function (finishcb) {
+    runSocialTests(tests, undefined, undefined, function() {
+      Services.prefs.clearUserPref("social.allowMultipleWorkers");
+      SocialStatus.removePosition(manifests[1].origin);
+      finishcb();
+    });
   });
 }
 
 var tests = {
   testProfileNone: function(next, useNull) {
     let profile = useNull ? null : {};
     Social.provider.updateUserProfile(profile);
     // check dom values
@@ -32,17 +50,17 @@ var tests = {
     is(userButton.getAttribute("label"), notLoggedInStatusValue, "label reflects not being logged in");
     next();
   },
   testProfileNull: function(next) {
     this.testProfileNone(next, true);
   },
   testProfileSet: function(next) {
     let statusIcon = document.getElementById("social-provider-button").style.listStyleImage;
-    is(statusIcon, "url(\"" + manifest.iconURL + "\")", "manifest iconURL is showing");
+    is(statusIcon, "url(\"" + manifests[0].iconURL + "\")", "manifest iconURL is showing");
     let profile = {
       portrait: "https://example.com/portrait.jpg",
       userName: "trickster",
       displayName: "Kuma Lisa",
       profileURL: "http://example.com/Kuma_Lisa",
       iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
     }
     Social.provider.updateUserProfile(profile);
@@ -161,26 +179,16 @@ var tests = {
         let menuitem = socialToggleMore.querySelector(".ambient-menuitem");
         is(menuitem.getAttribute("label"), "Test Ambient 1 \u2046", "Keyboard accessible ambient menuitem should have specified label");
         toolsPopup.hidePopup();
         next();
       }, false);
       document.getElementById("menu_ToolsPopup").openPopup();
     }, "statusIcon was never found");
   },
-  testProfileUnset: function(next) {
-    Social.provider.updateUserProfile({});
-    // check dom values
-    let ambientIcons = document.querySelectorAll("#social-toolbar-item > box");
-    for (let ambientIcon of ambientIcons) {
-      ok(ambientIcon.collapsed, "ambient icon (" + ambientIcon.id + ") is collapsed");
-    }
-    
-    next();
-  },
   testMenuitemsExist: function(next) {
     let toggleSidebarMenuitems = document.getElementsByClassName("social-toggle-sidebar-menuitem");
     is(toggleSidebarMenuitems.length, 2, "Toggle Sidebar menuitems exist");
     let toggleDesktopNotificationsMenuitems = document.getElementsByClassName("social-toggle-notifications-menuitem");
     is(toggleDesktopNotificationsMenuitems.length, 2, "Toggle notifications menuitems exist");
     let toggleSocialMenuitems = document.getElementsByClassName("social-toggle-menuitem");
     is(toggleSocialMenuitems.length, 2, "Toggle Social menuitems exist");
     next();
@@ -189,10 +197,10 @@ var tests = {
     let enabled = Services.prefs.getBoolPref("social.toast-notifications.enabled");
     let cmd = document.getElementById("Social:ToggleNotifications");
     is(cmd.getAttribute("checked"), enabled ? "true" : "false");
     enabled = !enabled;
     Services.prefs.setBoolPref("social.toast-notifications.enabled", enabled);
     is(cmd.getAttribute("checked"), enabled ? "true" : "false");
     Services.prefs.clearUserPref("social.toast-notifications.enabled");
     next();
-  },
+  }
 }
--- a/browser/base/content/test/social/browser_social_window.js
+++ b/browser/base/content/test/social/browser_social_window.js
@@ -26,22 +26,34 @@ function openWindowAndWaitForInit(callba
   createdWindows.push(w);
   Services.obs.addObserver(function providerSet(subject, topic, data) {
     Services.obs.removeObserver(providerSet, topic);
     info(topic + " observer was notified - continuing test");
     executeSoon(() => callback(w));
   }, topic, false);
 }
 
+function closeOneWindow(cb) {
+  let w = createdWindows.pop();
+  if (!w) {
+    cb();
+    return;
+  }
+  waitForCondition(function() w.closed,
+                   function() {
+                    closeOneWindow(cb);
+                    }, "window did not close");
+  w.close();
+}
+
 function postTestCleanup(cb) {
-  for (let w of createdWindows)
-    w.close();
-  createdWindows = [];
-  Services.prefs.clearUserPref("social.enabled");
-  cb();
+  closeOneWindow(function() {
+    Services.prefs.clearUserPref("social.enabled");
+    cb();
+  });
 }
 
 let manifest = { // normal provider
   name: "provider 1",
   origin: "https://example.com",
   sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
   workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -293,16 +293,24 @@ function checkSocialUI(win) {
   isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
 
   // broadcasters.
   isbool(!doc.getElementById("socialActiveBroadcaster").hidden, active, "socialActiveBroadcaster hidden?");
   // and report on overall success of failure of the various checks here.
   is(numGoodTests, numTests, "The Social UI tests succeeded.")
 }
 
+function waitForNotification(topic, cb) {
+  function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+    cb();
+  }
+  Services.obs.addObserver(observer, topic, false);
+}
+
 // blocklist testing
 function updateBlocklist(aCallback) {
   var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
                           .getService(Ci.nsITimerCallback);
   var observer = function() {
     Services.obs.removeObserver(observer, "blocklist-updated");
     if (aCallback)
       executeSoon(aCallback);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/social_sidebar_empty.html
@@ -0,0 +1,8 @@
+<html>
+  <head>
+    <meta charset="utf-8">
+  </head>
+  <body>
+    <p>This is a test social sidebar.</p>
+  </body>
+</html>
deleted file mode 100644
--- a/browser/components/downloads/content/indicator.css
+++ /dev/null
@@ -1,36 +0,0 @@
-/* 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/. */
-
-/*** Visibility of indicator controls ***/
-
-#downloads-indicator:-moz-any([progress],
-                              [counter],
-                              [paused])    #downloads-indicator-icon,
-
-#downloads-indicator:not(:-moz-any([progress],
-                                   [counter],
-                                   [paused]))
-                                           #downloads-indicator-progress-area
-
-{
-  visibility: hidden;
-}
-
-/* Hacks for toolbar full and text modes, until bug 573329 removes them */
-
-toolbar[mode="text"] > #downloads-indicator {
-  display: -moz-box;
-  -moz-box-orient: vertical;
-  -moz-box-pack: center;
-}
-
-toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text {
-  -moz-box-ordinal-group: 1;
-}
-
-toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon {
-  display: -moz-box;
-  -moz-box-ordinal-group: 2;
-  visibility: collapse;
-}
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -56,17 +56,21 @@ const DownloadsButton = {
    * This function is called asynchronously just after window initialization.
    *
    * NOTE: This function should limit the input/output it performs to improve
    *       startup time, and in particular should not cause the Download Manager
    *       service to start.
    */
   initializeIndicator: function DB_initializeIndicator()
   {
-    this._update();
+    if (!DownloadsCommon.useToolkitUI) {
+      DownloadsIndicatorView.ensureInitialized();
+    } else {
+      DownloadsIndicatorView.ensureTerminated();
+    }
   },
 
   /**
    * Indicates whether toolbar customization is in progress.
    */
   _customizing: false,
 
   /**
@@ -74,110 +78,54 @@ const DownloadsButton = {
    *
    * During customization, we never show the actual download progress indication
    * or the event notifications, but we show a neutral placeholder.  The neutral
    * placeholder is an ordinary button defined in the browser window that can be
    * moved freely between the toolbars and the customization palette.
    */
   customizeStart: function DB_customizeStart()
   {
-    // Hide the indicator and prevent it to be displayed as a temporary anchor
+    // Prevent the indicator from being displayed as a temporary anchor
     // during customization, even if requested using the getAnchor method.
     this._customizing = true;
     this._anchorRequested = false;
-
-    let indicator = DownloadsIndicatorView.indicator;
-    if (indicator) {
-      indicator.collapsed = true;
-    }
-
-    let placeholder = this._placeholder;
-    if (placeholder) {
-      placeholder.collapsed = false;
-    }
   },
 
   /**
    * This function is called when toolbar customization ends.
    */
   customizeDone: function DB_customizeDone()
   {
     this._customizing = false;
-    this._update();
-  },
-
-  /**
-   * This function is called during initialization or when toolbar customization
-   * ends.  It determines if we should enable or disable the object that keeps
-   * the indicator updated, and ensures that the placeholder is hidden unless it
-   * has been moved to the customization palette.
-   *
-   * NOTE: This function is also called on startup, thus it should limit the
-   *       input/output it performs, and in particular should not cause the
-   *       Download Manager service to start.
-   */
-  _update: function DB_update() {
-    this._updatePositionInternal();
-
     if (!DownloadsCommon.useToolkitUI) {
-      DownloadsIndicatorView.ensureInitialized();
+      DownloadsIndicatorView.afterCustomize();
     } else {
       DownloadsIndicatorView.ensureTerminated();
     }
   },
 
   /**
    * Determines the position where the indicator should appear, and moves its
-   * associated element to the new position.  This does not happen if the
-   * indicator is currently being used as the anchor for the panel, to ensure
-   * that the panel doesn't flicker because we move the DOM element to which
-   * it's anchored.
-   */
-  updatePosition: function DB_updatePosition()
-  {
-    if (!this._anchorRequested) {
-      this._updatePositionInternal();
-    }
-  },
-
-  /**
-   * Determines the position where the indicator should appear, and moves its
    * associated element to the new position.
    *
    * @return Anchor element, or null if the indicator is not visible.
    */
-  _updatePositionInternal: function DB_updatePositionInternal()
+  _getAnchorInternal: function DB_getAnchorInternal()
   {
     let indicator = DownloadsIndicatorView.indicator;
     if (!indicator) {
-      // Exit now if the indicator overlay isn't loaded yet.
+      // Exit now if the indicator overlay isn't loaded yet, or if the button
+      // is not in the document.
       return null;
     }
 
-    let placeholder = this._placeholder;
-    if (!placeholder) {
-      // The placeholder has been removed from the browser window.
-      indicator.collapsed = true;
-      // Move the indicator to a safe position on the toolbar, since otherwise
-      // it may break the merge of adjacent items, like back/forward + urlbar.
-      indicator.parentNode.appendChild(indicator);
-      return null;
-    }
-
-    // Position the indicator where the placeholder is located.  We should
-    // update the position even if the placeholder is located on an invisible
-    // toolbar, because the toolbar may be displayed later.
-    placeholder.parentNode.insertBefore(indicator, placeholder);
-    placeholder.collapsed = true;
-    indicator.collapsed = false;
-
     indicator.open = this._anchorRequested;
 
-    // Determine if the placeholder is located on an invisible toolbar.
-    if (!isElementVisible(placeholder.parentNode)) {
+    // Determine if we're located on an invisible toolbar.
+    if (!isElementVisible(indicator.parentNode)) {
       return null;
     }
 
     return DownloadsIndicatorView.indicatorAnchor;
   },
 
   /**
    * Checks whether the indicator is, or will soon be visible in the browser
@@ -220,30 +168,30 @@ const DownloadsButton = {
     // Do not allow anchoring the panel to the element while customizing.
     if (this._customizing) {
       aCallback(null);
       return;
     }
 
     function DB_GA_callback() {
       this._anchorRequested = true;
-      aCallback(this._updatePositionInternal());
+      aCallback(this._getAnchorInternal());
     }
 
     DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
                                                DB_GA_callback.bind(this));
   },
 
   /**
    * Allows the temporary anchor to be hidden.
    */
   releaseAnchor: function DB_releaseAnchor()
   {
     this._anchorRequested = false;
-    this._updatePositionInternal();
+    this._getAnchorInternal();
   },
 
   get _tabsToolbar()
   {
     delete this._tabsToolbar;
     return this._tabsToolbar = document.getElementById("TabsToolbar");
   },
 
@@ -312,30 +260,40 @@ const DownloadsIndicatorView = {
 
   /**
    * Ensures that the user interface elements required to display the indicator
    * are loaded, then invokes the given callback.
    */
   _ensureOperational: function DIV_ensureOperational(aCallback)
   {
     if (this._operational) {
-      aCallback();
+      if (aCallback) {
+        aCallback();
+      }
+      return;
+    }
+
+    // If we don't have a _placeholder, there's no chance that the overlay
+    // will load correctly: bail (and don't set _operational to true!)
+    if (!DownloadsButton._placeholder) {
       return;
     }
 
     function DIV_EO_callback() {
       this._operational = true;
 
       // If the view is initialized, we need to update the elements now that
       // they are finally available in the document.
       if (this._initialized) {
         DownloadsCommon.getIndicatorData(window).refreshView(this);
       }
 
-      aCallback();
+      if (aCallback) {
+        aCallback();
+      }
     }
 
     DownloadsOverlayLoader.ensureOverlayLoaded(
                                  DownloadsButton.kIndicatorOverlay,
                                  DIV_EO_callback.bind(this));
   },
 
   //////////////////////////////////////////////////////////////////////////////
@@ -368,20 +326,16 @@ const DownloadsIndicatorView = {
       return;
     }
 
     function DIV_SEN_callback() {
       if (this._notificationTimeout) {
         clearTimeout(this._notificationTimeout);
       }
 
-      // Now that the overlay is loaded, place the indicator in its final
-      // position.
-      DownloadsButton.updatePosition();
-
       let indicator = this.indicator;
       indicator.setAttribute("notification", aType);
       this._notificationTimeout = setTimeout(
         function () indicator.removeAttribute("notification"), 1000);
     }
 
     this._ensureOperational(DIV_SEN_callback.bind(this));
   },
@@ -394,21 +348,18 @@ const DownloadsIndicatorView = {
    * downloads to be displayed.
    */
   set hasDownloads(aValue)
   {
     if (this._hasDownloads != aValue) {
       this._hasDownloads = aValue;
 
       // If there is at least one download, ensure that the view elements are
-      // loaded before determining the position of the downloads button.
       if (aValue) {
-        this._ensureOperational(function() DownloadsButton.updatePosition());
-      } else {
-        DownloadsButton.updatePosition();
+        this._ensureOperational();
       }
     }
     return aValue;
   },
   get hasDownloads()
   {
     return this._hasDownloads;
   },
@@ -550,45 +501,68 @@ const DownloadsIndicatorView = {
       }
 
       let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
       saveURL(url, name.value, null, true, true, null, sourceDoc);
       aEvent.preventDefault();
     }
   },
 
+  _indicator: null,
+  _indicatorAnchor: null,
+  __indicatorCounter: null,
+  __indicatorProgress: null,
+
   /**
    * Returns a reference to the main indicator element, or null if the element
    * is not present in the browser window yet.
    */
   get indicator()
   {
-    let indicator = document.getElementById("downloads-indicator");
-    if (!indicator) {
+    if (this._indicator) {
+      return this._indicator;
+    }
+
+    let indicator = document.getElementById("downloads-button");
+    if (!indicator || indicator.getAttribute("indicator") != "true") {
       return null;
     }
 
-    // Once the element is loaded, it will never be unloaded.
-    delete this.indicator;
-    return this.indicator = indicator;
+    return this._indicator = indicator;
   },
 
   get indicatorAnchor()
   {
-    delete this.indicatorAnchor;
-    return this.indicatorAnchor =
-      document.getElementById("downloads-indicator-anchor");
+    return this._indicatorAnchor ||
+      (this._indicatorAnchor = document.getElementById("downloads-indicator-anchor"));
   },
 
   get _indicatorCounter()
   {
-    delete this._indicatorCounter;
-    return this._indicatorCounter =
-      document.getElementById("downloads-indicator-counter");
+    return this.__indicatorCounter ||
+      (this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
   },
 
   get _indicatorProgress()
   {
-    delete this._indicatorProgress;
-    return this._indicatorProgress =
-      document.getElementById("downloads-indicator-progress");
+    return this.__indicatorProgress ||
+      (this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
+  },
+
+  _onCustomizedAway: function() {
+    this._indicator = null;
+    this._indicatorAnchor = null;
+    this.__indicatorCounter = null;
+    this.__indicatorProgress = null;
+  },
+
+  afterCustomize: function() {
+    // If the cached indicator is not the one currently in the document,
+    // invalidate our references
+    if (this._indicator != document.getElementById("downloads-button")) {
+      this._onCustomizedAway();
+      this._operational = false;
+      this.ensureTerminated();
+      this.ensureInitialized();
+    }
   }
 };
+
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -1,60 +1,36 @@
 <?xml version="1.0"?>
 <!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
 <!-- vim: set ts=2 et sw=2 tw=80: -->
 
 <!-- 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/. -->
 
-<?xml-stylesheet href="chrome://browser/content/downloads/indicator.css"?>
-<?xml-stylesheet href="chrome://browser/skin/downloads/indicator.css"?>
-
 <!DOCTYPE overlay [
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
   %browserDTD;
-  <!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd" >
-  %downloadsDTD;
 ]>
 
 <overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          id="indicatorOverlay">
 
-  <popupset>
-    <!-- The downloads indicator is placed in its final toolbar location
-         programmatically, and can be shown temporarily even when its
-         placeholder is removed from the toolbars.  Its initial location within
-         the document must not be a toolbar or the toolbar palette, otherwise the
-         toolbar handling code could remove it from the document. -->
-    <toolbarbutton id="downloads-indicator"
-                   class="toolbarbutton-1 chromeclass-toolbar-additional"
-                   tooltiptext="&downloads.tooltip;"
-                   collapsed="true"
-                   oncommand="DownloadsIndicatorView.onCommand(event);"
-                   ondrop="DownloadsIndicatorView.onDrop(event);"
-                   ondragover="DownloadsIndicatorView.onDragOver(event);"
-                   ondragenter="DownloadsIndicatorView.onDragOver(event);"
-                   ondragleave="DownloadsIndicatorView.onDragLeave(event);"
-                   skipintoolbarset="true">
-      <!-- The panel's anchor area is smaller than the outer button, but must
-           always be visible and must not move or resize when the indicator
-           state changes, otherwise the panel could change its position or lose
-           its arrow unexpectedly. -->
-      <stack id="downloads-indicator-anchor"
-             class="toolbarbutton-icon">
-        <vbox id="downloads-indicator-progress-area"
-              pack="center">
-          <description id="downloads-indicator-counter"/>
-          <progressmeter id="downloads-indicator-progress"
-                         class="plain"
-                         min="0"
-                         max="100"/>
-        </vbox>
-        <vbox id="downloads-indicator-icon"/>
-        <vbox id="downloads-indicator-notification"/>
-      </stack>
-      <label class="toolbarbutton-text" crop="right" flex="1"
-             value="&downloads.label;"/>
-    </toolbarbutton>
-  </popupset>
+  <!-- We dynamically add the stack with the progress meter and notification icon,
+       originally loaded lazily because of performance reasons, to the existing
+       downloads-button. -->
+  <toolbarbutton id="downloads-button" indicator="true">
+    <!-- The panel's anchor area is smaller than the outer button, but must
+         always be visible and must not move or resize when the indicator
+         state changes, otherwise the panel could change its position or lose
+         its arrow unexpectedly. -->
+    <stack id="downloads-indicator-anchor" class="toolbarbutton-icon">
+      <vbox id="downloads-indicator-progress-area" pack="center">
+        <description id="downloads-indicator-counter"/>
+        <progressmeter id="downloads-indicator-progress" class="plain"
+                       min="0" max="100"/>
+      </vbox>
+      <vbox id="downloads-indicator-icon"/>
+      <vbox id="downloads-indicator-notification"/>
+    </stack>
+  </toolbarbutton>
 </overlay>
--- a/browser/components/downloads/jar.mn
+++ b/browser/components/downloads/jar.mn
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 *       content/browser/downloads/download.xml           (content/download.xml)
         content/browser/downloads/download.css           (content/download.css)
         content/browser/downloads/downloads.css          (content/downloads.css)
 *       content/browser/downloads/downloads.js           (content/downloads.js)
 *       content/browser/downloads/downloadsOverlay.xul   (content/downloadsOverlay.xul)
-        content/browser/downloads/indicator.css          (content/indicator.css)
         content/browser/downloads/indicator.js           (content/indicator.js)
         content/browser/downloads/indicatorOverlay.xul   (content/indicatorOverlay.xul)
 *       content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
         content/browser/downloads/allDownloadsViewOverlay.js  (content/allDownloadsViewOverlay.js)
         content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css)
 *       content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
         content/browser/downloads/contentAreaDownloadsView.js  (content/contentAreaDownloadsView.js)
         content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)
--- a/browser/metro/base/content/BrowserTouchHandler.js
+++ b/browser/metro/base/content/BrowserTouchHandler.js
@@ -62,16 +62,19 @@ const BrowserTouchHandler = {
   },
 
   /*
    * Events
    */
 
   handleEvent: function handleEvent(aEvent) {
     // ignore content events we generate
+    if (aEvent.target == document)
+      return;
+
     if (this._debugEvents)
       Util.dumpLn("BrowserTouchHandler:", aEvent.type);
 
     switch (aEvent.type) {
       case "PopupChanged":
       case "CancelTouchSequence":
         if (!aEvent.detail)
           ContextMenuUI.reset();
--- a/browser/metro/base/content/WebProgress.js
+++ b/browser/metro/base/content/WebProgress.js
@@ -103,45 +103,31 @@ const WebProgress = {
     if (aTab == Browser.selectedTab) {
       BrowserUI.updateURI();
       BrowserUI.update();
     }
 
     let locationHasChanged = (location != aTab.browser.lastLocation);
     if (locationHasChanged) {
       Browser.getNotificationBox(aTab.browser).removeTransientNotifications();
-      aTab.resetZoomLevel();
       aTab.browser.lastLocation = location;
       aTab.browser.userTypedValue = "";
       aTab.browser.appIcon = { href: null, size:-1 };
 
 #ifdef MOZ_CRASHREPORTER
       if (CrashReporter.enabled)
         CrashReporter.annotateCrashReport("URL", spec);
 #endif
-      this._waitForLoad(aTab);
     }
 
     let event = document.createEvent("UIEvents");
     event.initUIEvent("URLChanged", true, false, window, locationHasChanged);
     aTab.browser.dispatchEvent(event);
   },
 
-  _waitForLoad: function _waitForLoad(aTab) {
-    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);
-    });
-  },
-
   _networkStart: function _networkStart(aJson, aTab) {
     aTab.startLoading();
 
     if (aTab == Browser.selectedTab) {
       // NO_STARTUI_VISIBILITY since the current uri for the tab has not
       // been updated yet. If we're coming off of the start page, this
       // would briefly show StartUI until _locationChange is called.
       BrowserUI.update(BrowserUI.NO_STARTUI_VISIBILITY);
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -8,24 +8,20 @@ let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
 
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 
 // Page for which the start UI is shown
 const kStartURI = "about:start";
 
-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";
 
 // See grid.xml, we use this to cache style info across loads of the startui.
 var _richgridTileSizes = {};
 
 // Override sizeToContent in the main window. It breaks things (bug 565887)
@@ -63,37 +59,33 @@ var Browser = {
     try {
       messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/Content.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FormHelper.js", true);
       messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FindHandler.js", true);
-      // XXX Viewport resizing disabled because of bug 766142
-      //messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ViewportHandler.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
-      //messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginCTPHandler.js", true);
     } catch (e) {
       // XXX whatever is calling startup needs to dump errors!
       dump("###########" + e + "\n");
     }
 
     /* handles dispatching clicks on browser into clicks in content or zooms */
     Elements.browsers.customDragger = new Browser.MainDragger();
 
     /* handles web progress management for open browsers */
     Elements.browsers.webProgress = WebProgress.init();
 
     // Call InputSourceHelper first so global listeners get called before
     // we start processing input in TouchModule.
     InputSourceHelper.init();
 
     TouchModule.init();
-    ScrollwheelModule.init(Elements.browsers);
     GestureModule.init();
     BrowserTouchHandler.init();
     PopupBlockerObserver.init();
     APZCObserver.init();
 
     // Init the touch scrollbox
     this.contentScrollbox = Elements.browsers;
     this.contentScrollboxScroller = {
@@ -151,27 +143,22 @@ var Browser = {
         // BrowserCLH paramerter
         commandURL = window.arguments[0];
       }
     } catch (ex) {
       Util.dumpLn(ex);
     }
 
     messageManager.addMessageListener("DOMLinkAdded", this);
-    messageManager.addMessageListener("MozScrolledAreaChanged", this);
-    messageManager.addMessageListener("Browser:ViewportMetadata", this);
     messageManager.addMessageListener("Browser:FormSubmit", this);
-    messageManager.addMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.addMessageListener("Browser:CanUnload:Return", this);
     messageManager.addMessageListener("scroll", this);
     messageManager.addMessageListener("Browser:CertException", this);
     messageManager.addMessageListener("Browser:BlockedSite", this);
-    messageManager.addMessageListener("Browser:ErrorPage", this);
     messageManager.addMessageListener("Browser:TapOnSelection", this);
-    messageManager.addMessageListener("Browser:PluginClickToPlayClicked", this);
 
     Task.spawn(function() {
       // Activation URIs come from protocol activations, secondary tiles, and file activations
       let activationURI = yield this.getShortcutOrURI(Services.metro.activationURI);
 
       let self = this;
       function loadStartupURI() {
         if (activationURI) {
@@ -224,25 +211,20 @@ var Browser = {
   },
 
   shutdown: function shutdown() {
     APZCObserver.shutdown();
     BrowserUI.uninit();
     ContentAreaObserver.shutdown();
     Appbar.shutdown();
 
-    messageManager.removeMessageListener("MozScrolledAreaChanged", this);
-    messageManager.removeMessageListener("Browser:ViewportMetadata", this);
     messageManager.removeMessageListener("Browser:FormSubmit", this);
-    messageManager.removeMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.removeMessageListener("scroll", this);
     messageManager.removeMessageListener("Browser:CertException", this);
     messageManager.removeMessageListener("Browser:BlockedSite", this);
-    messageManager.removeMessageListener("Browser:ErrorPage", this);
-    messageManager.removeMessageListener("Browser:PluginClickToPlayClicked", this);
     messageManager.removeMessageListener("Browser:TapOnSelection", this);
 
     Services.obs.removeObserver(SessionHistoryObserver, "browser:purge-session-history");
 
     window.controllers.removeController(this);
     window.controllers.removeController(BrowserUI);
   },
 
@@ -766,56 +748,23 @@ var Browser = {
     Bookmarks.removeForURI(uri, callback);
   },
 
   isSiteStarredAsync: function browser_isSiteStarredAsync(callback) {
     let uri = this.selectedBrowser.currentURI;
     Bookmarks.isURIBookmarked(uri, callback);
   },
 
-  /** Zoom one step in (negative) or out (positive). */
-  zoom: function zoom(aDirection) {
-    let tab = this.selectedTab;
-    if (!tab.allowZoom)
-      return;
-
-    let browser = tab.browser;
-    let oldZoomLevel = browser.scale;
-    let zoomLevel = oldZoomLevel;
-
-    let zoomValues = ZoomManager.zoomValues;
-    let i = zoomValues.indexOf(ZoomManager.snap(zoomLevel)) + (aDirection < 0 ? 1 : -1);
-    if (i >= 0 && i < zoomValues.length)
-      zoomLevel = zoomValues[i];
-
-    zoomLevel = tab.clampZoomLevel(zoomLevel);
-
-    let browserRect = browser.getBoundingClientRect();
-    let center = browser.ptClientToBrowser(browserRect.width / 2,
-                                           browserRect.height / 2);
-    let rect = this._getZoomRectForPoint(center.xPos, center.yPos, zoomLevel);
-    AnimatedZoom.animateTo(rect);
-  },
-
   /** Rect should be in browser coordinates. */
   _getZoomLevelForRect: function _getZoomLevelForRect(rect) {
     const margin = 15;
     return this.selectedTab.clampZoomLevel(ContentAreaObserver.width / (rect.width + margin * 2));
   },
 
   /**
-   * Find an appropriate zoom rect for an element bounding rect, if it exists.
-   * @return Rect in viewport coordinates, or null
-   */
-  _getZoomRectForRect: function _getZoomRectForRect(rect, y) {
-    let zoomLevel = this._getZoomLevelForRect(rect);
-    return this._getZoomRectForPoint(rect.center().x, y, zoomLevel);
-  },
-
-  /**
    * Find a good zoom rectangle for point that is specified in browser coordinates.
    * @return Rect in viewport coordinates
    */
   _getZoomRectForPoint: function _getZoomRectForPoint(x, y, zoomLevel) {
     let browser = getBrowser();
     x = x * browser.scale;
     y = y * browser.scale;
 
@@ -826,62 +775,16 @@ var Browser = {
     let newVisW = browserRect.width / zoomRatio, newVisH = browserRect.height / zoomRatio;
     let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH);
 
     // Make sure rectangle doesn't poke out of viewport
     return result.translateInside(new Rect(0, 0, browser.contentDocumentWidth * oldScale,
                                                  browser.contentDocumentHeight * oldScale));
   },
 
-  zoomToPoint: function zoomToPoint(cX, cY, aRect) {
-    let tab = this.selectedTab;
-    if (!tab.allowZoom)
-      return null;
-
-    let zoomRect = null;
-    if (aRect)
-      zoomRect = this._getZoomRectForRect(aRect, cY);
-
-    if (!zoomRect && tab.isDefaultZoomLevel()) {
-      let scale = tab.clampZoomLevel(tab.browser.scale * 2);
-      zoomRect = this._getZoomRectForPoint(cX, cY, scale);
-    }
-
-    if (zoomRect)
-      AnimatedZoom.animateTo(zoomRect);
-
-    return zoomRect;
-  },
-
-  zoomFromPoint: function zoomFromPoint(cX, cY) {
-    let tab = this.selectedTab;
-    if (tab.allowZoom && !tab.isDefaultZoomLevel()) {
-      let zoomLevel = tab.getDefaultZoomLevel();
-      let zoomRect = this._getZoomRectForPoint(cX, cY, zoomLevel);
-      AnimatedZoom.animateTo(zoomRect);
-    }
-  },
-
-  // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
-  // This is higher on higher-dpi displays, so pages stay about the same physical size.
-  getScaleRatio: function getScaleRatio() {
-    let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio");
-    if (prefValue > 0)
-      return prefValue / 100;
-
-    let dpi = Util.displayDPI;
-    if (dpi < 200) // Includes desktop displays, and LDPI and MDPI Android devices
-      return 1;
-    else if (dpi < 300) // Includes Nokia N900, and HDPI Android devices
-      return 1.5;
-
-    // For very high-density displays like the iPhone 4, calculate an integer ratio.
-    return Math.floor(dpi / 150);
-  },
-
   /**
    * Convenience function for getting the scrollbox position off of a
    * scrollBoxObject interface.  Returns the actual values instead of the
    * wrapping objects.
    *
    * @param scroller a scrollBoxObject on which to call scroller.getPosition()
    */
   getScrollboxPosition: function getScrollboxPosition(scroller) {
@@ -925,83 +828,33 @@ var Browser = {
         else if ((rel.indexOf("apple-touch-icon") != -1) && (browser.appIcon.size < 57)) {
           // XXX should we support apple-touch-icon-precomposed ?
           // see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
           browser.appIcon.href = json.href;
           browser.appIcon.size = 57;
         }
         break;
       }
-      case "MozScrolledAreaChanged": {
-        let tab = this.getTabForBrowser(browser);
-        if (tab)
-          tab.scrolledAreaChanged();
-        break;
-      }
-      case "Browser:ViewportMetadata": {
-        let tab = this.getTabForBrowser(browser);
-        // Some browser such as iframes loaded dynamically into the chrome UI
-        // does not have any assigned tab
-        if (tab)
-          tab.updateViewportMetadata(json);
-        break;
-      }
       case "Browser:FormSubmit":
         browser.lastLocation = null;
         break;
 
       case "Browser:CanUnload:Return": {
-	if (json.permit) {
-	  let tab = this.getTabForBrowser(browser);
-	  BrowserUI.animateClosingTab(tab);
-	}
-	break;
-      }
-      case "Browser:ZoomToPoint:Return":
-        if (json.zoomTo) {
-          let rect = Rect.fromRect(json.zoomTo);
-          this.zoomToPoint(json.x, json.y, rect);
-        } else {
-          this.zoomFromPoint(json.x, json.y);
+        if (json.permit) {
+          let tab = this.getTabForBrowser(browser);
+          BrowserUI.animateClosingTab(tab);
         }
         break;
+      }
       case "Browser:CertException":
         this._handleCertException(aMessage);
         break;
       case "Browser:BlockedSite":
         this._handleBlockedSite(aMessage);
         break;
-      case "Browser:ErrorPage":
-        break;
-      case "Browser:PluginClickToPlayClicked": {
-        // Save off session history
-        let parent = browser.parentNode;
-        let data = browser.__SS_data;
-        if (data.entries.length == 0)
-          return;
-
-        // Remove the browser from the DOM, effectively killing it's content
-        parent.removeChild(browser);
-
-        // Re-create the browser as non-remote, so plugins work
-        browser.setAttribute("remote", "false");
-        parent.appendChild(browser);
-
-        // Reload the content using session history
-        browser.__SS_data = data;
-        let json = {
-          uri: data.entries[data.index - 1].url,
-          flags: null,
-          entries: data.entries,
-          index: data.index
-        };
-        browser.messageManager.sendAsyncMessage("WebNavigation:LoadURI", json);
-        break;
-      }
-
       case "Browser:TapOnSelection":
         if (!InputSourceHelper.isPrecise) {
           if (SelectionHelperUI.isActive) {
             SelectionHelperUI.shutdown();
           }
           if (SelectionHelperUI.canHandle(aMessage)) {
             SelectionHelperUI.openEditSession(aMessage);
           }
@@ -1230,20 +1083,16 @@ nsBrowserAccess.prototype = {
     return browser ? browser.contentWindow : null;
   },
 
   openURIInFrame: function browser_openURIInFrame(aURI, aOpener, aWhere, aContext) {
     let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
     return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
   },
 
-  zoom: function browser_zoom(aAmount) {
-    Browser.zoom(aAmount);
-  },
-
   isTabContentWindow: function(aWindow) {
     return Browser.browsers.some(function (browser) browser.contentWindow == aWindow);
   },
 
   get contentWindow() {
     return Browser.selectedBrowser.contentWindow;
   }
 };
@@ -1389,17 +1238,16 @@ 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;
@@ -1420,106 +1268,20 @@ Tab.prototype = {
   get notification() {
     return this._notification;
   },
 
   get chromeTab() {
     return this._chromeTab;
   },
 
-  get metadata() {
-    return this._metadata || kDefaultMetadata;
-  },
-
   get pageShowPromise() {
     return this._eventDeferred ? this._eventDeferred.promise : null;
   },
 
-  /** Update browser styles when the viewport metadata changes. */
-  updateViewportMetadata: function updateViewportMetadata(aMetadata) {
-    if (aMetadata && aMetadata.autoScale) {
-      let scaleRatio = aMetadata.scaleRatio = Browser.getScaleRatio();
-
-      if ("defaultZoom" in aMetadata && aMetadata.defaultZoom > 0)
-        aMetadata.defaultZoom *= scaleRatio;
-      if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
-        aMetadata.minZoom *= scaleRatio;
-      if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0)
-        aMetadata.maxZoom *= scaleRatio;
-    }
-    this._metadata = aMetadata;
-    this.updateViewportSize();
-  },
-
-  /**
-   * Update browser size when the metadata or the window size changes.
-   */
-  updateViewportSize: function updateViewportSize(width, height) {
-    /* XXX Viewport resizing disabled because of bug 766142
-
-    let browser = this._browser;
-    if (!browser)
-      return;
-
-    let screenW = width || ContentAreaObserver.width;
-    let screenH = height || ContentAreaObserver.height;
-    let viewportW, viewportH;
-
-    let metadata = this.metadata;
-    if (metadata.autoSize) {
-      if ("scaleRatio" in metadata) {
-        viewportW = screenW / metadata.scaleRatio;
-        viewportH = screenH / metadata.scaleRatio;
-      } else {
-        viewportW = screenW;
-        viewportH = screenH;
-      }
-    } else {
-      viewportW = metadata.width;
-      viewportH = metadata.height;
-
-      // If (scale * width) < device-width, increase the width (bug 561413).
-      let maxInitialZoom = metadata.defaultZoom || metadata.maxZoom;
-      if (maxInitialZoom && viewportW)
-        viewportW = Math.max(viewportW, screenW / maxInitialZoom);
-
-      let validW = viewportW > 0;
-      let validH = viewportH > 0;
-
-      if (!validW)
-        viewportW = validH ? (viewportH * (screenW / screenH)) : Browser.defaultBrowserWidth;
-      if (!validH)
-        viewportH = viewportW * (screenH / screenW);
-    }
-
-    // Make sure the viewport height is not shorter than the window when
-    // the page is zoomed out to show its full width.
-    let pageZoomLevel = this.getPageZoomLevel(screenW);
-    let minScale = this.clampZoomLevel(pageZoomLevel, pageZoomLevel);
-    viewportH = Math.max(viewportH, screenH / minScale);
-
-    if (browser.contentWindowWidth != viewportW || browser.contentWindowHeight != viewportH)
-      browser.setWindowSize(viewportW, viewportH);
-    */
-  },
-
-  restoreViewportPosition: function restoreViewportPosition(aOldWidth, aNewWidth) {
-    let browser = this._browser;
-
-    // zoom to keep the same portion of the document visible
-    let oldScale = browser.scale;
-    let newScale = this.clampZoomLevel(oldScale * aNewWidth / aOldWidth);
-    let scaleRatio = newScale / oldScale;
-
-    let view = browser.getRootView();
-    let pos = view.getPosition();
-    browser.fuzzyZoom(newScale, pos.x * scaleRatio, pos.y * scaleRatio);
-    browser.finishFuzzyZoom();
-  },
-
   startLoading: function startLoading() {
     if (this._loading) {
       let stack = new Error().stack;
       throw "Already Loading!\n" + stack;
     }
     this._loading = true;
   },
 
@@ -1692,118 +1454,19 @@ Tab.prototype = {
 
       Elements.browsers.removeChild(notification);
     }
   },
 
   /**
    * Takes a scale and restricts it based on this tab's zoom limits.
    * @param aScale The original scale.
-   * @param aPageZoomLevel (optional) The zoom-to-fit scale, if known.
-   *   This is a performance optimization to avoid extra calls.
    */
-  clampZoomLevel: function clampZoomLevel(aScale, aPageZoomLevel) {
-    let md = this.metadata;
-    if (!this.allowZoom) {
-      return (md && md.defaultZoom)
-        ? md.defaultZoom
-        : (aPageZoomLevel || this.getPageZoomLevel());
-    }
-
-    let browser = this._browser;
-    let bounded = Util.clamp(aScale, ZoomManager.MIN, ZoomManager.MAX);
-
-    if (md && md.minZoom)
-      bounded = Math.max(bounded, md.minZoom);
-    if (md && md.maxZoom)
-      bounded = Math.min(bounded, md.maxZoom);
-
-    bounded = Math.max(bounded, this.getPageZoomLevel());
-
-    let rounded = Math.round(bounded * kBrowserViewZoomLevelPrecision) / kBrowserViewZoomLevelPrecision;
-    return rounded || 1.0;
-  },
-
-  /** Record the initial zoom level when a page first loads. */
-  resetZoomLevel: function resetZoomLevel() {
-    this._defaultZoomLevel = this._browser.scale;
-  },
-
-  scrolledAreaChanged: function scrolledAreaChanged(firstPaint) {
-    if (!this._browser)
-      return;
-
-    if (firstPaint) {
-      // You only get one shot, do not miss your chance to reflow.
-      this.updateViewportSize();
-    }
-
-    this.updateDefaultZoomLevel();
-  },
-
-  /**
-   * Recalculate default zoom level when page size changes, and update zoom
-   * level if we are at default.
-   */
-  updateDefaultZoomLevel: function updateDefaultZoomLevel() {
-    let browser = this._browser;
-    if (!browser || !this._firstPaint)
-      return;
-
-    let isDefault = this.isDefaultZoomLevel();
-    this._defaultZoomLevel = this.getDefaultZoomLevel();
-    if (isDefault) {
-      if (browser.scale != this._defaultZoomLevel) {
-        browser.scale = this._defaultZoomLevel;
-      } else {
-        // If the scale level has not changed we want to be sure the content
-        // render correctly since the page refresh process could have been
-        // stalled during page load. In this case if the page has the exact
-        // same width (like the same page, so by doing 'refresh') and the
-        // page was scrolled the content is just checkerboard at this point
-        // and this call ensure we render it correctly.
-        browser.getRootView()._updateCacheViewport();
-      }
-    } else {
-      // if we are reloading, the page will retain its scale. if it is zoomed
-      // we need to refresh the viewport so that we do not show checkerboard
-      browser.getRootView()._updateCacheViewport();
-    }
-  },
-
-  isDefaultZoomLevel: function isDefaultZoomLevel() {
-    return this._browser.scale == this._defaultZoomLevel;
-  },
-
-  getDefaultZoomLevel: function getDefaultZoomLevel() {
-    let md = this.metadata;
-    if (md && md.defaultZoom)
-      return this.clampZoomLevel(md.defaultZoom);
-
-    let browserWidth = this._browser.getBoundingClientRect().width;
-    let defaultZoom = browserWidth / this._browser.contentWindowWidth;
-    return this.clampZoomLevel(defaultZoom);
-  },
-
-  /**
-   * @param aScreenWidth (optional) The width of the browser widget, if known.
-   *   This is a performance optimization to save extra calls to getBoundingClientRect.
-   * @return The scale at which the browser will be zoomed out to fit the document width.
-   */
-  getPageZoomLevel: function getPageZoomLevel(aScreenWidth) {
-    let browserW = this._browser.contentDocumentWidth;
-    if (browserW == 0)
-      return 1.0;
-
-    let screenW = aScreenWidth || this._browser.getBoundingClientRect().width;
-    return screenW / browserW;
-  },
-
-  get allowZoom() {
-    return this.metadata.allowZoom && !Util.isURLEmpty(this.browser.currentURI.spec);
+  clampZoomLevel: function clampZoomLevel(aScale) {
+    return Util.clamp(aScale, ZoomManager.MIN, ZoomManager.MAX);
   },
 
   updateThumbnail: function updateThumbnail() {
     PageThumbs.captureToCanvas(this.browser.contentWindow, this._chromeTab.thumbnailCanvas);
   },
 
   updateFavicon: function updateFavicon() {
     this._chromeTab.updateFavicon(this._browser.mIconURL);
deleted file mode 100644
--- a/browser/metro/base/content/contenthandlers/PluginCTPHandler.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* 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/. */
-
-var PluginCTPHandler = {
-  init: function() {
-    addEventListener("PluginClickToPlay", this, false);
-  },
-
-  addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
-     // XXX just doing (callback)(arg) was giving a same-origin error. bug?
-     let self = this;
-     let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
-     linkNode.addEventListener("click",
-                               function(evt) {
-                                 if (!evt.isTrusted)
-                                   return;
-                                 evt.preventDefault();
-                                 if (callbackArgs.length == 0)
-                                   callbackArgs = [ evt ];
-                                 (self[callbackName]).apply(self, callbackArgs);
-                               },
-                               true);
- 
-     linkNode.addEventListener("keydown",
-                               function(evt) {
-                                 if (!evt.isTrusted)
-                                   return;
-                                 if (evt.keyCode == evt.DOM_VK_RETURN) {
-                                   evt.preventDefault();
-                                   if (callbackArgs.length == 0)
-                                     callbackArgs = [ evt ];
-                                   evt.preventDefault();
-                                   (self[callbackName]).apply(self, callbackArgs);
-                                 }
-                               },
-                               true);
-   },
-
-  handleEvent : function(event) {
-    if (event.type != "PluginClickToPlay")
-      return;
-    let plugin = event.target;
-    PluginHandler.addLinkClickCallback(plugin, "reloadToEnablePlugin");
-  },
-
-  reloadToEnablePlugin: function() {
-    sendAsyncMessage("Browser:PluginClickToPlayClicked", { });
-  }
-};
-
-//PluginCTPHandler.init();
deleted file mode 100644
--- a/browser/metro/base/content/contenthandlers/ViewportHandler.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/* 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/. */
-
-dump("### ViewportHandler.js loaded\n");
-
-// See getViewportMetadata. Blindly copied from Safari
-// documentation for now.
-const kViewportMinScale  = 0;
-const kViewportMaxScale  = 10;
-const kViewportMinWidth  = 200;
-const kViewportMaxWidth  = 10000;
-const kViewportMinHeight = 223;
-const kViewportMaxHeight = 10000;
-
-/*
- * ViewportHandler
- *
- * Plucks zoom info out of web page metadata and forwards it to the
- * browser.
- */ 
-
-let ViewportHandler = {
-  init: function init() {
-    addEventListener("DOMWindowCreated", this, false);
-    addEventListener("DOMMetaAdded", this, false);
-    addEventListener("DOMContentLoaded", this, false);
-    addEventListener("pageshow", this, false);
-  },
-
-  handleEvent: function handleEvent(aEvent) {
-    let target = aEvent.originalTarget;
-    let isRootDocument = (target == content.document || target.ownerDocument == content.document);
-    if (!isRootDocument)
-      return;
-
-    switch (aEvent.type) {
-      case "DOMWindowCreated":
-        this.resetMetadata();
-        break;
-
-      case "DOMMetaAdded":
-        if (target.name == "viewport")
-          this.updateMetadata();
-        break;
-
-      case "DOMContentLoaded":
-      case "pageshow":
-        this.updateMetadata();
-        break;
-    }
-  },
-
-  resetMetadata: function resetMetadata() {
-    sendAsyncMessage("Browser:ViewportMetadata", null);
-  },
-
-  updateMetadata: function updateMetadata() {
-    sendAsyncMessage("Browser:ViewportMetadata", this.getViewportMetadata());
-  },
-
-  /**
-   * Returns an object with the page's preferred viewport properties:
-   *   defaultZoom (optional float): The initial scale when the page is loaded.
-   *   minZoom (optional float): The minimum zoom level.
-   *   maxZoom (optional float): The maximum zoom level.
-   *   width (optional int): The CSS viewport width in px.
-   *   height (optional int): The CSS viewport height in px.
-   *   autoSize (boolean): Resize the CSS viewport when the window resizes.
-   *   allowZoom (boolean): Let the user zoom in or out.
-   *   autoScale (boolean): Adjust the viewport properties to account for display density.
-   */
-  getViewportMetadata: function getViewportMetadata() {
-    let doctype = content.document.doctype;
-    if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))
-      return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
-
-    let windowUtils = Util.getWindowUtils(content);
-    let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
-    if (handheldFriendly == "true")
-      return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true };
-
-    if (content.document instanceof XULDocument)
-      return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
-
-    // HACK: Since we can't set the scale in local tabs (bug 597081), we force
-    // them to device-width and scale=1 so they will lay out reasonably.
-    if (Util.isLocalScheme(content.document.documentURI))
-      return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
-
-    // HACK: Since we can't set the scale correctly in frameset pages yet (bug 645756), we force
-    // them to device-width and scale=1 so they will lay out reasonably.
-    if (content.frames.length > 0 && (content.document.body instanceof HTMLFrameSetElement))
-      return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false };
-
-    // viewport details found here
-    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html
-    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html
-
-    // Note: These values will be NaN if parseFloat or parseInt doesn't find a number.
-    // Remember that NaN is contagious: Math.max(1, NaN) == Math.min(1, NaN) == NaN.
-    let scale = parseFloat(windowUtils.getDocumentMetadata("viewport-initial-scale"));
-    let minScale = parseFloat(windowUtils.getDocumentMetadata("viewport-minimum-scale"));
-    let maxScale = parseFloat(windowUtils.getDocumentMetadata("viewport-maximum-scale"));
-
-    let widthStr = windowUtils.getDocumentMetadata("viewport-width");
-    let heightStr = windowUtils.getDocumentMetadata("viewport-height");
-    let width = Util.clamp(parseInt(widthStr), kViewportMinWidth, kViewportMaxWidth);
-    let height = Util.clamp(parseInt(heightStr), kViewportMinHeight, kViewportMaxHeight);
-
-    let allowZoomStr = windowUtils.getDocumentMetadata("viewport-user-scalable");
-    let allowZoom = !/^(0|no|false)$/.test(allowZoomStr); // WebKit allows 0, "no", or "false"
-
-    scale = Util.clamp(scale, kViewportMinScale, kViewportMaxScale);
-    minScale = Util.clamp(minScale, kViewportMinScale, kViewportMaxScale);
-    maxScale = Util.clamp(maxScale, kViewportMinScale, kViewportMaxScale);
-
-    // If initial scale is 1.0 and width is not set, assume width=device-width
-    let autoSize = (widthStr == "device-width" ||
-                    (!widthStr && (heightStr == "device-height" || scale == 1.0)));
-
-    return {
-      defaultZoom: scale,
-      minZoom: minScale,
-      maxZoom: maxScale,
-      width: width,
-      height: height,
-      autoSize: autoSize,
-      allowZoom: allowZoom,
-      autoScale: true
-    };
-  }
-};
-
-ViewportHandler.init();
-
--- a/browser/metro/base/content/input.js
+++ b/browser/metro/base/content/input.js
@@ -966,85 +966,25 @@ KineticController.prototype = {
         this.end();
     }
 
     this.momentumBuffer.push({'t': now, 'dx' : dx, 'dy' : dy});
   }
 };
 
 
-/**
- * Input module for basic scrollwheel input.  Currently just zooms the browser
- * view accordingly.
- */
-var ScrollwheelModule = {
-  _pendingEvent : 0,
-  _container: null,
-  
-  init: function init(container) {
-    this._container = container;
-    window.addEventListener("MozPrecisePointer", this, true);
-    window.addEventListener("MozImprecisePointer", this, true);
-  },
-
-  handleEvent: function handleEvent(aEvent) {
-    switch(aEvent.type) {
-      case "DOMMouseScroll":
-      case "MozMousePixelScroll":
-        this._onScroll(aEvent);
-        break;
-      case "MozPrecisePointer":
-        this._container.removeEventListener("DOMMouseScroll", this, true);
-        this._container.removeEventListener("MozMousePixelScroll", this, true);
-        break;
-      case "MozImprecisePointer":
-        this._container.addEventListener("DOMMouseScroll", this, true);
-        this._container.addEventListener("MozMousePixelScroll", this, true);
-        break;
-    };
-  },
-
-  _onScroll: function _onScroll(aEvent) {
-    // If events come too fast we don't want their handling to lag the
-    // zoom in/zoom out execution. With the timeout the zoom is executed
-    // as we scroll.
-    if (this._pendingEvent)
-      clearTimeout(this._pendingEvent);
-
-    this._pendingEvent = setTimeout(function handleEventImpl(self) {
-      self._pendingEvent = 0;
-      Browser.zoom(aEvent.detail);
-    }, 0, this);
-
-    aEvent.stopPropagation();
-    aEvent.preventDefault();
-  },
-
-  /* We don't have much state to reset if we lose event focus */
-  cancelPending: function cancelPending() {}
-};
-
-
 /*
  * Simple gestures support
  */
 
 var GestureModule = {
   _debugEvents: false,
 
   init: function init() {
     window.addEventListener("MozSwipeGesture", this, true);
-    window.addEventListener("CancelTouchSequence", this, true);
-  },
-
-  _initMouseEventFromGestureEvent: function _initMouseEventFromGestureEvent(aDestEvent, aSrcEvent, aType, aCanBubble, aCancellable) {
-    aDestEvent.initMouseEvent(aType, aCanBubble, aCancellable, window, null,
-                              aSrcEvent.screenX, aSrcEvent.screenY, aSrcEvent.clientX, aSrcEvent.clientY,
-                              aSrcEvent.ctrlKey, aSrcEvent.altKey, aSrcEvent.shiftKey, aSrcEvent.metaKey,
-                              aSrcEvent.button, aSrcEvent.relatedTarget);
   },
 
   /*
    * Events
    *
    * Dispatch events based on the type of mouse gesture event. For now, make
    * sure to stop propagation of every gesture event so that web content cannot
    * receive gesture events.
@@ -1060,139 +1000,40 @@ var GestureModule = {
       switch (aEvent.type) {
         case "MozSwipeGesture":
           if (this._onSwipe(aEvent)) {
             let event = document.createEvent("Events");
             event.initEvent("CancelTouchSequence", true, true);
             aEvent.target.dispatchEvent(event);
           }
           break;
-
-        case "CancelTouchSequence":
-          this.cancelPending();
-          break;
       }
     } catch (e) {
       Util.dumpLn("Error while handling gesture event", aEvent.type,
                   "\nPlease report error at:", e);
       Cu.reportError(e);
     }
   },
 
-  /*
-   * Event handlers
-   */
-
-  cancelPending: function cancelPending() {
-    if (AnimatedZoom.isZooming())
-      AnimatedZoom.finish();
-  },
-
   _onSwipe: function _onSwipe(aEvent) {
     switch (aEvent.direction) {
       case Ci.nsIDOMSimpleGestureEvent.DIRECTION_LEFT:
         return this._tryCommand("cmd_forward");
       case Ci.nsIDOMSimpleGestureEvent.DIRECTION_RIGHT:
         return this._tryCommand("cmd_back");
     }
     return false;
   },
 
   _tryCommand: function _tryCommand(aId) {
      if (document.getElementById(aId).getAttribute("disabled") == "true")
        return false;
      CommandUpdater.doCommand(aId);
      return true;
   },
-
-  _pinchStart: function _pinchStart(aEvent) {
-    if (AnimatedZoom.isZooming())
-      return;
-    // Cancel other touch sequence events, and be courteous by allowing them
-    // to say no.
-    let event = document.createEvent("Events");
-    event.initEvent("CancelTouchSequence", true, true);
-    let success = aEvent.target.dispatchEvent(event);
-
-    if (!success || !Browser.selectedTab.allowZoom)
-      return;
-
-    AnimatedZoom.start();
-    this._pinchDelta = 0;
-
-    //this._ignoreNextUpdate = true; // first update gives useless, huge delta
-
-    // cache gesture limit values
-    this._maxGrowth = Services.prefs.getIntPref("browser.ui.pinch.maxGrowth");
-    this._maxShrink = Services.prefs.getIntPref("browser.ui.pinch.maxShrink");
-    this._scalingFactor = Services.prefs.getIntPref("browser.ui.pinch.scalingFactor");
-
-    // Adjust the client coordinates to be relative to the browser element's top left corner.
-    this._browserBCR = getBrowser().getBoundingClientRect();
-    this._pinchStartX = aEvent.clientX - this._browserBCR.left;
-    this._pinchStartY = aEvent.clientY - this._browserBCR.top;
-  },
-
-  _pinchUpdate: function _pinchUpdate(aEvent) {
-    if (!AnimatedZoom.isZooming() || !aEvent.delta)
-      return;
-
-    let delta = 0;
-    let browser = AnimatedZoom.browser;
-    let oldScale = browser.scale;
-    let bcr = this._browserBCR;
-
-    // Accumulate pinch delta. Small changes are just jitter.
-    this._pinchDelta += aEvent.delta;
-    if (Math.abs(this._pinchDelta) >= oldScale) {
-      delta = this._pinchDelta;
-      this._pinchDelta = 0;
-    }
-
-    // decrease the pinchDelta min/max values to limit zooming out/in speed
-    delta = Util.clamp(delta, -this._maxShrink, this._maxGrowth);
-
-    let newScale = Browser.selectedTab.clampZoomLevel(oldScale * (1 + delta / this._scalingFactor));
-    let startScale = AnimatedZoom.startScale;
-    let scaleRatio = startScale / newScale;
-    let cX = aEvent.clientX - bcr.left;
-    let cY = aEvent.clientY - bcr.top;
-
-    // Calculate the new zoom rect.
-    let rect = AnimatedZoom.zoomFrom.clone();
-    rect.translate(this._pinchStartX - cX + (1-scaleRatio) * cX * rect.width / bcr.width,
-                   this._pinchStartY - cY + (1-scaleRatio) * cY * rect.height / bcr.height);
-
-    rect.width *= scaleRatio;
-    rect.height *= scaleRatio;
-
-    this.translateInside(rect, new Rect(0, 0, browser.contentDocumentWidth * startScale,
-                                              browser.contentDocumentHeight * startScale));
-
-    // redraw zoom canvas according to new zoom rect
-    AnimatedZoom.updateTo(rect);
-  },
-
-  _pinchEnd: function _pinchEnd(aEvent) {
-    if (AnimatedZoom.isZooming())
-      AnimatedZoom.finish();
-  },
-
-  /**
-   * Ensure r0 is inside r1, if possible. Preserves w, h.
-   * Same as Rect.prototype.translateInside except it aligns the top left
-   * instead of the bottom right if r0 is bigger than r1.
-   */
-  translateInside: function translateInside(r0, r1) {
-    let offsetX = (r0.left < r1.left ? r1.left - r0.left :
-        (r0.right > r1.right ? Math.max(r1.left - r0.left, r1.right - r0.right) : 0));
-    let offsetY = (r0.top < r1.top ? r1.top - r0.top :
-        (r0.bottom > r1.bottom ? Math.max(r1.top - r0.top, r1.bottom - r0.bottom) : 0));
-    return r0.translate(offsetX, offsetY);
-  }
 };
 
 /**
  * Helper to track when the user is using a precise pointing device (pen/mouse)
  * versus an imprecise one (touch).
  */
 var InputSourceHelper = {
   isPrecise: false,
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -41,21 +41,19 @@ chrome.jar:
   content/helperui/SelectHelperUI.js           (content/helperui/SelectHelperUI.js)
   content/helperui/SelectionHelperUI.js        (content/helperui/SelectionHelperUI.js)
   content/helperui/ChromeSelectionHandler.js   (content/helperui/ChromeSelectionHandler.js)
   content/helperui/FormHelperUI.js             (content/helperui/FormHelperUI.js)
   content/helperui/FindHelperUI.js             (content/helperui/FindHelperUI.js)
   content/helperui/ItemPinHelper.js            (content/helperui/ItemPinHelper.js)
 
   content/contenthandlers/ContextMenuHandler.js (content/contenthandlers/ContextMenuHandler.js)
-  content/contenthandlers/PluginCTPHandler.js  (content/contenthandlers/PluginCTPHandler.js)
   content/contenthandlers/SelectionHandler.js  (content/contenthandlers/SelectionHandler.js)
   content/contenthandlers/FormHelper.js        (content/contenthandlers/FormHelper.js)
   content/contenthandlers/FindHandler.js       (content/contenthandlers/FindHandler.js)
-  content/contenthandlers/ViewportHandler.js   (content/contenthandlers/ViewportHandler.js)
   content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
   content/contenthandlers/Content.js           (content/contenthandlers/Content.js)
 
   content/library/SelectionPrototype.js        (content/library/SelectionPrototype.js)
 
   content/ContentAreaObserver.js               (content/ContentAreaObserver.js)
   content/BrowserTouchHandler.js               (content/BrowserTouchHandler.js)
 * content/WebProgress.js                       (content/WebProgress.js)
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -13,20 +13,16 @@ const URI_GENERIC_ICON_DOWNLOAD = "chrom
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DownloadUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() {
   Cu.import("resource:///modules/ContentUtil.jsm");
   return ContentUtil;
 });
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
-                                  "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
 
 // -----------------------------------------------------------------------
 // HelperApp Launcher Dialog
 // -----------------------------------------------------------------------
 
 function HelperAppLauncherDialog() { }
 
 HelperAppLauncherDialog.prototype = {
@@ -133,108 +129,108 @@ HelperAppLauncherDialog.prototype = {
                                                     URI_GENERIC_ICON_DOWNLOAD,
                                                     notificationBox.PRIORITY_WARNING_HIGH,
                                                     buttons);
     let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText");
     messageContainer.appendChild(fragment);
   },
 
   promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
-    return Task.spawn(function() {
-      let file = null;
-      let prefs = Services.prefs;
-
-      if (!aForcePrompt) {
-        // Check to see if the user wishes to auto save to the default download
-        // folder without prompting. Note that preference might not be set.
-        let autodownload = true;
-        try {
-          autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
-        } catch (e) { }
+    let file = null;
+    let prefs = Services.prefs;
 
-        if (autodownload) {
-          // Retrieve the user's default download directory
-          let defaultFolder = yield Downloads.getPreferredDownloadsDirectory();
+    if (!aForcePrompt) {
+      // Check to see if the user wishes to auto save to the default download
+      // folder without prompting. Note that preference might not be set.
+      let autodownload = true;
+      try {
+        autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
+      } catch (e) { }
 
-          try {
-            file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
-          }
-          catch (e) {
-          }
-
-          // Check to make sure we have a valid directory, otherwise, prompt
-          if (file)
-            throw new Task.Result(file);
-        }
-      }
+      if (autodownload) {
+        // Retrieve the user's default download directory
+        let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+        let defaultFolder = dnldMgr.userDownloadsDirectory;
 
-      // Use file picker to show dialog.
-      let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-      let windowTitle = "";
-      let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-      picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave);
-      picker.defaultString = aDefaultFile;
+        try {
+          file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
+        }
+        catch (e) {
+        }
 
-      if (aSuggestedFileExt) {
-        // aSuggestedFileExtension includes the period, so strip it
-        picker.defaultExtension = aSuggestedFileExt.substring(1);
+        // Check to make sure we have a valid directory, otherwise, prompt
+        if (file)
+          return file;
       }
-      else {
-        try {
-          picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension;
-        }
-        catch (e) { }
-      }
+    }
 
-      var wildCardExtension = "*";
-      if (aSuggestedFileExt) {
-        wildCardExtension += aSuggestedFileExt;
-        picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension);
-      }
-
-      picker.appendFilters(Ci.nsIFilePicker.filterAll);
+    // Use file picker to show dialog.
+    let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    let windowTitle = "";
+    let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+    picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave);
+    picker.defaultString = aDefaultFile;
 
-      // Default to lastDir if it is valid, otherwise use the user's default
-      // downloads directory.  userDownloadsDirectory should always return a
-      // valid directory, so we can safely default to it.
-      picker.displayDirectory = yield Downloads.getPreferredDownloadsDirectory();
-
-      // The last directory preference may not exist, which will throw.
+    if (aSuggestedFileExt) {
+      // aSuggestedFileExtension includes the period, so strip it
+      picker.defaultExtension = aSuggestedFileExt.substring(1);
+    }
+    else {
       try {
-        let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
-        if (isUsableDirectory(lastDir))
-          picker.displayDirectory = lastDir;
+        picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension;
       }
       catch (e) { }
+    }
 
-      if (picker.show() == Ci.nsIFilePicker.returnCancel) {
-        // null result means user cancelled.
-        throw new Task.Result(null);
-      }
+    var wildCardExtension = "*";
+    if (aSuggestedFileExt) {
+      wildCardExtension += aSuggestedFileExt;
+      picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension);
+    }
+
+    picker.appendFilters(Ci.nsIFilePicker.filterAll);
 
-      // Be sure to save the directory the user chose through the Save As...
-      // dialog  as the new browser.download.dir since the old one
-      // didn't exist.
-      file = picker.file;
+    // Default to lastDir if it is valid, otherwise use the user's default
+    // downloads directory.  userDownloadsDirectory should always return a
+    // valid directory, so we can safely default to it.
+    var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+    picker.displayDirectory = dnldMgr.userDownloadsDirectory;
+
+    // The last directory preference may not exist, which will throw.
+    try {
+      let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
+      if (isUsableDirectory(lastDir))
+        picker.displayDirectory = lastDir;
+    }
+    catch (e) { }
 
-      if (file) {
-        try {
-          // Remove the file so that it's not there when we ensure non-existence later;
-          // this is safe because for the file to exist, the user would have had to
-          // confirm that he wanted the file overwritten.
-          if (file.exists())
-            file.remove(false);
-        }
-        catch (e) { }
-        var newDir = file.parent.QueryInterface(Ci.nsILocalFile);
-        prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
-        file = this.validateLeafName(newDir, file.leafName, null);
+    if (picker.show() == Ci.nsIFilePicker.returnCancel) {
+      // null result means user cancelled.
+      return null;
+    }
+
+    // Be sure to save the directory the user chose through the Save As...
+    // dialog  as the new browser.download.dir since the old one
+    // didn't exist.
+    file = picker.file;
+
+    if (file) {
+      try {
+        // Remove the file so that it's not there when we ensure non-existence later;
+        // this is safe because for the file to exist, the user would have had to
+        // confirm that he wanted the file overwritten.
+        if (file.exists())
+          file.remove(false);
       }
-      throw new Task.Result(file);
-    }.bind(this));
+      catch (e) { }
+      var newDir = file.parent.QueryInterface(Ci.nsILocalFile);
+      prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
+      file = this.validateLeafName(newDir, file.leafName, null);
+    }
+    return file;
   },
 
   validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) {
     if (!(aLocalFile && this.isUsableDirectory(aLocalFile)))
       return null;
 
     // Remove any leading periods, since we don't want to save hidden files
     // automatically.
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -382,21 +382,16 @@ pref("browser.ui.snapped.maxWidth", 600)
 
 // kinetic tweakables
 pref("browser.ui.kinetic.updateInterval", 16);
 pref("browser.ui.kinetic.exponentialC", 1400);
 pref("browser.ui.kinetic.polynomialC", 100);
 pref("browser.ui.kinetic.swipeLength", 160);
 pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation
 
-// pinch gesture
-pref("browser.ui.pinch.maxGrowth", 150);     // max pinch distance growth
-pref("browser.ui.pinch.maxShrink", 200);     // max pinch distance shrinkage
-pref("browser.ui.pinch.scalingFactor", 500); // scaling factor for above pinch limits
-
 pref("ui.mouse.radius.enabled", true);
 pref("ui.touch.radius.enabled", true);
 
 // plugins
 pref("plugin.disable", true);
 pref("dom.ipc.plugins.enabled", true);
 
 // process priority
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -2063,16 +2063,18 @@ toolbar[mode="text"] toolbarbutton.chevr
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 
+%include downloads/indicator.css
+
 .gcli-panel {
   padding: 0;
 }
 
 .gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
   color: hsl(210,11%,16%);
 }
 
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -6,56 +6,56 @@
 
 #downloads-indicator-anchor {
   /* Makes the outermost stack element positioned, so that its contents are
      rendered over the main browser window in the Z order.  This is required by
      the animated event notification. */
   position: relative;
 }
 
-toolbar[iconsize="small"] > #downloads-indicator > #downloads-indicator-anchor {
+toolbar[iconsize="small"] > #downloads-button > #downloads-indicator-anchor {
   min-width: 16px;
   min-height: 16px;
 }
 
-toolbar[iconsize="large"] > #downloads-indicator > #downloads-indicator-anchor {
+toolbar[iconsize="large"] > #downloads-button > #downloads-indicator-anchor {
   min-width: 24px;
   min-height: 24px;
 }
 
 /*** Main indicator icon ***/
 
-toolbar[iconsize="small"] > #downloads-indicator > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[iconsize="small"] > #downloads-button > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
                               0, 16, 16, 0) center no-repeat;
 }
 
-toolbar[iconsize="large"] > #downloads-indicator > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[iconsize="large"] > #downloads-button > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 24, 24, 0) center no-repeat;
 }
 
-toolbar[iconsize="small"] > #downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[iconsize="small"] > #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: url("chrome://browser/skin/downloads/download-glow-small.png");
 }
 
-toolbar[iconsize="large"] > #downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[iconsize="large"] > #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
-#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
                               0, 16, 16, 0) center no-repeat;
   background-size: 12px;
 }
 
-#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
@@ -70,33 +70,33 @@ toolbar[iconsize="large"] > #downloads-i
 }
 
 @keyframes downloadsIndicatorNotificationStartLeft {
   from { opacity: 0; transform: translate(128px, 128px) scale(8); }
   20%  { opacity: .85; animation-timing-function: ease-out; }
   to   { opacity: 0; transform: translate(0) scale(1); }
 }
 
-#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
   animation-name: downloadsIndicatorNotificationStartRight;
   animation-duration: 1s;
 }
 
-#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
   animation-name: downloadsIndicatorNotificationStartLeft;
 }
 
 @keyframes downloadsIndicatorNotificationFinish {
   from { opacity: 0; transform: scale(1); }
   20%  { opacity: .65; animation-timing-function: ease-in; }
   to   { opacity: 0; transform: scale(8); }
 }
 
-#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
   animation-name: downloadsIndicatorNotificationFinish;
   animation-duration: 1s;
 }
 
 /*** Progress bar and text ***/
 
 #downloads-indicator-counter {
@@ -139,20 +139,20 @@ toolbar[iconsize="large"] > #downloads-i
   min-height: 0;
   background-image: linear-gradient(#505050, #575757);
   border: 1px solid;
   border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
   -moz-border-start: none;
   border-radius: 0 2px 2px 0;
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
   background-color: rgb(220, 230, 81);
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
   background-image: linear-gradient(#4b5000, #515700);
 }
 
-toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+toolbar[mode="full"] > #downloads-button > .toolbarbutton-text {
   margin: 0;
   text-align: center;
 }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -65,17 +65,16 @@ browser.jar:
   skin/classic/browser/downloads/buttons.png          (downloads/buttons.png)
   skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
   skin/classic/browser/downloads/download-glow.png    (downloads/download-glow.png)
   skin/classic/browser/downloads/download-glow-small.png (downloads/download-glow-small.png)
   skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
   skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
-  skin/classic/browser/downloads/indicator.css        (downloads/indicator.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3659,16 +3659,18 @@ toolbarbutton.chevron > .toolbarbutton-m
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 
+%include downloads/indicator.css
+
 /* On mac, the popup notification contents are indented by default and so
   the default closebutton margins from notification.css require adjustment */
 
 .click-to-play-plugins-notification-description-box > .popup-notification-closebutton {
   -moz-margin-end: -6px;
   margin-top: -7px;
 }
 
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -17,53 +17,53 @@
 
 /*** Main indicator icon ***/
 
 #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 140, 20, 120) center no-repeat;
 }
 
-#downloads-indicator[attention]
+#downloads-button[attention]
 #downloads-indicator-icon {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
-#downloads-indicator:not([counter])
+#downloads-button:not([counter])
 #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 140, 20, 120) center no-repeat;
   background-size: 12px;
 }
 
-#downloads-indicator:not([counter])[attention]
+#downloads-button:not([counter])[attention]
 #downloads-indicator-counter {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 @media (min-resolution: 2dppx) {
   #downloads-indicator-icon:not(:-moz-lwtheme-brighttext) {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 280, 40, 240);
     background-size: 20px;
   }
 
-  #downloads-indicator:not([counter]) > #downloads-indicator-anchor >
+  #downloads-button:not([counter]) > #downloads-indicator-anchor >
   #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 280, 40, 240);
   }
 
-  #downloads-indicator[attention] > #downloads-indicator-anchor >
+  #downloads-button[attention] > #downloads-indicator-anchor >
   #downloads-indicator-icon {
     background-image: url("chrome://browser/skin/downloads/download-glow@2x.png");
   }
 
-  #downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor >
+  #downloads-button:not([counter])[attention] > #downloads-indicator-anchor >
   #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: url("chrome://browser/skin/downloads/download-glow@2x.png");
   }
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
@@ -80,46 +80,46 @@
 }
 
 @keyframes downloadsIndicatorNotificationStartLeft {
   from { opacity: 0; transform: translate(128px, 128px) scale(8); }
   20%  { opacity: .85; animation-timing-function: ease-out; }
   to   { opacity: 0; transform: translate(0) scale(1); }
 }
 
-#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
   animation-name: downloadsIndicatorNotificationStartRight;
   animation-duration: 1s;
 }
 
 @media (min-resolution: 2dppx) {
-  #downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+  #downloads-button[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
     background-image: url("chrome://browser/skin/downloads/download-notification-start@2x.png");
   }
 }
 
-#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
   animation-name: downloadsIndicatorNotificationStartLeft;
 }
 
 @keyframes downloadsIndicatorNotificationFinish {
   from { opacity: 0; transform: scale(1); }
   20%  { opacity: .65; animation-timing-function: ease-in; }
   to   { opacity: 0; transform: scale(8); }
 }
 
-#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
   animation-name: downloadsIndicatorNotificationFinish;
   animation-duration: 1s;
 }
 
 @media (min-resolution: 2dppx) {
-  #downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+  #downloads-button[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
     background-image: url("chrome://browser/skin/downloads/download-notification-finish@2x.png");
   }
 }
 
 /*** Progress bar and text ***/
 
 #downloads-indicator-counter {
   height: 9px;
@@ -161,22 +161,22 @@
   min-height: 0;
   background-image: linear-gradient(#505050, #575757);
   border: 1px solid;
   border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
   -moz-border-start: none;
   border-radius: 0 2px 2px 0;
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
   background-color: rgb(220, 230, 81);
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
   background-image: linear-gradient(#4b5000, #515700);
 }
 
-toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+toolbar[mode="full"] > #downloads-button[indicator] > .toolbarbutton-text {
   margin: 2px 0 0;
   padding: 0;
   text-align: center;
   vertical-align: middle;
 }
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -107,17 +107,16 @@ browser.jar:
   skin/classic/browser/downloads/download-glow@2x.png       (downloads/download-glow@2x.png)
   skin/classic/browser/downloads/download-notification-finish.png  (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-finish@2x.png  (downloads/download-notification-finish@2x.png)
   skin/classic/browser/downloads/download-notification-start.png  (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-notification-start@2x.png  (downloads/download-notification-start@2x.png)
   skin/classic/browser/downloads/download-summary.png       (downloads/download-summary.png)
   skin/classic/browser/downloads/download-summary@2x.png    (downloads/download-summary@2x.png)
   skin/classic/browser/downloads/downloads.css              (downloads/downloads.css)
-  skin/classic/browser/downloads/indicator.css              (downloads/indicator.css)
   skin/classic/browser/feeds/subscribe.css                  (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css               (feeds/subscribe-ui.css)
   skin/classic/browser/feeds/feedIcon.png                   (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                 (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png            (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png            (feeds/feedIcon16.png)
--- a/browser/themes/shared/browser.inc
+++ b/browser/themes/shared/browser.inc
@@ -1,3 +1,3 @@
 %filter substitution
 
-%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #tabview-button, #webrtc-status-button
+%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #history-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #tabview-button, #webrtc-status-button
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -5,16 +5,18 @@
 %define WINDOWS_AERO
 %include browser.css
 %undef WINDOWS_AERO
 
 %define customToolbarColor hsl(210,75%,92%)
 %define glassActiveBorderColor rgb(37, 44, 51)
 %define glassInactiveBorderColor rgb(102, 102, 102)
 
+%include downloads/indicator-aero.css
+
 @media not all and (-moz-windows-classic) {
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container {
     margin-top: 1px;
   }
 
   #appmenu-button {
     border-width: 2px;
     -moz-border-left-colors: @appMenuButtonBorderColor@;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2813,16 +2813,18 @@ toolbarbutton.bookmark-item[dragover="tr
   font-size: 120%;
 }
 
 %include ../shared/devtools/responsivedesign.inc.css
 %include ../shared/devtools/highlighter.inc.css
 %include ../shared/devtools/commandline.inc.css
 %include ../shared/plugin-doorhanger.inc.css
 
+%include downloads/indicator.css
+
 /* Error counter */
 
 #developer-toolbar-toolbox-button[error-count]:before {
   color: #FDF3DE;
   min-width: 16px;
   text-shadow: none;
   background-image: linear-gradient(#B4211B, #8A1915);
   border-radius: 1px;
--- a/browser/themes/windows/downloads/indicator-aero.css
+++ b/browser/themes/windows/downloads/indicator-aero.css
@@ -1,37 +1,33 @@
 /* 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/. */
 
-%define WINDOWS_AERO
-%include indicator.css
-%undef WINDOWS_AERO
-
 @media (-moz-windows-compositor) {
   /* The following rules are for the downloads indicator when in its normal,
      non-downloading, non-paused state (ie, it's just showing the downloads
      button icon). */
-  #toolbar-menubar #downloads-indicator:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
-  #TabsToolbar[tabsontop=true] #downloads-indicator:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
-  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-indicator:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
-  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
+  #toolbar-menubar #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
+  #TabsToolbar[tabsontop=true] #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
+  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
+  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon:not(:-moz-lwtheme),
 
   /* The following rules are for the downloads indicator when in its paused
      or undetermined progress state. We use :not([counter]) as a shortcut for
      :-moz-any([progress], [paused]). */
 
   /* This is the case where the downloads indicator has been moved next to the menubar */
-  #toolbar-menubar #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
+  #toolbar-menubar #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
   /* This is the case where the downloads indicator is in the tabstrip toolbar with tabs on top. */
-  #TabsToolbar[tabsontop=true] #downloads-indicator:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
+  #TabsToolbar[tabsontop=true] #downloads-button:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
   /* This is the case where the downloads indicator is anywhere in the nav-bar with tabs on bottom. */
-  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-indicator:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
+  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-button:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter,
   /* This is the case where the downloads indicator is in the tabstrip when the tabstrip is the last item in the toolbox (and is therefore over glass) */
-  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-button:not(:-moz-lwtheme):not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 108, 18, 90);
   }
 
   #toolbar-menubar #downloads-indicator-counter:not(:-moz-lwtheme),
   #TabsToolbar[tabsontop=true] #downloads-indicator-counter:not(:-moz-lwtheme),
   #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-indicator-counter:not(:-moz-lwtheme),
   #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator-counter:not(:-moz-lwtheme) {
     color: white;
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -20,30 +20,30 @@
   min-height: 18px;
 }
 
 #downloads-indicator-icon:-moz-lwtheme-brighttext {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
                               0, 108, 18, 90) center no-repeat;
 }
 
-#downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
-#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 108, 18, 90) center no-repeat;
   background-size: 12px;
 }
 
-#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: url("chrome://browser/skin/downloads/download-glow.png");
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
@@ -58,33 +58,33 @@
 }
 
 @keyframes downloadsIndicatorNotificationStartLeft {
   from { opacity: 0; transform: translate(128px, 128px) scale(8); }
   20%  { opacity: .85; animation-timing-function: ease-out; }
   to   { opacity: 0; transform: translate(0) scale(1); }
 }
 
-#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
   animation-name: downloadsIndicatorNotificationStartRight;
   animation-duration: 1s;
 }
 
-#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
   animation-name: downloadsIndicatorNotificationStartLeft;
 }
 
 @keyframes downloadsIndicatorNotificationFinish {
   from { opacity: 0; transform: scale(1); }
   20%  { opacity: .65; animation-timing-function: ease-in; }
   to   { opacity: 0; transform: scale(8); }
 }
 
-#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+#downloads-button[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
   animation-name: downloadsIndicatorNotificationFinish;
   animation-duration: 1s;
 }
 
 /*** Progress bar and text ***/
 
 #downloads-indicator-counter {
@@ -133,20 +133,20 @@
   min-height: 0;
   background-image: linear-gradient(#505050, #575757);
   border: 1px solid;
   border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
   -moz-border-start: none;
   border-radius: 0 2px 2px 0;
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
   background-color: rgb(220, 230, 81);
 }
 
-#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
   background-image: linear-gradient(#4b5000, #515700);
 }
 
-toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+toolbar[mode="full"] > #downloads-button[indicator] > .toolbarbutton-text {
   margin: 0;
   text-align: center;
 }
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -82,17 +82,16 @@ browser.jar:
 *       skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
         skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
         skin/classic/browser/downloads/contentAreaDownloadsView.css  (downloads/contentAreaDownloadsView.css)
         skin/classic/browser/downloads/download-glow.png             (downloads/download-glow.png)
         skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
         skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
         skin/classic/browser/downloads/download-summary.png          (downloads/download-summary.png)
 *       skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
-        skin/classic/browser/downloads/indicator.css                 (downloads/indicator.css)
         skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/audioFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/audioFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/videoFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/videoFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/subscribe.css                     (feeds/subscribe.css)
         skin/classic/browser/feeds/subscribe-ui.css                  (feeds/subscribe-ui.css)
@@ -357,17 +356,16 @@ browser.jar:
 *       skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css)
         skin/classic/aero/browser/downloads/buttons.png              (downloads/buttons-aero.png)
         skin/classic/aero/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
         skin/classic/aero/browser/downloads/download-glow.png        (downloads/download-glow.png)
         skin/classic/aero/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
         skin/classic/aero/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
         skin/classic/aero/browser/downloads/download-summary.png     (downloads/download-summary.png)
 *       skin/classic/aero/browser/downloads/downloads.css            (downloads/downloads-aero.css)
-*       skin/classic/aero/browser/downloads/indicator.css            (downloads/indicator-aero.css)
         skin/classic/aero/browser/feeds/feedIcon.png                 (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/feedIcon16.png               (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/subscribe.css                (feeds/subscribe.css)
         skin/classic/aero/browser/feeds/subscribe-ui.css             (feeds/subscribe-ui.css)
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -849,30 +849,48 @@ public class LocalBrowserDB implements B
         } finally {
             if (c != null)
                 c.close();
         }
 
         return b;
     }
 
+    /**
+     * Query for non-null thumbnails matching the provided <code>urls</code>.
+     * The returned cursor will have no more than, but possibly fewer than,
+     * the requested number of thumbnails.
+     *
+     * Returns null if the provided list of URLs is empty or null.
+     */
     @Override
     public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
-        StringBuilder selection = new StringBuilder();
-        String[] selectionArgs = new String[urls.size()];
+        if (urls == null) {
+            return null;
+        }
 
-        for (int i = 0; i < urls.size(); i++) {
-          final String url = urls.get(i);
+        int urlCount = urls.size();
+        if (urlCount == 0) {
+            return null;
+        }
 
-          if (i > 0)
-            selection.append(" OR ");
+        // Don't match against null thumbnails.
+        StringBuilder selection = new StringBuilder(
+                Thumbnails.DATA + " IS NOT NULL AND " +
+                Thumbnails.URL + " IN ("
+        );
 
-          selection.append(Thumbnails.URL + " = ?");
-          selectionArgs[i] = url;
+        // Compute a (?, ?, ?) sequence to match the provided URLs.
+        int i = 1;
+        while (i++ < urlCount) {
+            selection.append("?, ");
         }
+        selection.append("?)");
+
+        String[] selectionArgs = urls.toArray(new String[urlCount]);
 
         return cr.query(mThumbnailsUriWithProfile,
                         new String[] { Thumbnails.URL, Thumbnails.DATA },
                         selection.toString(),
                         selectionArgs,
                         null);
     }
 
--- a/mobile/android/base/home/TopSitesPage.java
+++ b/mobile/android/base/home/TopSitesPage.java
@@ -450,34 +450,31 @@ public class TopSitesPage extends HomeFr
                 }
                 break;
             }
         }
     }
 
     private void updateUiFromCursor(Cursor c) {
         mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
-
-        if (c != null && c.getCount() > 0) {
-            return;
-        }
     }
 
     private static class TopSitesLoader extends SimpleCursorLoader {
         // Max number of search results
         private static final int SEARCH_LIMIT = 30;
         private int mMaxGridEntries;
 
         public TopSitesLoader(Context context) {
             super(context);
             mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
         }
 
         @Override
         public Cursor loadCursor() {
+            Log.d(LOGTAG, "TopSitesLoader.loadCursor()");
             return BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
         }
     }
 
     private class VisitedAdapter extends CursorAdapter {
         public VisitedAdapter(Context context, Cursor cursor) {
             super(context, cursor);
         }
@@ -575,39 +572,62 @@ public class TopSitesPage extends HomeFr
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return new TopSitesGridItemView(context);
         }
     }
 
     private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            Log.d(LOGTAG, "Creating TopSitesLoader: " + id);
             return new TopSitesLoader(getActivity());
         }
 
+        /**
+         * This method is called *twice* in some circumstances.
+         *
+         * If you try to avoid that through some kind of boolean flag,
+         * sometimes (e.g., returning to the activity) you'll *not* be called
+         * twice, and thus you'll never draw thumbnails.
+         *
+         * The root cause is TopSitesLoader.loadCursor being called twice.
+         * Why that is... dunno.
+         */
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
+            Log.d(LOGTAG, "onLoadFinished: " + c.getCount() + " rows.");
+
             mListAdapter.swapCursor(c);
             mGridAdapter.swapCursor(c);
             updateUiFromCursor(c);
 
+            final int col = c.getColumnIndexOrThrow(URLColumns.URL);
+
             // Load the thumbnails.
-            if (c.getCount() > 0 && c.moveToFirst()) {
-                final ArrayList<String> urls = new ArrayList<String>();
-                do {
-                    final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-                    urls.add(url);
-                } while (c.moveToNext());
+            // Even though the cursor we're given is supposed to be fresh,
+            // we get a bad first value unless we reset its position.
+            // Using move(-1) and moveToNext() doesn't work correctly under
+            // rotation, so we use moveToFirst.
+            if (!c.moveToFirst()) {
+                return;
+            }
+            
+            final ArrayList<String> urls = new ArrayList<String>();
+            int i = 1;
+            do {
+                urls.add(c.getString(col));
+            } while (i++ < mMaxGridEntries && c.moveToNext());
 
-                if (urls.size() > 0) {
-                    Bundle bundle = new Bundle();
-                    bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
-                    getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
-                }
+            if (urls.isEmpty()) {
+                return;
             }
+
+            Bundle bundle = new Bundle();
+            bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
+            getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
             if (mListAdapter != null) {
                 mListAdapter.swapCursor(null);
             }
 
@@ -630,39 +650,53 @@ public class TopSitesPage extends HomeFr
         }
 
         @Override
         public Map<String, Thumbnail> loadInBackground() {
             if (mUrls == null || mUrls.size() == 0) {
                 return null;
             }
 
-            final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
-
             // Query the DB for thumbnails.
             final ContentResolver cr = getContext().getContentResolver();
             final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
 
+            if (cursor == null) {
+                return null;
+            }
+
+            final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
+
             try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    do {
-                        // Try to get the thumbnail, if cursor is valid.
-                        String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
-                        final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
-                        final Bitmap bitmap = (b == null ? null : BitmapUtils.decodeByteArray(b));
+                final int urlIndex = cursor.getColumnIndexOrThrow(Thumbnails.URL);
+                final int dataIndex = cursor.getColumnIndexOrThrow(Thumbnails.DATA);
+
+                while (cursor.moveToNext()) {
+                    String url = cursor.getString(urlIndex);
+
+                    // This should never be null, but if it is...
+                    final byte[] b = cursor.getBlob(dataIndex);
+                    if (b == null) {
+                        continue;
+                    }
 
-                        if (bitmap != null) {
-                            thumbnails.put(url, new Thumbnail(bitmap, true));
-                        }
-                    } while (cursor.moveToNext());
+                    final Bitmap bitmap = BitmapUtils.decodeByteArray(b);
+
+                    // Our thumbnails are never null, so if we get a null decoded
+                    // bitmap, it's because we hit an OOM or some other disaster.
+                    // Give up immediately rather than hammering on.
+                    if (bitmap == null) {
+                        Log.w(LOGTAG, "Aborting thumbnail load; decode failed.");
+                        break;
+                    }
+
+                    thumbnails.put(url, new Thumbnail(bitmap, true));
                 }
             } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
+                cursor.close();
             }
 
             // Query the DB for favicons for the urls without thumbnails.
             for (String url : mUrls) {
                 if (!thumbnails.containsKey(url)) {
                     final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
                     if (bitmap != null) {
                         // Favicons.scaleImage can return several different size favicons,
--- a/toolkit/mozapps/update/test/chrome/utils.js
+++ b/toolkit/mozapps/update/test/chrome/utils.js
@@ -183,17 +183,17 @@ var gTestCounter = -1;
 var gWin;
 var gDocElem;
 var gPrefToCheck;
 var gDisableNoUpdateAddon = false;
 
 // Set to true to log additional information for debugging. To log additional
 // information for an individual test set DEBUG_AUS_TEST to true in the test's
 // onload function.
-var DEBUG_AUS_TEST = false;
+var DEBUG_AUS_TEST = true;
 
 #include ../shared.js
 
 /**
  * The current test in TESTS array.
  */
 this.__defineGetter__("gTest", function() {
   return TESTS[gTestCounter];
--- a/toolkit/mozapps/update/test/unit/head_update.js.in
+++ b/toolkit/mozapps/update/test/unit/head_update.js.in
@@ -1159,26 +1159,16 @@ function setupUpdaterTest(aMarFile) {
       }
     }
   });
 
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let afterApplyBinDir = getApplyDirFile("a/b/", true);
   helperBin.copyTo(afterApplyBinDir, gCallbackBinFile);
 
-  if (!gBackgroundUpdate && !gSwitchApp) {
-    let updaterIniContents = "[Strings]\n" +
-                             "Title=Update XPCShell Test\n" +
-                             "Info=Application Update Test - " + TEST_ID + "\n";
-    let updaterIni = updatesDir.clone();
-    updaterIni.append(FILE_UPDATER_INI);
-    writeFile(updaterIni, updaterIniContents);
-    updaterIni.copyTo(afterApplyBinDir, FILE_UPDATER_INI);
-  }
-
   // Copy the mar that will be applied
   let mar = do_get_file(aMarFile);
   mar.copyTo(updatesDir, FILE_UPDATE_ARCHIVE);
 
   // Add the test directory that will be updated for a successful update or left in
   // the initial state for a failed update.
   var testDirs = TEST_DIRS.concat(ADDITIONAL_TEST_DIRS);
   testDirs.forEach(function SUT_TD_FE(aTestDir) {
@@ -2235,16 +2225,18 @@ function adjustGeneralPaths() {
           iid.equals(AUS_Ci.nsISupports))
         return this;
       throw AUS_Cr.NS_ERROR_NO_INTERFACE;
     }
   };
   let ds = Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService);
   ds.registerProvider(dirProvider);
   do_register_cleanup(function() {
+    // Call end_test first before the directory provider is unregistered
+    end_test();
     ds.unregisterProvider(dirProvider);
     let testBin = do_get_file(getApplyDirPath() + "test" + APP_BIN_SUFFIX, true);
     // Try to remove the test.bin file if it exists (it shouldn't).
     if (testBin.exists()) {
       try {
         testBin.remove(false);
       }
       catch (e) {
--- a/toolkit/mozapps/update/test/unit/test_0010_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0010_general.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Service Tests */
 
 const TEST_ID = "0010";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   // Verify write access to the custom app dir
   logTestInfo("testing write access to the application directory");
   removeUpdateDirsAndFiles();
   var testFile = getCurrentProcessDir();
   testFile.append("update_write_access_test");
   testFile.create(AUS_Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
--- a/toolkit/mozapps/update/test/unit/test_0020_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0020_general.js
@@ -7,18 +7,18 @@
 
 const TEST_ID = "0020";
 
 var gNextRunFunc;
 var gExpectedCount;
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   setUpdateChannel("test_channel");
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   standardInit();
--- a/toolkit/mozapps/update/test/unit/test_0030_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0030_general.js
@@ -18,18 +18,18 @@ var gExpectedStatusResult;
 var gIncrementalDownloadClassID, gIncOldFactory;
 
 // gIncrementalDownloadErrorType is used to loop through each of the connection
 // error types in the Mock incremental downloader.
 var gIncrementalDownloadErrorType = 0;
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   standardInit();
--- a/toolkit/mozapps/update/test/unit/test_0040_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0040_general.js
@@ -10,18 +10,18 @@ Components.utils.import("resource://gre/
 const TEST_ID = "0040";
 
 const URL_PREFIX = URL_HOST + URL_PATH + "/";
 
 var gAppInfo;
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   standardInit();
   gAppInfo = AUS_Cc["@mozilla.org/xre/app-info;1"].
              getService(AUS_Ci.nsIXULAppInfo).
--- a/toolkit/mozapps/update/test/unit/test_0050_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0050_general.js
@@ -14,18 +14,18 @@
 const TEST_ID = "0050";
 
 var gNextRunFunc;
 var gExpectedStatusCode;
 var gExpectedStatusText;
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   standardInit();
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   do_execute_soon(run_test_pt1);
--- a/toolkit/mozapps/update/test/unit/test_0060_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0060_manager.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Manager Tests */
 
 const TEST_ID = "0060";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing addition of a successful update to " + FILE_UPDATES_DB +
               " and verification of update properties with the format prior " +
               "to bug 530872");
   removeUpdateDirsAndFiles();
   setUpdateChannel("test_channel");
 
--- a/toolkit/mozapps/update/test/unit/test_0061_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0061_manager.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Manager Tests */
 
 const TEST_ID = "0061";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing removal of an update download in progress for an " +
               "older version of the application on startup (bug 485624)");
   removeUpdateDirsAndFiles();
 
   var patches, updates;
 
--- a/toolkit/mozapps/update/test/unit/test_0062_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0062_manager.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Manager Tests */
 
 const TEST_ID = "0062";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing resuming an update download in progress for the same " +
               "version of the application on startup (bug 485624)");
   removeUpdateDirsAndFiles();
 
   var patches, updates;
 
--- a/toolkit/mozapps/update/test/unit/test_0063_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0063_manager.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Manager Tests */
 
 const TEST_ID = "0063";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing removing an active update for a channel that is not" +
               "valid due to switching channels (bug 486275)");
   removeUpdateDirsAndFiles();
 
   var patches, updates, update;
 
--- a/toolkit/mozapps/update/test/unit/test_0064_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0064_manager.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Manager Tests */
 
 const TEST_ID = "0064";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing removal of an update download in progress for the " +
               "same version of the application with the same application " +
               "build id on startup (bug 536547)");
   removeUpdateDirsAndFiles();
 
   var patches, updates;
--- a/toolkit/mozapps/update/test/unit/test_0070_update_dir_cleanup.js
+++ b/toolkit/mozapps/update/test/unit/test_0070_update_dir_cleanup.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Directory Cleanup Tests */
 
 const TEST_ID = "0070";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
 
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
   var patches = getLocalPatchString(null, null, null, null, null, null,
                                     STATE_PENDING);
   var updates = getLocalUpdateString(patches);
--- a/toolkit/mozapps/update/test/unit/test_0071_update_dir_cleanup.js
+++ b/toolkit/mozapps/update/test/unit/test_0071_update_dir_cleanup.js
@@ -4,18 +4,18 @@
  */
 
 /* General Update Directory Cleanup Tests */
 
 const TEST_ID = "0071";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
 
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
   var patches = getLocalPatchString(null, null, null, null, null, null,
                                     STATE_PENDING);
   var updates = getLocalUpdateString(patches);
--- a/toolkit/mozapps/update/test/unit/test_0072_update_dir_cleanup.js
+++ b/toolkit/mozapps/update/test/unit/test_0072_update_dir_cleanup.js
@@ -3,18 +3,18 @@
  */
 
 /* General Update Directory Cleanup Tests - Bug 539717 */
 
 const TEST_ID = "0072";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
 
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
   var patches = getLocalPatchString(null, null, null, null, null, null,
                                     STATE_DOWNLOADING);
   var updates = getLocalUpdateString(patches);
--- a/toolkit/mozapps/update/test/unit/test_0073_update_dir_cleanup.js
+++ b/toolkit/mozapps/update/test/unit/test_0073_update_dir_cleanup.js
@@ -3,18 +3,18 @@
  */
 
 /* General Update Directory Cleanup Tests - Bug 601701 */
 
 const TEST_ID = "0073";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
 
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
   var patches = getLocalPatchString(null, null, null, null, null, null,
                                     STATE_PENDING);
   var updates = getLocalUpdateString(patches);
--- a/toolkit/mozapps/update/test/unit/test_0080_prompt_silent.js
+++ b/toolkit/mozapps/update/test/unit/test_0080_prompt_silent.js
@@ -7,18 +7,18 @@
  * showUpdateAvailable, and showUpdateError when the app.update.silent
  * preference is true.
  */
 
 const TEST_ID = "0080";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing nsIUpdatePrompt notifications should not be seen " +
               "when the " + PREF_APP_UPDATE_SILENT + " preference is true");
 
   removeUpdateDirsAndFiles();
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
--- a/toolkit/mozapps/update/test/unit/test_0081_prompt_uiAlreadyOpen.js
+++ b/toolkit/mozapps/update/test/unit/test_0081_prompt_uiAlreadyOpen.js
@@ -6,18 +6,18 @@
  * Test that nsIUpdatePrompt doesn't display UI for showUpdateInstalled and
  * showUpdateAvailable when there is already an application update window open.
  */
 
 const TEST_ID = "0081";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing nsIUpdatePrompt notifications should not be seen when " +
               "there is already an application update window open");
 
   removeUpdateDirsAndFiles();
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
--- a/toolkit/mozapps/update/test/unit/test_0082_prompt_unsupportAlreadyNotified.js
+++ b/toolkit/mozapps/update/test/unit/test_0082_prompt_unsupportAlreadyNotified.js
@@ -6,18 +6,18 @@
  * Test that nsIUpdatePrompt doesn't display UI for showUpdateAvailable for an
  * unsupported system update when it has already been shown (bug 843497).
  */
 
 const TEST_ID = "0082";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   standardInit();
   // The HTTP server is only used for the mar file downloads which is slow
--- a/toolkit/mozapps/update/test/unit/test_0110_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0110_general.js
@@ -231,18 +231,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
@@ -281,8 +281,12 @@ function run_test() {
   }
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0111_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0111_general.js
@@ -233,18 +233,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
@@ -282,8 +282,12 @@ function run_test() {
   }
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0112_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0112_general.js
@@ -226,18 +226,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
@@ -278,8 +278,12 @@ function run_test() {
   }
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0113_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0113_general.js
@@ -264,18 +264,18 @@ function removeSymlink() {
 
 function checkSymlink() {
   let args = ["check-symlink", getApplyDirFile().path + "/a/b/link"];
   runHelperProcess(args);
 }
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
@@ -378,8 +378,12 @@ function run_test() {
 
   // Make sure that the intermediate directory has been removed
   let updatedDir = applyToDir.clone();
   updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
   do_check_false(updatedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
+++ b/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
@@ -13,21 +13,20 @@ const TEST_FILES = [];
 
 const VERSION_DOWNGRADE_ERROR = "23";
 
 function run_test() {
   if (!IS_MAR_CHECKS_ENABLED) {
     return;
   }
 
-  // Setup an old version MAR file
-  do_register_cleanup(cleanupUpdaterTest);
-
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
+  // Setup an old version MAR file
   setupUpdaterTest(MAR_OLD_VERSION_FILE);
 
   // Apply the MAR
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a version downgrade MAR");
   // Make sure the updater execution failed.
   // Note that if execv is used, the updater process will turn into the
@@ -35,8 +34,12 @@ function run_test() {
   // app.
   do_check_eq(exitValue, USE_EXECV ? 0 : 1);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
 
   //Make sure we get a version downgrade error
   let updateStatus = readStatusFile(updatesDir);
   do_check_eq(updateStatus.split(": ")[1], VERSION_DOWNGRADE_ERROR);
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0114_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0114_general.js
@@ -233,18 +233,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
@@ -323,8 +323,12 @@ function run_test() {
 
   // Make sure that the intermediate directory has been removed
   let updatedDir = applyToDir.clone();
   updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
   do_check_false(updatedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
+++ b/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
@@ -13,20 +13,20 @@ const TEST_FILES = [];
 
 const MAR_CHANNEL_MISMATCH_ERROR = "22";
 
 function run_test() {
   if (!IS_MAR_CHECKS_ENABLED) {
     return;
   }
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   // Setup a wrong channel MAR file
-  do_register_cleanup(cleanupUpdaterTest);
   setupUpdaterTest(MAR_WRONG_CHANNEL_FILE);
 
   // Apply the MAR
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a wrong product and channel MAR file");
   // Make sure the updater execution failed.
   // Note that if execv is used, the updater process will turn into the
@@ -34,8 +34,12 @@ function run_test() {
   // app.
   do_check_eq(exitValue, USE_EXECV ? 0 : 1);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
 
   //Make sure we get a version downgrade error
   let updateStatus = readStatusFile(updatesDir);
   do_check_eq(updateStatus.split(": ")[1], MAR_CHANNEL_MISMATCH_ERROR);
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0115_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0115_general.js
@@ -225,18 +225,19 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : false
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
-  do_register_cleanup(cleanupUpdaterTest);
+  do_test_pending();
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
@@ -277,9 +278,15 @@ function run_test() {
   }
 
   // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
   toBeDeletedDir = getTargetDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
+
+  do_test_finished();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0150_appBinReplaced_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0150_appBinReplaced_xp_win_complete.js
@@ -187,18 +187,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_WIN_FILE);
 
   gCallbackBinFile = "exe0.exe";
 
   // apply the complete mar
   let exitValue = runUpdate();
@@ -213,8 +213,12 @@ function run_test() {
   checkFilesAfterUpdateSuccess();
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0151_appBinPatched_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0151_appBinPatched_xp_win_partial.js
@@ -189,18 +189,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   gCallbackBinFile = "exe0.exe";
 
   // apply the complete mar
   let exitValue = runUpdate();
@@ -215,8 +215,12 @@ function run_test() {
   checkFilesAfterUpdateSuccess();
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0152_appBinReplaced_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0152_appBinReplaced_xp_win_complete.js
@@ -187,18 +187,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_WIN_FILE);
 
   gCallbackBinFile = "exe0.exe";
 
   // apply the complete mar
@@ -225,8 +225,12 @@ function run_test() {
   checkFilesAfterUpdateSuccess();
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0153_appBinPatched_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0153_appBinPatched_xp_win_partial.js
@@ -189,18 +189,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   gCallbackBinFile = "exe0.exe";
 
   // apply the complete mar
@@ -227,8 +227,12 @@ function run_test() {
   checkFilesAfterUpdateSuccess();
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0160_appInUse_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0160_appInUse_complete.js
@@ -230,18 +230,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch the callback helper application so it is in use during the update
   let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
   callbackApp.permissions = PERMS_DIRECTORY;
   let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20"];
@@ -296,8 +296,13 @@ function checkUpdate() {
   if (IS_WIN) {
     logTestInfo("testing tobedeleted directory doesn't exist");
     let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
     do_check_false(toBeDeletedDir.exists());
   }
 
   checkCallbackAppLog();
 }
+
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_unix_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_unix_complete.js
@@ -230,18 +230,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
@@ -323,8 +323,13 @@ function doUpdate() {
 
   setupHelperFinish();
 }
 
 
 function checkUpdate() {
   checkCallbackAppLog();
 }
+
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_win_complete.js
@@ -180,18 +180,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch the callback helper application so it is in use during the update
   let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
   let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
@@ -237,8 +237,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0162_appInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0162_appInUse_xp_win_complete.js
@@ -180,18 +180,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch the callback helper application so it is in use during the update
   let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
   let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
@@ -234,8 +234,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
@@ -180,18 +180,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
@@ -231,8 +231,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
@@ -181,18 +181,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
@@ -232,8 +232,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
@@ -180,18 +180,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
@@ -245,8 +245,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
@@ -181,18 +181,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
@@ -246,8 +246,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0174_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0174_fileLocked_xp_win_complete.js
@@ -180,18 +180,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
@@ -242,8 +242,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0175_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0175_fileLocked_xp_win_partial.js
@@ -181,18 +181,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
@@ -243,8 +243,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0180_fileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0180_fileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[14].relPathDir +
                                      TEST_FILES[14].fileName);
   let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20"];
@@ -228,8 +228,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_BACKUP_DISCARD);
 
   logTestInfo("testing tobedeleted directory exists");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_true(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0181_fileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0181_fileInUse_xp_win_partial.js
@@ -189,18 +189,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[12].relPathDir +
                                      TEST_FILES[12].fileName);
   let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20"];
@@ -231,8 +231,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_BACKUP_DISCARD);
 
   logTestInfo("testing tobedeleted directory exists");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_true(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0182_rmrfdirFileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0182_rmrfdirFileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[4].relPathDir +
                                      TEST_DIRS[4].subDirs[0] +
                                      TEST_DIRS[4].subDirFiles[0]);
   // Remove the empty file created for the test so the helper application can
@@ -238,8 +238,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_BACKUP_DISCARD);
 
   logTestInfo("testing tobedeleted directory exists");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_true(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0183_rmrfdirFileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0183_rmrfdirFileInUse_xp_win_partial.js
@@ -229,18 +229,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[2].relPathDir +
                                      TEST_DIRS[2].files[0]);
   // Remove the empty file created for the test so the helper application can
   // replace it.
@@ -279,8 +279,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_BACKUP_DISCARD);
 
   logTestInfo("testing tobedeleted directory exists");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_true(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0184_fileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0184_fileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[14].relPathDir +
                                      TEST_FILES[14].fileName);
@@ -242,8 +242,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0185_fileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0185_fileInUse_xp_win_partial.js
@@ -189,18 +189,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[12].relPathDir +
                                      TEST_FILES[12].fileName);
@@ -245,8 +245,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0186_rmrfdirFileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0186_rmrfdirFileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[4].relPathDir +
                                      TEST_DIRS[4].subDirs[0] +
                                      TEST_DIRS[4].subDirFiles[0]);
@@ -251,8 +251,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0187_rmrfdirFileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0187_rmrfdirFileInUse_xp_win_partial.js
@@ -229,18 +229,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[2].relPathDir +
                                      TEST_DIRS[2].files[0]);
   // Remove the empty file created for the test so the helper application can
@@ -292,8 +292,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0188_fileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0188_fileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[14].relPathDir +
                                      TEST_FILES[14].fileName);
@@ -241,8 +241,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0189_fileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0189_fileInUse_xp_win_partial.js
@@ -189,18 +189,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   // Launch an existing file so it is in use during the update
   let fileInUseBin = getApplyDirFile(TEST_FILES[12].relPathDir +
                                      TEST_FILES[12].fileName);
@@ -244,8 +244,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0190_rmrfdirFileInUse_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0190_rmrfdirFileInUse_xp_win_complete.js
@@ -186,18 +186,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[4].relPathDir +
                                      TEST_DIRS[4].subDirs[0] +
                                      TEST_DIRS[4].subDirFiles[0]);
@@ -250,8 +250,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_0191_rmrfdirFileInUse_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0191_rmrfdirFileInUse_xp_win_partial.js
@@ -229,18 +229,18 @@ ADDITIONAL_TEST_DIRS = [
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(cleanupUpdaterTest);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   gBackgroundUpdate = true;
   setupUpdaterTest(MAR_IN_USE_WIN_FILE);
 
   let fileInUseBin = getApplyDirFile(TEST_DIRS[2].relPathDir +
                                      TEST_DIRS[2].files[0]);
   // Remove the empty file created for the test so the helper application can
@@ -291,8 +291,12 @@ function checkUpdate() {
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory does not exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
+
+function end_test() {
+  cleanupUpdaterTest();
+}
--- a/toolkit/mozapps/update/test/unit/test_bug595059.js
+++ b/toolkit/mozapps/update/test/unit/test_bug595059.js
@@ -6,18 +6,18 @@
  * Bug 595059 - begin download of a complete update after a failure applying a
  * partial update.
  */
 
 const TEST_ID = "bug595059";
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("testing Bug 595059 - calling nsIUpdatePrompt::showUpdateError " +
               "should call getNewPrompter and alert on the object returned " +
               "by getNewPrompter when the update.state = " + STATE_FAILED +
               " and the update.errorCode = " + WRITE_ERROR);
 
   removeUpdateDirsAndFiles();
--- a/toolkit/mozapps/update/test/unit/test_bug794211.js
+++ b/toolkit/mozapps/update/test/unit/test_bug794211.js
@@ -7,18 +7,18 @@
 
 const TEST_ID = "bug794211";
 
 // Needs to be in sync w/ nsUpdateService.js
 const NETWORK_ERROR_OFFLINE = 111;
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   logTestInfo("test when an update check fails because the network is " +
               "offline that we check again when the network comes online. " +
               "(Bug 794211)");
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, false);
--- a/toolkit/mozapps/update/test/unit/test_bug833708.js
+++ b/toolkit/mozapps/update/test/unit/test_bug833708.js
@@ -22,19 +22,19 @@ FakeDirProvider.prototype = {
       }
     }
     return null;
   }
 };
 
 function run_test() {
   do_test_pending();
-  do_register_cleanup(end_test);
   DEBUG_AUS_TEST = true;
 
+  // adjustGeneralPaths registers a cleanup function that calls end_test.
   adjustGeneralPaths();
 
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   overrideXHR(xhr_pt1);
   standardInit();
 
   logTestInfo("testing that error codes set from a directory provider propagate" +
--- a/widget/windows/winrt/UIABridge.cpp
+++ b/widget/windows/winrt/UIABridge.cpp
@@ -164,16 +164,36 @@ ChildHasFocus(nsCOMPtr<nsIAccessible>& a
   return (((access->NativeState() & mozilla::a11y::states::EDITABLE) > 0) &&
            ((access->NativeState() & mozilla::a11y::states::READONLY) == 0));
 }
 
 HRESULT
 UIABridge::FocusChangeEvent()
 {
   LogFunction();
+  if (!Connected()) {
+    return UIA_E_ELEMENTNOTAVAILABLE;
+  }
+
+  nsCOMPtr<nsIAccessible> child;
+  mAccessible->GetFocusedChild(getter_AddRefs(child));
+  if (!child) {
+    return S_OK;
+  }
+
+  if (!ChildHasFocus(child)) {
+    ComPtr<IUIAElement> element;
+    gElement.As(&element);
+    if (!element) {
+      return S_OK;
+    }
+    element->ClearFocus();
+    UiaRaiseAutomationEvent(this, UIA_AutomationFocusChangedEventId);
+  }
+
   return S_OK;
 }
 
 // IRawElementProviderFragmentRoot
 
 HRESULT
 UIABridge::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment ** retVal)
 {