Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 02 May 2014 16:57:56 -0400
changeset 181403 43c5be10eae0b874634fc6456a11ab4bec2d0f50
parent 181373 f8497c9757f136708aea52f06e2d0efce18fdab9 (current diff)
parent 181402 c22612646a5e3651b6530ccadef28715f1bfab91 (diff)
child 181404 d40ab96e7e2ec9e72fcfab743f2b446d93255b30
child 181424 01bcda055d36d4ae6ee75e2c5fd92ace027b6eab
push id43044
push userryanvm@gmail.com
push dateFri, 02 May 2014 20:57:50 +0000
treeherdermozilla-inbound@43c5be10eae0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge 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