Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 02 May 2014 16:57:16 -0400
changeset 181824 a31b425d42f077fc11c8199c895138d874626e64
parent 181823 b607ce15719a45dde8f0cc43f42286255703548d (current diff)
parent 181808 c22612646a5e3651b6530ccadef28715f1bfab91 (diff)
child 181825 de2fb191111871a47d0912835e4ed42e2403bffc
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone32.0a1
Merge m-c to b2g-inbound.
--- a/accessible/tests/mochitest/jsat/dom_helper.js
+++ b/accessible/tests/mochitest/jsat/dom_helper.js
@@ -133,47 +133,51 @@ function testMozAccessFuGesture(aExpecte
  * Reset the thresholds and max delays that affect gesture rejection.
  * @param {Number} aTimeStamp Gesture time stamp.
  * @param {Boolean} aRemoveDwellThreshold An optional flag to reset dwell
  * threshold.
  * @param {Boolean} aRemoveSwipeMaxDuration An optional flag to reset swipe max
  * duration.
  */
 function setTimers(aTimeStamp, aRemoveDwellThreshold, aRemoveSwipeMaxDuration) {
-  GestureSettings.dwellThreshold = originalDwellThreshold;
-  GestureSettings.swipeMaxDuration = originalSwipeMaxDuration;
   if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) {
     return;
   }
   if (aRemoveDwellThreshold) {
     GestureSettings.dwellThreshold = 0;
   }
   if (aRemoveSwipeMaxDuration) {
     GestureSettings.swipeMaxDuration = 0;
   }
   GestureTracker.current.clearTimer();
   GestureTracker.current.startTimer(aTimeStamp);
 }
 
+function resetTimers() {
+  GestureSettings.dwellThreshold = originalDwellThreshold;
+  GestureSettings.swipeMaxDuration = originalSwipeMaxDuration;
+}
+
 /**
  * An extention to AccessFuTest that adds an ability to test a sequence of
  * pointer events and their expected mozAccessFuGesture events.
  * @param {Object} aSequence An object that has a list of pointer events to be
  * generated and the expected mozAccessFuGesture events.
  */
 AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
   AccessFuTest.addFunc(function testSequence() {
     testMozAccessFuGesture(aSequence.expectedGestures);
     var events = aSequence.events;
     function fireEvent(aEvent) {
       var event = {
         points: convertPointCoordinates(aEvent.points),
         type: aEvent.type
       };
       var timeStamp = Date.now();
+      resetTimers();
       GestureTracker.handle(event, timeStamp);
       setTimers(timeStamp, aEvent.removeDwellThreshold,
         aEvent.removeSwipeMaxDuration);
       processEvents();
     }
     function processEvents() {
       if (events.length === 0) {
         return;
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1030,16 +1030,17 @@ let PlacesToolbarHelper = {
 //// BookmarkingUI
 
 /**
  * Handles the bookmarks menu-button in the toolbar.
  */
 
 let BookmarkingUI = {
   BOOKMARK_BUTTON_ID: "bookmarks-menu-button",
+  BOOKMARK_BUTTON_SHORTCUT: "addBookmarkAsKb",
   get button() {
     delete this.button;
     let widgetGroup = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID);
     return this.button = widgetGroup.forWindow(window).node;
   },
 
   /* Can't make this a self-deleting getter because it's anonymous content
    * and might lose/regain bindings at some point. */
@@ -1091,24 +1092,32 @@ let BookmarkingUI = {
     return this.button.hasAttribute("starred") ? this.STATUS_STARRED
                                                : this.STATUS_UNSTARRED;
   },
 
   get _starredTooltip()
   {
     delete this._starredTooltip;
     return this._starredTooltip =
-      gNavigatorBundle.getString("starButtonOn.tooltip");
+      this._getFormattedTooltip("starButtonOn.tooltip2");
   },
 
   get _unstarredTooltip()
   {
     delete this._unstarredTooltip;
     return this._unstarredTooltip =
-      gNavigatorBundle.getString("starButtonOff.tooltip");
+      this._getFormattedTooltip("starButtonOff.tooltip2");
+  },
+
+  _getFormattedTooltip: function(strId) {
+    let args = [];
+    let shortcut = document.getElementById(this.BOOKMARK_BUTTON_SHORTCUT);
+    if (shortcut)
+      args.push(ShortcutUtils.prettifyShortcut(shortcut));
+    return gNavigatorBundle.getFormattedString(strId, args);
   },
 
   /**
    * The type of the area in which the button is currently located.
    * When in the panel, we don't update the button's icon.
    */
   _currentAreaType: null,
   _shouldUpdateStarState: function() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1201,18 +1201,16 @@ var gBrowserInit = {
         return;
       }
 
       // Enable the Restore Last Session command if needed
       RestoreLastSessionObserver.init();
 
       SocialUI.init();
       TabView.init();
-
-      setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
     });
     this.delayedStartupFinished = true;
 
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
     TelemetryTimestamps.add("delayedStartupFinished");
   },
 
   // Returns the URI(s) to load at startup.
@@ -4781,31 +4779,37 @@ var gHomeButton = {
 };
 
 const nodeToTooltipMap = {
   "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
 #ifdef XP_MACOSX
   "print-button": "printButton.tooltip",
 #endif
   "new-window-button": "newWindowButton.tooltip",
+  "new-tab-button": "newTabButton.tooltip",
+  "tabs-newtab-button": "newTabButton.tooltip",
   "fullscreen-button": "fullscreenButton.tooltip",
   "tabview-button": "tabviewButton.tooltip",
+  "downloads-button": "downloads.tooltip",
 };
 const nodeToShortcutMap = {
   "bookmarks-menu-button": "manBookmarkKb",
 #ifdef XP_MACOSX
   "print-button": "printKb",
 #endif
   "new-window-button": "key_newNavigator",
+  "new-tab-button": "key_newNavigatorTab",
+  "tabs-newtab-button": "key_newNavigatorTab",
   "fullscreen-button": "key_fullScreen",
   "tabview-button": "key_tabview",
+  "downloads-button": "key_openDownloads"
 };
 const gDynamicTooltipCache = new Map();
 function UpdateDynamicShortcutTooltipText(aTooltip) {
-  let nodeId = aTooltip.triggerNode.id;
+  let nodeId = aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
   if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
     let strId = nodeToTooltipMap[nodeId];
     let args = [];
     if (nodeId in nodeToShortcutMap) {
       let shortcutId = nodeToShortcutMap[nodeId];
       let shortcut = document.getElementById(shortcutId);
       if (shortcut) {
         args.push(ShortcutUtils.prettifyShortcut(shortcut));
@@ -7173,33 +7177,16 @@ var MousePosTracker = {
 
 function focusNextFrame(event) {
   let fm = Services.focus;
   let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC;
   let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY);
   if (element.ownerDocument == document)
     focusAndSelectUrlBar();
 }
-let BrowserChromeTest = {
-  _cb: null,
-  _ready: false,
-  markAsReady: function () {
-    this._ready = true;
-    if (this._cb) {
-      this._cb();
-      this._cb = null;
-    }
-  },
-  runWhenReady: function (cb) {
-    if (this._ready)
-      cb();
-    else
-      this._cb = cb;
-  }
-};
 
 function BrowserOpenNewTabOrWindow(event) {
   if (event.shiftKey) {
     OpenBrowserWindow();
   } else {
     BrowserOpenTab();
   }
 }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -572,17 +572,17 @@
         <tab class="tabbrowser-tab" selected="true" fadein="true"/>
       </tabs>
 
       <toolbarbutton id="new-tab-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&tabCmd.label;"
                      command="cmd_newNavigatorTab"
                      onclick="checkForMiddleClick(this, event);"
-                     tooltiptext="&newTabButton.tooltip;"
+                     tooltip="dynamic-shortcut-tooltip"
                      ondrop="newTabButtonObserver.onDrop(event)"
                      ondragover="newTabButtonObserver.onDragOver(event)"
                      ondragenter="newTabButtonObserver.onDragOver(event)"
                      ondragexit="newTabButtonObserver.onDragExit(event)"
                      cui-areatype="toolbar"
                      removable="true"/>
 
       <toolbarbutton id="alltabs-button"
@@ -869,17 +869,17 @@
         <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);"
                        label="&downloads.label;"
                        removable="true"
                        cui-areatype="toolbar"
-                       tooltiptext="&downloads.tooltip;"/>
+                       tooltip="dynamic-shortcut-tooltip"/>
 
         <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                        persist="class" removable="true"
                        label="&homeButton.label;"
                        ondragover="homeButtonObserver.onDragOver(event)"
                        ondragenter="homeButtonObserver.onDragOver(event)"
                        ondrop="homeButtonObserver.onDrop(event)"
                        ondragexit="homeButtonObserver.onDragExit(event)"
@@ -999,21 +999,21 @@
 
     <toolbarpalette id="BrowserToolbarPalette">
 
 # Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding
 # or removing default items with the toolbarbutton-1 class.
 
       <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
 #ifdef XP_MACOSX
-                     command="cmd_print" tooltip="dynamic-shortcut-tooltip"
+                     command="cmd_print"
 #else
-                     command="cmd_printPreview" tooltiptext="&printButton.tooltip;"
+                     command="cmd_printPreview"
 #endif
-                     label="&printButton.label;"/>
+                     tooltip="dynamic-shortcut-tooltip" label="&printButton.label;"/>
 
 
       <toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&newNavigatorCmd.label;"
                      command="key_newNavigator"
                      tooltip="dynamic-shortcut-tooltip"
                      ondrop="newWindowButtonObserver.onDrop(event)"
                      ondragover="newWindowButtonObserver.onDragOver(event)"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3364,21 +3364,22 @@
                           class="tabbrowser-arrowscrollbox">
 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
 # right of the newtab button.
         <children includes="tab"/>
 # This is to ensure anything extensions put here will go before the newtab
 # button, necessary due to the previous hack.
         <children/>
         <xul:toolbarbutton class="tabs-newtab-button"
+                           anonid="tabs-newtab-button"
                            command="cmd_newNavigatorTab"
                            onclick="checkForMiddleClick(this, event);"
                            onmouseover="document.getBindingParent(this)._enterNewTab();"
                            onmouseout="document.getBindingParent(this)._leaveNewTab();"
-                           tooltiptext="&newTabButton.tooltip;"/>
+                           tooltip="dynamic-shortcut-tooltip"/>
         <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
                     style="width: 0;"/>
       </xul:arrowscrollbox>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor>
         <![CDATA[
--- a/browser/components/privatebrowsing/test/browser/head.js
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -1,38 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   let gotLoad = false;
-  let gotActivate = (Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager).activeWindow == win);
+  let gotActivate = Services.focus.activeWindow == win;
 
   function maybeRunCallback() {
     if (gotLoad && gotActivate) {
-      win.BrowserChromeTest.runWhenReady(function() {
-        executeSoon(function() { aCallback(win); });
-      });
+      executeSoon(function() { aCallback(win); });
     }
   }
 
   if (!gotActivate) {
     win.addEventListener("activate", function onActivate() {
       info("Got activate.");
       win.removeEventListener("activate", onActivate, false);
       gotActivate = true;
       maybeRunCallback();
     }, false);
   } else {
     info("Was activated.");
   }
 
-  win.addEventListener("load", function onLoad() {
-    info("Got load");
-    win.removeEventListener("load", onLoad, false);
-    gotLoad = true;
-    maybeRunCallback();
-  }, false);
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (win == aSubject) {
+      info("Delayed startup finished");
+      Services.obs.removeObserver(observer, aTopic);
+      gotLoad = true;
+      maybeRunCallback();
+    }
+  }, "browser-delayed-startup-finished", false);
+
   return win;
 }
 
 function openWindow(aParent, aOptions, a3) {
   let { Promise: { defer } } = Components.utils.import("resource://gre/modules/Promise.jsm", {});
   let { promise, resolve } = defer();
 
   let win = aParent.OpenBrowserWindow(aOptions);
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -1,41 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   let gotLoad = false;
-  let gotActivate = (Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager).activeWindow == win);
+  let gotActivate = Services.focus.activeWindow == win;
 
   function maybeRunCallback() {
     if (gotLoad && gotActivate) {
-      win.BrowserChromeTest.runWhenReady(function() {
-        executeSoon(function() { aCallback(win); });
-      });
+      executeSoon(function() { aCallback(win); });
     }
   }
 
   if (!gotActivate) {
     win.addEventListener("activate", function onActivate() {
       info("Got activate.");
       win.removeEventListener("activate", onActivate, false);
       gotActivate = true;
       maybeRunCallback();
     }, false);
   } else {
     info("Was activated.");
   }
 
-  win.addEventListener("load", function onLoad() {
-    info("Got load");
-    win.removeEventListener("load", onLoad, false);
-    gotLoad = true;
-    maybeRunCallback();
-  }, false);
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (win == aSubject) {
+      info("Delayed startup finished");
+      Services.obs.removeObserver(observer, aTopic);
+      gotLoad = true;
+      maybeRunCallback();
+    }
+  }, "browser-delayed-startup-finished", false);
+
   return win;
 }
 
 /**
  * Recursively compare two objects and check that every property of expectedObj has the same value
  * on actualObj.
  */
 function isSubObjectOf(expectedObj, actualObj, name) {
--- a/browser/devtools/debugger/test/browser_dbg_clean-exit-window.js
+++ b/browser/devtools/debugger/test/browser_dbg_clean-exit-window.js
@@ -34,31 +34,28 @@ function testCleanExit() {
 
   is(Services.wm.getMostRecentWindow("navigator:browser"), gWindow,
     "The second window is on top.");
 
   let isActive = promise.defer();
   let isLoaded = promise.defer();
 
   promise.all([isActive.promise, isLoaded.promise]).then(() => {
-    gWindow.BrowserChromeTest.runWhenReady(() => {
-      waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
-        is(gDebugger.gThreadClient.paused, true,
-          "Should be paused after the debugger statement.");
-        gWindow.close();
-        deferred.resolve();
-        finish();
-      });
+    waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
+      is(gDebugger.gThreadClient.paused, true,
+        "Should be paused after the debugger statement.");
+      gWindow.close();
+      deferred.resolve();
+      finish();
+    });
 
-      gDebuggee.runDebuggerStatement();
-    });
+    gDebuggee.runDebuggerStatement();
   });
 
