Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Apr 2014 13:20:37 +0200
changeset 198455 fb7439e1e112b9320dfe7317b133f0a98a072e1c
parent 198454 3dd6d30c3050fbf931c6a2f98333b66f2fd55faf (current diff)
parent 198375 9d3da41ad0b6ef945bd06c680a9e23fcfc1fa406 (diff)
child 198456 5b54b4287fbf08ae3801de0b42c1c26067852da2
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.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 mozilla-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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="85f9690323b235f4dcf2901ea2240d3c60fc22a0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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"/>
@@ -123,14 +123,14 @@
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9a00b3db898a83ef0766baa93e935e525572899e"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bb11a0417efa7e6a08ed1cb2d5d6a13a3100abd3"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="baaf899afb158b9530690002f3656e958e3eb047"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="85f9690323b235f4dcf2901ea2240d3c60fc22a0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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": "9f480d1c52a46cac8e9a0be04bdb0d73810bc596", 
+    "revision": "2eae294604eb70e0e6eaee76b17e155ffd031107", 
     "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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- 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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -34,17 +34,16 @@ let CustomizationHandler = {
       childNode.setAttribute("disabled", true);
 
     let cmd = document.getElementById("cmd_CustomizeToolbars");
     cmd.setAttribute("disabled", "true");
 
     UpdateUrlbarSearchSplitterState();
 
     CombinedStopReload.uninit();
-    CombinedBackForward.uninit();
     PlacesToolbarHelper.customizeStart();
     DownloadsButton.customizeStart();
 
     // The additional padding on the sides of the browser
     // can cause the customize tab to get clipped.
     let tabContainer = gBrowser.tabContainer;
     if (tabContainer.getAttribute("overflow") == "true") {
       let tabstrip = tabContainer.mTabstrip;
@@ -82,17 +81,16 @@ let CustomizationHandler = {
     }
 
     PlacesToolbarHelper.customizeDone();
     DownloadsButton.customizeDone();
 
     // The url bar splitter state is dependent on whether stop/reload
     // and the location bar are combined, so we need this ordering
     CombinedStopReload.init();
-    CombinedBackForward.init();
     UpdateUrlbarSearchSplitterState();
 
     // Update the urlbar
     if (gURLBar) {
       URLBarSetURI();
       XULBrowserWindow.asyncUpdateUI();
     }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -255,25 +255,17 @@ function UpdateBackForwardCommands(aWebN
   var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
   if (backDisabled == aWebNavigation.canGoBack) {
     if (backDisabled)
       backBroadcaster.removeAttribute("disabled");
     else
       backBroadcaster.setAttribute("disabled", true);
   }
 
-  let canGoForward = aWebNavigation.canGoForward;
-  if (forwardDisabled) {
-    // Force the button to either be hidden (if we are already disabled,
-    // and should be), or to show if we're about to un-disable it:
-    // otherwise no transition will occur and it'll never show:
-    CombinedBackForward.setForwardButtonOcclusion(!canGoForward);
-  }
-
-  if (forwardDisabled == canGoForward) {
+  if (forwardDisabled == aWebNavigation.canGoForward) {
     if (forwardDisabled)
       forwardBroadcaster.removeAttribute("disabled");
     else
       forwardBroadcaster.setAttribute("disabled", true);
   }
 }
 
 /**
@@ -915,17 +907,16 @@ var gBrowserInit = {
         gURLBar.setAttribute("readonly", "true");
         gURLBar.setAttribute("enablehistory", "false");
       }
       goSetCommandEnabled("cmd_newNavigatorTab", false);
     }
 
     // Misc. inits.
     CombinedStopReload.init();
-    CombinedBackForward.init();
     gPrivateBrowsingUI.init();
     TabsInTitlebar.init();
 
     // Wait until chrome is painted before executing code not critical to making the window visible
     this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
 
     this._loadHandled = true;
@@ -1262,17 +1253,16 @@ var gBrowserInit = {
     if (desc && !desc.get) {
       DeveloperToolbar.destroy();
     }
 
     // First clean up services initialized in gBrowserInit.onLoad (or those whose
     // uninit methods don't depend on the services having been initialized).
 
     CombinedStopReload.uninit();
-    CombinedBackForward.uninit();
 
     gGestureSupport.init(false);
 
     gHistorySwipeAnimation.uninit();
 
     FullScreen.cleanup();
 
 #ifdef MOZ_SERVICES_SYNC
@@ -3794,17 +3784,16 @@ var XULBrowserWindow = {
     } catch (e) {}
     gIdentityHandler.checkIdentity(this._state, uri);
   },
 
   // simulate all change notifications after switching tabs
   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
     if (FullZoom.updateBackgroundTabs)
       FullZoom.onLocationChange(gBrowser.currentURI, true);
-    CombinedBackForward.setForwardButtonOcclusion(!gBrowser.webProgress.canGoForward);
     var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
     var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
     // use a pseudo-object instead of a (potentially nonexistent) channel for getting
     // a correct error message - and make sure that the UI is always either in
     // loading (STATE_START) or done (STATE_STOP) mode
     this.onStateChange(
       gBrowser.webProgress,
       { URI: gBrowser.currentURI },
@@ -3869,53 +3858,16 @@ var LinkTargetDisplay = {
 
   _hide: function () {
     clearTimeout(this._timer);
 
     XULBrowserWindow.updateStatusField();
   }
 };
 
-let CombinedBackForward = {
-  init: function() {
-    this.forwardButton = document.getElementById("forward-button");
-    // Add a transition listener to the url bar to hide the forward button
-    // when necessary
-    if (gURLBar)
-      gURLBar.addEventListener("transitionend", this);
-    // On startup, or if the user customizes, our listener isn't attached,
-    // and no transitions fire anyway, so we need to make sure we've hidden the
-    // button if necessary:
-    if (this.forwardButton && this.forwardButton.hasAttribute("disabled")) {
-      this.setForwardButtonOcclusion(true);
-    }
-  },
-  uninit: function() {
-    if (gURLBar)
-      gURLBar.removeEventListener("transitionend", this);
-  },
-  handleEvent: function(aEvent) {
-    if (aEvent.type == "transitionend" &&
-        (aEvent.propertyName == "margin-left" || aEvent.propertyName == "margin-right") &&
-        this.forwardButton.hasAttribute("disabled")) {
-      this.setForwardButtonOcclusion(true);
-    }
-  },
-  setForwardButtonOcclusion: function(shouldBeOccluded) {
-    if (!this.forwardButton)
-      return;
-
-    let hasAttribute = this.forwardButton.hasAttribute("occluded-by-urlbar");
-    if (shouldBeOccluded && !hasAttribute)
-      this.forwardButton.setAttribute("occluded-by-urlbar", "true");
-    else if (!shouldBeOccluded && hasAttribute)
-      this.forwardButton.removeAttribute("occluded-by-urlbar");
-  }
-}
-
 var CombinedStopReload = {
   init: function () {
     if (this._initialized)
       return;
 
     let reload = document.getElementById("urlbar-reload-button");
     let stop = document.getElementById("urlbar-stop-button");
     if (!stop || !reload || reload.nextSibling != stop)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -651,31 +651,24 @@
                      class="chromeclass-location" overflows="false">
           <toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                          label="&backCmd.label;"
                          command="Browser:BackOrBackDuplicate"
                          cui-areatype="toolbar"
                          onclick="checkForMiddleClick(this, event);"
                          tooltip="back-button-tooltip"
                          context="backForwardMenu"/>
-          <toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
-                         label="&forwardCmd.label;"
-                         command="Browser:ForwardOrForwardDuplicate"
-                         cui-areatype="toolbar"
-                         onclick="checkForMiddleClick(this, event);"
-                         tooltip="forward-button-tooltip"
-                         context="backForwardMenu"/>
-          <dummyobservertarget hidden="true"
-                               onbroadcast="if (this.getAttribute('disabled') == 'true')
-                                              this.parentNode.setAttribute('forwarddisabled', 'true');
-                                            else
-                                              this.parentNode.removeAttribute('forwarddisabled');">
-            <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
-          </dummyobservertarget>
-          <hbox id="urlbar-wrapper" flex="1" align="center">
+          <hbox id="urlbar-wrapper" flex="1">
+            <toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+                           label="&forwardCmd.label;"
+                           command="Browser:ForwardOrForwardDuplicate"
+                           cui-areatype="toolbar"
+                           onclick="checkForMiddleClick(this, event);"
+                           tooltip="forward-button-tooltip"
+                           context="backForwardMenu"/>
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
                      type="autocomplete"
                      autocompletesearch="urlinline history"
                      autocompletesearchparam="enable-actions"
                      autocompletepopup="PopupAutoCompleteRichResult"
                      completeselectedindex="true"
                      tabscrolling="true"
@@ -1183,32 +1176,23 @@
                          oncommand="DeveloperToolbar.hide();"
                          tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
 #endif
    </toolbar>
   </vbox>
 
   <svg:svg height="0">
 #include tab-shape.inc.svg
-
+    <svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
 #ifndef XP_MACOSX
-    <svg:clipPath id="keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
-      <svg:path d="m 0,0 c .3,.25 .3,.75, 0,1 l 1,0 0,-1 z"/>
-    </svg:clipPath>
-    <svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="m 0,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
+      <svg:path d="m 1,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
+#else
+      <svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
+#endif
     </svg:clipPath>
-#else
-    <svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="M 0,0 a 16 16 0 0 1 0,24 l 10000,0 l 0,-24 l -10000,0 z"/>
-    </svg:clipPath>
-    <svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="M -12,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
-    </svg:clipPath>
-#endif
   </svg:svg>
 
 </vbox>
 # <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
 #     Introducing the iframe dynamically, as needed, was found to be better than
 #     starting with an empty iframe here in browser.xul from a Ts standpoint.
 </deck>
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1007,22 +1007,22 @@
 
             newBrowser.setAttribute("type", "content-primary");
             newBrowser.docShellIsActive =
               (window.windowState != window.STATE_MINIMIZED);
             this.mCurrentBrowser = newBrowser;
             this.mCurrentTab = this.tabContainer.selectedItem;
             this.showTab(this.mCurrentTab);
 
-            var backForwardContainer = document.getElementById("urlbar-container");
-            if (backForwardContainer) {
-              backForwardContainer.setAttribute("switchingtabs", "true");
+            var forwardButtonContainer = document.getElementById("urlbar-wrapper");
+            if (forwardButtonContainer) {
+              forwardButtonContainer.setAttribute("switchingtabs", "true");
               window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
                 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
-                backForwardContainer.removeAttribute("switchingtabs");
+                forwardButtonContainer.removeAttribute("switchingtabs");
               });
             }
 
             this._appendStatusPanel();
 
             if (updateBlockedPopups)
               this.mCurrentBrowser.updateBlockedPopups(false);
 
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -85,67 +85,68 @@ let gTests = [
        "Search engine logo's alt text is a nonempty string");
 
     isnot(altText, "undefined",
           "Search engine logo's alt text shouldn't be the string 'undefined'");
   }
 },
 
 // Disabled on Linux for intermittent issues with FHR, see Bug 945667.
-// Disabled always due to bug 992485
 {
   desc: "Check that performing a search fires a search event and records to " +
         "Firefox Health Report.",
   setup: function () { },
   run: function () {
-    // Skip this test always for now since it loads google.com and that causes bug 992485
-    return;
-
     // Skip this test on Linux.
     if (navigator.platform.indexOf("Linux") == 0) { return; }
 
     try {
       let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
       cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
     } catch (ex) {
       // Health Report disabled, or no SearchesProvider.
       return Promise.resolve();
     }
 
     let numSearchesBefore = 0;
-    let deferred = Promise.defer();
+    let searchEventDeferred = Promise.defer();
     let doc = gBrowser.contentDocument;
     let engineName = doc.documentElement.getAttribute("searchEngineName");
 
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       let data = JSON.parse(e.detail);
       is(data.engineName, engineName, "Detail is search engine name");
 
       // We use executeSoon() to ensure that this code runs after the
       // count has been updated in browser.js, since it uses the same
       // event.
       executeSoon(function () {
         getNumberOfSearches(engineName).then(num => {
           is(num, numSearchesBefore + 1, "One more search recorded.");
-          deferred.resolve();
+          searchEventDeferred.resolve();
         });
       });
     }, true, true);
 
     // Get the current number of recorded searches.
+    let searchStr = "a search";
     getNumberOfSearches(engineName).then(num => {
       numSearchesBefore = num;
 
       info("Perform a search.");
-      doc.getElementById("searchText").value = "a search";
+      doc.getElementById("searchText").value = searchStr;
       doc.getElementById("searchSubmit").click();
-      gBrowser.stop();
     });
 
-    return deferred.promise;
+    let expectedURL = Services.search.currentEngine.
+                      getSubmission(searchStr, null, "homepage").
+                      uri.spec;
+    let loadPromise = waitForDocLoadAndStopIt(expectedURL);
+
+    return Promise.all([searchEventDeferred.promise, loadPromise]);
   }
 },
 
 {
   desc: "Check snippets map is cleared if cached version is old",
   setup: function (aSnippetsMap)
   {
     aSnippetsMap.set("snippets", "test");
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -287,16 +287,48 @@ function promiseHistoryClearedState(aURI
          "history visit " + aURI.spec + " should " + niceStr + " exist");
       callbackDone();
     });
   });
 
   return deferred.promise;
 }
 
+/**
+ * Waits for the next top-level document load in the current browser.  The URI
+ * of the document is compared against aExpectedURL.  The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ *        The URL of the document that is expected to load.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL) {
+  let deferred = Promise.defer();
+  let progressListener = {
+    onStateChange: function (webProgress, req, flags, status) {
+      info("waitForDocLoadAndStopIt: onStateChange: " + req.name);
+      let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+                     Ci.nsIWebProgressListener.STATE_START;
+      if ((flags & docStart) && webProgress.isTopLevel) {
+        info("waitForDocLoadAndStopIt: Document start: " +
+             req.QueryInterface(Ci.nsIChannel).URI.spec);
+        is(req.originalURI.spec, aExpectedURL,
+           "waitForDocLoadAndStopIt: The expected URL was loaded");
+        req.cancel(Components.results.NS_ERROR_FAILURE);
+        gBrowser.removeProgressListener(progressListener);
+        deferred.resolve();
+      }
+    },
+  };
+  gBrowser.addProgressListener(progressListener);
+  info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+  return deferred.promise;
+}
+
 let FullZoomHelper = {
 
   selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
     if (!tab)
       throw new Error("tab must be given.");
     if (gBrowser.selectedTab == tab)
       return Promise.resolve();
     gBrowser.selectedTab = tab;
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -99,16 +99,20 @@
       <richlistbox id="downloadsListBox"
                    class="plain"
                    flex="1"
                    context="downloadsContextMenu"
                    onmouseover="DownloadsView.onDownloadMouseOver(event);"
                    onmouseout="DownloadsView.onDownloadMouseOut(event);"
                    oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                    ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+      <description id="emptyDownloads"
+                   mousethrough="always">
+         &downloadsPanelEmpty.label;
+      </description>
 
       <vbox id="downloadsFooter">
         <hbox id="downloadsSummary"
               align="center"
               orient="horizontal"
               onkeydown="DownloadsSummary.onKeyDown(event);"
               onclick="DownloadsSummary.onClick(event);">
           <image class="downloadTypeIcon" />
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -79,13 +79,18 @@
 <!ENTITY clearDownloadsButton.tooltip     "Clears completed, canceled and failed downloads">
 
 <!-- LOCALIZATION NOTE (downloadsListEmpty.label):
      This string is shown when there are no items in the Downloads view, when it
      is displayed inside a browser tab.
      -->
 <!ENTITY downloadsListEmpty.label         "There are no downloads.">
 
+<!-- LOCALIZATION NOTE (downloadsPanelEmpty.label):
+     This string is shown when there are no items in the Downloads Panel.
+     -->
+<!ENTITY downloadsPanelEmpty.label        "No downloads for this session.">
+
 <!-- LOCALIZATION NOTE (downloadsListNoMatch.label):
      This string is shown when some search terms are specified, but there are no
      results in the Downloads view.
      -->
 <!ENTITY downloadsListNoMatch.label       "Could not find any matching downloads.">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -10,17 +10,17 @@
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include linuxShared.inc
 %filter substitution
 
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
 %define conditionalForwardWithUrlbarWidth 30
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -730,45 +730,49 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
   box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
               0 1px 0 hsla(210,54%,20%,.65) !important;
   transition: none;
 }
 
-#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
-@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
-}
-
 #forward-button {
   padding: 0;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box;
-  clip-path: url("chrome://browser/content/browser.xul#keyhole-forward-clip-path");
-  margin-left: -6px;
+  padding-left: 9px;
+  padding-right: 3px;
+  border: 1px solid #9a9a9a;
   border-left-style: none;
   border-radius: 0;
-  padding: 2px 3px 2px 9px;
-  border: 1px solid #9a9a9a;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
 }
 
 /* tabview menu item */
 
 #menu_tabview {
   list-style-image: url(chrome://browser/skin/tabview/tabview.png);
   -moz-image-region: rect(0, 80px, 16px, 64px);
 }
@@ -898,67 +902,44 @@ toolbarbutton[sdk-button="true"][cui-are
 .urlbar-history-dropmarker {
   -moz-appearance: toolbarbutton-dropdown;
 }
 
 #urlbar-container {
   -moz-box-align: center;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
+@conditionalForwardWithUrlbar@ > #urlbar {
+  -moz-border-start: none;
+  margin-left: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
-  -moz-border-start: none;
-  margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
+@conditionalForwardWithUrlbar@ {
   clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+  -moz-margin-start: -5px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+  transform: scaleX(-1);
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
-  transform: scaleX(-1);
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
 }
 
 #urlbar-icons {
   -moz-box-align: center;
 }
 
 .urlbar-icon {
   cursor: pointer;
@@ -1010,43 +991,43 @@ toolbarbutton[sdk-button="true"][cui-are
   border-bottom-right-radius: 1.5px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
-  padding-left: 5px;
-  transition: padding-left;
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  padding-right: 5px;
-  transition: padding-right;
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+  padding-left: 5px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+  padding-right: 5px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-left: 5.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-right: 5.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-margin-end: 4px;
 }
@@ -1066,24 +1047,20 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 %include ../shared/identity-block.inc.css
 
 #page-proxy-favicon {
   margin-top: 1px;
   margin-bottom: 1px;
   -moz-margin-start: 3px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-  -moz-margin-end: 1px;
-}
-
 #identity-box:hover > #page-proxy-favicon {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #identity-box:hover:active > #page-proxy-favicon,
 #identity-box[open=true] > #page-proxy-favicon {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
--- a/browser/themes/linux/downloads/downloads.css
+++ b/browser/themes/linux/downloads/downloads.css
@@ -13,23 +13,32 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsHistory {
   background: transparent;
   color: -moz-nativehyperlinktext;
   cursor: pointer;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   border-top: 1px solid ThreeDShadow;
   background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
 }
 
 #downloadsHistory > .button-box {
   margin: 1em;
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2,18 +2,18 @@
  * 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/. */
 
 @import url("chrome://global/skin/");
 
 %include shared.inc
 %filter substitution
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
-%define conditionalForwardWithUrlbarWidth 30
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
+%define conditionalForwardWithUrlbarWidth 32
 %define spaceAboveTabbar 9px
 %define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"])
 %define windowButtonMarginTop 11px
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
@@ -1415,30 +1415,27 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #back-button:-moz-window-inactive,
 #forward-button:-moz-window-inactive {
   background-color: rgba(0,0,0,0.04);
   border-color: rgba(0,0,0,0.2);
 }
 
-#back-button:-moz-locale-dir(rtl),
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
 /* Back button styles */
 
 #back-button {
-  -moz-margin-end: -7px;
-  position: relative;
-  z-index: 1;
   width: 32px;
   height: 32px;
   padding: 4px 5px 4px 3px;
+  -moz-margin-end: 0;
   border-radius: 10000px;
 }
 
 #back-button:not(:-moz-lwtheme) {
   height: 33px;
   padding: 4px 5px 5px 3px;
   margin-bottom: -1px;
   background: url(chrome://browser/skin/keyhole-circle.png) 0 0 no-repeat;
@@ -1458,68 +1455,50 @@ toolbarbutton[sdk-button="true"][cui-are
 #back-button:not([disabled="true"]):active:hover:not(:-moz-lwtheme),
 #back-button[open="true"]:not(:-moz-lwtheme) {
   background-position: -32px 0;
 }
 
 /* Forward button styles */
 
 #forward-button {
-  -moz-margin-start: 0;
-  -moz-margin-end: 0;
+  margin-left: -2px;
+  margin-right: 0;
+  padding-left: 2px;
   width: 32px;
-  clip-path: url(chrome://browser/content/browser.xul#osx-keyhole-forward-clip-path);
 }
 
 #forward-button > .toolbarbutton-icon {
   /* shift the icon away from the back button */
   margin-left: 3px;
   margin-right: -1px;
 }
 
