Merge m-c to inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 21 Nov 2014 16:59:03 -0800
changeset 241293 3e56ff4fc166f403d22cf72ea211c9c207691018
parent 241292 281d52f5d0e65f3071b6314ad8be2253cc593038 (current diff)
parent 241262 7ab92d922d193944248a608e961598e1cb6a000f (diff)
child 241294 fe4bdefa88d15456f9f98972b4e1881c83566761
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge
dom/browser-element/BrowserElementParent.js
dom/browser-element/BrowserElementParent.jsm
--- a/b2g/chrome/content/desktop.js
+++ b/b2g/chrome/content/desktop.js
@@ -25,21 +25,25 @@ function setupButtons() {
     // The toolbar only exists in b2g desktop build with
     // FXOS_SIMULATOR turned on.
     return;
   }
   // The touch event helper is enabled on shell.html document,
   // so that click events are delayed and it is better to
   // listen for touch events.
   homeButton.addEventListener('touchstart', function() {
-    shell.sendChromeEvent({type: 'home-button-press'});
+    let window = shell.contentBrowser.contentWindow;
+    let e = new window.KeyboardEvent('keydown', {key: 'Home'});
+    window.dispatchEvent(e);
     homeButton.classList.add('active');
   });
   homeButton.addEventListener('touchend', function() {
-    shell.sendChromeEvent({type: 'home-button-release'});
+    let window = shell.contentBrowser.contentWindow;
+    let e = new window.KeyboardEvent('keyup', {key: 'Home'});
+    window.dispatchEvent(e);
     homeButton.classList.remove('active');
   });
 
   Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
   let rotateButton = document.getElementById('rotate-button');
   rotateButton.addEventListener('touchstart', function () {
     rotateButton.classList.add('active');
   });
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,25 +10,25 @@
   <!--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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <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="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <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="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <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="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "dc9a51bd4d985bcb30c9a4413bdb5084f0f500ea", 
+    "revision": "bf8bc38f28bda6ccddffbbedd40693ed8a10322c", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- 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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <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/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="25388c6bce932657ebf93adedf31881bfaf88c15"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c5bad6d78c5fe168e3bb894fc5cb70902c9b19b1"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="6ce96da28ea23c3d6804d13dc00d886cdb797134"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0870da2b5a71b6f48be8222e3f056498fea66e10"/>
   <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="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -127,16 +127,17 @@
 #endif
 @BINPATH@/components/accessibility.xpt
 #endif
 @BINPATH@/components/appshell.xpt
 @BINPATH@/components/appstartup.xpt
 @BINPATH@/components/autocomplete.xpt
 @BINPATH@/components/autoconfig.xpt
 @BINPATH@/components/browsercompsbase.xpt
+@BINPATH@/components/browser-element.xpt
 @BINPATH@/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chardet.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
--- a/browser/components/loop/LoopCalls.jsm
+++ b/browser/components/loop/LoopCalls.jsm
@@ -6,22 +6,27 @@
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["LoopCalls"];
 
+const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
+
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
                                   "resource:///modules/loop/MozLoopService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
                                   "resource:///modules/loop/MozLoopService.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
+                                  "resource:///modules/loop/LoopContacts.jsm");
+
  /**
  * Attempts to open a websocket.
  *
  * A new websocket interface is used each time. If an onStop callback
  * was received, calling asyncOpen() on the same interface will
  * trigger a "alreay open socket" exception even though the channel
  * is logically closed.
  */