-  let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-  if (focusManager.activeWindow != gWindow) {
+  if (Services.focus.activeWindow != gWindow) {
     gWindow.addEventListener("activate", function onActivate(aEvent) {
       if (aEvent.target != gWindow) {
         return;
       }
       gWindow.removeEventListener("activate", onActivate, true);
       isActive.resolve();
     }, true);
   } else {
--- a/browser/devtools/debugger/test/browser_dbg_multiple-windows.js
+++ b/browser/devtools/debugger/test/browser_dbg_multiple-windows.js
@@ -70,28 +70,25 @@ function testNewWindow(aWindow) {
   let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
   is(topWindow, gNewWindow,
     "The second window is on top.");
 
   let isActive = promise.defer();
   let isLoaded = promise.defer();
 
   promise.all([isActive.promise, isLoaded.promise]).then(() => {
-    gNewWindow.BrowserChromeTest.runWhenReady(() => {
-      gClient.listTabs(aResponse => {
-        is(aResponse.selected, 2,
-          "The second tab is selected.");
+    gClient.listTabs(aResponse => {
+      is(aResponse.selected, 2,
+        "The second tab is selected.");
 
-        deferred.resolve();
-      });
+      deferred.resolve();
     });
   });
 
-  let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-  if (focusManager.activeWindow != gNewWindow) {
+  if (Services.focus.activeWindow != gNewWindow) {
     gNewWindow.addEventListener("activate", function onActivate(aEvent) {
       if (aEvent.target != gNewWindow) {
         return;
       }
       gNewWindow.removeEventListener("activate", onActivate, true);
       isActive.resolve();
     }, true);
   } else {
@@ -113,20 +110,16 @@ function testNewWindow(aWindow) {
 
   return deferred.promise;
 }
 
 function testFocusFirst() {
   let deferred = promise.defer();
 
   once(window.content, "focus").then(() => {
-    let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
-    is(top, getDOMWindow(window),
-      "The first window is on top.");
-
     gClient.listTabs(aResponse => {
       is(aResponse.selected, 1,
         "The first tab is selected after focusing on it.");
 
       deferred.resolve();
     });
   });
 
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -57,21 +57,21 @@ Services.scriptloader.loadSubScript(test
 function dbg_assert(cond, e) {
   if (!cond) {
     throw e;
   }
 }
 
 function addWindow(aUrl) {
   info("Adding window: " + aUrl);
-  return promise.resolve(getDOMWindow(window.open(aUrl)));
+  return promise.resolve(getChromeWindow(window.open(aUrl)));
 }
 
-function getDOMWindow(aReference) {
-  return aReference
+function getChromeWindow(aWindow) {
+  return aWindow
     .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
     .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
     .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
 }
 
 function addTab(aUrl, aWindow) {
   info("Adding tab: " + aUrl);
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -160,17 +160,16 @@ These should match what Safari and other
 <!ENTITY reloadCmd.accesskey          "R">
 <!ENTITY reloadButton.tooltip         "Reload current page">
 <!ENTITY stopCmd.label                "Stop">
 <!ENTITY stopCmd.accesskey            "S">
 <!ENTITY stopCmd.macCommandKey        ".">
 <!ENTITY stopButton.tooltip           "Stop loading this page">
 <!ENTITY goEndCap.tooltip             "Go to the address in the Location Bar">
 <!ENTITY printButton.label            "Print">
-<!ENTITY printButton.tooltip          "Print this page">
 
 <!ENTITY locationItem.title           "Location">
 <!ENTITY searchItem.title             "Search">
 
 <!-- Toolbar items --> 
 <!ENTITY homeButton.label             "Home">
 
 <!ENTITY tabGroupsButton.label        "Tab Groups">
@@ -194,17 +193,16 @@ These should match what Safari and other
 
 <!ENTITY toolsMenu.label              "Tools">
 <!ENTITY toolsMenu.accesskey          "T"> 
 
 <!ENTITY keywordfield.label           "Add a Keyword for this Search…">
 <!ENTITY keywordfield.accesskey       "K">
 
 <!ENTITY downloads.label              "Downloads">
-<!ENTITY downloads.tooltip            "Display the progress of ongoing downloads">
 <!ENTITY downloads.accesskey          "D">
 <!ENTITY downloads.commandkey         "j">
 <!ENTITY downloadsUnix.commandkey     "y">
 <!ENTITY addons.label                 "Add-ons">
 <!ENTITY addons.accesskey             "A">
 <!ENTITY addons.commandkey            "A">
 
 <!ENTITY webDeveloperMenu.label       "Web Developer">
@@ -558,22 +556,18 @@ you can use these alternative items. Oth
 <!ENTITY fullZoomResetCmd.commandkey    "0">
 <!ENTITY fullZoomResetCmd.commandkey2   "">
 
 <!ENTITY fullZoomToggleCmd.label        "Zoom Text Only">
 <!ENTITY fullZoomToggleCmd.accesskey    "T">
 <!ENTITY fullZoom.label                 "Zoom">
 <!ENTITY fullZoom.accesskey             "Z">
 
-<!ENTITY newTabButton.tooltip           "Open a new tab">
-<!ENTITY newWindowButton.tooltip        "Open a new window">
 <!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
 
-<!ENTITY fullScreenButton.tooltip       "Display the window in full screen">
-
 <!ENTITY quitApplicationCmdWin.label       "Exit"> 
 <!ENTITY quitApplicationCmdWin.accesskey   "x">
 <!ENTITY quitApplicationCmdWin.tooltip     "Exit &brandShortName;">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY quitApplicationCmd.label       "Quit"> 
 <!ENTITY quitApplicationCmd.accesskey   "Q">
 <!ENTITY quitApplicationCmdMac.label    "Quit &brandShortName;">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -232,33 +232,43 @@ refreshBlocked.goButton.accesskey=A
 refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
 refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
 
 # General bookmarks button
 # LOCALIZATION NOTE (bookmarksMenuButton.tooltip):
 # %S is the keyboard shortcut for "Show All Bookmarks"
 bookmarksMenuButton.tooltip=Show your bookmarks (%S)
 # Star button
-starButtonOn.tooltip=Edit this bookmark
-starButtonOff.tooltip=Bookmark this page
+starButtonOn.tooltip2=Edit this bookmark (%S)
+starButtonOff.tooltip2=Bookmark this page (%S)
 starButtonOverflowed.label=Bookmark This Page
 starButtonOverflowedStarred.label=Edit This Bookmark
 
+# Downloads button tooltip
+# LOCALIZATION NOTE (downloads.tooltip):
+# %S is the keyboard shortcut for "Downloads"
+downloads.tooltip=Display the progress of ongoing downloads (%S)
+
 # Print button tooltip on OS X
 # LOCALIZATION NOTE (printButton.tooltip):
 # Use the unicode ellipsis char, \u2026,
 # or use "..." if \u2026 doesn't suit traditions in your locale.
 # %S is the keyboard shortcut for "Print"
 printButton.tooltip=Print this page… (%S)
 
 # New Window button tooltip
 # LOCALIZATION NOTE (newWindowButton.tooltip):
 # %S is the keyboard shortcut for "New Window"
 newWindowButton.tooltip=Open a new window (%S)
 
+# New Tab button tooltip
+# LOCALIZATION NOTE (newTabButton.tooltip):
+# %S is the keyboard shortcut for "New Tab"
+newTabButton.tooltip=Open a new tab (%S)
+
 # Offline web applications
 offlineApps.available=This website (%S) is asking to store data on your computer for offline use.
 offlineApps.allow=Allow
 offlineApps.allowAccessKey=A
 offlineApps.never=Never for This Site
 offlineApps.neverAccessKey=e
 offlineApps.notNow=Not Now
 offlineApps.notNowAccessKey=N
--- a/browser/locales/en-US/chrome/browser/tabbrowser.dtd
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.dtd
@@ -1,6 +1,5 @@
 <!-- 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/. -->
 
 <!ENTITY  closeTab.label         "Close Tab">
-<!ENTITY  newTabButton.tooltip        "Open a new tab">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -735,16 +735,17 @@ toolbarbutton[sdk-button="true"][cui-are
   transition: none;
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 #forward-button {
+  -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
   padding: 0;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box;
   padding-left: 9px;
   padding-right: 3px;
   border: 1px solid #9a9a9a;
@@ -873,17 +874,17 @@ toolbarbutton[sdk-button="true"][cui-are
 
 /* Location bar */
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
   padding: 1px;
   border: 1px solid ThreeDShadow;
   border-radius: 2px;
-  margin: 1px 3px;
+  margin: 0 3px;
 }
 
 #urlbar[focused],
 .searchbar-textbox[focused] {
   border-color: Highlight;
 }
 
 #urlbar {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -872,16 +872,17 @@ toolbarbutton[sdk-button="true"][cui-are
   background-position: 1px -1px, 0 -1px, 100% -1px;
   background-size: calc(100% - 2px) 100%, 1px 100%, 1px 100%;
   background-repeat: no-repeat;
 }
 
 /* unified back/forward button */
 
 #forward-button {
+  -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
   padding: 0 !important;
 }
 
 #forward-button > menupopup {
   margin-top: 1px !important;
 }
 
 #forward-button > .toolbarbutton-icon {
@@ -1111,17 +1112,17 @@ toolbarbutton[sdk-button="true"][cui-are
   -moz-image-region: rect(32px, 48px, 48px, 32px);
 }
 
 /* ::::: Location Bar ::::: */
 
 #urlbar,
 .searchbar-textbox {
   -moz-appearance: none;
-  margin: 1px 3px;
+  margin: 0 3px;
   padding: 0;
   background-clip: padding-box;
   border: 1px solid ThreeDShadow;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
--- a/build/mobile/robocop/FennecNativeActions.java
+++ b/build/mobile/robocop/FennecNativeActions.java
@@ -67,17 +67,17 @@ public class FennecNativeActions impleme
                     FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
                             "handleMessage called for: " + event + "; expecting: " + mGeckoEvent);
                     mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event: " + message);
 
                     expecter.notifyOfEvent(message);
                 }
             };
 
-            GeckoAppShell.registerEventListener(mGeckoEvent, mListener);
+            EventDispatcher.getInstance().registerGeckoThreadListener(mListener, mGeckoEvent);
             mIsRegistered = true;
         }
 
         public void blockForEvent() {
             blockForEvent(MAX_WAIT_MS, true);
         }
 
         public void blockForEvent(long millis, boolean failOnTimeout) {
@@ -153,17 +153,17 @@ public class FennecNativeActions impleme
         public void unregisterListener() {
             if (!mIsRegistered) {
                 throw new IllegalStateException("listener not registered");
             }
 
             FennecNativeDriver.log(LogLevel.INFO,
                     "EventExpecter: no longer listening for " + mGeckoEvent);
 
-            GeckoAppShell.unregisterEventListener(mGeckoEvent, mListener);
+            EventDispatcher.getInstance().unregisterGeckoThreadListener(mListener, mGeckoEvent);
             mIsRegistered = false;
         }
 
         public boolean eventReceived() {
             return mEventEverReceived;
         }
 
         void notifyOfEvent(final JSONObject message) {
--- a/build/mobile/robocop/FennecNativeDriver.java
+++ b/build/mobile/robocop/FennecNativeDriver.java
@@ -229,33 +229,33 @@ public class FennecNativeDriver implemen
     public int getPageHeight() {
         return mPageHeight;
     }
     public int getHeight() {
         return mHeight;
     }
 
     public void setupScrollHandling() {
-        GeckoAppShell.registerEventListener("robocop:scroll", new GeckoEventListener() {
+        EventDispatcher.getInstance().registerGeckoThreadListener(new GeckoEventListener() {
             @Override
             public void handleMessage(final String event, final JSONObject message) {
                 try {
                     mScrollHeight = message.getInt("y");
                     mHeight = message.getInt("cheight");
                     // We don't want a height of 0. That means it's a bad response.
                     if (mHeight > 0) {
                         mPageHeight = message.getInt("height");
                     }
                 } catch (JSONException e) {
                     FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
                             "WARNING: ScrollReceived, but message does not contain " +
                             "expected fields: " + e);
                 }
             }
-        });
+        }, "robocop:scroll");
     }
 
     /**
      *  Takes a filename, loads the file, and returns a string version of the entire file.
      */
     public static String getFile(String filename)
     {
         StringBuilder text = new StringBuilder();
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -190,21 +190,16 @@ DOMInterfaces = {
     'headerFile': 'DOMCameraDetectedFace.h'
 },
 
 'CameraManager': {
     'nativeType': 'nsDOMCameraManager',
     'headerFile': 'DOMCameraManager.h'
 },
 
-'CameraPoint': {
-    'nativeType': 'mozilla::dom::DOMCameraPoint',
-    'headerFile': 'DOMCameraDetectedFace.h'
-},
-
 'CanvasRenderingContext2D': {
     'implicitJSContext': [
         'createImageData', 'getImageData'
     ],
     'resultNotAddRefed': [ 'canvas', 'measureText' ],
     'binaryNames': {
         'mozImageSmoothingEnabled': 'imageSmoothingEnabled',
         'mozFillRule': 'fillRule'
--- a/dom/camera/DOMCameraDetectedFace.cpp
+++ b/dom/camera/DOMCameraDetectedFace.cpp
@@ -3,56 +3,34 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DOMCameraDetectedFace.h"
 #include "Navigator.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMCameraPoint, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMCameraPoint)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMCameraPoint)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMCameraPoint)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMCameraDetectedFace, mParent,
                                       mBounds, mLeftEye, mRightEye, mMouth)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMCameraDetectedFace)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMCameraDetectedFace)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMCameraDetectedFace)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 /* static */
 bool
-DOMCameraPoint::HasSupport(JSContext* aCx, JSObject* aGlobal)
-{
-  return Navigator::HasCameraSupport(aCx, aGlobal);
-}
-
-/* static */
-bool
 DOMCameraDetectedFace::HasSupport(JSContext* aCx, JSObject* aGlobal)
 {
   return Navigator::HasCameraSupport(aCx, aGlobal);
 }
 
 JSObject*
-DOMCameraPoint::WrapObject(JSContext* aCx)
-{
-  return CameraPointBinding::Wrap(aCx, this);
-}
-
-JSObject*
 DOMCameraDetectedFace::WrapObject(JSContext* aCx)
 {
   return CameraDetectedFaceBinding::Wrap(aCx, this);
 }
 
 DOMCameraDetectedFace::DOMCameraDetectedFace(nsISupports* aParent,
                                              const ICameraControl::Face& aFace)
   : mParent(aParent)
@@ -61,19 +39,19 @@ DOMCameraDetectedFace::DOMCameraDetected
   , mBounds(new DOMRect(MOZ_THIS_IN_INITIALIZER_LIST()))
 {
   mBounds->SetRect(aFace.bound.left,
                    aFace.bound.top,
                    aFace.bound.right - aFace.bound.left,
                    aFace.bound.bottom - aFace.bound.top);
 
   if (aFace.hasLeftEye) {
-    mLeftEye = new DOMCameraPoint(this, aFace.leftEye);
+    mLeftEye = new DOMPoint(this, aFace.leftEye.x, aFace.leftEye.y);
   }
   if (aFace.hasRightEye) {
-    mRightEye = new DOMCameraPoint(this, aFace.rightEye);
+    mRightEye = new DOMPoint(this, aFace.rightEye.x, aFace.rightEye.y);
   }
   if (aFace.hasMouth) {
-    mMouth = new DOMCameraPoint(this, aFace.mouth);
+    mMouth = new DOMPoint(this, aFace.mouth.x, aFace.mouth.y);
   }
 
   SetIsDOMBinding();
 }
--- a/dom/camera/DOMCameraDetectedFace.h
+++ b/dom/camera/DOMCameraDetectedFace.h
@@ -4,74 +4,23 @@
 
 #ifndef DOM_CAMERA_DOMCAMERADETECTEDFACE_H
 #define DOM_CAMERA_DOMCAMERADETECTEDFACE_H
 
 #include "mozilla/dom/CameraControlBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMPoint.h"
 #include "ICameraControl.h"
 
 namespace mozilla {
 
 namespace dom {
 
-class DOMCameraPoint MOZ_FINAL : public nsISupports
-                               , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMCameraPoint)
-
-  // Because this header's filename doesn't match its C++ or DOM-facing
-  // classname, we can't rely on the [Func="..."] WebIDL tag to implicitly
-  // include the right header for us; instead we must explicitly include a
-  // HasSupport() method in each header. We can get rid of these with the
-  // Great Renaming proposed in bug 983177.
-  static bool HasSupport(JSContext* aCx, JSObject* aGlobal);
-
-  DOMCameraPoint(nsISupports* aParent, const ICameraControl::Point& aPoint)
-    : mParent(aParent)
-    , mX(aPoint.x)
-    , mY(aPoint.y)
-  {
-    SetIsDOMBinding();
-  }
-
-  void
-  SetPoint(int32_t aX, int32_t aY)
-  {
-    mX = aX;
-    mY = aY;
-  }
-
-  int32_t X() { return mX; }
-  int32_t Y() { return mY; }
-
-  void SetX(int32_t aX) { mX = aX; }
-  void SetY(int32_t aY) { mY = aY; }
-
-  nsISupports*
-  GetParentObject() const
-  {
-    MOZ_ASSERT(mParent);
-    return mParent;
-  }
-
-  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-
-protected:
-  virtual ~DOMCameraPoint() { }
-
-  nsCOMPtr<nsISupports> mParent;
-  int32_t mX;
-  int32_t mY;
-};
-
 class DOMCameraDetectedFace MOZ_FINAL : public nsISupports
                                       , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMCameraDetectedFace)
 
   // Because this header's filename doesn't match its C++ or DOM-facing
@@ -86,19 +35,19 @@ public:
   uint32_t Id()       { return mId; }
   uint32_t Score()    { return mScore; }
   bool HasLeftEye()   { return mLeftEye; }
   bool HasRightEye()  { return mRightEye; }
   bool HasMouth()     { return mMouth; }
 
   dom::DOMRect* Bounds()        { return mBounds; }
 
-  DOMCameraPoint* GetLeftEye()  { return mLeftEye; }
-  DOMCameraPoint* GetRightEye() { return mRightEye; }
-  DOMCameraPoint* GetMouth()    { return mMouth; }
+  dom::DOMPoint* GetLeftEye()  { return mLeftEye; }
+  dom::DOMPoint* GetRightEye() { return mRightEye; }
+  dom::DOMPoint* GetMouth()    { return mMouth; }
 
   nsISupports*
   GetParentObject() const
   {
     MOZ_ASSERT(mParent);
     return mParent;
   }
 
@@ -109,18 +58,18 @@ protected:
 
   nsCOMPtr<nsISupports> mParent;
 
   uint32_t mId;
   uint32_t mScore;
 
   nsRefPtr<dom::DOMRect> mBounds;
 
-  nsRefPtr<DOMCameraPoint> mLeftEye;
-  nsRefPtr<DOMCameraPoint> mRightEye;
-  nsRefPtr<DOMCameraPoint> mMouth;
+  nsRefPtr<dom::DOMPoint> mLeftEye;
+  nsRefPtr<dom::DOMPoint> mRightEye;
+  nsRefPtr<dom::DOMPoint> mMouth;
 };
 
 } // namespace dom
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_DOMCAMERADETECTEDFACE_H
--- a/dom/tests/browser/browser_geolocation_privatebrowsing_perwindowpb.js
+++ b/dom/tests/browser/browser_geolocation_privatebrowsing_perwindowpb.js
@@ -1,8 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
 function test() {
   var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
   let baseProvider = "http://mochi.test:8888/browser/dom/tests/browser/network_geolocation.sjs";
   prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=fff");
 
   prefs.setBoolPref("geo.prompt.testing", true);
   prefs.setBoolPref("geo.prompt.testing.allow", true);
   var origScanValue = true; // same default in NetworkGeolocationProvider.js.
@@ -14,40 +17,45 @@ function test() {
   const testPageURL = "http://mochi.test:8888/browser/" +
     "dom/tests/browser/browser_geolocation_privatebrowsing_page.html";
   waitForExplicitFinish();
 
   var windowsToClose = [];
   function testOnWindow(aIsPrivate, aCallback) {
     let win = OpenBrowserWindow({private: aIsPrivate});
     let gotLoad = false;
-    let gotActivate = 
-      (Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager).activeWindow == win);
+    let gotActivate = Services.focus.activeWindow == win;
+
+    function maybeRunCallback() {
+      if (gotLoad && gotActivate) {
+        windowsToClose.push(win);
+        executeSoon(function() { aCallback(win); });
+      }
+    }
+
     if (!gotActivate) {
       win.addEventListener("activate", function onActivate() {
         info("got activate");
         win.removeEventListener("activate", onActivate, true);
         gotActivate = true;
-        if (gotLoad) {
-          windowsToClose.push(win);
-          win.BrowserChromeTest.runWhenReady(function() { aCallback(win) });
-        }
+        maybeRunCallback();
       }, true);
     } else {
       info("Was activated");
     }
-    win.addEventListener("load", function onLoad() {
-      info("Got load");
-      win.removeEventListener("load", onLoad, true);
-      gotLoad = true;
-      if (gotActivate) {
-        windowsToClose.push(win);
-        setTimeout(function() { aCallback(win) }, 1000);
+
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      if (win == aSubject) {
+        info("Delayed startup finished");
+        Services.obs.removeObserver(observer, aTopic);
+        gotLoad = true;
+        maybeRunCallback();
       }
-    }, true);
+    }, "browser-delayed-startup-finished", false);
+
   }
 
   testOnWindow(false, function(aNormalWindow) {
     aNormalWindow.gBrowser.selectedBrowser.addEventListener("georesult", function load(ev) {
       aNormalWindow.gBrowser.selectedBrowser.removeEventListener("georesult", load, false);
       is(ev.detail, 200, "unexpected access token");
 
       prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=ggg");
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -171,18 +171,16 @@ var interfaceNamesInGlobalScope =
     {name: "CameraCapabilities", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraControl", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraDetectedFace", b2g: true, pref: "camera.control.face_detection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CameraManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "CameraPoint", b2g: true, pref: "camera.control.face_detection.enabled"},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasGradient",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasPattern",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CanvasRenderingContext2D",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CaretPosition",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -361,29 +361,16 @@ interface CameraControl : MediaStream
      once autoFocus() is called with a continuous autofocus mode set, the
      continuous autofocus process is stopped and focus is locked in the
      current state until this method is called.
   */
   [Throws]
   void resumeContinuousFocus();
 };
 
-/* The coordinates of a point, relative to the camera sensor, of the center of
-   detected facial features. As with CameraRegions:
-     { x: -1000, y: -1000 } is the top-left corner
-     { x:  1000, y:  1000 } is the bottom-right corner
-   x and y can range from -1000 to 1000.
-*/
-[Pref="camera.control.face_detection.enabled", Func="DOMCameraPoint::HasSupport"]
-interface CameraPoint
-{
-  attribute long x;
-  attribute long y;
-};
-
 /* The information of the each face detected by a camera device, e.g.
      {
        id: 1,
        score: 80,
        bound: { left:   -203,
                 top:    -400,
                 right:   300,
                 bottom:  250 },
@@ -403,41 +390,44 @@ interface CameraPoint
 
    'bounds' is the bounds of the face. It is guaranteed left < right and
    top < bottom. The coordinates can be smaller than -1000 or bigger than 1000.
    But at least one vertex will be within (-1000, -1000) and (1000, 1000).
 
    'leftEye' is the coordinates of the centre of the left eye. The coordinates
    are in the same space as the ones for 'bounds'. This is an optional field
    and may not be supported on all devices. If it is not supported or detected,
-   the value will be set to null.
+   the value will be set to null. The x and y coordinates are bounded by the
+   range (-1000, 1000) where:
+       { x: -1000, y: -1000 } is the top-left corner
+       { x:  1000, y:  1000 } is the bottom-right corner
 
    'rightEye' is the coordinates of the detected right eye; null if not
-   supported or detected.
+   supported or detected. Same boundary conditions as 'leftEye'.
 
    'mouth' is the coordinates of the detected mouth; null if not supported or
-   detected.
+   detected. Same boundary conditions as 'leftEye'.
 */
 [Pref="camera.control.face_detection.enabled", Func="DOMCameraDetectedFace::HasSupport"]
 interface CameraDetectedFace
 {
   readonly attribute unsigned long id;
 
   readonly attribute unsigned long score;
 
   readonly attribute DOMRect bounds;
 
   readonly attribute boolean hasLeftEye;
-  readonly attribute CameraPoint? leftEye;
+  readonly attribute DOMPoint? leftEye;
 
   readonly attribute boolean hasRightEye;
-  readonly attribute CameraPoint? rightEye;
+  readonly attribute DOMPoint? rightEye;
 
   readonly attribute boolean hasMouth;
-  readonly attribute CameraPoint? mouth;
+  readonly attribute DOMPoint? mouth;
 };
 
 callback CameraFaceDetectionCallback = void (sequence<CameraDetectedFace> faces);
 
 partial interface CameraControl
 {
   /* Starts the face detection. This should be called after the preview is
      started. The camera will periodically call 'onFacesDetected' with a
--- a/gfx/2d/BaseRect.h
+++ b/gfx/2d/BaseRect.h
@@ -230,35 +230,58 @@ struct BaseRect {
   }
   // Return true if the rectangles contain the same area of the plane.
   // Use when we do not care about differences in empty rectangles.
   bool IsEqualInterior(const Sub& aRect) const
   {
     return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty());
   }
 
-  Sub operator+(const Point& aPoint) const
+  friend Sub operator+(Sub aSub, const Point& aPoint)
   {
-    return Sub(x + aPoint.x, y + aPoint.y, width, height);
+    aSub += aPoint;
+    return aSub;
+  }
+  friend Sub operator-(Sub aSub, const Point& aPoint)
+  {
+    aSub -= aPoint;
+    return aSub;
   }
-  Sub operator-(const Point& aPoint) const
+  friend Sub operator+(Sub aSub, const SizeT& aSize)
   {
-    return Sub(x - aPoint.x, y - aPoint.y, width, height);
+    aSub += aSize;
+    return aSub;
+  }
+  friend Sub operator-(Sub aSub, const SizeT& aSize)
+  {
+    aSub -= aSize;
+    return aSub;
   }
   Sub& operator+=(const Point& aPoint)
   {
     MoveBy(aPoint);
     return *static_cast<Sub*>(this);
   }
   Sub& operator-=(const Point& aPoint)
   {
     MoveBy(-aPoint);
     return *static_cast<Sub*>(this);
   }
-
+  Sub& operator+=(const SizeT& aSize)
+  {
+    width += aSize.width;
+    height += aSize.height;
+    return *static_cast<Sub*>(this);
+  }
+  Sub& operator-=(const SizeT& aSize)
+  {
+    width -= aSize.width;
+    height -= aSize.height;
+    return *static_cast<Sub*>(this);
+  }
   // Find difference as a Margin
   MarginT operator-(const Sub& aRect) const
   {
     return MarginT(aRect.y - y,
                    XMost() - aRect.XMost(),
                    YMost() - aRect.YMost(),
                    aRect.x - x);
   }
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -78,38 +78,34 @@ APZCTreeManager::SetAllowedTouchBehavior
                                          const nsTArray<TouchBehaviorFlags> &aValues)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->SetAllowedTouchBehavior(aValues);
   }
 }
 
-void
-APZCTreeManager::AssertOnCompositorThread()
-{
-  Compositor::AssertOnCompositorThread();
-}
-
 /* Flatten the tree of APZC instances into the given nsTArray */
 static void
 Collect(AsyncPanZoomController* aApzc, nsTArray< nsRefPtr<AsyncPanZoomController> >* aCollection)
 {
   if (aApzc) {
     aCollection->AppendElement(aApzc);
     Collect(aApzc->GetLastChild(), aCollection);
     Collect(aApzc->GetPrevSibling(), aCollection);
   }
 }
 
 void
 APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor, Layer* aRoot,
                                              bool aIsFirstPaint, uint64_t aFirstPaintLayersId)
 {
-  AssertOnCompositorThread();
+  if (AsyncPanZoomController::GetThreadAssertionsEnabled()) {
+    Compositor::AssertOnCompositorThread();
+  }
 
   MonitorAutoLock lock(mTreeLock);
 
   // We do this business with collecting the entire tree into an array because otherwise
   // it's very hard to determine which APZC instances need to be destroyed. In the worst
   // case, there are two scenarios: (a) a layer with an APZC is removed from the layer
   // tree and (b) a layer with an APZC is moved in the layer tree from one place to a
   // completely different place. In scenario (a) we would want to destroy the APZC while
@@ -195,17 +191,19 @@ APZCTreeManager::UpdatePanZoomController
         // or omitted from the layer tree for whatever reason from a layers update. If it later comes
         // back it will have a reference to a destroyed APZC and so we need to throw that out and make
         // a new one.
         bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
         if (newApzc) {
           apzc = new AsyncPanZoomController(aLayersId, this, state->mController,
                                             AsyncPanZoomController::USE_GESTURE_DETECTOR);
           apzc->SetCompositorParent(aCompositor);
-          apzc->SetCrossProcessCompositorParent(state->mCrossProcessParent);
+          if (state->mCrossProcessParent != nullptr) {
+            apzc->ShareFrameMetricsAcrossProcesses();
+          }
         } else {
           // If there was already an APZC for the layer clear the tree pointers
           // so that it doesn't continue pointing to APZCs that should no longer
           // be in the tree. These pointers will get reset properly as we continue
           // building the tree. Also remove it from the set of APZCs that are going
           // to be destroyed, because it's going to remain active.
           aApzcsToDestroy->RemoveElement(apzc);
           apzc->SetPrevSibling(nullptr);
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -279,22 +279,16 @@ public:
    * Expects the overscroll handoff chain to already be built.
    */
   bool CanBePanned(AsyncPanZoomController* aApzc);
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~APZCTreeManager();
 
-  /**
-   * Debug-build assertion that can be called to ensure code is running on the
-   * compositor thread.
-   */
-  virtual void AssertOnCompositorThread();
-
   /*
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   void BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
 public:
   /* Some helper functions to find an APZC given some identifying input. These functions
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>                       // for fabsf, fabs, atan2
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include <sys/types.h>                  // for int32_t
 #include <algorithm>                    // for max, min
 #include "AnimationCommon.h"            // for ComputedTimingFunction
 #include "AsyncPanZoomController.h"     // for AsyncPanZoomController, etc
+#include "Compositor.h"                 // for Compositor
 #include "CompositorParent.h"           // for CompositorParent
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "GestureEventListener.h"       // for GestureEventListener
 #include "InputData.h"                  // for MultiTouchInput, etc
 #include "Units.h"                      // for CSSRect, CSSPoint, etc
 #include "UnitTransforms.h"             // for TransformTo
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for NewRunnableMethod, etc
@@ -325,16 +326,17 @@ static inline void LogRendertraceRect(co
   printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n",
     aGuid.mLayersId, aGuid.mPresShellId, aGuid.mScrollId,
     aDesc, delta.ToMilliseconds(), aColor,
     aRect.x, aRect.y, aRect.width, aRect.height);
 #endif
 }
 
 static TimeStamp sFrameTime;
+static bool sThreadAssertionsEnabled = true;
 
 // Counter used to give each APZC a unique id
 static uint32_t sAsyncPanZoomControllerCount = 0;
 
 static TimeStamp
 GetFrameTime() {
   if (sFrameTime.IsNull()) {
     return TimeStamp::Now();
@@ -434,16 +436,26 @@ private:
   CSSToScreenScale mEndZoom;
 };
 
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
   sFrameTime = aTime;
 }
 
+void
+AsyncPanZoomController::SetThreadAssertionsEnabled(bool aEnabled) {
+  sThreadAssertionsEnabled = aEnabled;
+}
+
+bool
+AsyncPanZoomController::GetThreadAssertionsEnabled() {
+  return sThreadAssertionsEnabled;
+}
+
 /*static*/ void
 AsyncPanZoomController::InitializeGlobalState()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool sInitialized = false;
   if (sInitialized)
     return;
@@ -455,20 +467,20 @@ AsyncPanZoomController::InitializeGlobal
   ClearOnShutdown(&gComputedTimingFunction);
 }
 
 AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
                                                APZCTreeManager* aTreeManager,
                                                GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
-     mCrossProcessCompositorParent(nullptr),
      mPaintThrottler(GetFrameTime()),
      mGeckoContentController(aGeckoContentController),
      mRefPtrMonitor("RefPtrMonitor"),
+     mSharingFrameMetricsAcrossProcesses(false),
      mMonitor("AsyncPanZoomController"),
      mTouchActionPropertyEnabled(gfxPrefs::TouchActionEnabled()),
      mContentResponseTimeoutTask(nullptr),
      mX(MOZ_THIS_IN_INITIALIZER_LIST()),
      mY(MOZ_THIS_IN_INITIALIZER_LIST()),
      mPanDirRestricted(false),
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
@@ -487,31 +499,44 @@ AsyncPanZoomController::AsyncPanZoomCont
   MOZ_COUNT_CTOR(AsyncPanZoomController);
 
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
 
 AsyncPanZoomController::~AsyncPanZoomController() {
-
-  PCompositorParent* compositor =
-    (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get());
+  PCompositorParent* compositor = GetSharedFrameMetricsCompositor();
 
   // Only send the release message if the SharedFrameMetrics has been created.
   if (compositor && mSharedFrameMetricsBuffer) {
     unused << compositor->SendReleaseSharedCompositorFrameMetrics(mFrameMetrics.GetScrollId(), mAPZCId);
   }
 
   delete mSharedFrameMetricsBuffer;
   delete mSharedLock;
 
   MOZ_COUNT_DTOR(AsyncPanZoomController);
 }
 
+PCompositorParent*
+AsyncPanZoomController::GetSharedFrameMetricsCompositor()
+{
+  if (GetThreadAssertionsEnabled()) {
+    Compositor::AssertOnCompositorThread();
+  }
+
+  if (mSharingFrameMetricsAcrossProcesses) {
+    const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(mLayersId);
+    // |state| may be null here if the CrossProcessCompositorParent has already been destroyed.
+    return state ? state->mCrossProcessParent : nullptr;
+  }
+  return mCompositorParent.get();
+}
+
 already_AddRefed<GeckoContentController>
 AsyncPanZoomController::GetGeckoContentController() {
   MonitorAutoLock lock(mRefPtrMonitor);
   nsRefPtr<GeckoContentController> controller = mGeckoContentController;
   return controller.forget();
 }
 
 already_AddRefed<GestureEventListener>
@@ -1389,18 +1414,18 @@ void AsyncPanZoomController::CancelAnima
   SetState(NOTHING);
   mAnimation = nullptr;
 }
 
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
-void AsyncPanZoomController::SetCrossProcessCompositorParent(PCompositorParent* aCrossProcessCompositorParent) {
-  mCrossProcessCompositorParent = aCrossProcessCompositorParent;
+void AsyncPanZoomController::ShareFrameMetricsAcrossProcesses() {
+  mSharingFrameMetricsAcrossProcesses = true;
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   mFrameMetrics.ScrollBy(aOffset);
 }
 
 void AsyncPanZoomController::ScaleWithFocus(float aScale,
                                             const CSSPoint& aFocus) {
@@ -2203,18 +2228,17 @@ void AsyncPanZoomController::UpdateShare
     mSharedLock->Lock();
     *frame = mFrameMetrics;
     mSharedLock->Unlock();
   }
 }
 
 void AsyncPanZoomController::ShareCompositorFrameMetrics() {
 
-  PCompositorParent* compositor =
-    (mCrossProcessCompositorParent ? mCrossProcessCompositorParent : mCompositorParent.get());
+  PCompositorParent* compositor = GetSharedFrameMetricsCompositor();
 
   // Only create the shared memory buffer if it hasn't already been created,
   // we are using progressive tile painting, and we have a
   // compositor to pass the shared memory back to the content process/thread.
   if (!mSharedFrameMetricsBuffer && compositor && gfxPrefs::UseProgressiveTilePainting()) {
 
     // Create shared memory and initialize it with the current FrameMetrics value
     mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -180,22 +180,21 @@ public:
 
   /**
    * The platform implementation must set the compositor parent so that we can
    * request composites.
    */
   void SetCompositorParent(CompositorParent* aCompositorParent);
 
   /**
-   * The platform implementation must set the cross process compositor if
-   * there is one associated with the layer tree. The cross process compositor
-   * allows the APZC to share its FrameMetrics with the content process.
-   * The shared FrameMetrics is used in progressive paint updates.
+   * Inform this APZC that it will be sharing its FrameMetrics with a cross-process
+   * compositor so that the associated content process can access it. This is only
+   * relevant when progressive painting is enabled.
    */
-  void SetCrossProcessCompositorParent(PCompositorParent* aCrossProcessCompositorParent);
+  void ShareFrameMetricsAcrossProcesses();
 
   // --------------------------------------------------------------------------
   // These methods can be called from any thread.
   //
 
   /**
    * Shut down the controller/UI thread state and prepare to be
    * deleted (which may happen from any thread).
@@ -273,23 +272,16 @@ public:
    */
   ScrollableLayerGuid GetGuid();
 
   /**
    * Returns true if this APZC instance is for the layer identified by the guid.
    */
   bool Matches(const ScrollableLayerGuid& aGuid);
 
-  /**
-   * Sync panning and zooming animation using a fixed frame time.
-   * This will ensure that we animate the APZC correctly with other external
-   * animations to the same timestamp.
-   */
-  static void SetFrameTime(const TimeStamp& aMilliseconds);
-
   void StartAnimation(AsyncPanZoomAnimation* aAnimation);
 
   /**
    * Cancels any currently running animation. Note that all this does is set the
    * state of the AsyncPanZoomController back to NOTHING, but it is the
    * animation's responsibility to check this before advancing.
    */
   void CancelAnimation();
@@ -316,24 +308,16 @@ public:
 
   /**
    * Returns whether this APZC is for an element marked with the 'scrollgrab'
    * attribute.
    */
   bool HasScrollgrab() const { return mFrameMetrics.mHasScrollgrab; }
 
   /**
-   * Set an extra offset for testing async scrolling.
-   */
-  void SetTestAsyncScrollOffset(const CSSPoint& aPoint)
-  {
-    mTestAsyncScrollOffset = aPoint;
-  }
-
-  /**
    * Returns whether this APZC has room to be panned (in any direction).
    */
   bool IsPannable() const;
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   ~AsyncPanZoomController();
 
@@ -648,30 +632,35 @@ private:
   // Helper function for OnSingleTapUp() and OnSingleTapConfirmed().
   nsEventStatus GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers);
 
   // Common processing at the end of a touch block.
   void OnTouchEndOrCancel();
 
   uint64_t mLayersId;
   nsRefPtr<CompositorParent> mCompositorParent;
-  PCompositorParent* mCrossProcessCompositorParent;
   TaskThrottler mPaintThrottler;
 
   /* Access to the following two fields is protected by the mRefPtrMonitor,
      since they are accessed on the UI thread but can be cleared on the
      compositor thread. */
   nsRefPtr<GeckoContentController> mGeckoContentController;
   nsRefPtr<GestureEventListener> mGestureEventListener;
   Monitor mRefPtrMonitor;
 
   /* Utility functions that return a addrefed pointer to the corresponding fields. */
   already_AddRefed<GeckoContentController> GetGeckoContentController();
   already_AddRefed<GestureEventListener> GetGestureEventListener();
 
+  // If we are sharing our frame metrics with content across processes
+  bool mSharingFrameMetricsAcrossProcesses;
+  /* Utility function to get the Compositor with which we share the FrameMetrics.
+     This function is only callable from the compositor thread. */
+  PCompositorParent* GetSharedFrameMetricsCompositor();
+
 protected:
   // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
   // monitor. Do not read from or modify either of them without locking.
   FrameMetrics mFrameMetrics;
 
   // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, and |mState|.
   // Before manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the
   // monitor should be held. When setting |mState|, either the SetState()
@@ -752,19 +741,16 @@ private:
   // WAITING_LISTENERS state. This is used in the case that we are processing a
   // queued up event block. If set, this means that we are handling this queue
   // and we don't want to queue the events back up again.
   bool mHandlingTouchQueue;
 
   // Stores information about the current touch block.
   TouchBlockState mTouchBlockState;
 
-  // Extra offset to add in SampleContentTransformForFrame for testing
-  CSSPoint mTestAsyncScrollOffset;
-
   RefPtr<AsyncPanZoomAnimation> mAnimation;
 
   friend class Axis;
 
 
   /* ===================================================================
    * The functions and members in this section are used to manage
    * fling animations.
@@ -932,16 +918,47 @@ private:
    */
   void UpdateSharedCompositorFrameMetrics();
   /**
    * Create a shared memory buffer for containing the FrameMetrics and
    * a CrossProcessMutex that may be shared with the content process
    * for use in progressive tiled update calculations.
    */
   void ShareCompositorFrameMetrics();
+
+
+  /* ===================================================================
+   * The functions and members in this section are used for testing
+   * purposes only.
+   */
+public:
+  /**
+   * Sync panning and zooming animation using a fixed frame time.
+   * This will ensure that we animate the APZC correctly with other external
+   * animations to the same timestamp.
+   */
+  static void SetFrameTime(const TimeStamp& aMilliseconds);
+  /**
+   * In the gtest environment everything runs on one thread, so we
+   * shouldn't assert that we're on a particular thread. This enables
+   * that behaviour.
+   */
+  static void SetThreadAssertionsEnabled(bool aEnabled);
+  static bool GetThreadAssertionsEnabled();
+  /**
+   * Set an extra offset for testing async scrolling.
+   */
+  void SetTestAsyncScrollOffset(const CSSPoint& aPoint)
+  {
+    mTestAsyncScrollOffset = aPoint;
+  }
+
+private:
+  // Extra offset to add in SampleContentTransformForFrame for testing
+  CSSPoint mTestAsyncScrollOffset;
 };
 
 class AsyncPanZoomAnimation {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
 
 public:
   AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
                         TimeDuration::Forever())
--- a/gfx/layers/apz/util/ActiveElementManager.cpp
+++ b/gfx/layers/apz/util/ActiveElementManager.cpp
@@ -9,16 +9,19 @@
 #include "mozilla/Services.h"
 #include "inIDOMUtils.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEventTarget.h"
 #include "base/message_loop.h"
 #include "base/task.h"
 
+#define AEM_LOG(...)
+// #define AEM_LOG(...) printf_stderr("AEM: " __VA_ARGS__)
+
 namespace mozilla {
 namespace layers {
 
 static int32_t sActivationDelayMs = 100;
 static bool sActivationDelayMsSet = false;
 
 ActiveElementManager::ActiveElementManager()
   : mDomUtils(services::GetInDOMUtils()),
@@ -36,29 +39,41 @@ ActiveElementManager::ActiveElementManag
 
 ActiveElementManager::~ActiveElementManager() {}
 
 void
 ActiveElementManager::SetTargetElement(nsIDOMEventTarget* aTarget)
 {
   if (mTarget) {
     // Multiple fingers on screen (since HandleTouchEnd clears mTarget).
+    AEM_LOG("Multiple fingers on-screen, clearing target element\n");
     CancelTask();
     ResetActive();
-    mTarget = nullptr;
+    ResetTouchBlockState();
     return;
   }
 
   mTarget = do_QueryInterface(aTarget);
+  AEM_LOG("Setting target element to %p\n", mTarget.get());
   TriggerElementActivation();
 }
 
 void
 ActiveElementManager::HandleTouchStart(bool aCanBePan)
 {
+  AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan);
+  if (mCanBePanSet) {
+    // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet).
+    AEM_LOG("Multiple fingers on-screen, clearing touch block state\n");
+    CancelTask();
+    ResetActive();
+    ResetTouchBlockState();
+    return;
+  }
+
   mCanBePan = aCanBePan;
   mCanBePanSet = true;
   TriggerElementActivation();
 }
 
 void
 ActiveElementManager::TriggerElementActivation()
 {
@@ -69,85 +84,105 @@ ActiveElementManager::TriggerElementActi
     return;
   }
 
   // If the touch cannot be a pan, make mTarget :active right away.
   // Otherwise, wait a bit to see if the user will pan or not.
   if (!mCanBePan) {
     SetActive(mTarget);
   } else {
+    MOZ_ASSERT(mSetActiveTask == nullptr);
     mSetActiveTask = NewRunnableMethod(
         this, &ActiveElementManager::SetActiveTask, mTarget);
     MessageLoop::current()->PostDelayedTask(
         FROM_HERE, mSetActiveTask, sActivationDelayMs);
+    AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask);
   }
 }
 
 void
 ActiveElementManager::HandlePanStart()
 {
+  AEM_LOG("Handle pan start\n");
+
   // The user started to pan, so we don't want mTarget to be :active.
   // Make it not :active, and clear any pending task to make it :active.
   CancelTask();
   ResetActive();
 }
 
 void
 ActiveElementManager::HandleTouchEnd(bool aWasClick)
 {
+  AEM_LOG("Touch end, aWasClick: %d\n", aWasClick);
+
   // If the touch was a click, make mTarget :active right away.
   // nsEventStateManager will reset the active element when processing
   // the mouse-down event generated by the click.
   CancelTask();
   if (aWasClick) {
     SetActive(mTarget);
   }
 
-  // Clear mTarget for next touch.
-  mTarget = nullptr;
+  ResetTouchBlockState();
 }
 
 void
 ActiveElementManager::SetActive(nsIDOMElement* aTarget)
 {
+  AEM_LOG("Setting active %p\n", aTarget);
   if (mDomUtils) {
-    mDomUtils->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE.GetInternalValue());;
+    mDomUtils->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE.GetInternalValue());
   }
 }
 
 void
 ActiveElementManager::ResetActive()
 {
+  AEM_LOG("Resetting active from %p\n", mTarget.get());
+
   // Clear the :active flag from mTarget by setting it on the document root.
   if (mTarget) {
     nsCOMPtr<nsIDOMDocument> doc;
     mTarget->GetOwnerDocument(getter_AddRefs(doc));
     if (doc) {
       nsCOMPtr<nsIDOMElement> root;
       doc->GetDocumentElement(getter_AddRefs(root));
       if (root) {
+        AEM_LOG("Found root %p, making active\n", root.get());
         SetActive(root);
       }
     }
   }
 }
 
 void
+ActiveElementManager::ResetTouchBlockState()
+{
+  mTarget = nullptr;
+  mCanBePanSet = false;
+}
+
+void
 ActiveElementManager::SetActiveTask(nsIDOMElement* aTarget)
 {
+  AEM_LOG("mSetActiveTask %p running\n", mSetActiveTask);
+
   // This gets called from mSetActiveTask's Run() method. The message loop
   // deletes the task right after running it, so we need to null out
   // mSetActiveTask to make sure we're not left with a dangling pointer.
   mSetActiveTask = nullptr;
   SetActive(aTarget);
 }
 
 void
 ActiveElementManager::CancelTask()
 {
+  AEM_LOG("Cancelling task %p\n", mSetActiveTask);
+
   if (mSetActiveTask) {
     mSetActiveTask->Cancel();
     mSetActiveTask = nullptr;
   }
 }
 
 }
 }
--- a/gfx/layers/apz/util/ActiveElementManager.h
+++ b/gfx/layers/apz/util/ActiveElementManager.h
@@ -70,16 +70,17 @@ private:
    * A task for calling SetActive() after a timeout.
    */
   CancelableTask* mSetActiveTask;
 
   // Helpers
   void TriggerElementActivation();
   void SetActive(nsIDOMElement* aTarget);
   void ResetActive();
+  void ResetTouchBlockState();
   void SetActiveTask(nsIDOMElement* aTarget);
   void CancelTask();
 };
 
 }
 }
 
 #endif /* mozilla_layers_ActiveElementManager_h */
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -29,26 +29,28 @@ using ::testing::MockFunction;
 using ::testing::InSequence;
 
 class Task;
 
 class AsyncPanZoomControllerTester : public ::testing::Test {
 protected:
   virtual void SetUp() {
     gfxPrefs::GetSingleton();
+    AsyncPanZoomController::SetThreadAssertionsEnabled(false);
   }
   virtual void TearDown() {
     gfxPrefs::DestroySingleton();
   }
 };
 
 class APZCTreeManagerTester : public ::testing::Test {
 protected:
   virtual void SetUp() {
     gfxPrefs::GetSingleton();
+    AsyncPanZoomController::SetThreadAssertionsEnabled(false);
   }
   virtual void TearDown() {
     gfxPrefs::DestroySingleton();
   }
 };
 
 class MockContentController : public GeckoContentController {
 public:
@@ -135,19 +137,16 @@ public:
 
   FrameMetrics GetFrameMetrics() {
     ReentrantMonitorAutoEnter lock(mMonitor);
     return mFrameMetrics;
   }
 };
 
 class TestAPZCTreeManager : public APZCTreeManager {
-protected:
-  void AssertOnCompositorThread() MOZ_OVERRIDE { /* no-op */ }
-
 public:
   // Expose this so test code can call it directly.
   void BuildOverscrollHandoffChain(AsyncPanZoomController* aApzc) {
     APZCTreeManager::BuildOverscrollHandoffChain(aApzc);
   }
 };
 
 static