-#forward-button:-moz-lwtheme {
-  -moz-padding-start: 2px;
-  -moz-padding-end: 0;
-}
-
 #forward-button:not(:-moz-lwtheme) {
-  -moz-padding-start: 2px;
   background: linear-gradient(hsl(0,0%,99%), hsl(0,0%,67%)) padding-box;
   border: 1px solid;
   border-color: hsl(0,0%,31%) hsla(0,0%,29%,.6) hsl(0,0%,27%);
   box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35),
               0 1px 0 hsla(0,0%,100%,.2);
 }
 
-#urlbar-container:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
-}
-
 #forward-button:hover:active:not(:-moz-lwtheme) {
   background-image: linear-gradient(hsl(0,0%,74%), hsl(0,0%,61%));
   box-shadow: inset rgba(0,0,0,.3) 0 -6px 10px,
               inset #000 0 1px 3px,
               inset rgba(0,0,0,.2) 0 1px 3px,
               0 1px 0 hsla(0,0%,100%,.2);
 }
 
 #forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
   border-color: hsl(0,0%,64%) hsl(0,0%,65%) hsl(0,0%,66%);
   background-image: linear-gradient(hsl(0,0%,99%), hsl(0,0%,82%));
   box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35);
 }
 
-#urlbar-container:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
-}
-
 @media (-moz-mac-lion-theme) {
   #forward-button:not(:-moz-lwtheme) {
     background-image: linear-gradient(hsla(0,0%,100%,.73), hsla(0,0%,100%,.05) 85%);
     border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.25) hsla(0,0%,0%,.2);
     box-shadow: inset 0 1px 0 hsla(0,0%,100%,.2),
                 inset 0 0 1px hsla(0,0%,100%,.1),
                 0 1px 0 hsla(0,0%,100%,.2);
   }
@@ -1533,16 +1512,34 @@ toolbarbutton[sdk-button="true"][cui-are
   }
 
   #forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
     background-image: none;
     border-color: hsla(0,0%,0%,.2);
   }
 }
 
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
+}
+
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-back.png") !important;
 }
 
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
@@ -1716,69 +1713,46 @@ toolbarbutton[sdk-button="true"][cui-are
   -moz-box-align: center;
 }
 
 #urlbar {
   -moz-padding-end: 4px;
   border-radius: @toolbarbuttonCornerRadius@;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
+@conditionalForwardWithUrlbar@ > #urlbar {
   -moz-border-start: none;
   margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
-  clip-path: url("chrome://browser/content/browser.xul#osx-urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let osx-urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+@conditionalForwardWithUrlbar@ {
+  clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+  -moz-margin-start: -6px;
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
 
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
+}
+
 #identity-box {
   -moz-margin-end: 3px;
   padding-top: 1px;
   padding-bottom: 1px;
   -moz-padding-start: 4px;
   -moz-padding-end: 0;
   font-size: .9em;
 }
@@ -1793,42 +1767,42 @@ toolbarbutton[sdk-button="true"][cui-are
   border-bottom-right-radius: 2px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
-  transition: 0s padding-left;
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 10px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  transition: 0s padding-right;
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 10px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
+  /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 10.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 10.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-padding-end: 4px;
 }
 
@@ -3395,17 +3369,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 @media (min-resolution: 2dppx) {
   #notification-popup-box {
     border-image: url("chrome://browser/skin/urlbar-arrow@2x.png") 0 16 0 0 fill;
   }
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box {
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar > #notification-popup-box {
   padding-left: 7px;
 }
 
 #notification-popup-box:-moz-locale-dir(rtl),
 .notification-anchor-icon:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
--- a/browser/themes/osx/downloads/downloads.css
+++ b/browser/themes/osx/downloads/downloads.css
@@ -17,28 +17,37 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsFooter {
   border-bottom-left-radius: 4px;
   border-bottom-right-radius: 4px;
 }
 
 #downloadsHistory {
   background: transparent;
   color: hsl(210,100%,75%);
   cursor: pointer;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   background: #e5e5e5;
   border-top: 1px solid hsla(0,0%,0%,.1);
   box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
 }
 
 #downloadsHistory > .button-box {
   color: #808080;
   margin: 1em;
@@ -47,21 +56,16 @@
 #downloadsPanel[keyfocus] > #downloadsFooter > #downloadsSummary:focus,
 #downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
   outline: 2px -moz-mac-focusring solid;
   outline-offset: -2px;
   -moz-outline-radius-bottomleft: 4px;
   -moz-outline-radius-bottomright: 4px;
 }
 
-#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus {
-  -moz-outline-radius-topleft: 4px;
-  -moz-outline-radius-topright: 4px;
-}
-
 /*** Downloads Summary and List items ***/
 
 #downloadsSummary,
 richlistitem[type="download"] {
   height: 7em;
   -moz-padding-end: 0;
   color: inherit;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -9,17 +9,17 @@
 @namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include windowsShared.inc
 %filter substitution
 %define toolbarShadowColor hsla(209,67%,12%,0.35)
 %define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
 %define conditionalForwardWithUrlbarWidth 30
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -881,46 +881,38 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #forward-button > menupopup {
   margin-top: 1px !important;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box !important;
-  /*mask: url(keyhole-forward-mask.svg#mask); XXX: this regresses twinopen */
-  clip-path: url(chrome://browser/content/browser.xul#keyhole-forward-clip-path) !important;
-  margin-left: -6px !important;
   border-left-style: none !important;
   border-radius: 0 !important;
   padding-left: 9px !important;
   padding-right: 3px !important;
 }
 
-%ifdef WINDOWS_AERO
-@media (-moz-os-version: windows-vista),
-       (-moz-os-version: windows-win7) {
-%endif
-  #forward-button > .toolbarbutton-icon {
-    margin-left: -6px !important;
-  }
-%ifdef WINDOWS_AERO
-}
-%endif
-
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
 }
 
 @conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
 }
 
 #back-button {
   padding-top: 3px !important;
   padding-bottom: 3px !important;
   -moz-padding-start: 5px !important;
   -moz-padding-end: 0 !important;
   position: relative !important;
@@ -994,18 +986,17 @@ toolbarbutton[sdk-button="true"][cui-are
     box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
                 0 1px 0 hsla(210,54%,20%,.65) !important;
     transition: none;
   }
 %ifdef WINDOWS_AERO
 }
 %endif
 
-#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-back.png") !important;
 }
 
@@ -1167,69 +1158,46 @@ toolbarbutton[sdk-button="true"][cui-are
   background-color: rgba(255,255,255,.9);
 }
 
 #urlbar:-moz-lwtheme[focused]:not([readonly]),
 .searchbar-textbox:-moz-lwtheme[focused] {
   background-color: white;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
+@conditionalForwardWithUrlbar@ > #urlbar {
   -moz-border-start: none;
   margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
+@conditionalForwardWithUrlbar@ {
   clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
+  -moz-margin-start: -5px;
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
 
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
+}
+
 html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
 .searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
   opacity: 1.0;
   color: #777;
 }
 
 #urlbar-container {
   -moz-box-align: center;
@@ -1309,43 +1277,43 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
   border-bottom-right-radius: 1.5px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 5px;
-  transition: padding-left;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 5px;
-  transition: padding-right;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-left: 5.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-right: 5.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-margin-end: 4px;
 }