@@ -264,17 +269,45 @@ let LoopCallsInternal = {
   /**
    * Starts a call, saves the call data, and opens a chat window.
    *
    * @param {Object} callData The data associated with the call including an id.
    *                          The data should include the type - "incoming" or
    *                          "outgoing".
    */
   _startCall: function(callData) {
-    this.conversationInProgress.id = MozLoopService.openChatWindow(callData);
+    const openChat = () => {
+      this.conversationInProgress.id = MozLoopService.openChatWindow(callData);
+    };
+
+    if (callData.type == "incoming" && ("callerId" in callData) &&
+        EMAIL_OR_PHONE_RE.test(callData.callerId)) {
+      LoopContacts.search({
+        q: callData.callerId,
+        field: callData.callerId.contains("@") ? "email" : "tel"
+      }, (err, contacts) => {
+        if (err) {
+          // Database error, helas!
+          openChat();
+          return;
+        }
+
+        for (let contact of contacts) {
+          if (contact.blocked) {
+            // Blocked! Send a busy signal back to the caller.
+            this._returnBusy(callData);
+            return;
+          }
+        }
+
+        openChat();
+      })
+    } else {
+      openChat();
+    }
   },
 
   /**
    * Starts a direct call to the contact addresses.
    *
    * @param {Object} contact The contact to call
    * @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
    * @return true if the call is opened, false if it is not opened (i.e. busy)
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -179,26 +179,20 @@ let LoopRoomsInternal = {
         if (orig) {
           checkForParticipantsUpdate(orig, room);
         }
         // Remove the `currSize` for posterity.
         if ("currSize" in room) {
           delete room.currSize;
         }
         this.rooms.set(room.roomToken, room);
-        // When a version is specified, all the data is already provided by this
-        // request.
-        if (version) {
-          eventEmitter.emit("update", room);
-          eventEmitter.emit("update" + ":" + room.roomToken, room);
-        } else {
-          // Next, request the detailed information for each room. If the request
-          // fails the room data will not be added to the map.
-          yield LoopRooms.promise("get", room.roomToken);
-        }
+
+        let eventName = orig ? "update" : "add";
+        eventEmitter.emit(eventName, room);
+        eventEmitter.emit(eventName + ":" + room.roomToken, room);
       }
 
       // If there's no rooms in the list, remove the guest created room flag, so that
       // we don't keep registering for guest when we don't need to.
       if (this.sessionType == LOOP_SESSION_TYPE.GUEST && !this.rooms.size) {
         this.setGuestCreatedRoom(false);
       }
 
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -84,38 +84,53 @@ const cloneValueInto = function(value, t
     }
   }
 
   // Inspect for an error this way, because the Error object is special.
   if (value.constructor.name == "Error") {
     return cloneErrorObject(value, targetWindow);
   }
 
-  return Cu.cloneInto(value, targetWindow);
+  let clone;
+  try {
+    clone = Cu.cloneInto(value, targetWindow);
+  } catch (ex) {
+    MozLoopService.log.debug("Failed to clone value:", value);
+    throw ex;
+  }
+
+  return clone;
 };
 
 /**
  * Inject any API containing _only_ function properties into the given window.
  *
  * @param {Object}       api          Object containing functions that need to
  *                                    be exposed to content
  * @param {nsIDOMWindow} targetWindow The content window to attach the API
  */
 const injectObjectAPI = function(api, targetWindow) {
   let injectedAPI = {};
   // Wrap all the methods in `api` to help results passed to callbacks get
   // through the priv => unpriv barrier with `Cu.cloneInto()`.
   Object.keys(api).forEach(func => {
     injectedAPI[func] = function(...params) {
       let lastParam = params.pop();
+      let callbackIsFunction = (typeof lastParam == "function");
 
       // If the last parameter is a function, assume its a callback
       // and wrap it differently.
-      if (lastParam && typeof lastParam === "function") {
+      if (callbackIsFunction) {
         api[func](...params, function(...results) {
+          // When the function was garbage collected due to async events, like
+          // closing a window, we want to circumvent a JS error.
+          if (callbackIsFunction && typeof lastParam != "function") {
+            MozLoopService.log.debug(func + ": callback function was lost.");
+            return;
+          }
           lastParam(...[cloneValueInto(r, targetWindow) for (r of results)]);
         });
       } else {
         try {
           return cloneValueInto(api[func](...params, lastParam), targetWindow);
         } catch (ex) {
           return cloneValueInto(ex, targetWindow);
         }
@@ -226,17 +241,17 @@ function injectLoopAPI(targetWindow) {
      *
      * @param {String} conversationWindowId
      * @returns {Object} The window data or null if error.
      */
     getConversationWindowData: {
       enumerable: true,
       writable: true,
       value: function(conversationWindowId) {
-        return Cu.cloneInto(MozLoopService.getConversationWindowData(conversationWindowId),
+        return cloneValueInto(MozLoopService.getConversationWindowData(conversationWindowId),
           targetWindow);
       }
     },
 
     /**
      * Returns the contacts API.
      *
      * @returns {Object} The contacts API object
@@ -490,19 +505,26 @@ function injectLoopAPI(targetWindow) {
      *                            transmitted with the request.
      * @param {Function} callback Called when the request completes.
      */
     hawkRequest: {
       enumerable: true,
       writable: true,
       value: function(sessionType, path, method, payloadObj, callback) {
         // XXX Should really return a DOM promise here.
+        let callbackIsFunction = (typeof callback == "function");
         MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
           callback(null, response.body);
         }, hawkError => {
+          // When the function was garbage collected due to async events, like
+          // closing a window, we want to circumvent a JS error.
+          if (callbackIsFunction && typeof callback != "function") {
+            MozLoopService.log.debug("hawkRequest: callback function was lost.");
+            return;
+          }
           // The hawkError.error property, while usually a string representing
           // an HTTP response status message, may also incorrectly be a native
           // error object that will cause the cloning function to fail.
           callback(Cu.cloneInto({
             error: (hawkError.error && typeof hawkError.error == "string")
                    ? hawkError.error : "Unexpected exception",
             message: hawkError.message,
             code: hawkError.code,
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -13,18 +13,16 @@ const INVALID_AUTH_TOKEN = 110;
 const LOOP_SESSION_TYPE = {
   GUEST: 1,
   FXA: 2,
 };
 
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "loop.debug.loglevel";
 
-const EMAIL_OR_PHONE_RE = /^(:?\S+@\S+|\+\d+)$/;
-
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
--- a/browser/components/loop/content/shared/css/contacts.css
+++ b/browser/components/loop/content/shared/css/contacts.css
@@ -127,17 +127,17 @@
   top: 35%;
   width: 14px;
   height: 14px;
   border-radius: 50%;
   background-image: url("../img/icons-16x16.svg#google");
   background-position: center;
   background-size: 16px 16px;
   background-repeat: no-repeat;
-  background-color: fff;
+  background-color: #fff;
 }
 
 .contact > .details > .email {
   color: #999;
   font-size: 11px;
   line-height: 16px;
 }
 
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -42,16 +42,17 @@ support-files =
 [browser_inspector_highlighter-hover_02.js]
 [browser_inspector_highlighter-hover_03.js]
 [browser_inspector_highlighter-iframes.js]
 [browser_inspector_highlighter-options.js]
 [browser_inspector_highlighter-rect_01.js]
 [browser_inspector_highlighter-rect_02.js]
 [browser_inspector_highlighter-selector_01.js]
 [browser_inspector_highlighter-selector_02.js]
+[browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
 [browser_inspector_initialization.js]
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu.js]
 [browser_inspector_navigation.js]
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-04.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-04.js
@@ -5,16 +5,17 @@
 "use strict";
 
 // Check that various highlighter elements exist.
 
 const TEST_URL = "data:text/html;charset=utf-8,<div>test</div>";
 
 // IDs of all highlighter elements that we expect to find in the canvasFrame.
 const ELEMENTS = ["box-model-root",
+                  "box-model-elements",
                   "box-model-margin",
                   "box-model-border",
                   "box-model-padding",
                   "box-model-content",
                   "box-model-guide-top",
                   "box-model-guide-right",
                   "box-model-guide-bottom",
                   "box-model-guide-left",
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_01.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-csstransform_01.js
@@ -28,17 +28,17 @@ add_task(function*() {
   yield highlighter.finalize();
 
   gBrowser.removeCurrentTab();
 });
 
 function* isHiddenByDefault(highlighterFront, inspector) {
   info("Checking that the highlighter is hidden by default");
 
-  let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
+  let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
   ok(hidden, "The highlighter is hidden by default");
 }
 
 function* has2PolygonsAnd4Lines(highlighterFront, inspector) {
   info("Checking that the highlighter is made up of 4 lines and 2 polygons");
 
   let value = yield getAttribute("css-transform-untransformed", "class", highlighterFront);
   is(value, "css-transform-untransformed", "The untransformed polygon exists");
@@ -53,43 +53,43 @@ function* has2PolygonsAnd4Lines(highligh
 }
 
 function* isNotShownForUntransformed(highlighterFront, inspector) {
   info("Asking to show the highlighter on the untransformed test node");
 
   let node = yield getNodeFront("#untransformed", inspector);
   yield highlighterFront.show(node);
 
-  let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
+  let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
   ok(hidden, "The highlighter is still hidden");
 }
 
 function* isNotShownForInline(highlighterFront, inspector) {
   info("Asking to show the highlighter on the inline test node");
 
   let node = yield getNodeFront("#inline", inspector);
   yield highlighterFront.show(node);
 
-  let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
+  let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
   ok(hidden, "The highlighter is still hidden");
 }
 
 function* isVisibleWhenShown(highlighterFront, inspector) {
   info("Asking to show the highlighter on the test node");
 
   let node = yield getNodeFront("#transformed", inspector);
   yield highlighterFront.show(node);
 
-  let hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
+  let hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
   ok(!hidden, "The highlighter is visible");
 
   info("Hiding the highlighter");
   yield highlighterFront.hide();
 
-  hidden = yield getAttribute("css-transform-root", "hidden", highlighterFront);
+  hidden = yield getAttribute("css-transform-elements", "hidden", highlighterFront);
   ok(hidden, "The highlighter is hidden");
 }
 
 function* linesLinkThePolygons(highlighterFront, inspector) {
   info("Showing the highlighter on the transformed node");
 
   let node = yield getNodeFront("#transformed", inspector);
   yield highlighterFront.show(node);
--- a/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-options.js
@@ -19,17 +19,17 @@ const TEST_URL = "data:text/html;charset
 const TEST_DATA = [
   {
     desc: "Guides and infobar should be shown by default",
     options: {},
     checkHighlighter: function*(toolbox) {
       let hidden = yield getAttribute("box-model-nodeinfobar-container", "hidden", toolbox);
       ok(!hidden, "Node infobar is visible");
 
-      hidden = yield getAttribute("box-model-root", "hidden", toolbox);
+      hidden = yield getAttribute("box-model-elements", "hidden", toolbox);
       ok(!hidden, "SVG container is visible");
 
       for (let side of ["top", "right", "bottom", "left"]) {
         hidden = yield getAttribute("box-model-guide-" + side, "hidden", toolbox);
         ok(!hidden, side + " guide is visible");
       }
     }
   },
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_highlighter-zoom.js
@@ -0,0 +1,74 @@
+/* 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";
+
+// Test that the highlighter stays correctly positioned and has the right aspect
+// ratio even when the page is zoomed in or out.
+
+const TEST_URL = "data:text/html;charset=utf-8,<div>zoom me</div>";
+
+// TEST_LEVELS entries should contain the following properties:
+// - level: the zoom level to test
+// - expected: the style attribute value to check for on the root highlighter
+//   element.
+const TEST_LEVELS = [{
+  level: 2,
+  expected: "position:absolute;transform-origin:top left;transform:scale(0.5);width:200%;height:200%;"
+}, {
+  level: 1,
+  expected: "position:absolute;width:100%;height:100%;"
+}, {
+  level: .5,
+  expected: "position:absolute;transform-origin:top left;transform:scale(2);width:50%;height:50%;"
+}];
+
+add_task(function*() {
+  let {inspector, toolbox} = yield openInspectorForURL(TEST_URL);
+
+  info("Highlighting the test node");
+
+  yield hoverElement("div", inspector);
+  let isVisible = yield isHighlighting(toolbox);
+  ok(isVisible, "The highlighter is visible");
+
+  for (let {level, expected} of TEST_LEVELS) {
+    info("Zoom to level " + level + " and check that the highlighter is correct");
+
+    yield zoomPageTo(level, getHighlighterActorID(toolbox));
+    isVisible = yield isHighlighting(toolbox);
+    ok(isVisible, "The highlighter is still visible at zoom level " + level);
+  
+    yield isNodeCorrectlyHighlighted(getNode("div"), toolbox);
+
+    info("Check that the highlighter root wrapper node was scaled down");
+
+    let style = yield getRootNodeStyle(toolbox);
+    is(style, expected, "The style attribute of the root element is correct");
+  }
+
+  gBrowser.removeCurrentTab();
+});
+
+function* hoverElement(selector, inspector) {
+  info("Hovering node " + selector + " in the markup view");
+  let container = yield getContainerForSelector(selector, inspector);
+  yield hoverContainer(container, inspector);
+}
+
+function* hoverContainer(container, inspector) {
+  let onHighlight = inspector.toolbox.once("node-highlight");
+  EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
+      inspector.markup.doc.defaultView);
+  yield onHighlight;
+}
+
+function* getRootNodeStyle(toolbox) {
+  let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
+    nodeID: "box-model-root",
+    name: "style",
+    actorID: getHighlighterActorID(toolbox)
+  });
+  return value;
+}
--- a/browser/devtools/inspector/test/doc_frame_script.js
+++ b/browser/devtools/inspector/test/doc_frame_script.js
@@ -127,16 +127,45 @@ addMessageListener("Test:ChangeHighlight
   h.once("updated", () => {
     sendAsyncMessage("Test:ChangeHighlightedNodeWaitForUpdate");
   });
 
   h.currentNode.setAttribute(name, value);
 });
 
 /**
+ * Change the zoom level of the page.
+ * Optionally subscribe to the box-model highlighter's update event and waiting
+ * for it to refresh before responding.
+ * @param {Object} msg The msg.data part expects the following properties
+ * - {Number} level The new zoom level
+ * - {String} actorID Optional. The highlighter actor ID
+ */
+addMessageListener("Test:ChangeZoomLevel", function(msg) {
+  let {level, actorID} = msg.data;
+  dumpn("Zooming page to " + level);
+
+  if (actorID) {
+    let {_highlighter: h} = getHighlighterActor(actorID);
+    h.once("updated", () => {
+      sendAsyncMessage("Test:ChangeZoomLevel");
+    });
+  }
+
+  let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIDocShell);
+  docShell.contentViewer.fullZoom = level;
+
+  if (!actorID) {
+    sendAsyncMessage("Test:ChangeZoomLevel");
+  }
+});
+
+/**
  * Get the element at the given x/y coordinates.
  * @param {Object} msg The msg.data part expects the following properties
  * - {Number} x
  * - {Number} y
  * @return {DOMNode} The CPOW of the element
  */
 addMessageListener("Test:ElementFromPoint", function(msg) {
   let {x, y} = msg.data;
--- a/browser/devtools/inspector/test/head.js
+++ b/browser/devtools/inspector/test/head.js
@@ -390,17 +390,17 @@ let isRegionHidden = Task.async(function
   return value !== null;
 });
 
 /**
  * Is the highlighter currently visible on the page?
  */
 let isHighlighting = Task.async(function*(toolbox) {
   let {data: value} = yield executeInContent("Test:GetHighlighterAttribute", {
-    nodeID: "box-model-root",
+    nodeID: "box-model-elements",
     name: "hidden",
     actorID: getHighlighterActorID(toolbox)
   });
   return value === null;
 });
 
 let getHighlitNode = Task.async(function*(toolbox) {
   let {visible, content} = yield getBoxModelStatus(toolbox);
@@ -551,16 +551,29 @@ let clickContainer = Task.async(function
   EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"},
     inspector.markup.doc.defaultView);
   EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mouseup"},
     inspector.markup.doc.defaultView);
   return updated;
 });
 
 /**
+ * Zoom the current page to a given level.
+ * @param {Number} level The new zoom level.
+ * @param {String} actorID Optional highlighter actor ID. If provided, the
+ * returned promise will only resolve when the highlighter has updated to the
+ * new zoom level.
+ * @return {Promise}
+ */
+let zoomPageTo = Task.async(function*(level, actorID) {
+  yield executeInContent("Test:ChangeZoomLevel",
+                         {level, actorID});
+});
+
+/**
  * Simulate the mouse leaving the markup-view area
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise when done
  */
 function mouseLeaveMarkupView(inspector) {
   info("Leaving the markup-view area");
   let def = promise.defer();
 
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -86,16 +86,18 @@ browser.jar:
     content/browser/devtools/profiler.js                               (profiler/profiler.js)
     content/browser/devtools/ui-recordings.js                          (profiler/ui-recordings.js)
     content/browser/devtools/ui-profile.js                             (profiler/ui-profile.js)
 #ifdef MOZ_DEVTOOLS_PERFTOOLS
     content/browser/devtools/performance.xul                           (performance/performance.xul)
     content/browser/devtools/performance/controller.js                 (performance/controller.js)
     content/browser/devtools/performance/views/main.js                 (performance/views/main.js)
     content/browser/devtools/performance/views/overview.js             (performance/views/overview.js)
+    content/browser/devtools/performance/views/details.js              (performance/views/details.js)
+    content/browser/devtools/performance/views/call-tree.js            (performance/views/call-tree.js)
 #endif
     content/browser/devtools/responsivedesign/resize-commands.js       (responsivedesign/resize-commands.js)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
     content/browser/devtools/commandline/commands-index.js             (commandline/commands-index.js)
     content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
--- a/browser/devtools/performance/controller.js
+++ b/browser/devtools/performance/controller.js
@@ -17,59 +17,64 @@ devtools.lazyRequireGetter(this, "EventE
 devtools.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
 devtools.lazyRequireGetter(this, "FramerateFront",
   "devtools/server/actors/framerate", true);
 devtools.lazyRequireGetter(this, "L10N",
   "devtools/profiler/global", true);
 devtools.lazyImporter(this, "LineGraphWidget",
   "resource:///modules/devtools/Graphs.jsm");
+devtools.lazyRequireGetter(this, "CallView",
+  "devtools/profiler/tree-view", true);
+devtools.lazyRequireGetter(this, "ThreadNode",
+  "devtools/profiler/tree-model", true);
 
 // Events emitted by the `PerformanceController`
 const EVENTS = {
   // When a recording is started or stopped via the controller
   RECORDING_STARTED: "Performance:RecordingStarted",
   RECORDING_STOPPED: "Performance:RecordingStopped",
   // When the PerformanceActor front emits `framerate` data
   TIMELINE_DATA: "Performance:TimelineData",
 
   // Emitted by the PerformanceView on record button click
   UI_START_RECORDING: "Performance:UI:StartRecording",
   UI_STOP_RECORDING: "Performance:UI:StopRecording",
 
   // Emitted by the OverviewView when more data has been rendered
-  OVERVIEW_RENDERED: "Performance:UI:OverviewRendered"
+  OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
+
+  // Emitted by the CallTreeView when a call tree has been rendered
+  CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered"
 };
 
 /**
  * The current target and the profiler connection, set by this tool's host.
  */
 let gToolbox, gTarget, gFront;
 
 /**
  * Initializes the profiler controller and views.
  */
 let startupPerformance = Task.async(function*() {
   yield promise.all([
     PrefObserver.register(),
     PerformanceController.initialize(),
-    PerformanceView.initialize(),
-    OverviewView.initialize()
+    PerformanceView.initialize()
   ]);
 });
 
 /**
  * Destroys the profiler controller and views.
  */
 let shutdownPerformance = Task.async(function*() {
   yield promise.all([
     PrefObserver.unregister(),
     PerformanceController.destroy(),
-    PerformanceView.destroy(),
-    OverviewView.destroy()
+    PerformanceView.destroy()
   ]);
 });
 
 /**
  * Observes pref changes on the devtools.profiler branch and triggers the
  * required frontend modifications.
  */
 let PrefObserver = {
@@ -142,16 +147,17 @@ let PerformanceController = {
  * Convenient way of emitting events from the controller.
  */
 EventEmitter.decorate(PerformanceController);
 
 /**
  * Shortcuts for accessing various profiler preferences.
  */
 const Prefs = new ViewHelpers.Prefs("devtools.profiler", {
+  showPlatformData: ["Bool", "ui.show-platform-data"]
 });
 
 /**
  * DOM query helpers.
  */
 function $(selector, target = document) {
   return target.querySelector(selector);
 }
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -12,16 +12,18 @@
   %profilerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script src="chrome://browser/content/devtools/theme-switching.js"/>
   <script type="application/javascript" src="performance/controller.js"/>
   <script type="application/javascript" src="performance/views/main.js"/>
   <script type="application/javascript" src="performance/views/overview.js"/>
+  <script type="application/javascript" src="performance/views/details.js"/>
+  <script type="application/javascript" src="performance/views/call-tree.js"/>
 
   <vbox class="theme-body" flex="1">
     <toolbar id="performance-toolbar" class="devtools-toolbar">
       <hbox id="performance-toolbar-controls-recordings" class="devtools-toolbarbutton-group">
         <toolbarbutton id="record-button"
                        class="devtools-toolbarbutton"
                        tooltiptext="&profilerUI.recordButton.tooltip;"/>
         <toolbarbutton id="clear-button"
@@ -40,11 +42,40 @@
          class="devtools-responsive-container"
          flex="1">
       <vbox id="time-framerate" flex="1"/>
     </box>
     <splitter class="devtools-horizontal-splitter" />
     <box id="details-pane"
          class="devtools-responsive-container"
          flex="1">
+      <vbox class="call-tree" flex="1">
+        <hbox class="call-tree-headers-container">
+          <label class="plain call-tree-header"
+                 type="duration"
+                 crop="end"
+                 value="&profilerUI.table.totalDuration;"/>
+          <label class="plain call-tree-header"
+                 type="percentage"
+                 crop="end"
+                 value="&profilerUI.table.totalPercentage;"/>
+          <label class="plain call-tree-header"
+                 type="self-duration"
+                 crop="end"
+                 value="&profilerUI.table.selfDuration;"/>
+          <label class="plain call-tree-header"
+                 type="self-percentage"
+                 crop="end"
+                 value="&profilerUI.table.selfPercentage;"/>
+          <label class="plain call-tree-header"
+                 type="samples"
+                 crop="end"
+                 value="&profilerUI.table.samples;"/>
+          <label class="plain call-tree-header"
+                 type="function"
+                 crop="end"
+                 value="&profilerUI.table.function;"/>
+        </hbox>
+        <vbox class="call-tree-cells-container" flex="1"/>
+      </vbox>
     </box>
   </vbox>
 </window>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -26,8 +26,9 @@ support-files =
 [browser_perf-shared-connection-03.js]
 # bug 1077464
 #[browser_perf-shared-connection-04.js]
 [browser_perf-data-samples.js]
 [browser_perf-data-massaging-01.js]
 [browser_perf-ui-recording.js]
 [browser_perf-overview-render-01.js]
 [browser_perf-overview-render-02.js]
+[browser_perf-details-calltree-render-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-details-calltree-render-01.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the call tree view renders after recording.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, CallTreeView } = panel.panelWin;
+
+  let updated = 0;
+  CallTreeView.on(EVENTS.CALL_TREE_RENDERED, () => updated++);
+
+  yield startRecording(panel);
+  yield busyWait(100);
+
+  let rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
+  yield stopRecording(panel);
+  yield rendered;
+
+  ok(true, "CallTreeView rendered on recording completed.");
+
+  yield startRecording(panel);
+  yield busyWait(100);
+
+  rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
+  yield stopRecording(panel);
+  yield rendered;
+
+  ok(true, "CallTreeView rendered again after recording completed a second time.");
+
+  yield teardown(panel);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/views/call-tree.js
@@ -0,0 +1,73 @@
+/* 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";
+
+/**
+ * CallTree view containing profiler call tree, controlled by DetailsView.
+ */
+let CallTreeView = {
+  /**
+   * Sets up the view with event binding.
+   */
+  initialize: function () {
+    this.el = $(".call-tree");
+    this._graphEl = $(".call-tree-cells-container");
+    this._stop = this._stop.bind(this);
+
+    PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
+  },
+
+  /**
+   * Unbinds events.
+   */
+  destroy: function () {
+    PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
+  },
+
+  _stop: function (_, { profilerData }) {
+    this._prepareCallTree(profilerData);
+  },
+
+  /**
+   * Called when the recording is stopped and prepares data to
+   * populate the call tree.
+   */
+  _prepareCallTree: function (profilerData, beginAt, endAt, options={}) {
+    let threadSamples = profilerData.profile.threads[0].samples;
+    let contentOnly = !Prefs.showPlatformData;
+    // TODO handle inverted tree bug 1102347
+    let invertTree = false;
+
+    let threadNode = new ThreadNode(threadSamples, contentOnly, beginAt, endAt, invertTree);
+    options.inverted = invertTree && threadNode.samples > 0;
+
+    this._populateCallTree(threadNode, options);
+  },
+
+  /**
+   * Renders the call tree.
+   */
+  _populateCallTree: function (frameNode, options={}) {
+    let root = new CallView({
+      autoExpandDepth: options.inverted ? 0 : undefined,
+      frame: frameNode,
+      hidden: options.inverted,
+      inverted: options.inverted
+    });
+
+    // Clear out other graphs
+    this._graphEl.innerHTML = "";
+    root.attachTo(this._graphEl);
+
+    let contentOnly = !Prefs.showPlatformData;
+    root.toggleCategories(!contentOnly);
+
+    this.emit(EVENTS.CALL_TREE_RENDERED);
+  }
+};
+
+/**
+ * Convenient way of emitting events from the view.
+ */
+EventEmitter.decorate(CallTreeView);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/views/details.js
@@ -0,0 +1,39 @@
+/* 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";
+
+/**
+ * Details view containing profiler call tree. Manages
+ * subviews and toggles visibility between them.
+ */
+let DetailsView = {
+  /**
+   * Sets up the view with event binding, initializes
+   * subviews.
+   */
+  initialize: function () {
+    this.views = {
+      callTree: CallTreeView
+    };
+
+    // Initialize subviews
+    return promise.all([
+      CallTreeView.initialize()
+    ]);
+  },
+
+  /**
+   * Unbinds events, destroys subviews.
+   */
+  destroy: function () {
+    return promise.all([
+      CallTreeView.destroy()
+    ]);
+  }
+};
+
+/**
+ * Convenient way of emitting events from the view.
+ */
+EventEmitter.decorate(DetailsView);
--- a/browser/devtools/performance/views/main.js
+++ b/browser/devtools/performance/views/main.js
@@ -3,38 +3,48 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /**
  * Master view handler for the performance tool.
  */
 let PerformanceView = {
   /**
-   * Sets up the view with event binding.
+   * Sets up the view with event binding and main subviews.
    */
   initialize: function () {
     this._recordButton = $("#record-button");
 
     this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
     this._unlockRecordButton = this._unlockRecordButton.bind(this);
 
     this._recordButton.addEventListener("click", this._onRecordButtonClick);
 
     // Bind to controller events to unlock the record button
     PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
+
+    return promise.all([
+      OverviewView.initialize(),
+      DetailsView.initialize()
+    ]);
   },
 
   /**
-   * Unbinds events.
+   * Unbinds events and destroys subviews.
    */
   destroy: function () {
     this._recordButton.removeEventListener("click", this._onRecordButtonClick);
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
+
+    return promise.all([
+      OverviewView.destroy(),
+      DetailsView.destroy()
+    ]);
   },
 
   /**
    * Removes the `locked` attribute on the record button.
    */
   _unlockRecordButton: function () {
     this._recordButton.removeAttribute("locked");
   },
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -162,16 +162,17 @@
 @BINPATH@/AccessibleMarshal.dll
 #endif
 @RESPATH@/components/accessibility.xpt
 #endif
 @RESPATH@/components/appshell.xpt
 @RESPATH@/components/appstartup.xpt
 @RESPATH@/components/autocomplete.xpt
 @RESPATH@/components/autoconfig.xpt
+@RESPATH@/components/browser-element.xpt
 @RESPATH@/browser/components/browsercompsbase.xpt
 @RESPATH@/browser/components/browser-feeds.xpt
 @RESPATH@/components/caps.xpt
 @RESPATH@/components/chrome.xpt
 @RESPATH@/components/commandhandler.xpt
 @RESPATH@/components/commandlines.xpt
 @RESPATH@/components/composer.xpt
 @RESPATH@/components/content_events.xpt
--- a/browser/modules/WindowsJumpLists.jsm
+++ b/browser/modules/WindowsJumpLists.jsm
@@ -47,41 +47,47 @@ XPCOMUtils.defineLazyGetter(this, "_pref
   return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
 });
 
 XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
   return Services.strings
                  .createBundle("chrome://browser/locale/taskbar.properties");
 });
 
-XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
-  Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-  return PlacesUtils;
-});
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
-  Components.utils.import("resource://gre/modules/NetUtil.jsm");
-  return NetUtil;
-});
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "_idle",
                                    "@mozilla.org/widget/idleservice;1",
                                    "nsIIdleService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
                                    "@mozilla.org/windows-taskbar;1",
                                    "nsIWinTaskbar");
 
 XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
                                    "@mozilla.org/browser/shell-service;1",
                                    "nsIWindowsShellService");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "gHistoryObserver", function() {
+  return Object.freeze({
+    onClearHistory() {
+      WinTaskbarJumpList.update();
+    },
+    QueryInterface: XPCOMUtils.generateQI(Ci.nsINavHistoryObserver),
+    __noSuchMethod__: () => {}, // Catch all of the other notifications.
+  });
+});
+
 /**
  * Global functions
  */
 
 function _getString(name) {
   return _stringBundle.GetStringFromName(name);
 }
 
@@ -141,26 +147,26 @@ var tasksCfg = [
 this.WinTaskbarJumpList =
 {
   _builder: null,
   _tasks: null,
   _shuttingDown: false,
 
   /**
    * Startup, shutdown, and update
-   */ 
+   */
 
   startup: function WTBJL_startup() {
     // exit if this isn't win7 or higher.
     if (!this._initTaskbar())
       return;
 
     // Win shell shortcut maintenance. If we've gone through an update,
     // this will update any pinned taskbar shortcuts. Not specific to
-    // jump lists, but this was a convienent place to call it. 
+    // jump lists, but this was a convienent place to call it.
     try {
       // dev builds may not have helper.exe, ignore failures.
       this._shortcutMaintenance();
     } catch (ex) {
     }
 
     // Store our task list config data
     this._tasks = tasksCfg;
@@ -181,24 +187,16 @@ this.WinTaskbarJumpList =
       return;
 
     // do what we came here to do, update the taskbar jumplist
     this._buildList();
   },
 
   _shutdown: function WTBJL__shutdown() {
     this._shuttingDown = true;
-
-    // Correctly handle a clear history on shutdown.  If there are no
-    // entries be sure to empty all history lists.  Luckily Places caches
-    // this value, so it's a pretty fast call.
-    if (!PlacesUtils.history.hasHistoryEntries) {
-      this.update();
-    }
-
     this._free();
   },
 
   _shortcutMaintenance: function WTBJL__maintenace() {
     _winShellService.shortcutMaintenance();
   },
 
   /**
@@ -248,23 +246,23 @@ this.WinTaskbarJumpList =
     if (this._showRecent)
       this._buildRecent();
 
     this._commitBuild();
   },
 
   /**
    * Taskbar api wrappers
-   */ 
+   */
 
   _startBuild: function WTBJL__startBuild() {
     var removedItems = Cc["@mozilla.org/array;1"].
                        createInstance(Ci.nsIMutableArray);
     this._builder.abortListBuild();
-    if (this._builder.initListBuild(removedItems)) { 
+    if (this._builder.initListBuild(removedItems)) {
       // Prior to building, delete removed items from history.
       this._clearHistory(removedItems);
       return true;
     }
     return false;
   },
 
   _commitBuild: function WTBJL__commitBuild() {
@@ -278,32 +276,27 @@ this.WinTaskbarJumpList =
                 createInstance(Ci.nsIMutableArray);
     this._tasks.forEach(function (task) {
       if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
         return;
       var item = this._getHandlerAppItem(task.title, task.description,
                                          task.args, task.iconIndex, null);
       items.appendElement(item, false);
     }, this);
-    
+
     if (items.length > 0)
       this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
   },
 
   _buildCustom: function WTBJL__buildCustom(title, items) {
     if (items.length > 0)
       this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
   },
 
   _buildFrequent: function WTBJL__buildFrequent() {
-    // If history is empty, just bail out.
-    if (!PlacesUtils.history.hasHistoryEntries) {
-      return;
-    }
-
     // Windows supports default frequent and recent lists,
     // but those depend on internal windows visit tracking
     // which we don't populate. So we build our own custom
     // frequent and recent lists using our nav history data.
 
     var items = Cc["@mozilla.org/array;1"].
                 createInstance(Ci.nsIMutableArray);
     // track frequent items so that we don't add them to
@@ -319,31 +312,26 @@ this.WinTaskbarJumpList =
           // The are no more results, build the list.
           this._buildCustom(_getString("taskbar.frequent.label"), items);
           this._commitBuild();
           return;
         }
 
         let title = aResult.title || aResult.uri;
         let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
-        let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1, 
+        let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
                                                faviconPageUri);
         items.appendElement(shortcut, false);
         this._frequentHashList.push(aResult.uri);
       },
       this
     );
   },
 
   _buildRecent: function WTBJL__buildRecent() {
-    // If history is empty, just bail out.
-    if (!PlacesUtils.history.hasHistoryEntries) {
-      return;
-    }
-
     var items = Cc["@mozilla.org/array;1"].
                 createInstance(Ci.nsIMutableArray);
     // Frequent items will be skipped, so we select a double amount of
     // entries and stop fetching results at _maxItemCount.
     var count = 0;
 
     this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
       Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
@@ -381,18 +369,18 @@ this.WinTaskbarJumpList =
   _deleteActiveJumpList: function WTBJL__deleteAJL() {
     this._builder.deleteActiveList();
   },
 
   /**
    * Jump list item creation helpers
    */
 
-  _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description, 
-                                                        args, iconIndex, 
+  _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
+                                                        args, iconIndex,
                                                         faviconPageUri) {
     var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
 
     var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
                      createInstance(Ci.nsILocalHandlerApp);
     handlerApp.executable = file;
     // handlers default to the leaf name if a name is not specified
     if (name && name.length != 0)
@@ -464,51 +452,53 @@ this.WinTaskbarJumpList =
     }
     if (URIsToRemove.length > 0) {
       PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
     }
   },
 
   /**
    * Prefs utilities
-   */ 
+   */
 
   _refreshPrefs: function WTBJL__refreshPrefs() {
     this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
     this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
     this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
     this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
     this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
   },
 
   /**
    * Init and shutdown utilities
-   */ 
+   */
 
   _initTaskbar: function WTBJL__initTaskbar() {
     this._builder = _taskbarService.createJumpListBuilder();
     if (!this._builder || !this._builder.available)
       return false;
 
     return true;
   },
 
   _initObs: function WTBJL__initObs() {
     // If the browser is closed while in private browsing mode, the "exit"
     // notification is fired on quit-application-granted.
     // History cleanup can happen at profile-change-teardown.
     Services.obs.addObserver(this, "profile-before-change", false);
     Services.obs.addObserver(this, "browser:purge-session-history", false);
     _prefs.addObserver("", this, false);
+    PlacesUtils.history.addObserver(gHistoryObserver, false);
   },
- 
+
   _freeObs: function WTBJL__freeObs() {
     Services.obs.removeObserver(this, "profile-before-change");
     Services.obs.removeObserver(this, "browser:purge-session-history");
     _prefs.removeObserver("", this);
+    PlacesUtils.history.removeObserver(gHistoryObserver);
   },
 
   _updateTimer: function WTBJL__updateTimer() {
     if (this._enabled && !this._shuttingDown && !this._timer) {
       this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       this._timer.initWithCallback(this,
                                    _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
                                    this._timer.TYPE_REPEATING_SLACK);
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -27,8 +27,231 @@
 
 #record-button[checked] {
   list-style-image: url(profiler-stopwatch-checked.svg);
 }
 
 #record-button[locked] {
   pointer-events: none;
 }
+
+/* Profile call tree */
+
+.theme-dark .call-tree-headers-container {
+  border-top: 1px solid #000;
+}
+
+.theme-light .call-tree-headers-container {
+  border-top: 1px solid #aaa;
+}
+
+.call-tree-cells-container {
+  /* Hack: force hardware acceleration */
+  transform: translateZ(1px);
+  overflow: auto;
+}
+
+.call-tree-cells-container[categories-hidden] .call-tree-category {
+  display: none;
+}
+
+.call-tree-header[type="duration"],
+.call-tree-cell[type="duration"],
+.call-tree-header[type="self-duration"],
+.call-tree-cell[type="self-duration"] {
+  width: 9em;
+}
+
+.call-tree-header[type="percentage"],
+.call-tree-cell[type="percentage"],
+.call-tree-header[type="self-percentage"],
+.call-tree-cell[type="self-percentage"] {
+  width: 6em;
+}
+
+.call-tree-header[type="samples"],
+.call-tree-cell[type="samples"] {
+  width: 5em;
+}
+
+.call-tree-header[type="function"],
+.call-tree-cell[type="function"] {
+  -moz-box-flex: 1;
+}
+
+.call-tree-header,
+.call-tree-cell {
+  -moz-box-align: center;
+  overflow: hidden;
+  padding: 1px 4px;
+}
+
+.call-tree-header:not(:last-child),
+.call-tree-cell:not(:last-child) {
+  -moz-border-end: 1px solid;
+}
+
+.theme-dark .call-tree-header,
+.theme-dark .call-tree-cell {
+  -moz-border-end-color: rgba(255,255,255,0.15);
+  color: #8fa1b2; /* Body Text */
+}
+
+.theme-light .call-tree-header,
+.theme-light .call-tree-cell {
+  -moz-border-end-color: rgba(0,0,0,0.15);
+  color: #18191a; /* Body Text */
+}
+
+.call-tree-header:not(:last-child) {
+  text-align: center;
+}
+
+.call-tree-cell:not(:last-child) {
+  text-align: end;
+}
+
+.theme-dark .call-tree-header {
+  background-color: #252c33; /* Tab Toolbar */
+}
+
+.theme-light .call-tree-header {
+  background-color: #ebeced; /* Tab Toolbar */
+}
+
+.theme-dark .call-tree-item:last-child:not(:focus) {
+  border-bottom: 1px solid rgba(255,255,255,0.15);
+}
+
+.theme-light .call-tree-item:last-child:not(:focus) {
+  border-bottom: 1px solid rgba(0,0,0,0.15);
+}
+
+.theme-dark .call-tree-item:nth-child(2n) {
+  background-color: rgba(29,79,115,0.15);
+}
+
+.theme-light .call-tree-item:nth-child(2n) {
+  background-color: rgba(76,158,217,0.1);
+}
+
+.theme-dark .call-tree-item:hover {
+  background-color: rgba(29,79,115,0.25);
+}
+
+.theme-light .call-tree-item:hover {
+  background-color: rgba(76,158,217,0.2);
+}
+
+.theme-dark .call-tree-item:focus {
+  background-color: #1d4f73; /* Select Highlight Blue */
+}
+
+.theme-light .call-tree-item:focus {
+  background-color: #4c9ed9; /* Select Highlight Blue */
+}
+
+.call-tree-item:focus label {
+  color: #f5f7fa !important; /* Light foreground text */
+}
+
+.theme-dark .call-tree-item:focus .call-tree-cell {
+  -moz-border-end-color: rgba(0,0,0,0.3);
+}
+
+.theme-light .call-tree-item:focus .call-tree-cell {
+  -moz-border-end-color: rgba(255,255,255,0.5);
+}
+
+.call-tree-item:not([origin="content"]) .call-tree-name,
+.call-tree-item:not([origin="content"]) .call-tree-url,
+.call-tree-item:not([origin="content"]) .call-tree-line {
+  /* Style chrome and non-JS nodes differently. */
+  opacity: 0.6;
+}
+
+.call-tree-url {
+  -moz-margin-start: 4px !important;
+  cursor: pointer;
+}
+
+.call-tree-url:hover {
+  text-decoration: underline;
+}
+
+.theme-dark .call-tree-url {
+  color: #46afe3;
+}
+
+.theme-light .call-tree-url {
+  color: #0088cc;
+}
+
+.theme-dark .call-tree-line {
+  color: #d96629;
+}
+
+.theme-light .call-tree-line {
+  color: #f13c00;
+}
+
+.call-tree-host {
+  -moz-margin-start: 8px !important;
+  font-size: 90%;
+}
+
+.theme-dark .call-tree-host {
+  color: #8fa1b2;
+}
+
+.theme-light .call-tree-host {
+  color: #8fa1b2;
+}
+
+.call-tree-url[value=""],
+.call-tree-line[value=""],
+.call-tree-host[value=""] {
+  display: none;
+}
+
+.call-tree-zoom {
+  -moz-appearance: none;
+  background-color: transparent;
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: 11px;
+  min-width: 11px;
+  -moz-margin-start: 8px !important;
+  cursor: zoom-in;
+  opacity: 0;
+}
+
+.theme-dark .call-tree-zoom {
+  background-image: url(magnifying-glass.png);
+}
+
+.theme-light .call-tree-zoom {
+  background-image: url(magnifying-glass-light.png);
+}
+
+@media (min-resolution: 2dppx) {
+  .theme-dark .call-tree-zoom {
+    background-image: url(magnifying-glass@2x.png);
+  }
+
+  .theme-light .call-tree-zoom {
+    background-image: url(magnifying-glass-light@2x.png);
+  }
+}
+
+.call-tree-item:hover .call-tree-zoom {
+  transition: opacity 0.3s ease-in;
+  opacity: 1;
+}
+
+.call-tree-item:hover .call-tree-zoom:hover {
+  opacity: 0;
+}
+
+.call-tree-category {
+  transform: scale(0.75);
+  transform-origin: center right;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -86,17 +86,17 @@
 #navigator-toolbox::after {
   content: "";
   display: -moz-box;
   -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
   height: 1px;
   background-color: ThreeDShadow;
 }
 
-#navigator-toolbox > toolbar:not(:-moz-lwtheme) {
+#navigator-toolbox > toolbar {
   -moz-appearance: none;
   border-style: none;
 }
 
 #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme) {
   background-color: -moz-Dialog;
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/file_test_widget.js
@@ -0,0 +1,231 @@
+var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
+var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
+var gApp;
+var gHasBrowserPermission;
+
+function onError() {
+  ok(false, "Error callback invoked");
+  finish();
+}
+
+function installApp(path) {
+  var request = navigator.mozApps.install(path);
+  request.onerror = onError;
+  request.onsuccess = function() {
+    gApp = request.result;
+
+    runTest();
+  }
+}
+
+function uninstallApp() {
+  // Uninstall the app.
+  var request = navigator.mozApps.mgmt.uninstall(gApp);
+  request.onerror = onError;
+  request.onsuccess = function() {
+    // All done.
+    info("All done");
+
+    runTest();
+  }
+}
+
+function testApp(isValidWidget) {
+  info("Test widget feature. IsValidWidget: " + isValidWidget);
+
+  var ifr = document.createElement('iframe');
+  ifr.setAttribute('mozbrowser', 'true');
+  ifr.setAttribute('mozwidget', gApp.manifestURL);
+  ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
+
+  var domParent = document.getElementById('container');
+  domParent.appendChild(ifr);
+
+  var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
+  mm.addMessageListener('OK', function(msg) {
+    ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
+  });
+  mm.addMessageListener('KO', function(msg) {
+    ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
+  });
+  mm.addMessageListener('DONE', function(msg) {
+    ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
+    domParent.removeChild(ifr);
+    runTest();
+  });
+
+  ifr.addEventListener('mozbrowserloadend', function() {
+    ok(true, "receive mozbrowserloadend");
+
+    // Test limited browser API feature only for valid widget case
+    if (isValidWidget) {
+      testLimitedBrowserAPI(ifr);
+    }
+    SimpleTest.executeSoon(()=>loadFrameScript(mm));
+  }, false);
+
+  // Test limited browser API feature only for valid widget case
+  if (!isValidWidget) {
+    return;
+  }
+
+  [
+    'mozbrowsertitlechange',
+    'mozbrowseropenwindow',
+    'mozbrowserscroll',
+    'mozbrowserasyncscroll'
+  ].forEach( function(topic) {
+    ifr.addEventListener(topic, function() {
+      ok(false, topic + " should be hidden");
+    }, false);
+  });
+}
+
+function testLimitedBrowserAPI(ifr) {
+  var securitySensitiveCalls = [
+    { api: 'sendMouseEvent'      , args: ['mousedown', 0, 0, 0, 0, 0] },
+    { api: 'sendTouchEvent'      , args: ['touchstart', [0], [0], [0], [1], [1], [0], [1], 1, 0] },
+    { api: 'goBack'              , args: [] },
+    { api: 'goForward'           , args: [] },
+    { api: 'reload'              , args: [] },
+    { api: 'stop'                , args: [] },
+    { api: 'download'            , args: ['http://example.org'] },
+    { api: 'purgeHistory'        , args: [] },
+    { api: 'getScreenshot'       , args: [0, 0] },
+    { api: 'zoom'                , args: [0.1] },
+    { api: 'getCanGoBack'        , args: [] },
+    { api: 'getCanGoForward'     , args: [] },
+    { api: 'getContentDimensions', args: [] }
+  ];
+  securitySensitiveCalls.forEach( function(call) {
+    if (gHasBrowserPermission) {
+      isnot(typeof ifr[call.api], "undefined", call.api + " should be defined");
+      var didThrow;
+      try {
+        ifr[call.api].apply(ifr, call.args);
+      } catch (e) {
+        ok(e instanceof DOMException, "throw right exception type");
+        didThrow = e.code;
+      }
+      is(didThrow, DOMException.INVALID_NODE_TYPE_ERR, "call " + call.api + " should throw exception");
+    } else {
+      is(typeof ifr[call.api], "undefined", call.api + " should be hidden for widget");
+    }
+  });
+}
+
+function loadFrameScript(mm) {
+  var script = 'data:,\
+  function ok(p, msg) { \
+  if (p) { \
+  sendAsyncMessage("OK", msg); \
+} else { \
+  sendAsyncMessage("KO", msg); \
+} \
+} \
+  \
+  function is(a, b, msg) { \
+  if (a == b) { \
+  sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
+} else { \
+  sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
+} \
+} \
+  \
+  function finish() { \
+  sendAsyncMessage("DONE",""); \
+} \
+  \
+  function onError() { \
+  ok(false, "Error callback invoked"); \
+  finish(); \
+} \
+  \
+  function checkWidget(widget) { \
+  /*For invalid widget case, ignore the following check*/\
+  if (widget) { \
+  var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
+  is(widget.origin, "http://test", "Widget origin should be correct"); \
+  is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
+} \
+  finish(); \
+} \
+  \
+  var request = content.window.navigator.mozApps.getSelf(); \
+  request.onsuccess = function() { \
+  var widget = request.result; \
+  ok(widget,"Should be a widget"); \
+  checkWidget(widget); \
+}; \
+  request.onerror = onError; \
+  content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
+  content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
+  ';
+  mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
+}
+
+var tests = [
+  // Permissions
+  function() {
+    SpecialPowers.pushPermissions(
+      [{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "context": document },
+       { "type": "embed-widgets", "allow": 1, "context": document },
+       { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
+  },
+
+  // Preferences
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
+                                       ["dom.enable_widgets", true],
+                                       ["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
+                                       ["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
+  },
+
+  function() {
+    if (SpecialPowers.isMainProcess()) {
+      SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+    }
+
+    SpecialPowers.setAllAppsLaunchable(true);
+    runTest();
+  },
+
+  // No confirmation needed when an app is installed
+  function() {
+    SpecialPowers.autoConfirmAppInstall(() => {
+      SpecialPowers.autoConfirmAppUninstall(runTest);
+    });
+  },
+
+  // Installing the app
+  ()=>installApp(gWidgetManifestURL),
+
+  // Run tests in app
+  ()=>testApp(true),
+
+  // Uninstall the app
+  uninstallApp,
+
+  // Installing the app for invalid widget case
+  ()=>installApp(gInvalidWidgetManifestURL),
+
+  // Run tests in app for invalid widget case
+  ()=>testApp(false),
+
+  // Uninstall the app
+  uninstallApp
+];
+
+function runTest() {
+  if (!tests.length) {
+    finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+function finish() {
+  SimpleTest.finish();
+}
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -12,16 +12,17 @@ support-files =
   file_manifest.json^headers^
   file_trusted_app.template.webapp
   file_invalidWidget_app.template.webapp
   file_packaged_app.sjs
   file_packaged_app.template.html
   file_packaged_app.template.webapp
   file_widget_app.template.webapp
   file_widget_app.template.html
+  file_test_widget.js
   signed_app.sjs
   signed_app_template.webapp
   signed/*
   test_packaged_app_common.js
   marketplace/*
   pkg_install_iframe.html
 
 [test_app_enabled.html]
@@ -39,8 +40,10 @@ skip-if = (toolkit == 'android' && proce
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_receipt_operations.html]
 [test_signed_pkg_install.html]
 [test_uninstall_errors.html]
 [test_theme_role.html]
 [test_web_app_install.html]
 [test_widget.html]
 skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
+[test_widget_browser.html]
+skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
--- a/dom/apps/tests/test_widget.html
+++ b/dom/apps/tests/test_widget.html
@@ -1,235 +1,18 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for DataStore - basic operation on a readonly db</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="file_test_widget.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
-
-  var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
-  var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
-  var gApp;
-
-  function onError() {
-    ok(false, "Error callback invoked");
-    finish();
-  }
-
-  function installApp(path) {
-    var request = navigator.mozApps.install(path);
-    request.onerror = onError;
-    request.onsuccess = function() {
-      gApp = request.result;
-
-      runTest();
-    }
-  }
-
-  function uninstallApp() {
-    // Uninstall the app.
-    var request = navigator.mozApps.mgmt.uninstall(gApp);
-    request.onerror = onError;
-    request.onsuccess = function() {
-      // All done.
-      info("All done");
-
-      runTest();
-    }
-  }
-
-  function testApp(isValidWidget) {
-    info("Test widget feature. IsValidWidget: " + isValidWidget);
-
-    var ifr = document.createElement('iframe');
-    ifr.setAttribute('mozbrowser', 'true');
-    ifr.setAttribute('mozwidget', gApp.manifestURL);
-    ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
-
-    var domParent = document.getElementById('container');
-    domParent.appendChild(ifr);
-
-    var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
-    mm.addMessageListener('OK', function(msg) {
-      ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
-    });
-    mm.addMessageListener('KO', function(msg) {
-      ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
-    });
-    mm.addMessageListener('DONE', function(msg) {
-      ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
-      domParent.removeChild(ifr);
-      runTest();
-    });
-
-    ifr.addEventListener('mozbrowserloadend', function() {
-      ok(true, "receive mozbrowserloadend");
-
-      // Test limited browser API feature only for valid widget case
-      if (isValidWidget) {
-        testLimitedBrowserAPI(ifr);
-      }
-      SimpleTest.executeSoon(()=>loadFrameScript(mm));
-    }, false);
-
-    // Test limited browser API feature only for valid widget case
-    if (!isValidWidget) {
-      return;
-    }
-
-    [
-      'mozbrowsertitlechange',
-      'mozbrowseropenwindow',
-      'mozbrowserscroll',
-      'mozbrowserasyncscroll'
-    ].forEach( function(topic) {
-      ifr.addEventListener(topic, function() {
-        ok(false, topic + " should be hidden");
-      }, false);
-    });
-  }
-
-  function testLimitedBrowserAPI(ifr) {
-    var securitySensitiveCalls = [
-      'sendMouseEvent',
-      'sendTouchEvent',
-      'goBack',
-      'goForward',
-      'reload',
-      'stop',
-      'download',
-      'purgeHistory',
-      'getScreenshot',
-      'zoom',
-      'getCanGoBack',
-      'getCanGoForward'
-    ];
-    securitySensitiveCalls.forEach( function(call) {
-      is(typeof ifr[call], "undefined", call + " should be hidden for widget");
-    });
-  }
-
-  function loadFrameScript(mm) {
-    var script = 'data:,\
-      function ok(p, msg) { \
-        if (p) { \
-          sendAsyncMessage("OK", msg); \
-        } else { \
-          sendAsyncMessage("KO", msg); \
-        } \
-      } \
-      \
-      function is(a, b, msg) { \
-        if (a == b) { \
-          sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
-        } else { \
-          sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
-        } \
-      } \
-      \
-      function finish() { \
-          sendAsyncMessage("DONE",""); \
-      } \
-      \
-      function onError() { \
-        ok(false, "Error callback invoked"); \
-        finish(); \
-      } \
-      \
-      function checkWidget(widget) { \
-        /*For invalid widget case, ignore the following check*/\
-        if (widget) { \
-          var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
-          is(widget.origin, "http://test", "Widget origin should be correct"); \
-          is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
-        } \
-        finish(); \
-      } \
-      \
-      var request = content.window.navigator.mozApps.getSelf(); \
-      request.onsuccess = function() { \
-        var widget = request.result; \
-        ok(widget,"Should be a widget"); \
-        checkWidget(widget); \
-      }; \
-      request.onerror = onError; \
-      content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
-      content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
-      ';
-    mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
-  }
-
-  var tests = [
-    // Permissions
-    function() {
-      SpecialPowers.pushPermissions(
-        [{ "type": "browser", "allow": 1, "context": document },
-         { "type": "embed-widgets", "allow": 1, "context": document },
-         { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
-    },
-
-    // Preferences
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
-                                         ["dom.enable_widgets", true],
-                                         ["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
-                                         ["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
-    },
-
-    function() {
-      if (SpecialPowers.isMainProcess()) {
-        SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
-      }
-
-      SpecialPowers.setAllAppsLaunchable(true);
-      runTest();
-    },
-
-    // No confirmation needed when an app is installed
-    function() {
-      SpecialPowers.autoConfirmAppInstall(() => {
-        SpecialPowers.autoConfirmAppUninstall(runTest);
-      });
-    },
-
-    // Installing the app
-    ()=>installApp(gWidgetManifestURL),
-
-    // Run tests in app
-    ()=>testApp(true),
-
-    // Uninstall the app
-    uninstallApp,
-
-    // Installing the app for invalid widget case
-    ()=>installApp(gInvalidWidgetManifestURL),
-
-    // Run tests in app for invalid widget case
-    ()=>testApp(false),
-
-    // Uninstall the app
-    uninstallApp
-  ];
-
-  function runTest() {
-    if (!tests.length) {
-      finish();
-      return;
-    }
-
-    var test = tests.shift();
-    test();
-  }
-
-  function finish() {
-    SimpleTest.finish();
-  }
-
   SimpleTest.waitForExplicitFinish();
+  gHasBrowserPermission = false;
   runTest();
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/test_widget_browser.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for DataStore - basic operation on a readonly db</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="file_test_widget.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+  SimpleTest.waitForExplicitFinish();
+  gHasBrowserPermission = true;
+  runTest();
+  </script>
+</body>
+</html>
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -1,129 +1,913 @@
 /* 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 {utils: Cu, interfaces: Ci} = Components;
+let Cu = Components.utils;
+let Ci = Components.interfaces;
+let Cc = Components.classes;
+let Cr = Components.results;
+
+/* BrowserElementParent injects script to listen for certain events in the
+ * child.  We then listen to messages from the child script and take
+ * appropriate action here in the parent.
+ */
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
-const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
+Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
-                                  "resource://gre/modules/BrowserElementParent.jsm",
-                                  "BrowserElementParentBuilder");
+XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
+  Cu.import("resource://gre/modules/Webapps.jsm");
+  return DOMApplicationRegistry;
+});
 
 function debug(msg) {
-  //dump("BrowserElementParent.js - " + msg + "\n");
+  //dump("BrowserElementParent - " + msg + "\n");
+}
+
+function getIntPref(prefName, def) {
+  try {
+    return Services.prefs.getIntPref(prefName);
+  }
+  catch(err) {
+    return def;
+  }
+}
+
+function visibilityChangeHandler(e) {
+  // The visibilitychange event's target is the document.
+  let win = e.target.defaultView;
+
+  if (!win._browserElementParents) {
+    return;
+  }
+
+  let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
+  if (beps.length == 0) {
+    win.removeEventListener('visibilitychange', visibilityChangeHandler);
+    return;
+  }
+
+  for (let i = 0; i < beps.length; i++) {
+    beps[i]._ownerVisibilityChange();
+  }
+}
+
+function defineNoReturnMethod(fn) {
+  return function method() {
+    if (!this._domRequestReady) {
+      // Remote browser haven't been created, we just queue the API call.
+      let args = Array.slice(arguments);
+      args.unshift(this);
+      this._pendingAPICalls.push(method.bind.apply(fn, args));
+      return;
+    }
+    if (this._isAlive()) {
+      fn.apply(this, arguments);
+    }
+  };
+}
+
+function defineDOMRequestMethod(msgName) {
+  return function() {
+    return this._sendDOMRequest(msgName);
+  };
+}
+
+function BrowserElementParent() {
+  debug("Creating new BrowserElementParent object");
+  this._domRequestCounter = 0;
+  this._domRequestReady = false;
+  this._pendingAPICalls = [];
+  this._pendingDOMRequests = {};
+  this._pendingSetInputMethodActive = [];
+  this._nextPaintListeners = [];
+
+  Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
+  Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
+  Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
 }
 
-/**
- * BrowserElementParent implements one half of <iframe mozbrowser>.  (The other
- * half is, unsurprisingly, BrowserElementChild.)
- *
- * BrowserElementParentFactory detects when we create a windows or docshell
- * contained inside a <iframe mozbrowser> and creates a BrowserElementParent
- * object for that window.
- *
- * It creates a BrowserElementParent that injects script to listen for
- * certain event.
- */
+BrowserElementParent.prototype = {
 
-function BrowserElementParentFactory() {
-  this._initialized = false;
-}
-
-BrowserElementParentFactory.prototype = {
-  classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+  classDescription: "BrowserElementAPI implementation",
+  classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
+  contractID: "@mozilla.org/dom/browser-element-api;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
+                                         Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
+  setFrameLoader: function(frameLoader) {
+    this._frameLoader = frameLoader;
+    this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+    if (!this._frameElement) {
+      debug("No frame element?");
+      return;
+    }
+    // Listen to visibilitychange on the iframe's owner window, and forward
+    // changes down to the child.  We want to do this while registering as few
+    // visibilitychange listeners on _window as possible, because such a listener
+    // may live longer than this BrowserElementParent object.
+    //
+    // To accomplish this, we register just one listener on the window, and have
+    // it reference a WeakMap whose keys are all the BrowserElementParent objects
+    // on the window.  Then when the listener fires, we iterate over the
+    // WeakMap's keys (which we can do, because we're chrome) to notify the
+    // BrowserElementParents.
+    if (!this._window._browserElementParents) {
+      this._window._browserElementParents = new WeakMap();
+      this._window.addEventListener('visibilitychange',
+                                    visibilityChangeHandler,
+                                    /* useCapture = */ false,
+                                    /* wantsUntrusted = */ false);
+    }
+
+    this._window._browserElementParents.set(this, null);
+
+    // Insert ourself into the prompt service.
+    BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
+    this._setupMessageListener();
+    this._registerAppManifest();
+  },
+
+  _runPendingAPICall: function() {
+    if (!this._pendingAPICalls) {
+      return;
+    }
+    for (let i = 0; i < this._pendingAPICalls.length; i++) {
+      try {
+        this._pendingAPICalls[i]();
+      } catch (e) {
+        // throw the expections from pending functions.
+        debug('Exception when running pending API call: ' +  e);
+      }
+    }
+    delete this._pendingAPICalls;
+  },
+
+  _registerAppManifest: function() {
+    // If this browser represents an app then let the Webapps module register for
+    // any messages that it needs.
+    let appManifestURL =
+          this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
+    if (appManifestURL) {
+      let inParent = Cc["@mozilla.org/xre/app-info;1"]
+                       .getService(Ci.nsIXULRuntime)
+                       .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+      if (inParent) {
+        DOMApplicationRegistry.registerBrowserElementParentForApp(
+          { manifestURL: appManifestURL }, this._mm);
+      } else {
+        this._mm.sendAsyncMessage("Webapps:RegisterBEP",
+                                  { manifestURL: appManifestURL });
+      }
+    }
+  },
+
+  _setupMessageListener: function() {
+    this._mm = this._frameLoader.messageManager;
+    let self = this;
+    let isWidget = this._frameLoader
+                       .QueryInterface(Ci.nsIFrameLoader)
+                       .ownerIsWidget;
+
+    // Messages we receive are handed to functions which take a (data) argument,
+    // where |data| is the message manager's data object.
+    // We use a single message and dispatch to various function based
+    // on data.msg_name
+    let mmCalls = {
+      "hello": this._recvHello,
+      "loadstart": this._fireProfiledEventFromMsg,
+      "loadend": this._fireProfiledEventFromMsg,
+      "close": this._fireEventFromMsg,
+      "error": this._fireEventFromMsg,
+      "firstpaint": this._fireProfiledEventFromMsg,
+      "documentfirstpaint": this._fireProfiledEventFromMsg,
+      "nextpaint": this._recvNextPaint,
+      "got-purge-history": this._gotDOMRequestResult,
+      "got-screenshot": this._gotDOMRequestResult,
+      "got-contentdimensions": this._gotDOMRequestResult,
+      "got-can-go-back": this._gotDOMRequestResult,
+      "got-can-go-forward": this._gotDOMRequestResult,
+      "fullscreen-origin-change": this._remoteFullscreenOriginChange,
+      "rollback-fullscreen": this._remoteFrameFullscreenReverted,
+      "exit-fullscreen": this._exitFullscreen,
+      "got-visible": this._gotDOMRequestResult,
+      "visibilitychange": this._childVisibilityChange,
+      "got-set-input-method-active": this._gotDOMRequestResult,
+      "selectionchange": this._handleSelectionChange,
+      "scrollviewchange": this._handleScrollViewChange,
+      "touchcarettap": this._handleTouchCaretTap
+    };
+
+    let mmSecuritySensitiveCalls = {
+      "showmodalprompt": this._handleShowModalPrompt,
+      "contextmenu": this._fireCtxMenuEvent,
+      "securitychange": this._fireEventFromMsg,
+      "locationchange": this._fireEventFromMsg,
+      "iconchange": this._fireEventFromMsg,
+      "scrollareachanged": this._fireEventFromMsg,
+      "titlechange": this._fireProfiledEventFromMsg,
+      "opensearch": this._fireEventFromMsg,
+      "manifestchange": this._fireEventFromMsg,
+      "metachange": this._fireEventFromMsg,
+      "resize": this._fireEventFromMsg,
+      "activitydone": this._fireEventFromMsg,
+      "scroll": this._fireEventFromMsg
+    };
+
+    this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
+      if (!self._isAlive()) {
+        return;
+      }
+
+      if (aMsg.data.msg_name in mmCalls) {
+        return mmCalls[aMsg.data.msg_name].apply(self, arguments);
+      } else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
+        return mmSecuritySensitiveCalls[aMsg.data.msg_name]
+                 .apply(self, arguments);
+      }
+    });
+  },
+
   /**
-   * Called on app startup, and also when the browser frames enabled pref is
-   * changed.
+   * You shouldn't touch this._frameElement or this._window if _isAlive is
+   * false.  (You'll likely get an exception if you do.)
    */
-  _init: function() {
-    if (this._initialized) {
+  _isAlive: function() {
+    return !Cu.isDeadWrapper(this._frameElement) &&
+           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
+           !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
+  },
+
+  get _window() {
+    return this._frameElement.ownerDocument.defaultView;
+  },
+
+  get _windowUtils() {
+    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDOMWindowUtils);
+  },
+
+  promptAuth: function(authDetail, callback) {
+    let evt;
+    let self = this;
+    let callbackCalled = false;
+    let cancelCallback = function() {
+      if (!callbackCalled) {
+        callbackCalled = true;
+        callback(false, null, null);
+      }
+    };
+
+    // 1. We don't handle password-only prompts.
+    // 2. We don't handle for widget case because of security concern.
+    if (authDetail.isOnlyPassword ||
+        this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
+      cancelCallback();
       return;
     }
 
-    // If the pref is disabled, do nothing except wait for the pref to change.
-    // (This is important for tests, if nothing else.)
-    if (!this._browserFramesPrefEnabled()) {
-      Services.prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
-      return;
+    /* username and password */
+    let detail = {
+      host:     authDetail.host,
+      realm:    authDetail.realm
+    };
+
+    evt = this._createEvent('usernameandpasswordrequired', detail,
+                            /* cancelable */ true);
+    Cu.exportFunction(function(username, password) {
+      if (callbackCalled)
+        return;
+      callbackCalled = true;
+      callback(true, username, password);
+    }, evt.detail, { defineAs: 'authenticate' });
+
+    Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
+
+    this._frameElement.dispatchEvent(evt);
+
+    if (!evt.defaultPrevented) {
+      cancelCallback();
+    }
+  },
+
+  _sendAsyncMsg: function(msg, data) {
+    try {
+      if (!data) {
+        data = { };
+      }
+
+      data.msg_name = msg;
+      this._mm.sendAsyncMessage('browser-element-api:call', data);
+    } catch (e) {
+      return false;
+    }
+    return true;
+  },
+
+  _recvHello: function() {
+    debug("recvHello");
+
+    // Inform our child if our owner element's document is invisible.  Note
+    // that we must do so here, rather than in the BrowserElementParent
+    // constructor, because the BrowserElementChild may not be initialized when
+    // we run our constructor.
+    if (this._window.document.hidden) {
+      this._ownerVisibilityChange();
+    }
+
+    if (!this._domRequestReady) {
+      // At least, one message listener such as for hello is registered.
+      // So we can use sendAsyncMessage now.
+      this._domRequestReady = true;
+      this._runPendingAPICall();
+    }
+
+    return {
+      name: this._frameElement.getAttribute('name'),
+      fullscreenAllowed:
+        this._frameElement.hasAttribute('allowfullscreen') ||
+        this._frameElement.hasAttribute('mozallowfullscreen'),
+      isPrivate: this._frameElement.hasAttribute('mozprivatebrowsing')
+    };
+  },
+
+  _fireCtxMenuEvent: function(data) {
+    let detail = data.json;
+    let evtName = detail.msg_name;
+
+    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
+    let evt = this._createEvent(evtName, detail, /* cancellable */ true);
+
+    if (detail.contextmenu) {
+      var self = this;
+      Cu.exportFunction(function(id) {
+        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
+      }, evt.detail, { defineAs: 'contextMenuItemSelected' });
+    }
+
+    // The embedder may have default actions on context menu events, so
+    // we fire a context menu event even if the child didn't define a
+    // custom context menu
+    return !this._frameElement.dispatchEvent(evt);
+  },
+
+  /**
+   * add profiler marker for each event fired.
+   */
+  _fireProfiledEventFromMsg: function(data) {
+    if (Services.profiler !== undefined) {
+      Services.profiler.AddMarker(data.json.msg_name);
+    }
+    this._fireEventFromMsg(data);
+  },
+
+  /**
+   * Fire either a vanilla or a custom event, depending on the contents of
+   * |data|.
+   */
+  _fireEventFromMsg: function(data) {
+    let detail = data.json;
+    let name = detail.msg_name;
+
+    // For events that send a "_payload_" property, we just want to transmit
+    // this in the event.
+    if ("_payload_" in detail) {
+      detail = detail._payload_;
     }
 
-    debug("_init");
-    this._initialized = true;
+    debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
+    let evt = this._createEvent(name, detail,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  _handleShowModalPrompt: function(data) {
+    // Fire a showmodalprmopt event on the iframe.  When this method is called,
+    // the child is spinning in a nested event loop waiting for an
+    // unblock-modal-prompt message.
+    //
+    // If the embedder calls preventDefault() on the showmodalprompt event,
+    // we'll block the child until event.detail.unblock() is called.
+    //
+    // Otherwise, if preventDefault() is not called, we'll send the
+    // unblock-modal-prompt message to the child as soon as the event is done
+    // dispatching.
+
+    let detail = data.json;
+    debug('handleShowPrompt ' + JSON.stringify(detail));
+
+    // Strip off the windowID property from the object we send along in the
+    // event.
+    let windowID = detail.windowID;
+    delete detail.windowID;
+    debug("Event will have detail: " + JSON.stringify(detail));
+    let evt = this._createEvent('showmodalprompt', detail,
+                                /* cancelable = */ true);
 
-    // Maps frame elements to BrowserElementParent objects.  We never look up
-    // anything in this map; the purpose is to keep the BrowserElementParent
-    // alive for as long as its frame element lives.
-    this._bepMap = new WeakMap();
+    let self = this;
+    let unblockMsgSent = false;
+    function sendUnblockMsg() {
+      if (unblockMsgSent) {
+        return;
+      }
+      unblockMsgSent = true;
+
+      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
+      // return value of confirm() to a boolean); Gecko does that for us.
+
+      let data = { windowID: windowID,
+                   returnValue: evt.detail.returnValue };
+      self._sendAsyncMsg('unblock-modal-prompt', data);
+    }
+
+    Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });
+
+    this._frameElement.dispatchEvent(evt);
 
-    Services.obs.addObserver(this, 'remote-browser-pending', /* ownsWeak = */ true);
-    Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
+    if (!evt.defaultPrevented) {
+      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
+      // evt.detail.unblock().
+      sendUnblockMsg();
+    }
+  },
+
+  _handleSelectionChange: function(data) {
+    let evt = this._createEvent('selectionchange', data.json,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  _handleScrollViewChange: function(data) {
+    let evt = this._createEvent("scrollviewchange", data.json,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
+  },
+
+  _handleTouchCaretTap: function(data) {
+    let evt = this._createEvent("touchcarettap", data.json,
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
   },
 
-  _browserFramesPrefEnabled: function() {
-    try {
-      return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
+  _createEvent: function(evtName, detail, cancelable) {
+    // This will have to change if we ever want to send a CustomEvent with null
+    // detail.  For now, it's OK.
+    if (detail !== undefined && detail !== null) {
+      detail = Cu.cloneInto(detail, this._window);
+      return new this._window.CustomEvent('mozbrowser' + evtName,
+                                          { bubbles: true,
+                                            cancelable: cancelable,
+                                            detail: detail });
     }
-    catch(e) {
-      return false;
+
+    return new this._window.Event('mozbrowser' + evtName,
+                                  { bubbles: true,
+                                    cancelable: cancelable });
+  },
+
+  /**
+   * Kick off a DOMRequest in the child process.
+   *
+   * We'll fire an event called |msgName| on the child process, passing along
+   * an object with two fields:
+   *
+   *  - id:  the ID of this request.
+   *  - arg: arguments to pass to the child along with this request.
+   *
+   * We expect the child to pass the ID back to us upon completion of the
+   * request.  See _gotDOMRequestResult.
+   */
+  _sendDOMRequest: function(msgName, args) {
+    let id = 'req_' + this._domRequestCounter++;
+    let req = Services.DOMRequest.createRequest(this._window);
+    let self = this;
+    let send = function() {
+      if (!self._isAlive()) {
+        return;
+      }
+      if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
+        self._pendingDOMRequests[id] = req;
+      } else {
+        Services.DOMRequest.fireErrorAsync(req, "fail");
+      }
+    };
+    if (this._domRequestReady) {
+      send();
+    } else {
+      // Child haven't been loaded.
+      this._pendingAPICalls.push(send);
+    }
+    return req;
+  },
+
+  /**
+   * Called when the child process finishes handling a DOMRequest.  data.json
+   * must have the fields [id, successRv], if the DOMRequest was successful, or
+   * [id, errorMsg], if the request was not successful.
+   *
+   * The fields have the following meanings:
+   *
+   *  - id:        the ID of the DOM request (see _sendDOMRequest)
+   *  - successRv: the request's return value, if the request succeeded
+   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
+   *               failed.
+   *
+   */
+  _gotDOMRequestResult: function(data) {
+    let req = this._pendingDOMRequests[data.json.id];
+    delete this._pendingDOMRequests[data.json.id];
+
+    if ('successRv' in data.json) {
+      debug("Successful gotDOMRequestResult.");
+      let clientObj = Cu.cloneInto(data.json.successRv, this._window);
+      Services.DOMRequest.fireSuccess(req, clientObj);
+    }
+    else {
+      debug("Got error in gotDOMRequestResult.");
+      Services.DOMRequest.fireErrorAsync(req,
+        Cu.cloneInto(data.json.errorMsg, this._window));
     }
   },
 
-  _observeInProcessBrowserFrameShown: function(frameLoader) {
-    // Ignore notifications that aren't from a BrowserOrApp
-    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
-      return;
+  setVisible: defineNoReturnMethod(function(visible) {
+    this._sendAsyncMsg('set-visible', {visible: visible});
+    this._frameLoader.visible = visible;
+  }),
+
+  getVisible: defineDOMRequestMethod('get-visible'),
+
+  setActive: defineNoReturnMethod(function(active) {
+    this._frameLoader.visible = active;
+  }),
+
+  getActive: function() {
+    if (!this._isAlive()) {
+      throw Components.Exception("Dead content process",
+                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
     }
-    debug("In-process browser frame shown " + frameLoader);
-    this._createBrowserElementParent(frameLoader,
-                                     /* hasRemoteFrame = */ false,
-                                     /* pending frame */ false);
+
+    return this._frameLoader.visible;
   },
 
-  _observeRemoteBrowserFramePending: function(frameLoader) {
-    // Ignore notifications that aren't from a BrowserOrApp
-    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
-      return;
+  sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
+    this._sendAsyncMsg("send-mouse-event", {
+      "type": type,
+      "x": x,
+      "y": y,
+      "button": button,
+      "clickCount": clickCount,
+      "modifiers": modifiers
+    });
+  }),
+
+  sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
+                                                radiisX, radiisY, rotationAngles, forces,
+                                                count, modifiers) {
+
+    let tabParent = this._frameLoader.tabParent;
+    if (tabParent && tabParent.useAsyncPanZoom) {
+      tabParent.injectTouchEvent(type,
+                                 identifiers,
+                                 touchesX,
+                                 touchesY,
+                                 radiisX,
+                                 radiisY,
+                                 rotationAngles,
+                                 forces,
+                                 count,
+                                 modifiers);
+    } else {
+      this._sendAsyncMsg("send-touch-event", {
+        "type": type,
+        "identifiers": identifiers,
+        "touchesX": touchesX,
+        "touchesY": touchesY,
+        "radiisX": radiisX,
+        "radiisY": radiisY,
+        "rotationAngles": rotationAngles,
+        "forces": forces,
+        "count": count,
+        "modifiers": modifiers
+      });
+    }
+  }),
+
+  getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
+  getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
+  getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
+
+  goBack: defineNoReturnMethod(function() {
+    this._sendAsyncMsg('go-back');
+  }),
+
+  goForward: defineNoReturnMethod(function() {
+    this._sendAsyncMsg('go-forward');
+  }),
+
+  reload: defineNoReturnMethod(function(hardReload) {
+    this._sendAsyncMsg('reload', {hardReload: hardReload});
+  }),
+
+  stop: defineNoReturnMethod(function() {
+    this._sendAsyncMsg('stop');
+  }),
+
+  /*
+   * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
+   */
+  zoom: defineNoReturnMethod(function(zoom) {
+    zoom *= 100;
+    zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
+    zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
+    this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
+  }),
+
+  purgeHistory: defineDOMRequestMethod('purge-history'),
+
+
+  download: function(_url, _options) {
+    if (!this._isAlive()) {
+      return null;
+    }
+    let ioService =
+      Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
+    let uri = ioService.newURI(_url, null, null);
+    let url = uri.QueryInterface(Ci.nsIURL);
+
+    // Ensure we have _options, we always use it to send the filename.
+    _options = _options || {};
+    if (!_options.filename) {
+      _options.filename = url.fileName;
+    }
+
+    debug('_options = ' + uneval(_options));
+
+    // Ensure we have a filename.
+    if (!_options.filename) {
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
     }
-    debug("Remote browser frame shown " + frameLoader);
-    this._createBrowserElementParent(frameLoader,
-                                     /* hasRemoteFrame = */ true,
-                                     /* pending frame */ true);
+
+    let interfaceRequestor =
+      this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
+    let req = Services.DOMRequest.createRequest(this._window);
+
+    function DownloadListener() {
+      debug('DownloadListener Constructor');
+    }
+    DownloadListener.prototype = {
+      extListener: null,
+      onStartRequest: function(aRequest, aContext) {
+        debug('DownloadListener - onStartRequest');
+        let extHelperAppSvc =
+          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
+          getService(Ci.nsIExternalHelperAppService);
+        let channel = aRequest.QueryInterface(Ci.nsIChannel);
+
+        // First, we'll ensure the filename doesn't have any leading
+        // periods. We have to do it here to avoid ending up with a filename
+        // that's only an extension with no extension (e.g. Sending in
+        // '.jpeg' without stripping the '.' would result in a filename of
+        // 'jpeg' where we want 'jpeg.jpeg'.
+        _options.filename = _options.filename.replace(/^\.+/, "");
+
+        let ext = null;
+        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
+        try {
+          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
+        } catch (e) { ext = null; }
+
+        // Check if we need to add an extension to the filename.
+        if (ext && !_options.filename.endsWith(ext)) {
+          _options.filename += ext;
+        }
+        // Set the filename to use when saving to disk.
+        channel.contentDispositionFilename = _options.filename;
+
+        this.extListener =
+          extHelperAppSvc.doContent(
+              channel.contentType,
+              aRequest,
+              interfaceRequestor,
+              true);
+        this.extListener.onStartRequest(aRequest, aContext);
+      },
+      onStopRequest: function(aRequest, aContext, aStatusCode) {
+        debug('DownloadListener - onStopRequest (aStatusCode = ' +
+               aStatusCode + ')');
+        if (aStatusCode == Cr.NS_OK) {
+          // Everything looks great.
+          debug('DownloadListener - Download Successful.');
+          Services.DOMRequest.fireSuccess(req, aStatusCode);
+        }
+        else {
+          // In case of failure, we'll simply return the failure status code.
+          debug('DownloadListener - Download Failed!');
+          Services.DOMRequest.fireError(req, aStatusCode);
+        }
+
+        if (this.extListener) {
+          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+        }
+      },
+      onDataAvailable: function(aRequest, aContext, aInputStream,
+                                aOffset, aCount) {
+        this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+                                         aOffset, aCount);
+      },
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
+                                             Ci.nsIRequestObserver])
+    };
+
+    let channel = ioService.newChannelFromURI(url);
+
+    // XXX We would set private browsing information prior to calling this.
+    channel.notificationCallbacks = interfaceRequestor;
+
+    // Since we're downloading our own local copy we'll want to bypass the
+    // cache and local cache if the channel let's us specify this.
+    let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
+                Ci.nsIChannel.LOAD_BYPASS_CACHE;
+    if (channel instanceof Ci.nsICachingChannel) {
+      debug('This is a caching channel. Forcing bypass.');
+      flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+    }
+
+    channel.loadFlags |= flags;
+
+    if (channel instanceof Ci.nsIHttpChannel) {
+      debug('Setting HTTP referrer = ' + this._window.document.documentURIObject);
+      channel.referrer = this._window.document.documentURIObject;
+      if (channel instanceof Ci.nsIHttpChannelInternal) {
+        channel.forceAllowThirdPartyCookie = true;
+      }
+    }
+
+    // Set-up complete, let's get things started.
+    channel.asyncOpen(new DownloadListener(), null);
+
+    return req;
   },
 
-  _createBrowserElementParent: function(frameLoader, hasRemoteFrame, isPendingFrame) {
-    let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-    this._bepMap.set(frameElement, BrowserElementParentBuilder.create(
-      frameLoader, hasRemoteFrame, isPendingFrame));
+  getScreenshot: function(_width, _height, _mimeType) {
+    if (!this._isAlive()) {
+      throw Components.Exception("Dead content process",
+                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+
+    let width = parseInt(_width);
+    let height = parseInt(_height);
+    let mimeType = (typeof _mimeType === 'string') ?
+      _mimeType.trim().toLowerCase() : 'image/jpeg';
+    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
+      throw Components.Exception("Invalid argument",
+                                 Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    return this._sendDOMRequest('get-screenshot',
+                                {width: width, height: height,
+                                 mimeType: mimeType});
+  },
+
+  _recvNextPaint: function(data) {
+    let listeners = this._nextPaintListeners;
+    this._nextPaintListeners = [];
+    for (let listener of listeners) {
+      try {
+        listener.recvNextPaint();
+      } catch (e) {
+        // If a listener throws we'll continue.
+      }
+    }
+  },
+
+  addNextPaintListener: function(listener) {
+    if (!this._isAlive()) {
+      throw Components.Exception("Dead content process",
+                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+
+    let self = this;
+    let run = function() {
+      if (self._nextPaintListeners.push(listener) == 1)
+        self._sendAsyncMsg('activate-next-paint-listener');
+    };
+    if (!this._domRequestReady) {
+      this._pendingAPICalls.push(run);
+    } else {
+      run();
+    }
+  },
+
+  removeNextPaintListener: function(listener) {
+    if (!this._isAlive()) {
+      throw Components.Exception("Dead content process",
+                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+
+    let self = this;
+    let run = function() {
+      for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
+        if (self._nextPaintListeners[i] == listener) {
+          self._nextPaintListeners.splice(i, 1);
+          break;
+        }
+      }
+
+      if (self._nextPaintListeners.length == 0)
+        self._sendAsyncMsg('deactivate-next-paint-listener');
+    };
+    if (!this._domRequestReady) {
+      this._pendingAPICalls.push(run);
+    } else {
+      run();
+    }
+  },
+
+  setInputMethodActive: function(isActive) {
+    if (!this._isAlive()) {
+      throw Components.Exception("Dead content process",
+                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+
+    if (typeof isActive !== 'boolean') {
+      throw Components.Exception("Invalid argument",
+                                 Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    return this._sendDOMRequest('set-input-method-active',
+                                {isActive: isActive});
+  },
+
+  /**
+   * Called when the visibility of the window which owns this iframe changes.
+   */
+  _ownerVisibilityChange: function() {
+    this._sendAsyncMsg('owner-visibility-change',
+                       {visible: !this._window.document.hidden});
+  },
+
+  /*
+   * Called when the child notices that its visibility has changed.
+   *
+   * This is sometimes redundant; for example, the child's visibility may
+   * change in response to a setVisible request that we made here!  But it's
+   * not always redundant; for example, the child's visibility may change in
+   * response to its parent docshell being hidden.
+   */
+  _childVisibilityChange: function(data) {
+    debug("_childVisibilityChange(" + data.json.visible + ")");
+    this._frameLoader.visible = data.json.visible;
+
+    this._fireEventFromMsg(data);
+  },
+
+  _exitFullscreen: function() {
+    this._windowUtils.exitFullscreen();
+  },
+
+  _remoteFullscreenOriginChange: function(data) {
+    let origin = data.json._payload_;
+    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
+  },
+
+  _remoteFrameFullscreenReverted: function(data) {
+    this._windowUtils.remoteFrameFullscreenReverted();
+  },
+
+  _fireFatalError: function() {
+    let evt = this._createEvent('error', {type: 'fatal'},
+                                /* cancelable = */ false);
+    this._frameElement.dispatchEvent(evt);
   },
 
   observe: function(subject, topic, data) {
     switch(topic) {
-    case 'app-startup':
-      this._init();
-      break;
-    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
-      if (data == BROWSER_FRAMES_ENABLED_PREF) {
-        this._init();
+    case 'oop-frameloader-crashed':
+      if (this._isAlive() && subject == this._frameLoader) {
+        this._fireFatalError();
       }
       break;
-    case 'remote-browser-pending':
-      this._observeRemoteBrowserFramePending(subject);
+    case 'ask-children-to-exit-fullscreen':
+      if (this._isAlive() &&
+          this._frameElement.ownerDocument == subject &&
+          this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) {
+        this._sendAsyncMsg('exit-fullscreen');
+      }
       break;
-    case 'inprocess-browser-shown':
-      this._observeInProcessBrowserFrameShown(subject);
+    case 'copypaste-docommand':
+      if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
+        this._sendAsyncMsg('do-command', { command: data });
+      }
       break;
-    }
+    default:
+      debug('Unknown topic: ' + topic);
+      break;
+    };
   },
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
deleted file mode 100644
--- a/dom/browser-element/BrowserElementParent.jsm
+++ /dev/null
@@ -1,947 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-let Cu = Components.utils;
-let Ci = Components.interfaces;
-let Cc = Components.classes;
-let Cr = Components.results;
-
-/* BrowserElementParent injects script to listen for certain events in the
- * child.  We then listen to messages from the child script and take
- * appropriate action here in the parent.
- */
-
-this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
-  Cu.import("resource://gre/modules/Webapps.jsm");
-  return DOMApplicationRegistry;
-});
-
-const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
-
-function debug(msg) {
-  //dump("BrowserElementParent.jsm - " + msg + "\n");
-}
-
-function getIntPref(prefName, def) {
-  try {
-    return Services.prefs.getIntPref(prefName);
-  }
-  catch(err) {
-    return def;
-  }
-}
-
-function visibilityChangeHandler(e) {
-  // The visibilitychange event's target is the document.
-  let win = e.target.defaultView;
-
-  if (!win._browserElementParents) {
-    return;
-  }
-
-  let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
-  if (beps.length == 0) {
-    win.removeEventListener('visibilitychange', visibilityChangeHandler);
-    return;
-  }
-
-  for (let i = 0; i < beps.length; i++) {
-    beps[i]._ownerVisibilityChange();
-  }
-}
-
-this.BrowserElementParentBuilder = {
-  create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
-    return new BrowserElementParent(frameLoader, hasRemoteFrame);
-  }
-}
-
-function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
-  debug("Creating new BrowserElementParent object for " + frameLoader);
-  this._domRequestCounter = 0;
-  this._domRequestReady = false;
-  this._pendingAPICalls = [];
-  this._pendingDOMRequests = {};
-  this._pendingSetInputMethodActive = [];
-  this._hasRemoteFrame = hasRemoteFrame;
-  this._nextPaintListeners = [];
-
-  this._frameLoader = frameLoader;
-  this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-  let self = this;
-  if (!this._frameElement) {
-    debug("No frame element?");
-    return;
-  }
-
-  Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
-
-  let defineMethod = function(name, fn) {
-    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function() {
-      if (self._isAlive()) {
-        return fn.apply(self, arguments);
-      }
-    }, self._frameElement);
-  }
-
-  let defineNoReturnMethod = function(name, fn) {
-    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function method() {
-      if (!self._domRequestReady) {
-        // Remote browser haven't been created, we just queue the API call.
-        let args = Array.slice(arguments);
-        args.unshift(self);
-        self._pendingAPICalls.push(method.bind.apply(fn, args));
-        return;
-      }
-      if (self._isAlive()) {
-        fn.apply(self, arguments);
-      }
-    }, self._frameElement);
-  };
-
-  let defineDOMRequestMethod = function(domName, msgName) {
-    XPCNativeWrapper.unwrap(self._frameElement)[domName] = Cu.exportFunction(function() {
-      return self._sendDOMRequest(msgName);
-    }, self._frameElement);
-  }
-
-  // Define methods on the frame element.
-  defineNoReturnMethod('setVisible', this._setVisible);
-  defineDOMRequestMethod('getVisible', 'get-visible');
-
-  // Not expose security sensitive browser API for widgets
-  if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
-    defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
-
-    // 0 = disabled, 1 = enabled, 2 - auto detect
-    if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
-      defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
-    }
-    defineNoReturnMethod('goBack', this._goBack);
-    defineNoReturnMethod('goForward', this._goForward);
-    defineNoReturnMethod('reload', this._reload);
-    defineNoReturnMethod('stop', this._stop);
-    defineMethod('download', this._download);
-    defineDOMRequestMethod('purgeHistory', 'purge-history');
-    defineMethod('getScreenshot', this._getScreenshot);
-    defineNoReturnMethod('zoom', this._zoom);
-
-    defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
-    defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
-    defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions');
-  }
-
-  defineMethod('addNextPaintListener', this._addNextPaintListener);
-  defineMethod('removeNextPaintListener', this._removeNextPaintListener);
-  defineNoReturnMethod('setActive', this._setActive);
-  defineMethod('getActive', 'this._getActive');
-
-  let principal = this._frameElement.ownerDocument.nodePrincipal;
-  let perm = Services.perms
-             .testExactPermissionFromPrincipal(principal, "input-manage");
-  if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
-    defineMethod('setInputMethodActive', this._setInputMethodActive);
-  }
-
-  // Listen to visibilitychange on the iframe's owner window, and forward
-  // changes down to the child.  We want to do this while registering as few
-  // visibilitychange listeners on _window as possible, because such a listener
-  // may live longer than this BrowserElementParent object.
-  //
-  // To accomplish this, we register just one listener on the window, and have
-  // it reference a WeakMap whose keys are all the BrowserElementParent objects
-  // on the window.  Then when the listener fires, we iterate over the
-  // WeakMap's keys (which we can do, because we're chrome) to notify the
-  // BrowserElementParents.
-  if (!this._window._browserElementParents) {
-    this._window._browserElementParents = new WeakMap();
-    this._window.addEventListener('visibilitychange',
-                                  visibilityChangeHandler,
-                                  /* useCapture = */ false,
-                                  /* wantsUntrusted = */ false);
-  }
-
-  this._window._browserElementParents.set(this, null);
-
-  // Insert ourself into the prompt service.
-  BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
-  if (!isPendingFrame) {
-    this._setupMessageListener();
-    this._registerAppManifest();
-  } else {
-    // if we are a pending frame, we setup message manager after
-    // observing remote-browser-frame-shown
-    Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
-  }
-}
-
-BrowserElementParent.prototype = {
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                         Ci.nsISupportsWeakReference]),
-
-  _runPendingAPICall: function() {
-    if (!this._pendingAPICalls) {
-      return;
-    }
-    for (let i = 0; i < this._pendingAPICalls.length; i++) {
-      try {
-        this._pendingAPICalls[i]();
-      } catch (e) {
-        // throw the expections from pending functions.
-        debug('Exception when running pending API call: ' +  e);
-      }
-    }
-    delete this._pendingAPICalls;
-  },
-
-  _registerAppManifest: function() {
-    // If this browser represents an app then let the Webapps module register for
-    // any messages that it needs.
-    let appManifestURL =
-          this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
-    if (appManifestURL) {
-      let inParent = Cc["@mozilla.org/xre/app-info;1"]
-                       .getService(Ci.nsIXULRuntime)
-                       .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-      if (inParent) {
-        DOMApplicationRegistry.registerBrowserElementParentForApp(
-          { manifestURL: appManifestURL }, this._mm);
-      } else {
-        this._mm.sendAsyncMessage("Webapps:RegisterBEP",
-                                  { manifestURL: appManifestURL });
-      }
-    }
-  },
-
-  _setupMessageListener: function() {
-    this._mm = this._frameLoader.messageManager;
-    let self = this;
-    let isWidget = this._frameLoader
-                       .QueryInterface(Ci.nsIFrameLoader)
-                       .ownerIsWidget;
-
-    // Messages we receive are handed to functions which take a (data) argument,
-    // where |data| is the message manager's data object.
-    // We use a single message and dispatch to various function based
-    // on data.msg_name
-    let mmCalls = {
-      "hello": this._recvHello,
-      "loadstart": this._fireProfiledEventFromMsg,
-      "loadend": this._fireProfiledEventFromMsg,
-      "close": this._fireEventFromMsg,
-      "error": this._fireEventFromMsg,
-      "firstpaint": this._fireProfiledEventFromMsg,
-      "documentfirstpaint": this._fireProfiledEventFromMsg,
-      "nextpaint": this._recvNextPaint,
-      "got-purge-history": this._gotDOMRequestResult,
-      "got-screenshot": this._gotDOMRequestResult,
-      "got-contentdimensions": this._gotDOMRequestResult,
-      "got-can-go-back": this._gotDOMRequestResult,
-      "got-can-go-forward": this._gotDOMRequestResult,
-      "fullscreen-origin-change": this._remoteFullscreenOriginChange,
-      "rollback-fullscreen": this._remoteFrameFullscreenReverted,
-      "exit-fullscreen": this._exitFullscreen,
-      "got-visible": this._gotDOMRequestResult,
-      "visibilitychange": this._childVisibilityChange,
-      "got-set-input-method-active": this._gotDOMRequestResult,
-      "selectionchange": this._handleSelectionChange,
-      "scrollviewchange": this._handleScrollViewChange,
-      "touchcarettap": this._handleTouchCaretTap
-    };
-
-    let mmSecuritySensitiveCalls = {
-      "showmodalprompt": this._handleShowModalPrompt,
-      "contextmenu": this._fireCtxMenuEvent,
-      "securitychange": this._fireEventFromMsg,
-      "locationchange": this._fireEventFromMsg,
-      "iconchange": this._fireEventFromMsg,
-      "scrollareachanged": this._fireEventFromMsg,
-      "titlechange": this._fireProfiledEventFromMsg,
-      "opensearch": this._fireEventFromMsg,
-      "manifestchange": this._fireEventFromMsg,
-      "metachange": this._fireEventFromMsg,
-      "resize": this._fireEventFromMsg,
-      "activitydone": this._fireEventFromMsg,
-      "scroll": this._fireEventFromMsg
-    };
-
-    this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
-      if (!self._isAlive()) {
-        return;
-      }
-
-      if (aMsg.data.msg_name in mmCalls) {
-        return mmCalls[aMsg.data.msg_name].apply(self, arguments);
-      } else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
-        return mmSecuritySensitiveCalls[aMsg.data.msg_name]
-                 .apply(self, arguments);
-      }
-    });
-  },
-
-  /**
-   * You shouldn't touch this._frameElement or this._window if _isAlive is
-   * false.  (You'll likely get an exception if you do.)
-   */
-  _isAlive: function() {
-    return !Cu.isDeadWrapper(this._frameElement) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
-  },
-
-  get _window() {
-    return this._frameElement.ownerDocument.defaultView;
-  },
-
-  get _windowUtils() {
-    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-  },
-
-  promptAuth: function(authDetail, callback) {
-    let evt;
-    let self = this;
-    let callbackCalled = false;
-    let cancelCallback = function() {
-      if (!callbackCalled) {
-        callbackCalled = true;
-        callback(false, null, null);
-      }
-    };
-
-    // 1. We don't handle password-only prompts.
-    // 2. We don't handle for widget case because of security concern.
-    if (authDetail.isOnlyPassword ||
-        this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
-      cancelCallback();
-      return;
-    }
-
-    /* username and password */
-    let detail = {
-      host:     authDetail.host,
-      realm:    authDetail.realm
-    };
-
-    evt = this._createEvent('usernameandpasswordrequired', detail,
-                            /* cancelable */ true);
-    Cu.exportFunction(function(username, password) {
-      if (callbackCalled)
-        return;
-      callbackCalled = true;
-      callback(true, username, password);
-    }, evt.detail, { defineAs: 'authenticate' });
-
-    Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      cancelCallback();
-    }
-  },
-
-  _sendAsyncMsg: function(msg, data) {
-    try {
-      if (!data) {
-        data = { };
-      }
-
-      data.msg_name = msg;
-      this._mm.sendAsyncMessage('browser-element-api:call', data);
-    } catch (e) {
-      return false;
-    }
-    return true;
-  },
-
-  _recvHello: function() {
-    debug("recvHello");
-
-    // Inform our child if our owner element's document is invisible.  Note
-    // that we must do so here, rather than in the BrowserElementParent
-    // constructor, because the BrowserElementChild may not be initialized when
-    // we run our constructor.
-    if (this._window.document.hidden) {
-      this._ownerVisibilityChange();
-    }
-
-    if (!this._domRequestReady) {
-      // At least, one message listener such as for hello is registered.
-      // So we can use sendAsyncMessage now.
-      this._domRequestReady = true;
-      this._runPendingAPICall();
-    }
-
-    return {
-      name: this._frameElement.getAttribute('name'),
-      fullscreenAllowed:
-        this._frameElement.hasAttribute('allowfullscreen') ||
-        this._frameElement.hasAttribute('mozallowfullscreen'),
-      isPrivate: this._frameElement.hasAttribute('mozprivatebrowsing')
-    };
-  },
-
-  _fireCtxMenuEvent: function(data) {
-    let detail = data.json;
-    let evtName = detail.msg_name;
-
-    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
-    let evt = this._createEvent(evtName, detail, /* cancellable */ true);
-
-    if (detail.contextmenu) {
-      var self = this;
-      Cu.exportFunction(function(id) {
-        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
-      }, evt.detail, { defineAs: 'contextMenuItemSelected' });
-    }
-
-    // The embedder may have default actions on context menu events, so
-    // we fire a context menu event even if the child didn't define a
-    // custom context menu
-    return !this._frameElement.dispatchEvent(evt);
-  },
-
-  /**
-   * add profiler marker for each event fired.
-   */
-  _fireProfiledEventFromMsg: function(data) {
-    if (Services.profiler !== undefined) {
-      Services.profiler.AddMarker(data.json.msg_name);
-    }
-    this._fireEventFromMsg(data);
-  },
-
-  /**
-   * Fire either a vanilla or a custom event, depending on the contents of
-   * |data|.
-   */
-  _fireEventFromMsg: function(data) {
-    let detail = data.json;
-    let name = detail.msg_name;
-
-    // For events that send a "_payload_" property, we just want to transmit
-    // this in the event.
-    if ("_payload_" in detail) {
-      detail = detail._payload_;
-    }
-
-    debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
-    let evt = this._createEvent(name, detail,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleShowModalPrompt: function(data) {
-    // Fire a showmodalprmopt event on the iframe.  When this method is called,
-    // the child is spinning in a nested event loop waiting for an
-    // unblock-modal-prompt message.
-    //
-    // If the embedder calls preventDefault() on the showmodalprompt event,
-    // we'll block the child until event.detail.unblock() is called.
-    //
-    // Otherwise, if preventDefault() is not called, we'll send the
-    // unblock-modal-prompt message to the child as soon as the event is done
-    // dispatching.
-
-    let detail = data.json;
-    debug('handleShowPrompt ' + JSON.stringify(detail));
-
-    // Strip off the windowID property from the object we send along in the
-    // event.
-    let windowID = detail.windowID;
-    delete detail.windowID;
-    debug("Event will have detail: " + JSON.stringify(detail));
-    let evt = this._createEvent('showmodalprompt', detail,
-                                /* cancelable = */ true);
-
-    let self = this;
-    let unblockMsgSent = false;
-    function sendUnblockMsg() {
-      if (unblockMsgSent) {
-        return;
-      }
-      unblockMsgSent = true;
-
-      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
-      // return value of confirm() to a boolean); Gecko does that for us.
-
-      let data = { windowID: windowID,
-                   returnValue: evt.detail.returnValue };
-      self._sendAsyncMsg('unblock-modal-prompt', data);
-    }
-
-    Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
-      // evt.detail.unblock().
-      sendUnblockMsg();
-    }
-  },
-
-  _handleSelectionChange: function(data) {
-    let evt = this._createEvent('selectionchange', data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleScrollViewChange: function(data) {
-    let evt = this._createEvent("scrollviewchange", data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleTouchCaretTap: function(data) {
-    let evt = this._createEvent("touchcarettap", data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _createEvent: function(evtName, detail, cancelable) {
-    // This will have to change if we ever want to send a CustomEvent with null
-    // detail.  For now, it's OK.
-    if (detail !== undefined && detail !== null) {
-      detail = Cu.cloneInto(detail, this._window);
-      return new this._window.CustomEvent('mozbrowser' + evtName,
-                                          { bubbles: true,
-                                            cancelable: cancelable,
-                                            detail: detail });
-    }
-
-    return new this._window.Event('mozbrowser' + evtName,
-                                  { bubbles: true,
-                                    cancelable: cancelable });
-  },
-
-  /**
-   * Kick off a DOMRequest in the child process.
-   *
-   * We'll fire an event called |msgName| on the child process, passing along
-   * an object with two fields:
-   *
-   *  - id:  the ID of this request.
-   *  - arg: arguments to pass to the child along with this request.
-   *
-   * We expect the child to pass the ID back to us upon completion of the
-   * request.  See _gotDOMRequestResult.
-   */
-  _sendDOMRequest: function(msgName, args) {
-    let id = 'req_' + this._domRequestCounter++;
-    let req = Services.DOMRequest.createRequest(this._window);
-    let self = this;
-    let send = function() {
-      if (!self._isAlive()) {
-        return;
-      }
-      if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
-        self._pendingDOMRequests[id] = req;
-      } else {
-        Services.DOMRequest.fireErrorAsync(req, "fail");
-      }
-    };
-    if (this._domRequestReady) {
-      send();
-    } else {
-      // Child haven't been loaded.
-      this._pendingAPICalls.push(send);
-    }
-    return req;
-  },
-
-  /**
-   * Called when the child process finishes handling a DOMRequest.  data.json
-   * must have the fields [id, successRv], if the DOMRequest was successful, or
-   * [id, errorMsg], if the request was not successful.
-   *
-   * The fields have the following meanings:
-   *
-   *  - id:        the ID of the DOM request (see _sendDOMRequest)
-   *  - successRv: the request's return value, if the request succeeded
-   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
-   *               failed.
-   *
-   */
-  _gotDOMRequestResult: function(data) {
-    let req = this._pendingDOMRequests[data.json.id];
-    delete this._pendingDOMRequests[data.json.id];
-
-    if ('successRv' in data.json) {
-      debug("Successful gotDOMRequestResult.");
-      let clientObj = Cu.cloneInto(data.json.successRv, this._window);
-      Services.DOMRequest.fireSuccess(req, clientObj);
-    }
-    else {
-      debug("Got error in gotDOMRequestResult.");
-      Services.DOMRequest.fireErrorAsync(req,
-        Cu.cloneInto(data.json.errorMsg, this._window));
-    }
-  },
-
-  _setVisible: function(visible) {
-    this._sendAsyncMsg('set-visible', {visible: visible});
-    this._frameLoader.visible = visible;
-  },
-
-  _setActive: function(active) {
-    this._frameLoader.visible = active;
-  },
-
-  _getActive: function() {
-    return this._frameLoader.visible;
-  },
-
-  _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
-    this._sendAsyncMsg("send-mouse-event", {
-      "type": type,
-      "x": x,
-      "y": y,
-      "button": button,
-      "clickCount": clickCount,
-      "modifiers": modifiers
-    });
-  },
-
-  _sendTouchEvent: function(type, identifiers, touchesX, touchesY,
-                            radiisX, radiisY, rotationAngles, forces,
-                            count, modifiers) {
-
-    let tabParent = this._frameLoader.tabParent;
-    if (tabParent && tabParent.useAsyncPanZoom) {
-      tabParent.injectTouchEvent(type,
-                                 identifiers,
-                                 touchesX,
-                                 touchesY,
-                                 radiisX,
-                                 radiisY,
-                                 rotationAngles,
-                                 forces,
-                                 count,
-                                 modifiers);
-    } else {
-      this._sendAsyncMsg("send-touch-event", {
-        "type": type,
-        "identifiers": identifiers,
-        "touchesX": touchesX,
-        "touchesY": touchesY,
-        "radiisX": radiisX,
-        "radiisY": radiisY,
-        "rotationAngles": rotationAngles,
-        "forces": forces,
-        "count": count,
-        "modifiers": modifiers
-      });
-    }
-  },
-
-  _goBack: function() {
-    this._sendAsyncMsg('go-back');
-  },
-
-  _goForward: function() {
-    this._sendAsyncMsg('go-forward');
-  },
-
-  _reload: function(hardReload) {
-    this._sendAsyncMsg('reload', {hardReload: hardReload});
-  },
-
-  _stop: function() {
-    this._sendAsyncMsg('stop');
-  },
-
-  /*
-   * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
-   */
-  _zoom: function(zoom) {
-    zoom *= 100;
-    zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
-    zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
-    this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
-  },
-
-  _download: function(_url, _options) {
-    let ioService =
-      Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
-    let uri = ioService.newURI(_url, null, null);
-    let url = uri.QueryInterface(Ci.nsIURL);
-
-    // Ensure we have _options, we always use it to send the filename.
-    _options = _options || {};
-    if (!_options.filename) {
-      _options.filename = url.fileName;
-    }
-
-    debug('_options = ' + uneval(_options));
-
-    // Ensure we have a filename.
-    if (!_options.filename) {
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    let interfaceRequestor =
-      this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
-    let req = Services.DOMRequest.createRequest(this._window);
-
-    function DownloadListener() {
-      debug('DownloadListener Constructor');
-    }
-    DownloadListener.prototype = {
-      extListener: null,
-      onStartRequest: function(aRequest, aContext) {
-        debug('DownloadListener - onStartRequest');
-        let extHelperAppSvc =
-          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
-          getService(Ci.nsIExternalHelperAppService);
-        let channel = aRequest.QueryInterface(Ci.nsIChannel);
-
-        // First, we'll ensure the filename doesn't have any leading
-        // periods. We have to do it here to avoid ending up with a filename
-        // that's only an extension with no extension (e.g. Sending in
-        // '.jpeg' without stripping the '.' would result in a filename of
-        // 'jpeg' where we want 'jpeg.jpeg'.
-        _options.filename = _options.filename.replace(/^\.+/, "");
-
-        let ext = null;
-        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
-        try {
-          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
-        } catch (e) { ext = null; }
-
-        // Check if we need to add an extension to the filename.
-        if (ext && !_options.filename.endsWith(ext)) {
-          _options.filename += ext;
-        }
-        // Set the filename to use when saving to disk.
-        channel.contentDispositionFilename = _options.filename;
-
-        this.extListener =
-          extHelperAppSvc.doContent(
-              channel.contentType,
-              aRequest,
-              interfaceRequestor,
-              true);
-        this.extListener.onStartRequest(aRequest, aContext);
-      },
-      onStopRequest: function(aRequest, aContext, aStatusCode) {
-        debug('DownloadListener - onStopRequest (aStatusCode = ' +
-               aStatusCode + ')');
-        if (aStatusCode == Cr.NS_OK) {
-          // Everything looks great.
-          debug('DownloadListener - Download Successful.');
-          Services.DOMRequest.fireSuccess(req, aStatusCode);
-        }
-        else {
-          // In case of failure, we'll simply return the failure status code.
-          debug('DownloadListener - Download Failed!');
-          Services.DOMRequest.fireError(req, aStatusCode);
-        }
-
-        if (this.extListener) {
-          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
-        }
-      },
-      onDataAvailable: function(aRequest, aContext, aInputStream,
-                                aOffset, aCount) {
-        this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
-                                         aOffset, aCount);
-      },
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, 
-                                             Ci.nsIRequestObserver])
-    };
-
-    let channel = ioService.newChannelFromURI(url);
-
-    // XXX We would set private browsing information prior to calling this.
-    channel.notificationCallbacks = interfaceRequestor;
-
-    // Since we're downloading our own local copy we'll want to bypass the
-    // cache and local cache if the channel let's us specify this.
-    let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
-                Ci.nsIChannel.LOAD_BYPASS_CACHE;
-    if (channel instanceof Ci.nsICachingChannel) {
-      debug('This is a caching channel. Forcing bypass.');
-      flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
-    }
-
-    channel.loadFlags |= flags;
-
-    if (channel instanceof Ci.nsIHttpChannel) {
-      debug('Setting HTTP referrer = ' + this._window.document.documentURIObject);
-      channel.referrer = this._window.document.documentURIObject;
-      if (channel instanceof Ci.nsIHttpChannelInternal) {
-        channel.forceAllowThirdPartyCookie = true;
-      }
-    }
-
-    // Set-up complete, let's get things started.
-    channel.asyncOpen(new DownloadListener(), null);
-
-    return req;
-  },
-
-  _getScreenshot: function(_width, _height, _mimeType) {
-    let width = parseInt(_width);
-    let height = parseInt(_height);
-    let mimeType = (typeof _mimeType === 'string') ?
-      _mimeType.trim().toLowerCase() : 'image/jpeg';
-    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
-      throw Components.Exception("Invalid argument",
-                                 Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return this._sendDOMRequest('get-screenshot',
-                                {width: width, height: height,
-                                 mimeType: mimeType});
-  },
-
-  _recvNextPaint: function(data) {
-    let listeners = this._nextPaintListeners;
-    this._nextPaintListeners = [];
-    for (let listener of listeners) {
-      try {
-        listener();
-      } catch (e) {
-        // If a listener throws we'll continue.
-      }
-    }
-  },
-
-  _addNextPaintListener: function(listener) {
-    if (typeof listener != 'function')
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
-
-    let self = this;
-    let run = function() {
-      if (self._nextPaintListeners.push(listener) == 1)
-        self._sendAsyncMsg('activate-next-paint-listener');
-    };
-    if (!this._domRequestReady) {
-      this._pendingAPICalls.push(run);
-    } else {
-      run();
-    }
-  },
-
-  _removeNextPaintListener: function(listener) {
-    if (typeof listener != 'function')
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
-
-    let self = this;
-    let run = function() {
-      for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
-        if (self._nextPaintListeners[i] == listener) {
-          self._nextPaintListeners.splice(i, 1);
-          break;
-        }
-      }
-
-      if (self._nextPaintListeners.length == 0)
-        self._sendAsyncMsg('deactivate-next-paint-listener');
-    };
-    if (!this._domRequestReady) {
-      this._pendingAPICalls.push(run);
-    } else {
-      run();
-    }
-  },
-
-  _setInputMethodActive: function(isActive) {
-    if (typeof isActive !== 'boolean') {
-      throw Components.Exception("Invalid argument",
-                                 Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return this._sendDOMRequest('set-input-method-active',
-                                {isActive: isActive});
-  },
-
-  /**
-   * Called when the visibility of the window which owns this iframe changes.
-   */
-  _ownerVisibilityChange: function() {
-    this._sendAsyncMsg('owner-visibility-change',
-                       {visible: !this._window.document.hidden});
-  },
-
-  /*
-   * Called when the child notices that its visibility has changed.
-   *
-   * This is sometimes redundant; for example, the child's visibility may
-   * change in response to a setVisible request that we made here!  But it's
-   * not always redundant; for example, the child's visibility may change in
-   * response to its parent docshell being hidden.
-   */
-  _childVisibilityChange: function(data) {
-    debug("_childVisibilityChange(" + data.json.visible + ")");
-    this._frameLoader.visible = data.json.visible;
-
-    this._fireEventFromMsg(data);
-  },
-
-  _exitFullscreen: function() {
-    this._windowUtils.exitFullscreen();
-  },
-
-  _remoteFullscreenOriginChange: function(data) {
-    let origin = data.json._payload_;
-    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
-  },
-
-  _remoteFrameFullscreenReverted: function(data) {
-    this._windowUtils.remoteFrameFullscreenReverted();
-  },
-
-  _fireFatalError: function() {
-    let evt = this._createEvent('error', {type: 'fatal'},
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  observe: function(subject, topic, data) {
-    switch(topic) {
-    case 'oop-frameloader-crashed':
-      if (this._isAlive() && subject == this._frameLoader) {
-        this._fireFatalError();
-      }
-      break;
-    case 'ask-children-to-exit-fullscreen':
-      if (this._isAlive() &&
-          this._frameElement.ownerDocument == subject &&
-          this._hasRemoteFrame) {
-        this._sendAsyncMsg('exit-fullscreen');
-      }
-      break;
-    case 'remote-browser-frame-shown':
-      if (this._frameLoader == subject) {
-        if (!this._mm) {
-          this._setupMessageListener();
-          this._registerAppManifest();
-        }
-        Services.obs.removeObserver(this, 'remote-browser-frame-shown');
-      }
-    case 'copypaste-docommand':
-      if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
-        this._sendAsyncMsg('do-command', { command: data });
-      }
-      break;
-    default:
-      debug('Unknown topic: ' + topic);
-      break;
-    };
-  },
-};
--- a/dom/browser-element/BrowserElementParent.manifest
+++ b/dom/browser-element/BrowserElementParent.manifest
@@ -1,3 +1,2 @@
-component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
-contract @mozilla.org/browser-element-parent-factory;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
-category app-startup BrowserElementParentFactory service,@mozilla.org/browser-element-parent-factory;1
+component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js
+contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060}
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -7,23 +7,28 @@
 EXPORTS.mozilla += [
     'BrowserElementParent.h',
 ]
 
 SOURCES += [
     'BrowserElementParent.cpp',
 ]
 
+XPIDL_SOURCES += [
+    'nsIBrowserElementAPI.idl',
+]
+
+XPIDL_MODULE = 'browser-element'
+
 EXTRA_COMPONENTS += [
     'BrowserElementParent.js',
     'BrowserElementParent.manifest',
 ]
 
 EXTRA_JS_MODULES += [
-    'BrowserElementParent.jsm',
     'BrowserElementPromptService.jsm',
 ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../bluetooth',
     '/dom/html',
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMDOMRequest;
+interface nsIFrameLoader;
+
+[scriptable, function, uuid(c0c2dd9b-41ef-42dd-a4c1-e456619c1941)]
+interface nsIBrowserElementNextPaintListener : nsISupports
+{
+  void recvNextPaint();
+};
+
+%{C++
+#define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
+#define BROWSER_ELEMENT_API_CID                                 \
+    { 0x651db7e3, 0x1734, 0x4536,                               \
+      { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
+%}
+
+/**
+ * Interface to the BrowserElementParent implementation. All methods
+ * but setFrameLoader throw when the remote process is dead.
+ */
+[scriptable, uuid(abae4fb1-7d6f-4e3f-b435-6501f1d4c659)]
+interface nsIBrowserElementAPI : nsISupports
+{
+  void setFrameLoader(in nsIFrameLoader frameLoader);
+
+  void setVisible(in boolean visible);
+  nsIDOMDOMRequest getVisible();
+  void setActive(in boolean active);
+  boolean getActive();
+
+  void sendMouseEvent(in DOMString type,
+                      in uint32_t x,
+                      in uint32_t y,
+                      in uint32_t button,
+                      in uint32_t clickCount,
+                      in uint32_t mifiers);
+  void sendTouchEvent(in DOMString aType,
+                      [const, array, size_is(count)] in uint32_t aIdentifiers,
+                      [const, array, size_is(count)] in int32_t aXs,
+                      [const, array, size_is(count)] in int32_t aYs,
+                      [const, array, size_is(count)] in uint32_t aRxs,
+                      [const, array, size_is(count)] in uint32_t aRys,
+                      [const, array, size_is(count)] in float aRotationAngles,
+                      [const, array, size_is(count)] in float aForces,
+                      in uint32_t count,
+                      in long aModifiers);
+  void goBack();
+  void goForward();
+  void reload(in boolean hardReload);
+  void stop();
+  nsIDOMDOMRequest download(in DOMString url,
+                            [optional] in jsval options);
+  nsIDOMDOMRequest purgeHistory();
+  nsIDOMDOMRequest getScreenshot(in uint32_t width,
+                                 in uint32_t height,
+                                 [optional] in DOMString mimeType);
+  void zoom(in float zoom);
+  nsIDOMDOMRequest getCanGoBack();
+  nsIDOMDOMRequest getCanGoForward();
+  nsIDOMDOMRequest getContentDimensions();
+
+  void addNextPaintListener(in nsIBrowserElementNextPaintListener listener);
+  void removeNextPaintListener(in nsIBrowserElementNextPaintListener listener);
+
+  nsIDOMDOMRequest setInputMethodActive(in boolean isActive);
+};
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -107,16 +107,17 @@ EXPORTS.mozilla.dom += [
     'HTMLTextAreaElement.h',
     'HTMLTimeElement.h',
     'HTMLTitleElement.h',
     'HTMLTrackElement.h',
     'HTMLUnknownElement.h',
     'HTMLVideoElement.h',
     'ImageDocument.h',
     'MediaError.h',
+    'nsBrowserElement.h',
     'RadioNodeList.h',
     'TextTrackManager.h',
     'TimeRanges.h',
     'UndoManager.h',
     'ValidityState.h',
 ]
 
 UNIFIED_SOURCES += [
@@ -185,16 +186,17 @@ UNIFIED_SOURCES += [
     'HTMLTimeElement.cpp',
     'HTMLTitleElement.cpp',
     'HTMLTrackElement.cpp',
     'HTMLUnknownElement.cpp',
     'HTMLVideoElement.cpp',
     'ImageDocument.cpp',
     'MediaDocument.cpp',
     'MediaError.cpp',
+    'nsBrowserElement.cpp',
     'nsDOMStringMap.cpp',
     'nsFormSubmission.cpp',
     'nsGenericHTMLElement.cpp',
     'nsGenericHTMLFrameElement.cpp',
     'nsHTMLContentSink.cpp',
     'nsHTMLDNSPrefetch.cpp',
     'nsHTMLDocument.cpp',
     'nsIConstraintValidation.cpp',
new file mode 100644
--- /dev/null
+++ b/dom/html/nsBrowserElement.cpp
@@ -0,0 +1,545 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBrowserElement.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/BrowserElementBinding.h"
+#include "mozilla/dom/DOMRequest.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsFrameLoader.h"
+#include "nsIDOMDOMRequest.h"
+#include "nsIDOMElement.h"
+#include "nsINode.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsWeakReference.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+static const char kRemoteBrowserPending[] = "remote-browser-pending";
+static const char kInprocessBrowserShown[] = "inprocess-browser-shown";
+
+class nsBrowserElement::BrowserShownObserver : public nsIObserver
+                                             , public nsSupportsWeakReference
+{
+public:
+  BrowserShownObserver(nsBrowserElement* aBrowserElement);
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  void AddObserver();
+  void RemoveObserver();
+private:
+  virtual ~BrowserShownObserver();
+
+  // Weak reference to the browser element. nsBrowserElement has a
+  // reference to us. nsBrowserElement's destructor is responsible to
+  // null out this weak reference via RemoveObserver()
+  nsBrowserElement* mBrowserElement;
+};
+
+NS_IMPL_ISUPPORTS(nsBrowserElement::BrowserShownObserver, nsIObserver, nsISupportsWeakReference)
+
+nsBrowserElement::BrowserShownObserver::BrowserShownObserver(nsBrowserElement* aBrowserElement)
+  : mBrowserElement(aBrowserElement)
+{
+}
+
+nsBrowserElement::BrowserShownObserver::~BrowserShownObserver()
+{
+  RemoveObserver();
+}
+
+NS_IMETHODIMP
+nsBrowserElement::BrowserShownObserver::Observe(nsISupports* aSubject,
+                                                const char* aTopic,
+                                                const char16_t* aData)
+{
+  NS_ENSURE_TRUE(mBrowserElement, NS_OK);
+
+  if (!strcmp(aTopic, kRemoteBrowserPending) ||
+      !strcmp(aTopic, kInprocessBrowserShown)) {
+    nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(aSubject);
+    nsCOMPtr<nsIFrameLoader> myFrameLoader = mBrowserElement->GetFrameLoader();
+    // The browser element API needs the frameloader to
+    // initialize. We still use the observer to get notified when the
+    // frameloader is created. So we check if the frameloader created
+    // is ours, then initialize the browser element API.
+    if (frameLoader && frameLoader == myFrameLoader) {
+      mBrowserElement->InitBrowserElementAPI();
+    }
+  }
+  return NS_OK;
+}
+
+void
+nsBrowserElement::BrowserShownObserver::AddObserver()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, kRemoteBrowserPending, true);
+    obs->AddObserver(this, kInprocessBrowserShown, true);
+  }
+}
+
+void
+nsBrowserElement::BrowserShownObserver::RemoveObserver()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, kRemoteBrowserPending);
+    obs->RemoveObserver(this, kInprocessBrowserShown);
+  }
+  mBrowserElement = nullptr;
+}
+
+bool
+nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv)
+{
+  if (mBrowserElementAPI) {
+    return true;
+  }
+  aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+  return false;
+}
+
+bool
+nsBrowserElement::IsNotWidgetOrThrow(ErrorResult& aRv)
+{
+  if (!mOwnerIsWidget) {
+    return true;
+  }
+  aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+  return false;
+}
+
+void
+nsBrowserElement::InitBrowserElementAPI()
+{
+  bool isBrowserOrApp;
+  nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
+  NS_ENSURE_TRUE_VOID(frameLoader);
+  nsresult rv = frameLoader->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  rv = frameLoader->GetOwnerIsWidget(&mOwnerIsWidget);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  if (!isBrowserOrApp) {
+    return;
+  }
+
+  mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
+  if (mBrowserElementAPI) {
+    mBrowserElementAPI->SetFrameLoader(frameLoader);
+  }
+}
+
+nsBrowserElement::nsBrowserElement()
+  : mOwnerIsWidget(false)
+{
+  mObserver = new BrowserShownObserver(this);
+  mObserver->AddObserver();
+}
+
+nsBrowserElement::~nsBrowserElement()
+{
+  mObserver->RemoveObserver();
+}
+
+void
+nsBrowserElement::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->SetVisible(aVisible);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetVisible(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetVisible(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::SetActive(bool aVisible, ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->SetActive(aVisible);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+bool
+nsBrowserElement::GetActive(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), false);
+
+  bool isActive;
+  nsresult rv = mBrowserElementAPI->GetActive(&isActive);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return false;
+  }
+
+  return isActive;
+}
+
+void
+nsBrowserElement::SendMouseEvent(const nsAString& aType,
+                                 uint32_t aX,
+                                 uint32_t aY,
+                                 uint32_t aButton,
+                                 uint32_t aClickCount,
+                                 uint32_t aModifiers,
+                                 ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->SendMouseEvent(aType,
+                                                   aX,
+                                                   aY,
+                                                   aButton,
+                                                   aClickCount,
+                                                   aModifiers);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::SendTouchEvent(const nsAString& aType,
+                                 const Sequence<uint32_t>& aIdentifiers,
+                                 const Sequence<int32_t>& aXs,
+                                 const Sequence<int32_t>& aYs,
+                                 const Sequence<uint32_t>& aRxs,
+                                 const Sequence<uint32_t>& aRys,
+                                 const Sequence<float>& aRotationAngles,
+                                 const Sequence<float>& aForces,
+                                 uint32_t aCount,
+                                 uint32_t aModifiers,
+                                 ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  if (aIdentifiers.Length() != aCount ||
+      aXs.Length() != aCount ||
+      aYs.Length() != aCount ||
+      aRxs.Length() != aCount ||
+      aRys.Length() != aCount ||
+      aRotationAngles.Length() != aCount ||
+      aForces.Length() != aCount) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return;
+  }
+
+  nsresult rv = mBrowserElementAPI->SendTouchEvent(aType,
+                                                   aIdentifiers.Elements(),
+                                                   aXs.Elements(),
+                                                   aYs.Elements(),
+                                                   aRxs.Elements(),
+                                                   aRys.Elements(),
+                                                   aRotationAngles.Elements(),
+                                                   aForces.Elements(),
+                                                   aCount,
+                                                   aModifiers);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::GoBack(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->GoBack();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::GoForward(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->GoForward();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::Reload(bool aHardReload, ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->Reload(aHardReload);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::Stop(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->Stop();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::Download(const nsAString& aUrl,
+                           const BrowserElementDownloadOptions& aOptions,
+                           ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
+  MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
+  AutoJSAPI jsapi;
+  jsapi.Init(wrappedObj->GetJSObject());
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JS::Value> options(cx);
+  if (!ToJSValue(cx, aOptions, &options)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+  nsresult rv = mBrowserElementAPI->Download(aUrl, options, getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::PurgeHistory(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->PurgeHistory(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetScreenshot(uint32_t aWidth,
+                                uint32_t aHeight,
+                                const nsAString& aMimeType,
+                                ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetScreenshot(aWidth, aHeight, aMimeType,
+                                                  getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (rv == NS_ERROR_INVALID_ARG) {
+      aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    } else {
+      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::Zoom(float aZoom, ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->Zoom(aZoom);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetCanGoBack(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetCanGoBack(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetCanGoForward(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetCanGoForward(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetContentDimensions(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetContentDimensions(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::AddNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
+                                       ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+  CallbackObjectHolder<BrowserElementNextPaintEventCallback,
+                       nsIBrowserElementNextPaintListener> holder(&aListener);
+  nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
+
+  nsresult rv = mBrowserElementAPI->AddNextPaintListener(listener);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::RemoveNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
+                                          ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+
+  CallbackObjectHolder<BrowserElementNextPaintEventCallback,
+                       nsIBrowserElementNextPaintListener> holder(&aListener);
+  nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
+
+  nsresult rv = mBrowserElementAPI->RemoveNextPaintListener(listener);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::SetInputMethodActive(bool aIsActive,
+                                       ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
+  if (!frameLoader) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDOMElement> ownerElement;
+  nsresult rv = frameLoader->GetOwnerElement(getter_AddRefs(ownerElement));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsINode> node = do_QueryInterface(ownerElement);
+  nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
+  if (!nsContentUtils::IsExactSitePermAllow(principal, "input-manage")) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  rv = mBrowserElementAPI->SetInputMethodActive(aIsActive,
+                                                getter_AddRefs(req));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (rv == NS_ERROR_INVALID_ARG) {
+      aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    } else {
+      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    }
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/html/nsBrowserElement.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBrowserElement_h
+#define nsBrowserElement_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+
+#include "nsCOMPtr.h"
+#include "nsIBrowserElementAPI.h"
+
+class nsFrameLoader;
+class nsIObserver;
+
+namespace mozilla {
+
+namespace dom {
+struct BrowserElementDownloadOptions;
+class BrowserElementNextPaintEventCallback;
+class DOMRequest;
+} // namespace dom
+
+class ErrorResult;
+
+/**
+ * A helper class for browser-element frames
+ */
+class nsBrowserElement
+{
+public:
+  nsBrowserElement();
+  virtual ~nsBrowserElement();
+
+  void SetVisible(bool aVisible, ErrorResult& aRv);
+  already_AddRefed<dom::DOMRequest> GetVisible(ErrorResult& aRv);
+  void SetActive(bool aActive, ErrorResult& aRv);
+  bool GetActive(ErrorResult& aRv);
+
+  void SendMouseEvent(const nsAString& aType,
+                      uint32_t aX,
+                      uint32_t aY,
+                      uint32_t aButton,
+                      uint32_t aClickCount,
+                      uint32_t aModifiers,
+                      ErrorResult& aRv);
+  void SendTouchEvent(const nsAString& aType,
+                      const dom::Sequence<uint32_t>& aIdentifiers,
+                      const dom::Sequence<int32_t>& aX,
+                      const dom::Sequence<int32_t>& aY,
+                      const dom::Sequence<uint32_t>& aRx,
+                      const dom::Sequence<uint32_t>& aRy,
+                      const dom::Sequence<float>& aRotationAngles,
+                      const dom::Sequence<float>& aForces,
+                      uint32_t aCount,
+                      uint32_t aModifiers,
+                      ErrorResult& aRv);
+  void GoBack(ErrorResult& aRv);
+  void GoForward(ErrorResult& aRv);
+  void Reload(bool aHardReload, ErrorResult& aRv);
+  void Stop(ErrorResult& aRv);
+
+  already_AddRefed<dom::DOMRequest>
+  Download(const nsAString& aUrl,
+           const dom::BrowserElementDownloadOptions& options,
+           ErrorResult& aRv);
+
+  already_AddRefed<dom::DOMRequest> PurgeHistory(ErrorResult& aRv);
+
+  already_AddRefed<dom::DOMRequest>
+  GetScreenshot(uint32_t aWidth,
+                uint32_t aHeight,
+                const nsAString& aMimeType,
+                ErrorResult& aRv);
+
+  void Zoom(float aZoom, ErrorResult& aRv);
+
+  already_AddRefed<dom::DOMRequest> GetCanGoBack(ErrorResult& aRv);
+  already_AddRefed<dom::DOMRequest> GetCanGoForward(ErrorResult& aRv);
+  already_AddRefed<dom::DOMRequest> GetContentDimensions(ErrorResult& aRv);
+
+  void AddNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
+                            ErrorResult& aRv);
+  void RemoveNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
+                               ErrorResult& aRv);
+
+  already_AddRefed<dom::DOMRequest> SetInputMethodActive(bool isActive,
+                                                         ErrorResult& aRv);
+
+protected:
+  NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
+  nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
+
+private:
+  void InitBrowserElementAPI();
+  bool IsBrowserElementOrThrow(ErrorResult& aRv);
+  bool IsNotWidgetOrThrow(ErrorResult& aRv);
+  bool mOwnerIsWidget;
+
+  class BrowserShownObserver;
+  friend class BrowserShownObserver;
+  nsRefPtr<BrowserShownObserver> mObserver;
+};
+
+} // namespace mozilla
+
+#endif // nsBrowserElement_h
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -29,16 +29,17 @@
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                   nsGenericHTMLElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
 NS_IMPL_RELEASE_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsGenericHTMLFrameElement)
   NS_INTERFACE_TABLE_INHERITED(nsGenericHTMLFrameElement,
                                nsIDOMMozBrowserFrame,
@@ -301,16 +302,20 @@ nsGenericHTMLFrameElement::GetReallyIsBr
   nsIPrincipal *principal = NodePrincipal();
   nsCOMPtr<nsIPermissionManager> permMgr =
     services::GetPermissionManager();
   NS_ENSURE_TRUE(permMgr, NS_OK);
 
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
   NS_ENSURE_SUCCESS(rv, NS_OK);
+  if (permission != nsIPermissionManager::ALLOW_ACTION) {
+    rv = permMgr->TestPermissionFromPrincipal(principal, "embed-widgets", &permission);
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+  }
   *aOut = permission == nsIPermissionManager::ALLOW_ACTION;
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
 {
   nsAutoString manifestURL;
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -4,39 +4,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsGenericHTMLFrameElement_h
 #define nsGenericHTMLFrameElement_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/nsBrowserElement.h"
+
 #include "nsElementFrameLoaderOwner.h"
+#include "nsFrameLoader.h"
 #include "nsGenericHTMLElement.h"
+#include "nsIDOMEventListener.h"
 #include "nsIFrameLoader.h"
 #include "nsIMozBrowserFrame.h"
-#include "nsIDOMEventListener.h"
-#include "mozilla/ErrorResult.h"
-
-#include "nsFrameLoader.h"
 
 class nsXULElement;
 
 /**
  * A helper class for frame elements
  */
 class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
                                   public nsElementFrameLoaderOwner,
+                                  public mozilla::nsBrowserElement,
                                   public nsIMozBrowserFrame
 {
 public:
   nsGenericHTMLFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
                             mozilla::dom::FromParser aFromParser)
     : nsGenericHTMLElement(aNodeInfo)
     , nsElementFrameLoaderOwner(aFromParser)
+    , nsBrowserElement()
   {
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMMOZBROWSERFRAME
   NS_DECL_NSIMOZBROWSERFRAME
 
@@ -67,16 +70,29 @@ public:
   virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsGenericHTMLFrameElement,
                                                      nsGenericHTMLElement)
 
   static bool BrowserFramesEnabled();
 
   /**
+   * nsIFrameLoaderOwner defines two GetFrameLoader() overloads. One
+   * is XPCOM style interface, the other one is C++ only.  "using" pulls
+   * them both in, now GetFrameLoader() is ambiguous because
+   * nsBrowserElement also has GetFrameLoader(). Explicit redefine
+   * GetFrameLoader() to choose nsElementFrameLoaderOwner::GetFrameLoader()
+   */
+  using nsElementFrameLoaderOwner::GetFrameLoader;
+  NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() MOZ_OVERRIDE
+  {
+    return nsElementFrameLoaderOwner::GetFrameLoader();
+  }
+
+  /**
    * Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
    * enum value.  scrolling="no" (and its synonyms) maps to
    * nsIScrollable::Scrollbar_Never, and anything else (including nullptr) maps
    * to nsIScrollable::Scrollbar_Auto.
    * @param aValue the attribute value to map or nullptr
    * @return nsIScrollable::Scrollbar_Never or nsIScrollable::Scrollbar_Auto
    */
   static int32_t MapScrollingAttribute(const nsAttrValue* aValue);
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -9,19 +9,19 @@
 #include "mozilla/dom/TimeRanges.h"
 #include "DecoderTraits.h"
 #include "MediaDataDecodedListener.h"
 #include "MediaDecoderOwner.h"
 #include "MediaSourceDecoder.h"
 #include "MediaSourceUtils.h"
 #include "SourceBufferDecoder.h"
 #include "TrackBuffer.h"
-#include "SharedDecoderManager.h"
 
 #ifdef MOZ_FMP4
+#include "SharedDecoderManager.h"
 #include "MP4Decoder.h"
 #include "MP4Reader.h"
 #endif
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
@@ -58,17 +58,19 @@ MediaSourceReader::MediaSourceReader(Med
   , mSeekResult(NS_OK)
   , mTimeThreshold(-1)
   , mDropAudioBeforeThreshold(false)
   , mDropVideoBeforeThreshold(false)
   , mEnded(false)
   , mAudioIsSeeking(false)
   , mVideoIsSeeking(false)
   , mHasEssentialTrackBuffers(false)
+#ifdef MOZ_FMP4
   , mSharedDecoderManager(new SharedDecoderManager())
+#endif
 {
 }
 
 void
 MediaSourceReader::PrepareInitialization()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MSE_DEBUG("MediaSourceReader(%p)::PrepareInitialization trackBuffers=%u",
@@ -366,17 +368,19 @@ MediaSourceReader::CreateSubDecoder(cons
 
   // Set a callback on the subreader that forwards calls to this reader.
   // This reader will then forward them onto the state machine via this
   // reader's callback.
   RefPtr<MediaDataDecodedListener<MediaSourceReader>> callback =
     new MediaDataDecodedListener<MediaSourceReader>(this, GetTaskQueue());
   reader->SetCallback(callback);
   reader->SetTaskQueue(GetTaskQueue());
+#ifdef MOZ_FMP4
   reader->SetSharedDecoderManager(mSharedDecoderManager);
+#endif
   reader->Init(nullptr);
 
   MSE_DEBUG("MediaSourceReader(%p)::CreateSubDecoder subdecoder %p subreader %p",
             this, decoder.get(), reader.get());
   decoder->SetReader(reader);
 #ifdef MOZ_EME
   decoder->SetCDMProxy(mCDMProxy);
 #endif
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -171,14 +171,16 @@ private:
   // the mDiscontinuity field set to true once we have the
   // first decoded sample. These flags are set during seeking
   // so we can detect when we have the first decoded sample
   // after a seek.
   bool mAudioIsSeeking;
   bool mVideoIsSeeking;
 
   bool mHasEssentialTrackBuffers;
+#ifdef MOZ_FMP4
   nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
+#endif
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_MEDIASOURCEREADER_H_ */
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -88,17 +88,21 @@ RtspMediaCodecReader::RequestVideoData(b
   EnsureActive();
   MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
 }
 
 nsresult
 RtspMediaCodecReader::ReadMetadata(MediaInfo* aInfo,
                                    MetadataTags** aTags)
 {
+  mRtspResource->DisablePlayoutDelay();
+  EnsureActive();
   nsresult rv = MediaCodecReader::ReadMetadata(aInfo, aTags);
+  SetIdle();
+
   if (rv == NS_OK && !IsWaitingMediaResources()) {
-    EnsureActive();
+    mRtspResource->EnablePlayoutDelay();
   }
 
   return rv;
 }
 
 } // namespace mozilla
--- a/dom/mobilemessage/gonk/MmsService.js
+++ b/dom/mobilemessage/gonk/MmsService.js
@@ -220,19 +220,21 @@ MmsConnection.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   /** MMS proxy settings. */
   mmsc:     "",
   mmsProxy: "",
   mmsPort:  -1,
 
   setApnSetting: function(network) {
-    this.mmsc = network.mmsc;
     // Workaround an xpconnect issue with undefined string objects. See bug 808220.
-    this.mmsProxy = (network === "undefined") ? undefined : network.mmsProxy;
+    this.mmsc =
+      (network.mmsc === "undefined") ? undefined : network.mmsc;
+    this.mmsProxy =
+      (network.mmsProxy === "undefined") ? undefined : network.mmsProxy;
     this.mmsPort = network.mmsPort;
   },
 
   get proxyInfo() {
     if (!this.mmsProxy) {
       if (DEBUG) debug("getProxyInfo: MMS proxy is not available.");
       return null;
     }
@@ -1208,22 +1210,16 @@ function SendTransaction(mmsConnection, 
   let phoneNumber = mmsConnection.getPhoneNumber();
   let from = (phoneNumber) ? { address: phoneNumber, type: "PLMN" } : null;
   msg.headers["from"] = from;
 
   msg.headers["date"] = new Date();
   msg.headers["x-mms-message-class"] = "personal";
   msg.headers["x-mms-expiry"] = 7 * 24 * 60 * 60;
   msg.headers["x-mms-priority"] = 129;
-  try {
-    msg.headers["x-mms-read-report"] =
-      Services.prefs.getBoolPref("dom.mms.requestReadReport");
-  } catch (e) {
-    msg.headers["x-mms-read-report"] = true;
-  }
   msg.headers["x-mms-delivery-report"] = requestDeliveryReport;
 
   if (!gMmsTransactionHelper.checkMaxValuesParameters(msg)) {
     //We should notify end user that the header format is wrong.
     if (DEBUG) debug("Check max values parameters fail.");
     throw new Error("Check max values parameters fail.");
   }
 
@@ -2181,16 +2177,22 @@ MmsService.prototype = {
     aMessage["sender"] = aMmsConnection.getPhoneNumber();
     aMessage["iccId"] = aMmsConnection.getIccId();
     try {
       aMessage["deliveryStatusRequested"] =
         Services.prefs.getBoolPref("dom.mms.requestStatusReport");
     } catch (e) {
       aMessage["deliveryStatusRequested"] = false;
     }
+    try {
+      headers["x-mms-read-report"] =
+        Services.prefs.getBoolPref("dom.mms.requestReadReport");
+    } catch (e) {
+      headers["x-mms-read-report"] = false;
+    }
 
     if (DEBUG) debug("createSavableFromParams: aMessage: " +
                      JSON.stringify(aMessage));
 
     return isAddrValid ? Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR
                        : Ci.nsIMobileMessageCallback.INVALID_ADDRESS_ERROR;
   },
 
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -10,16 +10,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include <android/log.h>
 #include <cutils/properties.h>
+#include <binder/IServiceManager.h>
 
 #include "AudioChannelService.h"
 #include "AudioManager.h"
 
 #include "nsIObserverService.h"
 #ifdef MOZ_B2G_RIL
 #include "nsIRadioInterfaceLayer.h"
 #endif
@@ -54,16 +55,17 @@ using namespace mozilla::dom::bluetooth;
 
 #define HEADPHONES_STATUS_HEADSET     MOZ_UTF16("headset")
 #define HEADPHONES_STATUS_HEADPHONE   MOZ_UTF16("headphone")
 #define HEADPHONES_STATUS_OFF         MOZ_UTF16("off")
 #define HEADPHONES_STATUS_UNKNOWN     MOZ_UTF16("unknown")
 #define HEADPHONES_STATUS_CHANGED     "headphones-status-changed"
 #define MOZ_SETTINGS_CHANGE_ID        "mozsettings-changed"
 #define AUDIO_CHANNEL_PROCESS_CHANGED "audio-channel-process-changed"
+#define AUDIO_POLICY_SERVICE_NAME     "media.audio_policy"
 
 static void BinderDeadCallback(status_t aErr);
 static void InternalSetAudioRoutes(SwitchState aState);
 // Refer AudioService.java from Android
 static int sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = {
   5,   // voice call
   15,  // system
   15,  // ring
@@ -89,16 +91,29 @@ namespace gonk {
 class RecoverTask : public nsRunnable
 {
 public:
   RecoverTask() {}
   NS_IMETHODIMP Run() {
     nsCOMPtr<nsIAudioManager> amService = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
     NS_ENSURE_TRUE(amService, NS_OK);
     AudioManager *am = static_cast<AudioManager *>(amService.get());
+
+    int attempt;
+    for (attempt = 0; attempt < 50; attempt++) {
+      if (defaultServiceManager()->checkService(String16(AUDIO_POLICY_SERVICE_NAME)) != 0) {
+        break;
+      }
+
+      LOG("AudioPolicyService is dead! attempt=%d", attempt);
+      usleep(1000 * 200);
+    }
+
+    MOZ_RELEASE_ASSERT(attempt < 50);
+
     for (int loop = 0; loop < AUDIO_STREAM_CNT; loop++) {
       AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(loop), 0,
                                    sMaxStreamVolumeTbl[loop]);
       int32_t index;
       am->GetStreamVolumeIndex(loop, &index);
       am->SetStreamVolumeIndex(loop, index);
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BrowserElement.webidl
@@ -0,0 +1,142 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+callback BrowserElementNextPaintEventCallback = void ();
+
+dictionary BrowserElementDownloadOptions {
+  DOMString? filename;
+};
+
+[NoInterfaceObject]
+interface BrowserElement {
+};
+
+BrowserElement implements BrowserElementCommon;
+BrowserElement implements BrowserElementPrivileged;
+
+[NoInterfaceObject]
+interface BrowserElementCommon {
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  void setVisible(boolean visible);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  DOMRequest getVisible();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  void setActive(boolean active);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  boolean getActive();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  void addNextPaintListener(BrowserElementNextPaintEventCallback listener);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser embed-widgets"]
+  void removeNextPaintListener(BrowserElementNextPaintEventCallback listener);
+};
+
+[NoInterfaceObject]
+interface BrowserElementPrivileged {
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void sendMouseEvent(DOMString type,
+                      unsigned long x,
+                      unsigned long y,
+                      unsigned long button,
+                      unsigned long clickCount,
+                      unsigned long modifiers);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   Func="TouchEvent::PrefEnabled",
+   CheckPermissions="browser"]
+  void sendTouchEvent(DOMString type,
+                      sequence<unsigned long> identifiers,
+                      sequence<long> x,
+                      sequence<long> y,
+                      sequence<unsigned long> rx,
+                      sequence<unsigned long> ry,
+                      sequence<float> rotationAngles,
+                      sequence<float> forces,
+                      unsigned long count,
+                      unsigned long modifiers);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void goBack();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void goForward();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void reload(optional boolean hardReload = false);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void stop();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest download(DOMString url,
+                      optional BrowserElementDownloadOptions options);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest purgeHistory();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest getScreenshot([EnforceRange] unsigned long width,
+                           [EnforceRange] unsigned long height,
+                           optional DOMString mimeType="");
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  void zoom(float zoom);
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest getCanGoBack();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest getCanGoForward();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest getContentDimensions();
+
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckPermissions="browser"]
+  DOMRequest setInputMethodActive(boolean isActive);
+};
--- a/dom/webidl/HTMLIFrameElement.webidl
+++ b/dom/webidl/HTMLIFrameElement.webidl
@@ -58,8 +58,9 @@ partial interface HTMLIFrameElement {
 
 partial interface HTMLIFrameElement {
   // nsIMozBrowserFrame
   [ChromeOnly]
   readonly attribute DOMString appManifestURL;
 };
 
 HTMLIFrameElement implements MozFrameLoaderOwner;
+HTMLIFrameElement implements BrowserElement;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -50,16 +50,17 @@ WEBIDL_FILES = [
     'AutocompleteInfo.webidl',
     'BarProp.webidl',
     'BatteryManager.webidl',
     'BeforeAfterKeyboardEvent.webidl',
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BoxObject.webidl',
+    'BrowserElement.webidl',
     'BrowserElementDictionaries.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',
     'CameraUtil.webidl',
     'CanvasRenderingContext2D.webidl',
     'CaretPosition.webidl',
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1948,22 +1948,22 @@ public class BrowserApp extends GeckoApp
             // which we save whenever the user performs a search.
             url = (TextUtils.isEmpty(userRequested) ? tab.getURL() : userRequested);
         }
 
         enterEditingMode(url);
     }
 
     /**
-     * Enters editing mode with the specified URL. This method will
-     * always open the HISTORY page on about:home.
+     * Enters editing mode with the specified URL. If a null
+     * url is given, the empty String will be used instead.
      */
     private void enterEditingMode(String url) {
         if (url == null) {
-            throw new IllegalArgumentException("Cannot handle null URLs in enterEditingMode");
+            url = "";
         }
 
         if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
             return;
         }
 
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         mTargetTabForEditingMode = (selectedTab != null ? selectedTab.getId() : null);
--- a/mobile/android/base/tabs/TabsPanel.java
+++ b/mobile/android/base/tabs/TabsPanel.java
@@ -236,28 +236,27 @@ public class TabsPanel extends LinearLay
 
         return mActivity.onOptionsItemSelected(item);
     }
 
     private static int getTabContainerHeight(TabsLayoutContainer tabsContainer) {
         Resources resources = tabsContainer.getContext().getResources();
 
         int screenHeight = resources.getDisplayMetrics().heightPixels;
+        int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
 
         if(NewTabletUI.isEnabled(tabsContainer.getContext())){
-            return screenHeight;
+            return screenHeight - actionBarHeight;
         }
 
         PanelView panelView = tabsContainer.getCurrentPanelView();
         if (panelView != null && !panelView.shouldExpand()) {
             return resources.getDimensionPixelSize(R.dimen.tabs_layout_horizontal_height);
         }
 
-        int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
-
         Rect windowRect = new Rect();
         tabsContainer.getWindowVisibleDisplayFrame(windowRect);
         int windowHeight = windowRect.bottom - windowRect.top;
 
         // The web content area should have at least 1.5x the height of the action bar.
         // The tabs panel shouldn't take less than 50% of the screen height and can take
         // up to 80% of the window height.
         return (int) Math.max(screenHeight * 0.5f,
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/javascript_redirect.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+  let page = "<!DOCTYPE html><html><head><script>window.opener = null; location.replace('" + request.queryString + "')</script></head><body><p>Redirecting...</p></body></html>";
+
+  response.setStatusLine("1.0", 200, "OK");
+  response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+  response.write(page);
+}
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -99,16 +99,17 @@ skip-if = android_version == "10"
 
 # Using JavascriptTest
 [testAccounts]
 [testAndroidLog]
 [testBrowserDiscovery]
 [testDebuggerServer]
 [testDeviceSearchEngine]
 [testFilePicker]
+[testHistoryService]
 [testJNI]
 # [testMozPay] # see bug 945675
 [testNetworkManager]
 [testOfflinePage]
 [testOrderedBroadcast]
 [testOSLocale]
 [testResourceSubstitutions]
 [testRestrictedProfiles]
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/simple_redirect.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response)
+{
+  response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+  response.setHeader("Location", request.queryString, false);
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testHistoryService.java
@@ -0,0 +1,12 @@
+/* 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.tests;
+
+public class testHistoryService extends JavascriptTest {
+
+    public testHistoryService() {
+        super("testHistoryService.js");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testHistoryService.js
@@ -0,0 +1,130 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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 } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ok(passed, text) {
+  do_report_result(passed, text, Components.stack.caller, false);
+}
+
+// Make the timer global so it doesn't get GC'd
+let gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+function sleep(wait) {
+  return new Promise((resolve, reject) => {
+    do_print("sleep start");
+    gTimer.initWithCallback({
+      notify: function () {
+        do_print("sleep end");
+        resolve();
+      },
+    }, wait, gTimer.TYPE_ONE_SHOT);
+  });
+}
+
+function promiseLoadEvent(browser, url, eventType="load") {
+  return new Promise((resolve, reject) => {
+    do_print("Wait browser event: " + eventType);
+
+    function handle(event) {
+      // Since we'll be redirecting, don't make assumptions about the given URL and the loaded URL
+      if (event.target != browser.contentDocument || event.target.location.href == "about:blank") {
+        do_print("Skipping spurious '" + eventType + "' event" + " for " + event.target.location.href);
+        return;
+      }
+
+      browser.removeEventListener(eventType, handle, true);
+      do_print("Browser event received: " + eventType);
+      resolve(event);
+    }
+
+    browser.addEventListener(eventType, handle, true);
+    if (url) {
+      browser.loadURI(url);
+    }
+  });
+}
+
+// Wait 4 seconds for the pending visits to flush (which should happen in 3 seconds)
+const PENDING_VISIT_WAIT = 4000;
+
+// Manage the saved history visits so we can compare in the tests
+let gVisitURLs = [];
+function visitObserver(subject, topic, data) {
+  let uri = subject.QueryInterface(Ci.nsIURI);
+  do_print("Observer: " + uri.spec);
+  gVisitURLs.push(uri.spec);
+};
+
+// Track the <browser> where the tests are happening
+let gBrowser;
+
+add_test(function setup_browser() {
+  let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+  let BrowserApp = chromeWin.BrowserApp;
+
+  do_register_cleanup(function cleanup() {
+    Services.obs.removeObserver(visitObserver, "link-visited");
+    BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
+  });
+
+  Services.obs.addObserver(visitObserver, "link-visited", false);
+
+  // Load a blank page
+  let url = "about:blank";
+  gBrowser = BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
+  gBrowser.addEventListener("load", function startTests(event) {
+    gBrowser.removeEventListener("load", startTests, true);
+    Services.tm.mainThread.dispatch(run_next_test, Ci.nsIThread.DISPATCH_NORMAL);
+  }, true);
+});
+
+add_task(function* () {
+  // Wait for any initial page loads to be saved to history
+  yield sleep(PENDING_VISIT_WAIT);
+
+  // Load a simple HTML page with no redirects
+  gVisitURLs = [];
+  yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/robocop_blank_01.html");
+  yield sleep(PENDING_VISIT_WAIT);
+
+  do_print("visit counts: " + gVisitURLs.length);
+  ok(gVisitURLs.length == 1, "Simple visit makes 1 history item");
+
+  do_print("visit URL: " + gVisitURLs[0]);
+  ok(gVisitURLs[0] == "http://example.org/tests/robocop/robocop_blank_01.html", "Simple visit makes final history item");
+
+  // Load a simple HTML page via a 301 temporary redirect
+  gVisitURLs = [];
+  yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/simple_redirect.sjs?http://example.org/tests/robocop/robocop_blank_02.html");
+  yield sleep(PENDING_VISIT_WAIT);
+
+  do_print("visit counts: " + gVisitURLs.length);
+  ok(gVisitURLs.length == 1, "Simple 301 redirect makes 1 history item");
+
+  do_print("visit URL: " + gVisitURLs[0]);
+  ok(gVisitURLs[0] == "http://example.org/tests/robocop/robocop_blank_02.html", "Simple 301 redirect makes final history item");
+
+  // Load a simple HTML page via a JavaScript redirect
+  gVisitURLs = [];
+  yield promiseLoadEvent(gBrowser, "http://example.org/tests/robocop/javascript_redirect.sjs?http://example.org/tests/robocop/robocop_blank_03.html");
+  yield sleep(PENDING_VISIT_WAIT);
+
+  do_print("visit counts: " + gVisitURLs.length);
+  ok(gVisitURLs.length == 2, "JavaScript redirect makes 2 history items");
+
+  do_print("visit URL 1: " + gVisitURLs[0]);
+  ok(gVisitURLs[0] == "http://example.org/tests/robocop/javascript_redirect.sjs?http://example.org/tests/robocop/robocop_blank_03.html", "JavaScript redirect makes intermediate history item");
+
+  do_print("visit URL 2: " + gVisitURLs[1]);
+  ok(gVisitURLs[1] == "http://example.org/tests/robocop/robocop_blank_03.html", "JavaScript redirect makes final history item");
+});
+
+run_next_test();
--- a/mobile/android/base/tests/testTrackingProtection.js
+++ b/mobile/android/base/tests/testTrackingProtection.js
@@ -10,32 +10,36 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Messaging.jsm");
 
 function ok(passed, text) {
   do_report_result(passed, text, Components.stack.caller, false);
 }
 
-function promiseLoadEvent(browser, url, eventType="load") {
+function promiseLoadEvent(browser, url, eventType="load", runBeforeLoad) {
   return new Promise((resolve, reject) => {
     do_print("Wait browser event: " + eventType);
 
     function handle(event) {
       if (event.target != browser.contentDocument || event.target.location.href == "about:blank" || (url && event.target.location.href != url)) {
         do_print("Skipping spurious '" + eventType + "' event" + " for " + event.target.location.href);
         return;
       }
 
       browser.removeEventListener(eventType, handle, true);
       do_print("Browser event received: " + eventType);
       resolve(event);
     }
 
-    browser.addEventListener(eventType, handle, true, true);
+    browser.addEventListener(eventType, handle, true);
+
+    if (runBeforeLoad) {
+      runBeforeLoad();
+    }
     if (url) {
       browser.loadURI(url);
     }
   });
 }
 
 // Test that the Tracking Protection is active and has the correct state when
 // tracking content is blocked (Bug 1063831)
@@ -117,16 +121,29 @@ add_task(function* () {
   // Point tab to a test page NOT containing tracking elements
   yield promiseLoadEvent(browser, "http://tracking.example.org/tests/robocop/tracking_good.html");
   Messaging.sendRequest({ type: "Test:Expected", expected: "unknown" });
 
   // Point tab to a test page containing tracking elements
   yield promiseLoadEvent(browser, "http://tracking.example.org/tests/robocop/tracking_bad.html");
   Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_blocked" });
 
+  // Simulate a click on the "Disable protection" button in the site identity popup.
+  // We need to wait for a "load" event because "Session:Reload" will cause a full page reload.
+  yield promiseLoadEvent(browser, undefined, undefined, () => {
+    Services.obs.notifyObservers(null, "Session:Reload", "{\"allowContent\":true,\"contentType\":\"tracking\"}");
+  });
+  Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_loaded" });
+
+  // Simulate a click on the "Enable protection" button in the site identity popup.
+  yield promiseLoadEvent(browser, undefined, undefined, () => {
+    Services.obs.notifyObservers(null, "Session:Reload", "{\"allowContent\":false,\"contentType\":\"tracking\"}");
+  });
+  Messaging.sendRequest({ type: "Test:Expected", expected: "tracking_content_blocked" });
+
   // Disable Tracking Protection
   Services.prefs.setBoolPref(PREF, false);
 
   // Point tab to a test page containing tracking elements
   yield promiseLoadEvent(browser, "http://tracking.example.org/tests/robocop/tracking_bad.html");
   Messaging.sendRequest({ type: "Test:Expected", expected: "unknown" });
 
   // Point tab to a test page NOT containing tracking elements
--- a/mobile/android/base/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/toolbar/SiteIdentityPopup.java
@@ -282,18 +282,18 @@ public class SiteIdentityPopup extends A
         removeTrackingContentNotification();
     }
 
     private class PopupButtonListener implements OnButtonClickListener {
         @Override
         public void onButtonClick(DoorHanger dh, String tag) {
             try {
                 JSONObject data = new JSONObject();
-                String allowType = (dh == mMixedContentNotification ? "allowMixedContent" : "allowTrackingContent");
-                data.put(allowType, tag.equals("disable"));
+                data.put("allowContent", tag.equals("disable"));
+                data.put("contentType", (dh == mMixedContentNotification ? "mixed" : "tracking"));
 
                 GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
                 GeckoAppShell.sendEventToGecko(e);
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Exception creating message to enable/disable content blocking", e);
             }
 
             dismiss();
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1543,24 +1543,44 @@ var BrowserApp = {
           browser.gotoIndex(index);
           break;
 
       case "Session:Reload": {
         let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
 
         // Check to see if this is a message to enable/disable mixed content blocking.
         if (aData) {
-          let allowMixedContent = JSON.parse(aData).allowMixedContent;
-          if (allowMixedContent) {
-            // Set a flag to disable mixed content blocking.
-            flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
-          } else {
-            // Set mixedContentChannel to null to re-enable mixed content blocking.
-            let docShell = browser.webNavigation.QueryInterface(Ci.nsIDocShell);
-            docShell.mixedContentChannel = null;
+          let data = JSON.parse(aData);
+          if (data.contentType === "mixed") {
+            if (data.allowContent) {
+              // Set a flag to disable mixed content blocking.
+              flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
+            } else {
+              // Set mixedContentChannel to null to re-enable mixed content blocking.
+              let docShell = browser.webNavigation.QueryInterface(Ci.nsIDocShell);
+              docShell.mixedContentChannel = null;
+            }
+          } else if (data.contentType === "tracking") {
+            if (data.allowContent) {
+              // Convert document URI into the format used by
+              // nsChannelClassifier::ShouldEnableTrackingProtection
+              // (any scheme turned into https is correct)
+              let normalizedUrl = Services.io.newURI("https://" + browser.currentURI.hostPort, null, null);
+              // Add the current host in the 'trackingprotection' consumer of
+              // the permission manager using a normalized URI. This effectively
+              // places this host on the tracking protection white list.
+              Services.perms.add(normalizedUrl, "trackingprotection", Services.perms.ALLOW_ACTION);
+              Telemetry.addData("TRACKING_PROTECTION_EVENTS", 1);
+            } else {
+              // Remove the current host from the 'trackingprotection' consumer
+              // of the permission manager. This effectively removes this host
+              // from the tracking protection white list (any list actually).
+              Services.perms.remove(browser.currentURI.host, "trackingprotection");
+              Telemetry.addData("TRACKING_PROTECTION_EVENTS", 2);
+            }
           }
         }
 
         // Try to use the session history to reload so that framesets are
         // handled properly. If the window has no session history, fall back
         // to using the web navigation's reload method.
         let webNav = browser.webNavigation;
         try {
@@ -5157,17 +5177,17 @@ var BrowserEventHandler = {
 
     this.motionBuffer.push({ dx: dx, dy: dy, time: this.lastTime });
   },
 
   _sendMouseEvent: function _sendMouseEvent(aName, aX, aY) {
     let win = BrowserApp.selectedBrowser.contentWindow;
     try {
       let cwu = win.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-      cwu.sendMouseEventToWindow(aName, aX, aY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
+      cwu.sendMouseEventToWindow(aName, aX, aY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH, false);
     } catch(e) {
       Cu.reportError(e);
     }
   },
 
   _hasScrollableOverflow: function(elem) {
     var win = elem.ownerDocument.defaultView;
     if (!win)
--- a/mobile/android/components/build/nsAndroidHistory.cpp
+++ b/mobile/android/components/build/nsAndroidHistory.cpp
@@ -223,32 +223,34 @@ nsAndroidHistory::VisitURI(nsIURI *aURI,
   if (!(aFlags & VisitFlags::TOP_LEVEL)) {
     return NS_OK;
   }
 
   if (aFlags & VisitFlags::UNRECOVERABLE_ERROR) {
     return NS_OK;
   }
 
-  if (aFlags & VisitFlags::REDIRECT_SOURCE || aFlags & VisitFlags::REDIRECT_PERMANENT || aFlags & VisitFlags::REDIRECT_TEMPORARY) {
-    // aLastVisitedURI redirected to aURI. We want to ignore aLastVisitedURI,
-    // so remove the pending visit. We want to give aURI a chance to be saved,
-    // so don't return early.
-    RemovePendingVisitURI(aLastVisitedURI);
-  }
-
   // Silently return if URI is something we shouldn't add to DB.
   bool canAdd;
   nsresult rv = CanAddURI(aURI, &canAdd);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!canAdd) {
     return NS_OK;
   }
 
   if (aLastVisitedURI) {
+    if (aFlags & VisitFlags::REDIRECT_SOURCE ||
+        aFlags & VisitFlags::REDIRECT_PERMANENT ||
+        aFlags & VisitFlags::REDIRECT_TEMPORARY) {
+      // aLastVisitedURI redirected to aURI. We want to ignore aLastVisitedURI,
+      // so remove the pending visit. We want to give aURI a chance to be saved,
+      // so don't return early.
+      RemovePendingVisitURI(aLastVisitedURI);
+    }
+
     bool same;
     rv = aURI->Equals(aLastVisitedURI, &same);
     NS_ENSURE_SUCCESS(rv, rv);
     if (same && IsRecentlyVisitedURI(aURI)) {
       // Do not save refresh visits if we have visited this URI recently.
       return NS_OK;
     }
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -112,16 +112,17 @@
 #ifdef ACCESSIBILITY
 @BINPATH@/components/accessibility.xpt
 #endif
 @BINPATH@/components/appshell.xpt
 @BINPATH@/components/appstartup.xpt
 @BINPATH@/components/autocomplete.xpt
 @BINPATH@/components/autoconfig.xpt
 @BINPATH@/components/browsercompsbase.xpt
+@BINPATH@/components/browser-element.xpt
 @BINPATH@/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chardet.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -45,17 +45,17 @@ LayoutHelpers.prototype = {
       box: region
     });
 
     if (!quads) {
       return null;
     }
 
     let [xOffset, yOffset] = this.getFrameOffsets(node);
-    let scale = this.calculateScale(node);
+    let scale = LayoutHelpers.getCurrentZoom(node);
 
     return {
       p1: {
         w: quads.p1.w * scale,
         x: quads.p1.x * scale + xOffset,
         y: quads.p1.y * scale + yOffset,
         z: quads.p1.z * scale
       },
@@ -86,29 +86,16 @@ LayoutHelpers.prototype = {
         width: quads.bounds.width * scale,
         x: quads.bounds.x * scale + xOffset,
         y: quads.bounds.y * scale + yOffset
       }
     };
   },
 
   /**
-   * Get the current zoom factor applied to the container window of a given node
-   * @param {DOMNode}
-   *        The node for which the zoom factor should be calculated
-   * @return {Number}
-   */
-  calculateScale: function(node) {
-    let win = node.ownerDocument.defaultView;
-    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIDOMWindowUtils);
-    return winUtils.fullZoom;
-  },
-
-  /**
    * Compute the absolute position and the dimensions of a node, relativalely
    * to the root window.
    *
    * @param {DOMNode} aNode
    *        a DOM element to get the bounds for
    * @param {DOMWindow} aContentWindow
    *        the content window holding the node
    * @return {Object}
@@ -406,17 +393,17 @@ LayoutHelpers.prototype = {
    *        The node for which we are to get the offset
    * @return {Array}
    *         The frame offset [x, y]
    */
   getFrameOffsets: function(node) {
     let xOffset = 0;
     let yOffset = 0;
     let frameWin = node.ownerDocument.defaultView;
-    let scale = this.calculateScale(node);
+    let scale = LayoutHelpers.getCurrentZoom(node);
 
     while (true) {
       // Are we in the top-level window?
       if (this.isTopLevelWindow(frameWin)) {
         break;
       }
 
       let frameElement = this.getFrameElement(frameWin);
@@ -449,17 +436,17 @@ LayoutHelpers.prototype = {
    * @return {Object}
    *         An object with p1,p2,p3,p4 properties being {x,y} objects
    */
   getNodeBounds: function(node) {
     if (!node) {
       return;
     }
 
-    let scale = this.calculateScale(node);
+    let scale = LayoutHelpers.getCurrentZoom(node);
 
     // Find out the offset of the node in its current frame
     let offsetLeft = 0;
     let offsetTop = 0;
     let el = node;
     while (el && el.parentNode) {
       offsetLeft += el.offsetLeft;
       offsetTop += el.offsetTop;
@@ -602,8 +589,30 @@ LayoutHelpers.isShadowAnonymous = functi
   if (!parent) {
     return false;
   }
 
   // If there is a shadowRoot and this is part of it then this
   // is not native anonymous
   return parent.shadowRoot && parent.shadowRoot.contains(node);
 };
+
+/**
+ * Get the current zoom factor applied to the container window of a given node.
+ * Container windows are used as a weakmap key to store the corresponding
+ * nsIDOMWindowUtils instance to avoid querying it every time.
+ *
+ * @param {DOMNode} The node for which the zoom factor should be calculated
+ * @return {Number}
+ */
+let windowUtils = new WeakMap;
+LayoutHelpers.getCurrentZoom = function(node, map = z=>z) {
+  let win = node.ownerDocument.defaultView;
+  let utils = windowUtils.get(win);
+  if (utils) {
+    return utils.fullZoom;
+  }
+
+  utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+             .getInterface(Ci.nsIDOMWindowUtils);
+  windowUtils.set(win, utils);
+  return utils.fullZoom;
+};
--- a/toolkit/devtools/server/actors/highlighter.css
+++ b/toolkit/devtools/server/actors/highlighter.css
@@ -21,17 +21,17 @@
 }
 
 :-moz-native-anonymous .highlighter-container [hidden] {
   display: none;
 }
 
 /* Box model highlighter */
 
-:-moz-native-anonymous .box-model-container {
+:-moz-native-anonymous .box-model-regions {
   opacity: 0.4;
 }
 
 :-moz-native-anonymous .box-model-content {
   fill: #80d4ff;
 }
 
 :-moz-native-anonymous .box-model-padding {
--- a/toolkit/devtools/server/actors/highlighter.js
+++ b/toolkit/devtools/server/actors/highlighter.js
@@ -493,16 +493,50 @@ CanvasFrameAnonymousContentHelper.protot
     }
   },
 
   get content() {
     if (Cu.isDeadWrapper(this._content)) {
       return null;
     }
     return this._content;
+  },
+
+  /**
+   * The canvasFrame anonymous content container gets zoomed in/out with the
+   * page. If this is unwanted, i.e. if you want the inserted element to remain
+   * unzoomed, then this method can be used.
+   *
+   * Consumers of the CanvasFrameAnonymousContentHelper should call this method,
+   * it isn't executed automatically. Typically, AutoRefreshHighlighter can call
+   * it when _update is executed.
+   *
+   * The matching element will be scaled down or up by 1/zoomLevel (using css
+   * transform) to cancel the current zoom. The element's width and height
+   * styles will also be set according to the scale. Finally, the element's
+   * position will be set as absolute.
+   *
+   * Note that if the matching element already has an inline style attribute, it
+   * *won't* be preserved.
+   *
+   * @param {DOMNode} node This node is used to determine which container window
+   * should be used to read the current zoom value.
+   * @param {String} id The ID of the root element inserted with this API.
+   */
+  scaleRootElement: function(node, id) {
+    let zoom = LayoutHelpers.getCurrentZoom(node);
+    let value = "position:absolute;width:100%;height:100%;";
+
+    if (zoom !== 1) {
+      value = "position:absolute;";
+      value += "transform-origin:top left;transform:scale(" + (1/zoom) + ");";
+      value += "width:" + (100*zoom) + "%;height:" + (100*zoom) + "%;";
+    }
+
+    this.setAttributeForElement(id, "style", value);
   }
 };
 
 /**
  * Base class for auto-refresh-on-change highlighters. Sub classes will have a
  * chance to update whenever the current node's geometry changes.
  *
  * Sub classes must implement the following methods:
@@ -694,45 +728,46 @@ AutoRefreshHighlighter.prototype = {
  * - hideInfoBar {Boolean}
  *   Defaults to false
  * - showOnly {String}
  *   "content", "padding", "border" or "margin"
  *    If set, only this region will be highlighted
  *
  * Structure:
  * <div class="highlighter-container">
- *   <svg class="box-model-root" hidden="true">
- *     <g class="box-model-container">
- *       <polygon class="box-model-margin" points="317,122 747,36 747,181 317,267" />
- *       <polygon class="box-model-border" points="317,128 747,42 747,161 317,247" />
- *       <polygon class="box-model-padding" points="323,127 747,42 747,161 323,246" />
- *       <polygon class="box-model-content" points="335,137 735,57 735,152 335,232" />
- *     </g>
- *     <line class="box-model-guide-top" x1="0" y1="592" x2="99999" y2="592" />
- *     <line class="box-model-guide-right" x1="735" y1="0" x2="735" y2="99999" />
- *     <line class="box-model-guide-bottom" x1="0" y1="612" x2="99999" y2="612" />
- *     <line class="box-model-guide-left" x1="334" y1="0" x2="334" y2="99999" />
- *   </svg>
- *   <div class="highlighter-nodeinfobar-container">
- *     <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
- *     <div class="highlighter-nodeinfobar">
- *       <div class="highlighter-nodeinfobar-text" align="center" flex="1">
- *         <span class="highlighter-nodeinfobar-tagname">Node name</span>
- *         <span class="highlighter-nodeinfobar-id">Node id</span>
- *         <span class="highlighter-nodeinfobar-classes">.someClass</span>
- *         <span class="highlighter-nodeinfobar-pseudo-classes">:hover</span>
+ *   <div class="box-model-root">
+ *     <svg class="box-model-elements" hidden="true">
+ *       <g class="box-model-regions">
+ *         <polygon class="box-model-margin" points="..." />
+ *         <polygon class="box-model-border" points="..." />
+ *         <polygon class="box-model-padding" points="..." />
+ *         <polygon class="box-model-content" points="..." />
+ *       </g>
+ *       <line class="box-model-guide-top" x1="..." y1="..." x2="..." y2="..." />
+ *       <line class="box-model-guide-right" x1="..." y1="..." x2="..." y2="..." />
+ *       <line class="box-model-guide-bottom" x1="..." y1="..." x2="..." y2="..." />
+ *       <line class="box-model-guide-left" x1="..." y1="..." x2="..." y2="..." />
+ *     </svg>
+ *     <div class="box-model-nodeinfobar-container">
+ *       <div class="box-model-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top" />
+ *       <div class="box-model-nodeinfobar">
+ *         <div class="box-model-nodeinfobar-text" align="center">
+ *           <span class="box-model-nodeinfobar-tagname">Node name</span>
+ *           <span class="box-model-nodeinfobar-id">Node id</span>
+ *           <span class="box-model-nodeinfobar-classes">.someClass</span>
+ *           <span class="box-model-nodeinfobar-pseudo-classes">:hover</span>
+ *         </div>
  *       </div>
+ *       <div class="box-model-nodeinfobar-arrow box-model-nodeinfobar-arrow-bottom"/>
  *     </div>
- *     <div class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
  *   </div>
  * </div>
  */
 function BoxModelHighlighter(tabActor) {
   AutoRefreshHighlighter.call(this, tabActor);
-  EventEmitter.decorate(this);
 
   this.markup = new CanvasFrameAnonymousContentHelper(this.tabActor,
     this._buildMarkup.bind(this));
 
   /**
    * Optionally customize each region's fill color by adding an entry to the
    * regionFill property: `highlighter.regionFill.margin = "red";
    */
@@ -759,112 +794,152 @@ BoxModelHighlighter.prototype = Heritage
   },
 
   _buildMarkup: function() {
     let doc = this.win.document;
 
     let highlighterContainer = doc.createElement("div");
     highlighterContainer.className = "highlighter-container";
 
+    // Build the root wrapper, used to adapt to the page zoom.
+    let rootWrapper = createNode(this.win, {
+      parent: highlighterContainer,
+      attributes: {
+        "id": "root",
+        "class": "root"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
     // Building the SVG element with its polygons and lines
 
-    let svgRoot = this._createSVGNode("svg", highlighterContainer, {
-      "id": "root",
-      "class": "root",
-      "width": "100%",
-      "height": "100%",
-      "style": "width:100%;height:100%;",
-      "hidden": "true"
+    let svg = createSVGNode(this.win, {
+      nodeType: "svg",
+      parent: rootWrapper,
+      attributes: {
+        "id": "elements",
+        "width": "100%",
+        "height": "100%",
+        "style": "width:100%;height:100%;",
+        "hidden": "true"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
-    let boxModelContainer = this._createSVGNode("g", svgRoot, {
-      "class": "container"
+    let regions = createSVGNode(this.win, {
+      nodeType: "g",
+      parent: svg,
+      attributes: {
+        "class": "regions"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
     for (let region of BOX_MODEL_REGIONS) {
-      this._createSVGNode("polygon", boxModelContainer, {
-        "class": region,
-        "id": region
+      createSVGNode(this.win, {
+        nodeType: "polygon",
+        parent: regions,
+        attributes: {
+          "class": region,
+          "id": region
+        },
+        prefix: this.ID_CLASS_PREFIX
       });
     }
 
     for (let side of BOX_MODEL_SIDES) {
-      this._createSVGNode("line", svgRoot, {
-        "class": "guide-" + side,
-        "id": "guide-" + side,
-        "stroke-width": GUIDE_STROKE_WIDTH
+      createSVGNode(this.win, {
+        nodeType: "line",
+        parent: svg,
+        attributes: {
+          "class": "guide-" + side,
+          "id": "guide-" + side,
+          "stroke-width": GUIDE_STROKE_WIDTH
+        },
+        prefix: this.ID_CLASS_PREFIX
       });
     }
 
-    highlighterContainer.appendChild(svgRoot);
-
     // Building the nodeinfo bar markup
 
-    let infobarContainer = this._createNode("div", highlighterContainer, {
-      "class": "nodeinfobar-container",
-      "id": "nodeinfobar-container",
-      "position": "top",
-      "hidden": "true"
+    let infobarContainer = createNode(this.win, {
+      parent: rootWrapper,
+      attributes: {
+        "class": "nodeinfobar-container",
+        "id": "nodeinfobar-container",
+        "position": "top",
+        "hidden": "true"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
-    let nodeInfobar = this._createNode("div", infobarContainer, {
-      "class": "nodeinfobar"
+    let nodeInfobar = createNode(this.win, {
+      parent: infobarContainer,
+      attributes: {
+        "class": "nodeinfobar"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
-    let texthbox = this._createNode("div", nodeInfobar, {
-      "class": "nodeinfobar-text"
+    let texthbox = createNode(this.win, {
+      parent: nodeInfobar,
+      attributes: {
+        "class": "nodeinfobar-text"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createNode("span", texthbox, {
-      "class": "nodeinfobar-tagname",
-      "id": "nodeinfobar-tagname"
+    createNode(this.win, {
+      nodeType: "span",
+      parent: texthbox,
+      attributes: {
+        "class": "nodeinfobar-tagname",
+        "id": "nodeinfobar-tagname"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createNode("span", texthbox, {
-      "class": "nodeinfobar-id",
-      "id": "nodeinfobar-id"
+    createNode(this.win, {
+      nodeType: "span",
+      parent: texthbox,
+      attributes: {
+        "class": "nodeinfobar-id",
+        "id": "nodeinfobar-id"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createNode("span", texthbox, {
-      "class": "nodeinfobar-classes",
-      "id": "nodeinfobar-classes"
+    createNode(this.win, {
+      nodeType: "span",
+      parent: texthbox,
+      attributes: {
+        "class": "nodeinfobar-classes",
+        "id": "nodeinfobar-classes"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createNode("span", texthbox, {
-      "class": "nodeinfobar-pseudo-classes",
-      "id": "nodeinfobar-pseudo-classes"
+    createNode(this.win, {
+      nodeType: "span",
+      parent: texthbox,
+      attributes: {
+        "class": "nodeinfobar-pseudo-classes",
+        "id": "nodeinfobar-pseudo-classes"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createNode("span", texthbox, {
-      "class": "nodeinfobar-dimensions",
-      "id": "nodeinfobar-dimensions"
+    createNode(this.win, {
+      nodeType: "span",
+      parent: texthbox,
+      attributes: {
+        "class": "nodeinfobar-dimensions",
+        "id": "nodeinfobar-dimensions"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
     return highlighterContainer;
   },
 
-  _createSVGNode: function(nodeType, parent, attributes={}) {
-    return this._createNode(nodeType, parent, attributes, SVG_NS);
-  },
-
-  _createNode: function(nodeType, parent, attributes={}, namespace=null) {
-    let node;
-    if (namespace) {
-      node = this.win.document.createElementNS(namespace, nodeType);
-    } else {
-      node = this.win.document.createElement(nodeType);
-    }
-
-    for (let name in attributes) {
-      let value = attributes[name];
-      if (name === "class" || name === "id") {
-        value = this.ID_CLASS_PREFIX + value
-      }
-      node.setAttribute(name, value);
-    }
-
-    parent.appendChild(node);
-    return node;
-  },
-
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
     AutoRefreshHighlighter.prototype.destroy.call(this);
 
     this.markup.destroy();
 
@@ -947,25 +1022,25 @@ BoxModelHighlighter.prototype = Heritage
       this.ID_CLASS_PREFIX + "nodeinfobar-container", "hidden");
     this._updateInfobar();
   },
 
   /**
    * Hide the box model
    */
   _hideBoxModel: function() {
-    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden",
-      "true");
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "elements",
+      "hidden", "true");
   },
 
   /**
    * Show the box model
    */
   _showBoxModel: function() {
-    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root",
+    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
       "hidden");
   },
 
   /**
    * Update the box model as per the current node.
    *
    * @return {boolean}
    *         True if the current node has a box model to be highlighted
@@ -997,16 +1072,20 @@ BoxModelHighlighter.prototype = Heritage
 
         if (boxType === this.options.region && !this.options.hideGuides) {
           this._showGuides(p1, p2, p3, p4);
         } else if (this.options.hideGuides) {
           this._hideGuides();
         }
       }
 
+      // Un-zoom the root wrapper if the page was zoomed.
+      let rootId = this.ID_CLASS_PREFIX + "root";
+      this.markup.scaleRootElement(this.currentNode, rootId);
+
       return true;
     }
 
     this._hideBoxModel();
     return false;
   },
 
   _nodeNeedsHighlighting: function() {
@@ -1250,86 +1329,113 @@ function CssTransformHighlighter(tabActo
 let MARKER_COUNTER = 1;
 
 CssTransformHighlighter.prototype = Heritage.extend(AutoRefreshHighlighter.prototype, {
   ID_CLASS_PREFIX: "css-transform-",
 
   _buildMarkup: function() {
     let doc = this.win.document;
 
-    let container = doc.createElement("div");
-    container.className = "highlighter-container";
+    let container = createNode(this.win, {
+      attributes: {
+        "class": "highlighter-container"
+      }
+    });
 
-    let svgRoot = this._createSVGNode("svg", container, {
-      "class": "root",
-      "id": "root",
-      "hidden": "true",
-      "width": "100%",
-      "height": "100%"
+    // The root wrapper is used to unzoom the highlighter when needed.
+    let rootWrapper = createNode(this.win, {
+      parent: container,
+      attributes: {
+        "id": "root",
+        "class": "root"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    let svg = createSVGNode(this.win, {
+      nodeType: "svg",
+      parent: rootWrapper,
+      attributes: {
+        "id": "elements",
+        "hidden": "true",
+        "width": "100%",
+        "height": "100%"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
     // Add a marker tag to the svg root for the arrow tip
     this.markerId = "arrow-marker-" + MARKER_COUNTER;
     MARKER_COUNTER ++;
-    let marker = this._createSVGNode("marker", svgRoot, {
-      "id": this.markerId,
-      "markerWidth": "10",
-      "markerHeight": "5",
-      "orient": "auto",
-      "markerUnits": "strokeWidth",
-      "refX": "10",
-      "refY": "5",
-      "viewBox": "0 0 10 10",
+    let marker = createSVGNode(this.win, {
+      nodeType: "marker",
+      parent: svg,
+      attributes: {
+        "id": this.markerId,
+        "markerWidth": "10",
+        "markerHeight": "5",
+        "orient": "auto",
+        "markerUnits": "strokeWidth",
+        "refX": "10",
+        "refY": "5",
+        "viewBox": "0 0 10 10"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createSVGNode("path", marker, {
-      "d": "M 0 0 L 10 5 L 0 10 z",
-      "fill": "#08C"
+    createSVGNode(this.win, {
+      nodeType: "path",
+      parent: marker,
+      attributes: {
+        "d": "M 0 0 L 10 5 L 0 10 z",
+        "fill": "#08C"
+      }
     });
 
-    let shapesGroup = this._createSVGNode("g", svgRoot);
+    let shapesGroup = createSVGNode(this.win, {
+      nodeType: "g",
+      parent: svg
+    });
 
     // Create the 2 polygons (transformed and untransformed)
-    this._createSVGNode("polygon", shapesGroup, {
-      "id": "untransformed",
-      "class": "untransformed"
+    createSVGNode(this.win, {
+      nodeType: "polygon",
+      parent: shapesGroup,
+      attributes: {
+        "id": "untransformed",
+        "class": "untransformed"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
-    this._createSVGNode("polygon", shapesGroup, {
-      "id": "transformed",
-      "class": "transformed"
+    createSVGNode(this.win, {
+      nodeType: "polygon",
+      parent: shapesGroup,
+      attributes: {
+        "id": "transformed",
+        "class": "transformed"
+      },
+      prefix: this.ID_CLASS_PREFIX
     });
 
     // Create the arrows
     for (let nb of ["1", "2", "3", "4"]) {
-      this._createSVGNode("line", shapesGroup, {
-        "id": "line" + nb,
-        "class": "line",
-        "marker-end": "url(#" + this.markerId + ")"
+      createSVGNode(this.win, {
+        nodeType: "line",
+        parent: shapesGroup,
+        attributes: {
+          "id": "line" + nb,
+          "class": "line",
+          "marker-end": "url(#" + this.markerId + ")"
+        },
+        prefix: this.ID_CLASS_PREFIX
       });
     }
 
-    container.appendChild(svgRoot);
-
     return container;
   },
 
-  _createSVGNode: function(nodeType, parent, attributes={}) {
-    let node = this.win.document.createElementNS(SVG_NS, nodeType);
-
-    for (let name in attributes) {
-      let value = attributes[name];
-      if (name === "class" || name === "id") {
-        value = this.ID_CLASS_PREFIX + value
-      }
-      node.setAttribute(name, value);
-    }
-
-    parent.appendChild(node);
-    return node;
-  },
-
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function() {
     AutoRefreshHighlighter.prototype.destroy.call(this);
     this.markup.destroy();
   },
 
@@ -1396,32 +1502,37 @@ CssTransformHighlighter.prototype = Heri
     let untransformedQuad = this.layoutHelpers.getNodeBounds(this.currentNode);
 
     this._setPolygonPoints(quad, "transformed");
     this._setPolygonPoints(untransformedQuad, "untransformed");
     for (let nb of ["1", "2", "3", "4"]) {
       this._setLinePoints(untransformedQuad["p" + nb], quad["p" + nb], "line" + nb);
     }
 
+    // Adapt to the current zoom
+    this.markup.scaleRootElement(this.currentNode, this.ID_CLASS_PREFIX + "root");
+
     this._showShapes();
   },
 
   /**
    * Hide the highlighter, the outline and the infobar.
    */
   _hide: function() {
     this._hideShapes();
   },
 
   _hideShapes: function() {
-    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden", "true");
+    this.markup.setAttributeForElement(this.ID_CLASS_PREFIX + "elements",
+      "hidden", "true");
   },
 
   _showShapes: function() {
-    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "root", "hidden");
+    this.markup.removeAttributeForElement(this.ID_CLASS_PREFIX + "elements",
+      "hidden");
   }
 });
 
 /**
  * The SelectorHighlighter runs a given selector through querySelectorAll on the
  * document of the provided context node and then uses the BoxModelHighlighter
  * to highlight the matching nodes
  */
@@ -1651,11 +1762,68 @@ function installHelperSheet(win, source,
 
 /**
  * Is the content window in this tabActor a XUL window
  */
 function isXUL(tabActor) {
   return tabActor.window.document.documentElement.namespaceURI === XUL_NS;
 }
 
+/**
+ * Helper function that creates SVG DOM nodes.
+ * @param {Window} This window's document will be used to create the element
+ * @param {Object} Options for the node include:
+ * - nodeType: the type of node, defaults to "box".
+ * - attributes: a {name:value} object to be used as attributes for the node.
+ * - prefix: a string that will be used to prefix the values of the id and class
+ *   attributes.
+ * - parent: if provided, the newly created element will be appended to this
+ *   node.
+ */
+function createSVGNode(win, options) {
+  if (!options.nodeType) {
+    options.nodeType = "box";
+  }
+  options.namespace = SVG_NS;
+  return createNode(win, options);
+}
+
+/**
+ * Helper function that creates DOM nodes.
+ * @param {Window} This window's document will be used to create the element
+ * @param {Object} Options for the node include:
+ * - nodeType: the type of node, defaults to "div".
+ * - namespace: if passed, doc.createElementNS will be used instead of
+ *   doc.creatElement.
+ * - attributes: a {name:value} object to be used as attributes for the node.
+ * - prefix: a string that will be used to prefix the values of the id and class
+ *   attributes.
+ * - parent: if provided, the newly created element will be appended to this
+ *   node.
+ */
+function createNode(win, options) {
+  let type = options.nodeType || "div";
+
+  let node;
+  if (options.namespace) {
+    node = win.document.createElementNS(options.namespace, type);
+  } else {
+    node = win.document.createElement(type);
+  }
+
+  for (let name in options.attributes || {}) {
+    let value = options.attributes[name];
+    if (options.prefix && (name === "class" || name === "id")) {
+      value = options.prefix + value
+    }
+    node.setAttribute(name, value);
+  }
+
+  if (options.parent) {
+    options.parent.appendChild(node);
+  }
+
+  return node;
+}
+
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
 });
--- a/widget/gonk/libdisplay/FramebufferSurface.cpp
+++ b/widget/gonk/libdisplay/FramebufferSurface.cpp
@@ -44,27 +44,30 @@
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
 /*
  * This implements the (main) framebuffer management. This class
  * was adapted from the version in SurfaceFlinger
  */
-FramebufferSurface::FramebufferSurface(int disp, uint32_t width, uint32_t height, uint32_t format,
-        sp<BufferQueue>& bq) :
+FramebufferSurface::FramebufferSurface(int disp,
+                                       uint32_t width,
+                                       uint32_t height,
+                                       uint32_t format,
+                                       const sp<StreamConsumer>& sc)
 #if ANDROID_VERSION >= 19
-    ConsumerBase(bq, true),
+    : ConsumerBase(sc, true)
 #else
-    ConsumerBase(bq),
+    : ConsumerBase(sc)
 #endif
-    mDisplayType(disp),
-    mCurrentBufferSlot(-1),
-    mCurrentBuffer(0),
-    lastHandle(0)
+    , mDisplayType(disp)
+    , mCurrentBufferSlot(-1)
+    , mCurrentBuffer(0)
+    , lastHandle(0)
 {
     mName = "FramebufferSurface";
 
 #if ANDROID_VERSION >= 19
     sp<IGraphicBufferConsumer> consumer = mConsumer;
 #else
     sp<BufferQueue> consumer = mBufferQueue;
     consumer->setSynchronousMode(true);
@@ -108,17 +111,17 @@ status_t FramebufferSurface::nextBuffer(
         // Release the previous buffer.
 #if ANDROID_VERSION >= 19
         err = releaseBufferLocked(mCurrentBufferSlot, mCurrentBuffer,
                 EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
 #else
         err = releaseBufferLocked(mCurrentBufferSlot, EGL_NO_DISPLAY,
                 EGL_NO_SYNC_KHR);
 #endif
-        if (err != NO_ERROR && err != BufferQueue::STALE_BUFFER_SLOT) {
+        if (err != NO_ERROR && err != StreamConsumer::STALE_BUFFER_SLOT) {
             ALOGE("error releasing buffer: %s (%d)", strerror(-err), err);
             return err;
         }
     }
     mCurrentBufferSlot = item.mBuf;
     mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer;
     outFence = item.mFence;
     outBuffer = mCurrentBuffer;
--- a/widget/gonk/libdisplay/FramebufferSurface.h
+++ b/widget/gonk/libdisplay/FramebufferSurface.h
@@ -25,21 +25,27 @@
 // ---------------------------------------------------------------------------
 namespace android {
 // ---------------------------------------------------------------------------
 
 class Rect;
 class String8;
 class HWComposer;
 
+#if ANDROID_VERSION >= 21
+typedef IGraphicBufferConsumer StreamConsumer;
+#else
+typedef BufferQueue StreamConsumer;
+#endif
+
 // ---------------------------------------------------------------------------
 
 class FramebufferSurface : public ConsumerBase {
 public:
-    FramebufferSurface(int disp, uint32_t width, uint32_t height, uint32_t format, sp<BufferQueue>& bq);
+    FramebufferSurface(int disp, uint32_t width, uint32_t height, uint32_t format, const sp<StreamConsumer>& sc);
 
     bool isUpdateOnDemand() const { return false; }
     status_t setUpdateRectangle(const Rect& updateRect);
     status_t compositionComplete();
 
     virtual void dump(String8& result);
     virtual void dump(String8& result, const char* prefix);
 
--- a/widget/gonk/libdisplay/GonkDisplayJB.cpp
+++ b/widget/gonk/libdisplay/GonkDisplayJB.cpp
@@ -101,32 +101,43 @@ GonkDisplayJB::GonkDisplayJB()
                                            (hw_module_t const**)&mPowerModule);
     if (!err)
         mPowerModule->init(mPowerModule);
     ALOGW_IF(err, "Couldn't load %s module (%s)", POWER_HARDWARE_MODULE_ID, strerror(-err));
 
     mAlloc = new GraphicBufferAlloc();
 
     status_t error;
-
-#if ANDROID_VERSION >= 19
-    sp<BufferQueue> bq = new BufferQueue(mAlloc);
+#if ANDROID_VERSION >= 21
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer, mAlloc);
+#elif ANDROID_VERSION >= 19
+    sp<BufferQueue> consumer = new BufferQueue(mAlloc);
+    sp<IGraphicBufferProducer> producer = consumer;
+#elif ANDROID_VERSION >= 18
+    sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
+    sp<IGraphicBufferProducer> producer = consumer;
 #else
-    sp<BufferQueue> bq = new BufferQueue(true, mAlloc);
+    sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
 #endif
-    mFBSurface = new FramebufferSurface(0, mWidth, mHeight, surfaceformat, bq);
+    mFBSurface = new FramebufferSurface(0, mWidth, mHeight, surfaceformat, consumer);
 
 #if ANDROID_VERSION == 17
-    sp<SurfaceTextureClient> stc = new SurfaceTextureClient(static_cast<sp<ISurfaceTexture> >(mFBSurface->getBufferQueue()));
+    sp<SurfaceTextureClient> stc = new SurfaceTextureClient(
+        static_cast<sp<ISurfaceTexture> >(mFBSurface->getBufferQueue()));
 #else
-    sp<Surface> stc = new Surface(static_cast<sp<IGraphicBufferProducer> >(bq));
+    sp<Surface> stc = new Surface(producer);
 #endif
     mSTClient = stc;
     mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_BUFFER_COUNT, 2);
-    mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_USAGE, GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER);
+    mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_USAGE,
+                                        GRALLOC_USAGE_HW_FB |
+                                        GRALLOC_USAGE_HW_RENDER |
+                                        GRALLOC_USAGE_HW_COMPOSER);
 
     mList = (hwc_display_contents_1_t *)malloc(sizeof(*mList) + (sizeof(hwc_layer_1_t)*2));
     if (mHwc)
         mHwc->blank(mHwc, HWC_DISPLAY_PRIMARY, 0);
 
     if (error == NO_ERROR) {
         ALOGI("Starting bootanimation with (%d) format framebuffer", surfaceformat);
         StartBootAnimation();