--- a/intl/unicharutil/util/ICUUtils.cpp
+++ b/intl/unicharutil/util/ICUUtils.cpp
@@ -103,16 +103,20 @@ ICUUtils::LocalizeNumber(double aValue,
   nsAutoCString langTag;
   aLangTags.GetNext(langTag);
   while (!langTag.IsEmpty()) {
     UErrorCode status = U_ZERO_ERROR;
     AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0,
                                             langTag.get(), nullptr, &status));
     unum_setAttribute(format, UNUM_GROUPING_USED,
                       LocaleNumberGroupingIsEnabled());
+    // ICU default is a maximum of 3 significant fractional digits. We don't
+    // want that limit, so we set it to the maximum that a double can represent
+    // (14-16 decimal fractional digits).
+    unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, 16);
     int32_t length = unum_formatDouble(format, aValue, buffer, kBufferSize,
                                        nullptr, &status);
     NS_ASSERTION(length < kBufferSize &&
                  status != U_BUFFER_OVERFLOW_ERROR &&
                  status != U_STRING_NOT_TERMINATED_WARNING,
                  "Need a bigger buffer?!");
     if (U_SUCCESS(status)) {
       ICUUtils::AssignUCharArrayToString(buffer, length, aLocalizedValue);
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -37,17 +37,17 @@ extern const JSFunctionSpec Int32x4Metho
 
 static const char *laneNames[] = {"lane 0", "lane 1", "lane 2", "lane3"};
 
 template<typename Type32x4, int lane>
 static bool GetX4Lane(JSContext *cx, unsigned argc, Value *vp) {
     typedef typename Type32x4::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if(!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
+    if (!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, laneNames[lane],
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     TypedObject &typedObj = args.thisv().toObject().as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
@@ -79,17 +79,17 @@ static bool type##Lane##lane(JSContext *
 #undef FOUR_LANES_ACCESSOR
 #undef LANE_ACCESSOR
 
 template<typename Type32x4>
 static bool SignMask(JSContext *cx, unsigned argc, Value *vp) {
     typedef typename Type32x4::Elem Elem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if(!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
+    if (!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, "signMask",
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     TypedObject &typedObj = args.thisv().toObject().as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
@@ -250,43 +250,42 @@ CreateX4Class(JSContext *cx,
 
     return x4;
 }
 
 bool
 X4TypeDescr::call(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    const uint32_t LANES = 4;
+    const unsigned LANES = 4;
 
     if (args.length() < LANES) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                              args.callee().getClass()->name, "3", "s");
         return false;
     }
 
     double values[LANES];
-    for (uint32_t i = 0; i < LANES; i++) {
+    for (unsigned i = 0; i < LANES; i++) {
         if (!ToNumber(cx, args[i], &values[i]))
             return false;
     }
 
     Rooted<X4TypeDescr*> descr(cx, &args.callee().as<X4TypeDescr>());
     Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0));
     if (!result)
         return false;
 
     switch (descr->type()) {
 #define STORE_LANES(_constant, _type, _name)                                  \
       case _constant:                                                         \
       {                                                                       \
         _type *mem = reinterpret_cast<_type*>(result->typedMem());            \
-        for (uint32_t i = 0; i < LANES; i++) {                                \
+        for (unsigned i = 0; i < LANES; i++)                                  \
             mem[i] = ConvertScalar<_type>(values[i]);                         \
-        }                                                                     \
         break;                                                                \
       }
       JS_FOR_EACH_X4_TYPE_REPR(STORE_LANES)
 #undef STORE_LANES
     }
     args.rval().setObject(*result);
     return true;
 }
@@ -319,71 +318,66 @@ SIMDObject::initClass(JSContext *cx, Han
     // to be able to call GetTypedObjectModule(). It is NOT necessary
     // to install the TypedObjectModule global, but at the moment
     // those two things are not separable.
     if (!global->getOrCreateTypedObjectModule(cx))
         return nullptr;
 
     // Create SIMD Object.
     RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
-    if(!objProto)
+    if (!objProto)
         return nullptr;
     RootedObject SIMD(cx, NewObjectWithGivenProto(cx, &SIMDObject::class_, objProto,
                                                   global, SingletonObject));
     if (!SIMD)
         return nullptr;
 
     // float32x4
-
     RootedObject float32x4Object(cx);
     float32x4Object = CreateX4Class<Float32x4Defn>(cx, global,
                                                    cx->names().float32x4);
     if (!float32x4Object)
         return nullptr;
 
+    // Define float32x4 functions and install as a property of the SIMD object.
     RootedValue float32x4Value(cx, ObjectValue(*float32x4Object));
     if (!JS_DefineFunctions(cx, float32x4Object, Float32x4Methods) ||
         !JSObject::defineProperty(cx, SIMD, cx->names().float32x4,
                                   float32x4Value, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     // int32x4
-
     RootedObject int32x4Object(cx);
     int32x4Object = CreateX4Class<Int32x4Defn>(cx, global,
                                                cx->names().int32x4);
     if (!int32x4Object)
         return nullptr;
 
+    // Define int32x4 functions and install as a property of the SIMD object.
     RootedValue int32x4Value(cx, ObjectValue(*int32x4Object));
     if (!JS_DefineFunctions(cx, int32x4Object, Int32x4Methods) ||
         !JSObject::defineProperty(cx, SIMD, cx->names().int32x4,
                                   int32x4Value, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     RootedValue SIMDValue(cx, ObjectValue(*SIMD));
 
     // Everything is set up, install SIMD on the global object.
     if (!JSObject::defineProperty(cx, global, cx->names().SIMD, SIMDValue, nullptr, nullptr, 0))
         return nullptr;
 
     global->setConstructor(JSProto_SIMD, SIMDValue);
-
-    // Define float32x4 functions and install as a property of the SIMD object.
     global->setFloat32x4TypeDescr(*float32x4Object);
-
-    // Define int32x4 functions and install as a property of the SIMD object.
     global->setInt32x4TypeDescr(*int32x4Object);
-
     return SIMD;
 }
 
 JSObject *
 js_InitSIMDClass(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->is<GlobalObject>());
     Rooted<GlobalObject *> global(cx, &obj->as<GlobalObject>());
@@ -555,90 +549,110 @@ struct WithFlagZ {
 template<typename T, typename V>
 struct WithFlagW {
     static inline T apply(T l, T f, T x) { return V::toType(l == 3 ? (f ? 0xFFFFFFFF : 0x0) : x); }
 };
 template<typename T, typename V>
 struct Shuffle {
     static inline int32_t apply(int32_t l, int32_t mask) { return V::toType((mask >> l) & 0x3); }
 };
+struct ShiftLeft {
+    static inline int32_t apply(int32_t v, int32_t bits) { return v << bits; }
+};
+struct ShiftRight {
+    static inline int32_t apply(int32_t v, int32_t bits) { return v >> bits; }
+};
+struct ShiftRightLogical {
+    static inline int32_t apply(int32_t v, int32_t bits) { return uint32_t(v) >> (bits & 31); }
+};
 }
 
-template<typename V, typename Op, typename Vret>
+static inline bool
+ErrorBadArgs(JSContext *cx)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+    return false;
+}
+
+// Coerces the inputs of type In to the type Coercion, apply the operator Op
+// and converts the result to the type Out.
+template<typename In, typename Coercion, typename Op, typename Out>
 static bool
-Func(JSContext *cx, unsigned argc, Value *vp)
+CoercedFunc(JSContext *cx, unsigned argc, Value *vp)
 {
-    typedef typename V::Elem Elem;
-    typedef typename Vret::Elem RetElem;
+    typedef typename Coercion::Elem CoercionElem;
+    typedef typename Out::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 1 && args.length() != 2) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+    if (args.length() != 1 && args.length() != 2)
+        return ErrorBadArgs(cx);
 
-    RetElem result[Vret::lanes];
+    CoercionElem result[Coercion::lanes];
     if (args.length() == 1) {
-        if (!IsVectorObject<V>(args[0])) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return false;
-        }
+        if (!IsVectorObject<In>(args[0]))
+            return ErrorBadArgs(cx);
 
-        Elem *val = TypedObjectMemory<Elem *>(args[0]);
-        for (int32_t i = 0; i < Vret::lanes; i++)
+        CoercionElem *val = TypedObjectMemory<CoercionElem *>(args[0]);
+        for (unsigned i = 0; i < Coercion::lanes; i++)
             result[i] = Op::apply(val[i], 0);
     } else {
         JS_ASSERT(args.length() == 2);
-        if(!IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]))
-        {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return false;
-        }
+        if (!IsVectorObject<In>(args[0]) || !IsVectorObject<In>(args[1]))
+            return ErrorBadArgs(cx);
 
-        Elem *left = TypedObjectMemory<Elem *>(args[0]);
-        Elem *right = TypedObjectMemory<Elem *>(args[1]);
-        for (int32_t i = 0; i < Vret::lanes; i++)
+        CoercionElem *left = TypedObjectMemory<CoercionElem *>(args[0]);
+        CoercionElem *right = TypedObjectMemory<CoercionElem *>(args[1]);
+        for (unsigned i = 0; i < Coercion::lanes; i++)
             result[i] = Op::apply(left[i], right[i]);
     }
 
-    RootedObject obj(cx, Create<Vret>(cx, result));
+    RetElem *coercedResult = reinterpret_cast<RetElem *>(result);
+    RootedObject obj(cx, Create<Out>(cx, coercedResult));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
+// Same as above, with Coercion == Out
+template<typename In, typename Op, typename Out>
+static bool
+Func(JSContext *cx, unsigned argc, Value *vp)
+{
+    return CoercedFunc<In, Out, Op, Out>(cx, argc, vp);
+}
+
 template<typename V, typename OpWith, typename Vret>
 static bool
 FuncWith(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename V::Elem Elem;
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 2 || !IsVectorObject<V>(args[0]) ||
         (!args[1].isNumber() && !args[1].isBoolean()))
     {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
+        return ErrorBadArgs(cx);
     }
 
     Elem *val = TypedObjectMemory<Elem *>(args[0]);
     RetElem result[Vret::lanes];
 
     if (args[1].isNumber()) {
         Elem withAsNumber;
         if (!Vret::toType(cx, args[1], &withAsNumber))
             return false;
-        for (int32_t i = 0; i < Vret::lanes; i++)
+        for (unsigned i = 0; i < Vret::lanes; i++)
             result[i] = OpWith::apply(i, withAsNumber, val[i]);
-    } else if (args[1].isBoolean()) {
+    } else {
+        JS_ASSERT(args[1].isBoolean());
         bool withAsBool = args[1].toBoolean();
-        for (int32_t i = 0; i < Vret::lanes; i++)
+        for (unsigned i = 0; i < Vret::lanes; i++)
             result[i] = OpWith::apply(i, withAsBool, val[i]);
     }
 
     RootedObject obj(cx, Create<Vret>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
@@ -648,83 +662,99 @@ FuncWith(JSContext *cx, unsigned argc, V
 template<typename V, typename OpShuffle, typename Vret>
 static bool
 FuncShuffle(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename V::Elem Elem;
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 2 && args.length() != 3) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+    if (args.length() != 2 && args.length() != 3)
+        return ErrorBadArgs(cx);
 
     RetElem result[Vret::lanes];
     if (args.length() == 2) {
         if (!IsVectorObject<V>(args[0]) || !args[1].isNumber())
-        {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return false;
-        }
+            return ErrorBadArgs(cx);
 
         Elem *val = TypedObjectMemory<Elem *>(args[0]);;
         Elem arg1;
         if (!Vret::toType(cx, args[1], &arg1))
             return false;
 
-        for (int32_t i = 0; i < Vret::lanes; i++)
+        for (unsigned i = 0; i < Vret::lanes; i++)
             result[i] = val[OpShuffle::apply(i * 2, arg1)];
     } else {
         JS_ASSERT(args.length() == 3);
         if (!IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]) || !args[2].isNumber())
-        {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-            return false;
-        }
+            return ErrorBadArgs(cx);
 
         Elem *val1 = TypedObjectMemory<Elem *>(args[0]);
         Elem *val2 = TypedObjectMemory<Elem *>(args[1]);
         Elem arg2;
         if (!Vret::toType(cx, args[2], &arg2))
             return false;
 
-        for (int32_t i = 0; i < Vret::lanes; i++) {
-            if (i < Vret::lanes / 2)
-                result[i] = val1[OpShuffle::apply(i * 2, arg2)];
-            else
-                result[i] = val2[OpShuffle::apply(i * 2, arg2)];
-        }
+        unsigned i = 0;
+        for (; i < Vret::lanes / 2; i++)
+            result[i] = val1[OpShuffle::apply(i * 2, arg2)];
+        for (; i < Vret::lanes; i++)
+            result[i] = val2[OpShuffle::apply(i * 2, arg2)];
     }
 
     RootedObject obj(cx, Create<Vret>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
+template<typename Op>
+static bool
+Int32x4BinaryScalar(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 2)
+        return ErrorBadArgs(cx);
+
+    int32_t result[4];
+    if (!IsVectorObject<Int32x4>(args[0]) || !args[1].isNumber())
+        return ErrorBadArgs(cx);
+
+    int32_t *val = TypedObjectMemory<int32_t *>(args[0]);;
+    int32_t bits;
+    if (!ToInt32(cx, args[1], &bits))
+        return false;
+
+    for (unsigned i = 0; i < 4; i++)
+        result[i] = Op::apply(val[i], bits);
+
+    RootedObject obj(cx, Create<Int32x4>(cx, result));
+    if (!obj)
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
 template<typename V, typename Vret>
 static bool
 FuncConvert(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename V::Elem Elem;
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 1 || !IsVectorObject<V>(args[0]))
-    {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+        return ErrorBadArgs(cx);
 
     Elem *val = TypedObjectMemory<Elem *>(args[0]);
     RetElem result[Vret::lanes];
-    for (int32_t i = 0; i < Vret::lanes; i++)
+    for (unsigned i = 0; i < Vret::lanes; i++)
         result[i] = RetElem(val[i]);
 
     RootedObject obj(cx, Create<Vret>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
@@ -733,20 +763,17 @@ FuncConvert(JSContext *cx, unsigned argc
 template<typename V, typename Vret>
 static bool
 FuncConvertBits(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 1 || !IsVectorObject<V>(args[0]))
-    {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+        return ErrorBadArgs(cx);
 
     RetElem *val = TypedObjectMemory<RetElem *>(args[0]);
     RootedObject obj(cx, Create<Vret>(cx, val));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
@@ -754,23 +781,21 @@ FuncConvertBits(JSContext *cx, unsigned 
 
 template<typename Vret>
 static bool
 FuncZero(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 0) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+    if (args.length() != 0)
+        return ErrorBadArgs(cx);
 
     RetElem result[Vret::lanes];
-    for (int32_t i = 0; i < Vret::lanes; i++)
+    for (unsigned i = 0; i < Vret::lanes; i++)
         result[i] = RetElem(0);
 
     RootedObject obj(cx, Create<Vret>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
@@ -778,27 +803,25 @@ FuncZero(JSContext *cx, unsigned argc, V
 
 template<typename Vret>
 static bool
 FuncSplat(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename Vret::Elem RetElem;
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (args.length() != 1 || !args[0].isNumber()) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
-    }
+    if (args.length() != 1 || !args[0].isNumber())
+        return ErrorBadArgs(cx);
 
     RetElem arg;
     if (!Vret::toType(cx, args[0], &arg))
         return false;
 
     RetElem result[Vret::lanes];
-    for (int32_t i = 0; i < Vret::lanes; i++)
+    for (unsigned i = 0; i < Vret::lanes; i++)
         result[i] = arg;
 
     RootedObject obj(cx, Create<Vret>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
@@ -807,22 +830,21 @@ FuncSplat(JSContext *cx, unsigned argc, 
 static bool
 Int32x4Bool(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 4 ||
         !args[0].isBoolean() || !args[1].isBoolean() ||
         !args[2].isBoolean() || !args[3].isBoolean())
     {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
+        return ErrorBadArgs(cx);
     }
 
     int32_t result[Int32x4::lanes];
-    for (int32_t i = 0; i < Int32x4::lanes; i++)
+    for (unsigned i = 0; i < Int32x4::lanes; i++)
         result[i] = args[i].toBoolean() ? 0xFFFFFFFF : 0x0;
 
     RootedObject obj(cx, Create<Int32x4>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
@@ -830,26 +852,25 @@ Int32x4Bool(JSContext *cx, unsigned argc
 
 static bool
 Float32x4Clamp(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 3 || !IsVectorObject<Float32x4>(args[0]) ||
         !IsVectorObject<Float32x4>(args[1]) || !IsVectorObject<Float32x4>(args[2]))
     {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
+        return ErrorBadArgs(cx);
     }
 
     float *val = TypedObjectMemory<float *>(args[0]);
     float *lowerLimit = TypedObjectMemory<float *>(args[1]);
     float *upperLimit = TypedObjectMemory<float *>(args[2]);
 
     float result[Float32x4::lanes];
-    for (int32_t i = 0; i < Float32x4::lanes; i++) {
+    for (unsigned i = 0; i < Float32x4::lanes; i++) {
         result[i] = val[i] < lowerLimit[i] ? lowerLimit[i] : val[i];
         result[i] = result[i] > upperLimit[i] ? upperLimit[i] : result[i];
     }
 
     RootedObject obj(cx, Create<Float32x4>(cx, result));
     if (!obj)
         return false;
 
@@ -859,34 +880,33 @@ Float32x4Clamp(JSContext *cx, unsigned a
 
 static bool
 Int32x4Select(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 3 || !IsVectorObject<Int32x4>(args[0]) ||
         !IsVectorObject<Float32x4>(args[1]) || !IsVectorObject<Float32x4>(args[2]))
     {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return false;
+        return ErrorBadArgs(cx);
     }
 
     int32_t *val = TypedObjectMemory<int32_t *>(args[0]);
     int32_t *tv = TypedObjectMemory<int32_t *>(args[1]);
     int32_t *fv = TypedObjectMemory<int32_t *>(args[2]);
 
     int32_t tr[Int32x4::lanes];
-    for (int32_t i = 0; i < Int32x4::lanes; i++)
+    for (unsigned i = 0; i < Int32x4::lanes; i++)
         tr[i] = And<int32_t, Int32x4>::apply(val[i], tv[i]);
 
     int32_t fr[Int32x4::lanes];
-    for (int32_t i = 0; i < Int32x4::lanes; i++)
+    for (unsigned i = 0; i < Int32x4::lanes; i++)
         fr[i] = And<int32_t, Int32x4>::apply(Not<int32_t, Int32x4>::apply(val[i], 0), fv[i]);
 
     int32_t orInt[Int32x4::lanes];
-    for (int32_t i = 0; i < Int32x4::lanes; i++)
+    for (unsigned i = 0; i < Int32x4::lanes; i++)
         orInt[i] = Or<int32_t, Int32x4>::apply(tr[i], fr[i]);
 
     float *result = reinterpret_cast<float *>(orInt);
     RootedObject obj(cx, Create<Float32x4>(cx, result));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -20,41 +20,45 @@
 
 #define FLOAT32X4_NULLARY_FUNCTION_LIST(V)                                                                        \
   V(zero, (FuncZero<Float32x4>), 0, 0, Zero)
 
 #define FLOAT32X4_UNARY_FUNCTION_LIST(V)                                                                          \
   V(abs, (Func<Float32x4, Abs<float, Float32x4>, Float32x4>), 1, 0, Abs)                                          \
   V(bitsToInt32x4, (FuncConvertBits<Float32x4, Int32x4>), 1, 0, BitsToInt32x4)                                    \
   V(neg, (Func<Float32x4, Neg<float, Float32x4>, Float32x4>), 1, 0, Neg)                                          \
+  V(not, (CoercedFunc<Float32x4, Int32x4, Not<int32_t, Int32x4>, Float32x4>), 1, 0, Not)                          \
   V(reciprocal, (Func<Float32x4, Rec<float, Float32x4>, Float32x4>), 1, 0, Reciprocal)                            \
   V(reciprocalSqrt, (Func<Float32x4, RecSqrt<float, Float32x4>, Float32x4>), 1, 0, ReciprocalSqrt)                \
   V(splat, (FuncSplat<Float32x4>), 1, 0, Splat)                                                                   \
   V(sqrt, (Func<Float32x4, Sqrt<float, Float32x4>, Float32x4>), 1, 0, Sqrt)                                       \
   V(toInt32x4, (FuncConvert<Float32x4, Int32x4>), 1, 0, ToInt32x4)
 
 #define FLOAT32X4_BINARY_FUNCTION_LIST(V)                                                                         \
   V(add, (Func<Float32x4, Add<float, Float32x4>, Float32x4>), 2, 0, Add)                                          \
+  V(and, (CoercedFunc<Float32x4, Int32x4, And<int32_t, Int32x4>, Float32x4>), 2, 0, And)                          \
   V(div, (Func<Float32x4, Div<float, Float32x4>, Float32x4>), 2, 0, Div)                                          \
   V(equal, (Func<Float32x4, Equal<float, Int32x4>, Int32x4>), 2, 0, Equal)                                        \
   V(greaterThan, (Func<Float32x4, GreaterThan<float, Int32x4>, Int32x4>), 2, 0, GreaterThan)                      \
   V(greaterThanOrEqual, (Func<Float32x4, GreaterThanOrEqual<float, Int32x4>, Int32x4>), 2, 0, GreaterThanOrEqual) \
   V(lessThan, (Func<Float32x4, LessThan<float, Int32x4>, Int32x4>), 2, 0, LessThan)                               \
   V(lessThanOrEqual, (Func<Float32x4, LessThanOrEqual<float, Int32x4>, Int32x4>), 2, 0, LessThanOrEqual)          \
   V(max, (Func<Float32x4, Maximum<float, Float32x4>, Float32x4>), 2, 0, Max)                                      \
   V(min, (Func<Float32x4, Minimum<float, Float32x4>, Float32x4>), 2, 0, Min)                                      \
   V(mul, (Func<Float32x4, Mul<float, Float32x4>, Float32x4>), 2, 0, Mul)                                          \
   V(notEqual, (Func<Float32x4, NotEqual<float, Int32x4>, Int32x4>), 2, 0, NotEqual)                               \
+  V(or, (CoercedFunc<Float32x4, Int32x4, Or<int32_t, Int32x4>, Float32x4>), 2, 0, Or)                             \
   V(shuffle, (FuncShuffle<Float32x4, Shuffle<float, Float32x4>, Float32x4>), 2, 0, Shuffle)                       \
   V(scale, (FuncWith<Float32x4, Scale<float, Float32x4>, Float32x4>), 2, 0, Scale)                                \
   V(sub, (Func<Float32x4, Sub<float, Float32x4>, Float32x4>), 2, 0, Sub)                                          \
   V(withX, (FuncWith<Float32x4, WithX<float, Float32x4>, Float32x4>), 2, 0, WithX)                                \
   V(withY, (FuncWith<Float32x4, WithY<float, Float32x4>, Float32x4>), 2, 0, WithY)                                \
   V(withZ, (FuncWith<Float32x4, WithZ<float, Float32x4>, Float32x4>), 2, 0, WithZ)                                \
-  V(withW, (FuncWith<Float32x4, WithW<float, Float32x4>, Float32x4>), 2, 0, WithW)
+  V(withW, (FuncWith<Float32x4, WithW<float, Float32x4>, Float32x4>), 2, 0, WithW)                                \
+  V(xor, (CoercedFunc<Float32x4, Int32x4, Xor<int32_t, Int32x4>, Float32x4>), 2, 0, Xor)
 
 #define FLOAT32X4_TERNARY_FUNCTION_LIST(V)                                                                        \
   V(clamp, Float32x4Clamp, 3, 0, Clamp)                                                                           \
   V(shuffleMix, (FuncShuffle<Float32x4, Shuffle<float, Float32x4>, Float32x4>), 3, 0, ShuffleMix)
 
 #define FLOAT32X4_FUNCTION_LIST(V)                                                                                \
   FLOAT32X4_NULLARY_FUNCTION_LIST(V)                                                                              \
   FLOAT32X4_UNARY_FUNCTION_LIST(V)                                                                                \
@@ -69,19 +73,25 @@
   V(neg, (Func<Int32x4, Neg<int32_t, Int32x4>, Int32x4>), 1, 0, Neg)                                              \
   V(not, (Func<Int32x4, Not<int32_t, Int32x4>, Int32x4>), 1, 0, Not)                                              \
   V(splat, (FuncSplat<Int32x4>), 0, 0, Splat)                                                                     \
   V(toFloat32x4, (FuncConvert<Int32x4, Float32x4>), 1, 0, ToFloat32x4)
 
 #define INT32X4_BINARY_FUNCTION_LIST(V)                                                                           \
   V(add, (Func<Int32x4, Add<int32_t, Int32x4>, Int32x4>), 2, 0, Add)                                              \
   V(and, (Func<Int32x4, And<int32_t, Int32x4>, Int32x4>), 2, 0, And)                                              \
+  V(equal, (Func<Int32x4, Equal<int32_t, Int32x4>, Int32x4>), 2, 0, Equal)                                        \
+  V(greaterThan, (Func<Int32x4, GreaterThan<int32_t, Int32x4>, Int32x4>), 2, 0, GreaterThan)                      \
+  V(lessThan, (Func<Int32x4, LessThan<int32_t, Int32x4>, Int32x4>), 2, 0, LessThan)                               \
   V(mul, (Func<Int32x4, Mul<int32_t, Int32x4>, Int32x4>), 2, 0, Mul)                                              \
   V(or, (Func<Int32x4, Or<int32_t, Int32x4>, Int32x4>), 2, 0, Or)                                                 \
   V(sub, (Func<Int32x4, Sub<int32_t, Int32x4>, Int32x4>), 2, 0, Sub)                                              \
+  V(shiftLeft, (Int32x4BinaryScalar<ShiftLeft>), 2, 0, ShiftLeft)                                                 \
+  V(shiftRight, (Int32x4BinaryScalar<ShiftRight>), 2, 0, ShiftRight)                                              \
+  V(shiftRightLogical, (Int32x4BinaryScalar<ShiftRightLogical>), 2, 0, ShiftRightLogical)                         \
   V(shuffle, (FuncShuffle<Int32x4, Shuffle<int32_t, Int32x4>, Int32x4>), 2, 0, Shuffle)                           \
   V(withFlagX, (FuncWith<Int32x4, WithFlagX<int32_t, Int32x4>, Int32x4>), 2, 0, WithFlagX)                        \
   V(withFlagY, (FuncWith<Int32x4, WithFlagY<int32_t, Int32x4>, Int32x4>), 2, 0, WithFlagY)                        \
   V(withFlagZ, (FuncWith<Int32x4, WithFlagZ<int32_t, Int32x4>, Int32x4>), 2, 0, WithFlagZ)                        \
   V(withFlagW, (FuncWith<Int32x4, WithFlagW<int32_t, Int32x4>, Int32x4>), 2, 0, WithFlagW)                        \
   V(withX, (FuncWith<Int32x4, WithX<int32_t, Int32x4>, Int32x4>), 2, 0, WithX)                                    \
   V(withY, (FuncWith<Int32x4, WithY<int32_t, Int32x4>, Int32x4>), 2, 0, WithY)                                    \
   V(withZ, (FuncWith<Int32x4, WithZ<int32_t, Int32x4>, Int32x4>), 2, 0, WithZ)                                    \
@@ -111,17 +121,17 @@ class SIMDObject : public JSObject
     static JSObject* initClass(JSContext *cx, Handle<GlobalObject *> global);
     static bool toString(JSContext *cx, unsigned int argc, jsval *vp);
 };
 
 // These classes exist for use with templates below.
 
 struct Float32x4 {
     typedef float Elem;
-    static const int32_t lanes = 4;
+    static const unsigned lanes = 4;
     static const X4TypeDescr::Type type = X4TypeDescr::TYPE_FLOAT32;
 
     static TypeDescr &GetTypeDescr(GlobalObject &global) {
         return global.float32x4TypeDescr().as<TypeDescr>();
     }
     static Elem toType(Elem a) {
         return a;
     }
@@ -131,17 +141,17 @@ struct Float32x4 {
     }
     static void setReturn(CallArgs &args, Elem value) {
         args.rval().setDouble(JS::CanonicalizeNaN(value));
     }
 };
 
 struct Int32x4 {
     typedef int32_t Elem;
-    static const int32_t lanes = 4;
+    static const unsigned lanes = 4;
     static const X4TypeDescr::Type type = X4TypeDescr::TYPE_INT32;
 
     static TypeDescr &GetTypeDescr(GlobalObject &global) {
         return global.int32x4TypeDescr().as<TypeDescr>();
     }
     static Elem toType(Elem a) {
         return ToInt32(a);
     }
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -766,20 +766,19 @@ StoreTypedArrayPolicy::adjustValueInput(
             value = MToDouble::New(alloc, value);
             ins->block()->insertBefore(ins, value->toInstruction());
         }
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid array type");
     }
 
-    if (value != curValue) {
+    if (value != curValue)
         ins->replaceOperand(valueOperand, value);
-        curValue = value;
-    }
+
     return true;
 }
 
 bool
 StoreTypedArrayPolicy::adjustInputs(TempAllocator &alloc, MInstruction *ins)
 {
     MStoreTypedArrayElement *store = ins->toStoreTypedArrayElement();
     JS_ASSERT(store->elements()->type() == MIRType_Elements);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4and.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'float32x4 and';
+
+var andf = (function() {
+    var i = new Int32Array(3);
+    var f = new Float32Array(i.buffer);
+    return function(x, y) {
+        f[0] = x;
+        f[1] = y;
+        i[2] = i[0] & i[1];
+        return f[2];
+    }
+})();
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = float32x4(1, 2, 3, 4);
+  var b = float32x4(10, 20, 30, 40);
+  var c = SIMD.float32x4.and(a, b);
+  assertEq(c.x, andf(1, 10));
+  assertEq(c.y, andf(2, 20));
+  assertEq(c.z, andf(3, 30));
+  assertEq(c.w, andf(4, 40));
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4not.js
@@ -0,0 +1,35 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'float32x4 not';
+
+var notf = (function() {
+    var i = new Int32Array(1);
+    var f = new Float32Array(i.buffer);
+    return function(x) {
+        f[0] = x;
+        i[0] = ~i[0];
+        return f[0];
+    }
+})();
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = float32x4(2, 13, -37, 4.2);
+  var c = SIMD.float32x4.not(a);
+  assertEq(c.x, notf(2));
+  assertEq(c.y, notf(13));
+  assertEq(c.z, notf(-37));
+  assertEq(c.w, notf(4.2));
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4or.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'float32x4 and';
+
+var orf = (function() {
+    var i = new Int32Array(3);
+    var f = new Float32Array(i.buffer);
+    return function(x, y) {
+        f[0] = x;
+        f[1] = y;
+        i[2] = i[0] | i[1];
+        return f[2];
+    }
+})();
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = float32x4(1, 2, 3, 4);
+  var b = float32x4(10, 20, 30, 40);
+  var c = SIMD.float32x4.or(a, b);
+  assertEq(c.x, orf(1, 10));
+  assertEq(c.y, orf(2, 20));
+  assertEq(c.z, orf(3, 30));
+  assertEq(c.w, orf(4, 40));
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4xor.js
@@ -0,0 +1,37 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'float32x4 and';
+
+var xorf = (function() {
+    var i = new Int32Array(3);
+    var f = new Float32Array(i.buffer);
+    return function(x, y) {
+        f[0] = x;
+        f[1] = y;
+        i[2] = i[0] ^ i[1];
+        return f[2];
+    }
+})();
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = float32x4(1, 2, 3, 4);
+  var b = float32x4(10, 20, 30, 40);
+  var c = SIMD.float32x4.xor(a, b);
+  assertEq(c.x, xorf(1, 10));
+  assertEq(c.y, xorf(2, 20));
+  assertEq(c.z, xorf(3, 30));
+  assertEq(c.w, xorf(4, 40));
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4equal.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 equal';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = int32x4(1, 20, 30, 40);
+  var b = int32x4(10, 20, 30, 4);
+  var c = SIMD.int32x4.equal(a, b);
+  assertEq(c.x, 0);
+  assertEq(c.y, -1);
+  assertEq(c.z, -1);
+  assertEq(c.w, 0);
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4greaterthan.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 greaterThan';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = int32x4(1, 20, 3, 40);
+  var b = int32x4(10, 2, 30, 4);
+  var c = SIMD.int32x4.greaterThan(b,a);
+  assertEq(c.x, -1);
+  assertEq(c.y, 0);
+  assertEq(c.z, -1);
+  assertEq(c.w, 0);
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4lessthan.js
@@ -0,0 +1,26 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 lessThan';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  var a = int32x4(1, 20, 3, 40);
+  var b = int32x4(10, 2, 30, 4);
+  var c = SIMD.int32x4.lessThan(a, b);
+  assertEq(c.x, -1);
+  assertEq(c.y, 0);
+  assertEq(c.z, -1);
+  assertEq(c.w, 0);
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4lsh.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 lsh';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  for (var bits = 0; bits < 32; bits++) {
+      var a = int32x4(-1, 2, -3, 4);
+      var c = SIMD.int32x4.shiftLeft(a, bits);
+      assertEq(c.x, -1 << bits);
+      assertEq(c.y, 2 << bits);
+      assertEq(c.z, -3 << bits);
+      assertEq(c.w, 4 << bits);
+  }
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4rsh.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 rsh';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  for (var bits = 0; bits < 32; bits++) {
+      var a = int32x4(-1, 2, -3, 4);
+      var c = SIMD.int32x4.shiftRight(a, bits);
+      assertEq(c.x, -1 >> bits);
+      assertEq(c.y, 2 >> bits);
+      assertEq(c.z, -3 >> bits);
+      assertEq(c.w, 4 >> bits);
+  }
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4ursh.js
@@ -0,0 +1,27 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+var BUGNUMBER = 996076;
+var float32x4 = SIMD.float32x4;
+var int32x4 = SIMD.int32x4;
+
+var summary = 'int32x4 ursh';
+
+function test() {
+  print(BUGNUMBER + ": " + summary);
+
+  // FIXME -- Bug 948379: Amend to check for correctness of border cases.
+
+  for (var bits = 0; bits < 32; bits++) {
+      var a = int32x4(-1, 2, -3, 4);
+      var c = SIMD.int32x4.shiftRightLogical(a, bits);
+      assertEq(c.x >>> 0, -1 >>> bits);
+      assertEq(c.y >>> 0, 2 >>> bits);
+      assertEq(c.z >>> 0, -3 >>> bits);
+      assertEq(c.w >>> 0, 4 >>> bits);
+  }
+
+  if (typeof reportCompare === "function")
+    reportCompare(true, true);
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/number/number-significant-fractional-digits-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="number" value="1.00100000000001" style="width:100%;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/number/number-significant-fractional-digits.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <script>
+
+document.addEventListener("MozReftestInvalidate", function() {
+  document.getElementById("i").stepUp();
+  document.documentElement.className = "";
+}, false);
+
+    </script>
+  </head>
+  <body>
+    <input id="i" type="number" value="1.001" step="0.00000000000001"
+           style="width:100%;">
+  </body>
+</html>
--- a/layout/reftests/forms/input/number/reftest.list
+++ b/layout/reftests/forms/input/number/reftest.list
@@ -24,16 +24,19 @@ fuzzy-if(/^Windows\x20NT\x205\.1/.test(h
 == number-auto-width-1.html number-auto-width-1-ref.html
 
 # min-height/max-height tests:
 skip-if(B2G||Android) == number-min-height-1.html number-min-height-1-ref.html
 skip-if(B2G||Android) == number-min-height-2.html number-min-height-2-ref.html
 skip-if(B2G||Android) == number-max-height-1.html number-max-height-1-ref.html
 skip-if(B2G||Android) == number-max-height-2.html number-max-height-2-ref.html
 
+# number of significant fractional digits:
+== number-significant-fractional-digits.html number-significant-fractional-digits-ref.html
+
 # focus
 # autofocus is disabled on B2G
 # https://bugzilla.mozilla.org/show_bug.cgi?id=965763
 skip-if(B2G) needs-focus == focus-handling.html focus-handling-ref.html
 
 # select
 == number-selected.html number-selected-ref.html
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -521,35 +521,36 @@ abstract public class BrowserApp extends
             mBrowserSearch.setUserVisibleHint(false);
         }
 
         setBrowserToolbarListeners();
 
         mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
         mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
 
-        registerEventListener("CharEncoding:Data");
-        registerEventListener("CharEncoding:State");
-        registerEventListener("Feedback:LastUrl");
-        registerEventListener("Feedback:OpenPlayStore");
-        registerEventListener("Feedback:MaybeLater");
-        registerEventListener("Telemetry:Gather");
-        registerEventListener("Settings:Show");
-        registerEventListener("Updater:Launch");
-        registerEventListener("Menu:Add");
-        registerEventListener("Menu:Remove");
-        registerEventListener("Menu:Update");
-        registerEventListener("Accounts:Create");
-        registerEventListener("Accounts:Exist");
-        registerEventListener("Prompt:ShowTop");
-        registerEventListener("Reader:ListStatusRequest");
-        registerEventListener("Reader:Added");
-        registerEventListener("Reader:Removed");
-        registerEventListener("Reader:Share");
-        registerEventListener("Reader:FaviconRequest");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "CharEncoding:Data",
+            "CharEncoding:State",
+            "Feedback:LastUrl",
+            "Feedback:OpenPlayStore",
+            "Feedback:MaybeLater",
+            "Telemetry:Gather",
+            "Settings:Show",
+            "Updater:Launch",
+            "Menu:Add",
+            "Menu:Remove",
+            "Menu:Update",
+            "Accounts:Create",
+            "Accounts:Exist",
+            "Prompt:ShowTop",
+            "Reader:ListStatusRequest",
+            "Reader:Added",
+            "Reader:Removed",
+            "Reader:Share",
+            "Reader:FaviconRequest");
 
         Distribution.init(this);
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
         mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
@@ -601,24 +602,24 @@ abstract public class BrowserApp extends
         }
 
         super.onBackPressed();
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        unregisterEventListener("Prompt:ShowTop");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Prompt:ShowTop");
     }
 
     @Override
     public void onPause() {
         super.onPause();
         // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
-        registerEventListener("Prompt:ShowTop");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Prompt:ShowTop");
     }
 
     private void setBrowserToolbarListeners() {
         mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
             public void onActivate() {
                 enterEditingMode();
             }
         });
@@ -880,34 +881,36 @@ abstract public class BrowserApp extends
             mOrderedBroadcastHelper = null;
         }
 
         if (mBrowserHealthReporter != null) {
             mBrowserHealthReporter.uninit();
             mBrowserHealthReporter = null;
         }
 
-        unregisterEventListener("CharEncoding:Data");
-        unregisterEventListener("CharEncoding:State");
-        unregisterEventListener("Feedback:LastUrl");
-        unregisterEventListener("Feedback:OpenPlayStore");
-        unregisterEventListener("Feedback:MaybeLater");
-        unregisterEventListener("Telemetry:Gather");
-        unregisterEventListener("Settings:Show");
-        unregisterEventListener("Updater:Launch");
-        unregisterEventListener("Menu:Add");
-        unregisterEventListener("Menu:Remove");
-        unregisterEventListener("Menu:Update");
-        unregisterEventListener("Accounts:Create");
-        unregisterEventListener("Accounts:Exist");
-        unregisterEventListener("Reader:ListStatusRequest");
-        unregisterEventListener("Reader:Added");
-        unregisterEventListener("Reader:Removed");
-        unregisterEventListener("Reader:Share");
-        unregisterEventListener("Reader:FaviconRequest");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "CharEncoding:Data",
+            "CharEncoding:State",
+            "Feedback:LastUrl",
+            "Feedback:OpenPlayStore",
+            "Feedback:MaybeLater",
+            "Telemetry:Gather",
+            "Settings:Show",
+            "Updater:Launch",
+            "Menu:Add",
+            "Menu:Remove",
+            "Menu:Update",
+            "Accounts:Create",
+            "Accounts:Exist",
+            "Prompt:ShowTop",
+            "Reader:ListStatusRequest",
+            "Reader:Added",
+            "Reader:Removed",
+            "Reader:Share",
+            "Reader:FaviconRequest");
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 // null this out even though the docs say it's not needed,
                 // because the source code looks like it will only do this
                 // automatically on API 14+
                 nfc.setNdefPushMessageCallback(null, this);
@@ -1682,17 +1685,17 @@ abstract public class BrowserApp extends
                       ((engine == null) ? "null" : engine.name) +
                       ", " + where);
         try {
             String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
             JSONObject message = new JSONObject();
             message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
             message.put("location", where);
             message.put("identifier", identifier);
-            GeckoAppShell.getEventDispatcher().dispatchEvent(message, null);
+            EventDispatcher.getInstance().dispatchEvent(message, null);
         } catch (Exception e) {
             Log.w(LOGTAG, "Error recording search.", e);
         }
     }
 
     void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
         if (TextUtils.isEmpty(searchTerm)) {
             hideBrowserSearch();
--- a/mobile/android/base/ContactService.java
+++ b/mobile/android/base/ContactService.java
@@ -94,31 +94,33 @@ public class ContactService implements G
     private GeckoApp mActivity;
 
     ContactService(EventDispatcher eventDispatcher, GeckoApp activity) {
         mEventDispatcher = eventDispatcher;
         mActivity = activity;
         mContentResolver = mActivity.getContentResolver();
         mGotDeviceAccount = false;
 
-        registerEventListener("Android:Contacts:Clear");
-        registerEventListener("Android:Contacts:Find");
-        registerEventListener("Android:Contacts:GetAll");
-        registerEventListener("Android:Contacts:GetCount");
-        registerEventListener("Android:Contact:Remove");
-        registerEventListener("Android:Contact:Save");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Android:Contacts:Clear",
+            "Android:Contacts:Find",
+            "Android:Contacts:GetAll",
+            "Android:Contacts:GetCount",
+            "Android:Contact:Remove",
+            "Android:Contact:Save");
     }
 
     public void destroy() {
-        unregisterEventListener("Android:Contacts:Clear");
-        unregisterEventListener("Android:Contacts:Find");
-        unregisterEventListener("Android:Contacts:GetAll");
-        unregisterEventListener("Android:Contacts:GetCount");
-        unregisterEventListener("Android:Contact:Remove");
-        unregisterEventListener("Android:Contact:Save");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Android:Contacts:Clear",
+            "Android:Contacts:Find",
+            "Android:Contacts:GetAll",
+            "Android:Contacts:GetCount",
+            "Android:Contact:Remove",
+            "Android:Contact:Save");
     }
 
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         // If the account chooser dialog needs shown to the user, the message handling becomes
         // asychronous so it needs posted to a background thread from the UI thread when the
         // account chooser dialog is dismissed by the user.
         Runnable handleMessage = new Runnable() {
@@ -1502,24 +1504,16 @@ public class ContactService implements G
             }
 
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, callbackMessage.toString()));
         } catch (JSONException e) {
             throw new IllegalArgumentException(e);
         }
     }
 
-    private void registerEventListener(final String event) {
-        mEventDispatcher.registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(final String event) {
-        mEventDispatcher.unregisterEventListener(event, this);
-    }
-
     private ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
         try {
             return mContentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
         } catch (RemoteException e) {
             Log.e(LOGTAG, "RemoteException", e);
         } catch (OperationApplicationException e) {
             Log.e(LOGTAG, "OperationApplicationException", e);
         }
--- a/mobile/android/base/DoorHangerPopup.java
+++ b/mobile/android/base/DoorHangerPopup.java
@@ -35,24 +35,26 @@ public class DoorHangerPopup extends Arr
     // Whether or not the doorhanger popup is disabled.
     private boolean mDisabled;
 
     DoorHangerPopup(GeckoApp activity) {
         super(activity);
 
         mDoorHangers = new HashSet<DoorHanger>();
 
-        registerEventListener("Doorhanger:Add");
-        registerEventListener("Doorhanger:Remove");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Doorhanger:Add",
+            "Doorhanger:Remove");
         Tabs.registerOnTabsChangedListener(this);
     }
 
     void destroy() {
-        unregisterEventListener("Doorhanger:Add");
-        unregisterEventListener("Doorhanger:Remove");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Doorhanger:Add",
+            "Doorhanger:Remove");
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     /**
      * Temporarily disables the doorhanger popup. If the popup is disabled,
      * it will not be shown to the user, but it will continue to process
      * calls to add/remove doorhanger notifications.
      */
@@ -331,24 +333,16 @@ public class DoorHangerPopup extends Arr
                 lastVisibleDoorHanger = dh;
             }
         }
         if (lastVisibleDoorHanger != null) {
             lastVisibleDoorHanger.hideDivider();
         }
     }
 
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
-    }
-
     @Override
     public void dismiss() {
         // If the popup is focusable while it is hidden, we run into crashes
         // on pre-ICS devices when the popup gets focus before it is shown.
         setFocusable(false);
         super.dismiss();
     }
 }