@@ -1390,24 +1358,20 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
 /* page proxy icon */
 
 %include ../shared/identity-block.inc.css
 
 #page-proxy-favicon {
   margin-top: 1px;
   margin-bottom: 1px;
   -moz-margin-start: 3px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-  -moz-margin-end: 1px;
-}
-
 #identity-box:hover > #page-proxy-favicon {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #identity-box:hover:active > #page-proxy-favicon,
 #identity-box[open=true] > #page-proxy-favicon {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -13,16 +13,25 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsHistory {
   background: transparent;
   cursor: pointer;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
@@ -39,46 +48,46 @@
   outline-offset: -1px;
 }
 
 #downloadsHistory > .button-box {
   border: none;
   margin: 1em;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   background-color: hsla(210,4%,10%,.04);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
   transition-duration: 150ms;
   transition-property: background-color;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter:hover {
+#downloadsFooter:hover {
   background-color: hsla(210,4%,10%,.05);
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
+#downloadsFooter:hover:active {
   background-color: hsla(210,4%,10%,.1);
   box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
 %endif
 @media (-moz-windows-default-theme) {
-  #downloadsPanel[hasdownloads] > #downloadsFooter {
+  #downloadsFooter {
     border-bottom-left-radius: 3px;
     border-bottom-right-radius: 3px;
     transition-duration: 0s;
   }
 
-  #downloadsPanel[hasdownloads] > #downloadsFooter,
-  #downloadsPanel[hasdownloads] > #downloadsFooter:hover,
-  #downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
+  #downloadsFooter,
+  #downloadsFooter:hover,
+  #downloadsFooter:hover:active {
 %ifdef WINDOWS_AERO
     background-color: #f1f5fb;
 %else
     background-color: hsla(216,45%,88%,.98);
 %endif
     box-shadow: 0px 1px 2px rgb(204,214,234) inset;
   }
 }
--- a/dom/apps/src/InterAppCommService.js
+++ b/dom/apps/src/InterAppCommService.js
@@ -1,874 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/InterAppCommService.jsm");
 
