Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 02 May 2014 16:57:56 -0400
changeset 181809 43c5be10eae0b874634fc6456a11ab4bec2d0f50
parent 181779 f8497c9757f136708aea52f06e2d0efce18fdab9 (current diff)
parent 181808 c22612646a5e3651b6530ccadef28715f1bfab91 (diff)
child 181810 d40ab96e7e2ec9e72fcfab743f2b446d93255b30
child 181830 01bcda055d36d4ae6ee75e2c5fd92ace027b6eab
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone32.0a1
Merge m-c to inbound.
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d5800c36b2d5822fc3fe1899b9280401de466e1e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="65fba428f8d76336b33ddd9e15900357953600ba">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d5800c36b2d5822fc3fe1899b9280401de466e1e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "2796d30073cc12313afc0deef9d7700d25acdc77", 
+    "revision": "a328570f9becbe7f3f56057cdece95e508bbed1f", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e3fc29c5d4a9196b0e32e246f3d0150ae0c39221"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="f603a1ed83fa9f17ea91795d6bc7a49cfa7b4aef"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f313e6d3aaaefe8c82eaed15912a09b120fb7260"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a054c7385854b0e71b8d3071a465b9bc21581ee0"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- 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/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/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/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/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