--- a/mobile/android/base/EventDispatcher.java
+++ b/mobile/android/base/EventDispatcher.java
@@ -1,52 +1,63 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
+@RobocopTarget
 public final class EventDispatcher {
     private static final String LOGTAG = "GeckoEventDispatcher";
     private static final String GUID = "__guid__";
     private static final String STATUS_CANCEL = "cancel";
     private static final String STATUS_ERROR = "error";
     private static final String STATUS_SUCCESS = "success";
 
+    private static final EventDispatcher INSTANCE = new EventDispatcher();
+
     /**
      * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size
      * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to
      * empirically determine the initial capacity that avoids rehashing, we need to
      * determine the initial size, divide it by 75%, and round up to the next power-of-2.
      */
     private static final int GECKO_NATIVE_EVENTS_COUNT = 0; // Default for HashMap
     private static final int GECKO_JSON_EVENTS_COUNT = 256; // Empirically measured
 
     private final Map<String, List<NativeEventListener>> mGeckoThreadNativeListeners =
         new HashMap<String, List<NativeEventListener>>(GECKO_NATIVE_EVENTS_COUNT);
     private final Map<String, List<GeckoEventListener>> mGeckoThreadJSONListeners =
         new HashMap<String, List<GeckoEventListener>>(GECKO_JSON_EVENTS_COUNT);
 
+    public static EventDispatcher getInstance() {
+        return INSTANCE;
+    }
+
+    private EventDispatcher() {
+    }
+
     private <T> void registerListener(final Class<? extends List<T>> listType,
                                       final Map<String, List<T>> listenersMap,
                                       final T listener,
                                       final String[] events) {
         try {
             synchronized (listenersMap) {
                 for (final String event : events) {
                     List<T> listeners = listenersMap.get(event);
@@ -104,45 +115,35 @@ public final class EventDispatcher {
         // iterating the list outside of the synchronized block, we use a
         // CopyOnWriteArrayList.
         registerListener((Class)CopyOnWriteArrayList.class,
                          mGeckoThreadNativeListeners, listener, events);
     }
 
     @Deprecated // Use NativeEventListener instead
     @SuppressWarnings("unchecked")
-    private void registerGeckoThreadListener(final GeckoEventListener listener,
-                                             final String... events) {
+    public void registerGeckoThreadListener(final GeckoEventListener listener,
+                                            final String... events) {
         checkNotRegistered(mGeckoThreadNativeListeners, events);
 
         registerListener((Class)CopyOnWriteArrayList.class,
                          mGeckoThreadJSONListeners, listener, events);
     }
 
     public void unregisterGeckoThreadListener(final NativeEventListener listener,
                                               final String... events) {
         unregisterListener(mGeckoThreadNativeListeners, listener, events);
     }
 
     @Deprecated // Use NativeEventListener instead
-    private void unregisterGeckoThreadListener(final GeckoEventListener listener,
-                                               final String... events) {
+    public void unregisterGeckoThreadListener(final GeckoEventListener listener,
+                                              final String... events) {
         unregisterListener(mGeckoThreadJSONListeners, listener, events);
     }
 
-    @Deprecated // Use one of the variants above.
-    public void registerEventListener(final String event, final GeckoEventListener listener) {
-        registerGeckoThreadListener(listener, event);
-    }
-
-    @Deprecated // Use one of the variants above
-    public void unregisterEventListener(final String event, final GeckoEventListener listener) {
-        unregisterGeckoThreadListener(listener, event);
-    }
-
     public void dispatchEvent(final NativeJSContainer message) {
         EventCallback callback = null;
         try {
             // First try native listeners.
             final String type = message.getString("type");
 
             final List<NativeEventListener> listeners;
             synchronized (mGeckoThreadNativeListeners) {
--- a/mobile/android/base/FilePicker.java
+++ b/mobile/android/base/FilePicker.java
@@ -42,17 +42,17 @@ public class FilePicker implements Gecko
     public static void init(Context context) {
         if (sFilePicker == null) {
             sFilePicker = new FilePicker(context.getApplicationContext());
         }
     }
 
     protected FilePicker(Context context) {
         this.context = context;
-        GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "FilePicker:Show");
     }
 
     @Override
     public void handleMessage(String event, final JSONObject message) {
         if (event.equals("FilePicker:Show")) {
             String mimeType = "*/*";
             final String mode = message.optString("mode");
             final int tabId = message.optInt("tabId", -1);
--- a/mobile/android/base/FindInPageBar.java
+++ b/mobile/android/base/FindInPageBar.java
@@ -54,17 +54,17 @@ public class FindInPageBar extends Linea
                     hide();
                     return true;
                 }
                 return false;
             }
         });
 
         mInflated = true;
-        GeckoAppShell.getEventDispatcher().registerEventListener("TextSelection:Data", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "TextSelection:Data");
     }
 
     public void show() {
         if (!mInflated)
             inflateContent();
 
         setVisibility(VISIBLE);
         mFindText.requestFocus();
@@ -83,17 +83,17 @@ public class FindInPageBar extends Linea
         Context context = view.getContext();
         return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
      }
 
     public void onDestroy() {
         if (!mInflated) {
             return;
         }
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("TextSelection:Data", this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "TextSelection:Data");
     }
 
     // TextWatcher implementation
 
     @Override
     public void afterTextChanged(Editable s) {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Find", s.toString()));
     }
--- a/mobile/android/base/FormAssistPopup.java
+++ b/mobile/android/base/FormAssistPopup.java
@@ -80,25 +80,27 @@ public class FormAssistPopup extends Rel
         super(context, attrs);
         mContext = context;
 
         mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in);
         mAnimation.setDuration(75);
 
         setFocusable(false);
 
-        registerEventListener("FormAssist:AutoComplete");
-        registerEventListener("FormAssist:ValidationMessage");
-        registerEventListener("FormAssist:Hide");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "FormAssist:AutoComplete",
+            "FormAssist:ValidationMessage",
+            "FormAssist:Hide");
     }
 
     void destroy() {
-        unregisterEventListener("FormAssist:AutoComplete");
-        unregisterEventListener("FormAssist:ValidationMessage");
-        unregisterEventListener("FormAssist:Hide");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "FormAssist:AutoComplete",
+            "FormAssist:ValidationMessage",
+            "FormAssist:Hide");
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("FormAssist:AutoComplete")) {
                 handleAutoCompleteMessage(message);
             } else if (event.equals("FormAssist:ValidationMessage")) {
@@ -392,17 +394,9 @@ public class FormAssistPopup extends Rel
             itemView.setText(item.first);
 
             // Set a tag with the suggestion value
             itemView.setTag(item.second);
 
             return convertView;
         }
     }
-
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
-    }
 }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1264,17 +1264,17 @@ public abstract class GeckoApp
                 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
 
                 editor.commit();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