-const DEBUG = false;
-function debug(aMsg) {
-  dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
+function InterAppCommServiceProxy() {
 }
 
-XPCOMUtils.defineLazyServiceGetter(this, "appsService",
-                                   "@mozilla.org/AppsService;1",
-                                   "nsIAppsService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
-                                   "@mozilla.org/parentprocessmessagemanager;1",
-                                   "nsIMessageBroadcaster");
-
-XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-XPCOMUtils.defineLazyServiceGetter(this, "messenger",
-                                   "@mozilla.org/system-message-internal;1",
-                                   "nsISystemMessagesInternal");
-
-const kMessages =["Webapps:Connect",
-                  "Webapps:GetConnections",
-                  "InterAppConnection:Cancel",
-                  "InterAppMessagePort:PostMessage",
-                  "InterAppMessagePort:Register",
-                  "InterAppMessagePort:Unregister",
-                  "child-process-shutdown"];
-
-function InterAppCommService() {
-  Services.obs.addObserver(this, "xpcom-shutdown", false);
-  Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
-
-  kMessages.forEach(function(aMsg) {
-    ppmm.addMessageListener(aMsg, this);
-  }, this);
-
-  // This matrix is used for saving the inter-app connection info registered in
-  // the app manifest. The object literal is defined as below:
-  //
-  // {
-  //   "keyword1": {
-  //     "subAppManifestURL1": {
-  //       /* subscribed info */
-  //     },
-  //     "subAppManifestURL2": {
-  //       /* subscribed info */
-  //     },
-  //     ...
-  //   },
-  //   "keyword2": {
-  //     "subAppManifestURL3": {
-  //       /* subscribed info */
-  //     },
-  //     ...
-  //   },
-  //   ...
-  // }
-  //
-  // For example:
-  //
-  // {
-  //   "foo": {
-  //     "app://subApp1.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp1.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     },
-  //     "app://subApp2.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp2.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     }
-  //   },
-  //   "bar": {
-  //     "app://subApp3.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp3.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     }
-  //   }
-  // }
-  //
-  // TODO Bug 908999 - Update registered connections when app gets uninstalled.
-  this._registeredConnections = {};
-
-  // This matrix is used for saving the permitted connections, which allows
-  // the messaging between publishers and subscribers. The object literal is
-  // defined as below:
-  //
-  // {
-  //   "keyword1": {
-  //     "pubAppManifestURL1": [
-  //       "subAppManifestURL1",
-  //       "subAppManifestURL2",
-  //       ...
-  //     ],
-  //     "pubAppManifestURL2": [
-  //       "subAppManifestURL3",
-  //       "subAppManifestURL4",
-  //       ...
-  //     ],
-  //     ...
-  //   },
-  //   "keyword2": {
-  //     "pubAppManifestURL3": [
-  //       "subAppManifestURL5",
-  //       ...
-  //     ],
-  //     ...
-  //   },
-  //   ...
-  // }
-  //
-  // For example:
-  //
-  // {
-  //   "foo": {
-  //     "app://pubApp1.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp1.gaiamobile.org/manifest.webapp",
-  //       "app://subApp2.gaiamobile.org/manifest.webapp"
-  //     ],
-  //     "app://pubApp2.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp3.gaiamobile.org/manifest.webapp",
-  //       "app://subApp4.gaiamobile.org/manifest.webapp"
-  //     ]
-  //   },
-  //   "bar": {
-  //     "app://pubApp3.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp5.gaiamobile.org/manifest.webapp",
-  //     ]
-  //   }
-  // }
-  //
-  // TODO Bug 908999 - Update allowed connections when app gets uninstalled.
-  this._allowedConnections = {};
-
-  // This matrix is used for saving the caller info from the content process,
-  // which is indexed by a random UUID, to know where to return the promise
-  // resolvser's callback when the prompt UI for allowing connections returns.
-  // An example of the object literal is shown as below:
-  //
-  // {
-  //   "fooID": {
-  //     outerWindowID: 12,
-  //     requestID: 34,
-  //     target: pubAppTarget1
-  //   },
-  //   "barID": {
-  //     outerWindowID: 56,
-  //     requestID: 78,
-  //     target: pubAppTarget2
-  //   }
-  // }
-  //
-  // where |outerWindowID| is the ID of the window requesting the connection,
-  //       |requestID| is the ID specifying the promise resolver to return,
-  //       |target| is the target of the process requesting the connection.
-  this._promptUICallers = {};
-
-  // This matrix is used for saving the pair of message ports, which is indexed
-  // by a random UUID, so that each port can know whom it should talk to.
-  // An example of the object literal is shown as below:
-  //
-  // {
-  //   "UUID1": {
-  //     keyword: "keyword1",
-  //     publisher: {
-  //       manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
-  //       target: pubAppTarget1,
-  //       pageURL: "app://pubApp1.gaiamobile.org/caller.html",
-  //       messageQueue: [...]
-  //     },
-  //     subscriber: {
-  //       manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
-  //       target: subAppTarget1,
-  //       pageURL: "app://pubApp1.gaiamobile.org/handler.html",
-  //       messageQueue: [...]
-  //     }
-  //   },
-  //   "UUID2": {
-  //     keyword: "keyword2",
-  //     publisher: {
-  //       manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
-  //       target: pubAppTarget2,
-  //       pageURL: "app://pubApp2.gaiamobile.org/caller.html",
-  //       messageQueue: [...]
-  //     },
-  //     subscriber: {
-  //       manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
-  //       target: subAppTarget2,
-  //       pageURL: "app://pubApp2.gaiamobile.org/handler.html",
-  //       messageQueue: [...]
-  //     }
-  //   }
-  // }
-  this._messagePortPairs = {};
-}
-
-InterAppCommService.prototype = {
+InterAppCommServiceProxy.prototype = {
   registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
                                aDescription, aRules) {
-    let manifestURL = aManifestURI.spec;
-    let pageURL = aHandlerPageURI.spec;
-
-    if (DEBUG) {
-      debug("registerConnection: aKeyword: " + aKeyword +
-            " manifestURL: " + manifestURL + " pageURL: " + pageURL +
-            " aDescription: " + aDescription +
-            " aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
-            " aRules.manifestURLs: " + aRules.manifestURLs +
-            " aRules.installOrigins: " + aRules.installOrigins);
-    }
-
-    let subAppManifestURLs = this._registeredConnections[aKeyword];
-    if (!subAppManifestURLs) {
-      subAppManifestURLs = this._registeredConnections[aKeyword] = {};
-    }
-
-    subAppManifestURLs[manifestURL] = {
-      pageURL: pageURL,
-      description: aDescription,
-      rules: aRules,
-      manifestURL: manifestURL
-    };
-  },
-
-  _matchMinimumAccessLevel: function(aRules, aAppStatus) {
-    if (!aRules || !aRules.minimumAccessLevel) {
-      if (DEBUG) {
-        debug("rules.minimumAccessLevel is not available. No need to match.");
-      }
-      return true;
-    }
-
-    let minAccessLevel = aRules.minimumAccessLevel;
-    switch (minAccessLevel) {
-      case "web":
-        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
-            aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
-            aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-          return true;
-        }
-        break;
-      case "privileged":
-        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
-            aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-          return true;
-        }
-        break;
-      case "certified":
-        if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-          return true;
-        }
-        break;
-    }
-
-    if (DEBUG) {
-      debug("rules.minimumAccessLevel is not matched!" +
-            " minAccessLevel: " + minAccessLevel +
-            " aAppStatus : " + aAppStatus);
-    }
-    return false;
-  },
-
-  _matchManifestURLs: function(aRules, aManifestURL) {
-    if (!aRules || !Array.isArray(aRules.manifestURLs)) {
-      if (DEBUG) {
-        debug("rules.manifestURLs is not available. No need to match.");
-      }
-      return true;
-    }
-
-    let manifestURLs = aRules.manifestURLs;
-    if (manifestURLs.indexOf(aManifestURL) != -1) {
-      return true;
-    }
-
-    if (DEBUG) {
-      debug("rules.manifestURLs is not matched!" +
-            " manifestURLs: " + manifestURLs +
-            " aManifestURL : " + aManifestURL);
-    }
-    return false;
-  },
-
-  _matchInstallOrigins: function(aRules, aInstallOrigin) {
-    if (!aRules || !Array.isArray(aRules.installOrigins)) {
-      if (DEBUG) {
-        debug("rules.installOrigins is not available. No need to match.");
-      }
-      return true;
-    }
-
-    let installOrigins = aRules.installOrigins;
-    if (installOrigins.indexOf(aInstallOrigin) != -1) {
-      return true;
-    }
-
-    if (DEBUG) {
-      debug("rules.installOrigins is not matched!" +
-            " installOrigins: " + installOrigins +
-            " installOrigin : " + aInstallOrigin);
-    }
-    return false;
-  },
-
-  _matchRules: function(aPubAppManifestURL, aPubRules,
-                        aSubAppManifestURL, aSubRules) {
-    let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
-    let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
-
-    // TODO Bug 907068 In the initiative step, we only expose this API to
-    // certified apps to meet the time line. Eventually, we need to make
-    // it available for the non-certified apps as well. For now, only the
-    // certified apps can match the rules.
-    if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
-        subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-      if (DEBUG) {
-        debug("Only certified apps are allowed to do connections.");
-      }
-      return false;
-    }
-
-    if (!aPubRules && !aSubRules) {
-      if (DEBUG) {
-        debug("No rules for publisher and subscriber. No need to match.");
-      }
-      return true;
-    }
-
-    // Check minimumAccessLevel.
-    if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
-        !this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
-      return false;
-    }
-
-    // Check manifestURLs.
-    if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
-        !this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
-      return false;
-    }
-
-    // Check installOrigins.
-    if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
-        !this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
-      return false;
-    }
-
-    // Check developers.
-    // TODO Do we really want to check this? This one seems naive.
-
-    if (DEBUG) debug("All rules are matched.");
-    return true;
-  },
-
-  _dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
-                                  aAllowedSubAppManifestURLs,
-                                  aTarget, aOuterWindowID, aRequestID) {
-    if (DEBUG) {
-      debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
-            " aPubAppManifestURL: " + aPubAppManifestURL +
-            " aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
-    }
-
-    if (aAllowedSubAppManifestURLs.length == 0) {
-      if (DEBUG) debug("No apps are allowed to connect. Returning.");
-      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
-                               { oid: aOuterWindowID, requestID: aRequestID });
-      return;
-    }
-
-    let subAppManifestURLs = this._registeredConnections[aKeyword];
-    if (!subAppManifestURLs) {
-      if (DEBUG) debug("No apps are subscribed to connect. Returning.");
-      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
-                               { oid: aOuterWindowID, requestID: aRequestID });
-      return;
-    }
-
-    let messagePortIDs = [];
-    aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
-      let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
-      if (!subscribedInfo) {
-        if (DEBUG) {
-          debug("The sunscribed info is not available. Skipping: " +
-                aAllowedSubAppManifestURL);
-        }
-        return;
-      }
-
-      // The message port ID is aimed for identifying the coupling targets
-      // to deliver messages with each other. This ID is centrally generated
-      // by the parent and dispatched to both the sender and receiver ends
-      // for creating their own message ports respectively.
-      let messagePortID = UUIDGenerator.generateUUID().toString();
-      this._messagePortPairs[messagePortID] = {
-        keyword: aKeyword,
-        publisher: {
-          manifestURL: aPubAppManifestURL
-        },
-        subscriber: {
-          manifestURL: aAllowedSubAppManifestURL
-        }
-      };
-
-      // Fire system message to deliver the message port to the subscriber.
-      messenger.sendMessage("connection",
-        { keyword: aKeyword,
-          messagePortID: messagePortID },
-        Services.io.newURI(subscribedInfo.pageURL, null, null),
-        Services.io.newURI(subscribedInfo.manifestURL, null, null));
-
-      messagePortIDs.push(messagePortID);
-    }, this);
-
-    if (messagePortIDs.length == 0) {
-      if (DEBUG) debug("No apps are subscribed to connect. Returning.");
-      aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
-                               { oid: aOuterWindowID, requestID: aRequestID });
-      return;
-    }
-
-    // Return the message port IDs to open the message ports for the publisher.
-    if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
-    aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
-                             { keyword: aKeyword,
-                               messagePortIDs: messagePortIDs,
-                               oid: aOuterWindowID, requestID: aRequestID });
-  },
-
-  _connect: function(aMessage, aTarget) {
-    let keyword = aMessage.keyword;
-    let pubRules = aMessage.rules;
-    let pubAppManifestURL = aMessage.manifestURL;
-    let outerWindowID = aMessage.outerWindowID;
-    let requestID = aMessage.requestID;
-
-    let subAppManifestURLs = this._registeredConnections[keyword];
-    if (!subAppManifestURLs) {
-      if (DEBUG) {
-        debug("No apps are subscribed for this connection. Returning.");
-      }
-      this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
-                                 aTarget, outerWindowID, requestID);
-      return;
-    }
-
-    // Fetch the apps that used to be allowed to connect before, so that
-    // users don't need to select/allow them again. That is, we only pop up
-    // the prompt UI for the *new* connections.
-    let allowedSubAppManifestURLs = [];
-    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
-    if (allowedPubAppManifestURLs &&
-        allowedPubAppManifestURLs[pubAppManifestURL]) {
-      allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
-    }
-
-    // Check rules to see if a subscribed app is allowed to connect.
-    let appsToSelect = [];
-    for (let subAppManifestURL in subAppManifestURLs) {
-      if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
-        if (DEBUG) {
-          debug("Don't need to select again. Skipping: " + subAppManifestURL);
-        }
-        continue;
-      }
-
-      // Only rule-matched publishers/subscribers are allowed to connect.
-      let subscribedInfo = subAppManifestURLs[subAppManifestURL];
-      let subRules = subscribedInfo.rules;
-
-      let matched =
-        this._matchRules(pubAppManifestURL, pubRules,
-                         subAppManifestURL, subRules);
-      if (!matched) {
-        if (DEBUG) {
-          debug("Rules are not matched. Skipping: " + subAppManifestURL);
-        }
-        continue;
-      }
-
-      appsToSelect.push({
-        manifestURL: subAppManifestURL,
-        description: subscribedInfo.description
-      });
-    }
-
-    if (appsToSelect.length == 0) {
-      if (DEBUG) {
-        debug("No additional apps need to be selected for this connection. " +
-              "Just dispatch message ports for the existing connections.");
-      }
-
-      this._dispatchMessagePorts(keyword, pubAppManifestURL,
-                                 allowedSubAppManifestURLs,
-                                 aTarget, outerWindowID, requestID);
-      return;
-    }
-
-    // Remember the caller info with an UUID so that we can know where to
-    // return the promise resolver's callback when the prompt UI returns.
-    let callerID = UUIDGenerator.generateUUID().toString();
-    this._promptUICallers[callerID] = {
-      outerWindowID: outerWindowID,
-      requestID: requestID,
-      target: aTarget
-    };
-
-    // TODO Bug 897169 Temporarily disable the notification for popping up
-    // the prompt until the UX/UI for the prompt is confirmed.
-    //
-    // TODO Bug 908191 We need to change the way of interaction between API and
-    // run-time prompt from observer notification to xpcom-interface caller.
-    //
-    /*
-    if (DEBUG) debug("appsToSelect: " + appsToSelect);
-    Services.obs.notifyObservers(null, "inter-app-comm-select-app",
-      JSON.stringify({ callerID: callerID,
-                       manifestURL: pubAppManifestURL,
-                       keyword: keyword,
-                       appsToSelect: appsToSelect }));
-    */
-
-    // TODO Bug 897169 Simulate the return of the app-selected result by
-    // the prompt, which always allows the connection. This dummy codes
-    // will be removed when the UX/UI for the prompt is ready.
-    if (DEBUG) debug("appsToSelect: " + appsToSelect);
-    Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
-      JSON.stringify({ callerID: callerID,
-                       manifestURL: pubAppManifestURL,
-                       keyword: keyword,
-                       selectedApps: appsToSelect }));
-  },
-
-  _getConnections: function(aMessage, aTarget) {
-    let outerWindowID = aMessage.outerWindowID;
-    let requestID = aMessage.requestID;
-
-    let connections = [];
-    for (let keyword in this._allowedConnections) {
-      let allowedPubAppManifestURLs = this._allowedConnections[keyword];
-      for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
-        let allowedSubAppManifestURLs =
-          allowedPubAppManifestURLs[allowedPubAppManifestURL];
-        allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
-          connections.push({ keyword: keyword,
-                             pubAppManifestURL: allowedPubAppManifestURL,
-                             subAppManifestURL: allowedSubAppManifestURL });
-        });
-      }
-    }
-
-    aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
-                             { connections: connections,
-                               oid: outerWindowID, requestID: requestID });
-  },
-
-  _cancelConnection: function(aMessage) {
-    let keyword = aMessage.keyword;
-    let pubAppManifestURL = aMessage.pubAppManifestURL;
-    let subAppManifestURL = aMessage.subAppManifestURL;
-
-    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
-    if (!allowedPubAppManifestURLs) {
-      if (DEBUG) debug("keyword is not found: " + keyword);
-      return;
-    }
-
-    let allowedSubAppManifestURLs =
-      allowedPubAppManifestURLs[pubAppManifestURL];
-    if (!allowedSubAppManifestURLs) {
-      if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
-      return;
-    }
-
-    let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
-    if (index == -1) {
-      if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
-      return;
-    }
-
-    if (DEBUG) debug("Cancelling the connection.");
-    allowedSubAppManifestURLs.splice(index, 1);
-
-    // Clean up the parent entries if needed.
-    if (allowedSubAppManifestURLs.length == 0) {
-      delete allowedPubAppManifestURLs[pubAppManifestURL];
-      if (Object.keys(allowedPubAppManifestURLs).length == 0) {
-        delete this._allowedConnections[keyword];
-      }
-    }
-
-    if (DEBUG) debug("Unregistering message ports based on this connection.");
-    let messagePortIDs = [];
-    for (let messagePortID in this._messagePortPairs) {
-      let pair = this._messagePortPairs[messagePortID];
-      if (pair.keyword == keyword &&
-          pair.publisher.manifestURL == pubAppManifestURL &&
-          pair.subscriber.manifestURL == subAppManifestURL) {
-        messagePortIDs.push(messagePortID);
-      }
-    }
-    messagePortIDs.forEach(function(aMessagePortID) {
-      delete this._messagePortPairs[aMessagePortID];
-    }, this);
-  },
-
-  _identifyMessagePort: function(aMessagePortID, aManifestURL) {
-    let pair = this._messagePortPairs[aMessagePortID];
-    if (!pair) {
-      if (DEBUG) {
-        debug("Error! The message port ID is invalid: " + aMessagePortID +
-              ", which should have been generated by parent.");
-      }
-      return null;
-    }
-
-    // Check it the message port is for publisher.
-    if (pair.publisher.manifestURL == aManifestURL) {
-      return { pair: pair, isPublisher: true };
-    }
-
-    // Check it the message port is for subscriber.
-    if (pair.subscriber.manifestURL == aManifestURL) {
-      return { pair: pair, isPublisher: false };
-    }
-
-    if (DEBUG) {
-      debug("Error! The manifest URL is invalid: " + aManifestURL +
-            ", which might be a hacked app.");
-    }
-    return null;
-  },
-
-  _registerMessagePort: function(aMessage, aTarget) {
-    let messagePortID = aMessage.messagePortID;
-    let manifestURL = aMessage.manifestURL;
-    let pageURL = aMessage.pageURL;
-
-    let identity = this._identifyMessagePort(messagePortID, manifestURL);
-    if (!identity) {
-      if (DEBUG) {
-        debug("Cannot identify the message port. Failed to register.");
-      }
-      return;
-    }
-
-    if (DEBUG) debug("Registering message port for " + manifestURL);
-    let pair = identity.pair;
-    let isPublisher = identity.isPublisher;
-
-    let sender = isPublisher ? pair.publisher : pair.subscriber;
-    sender.target = aTarget;
-    sender.pageURL = pageURL;
-    sender.messageQueue = [];
-
-    // Check if the other port has queued messages. Deliver them if needed.
-    if (DEBUG) {
-      debug("Checking if the other port used to send messages but queued.");
-    }
-    let receiver = isPublisher ? pair.subscriber : pair.publisher;
-    if (receiver.messageQueue) {
-      while (receiver.messageQueue.length) {
-        let message = receiver.messageQueue.shift();
-        if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
-        sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
-                                       { message: message,
-                                         manifestURL: sender.manifestURL,
-                                         pageURL: sender.pageURL,
-                                         messagePortID: messagePortID });
-      }
-    }
-  },
-
-  _unregisterMessagePort: function(aMessage) {
-    let messagePortID = aMessage.messagePortID;
-    let manifestURL = aMessage.manifestURL;
-
-    let identity = this._identifyMessagePort(messagePortID, manifestURL);
-    if (!identity) {
-      if (DEBUG) {
-        debug("Cannot identify the message port. Failed to unregister.");
-      }
-      return;
-    }
-
-    if (DEBUG) {
-      debug("Unregistering message port for " + manifestURL);
-    }
-    delete this._messagePortPairs[messagePortID];
-  },
-
-  _removeTarget: function(aTarget) {
-    if (!aTarget) {
-      if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
-      return
-    }
-
-    if (DEBUG) debug("Unregistering message ports based on this target.");
-    let messagePortIDs = [];
-    for (let messagePortID in this._messagePortPairs) {
-      let pair = this._messagePortPairs[messagePortID];
-      if (pair.publisher.target === aTarget ||
-          pair.subscriber.target === aTarget) {
-        messagePortIDs.push(messagePortID);
-      }
-    }
-    messagePortIDs.forEach(function(aMessagePortID) {
-      delete this._messagePortPairs[aMessagePortID];
-    }, this);
-  },
-
-  _postMessage: function(aMessage) {
-    let messagePortID = aMessage.messagePortID;
-    let manifestURL = aMessage.manifestURL;
-    let message = aMessage.message;
-
-    let identity = this._identifyMessagePort(messagePortID, manifestURL);
-    if (!identity) {
-      if (DEBUG) debug("Cannot identify the message port. Failed to post.");
-      return;
-    }
-
-    let pair = identity.pair;
-    let isPublisher = identity.isPublisher;
-
-    let receiver = isPublisher ? pair.subscriber : pair.publisher;
-    if (!receiver.target) {
-      if (DEBUG) {
-        debug("The receiver's target is not ready yet. Queuing the message.");
-      }
-      let sender = isPublisher ? pair.publisher : pair.subscriber;
-      sender.messageQueue.push(message);
-      return;
-    }
-
-    if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
-    receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
-                                     { manifestURL: receiver.manifestURL,
-                                       pageURL: receiver.pageURL,
-                                       messagePortID: messagePortID,
-                                       message: message });
-  },
-
-  _handleSelectcedApps: function(aData) {
-    let callerID = aData.callerID;
-    let caller = this._promptUICallers[callerID];
-    if (!caller) {
-      if (DEBUG) debug("Error! Cannot find the caller.");
-      return;
-    }
-
-    delete this._promptUICallers[callerID];
-
-    let outerWindowID = caller.outerWindowID;
-    let requestID = caller.requestID;
-    let target = caller.target;
-
-    let manifestURL = aData.manifestURL;
-    let keyword = aData.keyword;
-    let selectedApps = aData.selectedApps;
-
-    if (selectedApps.length == 0) {
-      if (DEBUG) debug("No apps are selected to connect.")
-      this._dispatchMessagePorts(keyword, manifestURL, [],
-                                 target, outerWindowID, requestID);
-      return;
-    }
-
-    // Find the entry of allowed connections to add the selected apps.
-    let allowedPubAppManifestURLs = this._allowedConnections[keyword];
-    if (!allowedPubAppManifestURLs) {
-      allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
-    }
-    let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
-    if (!allowedSubAppManifestURLs) {
-      allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
-    }
-
-    // Add the selected app into the existing set of allowed connections.
-    selectedApps.forEach(function(aSelectedApp) {
-      let allowedSubAppManifestURL = aSelectedApp.manifestURL;
-      if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
-        allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
-      }
-    });
-
-    // Finally, dispatch the message ports for the allowed connections,
-    // including the old connections and the newly selected connection.
-    this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
-                               target, outerWindowID, requestID);
-  },
-
-  receiveMessage: function(aMessage) {
-    if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
-    let message = aMessage.json;
-    let target = aMessage.target;
-
-    // To prevent the hacked child process from sending commands to parent
-    // to do illegal connections, we need to check its manifest URL.
-    if (aMessage.name !== "child-process-shutdown" &&
-        // TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister".
-        aMessage.name !== "InterAppMessagePort:Unregister" &&
-        kMessages.indexOf(aMessage.name) != -1) {
-      if (!target.assertContainApp(message.manifestURL)) {
-        if (DEBUG) {
-          debug("Got message from a process carrying illegal manifest URL.");
-        }
-        return null;
-      }
-    }
-
-    switch (aMessage.name) {
-      case "Webapps:Connect":
-        this._connect(message, target);
-        break;
-      case "Webapps:GetConnections":
-        this._getConnections(message, target);
-        break;
-      case "InterAppConnection:Cancel":
-        this._cancelConnection(message);
-        break;
-      case "InterAppMessagePort:PostMessage":
-        this._postMessage(message);
-        break;
-      case "InterAppMessagePort:Register":
-        this._registerMessagePort(message, target);
-        break;
-      case "InterAppMessagePort:Unregister":
-        this._unregisterMessagePort(message);
-        break;
-      case "child-process-shutdown":
-        this._removeTarget(target);
-        break;
-    }
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    switch (aTopic) {
-      case "xpcom-shutdown":
-        Services.obs.removeObserver(this, "xpcom-shutdown");
-        Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
-        kMessages.forEach(function(aMsg) {
-          ppmm.removeMessageListener(aMsg, this);
-        }, this);
-        ppmm = null;
-        break;
-      case "inter-app-comm-select-app-result":
-        if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
-        this._handleSelectcedApps(JSON.parse(aData));
-        break;
-    }
+    InterAppCommService.
+      registerConnection(aKeyword, aHandlerPageURI, aManifestURI,
+                         aDescription, aRules);
   },
 
   classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