-                final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+                final EventDispatcher dispatcher = EventDispatcher.getInstance();
                 Log.i(LOGTAG, "Creating HealthRecorder.");
 
                 final String osLocale = Locale.getDefault().toString();
                 String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this);
                 Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
 
                 if (appLocale == null) {
                     appLocale = osLocale;
@@ -1345,17 +1345,17 @@ public abstract class GeckoApp
                 ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
             } else {
                 mCameraView = new TextureView(this);
             }
         }
 
         if (mLayerView == null) {
             LayerView layerView = (LayerView) findViewById(R.id.layer_view);
-            layerView.initializeView(GeckoAppShell.getEventDispatcher());
+            layerView.initializeView(EventDispatcher.getInstance());
             mLayerView = layerView;
             GeckoAppShell.setLayerView(layerView);
             // bind the GeckoEditable instance to the new LayerView
             GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
         }
     }
 
     /**
@@ -1484,58 +1484,59 @@ public abstract class GeckoApp
             settingsIntent.putExtras(intent);
             startActivity(settingsIntent);
         }
 
         //app state callbacks
         mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
 
         //register for events
-        registerEventListener("log");
-        registerEventListener("onCameraCapture");
-        registerEventListener("Gecko:Ready");
-        registerEventListener("Gecko:DelayedStartup");
-        registerEventListener("Toast:Show");
-        registerEventListener("DOMFullScreen:Start");
-        registerEventListener("DOMFullScreen:Stop");
-        registerEventListener("ToggleChrome:Hide");
-        registerEventListener("ToggleChrome:Show");
-        registerEventListener("ToggleChrome:Focus");
-        registerEventListener("Permissions:Data");
-        registerEventListener("Session:StatePurged");
-        registerEventListener("Bookmark:Insert");
-        registerEventListener("Accessibility:Event");
-        registerEventListener("Accessibility:Ready");
-        registerEventListener("Shortcut:Remove");
-        registerEventListener("Share:Text");
-        registerEventListener("Image:SetAs");
-        registerEventListener("Sanitize:ClearHistory");
-        registerEventListener("Update:Check");
-        registerEventListener("Update:Download");
-        registerEventListener("Update:Install");
-        registerEventListener("PrivateBrowsing:Data");
-        registerEventListener("Contact:Add");
-        registerEventListener("Locale:Set");
-        registerEventListener("NativeApp:IsDebuggable");
-        registerEventListener("SystemUI:Visibility");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "log",
+            "onCameraCapture",
+            "Gecko:Ready",
+            "Gecko:DelayedStartup",
+            "Toast:Show",
+            "DOMFullScreen:Start",
+            "DOMFullScreen:Stop",
+            "ToggleChrome:Hide",
+            "ToggleChrome:Show",
+            "ToggleChrome:Focus",
+            "Permissions:Data",
+            "Session:StatePurged",
+            "Bookmark:Insert",
+            "Accessibility:Event",
+            "Accessibility:Ready",
+            "Shortcut:Remove",
+            "Share:Text",
+            "Image:SetAs",
+            "Sanitize:ClearHistory",
+            "Update:Check",
+            "Update:Download",
+            "Update:Install",
+            "PrivateBrowsing:Data",
+            "Contact:Add",
+            "Locale:Set",
+            "NativeApp:IsDebuggable",
+            "SystemUI:Visibility");
 
         EventListener.registerEvents();
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
-        mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
+        mContactService = new ContactService(EventDispatcher.getInstance(), this);
 
         mPromptService = new PromptService(this);
 
         mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
                                            (TextSelectionHandle) findViewById(R.id.middle_handle),
                                            (TextSelectionHandle) findViewById(R.id.end_handle),
-                                           GeckoAppShell.getEventDispatcher(),
+                                           EventDispatcher.getInstance(),
                                            this);
 
         PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, String value) {
                 UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
             }
         });
 
@@ -2017,43 +2018,44 @@ public abstract class GeckoApp
         });
 
         super.onRestart();
     }
 
     @Override
     public void onDestroy()
     {
-        unregisterEventListener("log");
-        unregisterEventListener("onCameraCapture");
-        unregisterEventListener("Gecko:Ready");
-        unregisterEventListener("Gecko:DelayedStartup");
-        unregisterEventListener("Toast:Show");
-        unregisterEventListener("DOMFullScreen:Start");
-        unregisterEventListener("DOMFullScreen:Stop");
-        unregisterEventListener("ToggleChrome:Hide");
-        unregisterEventListener("ToggleChrome:Show");
-        unregisterEventListener("ToggleChrome:Focus");
-        unregisterEventListener("Permissions:Data");
-        unregisterEventListener("Session:StatePurged");
-        unregisterEventListener("Bookmark:Insert");
-        unregisterEventListener("Accessibility:Event");
-        unregisterEventListener("Accessibility:Ready");
-        unregisterEventListener("Shortcut:Remove");
-        unregisterEventListener("Share:Text");
-        unregisterEventListener("Image:SetAs");
-        unregisterEventListener("Sanitize:ClearHistory");
-        unregisterEventListener("Update:Check");
-        unregisterEventListener("Update:Download");
-        unregisterEventListener("Update:Install");
-        unregisterEventListener("PrivateBrowsing:Data");
-        unregisterEventListener("Contact:Add");
-        unregisterEventListener("Locale:Set");
-        unregisterEventListener("NativeApp:IsDebuggable");
-        unregisterEventListener("SystemUI:Visibility");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "log",
+            "onCameraCapture",
+            "Gecko:Ready",
+            "Gecko:DelayedStartup",
+            "Toast:Show",
+            "DOMFullScreen:Start",
+            "DOMFullScreen:Stop",
+            "ToggleChrome:Hide",
+            "ToggleChrome:Show",
+            "ToggleChrome:Focus",
+            "Permissions:Data",
+            "Session:StatePurged",
+            "Bookmark:Insert",
+            "Accessibility:Event",
+            "Accessibility:Ready",
+            "Shortcut:Remove",
+            "Share:Text",
+            "Image:SetAs",
+            "Sanitize:ClearHistory",
+            "Update:Check",
+            "Update:Download",
+            "Update:Install",
+            "PrivateBrowsing:Data",
+            "Contact:Add",
+            "Locale:Set",
+            "NativeApp:IsDebuggable",
+            "SystemUI:Visibility");
 
         EventListener.unregisterEvents();
 
         deleteTempFiles();
 
         if (mLayerView != null)
             mLayerView.destroy();
         if (mDoorHangerPopup != null)
@@ -2089,24 +2091,16 @@ public abstract class GeckoApp
 
         Favicons.close();
 
         super.onDestroy();
 
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
-    protected void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    protected void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
-    }
-
     // Get a temporary directory, may return null
     public static File getTempDirectory() {
         File dir = GeckoApplication.get().getExternalFilesDir("temp");
         return dir;
     }
 
     // Delete any files in our temporary directory
     public static void deleteTempFiles() {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -130,18 +130,16 @@ public class GeckoAppShell
     private static final int HIGH_MEMORY_DEVICE_THRESHOLD_MB = 768;
 
     public static final String SHORTCUT_TYPE_WEBAPP = "webapp";
     public static final String SHORTCUT_TYPE_BOOKMARK = "bookmark";
 
     static private int sDensityDpi = 0;
     static private int sScreenDepth = 0;
 
-    private static final EventDispatcher sEventDispatcher = new EventDispatcher();
-
     /* Default colors. */
     private static final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
 
     /* Is the value in sVibrationEndTime valid? */
     private static boolean sVibrationMaybePlaying = false;
 
     /* Time (in System.nanoTime() units) when the currently-playing vibration
      * is scheduled to end.  This value is valid only when
@@ -2277,55 +2275,27 @@ public class GeckoAppShell
         if (sCamera != null) {
             sCamera.stopPreview();
             sCamera.release();
             sCamera = null;
             sCameraBuffer = null;
         }
     }
 
-    /**
-     * Adds a listener for a gecko event.
-     * This method is thread-safe and may be called at any time. In particular, calling it
-     * with an event that is currently being processed has the properly-defined behaviour that
-     * any added listeners will not be invoked on the event currently being processed, but
-     * will be invoked on future events of that type.
-     */
-    @RobocopTarget
-    public static void registerEventListener(String event, GeckoEventListener listener) {
-        sEventDispatcher.registerEventListener(event, listener);
-    }
-
-    public static EventDispatcher getEventDispatcher() {
-        return sEventDispatcher;
-    }
-
-    /**
-     * Remove a previously-registered listener for a gecko event.
-     * This method is thread-safe and may be called at any time. In particular, calling it
-     * with an event that is currently being processed has the properly-defined behaviour that
-     * any removed listeners will still be invoked on the event currently being processed, but
-     * will not be invoked on future events of that type.
-     */
-    @RobocopTarget
-    public static void unregisterEventListener(String event, GeckoEventListener listener) {
-        sEventDispatcher.unregisterEventListener(event, listener);
-    }
-
     /*
      * Battery API related methods.
      */
     @WrapElementForJNI
     public static void enableBatteryNotifications() {
         GeckoBatteryManager.enableNotifications();
     }
 
     @WrapElementForJNI(stubName = "HandleGeckoMessageWrapper")
     public static void handleGeckoMessage(final NativeJSContainer message) {
-        sEventDispatcher.dispatchEvent(message);
+        EventDispatcher.getInstance().dispatchEvent(message);
         message.dispose();
     }
 
     @WrapElementForJNI
     public static void disableBatteryNotifications() {
         GeckoBatteryManager.disableNotifications();
     }
 
--- a/mobile/android/base/GeckoEditable.java
+++ b/mobile/android/base/GeckoEditable.java
@@ -761,23 +761,23 @@ final class GeckoEditable
 
         // Register/unregister Gecko-side text selection listeners
         // and update the mGeckoFocused flag.
         if (type == NOTIFY_IME_OF_BLUR && mGeckoFocused) {
             // Check for focus here because Gecko may send us a blur before a focus in some
             // cases, and we don't want to unregister an event that was not registered.
             mGeckoFocused = false;
             mSuppressCompositions = false;
-            GeckoAppShell.getEventDispatcher().
-                unregisterEventListener("TextSelection:DraggingHandle", this);
+            EventDispatcher.getInstance().
+                unregisterGeckoThreadListener(this, "TextSelection:DraggingHandle");
         } else if (type == NOTIFY_IME_OF_FOCUS) {
             mGeckoFocused = true;
             mSuppressCompositions = false;
-            GeckoAppShell.getEventDispatcher().
-                registerEventListener("TextSelection:DraggingHandle", this);
+            EventDispatcher.getInstance().
+                registerGeckoThreadListener(this, "TextSelection:DraggingHandle");
         }
     }
 
     @Override
     public void notifyIMEContext(final int state, final String typeHint,
                           final String modeHint, final String actionHint) {
         // Because we want to be able to bind GeckoEditable to the newest LayerView instance,
         // this can be called from the Java IC thread in addition to the Gecko thread.
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -68,17 +68,17 @@ public class GeckoThread extends Thread 
         sUri = uri;
     }
 
     GeckoThread(String args, String action, String uri) {
         mArgs = args;
         mAction = action;
         mUri = uri;
         setName("Gecko");
-        GeckoAppShell.getEventDispatcher().registerEventListener("Gecko:Ready", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "Gecko:Ready");
     }
 
     public static boolean isCreated() {
         return sGeckoThread != null;
     }
 
     public static void createAndStart() {
         if (ensureInit())
@@ -174,17 +174,17 @@ public class GeckoThread extends Thread 
         GeckoAppShell.runGecko(path, args, mUri, type);
     }
 
     private static Object sLock = new Object();
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         if ("Gecko:Ready".equals(event)) {
-            GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
+            EventDispatcher.getInstance().unregisterGeckoThreadListener(this, event);
             setLaunchState(LaunchState.GeckoRunning);
             GeckoAppShell.sendPendingEventsToGecko();
         }
     }
 
     @RobocopTarget
     public static boolean checkLaunchState(LaunchState checkState) {
         synchronized (sLock) {
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -88,27 +88,28 @@ public class GeckoView extends LayerView
             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(url));
         }
         GeckoAppShell.setContextGetter(this);
         if (context instanceof Activity) {
             Tabs tabs = Tabs.getInstance();
             tabs.attachToContext(context);
         }
 
-        GeckoAppShell.registerEventListener("Gecko:Ready", this);
-        GeckoAppShell.registerEventListener("Content:StateChange", this);
-        GeckoAppShell.registerEventListener("Content:LoadError", this);
-        GeckoAppShell.registerEventListener("Content:PageShow", this);
-        GeckoAppShell.registerEventListener("DOMTitleChanged", this);
-        GeckoAppShell.registerEventListener("Link:Favicon", this);
-        GeckoAppShell.registerEventListener("Prompt:Show", this);
-        GeckoAppShell.registerEventListener("Prompt:ShowTop", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Gecko:Ready",
+            "Content:StateChange",
+            "Content:LoadError",
+            "Content:PageShow",
+            "DOMTitleChanged",
+            "Link:Favicon",
+            "Prompt:Show",
+            "Prompt:ShowTop");
 
         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