-                                         Ci.nsIObserver])
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService])
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommServiceProxy]);
copy from dom/apps/src/InterAppCommService.js
copy to dom/apps/src/InterAppCommService.jsm
--- a/dom/apps/src/InterAppCommService.js
+++ b/dom/apps/src/InterAppCommService.jsm
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+this.EXPORTED_SYMBOLS = ["InterAppCommService"];
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 const DEBUG = false;
 function debug(aMsg) {
   dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
 }
@@ -34,187 +36,205 @@ XPCOMUtils.defineLazyServiceGetter(this,
 const kMessages =["Webapps:Connect",
                   "Webapps:GetConnections",
                   "InterAppConnection:Cancel",
                   "InterAppMessagePort:PostMessage",
                   "InterAppMessagePort:Register",
                   "InterAppMessagePort:Unregister",
                   "child-process-shutdown"];
 
-function InterAppCommService() {
-  Services.obs.addObserver(this, "xpcom-shutdown", false);
-  Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
+/**
+ * This module contains helpers for Inter-App Communication API [1] related
+ * purposes, which plays the role of the central service receiving messages
+ * from and interacting with the content processes.
+ *
+ * [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
+ */
 
-  kMessages.forEach(function(aMsg) {
-    ppmm.addMessageListener(aMsg, this);
-  }, this);
+this.InterAppCommService = {
+  init: function() {
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
+
+    kMessages.forEach(function(aMsg) {
+      ppmm.addMessageListener(aMsg, this);
+    }, this);
 
-  // This matrix is used for saving the inter-app connection info registered in
-  // the app manifest. The object literal is defined as below:
-  //
-  // {
-  //   "keyword1": {
-  //     "subAppManifestURL1": {
-  //       /* subscribed info */
-  //     },
-  //     "subAppManifestURL2": {
-  //       /* subscribed info */
-  //     },
-  //     ...
-  //   },
-  //   "keyword2": {
-  //     "subAppManifestURL3": {
-  //       /* subscribed info */
-  //     },
-  //     ...
-  //   },
-  //   ...
-  // }
-  //
-  // For example:
-  //
-  // {
-  //   "foo": {
-  //     "app://subApp1.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp1.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     },
-  //     "app://subApp2.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp2.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     }
-  //   },
-  //   "bar": {
-  //     "app://subApp3.gaiamobile.org/manifest.webapp": {
-  //       pageURL: "app://subApp3.gaiamobile.org/handler.html",
-  //       description: "blah blah",
-  //       rules: { ... }
-  //     }
-  //   }
-  // }
-  //
-  // TODO Bug 908999 - Update registered connections when app gets uninstalled.
-  this._registeredConnections = {};
+    // This matrix is used for saving the inter-app connection info registered in
+    // the app manifest. The object literal is defined as below:
+    //
+    // {
+    //   "keyword1": {
+    //     "subAppManifestURL1": {
+    //       /* subscribed info */
+    //     },
+    //     "subAppManifestURL2": {
+    //       /* subscribed info */
+    //     },
+    //     ...
+    //   },
+    //   "keyword2": {
+    //     "subAppManifestURL3": {
+    //       /* subscribed info */
+    //     },
+    //     ...
+    //   },
+    //   ...
+    // }
+    //
+    // For example:
+    //
+    // {
+    //   "foo": {
+    //     "app://subApp1.gaiamobile.org/manifest.webapp": {
+    //       pageURL: "app://subApp1.gaiamobile.org/handler.html",
+    //       description: "blah blah",
+    //       rules: { ... }
+    //     },
+    //     "app://subApp2.gaiamobile.org/manifest.webapp": {
+    //       pageURL: "app://subApp2.gaiamobile.org/handler.html",
+    //       description: "blah blah",
+    //       rules: { ... }
+    //     }
+    //   },
+    //   "bar": {
+    //     "app://subApp3.gaiamobile.org/manifest.webapp": {
+    //       pageURL: "app://subApp3.gaiamobile.org/handler.html",
+    //       description: "blah blah",
+    //       rules: { ... }
+    //     }
+    //   }
+    // }
+    //
+    // TODO Bug 908999 - Update registered connections when app gets uninstalled.
+    this._registeredConnections = {};
 
-  // This matrix is used for saving the permitted connections, which allows
-  // the messaging between publishers and subscribers. The object literal is
-  // defined as below:
-  //
-  // {
-  //   "keyword1": {
-  //     "pubAppManifestURL1": [
-  //       "subAppManifestURL1",
-  //       "subAppManifestURL2",
-  //       ...
-  //     ],
-  //     "pubAppManifestURL2": [
-  //       "subAppManifestURL3",
-  //       "subAppManifestURL4",
-  //       ...
-  //     ],
-  //     ...
-  //   },
-  //   "keyword2": {
-  //     "pubAppManifestURL3": [
-  //       "subAppManifestURL5",
-  //       ...
-  //     ],
-  //     ...
-  //   },
-  //   ...
-  // }
-  //
-  // For example:
-  //
-  // {
-  //   "foo": {
-  //     "app://pubApp1.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp1.gaiamobile.org/manifest.webapp",
-  //       "app://subApp2.gaiamobile.org/manifest.webapp"
-  //     ],
-  //     "app://pubApp2.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp3.gaiamobile.org/manifest.webapp",
-  //       "app://subApp4.gaiamobile.org/manifest.webapp"
-  //     ]
-  //   },
-  //   "bar": {
-  //     "app://pubApp3.gaiamobile.org/manifest.webapp": [
-  //       "app://subApp5.gaiamobile.org/manifest.webapp",
-  //     ]
-  //   }
-  // }
-  //
-  // TODO Bug 908999 - Update allowed connections when app gets uninstalled.
-  this._allowedConnections = {};
+    // This matrix is used for saving the permitted connections, which allows
+    // the messaging between publishers and subscribers. The object literal is
+    // defined as below:
+    //
+    // {
+    //   "keyword1": {
+    //     "pubAppManifestURL1": [
+    //       "subAppManifestURL1",
+    //       "subAppManifestURL2",
+    //       ...
+    //     ],
+    //     "pubAppManifestURL2": [
+    //       "subAppManifestURL3",
+    //       "subAppManifestURL4",
+    //       ...
+    //     ],
+    //     ...
+    //   },
+    //   "keyword2": {
+    //     "pubAppManifestURL3": [
+    //       "subAppManifestURL5",
+    //       ...
+    //     ],
+    //     ...
+    //   },
+    //   ...
+    // }
+    //
+    // For example:
+    //
+    // {
+    //   "foo": {
+    //     "app://pubApp1.gaiamobile.org/manifest.webapp": [
+    //       "app://subApp1.gaiamobile.org/manifest.webapp",
+    //       "app://subApp2.gaiamobile.org/manifest.webapp"
+    //     ],
+    //     "app://pubApp2.gaiamobile.org/manifest.webapp": [
+    //       "app://subApp3.gaiamobile.org/manifest.webapp",
+    //       "app://subApp4.gaiamobile.org/manifest.webapp"
+    //     ]
+    //   },
+    //   "bar": {
+    //     "app://pubApp3.gaiamobile.org/manifest.webapp": [
+    //       "app://subApp5.gaiamobile.org/manifest.webapp",
+    //     ]
+    //   }
+    // }
+    //
+    // TODO Bug 908999 - Update allowed connections when app gets uninstalled.
+    this._allowedConnections = {};
 
-  // This matrix is used for saving the caller info from the content process,
-  // which is indexed by a random UUID, to know where to return the promise
-  // resolvser's callback when the prompt UI for allowing connections returns.
-  // An example of the object literal is shown as below:
-  //
-  // {
-  //   "fooID": {
-  //     outerWindowID: 12,
-  //     requestID: 34,
-  //     target: pubAppTarget1
-  //   },
-  //   "barID": {
-  //     outerWindowID: 56,
-  //     requestID: 78,
-  //     target: pubAppTarget2
-  //   }
-  // }
-  //
-  // where |outerWindowID| is the ID of the window requesting the connection,
-  //       |requestID| is the ID specifying the promise resolver to return,
-  //       |target| is the target of the process requesting the connection.
-  this._promptUICallers = {};
+    // This matrix is used for saving the caller info from the content process,
+    // which is indexed by a random UUID, to know where to return the promise
+    // resolvser's callback when the prompt UI for allowing connections returns.
+    // An example of the object literal is shown as below:
+    //
+    // {
+    //   "fooID": {
+    //     outerWindowID: 12,
+    //     requestID: 34,
+    //     target: pubAppTarget1
+    //   },
+    //   "barID": {
+    //     outerWindowID: 56,
+    //     requestID: 78,
+    //     target: pubAppTarget2
+    //   }
+    // }
+    //
+    // where |outerWindowID| is the ID of the window requesting the connection,
+    //       |requestID| is the ID specifying the promise resolver to return,
+    //       |target| is the target of the process requesting the connection.
+    this._promptUICallers = {};
 
-  // This matrix is used for saving the pair of message ports, which is indexed
-  // by a random UUID, so that each port can know whom it should talk to.
-  // An example of the object literal is shown as below:
-  //
-  // {
-  //   "UUID1": {
-  //     keyword: "keyword1",
-  //     publisher: {
-  //       manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
-  //       target: pubAppTarget1,
-  //       pageURL: "app://pubApp1.gaiamobile.org/caller.html",
-  //       messageQueue: [...]
-  //     },
-  //     subscriber: {
-  //       manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
-  //       target: subAppTarget1,
-  //       pageURL: "app://pubApp1.gaiamobile.org/handler.html",
-  //       messageQueue: [...]
-  //     }
-  //   },
-  //   "UUID2": {
-  //     keyword: "keyword2",
-  //     publisher: {
-  //       manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
-  //       target: pubAppTarget2,
-  //       pageURL: "app://pubApp2.gaiamobile.org/caller.html",
-  //       messageQueue: [...]
-  //     },
-  //     subscriber: {
-  //       manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
-  //       target: subAppTarget2,
-  //       pageURL: "app://pubApp2.gaiamobile.org/handler.html",
-  //       messageQueue: [...]
-  //     }
-  //   }
-  // }
-  this._messagePortPairs = {};
-}
+    // This matrix is used for saving the pair of message ports, which is indexed
+    // by a random UUID, so that each port can know whom it should talk to.
+    // An example of the object literal is shown as below:
+    //
+    // {
+    //   "UUID1": {
+    //     keyword: "keyword1",
+    //     publisher: {
+    //       manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
+    //       target: pubAppTarget1,
+    //       pageURL: "app://pubApp1.gaiamobile.org/caller.html",
+    //       messageQueue: [...]
+    //     },
+    //     subscriber: {
+    //       manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
+    //       target: subAppTarget1,
+    //       pageURL: "app://pubApp1.gaiamobile.org/handler.html",
+    //       messageQueue: [...]
+    //     }
+    //   },
+    //   "UUID2": {
+    //     keyword: "keyword2",
+    //     publisher: {
+    //       manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
+    //       target: pubAppTarget2,
+    //       pageURL: "app://pubApp2.gaiamobile.org/caller.html",
+    //       messageQueue: [...]
+    //     },
+    //     subscriber: {
+    //       manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
+    //       target: subAppTarget2,
+    //       pageURL: "app://pubApp2.gaiamobile.org/handler.html",
+    //       messageQueue: [...]
+    //     }
+    //   }
+    // }
+    this._messagePortPairs = {};
+  },
 
-InterAppCommService.prototype = {
+  /**
+   * Registration of a page that wants to be connected to other apps through
+   * the Inter-App Communication API.
+   *
+   * @param aKeyword        The connection's keyword.
+   * @param aHandlerPageURI The URI of the handler's page.
+   * @param aManifestURI    The webapp's manifest URI.
+   * @param aDescription    The connection's description.
+   * @param aRules          The connection's rules.
+   */
   registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
                                aDescription, aRules) {
     let manifestURL = aManifestURI.spec;
     let pageURL = aHandlerPageURI.spec;
 
     if (DEBUG) {
       debug("registerConnection: aKeyword: " + aKeyword +
             " manifestURL: " + manifestURL + " pageURL: " + pageURL +
@@ -858,17 +878,12 @@ InterAppCommService.prototype = {
         }, this);
         ppmm = null;
         break;
       case "inter-app-comm-select-app-result":
         if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
         this._handleSelectcedApps(JSON.parse(aData));
         break;
     }
-  },
-
-  classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
+  }
+};
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
-                                         Ci.nsIObserver])
-}
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);
+InterAppCommService.init();
--- a/dom/apps/src/moz.build
+++ b/dom/apps/src/moz.build
@@ -22,16 +22,17 @@ EXTRA_COMPONENTS += [
     'Webapps.js',
     'Webapps.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'AppDownloadManager.jsm',
     'AppsServiceChild.jsm',
     'FreeSpaceWatcher.jsm',
+    'InterAppCommService.jsm',
     'OfflineCacheInstaller.jsm',
     'PermissionsInstaller.jsm',
     'PermissionsTable.jsm',
     'StoreTrustAnchor.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'AppsUtils.jsm',
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/test_inter_app_comm_service.js
@@ -0,0 +1,457 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/InterAppCommService.jsm");
+
+let UUIDGenerator = Cc["@mozilla.org/uuid-generator;1"]
+                      .getService(Ci.nsIUUIDGenerator);
+
+const MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
+const FAKE_MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
+const OUTER_WINDOW_ID = UUIDGenerator.generateUUID().toString();
+const REQUEST_ID = UUIDGenerator.generateUUID().toString();
+
+const PUB_APP_MANIFEST_URL = "app://pubApp.gaiamobile.org/manifest.webapp";
+const SUB_APP_MANIFEST_URL = "app://subApp.gaiamobile.org/manifest.webapp";
+
+const PUB_APP_PAGE_URL = "app://pubApp.gaiamobile.org/handler.html";
+const SUB_APP_PAGE_URL = "app://subApp.gaiamobile.org/handler.html";
+
+const KEYWORD = "test";
+
+function create_message_port_pair(aMessagePortId,
+                                  aKeyword,
+                                  aPubManifestURL,
+                                  aSubManifestURL) {
+  InterAppCommService._messagePortPairs[aMessagePortId] = {
+    keyword: aKeyword,
+    publisher: {
+      manifestURL: aPubManifestURL
+    },
+    subscriber: {
+      manifestURL: aSubManifestURL
+    }
+  };
+}
+
+function clear_message_port_pairs() {
+  InterAppCommService._messagePortPairs = {};
+}
+
+function register_message_port(aMessagePortId,
+                               aManifestURL,
+                               aPageURL,
+                               aTargetSendAsyncMessage) {
+  let message = {
+    name: "InterAppMessagePort:Register",
+    json: {
+      messagePortID: aMessagePortId,
+      manifestURL: aManifestURL,
+      pageURL: aPageURL
+    },
+    target: {
+      sendAsyncMessage: function(aName, aData) {
+        if (aTargetSendAsyncMessage) {
+          aTargetSendAsyncMessage(aName, aData);
+        }
+      },
+      assertContainApp: function(_manifestURL) {
+        return (aManifestURL == _manifestURL);
+      }
+    }
+  };
+
+  InterAppCommService.receiveMessage(message);
+
+  return message.target;
+}
+
+function register_message_ports(aMessagePortId,
+                                aPubTargetSendAsyncMessage,
+                                aSubTargetSendAsyncMessage) {
+  let pubTarget = register_message_port(aMessagePortId,
+                                        PUB_APP_MANIFEST_URL,
+                                        PUB_APP_PAGE_URL,
+                                        aPubTargetSendAsyncMessage);
+
+  let subTarget = register_message_port(aMessagePortId,
+                                        SUB_APP_MANIFEST_URL,
+                                        SUB_APP_PAGE_URL,
+                                        aSubTargetSendAsyncMessage);
+
+  return { pubTarget: pubTarget, subTarget: subTarget };
+}
+
+function unregister_message_port(aMessagePortId,
+                                 aManifestURL) {
+  let message = {
+    name: "InterAppMessagePort:Unregister",
+    json: {
+      messagePortID: aMessagePortId,
+      manifestURL: aManifestURL
+    },
+    target: {
+      assertContainApp: function(_manifestURL) {
+        return (aManifestURL == _manifestURL);
+      }
+    }
+  };
+
+  InterAppCommService.receiveMessage(message);
+}
+
+function remove_target(aTarget) {
+  let message = {
+    name: "child-process-shutdown",
+    target: aTarget
+  };
+
+  InterAppCommService.receiveMessage(message);
+}
+
+function post_message(aMessagePortId,
+                      aManifestURL,
+                      aMessage) {
+  let message = {
+    name: "InterAppMessagePort:PostMessage",
+    json: {
+      messagePortID: aMessagePortId,
+      manifestURL: aManifestURL,
+      message: aMessage
+    },
+    target: {
+      assertContainApp: function(_manifestURL) {
+        return (aManifestURL == _manifestURL);
+      }
+    }
+  };
+
+  InterAppCommService.receiveMessage(message);
+}
+
+function create_allowed_connections(aKeyword,
+                                    aPubManifestURL,
+                                    aSubManifestURL) {
+  let allowedPubAppManifestURLs =
+    InterAppCommService._allowedConnections[aKeyword] = {};
+
+  allowedPubAppManifestURLs[aPubManifestURL] = [aSubManifestURL];
+}
+
+function clear_allowed_connections() {
+  InterAppCommService._allowedConnections = {};
+}
+
+function get_connections(aManifestURL,
+                         aOuterWindowID,
+                         aRequestID,
+                         aTargetSendAsyncMessage) {
+  let message = {
+    name: "Webapps:GetConnections",
+    json: {
+      manifestURL: aManifestURL,
+      outerWindowID: aOuterWindowID,
+      requestID: aRequestID
+    },
+    target: {
+      sendAsyncMessage: function(aName, aData) {
+        if (aTargetSendAsyncMessage) {
+          aTargetSendAsyncMessage(aName, aData);
+        }
+      },
+      assertContainApp: function(_manifestURL) {
+        return (aManifestURL == _manifestURL);
+      }
+    }
+  };
+
+  InterAppCommService.receiveMessage(message);
+}
+
+function cancel_connections(aManifestURL,
+                            aKeyword,
+                            aPubManifestURL,
+                            aSubManifestURL) {
+  let message = {
+    name: "InterAppConnection:Cancel",
+    json: {
+      manifestURL: aManifestURL,
+      keyword: aKeyword,
+      pubAppManifestURL: aPubManifestURL,
+      subAppManifestURL: aSubManifestURL
+    },
+    target: {
+      assertContainApp: function(_manifestURL) {
+        return (aManifestURL == _manifestURL);
+      }
+    }
+  };
+
+  InterAppCommService.receiveMessage(message);
+}
+
+add_test(function test_registerMessagePort() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  let targets = register_message_ports(MESSAGE_PORT_ID);
+
+  let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
+
+  do_check_eq(PUB_APP_PAGE_URL, messagePortPair.publisher.pageURL);
+  do_check_eq(SUB_APP_PAGE_URL, messagePortPair.subscriber.pageURL);
+
+  do_check_true(targets.pubTarget === messagePortPair.publisher.target);
+  do_check_true(targets.subTarget === messagePortPair.subscriber.target);
+
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+add_test(function test_failToRegisterMessagePort() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  let targets = register_message_ports(FAKE_MESSAGE_PORT_ID);
+
+  let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
+
+  // Because it failed to register, the page URLs and targets don't exist.
+  do_check_true(messagePortPair.publisher.pageURL === undefined);
+  do_check_true(messagePortPair.subscriber.pageURL === undefined);
+
+  do_check_true(messagePortPair.publisher.target === undefined);
+  do_check_true(messagePortPair.subscriber.target === undefined);
+
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+add_test(function test_unregisterMessagePort() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  register_message_ports(MESSAGE_PORT_ID);
+
+  unregister_message_port(MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
+
+  do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
+                === undefined);
+
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+add_test(function test_failToUnregisterMessagePort() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  register_message_ports(MESSAGE_PORT_ID);
+
+  unregister_message_port(FAKE_MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
+
+  // Because it failed to unregister, the entry still exists.
+  do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
+                !== undefined);
+
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+add_test(function test_removeTarget() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  let targets = register_message_ports(MESSAGE_PORT_ID);
+
+  remove_target(targets.pubTarget);
+
+  do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
+                === undefined);
+
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+add_test(function test_postMessage() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  let countPubAppOnMessage = 0;
+  function pubAppOnMessage(aName, aData) {
+    countPubAppOnMessage++;
+
+    do_check_eq(aName, "InterAppMessagePort:OnMessage");
+    do_check_eq(aData.manifestURL, PUB_APP_MANIFEST_URL);
+    do_check_eq(aData.pageURL, PUB_APP_PAGE_URL);
+    do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
+
+    if (countPubAppOnMessage == 1) {
+      do_check_eq(aData.message.text, "sub app says world");
+
+      post_message(MESSAGE_PORT_ID,
+                   PUB_APP_MANIFEST_URL,
+                   { text: "pub app says hello again" });
+
+    } else if (countPubAppOnMessage == 2) {
+      do_check_eq(aData.message.text, "sub app says world again");
+
+      clear_message_port_pairs();
+      run_next_test();
+    } else {
+      do_throw("pub app receives an unexpected message")
+    }
+  };
+
+  let countSubAppOnMessage = 0;
+  function subAppOnMessage(aName, aData) {
+    countSubAppOnMessage++;
+
+    do_check_eq(aName, "InterAppMessagePort:OnMessage");
+    do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
+    do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
+    do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
+
+    if (countSubAppOnMessage == 1) {
+      do_check_eq(aData.message.text, "pub app says hello");
+
+      post_message(MESSAGE_PORT_ID,
+                   SUB_APP_MANIFEST_URL,
+                   { text: "sub app says world" });
+
+    } else if (countSubAppOnMessage == 2) {
+      do_check_eq(aData.message.text, "pub app says hello again");
+
+      post_message(MESSAGE_PORT_ID,
+                   SUB_APP_MANIFEST_URL,
+                   { text: "sub app says world again" });
+    } else {
+      do_throw("sub app receives an unexpected message")
+    }
+  };
+
+  register_message_ports(MESSAGE_PORT_ID, pubAppOnMessage, subAppOnMessage);
+
+  post_message(MESSAGE_PORT_ID,
+               PUB_APP_MANIFEST_URL,
+               { text: "pub app says hello" });
+});
+
+add_test(function test_registerMessagePort_with_queued_messages() {
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  register_message_port(MESSAGE_PORT_ID,
+                        PUB_APP_MANIFEST_URL,
+                        PUB_APP_PAGE_URL);
+
+  post_message(MESSAGE_PORT_ID,
+               PUB_APP_MANIFEST_URL,
+               { text: "pub app says hello" });
+
+  post_message(MESSAGE_PORT_ID,
+               PUB_APP_MANIFEST_URL,
+               { text: "pub app says hello again" });
+
+  let countSubAppOnMessage = 0;
+  function subAppOnMessage(aName, aData) {
+    countSubAppOnMessage++;
+
+    do_check_eq(aName, "InterAppMessagePort:OnMessage");
+    do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
+    do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
+    do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
+
+    if (countSubAppOnMessage == 1) {
+      do_check_eq(aData.message.text, "pub app says hello");
+    } else if (countSubAppOnMessage == 2) {
+      do_check_eq(aData.message.text, "pub app says hello again");
+
+      clear_message_port_pairs();
+      run_next_test();
+    } else {
+      do_throw("sub app receives an unexpected message")
+    }
+  };
+
+  register_message_port(MESSAGE_PORT_ID,
+                        SUB_APP_MANIFEST_URL,
+                        SUB_APP_PAGE_URL,
+                        subAppOnMessage);
+});
+
+add_test(function test_getConnections() {
+  create_allowed_connections(KEYWORD,
+                             PUB_APP_MANIFEST_URL,
+                             SUB_APP_MANIFEST_URL);
+
+  function onGetConnections(aName, aData) {
+    do_check_eq(aName, "Webapps:GetConnections:Return:OK");
+    do_check_eq(aData.oid, OUTER_WINDOW_ID);
+    do_check_eq(aData.requestID, REQUEST_ID);
+
+    let connections = aData.connections;
+    do_check_eq(connections.length, 1);
+    do_check_eq(connections[0].keyword, KEYWORD);
+    do_check_eq(connections[0].pubAppManifestURL, PUB_APP_MANIFEST_URL);
+    do_check_eq(connections[0].subAppManifestURL, SUB_APP_MANIFEST_URL);
+
+    clear_allowed_connections();
+    run_next_test();
+  };
+
+  get_connections(PUB_APP_MANIFEST_URL,
+                  OUTER_WINDOW_ID,
+                  REQUEST_ID,
+                  onGetConnections);
+});
+
+add_test(function test_cancelConnection() {
+  create_allowed_connections(KEYWORD,
+                             PUB_APP_MANIFEST_URL,
+                             SUB_APP_MANIFEST_URL);
+
+  create_message_port_pair(MESSAGE_PORT_ID,
+                           KEYWORD,
+                           PUB_APP_MANIFEST_URL,
+                           SUB_APP_MANIFEST_URL);
+
+  register_message_ports(MESSAGE_PORT_ID);
+
+  cancel_connections(PUB_APP_MANIFEST_URL,
+                     KEYWORD,
+                     PUB_APP_MANIFEST_URL,
+                     SUB_APP_MANIFEST_URL);
+
+  do_check_true(InterAppCommService._allowedConnections[KEYWORD]
+                === undefined);
+
+  do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
+                === undefined);
+
+  clear_allowed_connections();
+  clear_message_port_pairs();
+  run_next_test();
+});
+
+function run_test() {
+  do_get_profile();
+
+  run_next_test();
+}
--- a/dom/apps/tests/unit/xpcshell.ini
+++ b/dom/apps/tests/unit/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head =
 tail =
 
+[test_inter_app_comm_service.js]
 [test_manifestSanitizer.js]
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1624,68 +1624,58 @@ int64_t GetPluginLastModifiedTime(const 
   localfile->GetLastModifiedTime(&fileModTime);
 #endif
 
   return fileModTime;
 }
 
 bool
 GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
-                         const nsCOMPtr<nsISimpleEnumerator>& extensionDirs)
+                         const nsCOMArray<nsIFile>& extensionDirs)
 {
-  if (!extensionDirs) {
-    return false;
-  }
-
-  bool hasMore;
-  while (NS_SUCCEEDED(extensionDirs->HasMoreElements(&hasMore)) && hasMore) {
-    nsCOMPtr<nsISupports> supports;
-    nsresult rv = extensionDirs->GetNext(getter_AddRefs(supports));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIFile> extDir(do_QueryInterface(supports, &rv));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIFile> dir;
-    if (NS_FAILED(extDir->Clone(getter_AddRefs(dir)))) {
-      continue;
-    }
-
+  for (uint32_t i = 0; i < extensionDirs.Length(); ++i) {
     bool contains;
-    if (NS_FAILED(dir->Contains(pluginFile, true, &contains)) || !contains) {
+    if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, true, &contains)) || !contains) {
       continue;
     }
 
     return true;
   }
 
   return false;
 }
 