-        initializeView(GeckoAppShell.getEventDispatcher());
+        initializeView(EventDispatcher.getInstance());
 
         if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
             // This is the first launch, so finish initialization and go.
             GeckoProfile profile = GeckoProfile.get(context).forceCreate();
             BrowserDB.initialize(profile.getName());
 
             GeckoAppShell.setLayerView(this);
             GeckoThread.createAndStart();
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -31,37 +31,32 @@ public final class IntentHelper implemen
         "WebActivity:Open"
     };
     private static IntentHelper instance;
 
     private Activity activity;
 
     private IntentHelper(Activity activity) {
         this.activity = activity;
-        for (String event : EVENTS) {
-            GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-        }
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENTS);
     }
 
     public static IntentHelper init(Activity activity) {
         if (instance == null) {
             instance = new IntentHelper(activity);
         } else {
             Log.w(LOGTAG, "IntentHelper.init() called twice, ignoring.");
         }
 
         return instance;
     }
 
     public static void destroy() {
         if (instance != null) {
-            for (String event : EVENTS) {
-                GeckoAppShell.getEventDispatcher().unregisterEventListener(event, instance);
-            }
-
+            EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, EVENTS);
             instance = null;
         }
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Intent:GetHandlers")) {
--- a/mobile/android/base/JavaAddonManager.java
+++ b/mobile/android/base/JavaAddonManager.java
@@ -60,28 +60,29 @@ class JavaAddonManager implements GeckoE
     public static JavaAddonManager getInstance() {
         if (sInstance == null) {
             sInstance = new JavaAddonManager();
         }
         return sInstance;
     }
 
     private JavaAddonManager() {
-        mDispatcher = GeckoAppShell.getEventDispatcher();
+        mDispatcher = EventDispatcher.getInstance();
         mAddonCallbacks = new HashMap<String, Map<String, GeckoEventListener>>();
     }
 
     void init(Context applicationContext) {
         if (mApplicationContext != null) {
             // we've already done this registration. don't do it again
             return;
         }
         mApplicationContext = applicationContext;
-        mDispatcher.registerEventListener("Dex:Load", this);
-        mDispatcher.registerEventListener("Dex:Unload", this);
+        mDispatcher.registerGeckoThreadListener(this,
+            "Dex:Load",
+            "Dex:Unload");
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Dex:Load")) {
                 String zipFile = message.getString("zipfile");
                 String implClass = message.getString("impl");
@@ -116,30 +117,30 @@ class JavaAddonManager implements GeckoE
         Map<String, GeckoEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
         if (addonCallbacks != null) {
             Log.w(LOGTAG, "Found pre-existing callbacks for zipfile [" + zipFile + "]; aborting re-registration!");
             return;
         }
         addonCallbacks = new HashMap<String, GeckoEventListener>();
         for (String event : callbacks.keySet()) {
             CallbackWrapper wrapper = new CallbackWrapper(callbacks.get(event));
-            mDispatcher.registerEventListener(event, wrapper);
+            mDispatcher.registerGeckoThreadListener(wrapper, event);
             addonCallbacks.put(event, wrapper);
         }
         mAddonCallbacks.put(zipFile, addonCallbacks);
     }
 
     private void unregisterCallbacks(String zipFile) {
         Map<String, GeckoEventListener> callbacks = mAddonCallbacks.remove(zipFile);
         if (callbacks == null) {
             Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile + "] which has no callbacks registered.");
             return;
         }
         for (String event : callbacks.keySet()) {
-            mDispatcher.unregisterEventListener(event, callbacks.get(event));
+            mDispatcher.unregisterGeckoThreadListener(callbacks.get(event), event);
         }
     }
 
     private static class CallbackWrapper implements GeckoEventListener {
         private final Handler.Callback mDelegate;
         private Bundle mBundle;
 
         CallbackWrapper(Handler.Callback delegate) {
--- a/mobile/android/base/LightweightTheme.java
+++ b/mobile/android/base/LightweightTheme.java
@@ -52,18 +52,19 @@ public class LightweightTheme implements
     private List<OnChangeListener> mListeners;
     
     public LightweightTheme(Application application) {
         mApplication = application;
         mHandler = new Handler(Looper.getMainLooper());
         mListeners = new ArrayList<OnChangeListener>();
 
         // unregister isn't needed as the lifetime is same as the application.
-        GeckoAppShell.getEventDispatcher().registerEventListener("LightweightTheme:Update", this);
-        GeckoAppShell.getEventDispatcher().registerEventListener("LightweightTheme:Disable", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "LightweightTheme:Update",
+            "LightweightTheme:Disable");
     }
 
     public void addListener(final OnChangeListener listener) {
         // Don't inform the listeners that attached late.
         // Their onLayout() will take care of them before their onDraw();
         mListeners.add(listener);
     }
 
--- a/mobile/android/base/MediaCastingBar.java
+++ b/mobile/android/base/MediaCastingBar.java
@@ -27,18 +27,19 @@ public class MediaCastingBar extends Rel
     private ImageButton mMediaPause;
     private ImageButton mMediaStop;
 
     private boolean mInflated = false;
 
     public MediaCastingBar(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        GeckoAppShell.getEventDispatcher().registerEventListener("Casting:Started", this);
-        GeckoAppShell.getEventDispatcher().registerEventListener("Casting:Stopped", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Casting:Started",
+            "Casting:Stopped");
     }
 
     public void inflateContent() {
         LayoutInflater inflater = LayoutInflater.from(getContext());
         View content = inflater.inflate(R.layout.media_casting, this);
 
         mMediaPlay = (ImageButton) content.findViewById(R.id.media_play);
         mMediaPlay.setOnClickListener(this);
@@ -63,18 +64,19 @@ public class MediaCastingBar extends Rel
         setVisibility(VISIBLE);
     }
 
     public void hide() {
         setVisibility(GONE);
     }
 
     public void onDestroy() {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("Casting:Started", this);
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("Casting:Stopped", this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Casting:Started",
+            "Casting:Stopped");
     }
 
     // View.OnClickListener implementation
     @Override
     public void onClick(View v) {
         final int viewId = v.getId();
 
         if (viewId == R.id.media_play) {
--- a/mobile/android/base/NotificationHelper.java
+++ b/mobile/android/base/NotificationHelper.java
@@ -73,25 +73,22 @@ public final class NotificationHelper im
     public static void init(Context context) {
         if (mInstance != null) {
             Log.w(LOGTAG, "NotificationHelper.init() called twice!");
             return;
         }
         mInstance = new NotificationHelper();
         mContext = context;
         mClearableNotifications = new HashSet<String>();
-        registerEventListener("Notification:Show");
-        registerEventListener("Notification:Hide");
+        EventDispatcher.getInstance().registerGeckoThreadListener(mInstance,
+            "Notification:Show",
+            "Notification:Hide");
         registerReceiver(context);
     }
 
-    private static void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, mInstance);
-    }
-
     @Override
     public void handleMessage(String event, JSONObject message) {
         if (event.equals("Notification:Show")) {
             showNotification(message);
         } else if (event.equals("Notification:Hide")) {
             hideNotification(message);
         }
     }
--- a/mobile/android/base/OrderedBroadcastHelper.java
+++ b/mobile/android/base/OrderedBroadcastHelper.java
@@ -30,31 +30,31 @@ public final class OrderedBroadcastHelpe
 
     public static final String SEND_EVENT = "OrderedBroadcast:Send";
 
     protected final Context mContext;
 
     public OrderedBroadcastHelper(Context context) {
         mContext = context;
 
-        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
-        dispatcher.registerEventListener(SEND_EVENT, this);
+        dispatcher.registerGeckoThreadListener(this, SEND_EVENT);
     }
 
     public synchronized void uninit() {
-        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
-        dispatcher.unregisterEventListener(SEND_EVENT, this);
+        dispatcher.unregisterGeckoThreadListener(this, SEND_EVENT);
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         if (!SEND_EVENT.equals(event)) {
             Log.e(LOGTAG, "OrderedBroadcastHelper got unexpected message " + event);
             return;
         }
--- a/mobile/android/base/PrefsHelper.java
+++ b/mobile/android/base/PrefsHelper.java
@@ -58,17 +58,17 @@ public final class PrefsHelper {
         return requestId;
     }
 
     private static void ensureRegistered() {
         if (sRegistered) {
             return;
         }
 
-        GeckoAppShell.getEventDispatcher().registerEventListener("Preferences:Data", new GeckoEventListener() {
+        EventDispatcher.getInstance().registerGeckoThreadListener(new GeckoEventListener() {
             @Override public void handleMessage(String event, JSONObject message) {
                 try {
                     PrefHandler callback;
                     synchronized (PrefsHelper.class) {
                         try {
                             int requestId = message.getInt("requestId");
                             callback = sCallbacks.get(requestId);
                             if (callback != null && !callback.isObserver()) {
@@ -102,17 +102,17 @@ public final class PrefsHelper {
                             Log.e(LOGTAG, "Handler for preference [" + name + "] threw exception", e);
                         }
                     }
                     callback.finish();
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Error handling Preferences:Data message", e);
                 }
             }
-        });
+        }, "Preferences:Data");
         sRegistered = true;
     }
 
     public static void setPref(String pref, Object value) {
         if (pref == null || pref.length() == 0) {
             throw new IllegalArgumentException("Pref name must be non-empty");
         }
 
--- a/mobile/android/base/SharedPreferencesHelper.java
+++ b/mobile/android/base/SharedPreferencesHelper.java
@@ -33,36 +33,37 @@ public final class SharedPreferencesHelp
     // handleObserve, which is called from Gecko serially.
     protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
 
     public SharedPreferencesHelper(Context context) {
         mContext = context;
 
         mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>();
 
-        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
-        dispatcher.registerEventListener("SharedPreferences:Set", this);
-        dispatcher.registerEventListener("SharedPreferences:Get", this);
-        dispatcher.registerEventListener("SharedPreferences:Observe", this);
+        dispatcher.registerGeckoThreadListener(this,
+            "SharedPreferences:Set",
+            "SharedPreferences:Get",
+            "SharedPreferences:Observe");
     }
 
     public synchronized void uninit() {
-        EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
+        EventDispatcher dispatcher = EventDispatcher.getInstance();
         if (dispatcher == null) {
             Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
             return;
         }
-
-        dispatcher.unregisterEventListener("SharedPreferences:Set", this);
-        dispatcher.unregisterEventListener("SharedPreferences:Get", this);
-        dispatcher.unregisterEventListener("SharedPreferences:Observe", this);
+        dispatcher.unregisterGeckoThreadListener(this,
+            "SharedPreferences:Set",
+            "SharedPreferences:Get",
+            "SharedPreferences:Observe");
     }
 
     private SharedPreferences getSharedPreferences(String branch) {
         if (branch == null) {
             return GeckoSharedPrefs.forApp(mContext);
         } else {
             return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE);
         }
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -77,40 +77,41 @@ public class Tabs implements GeckoEventL
                 if (syncIsSetup) {
                     TabsAccessor.persistLocalTabs(getContentResolver(), getTabsInOrder());
                 }
             } catch (SecurityException se) {} // will fail without android.permission.GET_ACCOUNTS
         }
     };
 
     private Tabs() {
-        registerEventListener("Session:RestoreEnd");
-        registerEventListener("SessionHistory:New");
-        registerEventListener("SessionHistory:Back");
-        registerEventListener("SessionHistory:Forward");
-        registerEventListener("SessionHistory:Goto");
-        registerEventListener("SessionHistory:Purge");
-        registerEventListener("Tab:Added");
-        registerEventListener("Tab:Close");
-        registerEventListener("Tab:Select");
-        registerEventListener("Content:LocationChange");
-        registerEventListener("Content:SecurityChange");
-        registerEventListener("Content:ReaderEnabled");
-        registerEventListener("Content:StateChange");
-        registerEventListener("Content:LoadError");
-        registerEventListener("Content:PageShow");
-        registerEventListener("DOMContentLoaded");
-        registerEventListener("DOMTitleChanged");
-        registerEventListener("Link:Favicon");
-        registerEventListener("Link:Feed");
-        registerEventListener("Link:OpenSearch");
-        registerEventListener("DesktopMode:Changed");
-        registerEventListener("Tab:ViewportMetadata");
-        registerEventListener("Tab:StreamStart");
-        registerEventListener("Tab:StreamStop");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Session:RestoreEnd",
+            "SessionHistory:New",
+            "SessionHistory:Back",
+            "SessionHistory:Forward",
+            "SessionHistory:Goto",
+            "SessionHistory:Purge",
+            "Tab:Added",
+            "Tab:Close",
+            "Tab:Select",
+            "Content:LocationChange",
+            "Content:SecurityChange",
+            "Content:ReaderEnabled",
+            "Content:StateChange",
+            "Content:LoadError",
+            "Content:PageShow",
+            "DOMContentLoaded",
+            "DOMTitleChanged",
+            "Link:Favicon",
+            "Link:Feed",
+            "Link:OpenSearch",
+            "DesktopMode:Changed",
+            "Tab:ViewportMetadata",
+            "Tab:StreamStart",
+            "Tab:StreamStop");
 
     }
 
     public synchronized void attachToContext(Context context) {
         final Context appContext = context.getApplicationContext();
         if (mAppContext == appContext) {
             return;
         }
@@ -631,20 +632,16 @@ public class Tabs implements GeckoEventL
      * those requests are removed.
      */
     private void queuePersistAllTabs() {
         Handler backgroundHandler = ThreadUtils.getBackgroundHandler();
         backgroundHandler.removeCallbacks(mPersistTabsRunnable);
         backgroundHandler.postDelayed(mPersistTabsRunnable, PERSIST_TABS_AFTER_MILLISECONDS);
     }
 
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
     /**
      * Looks for an open tab with the given URL.
      * @param url       the URL of the tab we're looking for
      *
      * @return first Tab with the given URL, or null if there is no such tab.
      */
     public Tab getFirstTabForUrl(String url) {
         return getFirstTabForUrlHelper(url, null);
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -86,30 +86,32 @@ class TextSelection extends Layer implem
                 }
             }
         };
 
         // Only register listeners if we have valid start/middle/end handles
         if (mStartHandle == null || mMiddleHandle == null || mEndHandle == null) {
             Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
         } else {
-            registerEventListener("TextSelection:ShowHandles");
-            registerEventListener("TextSelection:HideHandles");
-            registerEventListener("TextSelection:PositionHandles");
-            registerEventListener("TextSelection:Update");
-            registerEventListener("TextSelection:DraggingHandle");
+            EventDispatcher.getInstance().registerGeckoThreadListener(this,
+                "TextSelection:ShowHandles",
+                "TextSelection:HideHandles",
+                "TextSelection:PositionHandles",
+                "TextSelection:Update",
+                "TextSelection:DraggingHandle");
         }
     }
 
     void destroy() {
-        unregisterEventListener("TextSelection:ShowHandles");
-        unregisterEventListener("TextSelection:HideHandles");
-        unregisterEventListener("TextSelection:PositionHandles");
-        unregisterEventListener("TextSelection:Update");
-        unregisterEventListener("TextSelection:DraggingHandle");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "TextSelection:ShowHandles",
+            "TextSelection:HideHandles",
+            "TextSelection:PositionHandles",
+            "TextSelection:Update",
+            "TextSelection:DraggingHandle");
     }
 
     private TextSelectionHandle getHandle(String name) {
         if (name.equals("START")) {
             return mStartHandle;
         } else if (name.equals("MIDDLE")) {
             return mMiddleHandle;
         } else {
@@ -238,24 +240,16 @@ class TextSelection extends Layer implem
             public void run() {
                 mStartHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
                 mMiddleHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
                 mEndHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
             }
         });
     }
 
-    private void registerEventListener(String event) {
-        mEventDispatcher.registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        mEventDispatcher.unregisterEventListener(event, this);
-    }
-
     private class TextSelectionActionModeCallback implements Callback {
         private JSONArray mItems;
         private ActionModeCompat mActionMode;
     
         public TextSelectionActionModeCallback(JSONArray items) {
             mItems = items;
         }
 
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -141,19 +141,20 @@ class JavaPanZoomController
         mY = new AxisY(mSubscroller);
         mTouchEventHandler = new TouchEventHandler(view.getContext(), view, this);
 
         checkMainThread();
 
         setState(PanZoomState.NOTHING);
 
         mEventDispatcher = eventDispatcher;
-        registerEventListener(MESSAGE_ZOOM_RECT);
-        registerEventListener(MESSAGE_ZOOM_PAGE);
-        registerEventListener(MESSAGE_TOUCH_LISTENER);
+        mEventDispatcher.registerGeckoThreadListener(this,
+            MESSAGE_ZOOM_RECT,
+            MESSAGE_ZOOM_PAGE,
+            MESSAGE_TOUCH_LISTENER);
 
         mMode = AxisLockMode.STANDARD;
 
         String[] prefs = { "ui.scrolling.axis_lock_mode",
                            "ui.scrolling.negate_wheel_scrollY",
                            "ui.scrolling.gamepad_dead_zone" };
         mNegateWheelScrollY = false;
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
@@ -188,38 +189,31 @@ class JavaPanZoomController
 
         });
 
         Axis.initPrefs();
     }
 
     @Override
     public void destroy() {
-        unregisterEventListener(MESSAGE_ZOOM_RECT);
-        unregisterEventListener(MESSAGE_ZOOM_PAGE);
-        unregisterEventListener(MESSAGE_TOUCH_LISTENER);
+        mEventDispatcher.unregisterGeckoThreadListener(this,
+            MESSAGE_ZOOM_RECT,
+            MESSAGE_ZOOM_PAGE,
+            MESSAGE_TOUCH_LISTENER);
         mSubscroller.destroy();
         mTouchEventHandler.destroy();
     }
 
     private final static float easeOut(float t) {
         // ease-out approx.
         // -(t-1)^2+1
         t = t-1;
         return -t*t+1;
     }
 
-    private void registerEventListener(String event) {
-        mEventDispatcher.registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        mEventDispatcher.unregisterEventListener(event, this);
-    }
-
     private void setState(PanZoomState state) {
         if (state != mState) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PanZoom:StateChange", state.toString()));
             mState = state;
 
             // Let the target know we've finished with it (for now)
             if (state == PanZoomState.NOTHING) {
                 mTarget.panZoomStopped();
--- a/mobile/android/base/gfx/NativePanZoomController.java
+++ b/mobile/android/base/gfx/NativePanZoomController.java
@@ -25,23 +25,23 @@ class NativePanZoomController implements
 
     NativePanZoomController(PanZoomTarget target, View view, EventDispatcher dispatcher) {
         mTarget = target;
         mDispatcher = dispatcher;
         mCallbackRunnable = new CallbackRunnable();
         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             init();
         } else {
-            mDispatcher.registerEventListener("Gecko:Ready", this);
+            mDispatcher.registerGeckoThreadListener(this, "Gecko:Ready");
         }
     }
 
     public void handleMessage(String event, JSONObject message) {
         if ("Gecko:Ready".equals(event)) {
-            mDispatcher.unregisterEventListener("Gecko:Ready", this);
+            mDispatcher.unregisterGeckoThreadListener(this, "Gecko:Ready");
             init();
         }
     }
 
     public boolean onTouchEvent(MotionEvent event) {
         GeckoEvent wrapped = GeckoEvent.createMotionEvent(event, true);
         handleTouchEvent(wrapped);
         return false;
--- a/mobile/android/base/gfx/SubdocumentScrollHelper.java
+++ b/mobile/android/base/gfx/SubdocumentScrollHelper.java
@@ -49,33 +49,27 @@ class SubdocumentScrollHelper implements
     private boolean mScrollSucceeded;
 
     SubdocumentScrollHelper(EventDispatcher eventDispatcher) {
         // mUiHandler will be bound to the UI thread since that's where this constructor runs
         mUiHandler = new Handler();
         mPendingDisplacement = new PointF();
 
         mEventDispatcher = eventDispatcher;
-        registerEventListener(MESSAGE_PANNING_OVERRIDE);
-        registerEventListener(MESSAGE_CANCEL_OVERRIDE);
-        registerEventListener(MESSAGE_SCROLL_ACK);
+        mEventDispatcher.registerGeckoThreadListener(this,
+            MESSAGE_PANNING_OVERRIDE,
+            MESSAGE_CANCEL_OVERRIDE,
+            MESSAGE_SCROLL_ACK);
     }
 
     void destroy() {
-        unregisterEventListener(MESSAGE_PANNING_OVERRIDE);
-        unregisterEventListener(MESSAGE_CANCEL_OVERRIDE);
-        unregisterEventListener(MESSAGE_SCROLL_ACK);
-    }
-
-    private void registerEventListener(String event) {
-        mEventDispatcher.registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        mEventDispatcher.unregisterEventListener(event, this);
+        mEventDispatcher.unregisterGeckoThreadListener(this,
+            MESSAGE_PANNING_OVERRIDE,
+            MESSAGE_CANCEL_OVERRIDE,
+            MESSAGE_SCROLL_ACK);
     }
 
     boolean scrollBy(PointF displacement) {
         if (! mOverridePanning) {
             return false;
         }
 
         if (! mOverrideScrollAck) {
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -188,22 +188,23 @@ public class BrowserHealthRecorder imple
             this.client = null;
         }
     }
 
     private void unregisterEventListeners() {
         if (state != State.INITIALIZED) {
             return;
         }
-        this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this);
-        this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
-        this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
-        this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
-        this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this);
-        this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
+        dispatcher.unregisterGeckoThreadListener(this,
+            EVENT_SNAPSHOT,
+            EVENT_ADDONS_CHANGE,
+            EVENT_ADDONS_UNINSTALLING,
+            EVENT_PREF_CHANGE,
+            EVENT_KEYWORD_SEARCH,
+            EVENT_SEARCH);
     }
 
     public void onAppLocaleChanged(String to) {
         Log.d(LOG_TAG, "Setting health recorder app locale to " + to);
         this.profileCache.beginInitialization();
         this.profileCache.setAppLocale(to);
     }
 
@@ -454,19 +455,20 @@ public class BrowserHealthRecorder imple
                     } catch (Exception e) {
                         Log.e(LOG_TAG, "Failed to init storage.", e);
                         state = State.INITIALIZATION_FAILED;
                         return;
                     }
 
                     try {
                         // Listen for add-ons and prefs changes.
-                        dispatcher.registerEventListener(EVENT_ADDONS_UNINSTALLING, self);
-                        dispatcher.registerEventListener(EVENT_ADDONS_CHANGE, self);
-                        dispatcher.registerEventListener(EVENT_PREF_CHANGE, self);
+                        dispatcher.registerGeckoThreadListener(self,
+                            EVENT_ADDONS_UNINSTALLING,
+                            EVENT_ADDONS_CHANGE,
+                            EVENT_PREF_CHANGE);
 
                         // Initialize each provider here.
                         initializeSessionsProvider();
                         initializeSearchProvider();
 
                         Log.d(LOG_TAG, "Ensuring environment.");
                         ensureEnvironment();
 
@@ -521,17 +523,17 @@ public class BrowserHealthRecorder imple
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 final DistributionDescriptor desc = new Distribution(context).getDescriptor();
                 if (desc != null && desc.valid) {
                     profileCache.setDistributionString(desc.id, desc.version);
                 }
                 Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
-                dispatcher.registerEventListener(EVENT_SNAPSHOT, self);
+                dispatcher.registerGeckoThreadListener(self, EVENT_SNAPSHOT);
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
             }
         });
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
@@ -664,18 +666,19 @@ public class BrowserHealthRecorder imple
                     }
                     return out;
                 }
         });
 
         // Do this here, rather than in a centralized registration spot, in
         // case the above throws and we wind up handling events that we can't
         // store.
-        this.dispatcher.registerEventListener(EVENT_KEYWORD_SEARCH, this);
-        this.dispatcher.registerEventListener(EVENT_SEARCH, this);
+        this.dispatcher.registerGeckoThreadListener(this,
+            EVENT_KEYWORD_SEARCH,
+            EVENT_SEARCH);
     }
 
     /**
      * Record a search.
      *
      * @param engineID the string identifier for the engine. Can be <code>null</code>.
      * @param location one of a fixed set of locations: see {@link #SEARCH_LOCATIONS}.
      */
--- a/mobile/android/base/health/BrowserHealthReporter.java
+++ b/mobile/android/base/health/BrowserHealthReporter.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.health;
 
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.util.Log;
 
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 
 import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