-nsCOMPtr<nsISimpleEnumerator>
-GetExtensionDirectories()
+void
+GetExtensionDirectories(nsCOMArray<nsIFile>& dirs)
 {
   nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
   if (!dirService) {
-    return nullptr;
+    return;
   }
 
   nsCOMPtr<nsISimpleEnumerator> list;
   nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST,
                                 NS_GET_IID(nsISimpleEnumerator),
                                 getter_AddRefs(list));
   if (NS_FAILED(rv)) {
-    return nullptr;
+    return;
   }
 
-  return list;
+  bool more;
+  while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) {
+    nsCOMPtr<nsISupports> next;
+    if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) {
+      break;
+    }
+    nsCOMPtr<nsIFile> file = do_QueryInterface(next);
+    if (file) {
+      file->Normalize();
+      dirs.AppendElement(file);
+    }
+  }
 }
 
 struct CompareFilesByTime
 {
   bool
   LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
   {
     return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b);
@@ -1741,20 +1731,18 @@ nsresult nsPluginHost::ScanPluginsDirect
 
     if (nsPluginsDir::IsPluginFile(dirEntry)) {
       pluginFiles.AppendElement(dirEntry);
     }
   }
 
   pluginFiles.Sort(CompareFilesByTime());
 
-  nsCOMPtr<nsISimpleEnumerator> extensionDirs = GetExtensionDirectories();
-  if (!extensionDirs) {
-    PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("Could not get extension directories."));
-  }
+  nsCOMArray<nsIFile> extensionDirs;
+  GetExtensionDirectories(extensionDirs);
 
   bool warnOutdated = false;
 
   for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
     nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
 
     nsString utf16FilePath;
     rv = localfile->GetPath(utf16FilePath);
--- a/dom/plugins/test/testaddon/Makefile.in
+++ b/dom/plugins/test/testaddon/Makefile.in
@@ -1,23 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/config/rules.mk
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-plugin_file_name = Test.plugin
+plugin_file_names = Test.plugin SecondTest.plugin
 addon_file_name = testaddon_$(TARGET_XPCOM_ABI).xpi
 else
-plugin_file_name = $(DLL_PREFIX)nptest$(DLL_SUFFIX)
+plugin_file_names = $(DLL_PREFIX)nptest$(DLL_SUFFIX) $(DLL_PREFIX)npsecondtest$(DLL_SUFFIX)
 addon_file_name = testaddon.xpi
 endif
 
 # This is so hacky. Waiting on bug 988938.
 testdir = $(abspath $(DEPTH)/_tests/xpcshell/dom/plugins/test/unit/)
 addonpath = $(testdir)/$(addon_file_name)
 
 libs::
 	$(NSINSTALL) -D $(testdir)
 	rm -f $(addonpath)
 	cd $(srcdir) && zip -rD $(addonpath) install.rdf
-	cd $(DIST) && zip -rD $(addonpath) plugins/$(plugin_file_name)
+	cd $(DIST) && zip -rD $(addonpath) $(foreach name,$(plugin_file_names),plugins/$(name))
--- a/dom/plugins/test/unit/head_plugins.js
+++ b/dom/plugins/test/unit/head_plugins.js
@@ -9,37 +9,23 @@ Cu.import("resource://gre/modules/Promis
 
 const gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 const gIsOSX = ("nsILocalFileMac" in Ci);
 const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc) ||
   ("@mozilla.org/gio-service;1" in Cc);
 const gDirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
 
 // Finds the test plugin library
-function get_test_plugin() {
+function get_test_plugin(secondplugin=false) {
   var pluginEnum = gDirSvc.get("APluginsDL", Ci.nsISimpleEnumerator);
   while (pluginEnum.hasMoreElements()) {
     let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile);
+    let name = get_platform_specific_plugin_name(secondplugin);
     let plugin = dir.clone();
-    // OSX plugin
-    plugin.append("Test.plugin");
-    if (plugin.exists()) {
-      plugin.normalize();
-      return plugin;
-    }
-    plugin = dir.clone();
-    // *nix plugin
-    plugin.append("libnptest.so");
-    if (plugin.exists()) {
-      plugin.normalize();
-      return plugin;
-    }
-    // Windows plugin
-    plugin = dir.clone();
-    plugin.append("nptest.dll");
+    plugin.append(name);
     if (plugin.exists()) {
       plugin.normalize();
       return plugin;
     }
   }
   return null;
 }
 
@@ -88,21 +74,27 @@ function do_get_profile_startup() {
       throw Components.results.NS_ERROR_NO_INTERFACE;
     }
   };
   dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService)
         .registerProvider(provider);
   return file.clone();
 }
 
-function get_platform_specific_plugin_name() {
-  if (gIsWindows) return "nptest.dll";
-  else if (gIsOSX) return "Test.plugin";
-  else if (gIsLinux) return "libnptest.so";
-  else return null;
+function get_platform_specific_plugin_name(secondplugin=false) {
+  if (secondplugin) {
+    if (gIsWindows) return "npsecondtest.dll";
+    if (gIsOSX) return "SecondTest.plugin";
+    if (gIsLinux) return "libnpsecondtest.so";
+  } else {
+    if (gIsWindows) return "nptest.dll";
+    if (gIsOSX) return "Test.plugin";
+    if (gIsLinux) return "libnptest.so";
+  }
+  return null;
 }
 
 function get_platform_specific_plugin_suffix() {
   if (gIsWindows) return ".dll";
   else if (gIsOSX) return ".plugin";
   else if (gIsLinux) return ".so";
   else return null;
 }
--- a/dom/plugins/test/unit/test_plugin_default_state_xpi.js
+++ b/dom/plugins/test/unit/test_plugin_default_state_xpi.js
@@ -36,16 +36,21 @@ function run_test() {
   run_next_test();
 }
 
 add_task(function* test_state() {
   // Remove test so we will have only one "Test Plug-in" registered.
   // xpcshell tests have plugins in per-test profiles, so that's fine.
   let file = get_test_plugin();
   file.remove(true);
+  file = get_test_plugin(true);
+  file.remove(true);
+
+  Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED);
 
   let success = yield installAddon(getTestaddonFilename());
   Assert.ok(success, "Should have installed addon.");
   let addonDir = getAddonRoot(gProfileDir, ADDON_ID);
 
   let provider = {
     classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"),
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
@@ -100,9 +105,13 @@ add_task(function* test_state() {
   Assert.ok(!addon.userDisabled, "Addon should not be user disabled");
 
   let testPlugin = get_test_plugintag();
   Assert.notEqual(testPlugin, null, "Test plugin should have been found");
   Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled");
 
   pluginDir.append(testPlugin.filename);
   Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path);
+
+  testPlugin = get_test_plugintag("Second Test Plug-in");
+  Assert.notEqual(testPlugin, null, "Second test plugin should have been found");
+  Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled");
 });
--- a/layout/tools/reftest/b2g_desktop.py
+++ b/layout/tools/reftest/b2g_desktop.py
@@ -59,17 +59,18 @@ class B2GDesktopReftest(RefTest):
                 if mozinfo.info['debug']:
                     options.timeout = 420
                 else:
                     options.timeout = 300
             self.timeout = options.timeout + 30.0
 
         log.info("%s | Running tests: start.", os.path.basename(__file__))
         cmd, args = self.build_command_line(options.app,
-                            ignore_window_size=options.ignoreWindowSize)
+                            ignore_window_size=options.ignoreWindowSize,
+                            browser_arg=options.browser_arg)
         self.runner = FirefoxRunner(profile=self.profile,
                                     binary=cmd,
                                     cmdargs=args,
                                     env=env,
                                     process_class=ProcessHandler,
                                     symbols_path=options.symbolsPath,
                                     kp_kwargs=kp_kwargs)
 
@@ -118,20 +119,24 @@ class B2GDesktopReftest(RefTest):
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
 
         # Set the extra prefs.
         profile.set_preferences(prefs)
         return profile
 
-    def build_command_line(self, app, ignore_window_size=False):
+    def build_command_line(self, app, ignore_window_size=False,
+                           browser_arg=None):
         cmd = os.path.abspath(app)
         args = ['-marionette']
 
+        if browser_arg:
+            args += [browser_arg]
+
         if not ignore_window_size:
             args.extend(['--screen', '800x1000'])
         return cmd, args
 
     def _on_output(self, line):
         print(line)
         # TODO use structured logging
         if "TEST-START" in line and "|" in line:
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -26,16 +26,21 @@ class B2GOptions(ReftestOptions):
 
     def __init__(self, automation=None, **kwargs):
         defaults = {}
         if not automation:
             automation = B2GRemoteAutomation(None, "fennec", context_chrome=True)
 
         ReftestOptions.__init__(self, automation)
 
+        self.add_option("--browser-arg", action="store",
+                    type = "string", dest = "browser_arg",
+                    help = "Optional command-line arg to pass to the browser")
+        defaults["browser_arg"] = None
+
         self.add_option("--b2gpath", action="store",
                     type = "string", dest = "b2gPath",
                     help = "path to B2G repo or qemu dir")
         defaults["b2gPath"] = None
 
         self.add_option("--marionette", action="store",
                     type = "string", dest = "marionette",
                     help = "host:port to use when connecting to Marionette")
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1915,17 +1915,17 @@ abstract public class BrowserApp extends
     }
 
     /**
      * Add the provided item to the provided menu, which should be
      * the root (mMenu).
      */
     private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
         info.added = true;
-        
+
         final Menu destination;
         if (info.parent == 0) {
             destination = menu;
         } else if (info.parent == GECKO_TOOLS_MENU) {
             MenuItem tools = menu.findItem(R.id.tools);
             destination = tools != null ? tools.getSubMenu() : menu;
         } else {
             MenuItem parent = menu.findItem(info.parent);
@@ -2051,17 +2051,17 @@ abstract public class BrowserApp extends
         }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Sets mMenu = menu.
         super.onCreateOptionsMenu(menu);
 
-        // Inform the menu about the action-items bar. 
+        // Inform the menu about the action-items bar.
         if (menu instanceof GeckoMenu &&
             HardwareUtils.isTablet()) {
             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
         }
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.browser_app_menu, mMenu);
 
@@ -2465,17 +2465,17 @@ abstract public class BrowserApp extends
             }
         }
         return super.onKeyLongPress(keyCode, event);
     }
 
     /*
      * If the app has been launched a certain number of times, and we haven't asked for feedback before,
      * open a new tab with about:feedback when launching the app from the icon shortcut.
-     */ 
+     */
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
 
         String action = intent.getAction();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
             String uri = intent.getDataString();
@@ -2567,17 +2567,17 @@ abstract public class BrowserApp extends
             }
         }).execute();
     }
 
     // HomePager.OnNewTabsListener
     @Override
     public void onNewTabs(String[] urls) {
         final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
- 
+
         for (String url : urls) {
             if (!maybeSwitchToTab(url, flags)) {
                 openUrlAndStopEditing(url, true);
             }
         }
     }
 
     // HomePager.OnUrlOpenListener
--- a/mobile/android/base/gfx/TouchEventHandler.java
+++ b/mobile/android/base/gfx/TouchEventHandler.java
@@ -249,16 +249,18 @@ final class TouchEventHandler implements
 
         MotionEvent event = mEventQueue.poll();
         while (true) {
             // event being null here is valid and represents a block of events
             // that has already been dispatched.
 
             if (event != null) {
                 dispatchEvent(event, allowDefaultAction);
+                event.recycle();
+                event = null;
             }
             if (mEventQueue.isEmpty()) {
                 // we have processed the backlog of events, and are all caught up.
                 // now we can set clear the hold flag and set the dispatch flag so
                 // that the handleEvent() function can do the right thing for all
                 // remaining events in this block (which is still ongoing) without
                 // having to put them in the queue.
                 mHoldInQueue = false;
--- a/mobile/android/base/home/PanelItemView.java
+++ b/mobile/android/base/home/PanelItemView.java
@@ -73,16 +73,19 @@ class PanelItemView extends LinearLayout
                    .into(image);
         }
     }
 
     private static class ArticleItemView extends PanelItemView {
         private ArticleItemView(Context context) {
             super(context, R.layout.panel_article_item);
             setOrientation(LinearLayout.HORIZONTAL);
+
+            final int padding = getResources().getDimensionPixelSize(R.dimen.article_item_view_padding);
+            setPadding(0, padding, 0, padding);
         }
     }
 
     private static class ImageItemView extends PanelItemView {
         private ImageItemView(Context context) {
             super(context, R.layout.panel_image_item);
             setOrientation(LinearLayout.VERTICAL);
         }
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -11,191 +11,90 @@ import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class TabMenuStrip extends LinearLayout
-                          implements HomePager.Decor,
-                                     View.OnFocusChangeListener {
-    private static final String LOGTAG = "GeckoTabMenuStrip";
+/**
+ * {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
+ * on tablets. See {@code TabMenuStripLayout} for details about how the
+ * tabs are created and updated.
+ */
+public class TabMenuStrip extends HorizontalScrollView
+                          implements HomePager.Decor {
 
-    private HomePager.OnTitleClickListener mOnTitleClickListener;
-    private Drawable mStrip;
-    private View mSelectedView;
+    // Offset between the selected tab title and the edge of the screen,
+    // except for the first and last tab in the tab strip.
+    private static final int TITLE_OFFSET_DIPS = 24;
 
-    // Data associated with the scrolling of the strip drawable.
-    private View toTab;
-    private View fromTab;
-    private float progress;
-
-    // This variable is used to predict the direction of scroll.
-    private float mPrevProgress;
+    private final int titleOffset;
+    private final TabMenuStripLayout layout;
 
     public TabMenuStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
-        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
-        a.recycle();
+        // Disable the scroll bar.
+        setHorizontalScrollBarEnabled(false);
 
-        if (stripResId != -1) {
-            mStrip = getResources().getDrawable(stripResId);
-        }
+        titleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
 
-        setWillNotDraw(false);
+        layout = new TabMenuStripLayout(context, attrs);
+        addView(layout, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
     }
 
     @Override
     public void onAddPagerView(String title) {
-        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
-        button.setText(title.toUpperCase());
-
-        addView(button);
-        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
-        button.setOnFocusChangeListener(this);
+        layout.onAddPagerView(title);
     }
 
     @Override
     public void removeAllPagerViews() {
-        removeAllViews();
+        layout.removeAllViews();
     }
 
     @Override
     public void onPageSelected(final int position) {
-        mSelectedView = getChildAt(position);
-
-        // Callback to measure and draw the strip after the view is visible.
-        ViewTreeObserver vto = mSelectedView.getViewTreeObserver();
-        if (vto.isAlive()) {
-            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
-                @Override
-                public void onGlobalLayout() {
-                    mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-
-                    if (mStrip != null) {
-                        mStrip.setBounds(mSelectedView.getLeft(),
-                                         mSelectedView.getTop(),
-                                         mSelectedView.getRight(),
-                                         mSelectedView.getBottom());
-                    }
-
-                    mPrevProgress = position;
-                }
-            });
-        }
+        layout.onPageSelected(position);
     }
 
-    // Page scroll animates the drawable and its bounds from the previous to next child view.
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        if (mStrip == null) {
-            return;
-        }
+        layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
 
-        setScrollingData(position, positionOffset);
-
-        if (fromTab == null || toTab == null) {
+        final View selectedTitle = layout.getChildAt(position);
+        if (selectedTitle == null) {
             return;
         }
 
-        final int fromTabLeft =  fromTab.getLeft();
-        final int fromTabRight = fromTab.getRight();
-
-        final int toTabLeft =  toTab.getLeft();
-        final int toTabRight = toTab.getRight();
-
-        mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
-                         0,
-                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
-                         getHeight());
-        invalidate();
-    }
+        final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
 