@@ -37,26 +38,26 @@ public class BrowserHealthReporter imple
     private static final String LOGTAG = "GeckoHealthRep";
 
     public static final String EVENT_REQUEST  = "HealthReport:Request";
     public static final String EVENT_RESPONSE = "HealthReport:Response";
 
     protected final Context context;
 
     public BrowserHealthReporter() {
-        GeckoAppShell.registerEventListener(EVENT_REQUEST, this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENT_REQUEST);
 
         context = GeckoAppShell.getContext();
         if (context == null) {
             throw new IllegalStateException("Null Gecko context");
         }
     }
 
     public void uninit() {
-        GeckoAppShell.unregisterEventListener(EVENT_REQUEST, this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, EVENT_REQUEST);
     }
 
     /**
      * Generate a new Health Report.
      *
      * This method performs IO, so call it from a background thread.
      *
      * @param since timestamp of first day to report (milliseconds since epoch).
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
@@ -245,17 +246,18 @@ public class BrowserSearch extends HomeF
 
         return mView;
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
 
-        unregisterEventListener("SearchEngines:Data");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "SearchEngines:Data");
 
         mList.setAdapter(null);
         mList = null;
 
         mView = null;
         mSuggestionsOptInPrompt = null;
         mSuggestClient = null;
     }
@@ -316,17 +318,18 @@ public class BrowserSearch extends HomeF
                 if (selected instanceof SearchEngineRow) {
                     return selected.onKeyDown(keyCode, event);
                 }
                 return false;
             }
         });
 
         registerForContextMenu(mList);
-        registerEventListener("SearchEngines:Data");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "SearchEngines:Data");
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
@@ -702,24 +705,16 @@ public class BrowserSearch extends HomeF
         mSuggestionsOptInPrompt.startAnimation(shrinkAnimation);
         mList.startAnimation(shrinkAnimation);
     }
 
     private int getSuggestEngineCount() {
         return (TextUtils.isEmpty(mSearchTerm) || mSuggestClient == null || !mSuggestionsEnabled) ? 0 : 1;
     }
 
-    private void registerEventListener(String eventName) {
-        GeckoAppShell.registerEventListener(eventName, this);
-    }
-
-    private void unregisterEventListener(String eventName) {
-        GeckoAppShell.unregisterEventListener(eventName, this);
-    }
-
     private void restartSearchLoader() {
         SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
     }
 
     private void initSearchLoader() {
         SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
     }
 
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.Property;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
@@ -113,24 +114,24 @@ public class HomeBanner extends LinearLa
             public void onClick(View v) {
                 HomeBanner.this.dismiss();
 
                 // Send the current message id back to JS.
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
             }
         });
 
-        GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "HomeBanner:Data");
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "HomeBanner:Data");
     }
 
     @Override
     public void setVisibility(int visibility) {
         // On pre-Honeycomb devices, setting the visibility to GONE won't actually
         // hide the view unless we clear animations first.
         if (Build.VERSION.SDK_INT < 11 && visibility == View.GONE) {
             clearAnimation();
--- a/mobile/android/base/home/HomePanelsManager.java
+++ b/mobile/android/base/home/HomePanelsManager.java
@@ -12,17 +12,17 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.db.HomeProvider;
-import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
 import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -80,20 +80,21 @@ public class HomePanelsManager implement
     public static HomePanelsManager getInstance() {
         return sInstance;
     }
 
     public void init(Context context) {
         mContext = context;
         mHomeConfig = HomeConfig.getDefault(context);
 
-        GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
-        GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UNINSTALL, this);
-        GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UPDATE, this);
-        GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REFRESH, this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            EVENT_HOMEPANELS_INSTALL,
+            EVENT_HOMEPANELS_UNINSTALL,
+            EVENT_HOMEPANELS_UPDATE,
+            EVENT_HOMEPANELS_REFRESH);
     }
 
     public void onLocaleReady(final String locale) {
         ThreadUtils.getBackgroundHandler().post(new Runnable() {
             @Override
             public void run() {
                 final String configLocale = mHomeConfig.getLocale();
                 if (configLocale == null || !configLocale.equals(locale)) {
--- a/mobile/android/base/home/PanelInfoManager.java
+++ b/mobile/android/base/home/PanelInfoManager.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko.home;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.util.Log;
 import android.util.SparseArray;
@@ -72,17 +73,18 @@ public class PanelInfoManager implements
      * @param callback onComplete will be called on the UI thread.
      */
     public void requestPanelsById(Set<String> ids, RequestCallback callback) {
         final int requestId = sRequestId.getAndIncrement();
 
         synchronized(sCallbacks) {
             // If there are no pending callbacks, register the event listener.
             if (sCallbacks.size() == 0) {
-                GeckoAppShell.getEventDispatcher().registerEventListener("HomePanels:Data", this);
+                EventDispatcher.getInstance().registerGeckoThreadListener(this,
+                    "HomePanels:Data");
             }
             sCallbacks.put(requestId, callback);
         }
 
         final JSONObject message = new JSONObject();
         try {
             message.put("requestId", requestId);
 
@@ -130,17 +132,18 @@ public class PanelInfoManager implements
             final int requestId = message.getInt("requestId");
 
             synchronized(sCallbacks) {
                 callback = sCallbacks.get(requestId);
                 sCallbacks.delete(requestId);
 
                 // Unregister the event listener if there are no more pending callbacks.
                 if (sCallbacks.size() == 0) {
-                    GeckoAppShell.getEventDispatcher().unregisterEventListener("HomePanels:Data", this);
+                    EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+                        "HomePanels:Data");
                 }
             }
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     callback.onComplete(panelInfos);
                 }
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko.preferences;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.DataReportingNotification;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoActivityStatus;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.PrefsHelper;
@@ -140,17 +141,18 @@ public class GeckoPreferences
             if (res == 0) {
                 // No resource specified, or the resource was invalid; use the default preferences screen.
                 Log.e(LOGTAG, "Displaying default settings.");
                 res = R.xml.preferences;
             }
             addPreferencesFromResource(res);
         }
 
-        registerEventListener("Sanitize:Finished");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Sanitize:Finished");
 
         // Add handling for long-press click.
         // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm).
         final ListView mListView = getListView();
         mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
             @Override
             public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                 // Call long-click handler if it the item implements it.
@@ -218,17 +220,18 @@ public class GeckoPreferences
             PreferenceScreen screen = getPreferenceScreen();
             mPrefsRequestId = setupPreferences(screen);
         }
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        unregisterEventListener("Sanitize:Finished");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Sanitize:Finished");
         if (mPrefsRequestId > 0) {
             PrefsHelper.removeObserver(mPrefsRequestId);
         }
     }
 
     @Override
     public void onPause() {
         super.onPause();
@@ -895,24 +898,16 @@ public class GeckoPreferences
                     public void run() {
                         screen.setEnabled(true);
                     }
                 });
             }
         });
     }
 
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
-    }
-
     @Override
     public boolean isGeckoActivityOpened() {
         return false;
     }
 
     /**
      * Given an Intent instance, add extras to specify which settings section to
      * open.
--- a/mobile/android/base/preferences/SearchPreferenceCategory.java
+++ b/mobile/android/base/preferences/SearchPreferenceCategory.java
@@ -8,16 +8,17 @@ import android.content.Context;
 import android.preference.Preference;
 import android.util.AttributeSet;
 import android.util.Log;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
     public static final String LOGTAG = "SearchPrefCategory";
 
@@ -33,23 +34,23 @@ public class SearchPreferenceCategory ex
         super(context, attrs, defStyle);
     }
 
     @Override
     protected void onAttachedToActivity() {
         super.onAttachedToActivity();
 
         // Register for SearchEngines messages and request list of search engines from Gecko.
-        GeckoAppShell.registerEventListener("SearchEngines:Data", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "SearchEngines:Data");
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
     }
 
     @Override
     protected void onPrepareForRemoval() {
-        GeckoAppShell.unregisterEventListener("SearchEngines:Data", this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "SearchEngines:Data");
     }
 
     @Override
     public void setDefault(CustomListPreference item) {
         super.setDefault(item);
 
         sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString());
     }
--- a/mobile/android/base/prompts/PromptService.java
+++ b/mobile/android/base/prompts/PromptService.java
@@ -16,24 +16,26 @@ import android.content.Context;
 import android.util.Log;
 
 public class PromptService implements GeckoEventListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private final Context mContext;
 
     public PromptService(Context context) {
-        GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:Show", this);
-        GeckoAppShell.getEventDispatcher().registerEventListener("Prompt:ShowTop", this);
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Prompt:Show",
+            "Prompt:ShowTop");
         mContext = context;
     }
 
     public void destroy() {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("Prompt:Show", this);
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("Prompt:ShowTop", this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Prompt:Show",
+            "Prompt:ShowTop");
     }
 
     public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
                      final int aChoiceMode, final Prompt.PromptCallback callback) {
         // The dialog must be created on the UI thread.
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.toolbar;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.List;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
@@ -175,18 +176,19 @@ public class BrowserToolbar extends Them
 
         // Inflate the content.
         LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
 
         Tabs.registerOnTabsChangedListener(this);
         isSwitchingTabs = true;
         isAnimatingEntry = false;
 
-        registerEventListener("Reader:Click");
-        registerEventListener("Reader:LongClick");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "Reader:Click",
+            "Reader:LongClick");
 
         final Resources res = getResources();
         urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
         defaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
         urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
         urlBarEntry = findViewById(R.id.url_bar_entry);
         urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
@@ -1353,18 +1355,19 @@ public class BrowserToolbar extends Them
 
     public View getDoorHangerAnchor() {
         return urlDisplayLayout.getDoorHangerAnchor();
     }
 
     public void onDestroy() {
         Tabs.unregisterOnTabsChangedListener(this);
 
-        unregisterEventListener("Reader:Click");
-        unregisterEventListener("Reader:LongClick");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "Reader:Click",
+            "Reader:LongClick");
     }
 
     public boolean openOptionsMenu() {
         if (!hasSoftMenuButton) {
             return false;
         }
 
         // Initialize the popup.
@@ -1396,24 +1399,16 @@ public class BrowserToolbar extends Them
 
         if (menuPopup != null && menuPopup.isShowing()) {
             menuPopup.dismiss();
         }
 
         return true;
     }
 
-    private void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    private void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
-    }
-
     @Override
     public void handleMessage(String event, JSONObject message) {
         Log.d(LOGTAG, "handleMessage: " + event);
         if (event.equals("Reader:Click")) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 tab.toggleReaderMode();
             }
--- a/mobile/android/base/toolbar/PageActionLayout.java
+++ b/mobile/android/base/toolbar/PageActionLayout.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.toolbar;
 
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 
@@ -55,41 +56,35 @@ public class PageActionLayout extends Li
         super(context, attrs);
         mContext = context;
         mLayout = this;
 
         mPageActionList = new ArrayList<PageAction>();
         setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
         refreshPageActionIcons();
 
-        registerEventListener("PageActions:Add");
-        registerEventListener("PageActions:Remove");
+        EventDispatcher.getInstance().registerGeckoThreadListener(this,
+            "PageActions:Add",
+            "PageActions:Remove");
     }
 
     private void setNumberShown(int count) {
         mMaxVisiblePageActions = count;
 
         for(int index = 0; index < count; index++) {
             if ((this.getChildCount() - 1) < index) {
                 mLayout.addView(createImageButton());
             }
         }
     }
 
     public void onDestroy() {
-        unregisterEventListener("PageActions:Add");
-        unregisterEventListener("PageActions:Remove");
-    }
-
-    protected void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
-    }
-
-    protected void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
+            "PageActions:Add",
+            "PageActions:Remove");
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("PageActions:Add")) {
                 final String id = message.getString("id");
                 final String title = message.getString("title");
--- a/mobile/android/base/webapp/EventListener.java
+++ b/mobile/android/base/webapp/EventListener.java
@@ -48,40 +48,36 @@ public class EventListener implements Ge
 
     private static EventListener getEventListener() {
         if (mEventListener == null) {
             mEventListener = new EventListener();
         }
         return mEventListener;
     }
 
-    private static void registerEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().registerEventListener(event, EventListener.getEventListener());
-    }
-
-    private static void unregisterEventListener(String event) {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, EventListener.getEventListener());
-    }
-
     public static void registerEvents() {
-        registerEventListener("Webapps:Preinstall");
-        registerEventListener("Webapps:InstallApk");
-        registerEventListener("Webapps:Postinstall");
-        registerEventListener("Webapps:Open");
-        registerEventListener("Webapps:Uninstall");
-        registerEventListener("Webapps:GetApkVersions");
+        EventDispatcher.getInstance().registerGeckoThreadListener(
+            EventListener.getEventListener(),
+            "Webapps:Preinstall",
+            "Webapps:InstallApk",
+            "Webapps:Postinstall",
+            "Webapps:Open",
+            "Webapps:Uninstall",
+            "Webapps:GetApkVersions");
     }
 
     public static void unregisterEvents() {
-        unregisterEventListener("Webapps:Preinstall");
-        unregisterEventListener("Webapps:InstallApk");
-        unregisterEventListener("Webapps:Postinstall");
-        unregisterEventListener("Webapps:Open");
-        unregisterEventListener("Webapps:Uninstall");
-        unregisterEventListener("Webapps:GetApkVersions");
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(
+            EventListener.getEventListener(),
+            "Webapps:Preinstall",
+            "Webapps:InstallApk",
+            "Webapps:Postinstall",
+            "Webapps:Open",
+            "Webapps:Uninstall",
+            "Webapps:GetApkVersions");
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:InstallApk")) {
                 installApk(GeckoAppShell.getGeckoInterface().getActivity(), message.getString("filePath"), message.getString("data"));
             } else if (event.equals("Webapps:Postinstall")) {
--- a/mobile/android/base/webapp/InstallHelper.java
+++ b/mobile/android/base/webapp/InstallHelper.java
@@ -9,16 +9,17 @@ import java.io.Closeable;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
@@ -144,32 +145,28 @@ public class InstallHelper implements Ge
         try {
             close.close();
         } catch (IOException e) {
             // NOP
         }
     }
 
     public void registerGeckoListener() {
-        for (String eventName : INSTALL_EVENT_NAMES) {
-            GeckoAppShell.registerEventListener(eventName, this);
-        }
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, INSTALL_EVENT_NAMES);
     }
 
     private void calculateColor() {
         ThreadUtils.assertOnBackgroundThread();
         Allocator slots = Allocator.getInstance(mContext);
         int index = slots.getIndexForApp(mApkResources.getPackageName());
         Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(mApkResources.getAppIcon());
         slots.updateColor(index, BitmapUtils.getDominantColor(bitmap));
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
-        for (String eventName : INSTALL_EVENT_NAMES) {
-            GeckoAppShell.unregisterEventListener(eventName, this);
-        }
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, INSTALL_EVENT_NAMES);
 
         if (mCallback != null) {
             mCallback.installCompleted(this, event, message);
         }
     }
 }
--- a/netwerk/streamconv/src/nsStreamConverterService.cpp
+++ b/netwerk/streamconv/src/nsStreamConverterService.cpp
@@ -264,20 +264,18 @@ nsStreamConverterService::FindConverter(
         // reachable vertex in the loop.
         BFSTableData *headVertexState = lBFSTable.Get(*currentHead);
         if (!headVertexState) return NS_ERROR_FAILURE;
 
         int32_t edgeCount = data2->Count();
 
         for (int32_t i = 0; i < edgeCount; i++) {
             nsIAtom* curVertexAtom = data2->ObjectAt(i);
-            nsAutoString curVertexStr;
-            curVertexAtom->ToString(curVertexStr);
-            nsCString *curVertex = nullptr;
-            CopyUTF16toUTF8(curVertexStr, *curVertex);
+            nsCString *curVertex = new nsCString();
+            curVertexAtom->ToUTF8String(*curVertex);
 
             BFSTableData *curVertexState = lBFSTable.Get(*curVertex);
             if (!curVertexState) {
                 delete curVertex;
                 return NS_ERROR_FAILURE;
             }
 
             if (white == curVertexState->color) {
--- a/security/manager/boot/src/PublicKeyPinningService.cpp
+++ b/security/manager/boot/src/PublicKeyPinningService.cpp
@@ -1,24 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PublicKeyPinningService.h"
 #include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js
-#include "ScopedNSSTypes.h"
-#include "pkix/pkixtypes.h"
 
+#include "cert.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Telemetry.h"
 #include "nsString.h"
-#include "cert.h"
 #include "nssb64.h"
+#include "pkix/pkixtypes.h"
 #include "prlog.h"
+#include "ScopedNSSTypes.h"
 #include "seccomon.h"
 #include "sechash.h"
-#include "mozilla/Base64.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 #if defined(PR_LOGGING)
 PRLogModuleInfo* gPublicKeyPinningLog =
   PR_NewLogModule("PublicKeyPinningService");
 #endif
@@ -143,17 +144,20 @@ CheckPinsForHostname(const CERTCertList 
       }
     }
     evalHost = evalPart;
     // Advance past the '.'
     evalHost++;
   } // end while
 
   if (foundEntry && foundEntry->pinset) {
-    return EvalPinWithPinset(certList, foundEntry->pinset);
+    bool result = EvalPinWithPinset(certList, foundEntry->pinset);
+    Telemetry::Accumulate(Telemetry::CERT_PINNING_EVALUATION_RESULTS,
+                          result ? 1 : 0);
+    return result;
   }
   return true; // No pinning information for this hostname
 }
 
 /**
  * Extract all the DNS names for a host (including CN) and evaluate the
  * certifiate pins against all of them (Currently is an OR so we stop
  * evaluating at the first OK pin).
--- a/security/manager/ssl/tests/unit/test_pinning.js
+++ b/security/manager/ssl/tests/unit/test_pinning.js
@@ -78,19 +78,34 @@ function test_disabled() {
 
   add_connection_test("include-subdomains.pinning.example.com", Cr.NS_OK);
   add_connection_test("good.include-subdomains.pinning.example.com", Cr.NS_OK);
   add_connection_test("bad.include-subdomains.pinning.example.com", Cr.NS_OK);
   add_connection_test("exclude-subdomains.pinning.example.com", Cr.NS_OK);
   add_connection_test("sub.exclude-subdomains.pinning.example.com", Cr.NS_OK);
 };
 
+function check_pinning_telemetry() {
+  let histogram = Cc["@mozilla.org/base/telemetry;1"]
+                    .getService(Ci.nsITelemetry)
+                    .getHistogramById("CERT_PINNING_EVALUATION_RESULTS")
+                    .snapshot();
+   // Currently only strict mode gets evaluated
+   do_check_eq(histogram.counts[0], 1); // Failure count
+   do_check_eq(histogram.counts[1], 3); // Success count
+   run_next_test();
+}
+
 function run_test() {
   add_tls_server_setup("BadCertServer");
 
   // Add a user-specified trust anchor.
   addCertFromFile(certdb, "tlsserver/other-test-ca.der", "CTu,u,u");
 
   test_strict();
   test_mitm();
   test_disabled();
+
+  add_test(function () {
+    check_pinning_telemetry();
+  });
   run_next_test();
 }
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -19,21 +19,25 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader",
   "resource:///modules/CustomizationTabPreloader.jsm", "CustomizationTabPreloader");
 
 const SIMPLETEST_OVERRIDES =
   ["ok", "is", "isnot", "ise", "todo", "todo_is", "todo_isnot", "info", "expectAssertions"];
 
-window.addEventListener("load", testOnLoad, false);
+window.addEventListener("load", function testOnLoad() {
+  window.removeEventListener("load", testOnLoad);
+  window.addEventListener("MozAfterPaint", function testOnMozAfterPaint() {
+    window.removeEventListener("MozAfterPaint", testOnMozAfterPaint);
+    setTimeout(testInit, 0);
+  });
+});
 
-function testOnLoad() {
-  window.removeEventListener("load", testOnLoad, false);
-
+function testInit() {
   gConfig = readConfig();
   if (gConfig.testRoot == "browser" ||
       gConfig.testRoot == "metro" ||
       gConfig.testRoot == "webapprtChrome") {
     // Make sure to launch the test harness for the first opened window only
     var prefs = Services.prefs;
     if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
       return;
@@ -138,25 +142,16 @@ Tester.prototype = {
   get currentTest() {
     return this.tests[this.currentTestIndex];
   },
   get done() {
     return this.currentTestIndex == this.tests.length - 1;
   },
 
   start: function Tester_start() {
-    // Check whether this window is ready to run tests.
-    if (window.BrowserChromeTest) {
-      BrowserChromeTest.runWhenReady(this.actuallyStart.bind(this));
-      return;
-    }
-    this.actuallyStart();
-  },
-
-  actuallyStart: function Tester_actuallyStart() {
     //if testOnLoad was not called, then gConfig is not defined
     if (!gConfig)
       gConfig = readConfig();
 
     if (gConfig.runUntilFailure)
       this.runUntilFailure = true;
 
     if (gConfig.repeat)
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5911,10 +5911,16 @@
   },
   "OSFILE_WRITEATOMIC_JANK_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "description": "The duration during which the main thread is blocked during a call to OS.File.writeAtomic, in milliseconds",
     "high": "5000",
     "n_buckets": 10,
     "extended_statistics_ok": true
+  },
+  "CERT_PINNING_EVALUATION_RESULTS": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 4,
+    "description": "Certificate pinning evalutation results(pinned host)(0 = failure, 1 = success)"
   }
 }
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -1249,17 +1249,17 @@
         -->
       <method name="onMatchesCountResult">
         <parameter name="aResult"/>
         <body><![CDATA[
           if (aResult.total !== 0) {
             if (aResult.total == -1) {
               this._foundMatches.value = this.pluralForm.get(
                 this._matchesCountLimit,
-                this.strBundle.GetStringFromName("FoundTooManyMatches")
+                this.strBundle.GetStringFromName("FoundMatchesCountLimit")
               ).replace("#1", this._matchesCountLimit);
             } else {
               this._foundMatches.value = this.pluralForm.get(
                 aResult.total,
                 this.strBundle.GetStringFromName("FoundMatches")
               ).replace("#1", aResult.current)
                .replace("#2", aResult.total);
             }
--- a/toolkit/locales/en-US/chrome/global/findbar.properties
+++ b/toolkit/locales/en-US/chrome/global/findbar.properties
@@ -9,13 +9,13 @@ WrappedToBottom=Reached top of page, con
 NormalFind=Find in page
 FastFind=Quick find
 FastFindLinks=Quick find (links only)
 CaseSensitive=(Case sensitive)
 # LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is currently selected match and #2 the total amount of matches.
 FoundMatches=#1 of #2 match;#1 of #2 matches
-# LOCALIZATION NOTE (FoundTooManyMatches): Semicolon-separated list of plural
+# LOCALIZATION NOTE (FoundMatchesCountLimit): Semicolon-separated list of plural
 # forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the total amount of matches allowed before counting stops.
-FoundTooManyMatches=More than #1 match;More than #1 matches
+FoundMatchesCountLimit=More than #1 match;More than #1 matches
--- a/toolkit/mozapps/extensions/test/browser/browser-common.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser-common.ini
@@ -7,17 +7,16 @@ support-files =
 [browser_bug557943.js]
 [browser_bug562797.js]
 skip-if = e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_bug562854.js]
 [browser_bug562890.js]
 [browser_bug562899.js]
 [browser_bug562992.js]
 [browser_bug567127.js]
-skip-if = os == 'win' || os == 'mac' # Bug 608820
 [browser_bug567137.js]
 [browser_bug570760.js]
 skip-if = e10s # Bug ?????? - EventUtils.synthesizeKey not e10s friendly
 [browser_bug572561.js]
 [browser_bug577990.js]
 [browser_bug580298.js]
 [browser_bug581076.js]
 [browser_bug586574.js]
--- a/widget/cocoa/OSXNotificationCenter.mm
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -108,22 +108,24 @@ typedef NSInteger NSUserNotificationActi
     mOSXNC->CloseAlertCocoaString(name);
   }
 }
 
 @end
 
 namespace mozilla {
 
-class OSXNotificationInfo : public RefCounted<OSXNotificationInfo> {
+class OSXNotificationInfo {
+private:
+  ~OSXNotificationInfo();
+
 public:
-  MOZ_DECLARE_REFCOUNTED_TYPENAME(OSXNotificationInfo)
+  NS_INLINE_DECL_REFCOUNTING(OSXNotificationInfo)
   OSXNotificationInfo(NSString *name, nsIObserver *observer,
                       const nsAString & alertCookie);
-  ~OSXNotificationInfo();
 
   NSString *mName;
   nsCOMPtr<nsIObserver> mObserver;
   nsString mCookie;
   nsRefPtr<imgRequestProxy> mIconRequest;
   id<FakeNSUserNotification> mPendingNotifiction;
   nsCOMPtr<nsITimer> mIconTimeoutTimer;
 };