-    /*
-     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
-     * Normalized progress is relative to the the direction the page is being scrolled towards.
-     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
-     */
-    private void setScrollingData(int position, float positionOffset) {
-        if (position >= getChildCount() - 1) {
-            return;
-        }
-
-        final float currProgress = position + positionOffset;
-
-        if (mPrevProgress > currProgress) {
-            toTab = getChildAt(position);
-            fromTab = getChildAt(position + 1);
-            progress = 1 - positionOffset;
-        } else {
-            toTab = getChildAt(position + 1);
-            fromTab = getChildAt(position);
-            progress = positionOffset;
+        int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
+        if (position > 0) {
+            titleLeft -= titleOffset;
         }
 
-        mPrevProgress = currProgress;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (mStrip != null) {
-            mStrip.draw(canvas);
-        }
-    }
-
-    @Override
-    public void onFocusChange(View v, boolean hasFocus) {
-        if (v == this && hasFocus && getChildCount() > 0) {
-            mSelectedView.requestFocus();
-            return;
+        int titleRight = selectedTitle.getRight() + selectedTitleOffset;
+        if (position < layout.getChildCount() - 1) {
+            titleRight += titleOffset;
         }
 
-        if (!hasFocus) {
-            return;
-        }
-
-        int i = 0;
-        final int numTabs = getChildCount();
-
-        while (i < numTabs) {
-            View view = getChildAt(i);
-            if (view == v) {
-                view.requestFocus();
-                if (isShown()) {
-                    // A view is focused so send an event to announce the menu strip state.
-                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-                }
-                break;
-            }
-
-            i++;
+        final int scrollX = getScrollX();
+        if (titleLeft < scrollX) {
+            // Tab strip overflows to the left.
+            scrollTo(titleLeft, 0);
+        } else if (titleRight > scrollX + getWidth()) {
+            // Tab strip overflows to the right.
+            scrollTo(titleRight - getWidth(), 0);
         }
     }
 
     @Override
     public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
-        mOnTitleClickListener = onTitleClickListener;
-    }
-
-    private class ViewClickListener implements OnClickListener {
-        private final int mIndex;
-
-        public ViewClickListener(int index) {
-            mIndex = index;
-        }
-
-        @Override
-        public void onClick(View view) {
-            if (mOnTitleClickListener != null) {
-                mOnTitleClickListener.onTitleClicked(mIndex);
-            }
-        }
+        layout.setOnTitleClickListener(onTitleClickListener);
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/TabMenuStripLayout.java
@@ -0,0 +1,194 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
+ * tabs that are displayed in {@code TabMenuStrip}.
+ */
+class TabMenuStripLayout extends LinearLayout
+                         implements View.OnFocusChangeListener {
+
+    private HomePager.OnTitleClickListener onTitleClickListener;
+    private Drawable strip;
+    private View selectedView;
+
+    // Data associated with the scrolling of the strip drawable.
+    private View toTab;
+    private View fromTab;
+    private float progress;
+
+    // This variable is used to predict the direction of scroll.
+    private float prevProgress;
+
+    TabMenuStripLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
+        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
+        a.recycle();
+
+        if (stripResId != -1) {
+            strip = getResources().getDrawable(stripResId);
+        }
+
+        setWillNotDraw(false);
+    }
+
+    void onAddPagerView(String title) {
+        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
+        button.setText(title.toUpperCase());
+
+        addView(button);
+        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
+        button.setOnFocusChangeListener(this);
+    }
+
+    void onPageSelected(final int position) {
+        selectedView = getChildAt(position);
+
+        // Callback to measure and draw the strip after the view is visible.
+        ViewTreeObserver vto = selectedView.getViewTreeObserver();
+        if (vto.isAlive()) {
+            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+
+                    if (strip != null) {
+                        strip.setBounds(selectedView.getLeft(),
+                                        selectedView.getTop(),
+                                        selectedView.getRight(),
+                                        selectedView.getBottom());
+                    }
+
+                    prevProgress = position;
+                }
+            });
+        }
+    }
+
+    // Page scroll animates the drawable and its bounds from the previous to next child view.
+    void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        if (strip == null) {
+            return;
+        }
+
+        setScrollingData(position, positionOffset);
+
+        if (fromTab == null || toTab == null) {
+            return;
+        }
+
+        final int fromTabLeft =  fromTab.getLeft();
+        final int fromTabRight = fromTab.getRight();
+
+        final int toTabLeft =  toTab.getLeft();
+        final int toTabRight = toTab.getRight();
+
+        strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
+                         0,
+                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
+                         getHeight());
+        invalidate();
+    }
+
+    /*
+     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
+     * Normalized progress is relative to the the direction the page is being scrolled towards.
+     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
+     */
+    void setScrollingData(int position, float positionOffset) {
+        if (position >= getChildCount() - 1) {
+            return;
+        }
+
+        final float currProgress = position + positionOffset;
+
+        if (prevProgress > currProgress) {
+            toTab = getChildAt(position);
+            fromTab = getChildAt(position + 1);
+            progress = 1 - positionOffset;
+        } else {
+            toTab = getChildAt(position + 1);
+            fromTab = getChildAt(position);
+            progress = positionOffset;
+        }
+
+        prevProgress = currProgress;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (strip != null) {
+            strip.draw(canvas);
+        }
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == this && hasFocus && getChildCount() > 0) {
+            selectedView.requestFocus();
+            return;
+        }
+
+        if (!hasFocus) {
+            return;
+        }
+
+        int i = 0;
+        final int numTabs = getChildCount();
+
+        while (i < numTabs) {
+            View view = getChildAt(i);
+            if (view == v) {
+                view.requestFocus();
+                if (isShown()) {
+                    // A view is focused so send an event to announce the menu strip state.
+                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                }
+                break;
+            }
+
+            i++;
+        }
+    }
+
+    void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
+        this.onTitleClickListener = onTitleClickListener;
+    }
+
+    private class ViewClickListener implements OnClickListener {
+        private final int mIndex;
+
+        public ViewClickListener(int index) {
+            mIndex = index;
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (onTitleClickListener != null) {
+                onTitleClickListener.onTitleClicked(mIndex);
+            }
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -274,16 +274,17 @@ gbjar.sources += [
     'home/ReadingListPanel.java',
     'home/ReadingListRow.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SuggestClient.java',
     'home/TabMenuStrip.java',
+    'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
     'home/TopSitesGridView.java',
     'home/TopSitesPanel.java',
     'home/TopSitesThumbnailView.java',
     'home/TwoLinePageRow.java',
     'InputMethods.java',
     'JavaAddonManager.java',
     'LightweightTheme.java',
--- a/mobile/android/base/resources/layout/panel_article_item.xml
+++ b/mobile/android/base/resources/layout/panel_article_item.xml
@@ -3,29 +3,24 @@
    - 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <ImageView android:id="@+id/image"
                android:layout_width="54dp"
                android:layout_height="44dp"
-               android:layout_marginTop="10dip"
-               android:layout_marginBottom="10dip"
                android:layout_marginLeft="10dip"
                android:scaleType="centerCrop"/>
 
     <LinearLayout android:id="@+id/title_desc_container"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
-                  android:paddingTop="15dip"
-                  android:paddingBottom="15dip"
                   android:paddingLeft="10dip"
                   android:paddingRight="10dip"
-                  android:minHeight="@dimen/page_row_height"
                   android:gravity="center_vertical"
                   android:orientation="vertical">
 
         <TextView android:id="@+id/title"
                   style="@style/Widget.PanelItemView.Title"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"/>
 
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -102,9 +102,12 @@
     <dimen name="home_banner_height">72dp</dimen>
 
     <!-- Icon Grid -->
     <dimen name="icongrid_columnwidth">128dp</dimen>
     <dimen name="icongrid_padding">16dp</dimen>
 
     <!-- PanelGridView dimensions -->
     <dimen name="panel_grid_view_column_width">150dp</dimen>
+
+    <!-- ArticleItemView dimensions -->
+    <dimen name="article_item_view_padding">15dp</dimen>
 </resources>
--- a/mobile/android/base/tests/BaseTest.java
+++ b/mobile/android/base/tests/BaseTest.java
@@ -433,19 +433,18 @@ abstract class BaseTest extends BaseRobo
     public void selectMenuItemByPath(String[] listItems) {
         int listLength = listItems.length;
         if (listLength > 0) {
             selectMenuItem(listItems[0]);
         }
         if (listLength > 1) {
             for (int i = 1; i < listLength; i++) {
                 String itemName = "^" + listItems[i] + "$";
-                if (!waitForPreferencesText(itemName)) {
-                    mSolo.scrollDown();
-                }
+                mAsserter.ok(waitForPreferencesText(itemName), "Waiting for and scrolling once to find item " + itemName, itemName + " found");
+                mAsserter.ok(waitForEnabledText(itemName), "Waiting for enabled text " + itemName, itemName + " option is present and enabled");
                 mSolo.clickOnText(itemName);
             }
         }
     }
 
     public final void selectMenuItem(String menuItemName) {
         // build the item name ready to be used
         String itemName = "^" + menuItemName + "$";
--- a/mobile/android/base/tests/MotionEventHelper.java
+++ b/mobile/android/base/tests/MotionEventHelper.java
@@ -21,39 +21,54 @@ class MotionEventHelper {
         mSurfaceOffsetY = surfaceOffsetY;
         Log.i(LOGTAG, "Initialized using offset (" + mSurfaceOffsetX + "," + mSurfaceOffsetY + ")");
     }
 
     public long down(float x, float y) {
         Log.d(LOGTAG, "Triggering down at (" + x + "," + y + ")");
         long downTime = SystemClock.uptimeMillis();
         MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return downTime;
     }
 
     public long move(long downTime, float x, float y) {
         return move(downTime, SystemClock.uptimeMillis(), x, y);
     }
 
     public long move(long downTime, long moveTime, float x, float y) {
         Log.d(LOGTAG, "Triggering move to (" + x + "," + y + ")");
         MotionEvent event = MotionEvent.obtain(downTime, moveTime, MotionEvent.ACTION_MOVE, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return downTime;
     }
 
     public long up(long downTime, float x, float y) {
         return up(downTime, SystemClock.uptimeMillis(), x, y);
     }
 
     public long up(long downTime, long upTime, float x, float y) {
         Log.d(LOGTAG, "Triggering up at (" + x + "," + y + ")");
         MotionEvent event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return -1L;
     }
 
     public Thread dragAsync(final float startX, final float startY, final float endX, final float endY, final long durationMillis) {
         Thread t = new Thread() {
             @Override
             public void run() {
                 int numEvents = (int)(durationMillis * DRAG_EVENTS_PER_SECOND / 1000);
--- a/mobile/android/base/tests/MotionEventReplayer.java
+++ b/mobile/android/base/tests/MotionEventReplayer.java
@@ -198,18 +198,23 @@ class MotionEventReplayer {
                             long.class, long.class, int.class, int.class, pointerIds.getClass(),
                             pointerData.getClass(), int.class, float.class, float.class,
                             int.class, int.class);
                     }
                     event = (MotionEvent)mObtainNanoMethod.invoke(null, downTime, eventTime,
                             eventTime * 1000000, action, pointerCount, pointerIds, (float[])pointerData,
                             metaState, xPrecision, yPrecision, deviceId, edgeFlags);
                 }
-                Log.v(LOGTAG, "Injecting " + event.toString());
-                mInstrumentation.sendPointerSync(event);
+                try {
+                    Log.v(LOGTAG, "Injecting " + event.toString());
+                    mInstrumentation.sendPointerSync(event);
+                } finally {
+                    event.recycle();
+                    event = null;
+                }
 
                 eventProperties.clear();
             }
         } finally {
             br.close();
         }
     }
 }
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -45,16 +45,17 @@ import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
@@ -185,17 +186,20 @@ public class BrowserToolbar extends Them
         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);
 
         urlBarEntryDefaultLayoutParams = (RelativeLayout.LayoutParams) urlBarEntry.getLayoutParams();
-        urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(urlBarEntryDefaultLayoutParams);
+        // API level 19 adds a RelativeLayout.LayoutParams copy constructor, so we explicitly cast
+        // to ViewGroup.MarginLayoutParams to ensure consistency across platforms.
+        urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(
+                (ViewGroup.MarginLayoutParams) urlBarEntryDefaultLayoutParams);
         urlBarEntryShrunkenLayoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.edit_layout);
         urlBarEntryShrunkenLayoutParams.rightMargin = 0;
 
         // This will clip the translating edge's image at 60% of its width
         urlBarTranslatingEdge = (ImageView) findViewById(R.id.url_bar_translating_edge);
         if (urlBarTranslatingEdge != null) {
             urlBarTranslatingEdge.getDrawable().setLevel(6000);
         }
@@ -392,17 +396,17 @@ public class BrowserToolbar extends Them
     }
 
     public void refresh() {
         urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onBackPressed() {
         if (isEditing()) {
-            stopEditing();
+            cancelEdit();
             return true;
         }
 
         return urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
--- a/testing/config/mozharness/b2g_desktop_config.py
+++ b/testing/config/mozharness/b2g_desktop_config.py
@@ -3,17 +3,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 config = {
     "mochitest_options": [
         "--console-level=INFO", "%(test_manifest)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "--profile=%(gaia_profile)s", "--app=%(application)s", "--desktop",
         "--utility-path=%(utility_path)s", "--certificate-path=%(cert_path)s",
-        "--symbols-path=%(symbols_path)s",
+        "--symbols-path=%(symbols_path)s", "--browser-arg=%(browser_arg)s",
         "--quiet"
     ],
 
     "reftest_options": [
         "--desktop", "--profile=%(gaia_profile)s", "--appname=%(application)s",
-        "--symbols-path=%(symbols_path)s", "%(test_manifest)s",
+        "--browser-arg=%(browser_arg)s", "--symbols-path=%(symbols_path)s",
+        "%(test_manifest)s"
     ]
 }