Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Sep 2015 12:07:23 +0200
changeset 264285 4d86ae08548baf2cd2cab3e4e58084dd912cfad3
parent 264284 5aeeb7979d4fbcc7c7efd93d7068b8fdd180643d (current diff)
parent 264199 001942e4617b2324bfa6cdfb1155581cbc3f0cc4 (diff)
child 264286 0f1f7daec31f73372eb11b47157d557a553bc8cf
push id65590
push userkwierso@gmail.com
push dateFri, 25 Sep 2015 00:14:23 +0000
treeherdermozilla-inbound@0ab67cace54f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone44.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 mozilla-central to fx-team
browser/components/extensions/test/browser/browser_extensions_simple.js
ipc/glue/SharedMemoryBasic_mach.cpp
toolkit/components/extensions/test/mochitest/file_contentscript_page1.html
toolkit/components/extensions/test/mochitest/test_extension_contentscript.html
toolkit/components/extensions/test/mochitest/test_extension_webrequest.html
toolkit/components/extensions/test/mochitest/test_generate_extension.html
toolkit/components/extensions/test/mochitest/test_sandbox_var.html
toolkit/components/extensions/test/mochitest/test_simple_extensions.html
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12ff7481566587aa4198cf1287598acb3a999973"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12ff7481566587aa4198cf1287598acb3a999973"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "8472f0c736660072799aaae60e4b6001a6aaceb4", 
+        "git_revision": "4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "e4b5ba76d5a4de0cd220310e0fe2ba334f0e250a", 
+    "revision": "0bdd0b54cb40d7e928e9e6de720c0506dc7e52db", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="4bb17b24620818cbda0ba0c0d69e0ce3f914e1b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -9,16 +9,21 @@ var {
   DefaultWeakMap,
   ignoreEvent,
   runSafe,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> BrowserAction]
 var browserActionMap = new WeakMap();
 
+// WeakMap[Extension -> docshell]
+// This map is a cache of the windowless browser that's used to render ImageData
+// for the browser_action icon.
+let imageRendererMap = new WeakMap();
+
 function browserActionOf(extension)
 {
   return browserActionMap.get(extension);
 }
 
 function makeWidgetId(id)
 {
   id = id.toLowerCase();
@@ -36,19 +41,16 @@ function BrowserAction(options, extensio
   this.widget = null;
 
   this.title = new DefaultWeakMap(extension.localize(options.default_title));
   this.badgeText = new DefaultWeakMap();
   this.badgeBackgroundColor = new DefaultWeakMap();
   this.icon = new DefaultWeakMap(options.default_icon);
   this.popup = new DefaultWeakMap(options.default_popup);
 
-  // Make the default something that won't compare equal to anything.
-  this.prevPopups = new DefaultWeakMap({});
-
   this.context = null;
 }
 
 BrowserAction.prototype = {
   build() {
     let widget = CustomizableUI.createWidget({
       id: this.id,
       type: "custom",
@@ -58,32 +60,109 @@ BrowserAction.prototype = {
         let node = document.createElement("toolbarbutton");
         node.id = this.id;
         node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
         node.setAttribute("constrain-size", "true");
 
         this.updateTab(null, node);
 
         let tabbrowser = document.defaultView.gBrowser;
-        tabbrowser.ownerDocument.addEventListener("TabSelect", () => {
-          this.updateTab(tabbrowser.selectedTab, node);
-        });
+        tabbrowser.tabContainer.addEventListener("TabSelect", this);
 
         node.addEventListener("command", event => {
-          if (node.getAttribute("type") != "panel") {
+          let tab = tabbrowser.selectedTab;
+          let popup = this.getProperty(tab, "popup");
+          if (popup) {
+            this.togglePopup(node, popup);
+          } else {
             this.emit("click");
           }
         });
 
         return node;
       },
     });
     this.widget = widget;
   },
 
+  handleEvent(event) {
+    if (event.type == "TabSelect") {
+      let window = event.target.ownerDocument.defaultView;
+      let tabbrowser = window.gBrowser;
+      let instance = CustomizableUI.getWidget(this.id).forWindow(window);
+      if (instance) {
+        this.updateTab(tabbrowser.selectedTab, instance.node);
+      }
+    }
+  },
+
+  togglePopup(node, popupResource) {
+    let popupURL = this.extension.baseURI.resolve(popupResource);
+
+    let document = node.ownerDocument;
+    let panel = document.createElement("panel");
+    panel.setAttribute("class", "browser-action-panel");
+    panel.setAttribute("type", "arrow");
+    panel.setAttribute("flip", "slide");
+    node.appendChild(panel);
+
+    panel.addEventListener("popuphidden", () => {
+      this.context.unload();
+      this.context = null;
+      panel.remove();
+    });
+
+    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+    let browser = document.createElementNS(XUL_NS, "browser");
+    browser.setAttribute("type", "content");
+    browser.setAttribute("disableglobalhistory", "true");
+    panel.appendChild(browser);
+
+    let loadListener = () => {
+      panel.removeEventListener("load", loadListener);
+
+      this.context = new ExtensionPage(this.extension, {
+        type: "popup",
+        contentWindow: browser.contentWindow,
+        uri: Services.io.newURI(popupURL, null, null),
+        docShell: browser.docShell,
+      });
+      GlobalManager.injectInDocShell(browser.docShell, this.extension, this.context);
+      browser.setAttribute("src", popupURL);
+
+      let contentLoadListener = () => {
+        browser.removeEventListener("load", contentLoadListener);
+
+        let contentViewer = browser.docShell.contentViewer;
+        let width = {}, height = {};
+        try {
+          contentViewer.getContentSize(width, height);
+          [width, height] = [width.value, height.value];
+        } catch (e) {
+          // getContentSize can throw
+          [width, height] = [400, 400];
+        }
+
+        let window = document.defaultView;
+        width /= window.devicePixelRatio;
+        height /= window.devicePixelRatio;
+        width = Math.min(width, 800);
+        height = Math.min(height, 800);
+
+        browser.setAttribute("width", width);
+        browser.setAttribute("height", height);
+
+        let anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon");
+        panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+      };
+      browser.addEventListener("load", contentLoadListener, true);
+    };
+    panel.addEventListener("load", loadListener);
+  },
+
   // Initialize the toolbar icon and popup given that |tab| is the
   // current tab and |node| is the CustomizableUI node. Note: |tab|
   // will be null if we don't know the current tab yet (during
   // initialization).
   updateTab(tab, node) {
     let window = node.ownerDocument.defaultView;
 
     let title = this.getProperty(tab, "title");
@@ -113,67 +192,16 @@ BrowserAction.prototype = {
       if (Array.isArray(color)) {
         color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
       }
       badgeNode.style.backgroundColor = color;
     }
 
     let iconURL = this.getIcon(tab, node);
     node.setAttribute("image", iconURL);
-
-    let popup = this.getProperty(tab, "popup");
-
-    if (popup != this.prevPopups.get(window)) {
-      this.prevPopups.set(window, popup);
-
-      let panel = node.querySelector("panel");
-      if (panel) {
-        panel.remove();
-      }
-
-      if (popup) {
-        let popupURL = this.extension.baseURI.resolve(popup);
-        node.setAttribute("type", "panel");
-
-        let document = node.ownerDocument;
-        let panel = document.createElement("panel");
-        panel.setAttribute("class", "browser-action-panel");
-        panel.setAttribute("type", "arrow");
-        panel.setAttribute("flip", "slide");
-        node.appendChild(panel);
-
-        const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-        let browser = document.createElementNS(XUL_NS, "browser");
-        browser.setAttribute("type", "content");
-        browser.setAttribute("disableglobalhistory", "true");
-        browser.setAttribute("width", "500");
-        browser.setAttribute("height", "500");
-        panel.appendChild(browser);
-
-        let loadListener = () => {
-          panel.removeEventListener("load", loadListener);
-
-          if (this.context) {
-            this.context.unload();
-          }
-
-          this.context = new ExtensionPage(this.extension, {
-            type: "popup",
-            contentWindow: browser.contentWindow,
-            uri: Services.io.newURI(popupURL, null, null),
-            docShell: browser.docShell,
-          });
-          GlobalManager.injectInDocShell(browser.docShell, this.extension, this.context);
-          browser.setAttribute("src", popupURL);
-        };
-        panel.addEventListener("load", loadListener);
-      } else {
-        node.removeAttribute("type");
-      }
-    }
   },
 
   // Note: tab is allowed to be null here.
   getIcon(tab, node) {
     let icon = this.icon.get(tab);
 
     let url;
     if (typeof(icon) != "object") {
@@ -231,16 +259,23 @@ BrowserAction.prototype = {
 
   // tab is allowed to be null.
   // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
   getProperty(tab, prop) {
     return this[prop].get(tab);
   },
 
   shutdown() {
+    let widget = CustomizableUI.getWidget(this.id);
+    for (let instance of widget.instances) {
+      let window = instance.node.ownerDocument.defaultView;
+      let tabbrowser = window.gBrowser;
+      tabbrowser.tabContainer.removeEventListener("TabSelect", this);
+    }
+
     CustomizableUI.destroyWidget(this.id);
   },
 };
 
 EventEmitter.decorate(BrowserAction.prototype);
 
 extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
   let browserAction = new BrowserAction(manifest.browser_action, extension);
@@ -248,18 +283,47 @@ extensions.on("manifest_browser_action",
   browserActionMap.set(extension, browserAction);
 });
 
 extensions.on("shutdown", (type, extension) => {
   if (browserActionMap.has(extension)) {
     browserActionMap.get(extension).shutdown();
     browserActionMap.delete(extension);
   }
+  imageRendererMap.delete(extension);
 });
 
+function convertImageDataToPNG(extension, imageData)
+{
+  let webNav = imageRendererMap.get(extension);
+  if (!webNav) {
+    webNav = Services.appShell.createWindowlessBrowser(false);
+    let principal = Services.scriptSecurityManager.createCodebasePrincipal(extension.baseURI,
+                                                                           {addonId: extension.id});
+    let interfaceRequestor = webNav.QueryInterface(Ci.nsIInterfaceRequestor);
+    let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
+
+    GlobalManager.injectInDocShell(docShell, extension, null);
+
+    docShell.createAboutBlankContentViewer(principal);
+  }
+
+  let document = webNav.document;
+  let canvas = document.createElement("canvas");
+  canvas.width = imageData.width;
+  canvas.height = imageData.height;
+  canvas.getContext("2d").putImageData(imageData, 0, 0);
+
+  let url = canvas.toDataURL("image/png");
+
+  canvas.remove();
+
+  return url;
+}
+
 extensions.registerAPI((extension, context) => {
   return {
     browserAction: {
       onClicked: new EventManager(context, "browserAction.onClicked", fire => {
         let listener = () => {
           let tab = TabManager.activeTab;
           fire(TabManager.convert(extension, tab));
         };
@@ -278,20 +342,21 @@ extensions.registerAPI((extension, conte
         let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
         let title = browserActionOf(extension).getProperty(tab, "title");
         runSafe(context, callback, title);
       },
 
       setIcon: function(details, callback) {
         let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
         if (details.imageData) {
-          // FIXME: Support the imageData attribute.
-          return;
+          let url = convertImageDataToPNG(extension, details.imageData);
+          browserActionOf(extension).setProperty(tab, "icon", url);
+        } else {
+          browserActionOf(extension).setProperty(tab, "icon", details.path);
         }
-        browserActionOf(extension).setProperty(tab, "icon", details.path);
       },
 
       setBadgeText: function(details) {
         let tab = details.tabId ? TabManager.getTab(details.tabId) : null;
         browserActionOf(extension).setProperty(tab, "badgeText", details.text);
       },
 
       getBadgeText: function(details, callback) {
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -37,51 +37,69 @@ function getSender(context, target, send
 // WeakMap[ExtensionPage -> {tab, parentWindow}]
 var pageDataMap = new WeakMap();
 
 // This listener fires whenever an extension page opens in a tab
 // (either initiated by the extension or the user). Its job is to fill
 // in some tab-specific details and keep data around about the
 // ExtensionPage.
 extensions.on("page-load", (type, page, params, sender, delegate) => {
-  if (params.type == "tab") {
+  if (params.type == "tab" || params.type == "popup") {
     let browser = params.docShell.chromeEventHandler;
+
     let parentWindow = browser.ownerDocument.defaultView;
-    let tab = parentWindow.gBrowser.getTabForBrowser(browser);
-    sender.tabId = TabManager.getId(tab);
+    page.windowId = WindowManager.getId(parentWindow);
+
+    let tab = null;
+    if (params.type == "tab") {
+      tab = parentWindow.gBrowser.getTabForBrowser(browser);
+      sender.tabId = TabManager.getId(tab);
+      page.tabId = TabManager.getId(tab);
+    }
 
     pageDataMap.set(page, {tab, parentWindow});
   }
 
   delegate.getSender = getSender;
 });
 
 extensions.on("page-unload", (type, page) => {
   pageDataMap.delete(page);
 });
 
 extensions.on("page-shutdown", (type, page) => {
   if (pageDataMap.has(page)) {
     let {tab, parentWindow} = pageDataMap.get(page);
     pageDataMap.delete(page);
 
-    parentWindow.gBrowser.removeTab(tab);
+    if (tab) {
+      parentWindow.gBrowser.removeTab(tab);
+    }
   }
 });
 
 extensions.on("fill-browser-data", (type, browser, data, result) => {
   let tabId = TabManager.getBrowserId(browser);
   if (tabId == -1) {
     result.cancel = true;
     return;
   }
 
   data.tabId = tabId;
 });
 
+global.currentWindow = function(context)
+{
+  let pageData = pageDataMap.get(context);
+  if (pageData) {
+    return pageData.parentWindow;
+  }
+  return WindowManager.topWindow;
+}
+
 // TODO: activeTab permission
 
 extensions.registerAPI((extension, context) => {
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
@@ -252,17 +270,17 @@ extensions.registerAPI((extension, conte
             window.gBrowser.pinTab(tab);
           }
 
           if (callback) {
             runSafe(context, callback, TabManager.convert(extension, tab));
           }
         }
 
-        let window = createProperties.windowId ?
+        let window = "windowId" in createProperties ?
           WindowManager.getWindow(createProperties.windowId) :
           WindowManager.topWindow;
         if (!window.gBrowser) {
           let obs = (finishedWindow, topic, data) => {
             if (finishedWindow != window) {
               return;
             }
             Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
@@ -377,40 +395,43 @@ extensions.registerAPI((extension, conte
 
           let windowType = WindowManager.windowType(window);
           if ("windowType" in queryInfo && queryInfo.windowType != windowType) {
             return false;
           }
 
           if ("windowId" in queryInfo) {
             if (queryInfo.windowId == WindowManager.WINDOW_ID_CURRENT) {
-              if (context.contentWindow != window) {
+              if (currentWindow(context) != window) {
                 return false;
               }
             } else {
               if (queryInfo.windowId != tab.windowId) {
                 return false;
               }
             }
           }
 
           if ("currentWindow" in queryInfo) {
-            let eq = window == context.contentWindow;
+            let eq = window == currentWindow(context);
             if (queryInfo.currentWindow != eq) {
               return false;
             }
           }
 
           return true;
         }
 
         let result = [];
         let e = Services.wm.getEnumerator("navigator:browser");
         while (e.hasMoreElements()) {
           let window = e.getNext();
+          if (window.document.readyState != "complete") {
+            continue;
+          }
           let tabs = TabManager.getTabs(extension, window);
           for (let tab of tabs) {
             if (matches(window, tab)) {
               result.push(tab);
             }
           }
         }
         runSafe(context, callback, result);
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -40,17 +40,21 @@ extensions.registerAPI((extension, conte
       }).api(),
 
       get: function(windowId, getInfo, callback) {
         let window = WindowManager.getWindow(windowId);
         runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
       },
 
       getCurrent: function(getInfo, callback) {
-        let window = context.contentWindow;
+        if (!callback) {
+          callback = getInfo;
+          getInfo = {};
+        }
+        let window = currentWindow(context);
         runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
       },
 
       getLastFocused: function(...args) {
         let getInfo, callback;
         if (args.length == 1) {
           callback = args[0];
         } else {
@@ -60,17 +64,19 @@ extensions.registerAPI((extension, conte
         runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
       },
 
       getAll: function(getInfo, callback) {
         let e = Services.wm.getEnumerator("navigator:browser");
         let windows = [];
         while (e.hasMoreElements()) {
           let window = e.getNext();
-          windows.push(WindowManager.convert(extension, window, getInfo));
+          if (window.document.readyState == "complete") {
+            windows.push(WindowManager.convert(extension, window, getInfo));
+          }
         }
         runSafe(context, callback, windows);
       },
 
       create: function(createData, callback) {
         function mkstr(s) {
           let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
           result.data = s;
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -1,9 +1,14 @@
 [DEFAULT]
 skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
+support-files =
+  head.js
 
-[browser_extensions_simple.js]
+[browser_ext_simple.js]
+[browser_ext_currentWindow.js]
 [browser_ext_browserAction_simple.js]
+[browser_ext_browserAction_icon.js]
+[browser_ext_getViews.js]
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_update.js]
 [browser_ext_windows_update.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_icon.js
@@ -0,0 +1,40 @@
+add_task(function* () {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "browser_action": {},
+      "background": {
+        "page": "background.html",
+      }
+    },
+
+    files: {
+      "background.html": `<canvas id="canvas" width="2" height="2">
+        <script src="background.js"></script>`,
+
+      "background.js": function() {
+        var canvas = document.getElementById("canvas");
+        var canvasContext = canvas.getContext("2d");
+
+        canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+        canvasContext.fillStyle = "green";
+        canvasContext.fillRect(0, 0, 1, 1);
+
+        var url = canvas.toDataURL("image/png");
+        var imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height);
+        browser.browserAction.setIcon({imageData});
+
+        browser.test.sendMessage("imageURL", url);
+      }
+    },
+  });
+
+  let [_, url] = yield Promise.all([extension.startup(), extension.awaitMessage("imageURL")]);
+
+  let widgetId = makeWidgetId(extension.id) + "-browser-action";
+  let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
+
+  let image = node.getAttribute("image");
+  is(image, url, "image is correct");
+
+  yield extension.unload();
+});
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
@@ -17,20 +17,36 @@ add_task(function* () {
       "popup.js": function() {
         browser.runtime.sendMessage("from-popup");
       }
     },
 
     background: function() {
       browser.runtime.onMessage.addListener(msg => {
         browser.test.assertEq(msg, "from-popup", "correct message received");
-        browser.test.notifyPass("browser_action.simple");
+        browser.test.sendMessage("popup");
       });
     },
   });
 
   yield extension.startup();
 
-  // FIXME: Should really test opening the popup here.
+  let widgetId = makeWidgetId(extension.id) + "-browser-action";
+  let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
 
-  yield extension.awaitFinish("browser_action.simple");
+  // Do this a few times to make sure the pop-up is reloaded each time.
+  for (let i = 0; i < 3; i++) {
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true
+    });
+    node.dispatchEvent(evt);
+
+    yield extension.awaitMessage("popup");
+
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
+  }
+
   yield extension.unload();
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -0,0 +1,173 @@
+function* focusWindow(win)
+{
+  let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+  if (fm.activeWindow == win) {
+    return;
+  }
+
+  let promise = new Promise(resolve => {
+    win.addEventListener("focus", function listener() {
+      win.removeEventListener("focus", listener, true);
+      resolve();
+    }, true);
+  });
+
+  win.focus();
+  yield promise;
+}
+
+function genericChecker()
+{
+  var kind = "background";
+  var path = window.location.pathname;
+  if (path.indexOf("popup") != -1) {
+    kind = "popup";
+  } else if (path.indexOf("page") != -1) {
+    kind = "page";
+  }
+
+  browser.test.onMessage.addListener((msg, ...args) => {
+    if (msg == kind + "-check-current1") {
+      browser.tabs.query({
+        currentWindow: true
+      }, function(tabs) {
+        browser.test.sendMessage("result", tabs[0].windowId);
+      });
+    } else if (msg == kind + "-check-current2") {
+      browser.tabs.query({
+        windowId: browser.windows.WINDOW_ID_CURRENT
+      }, function(tabs) {
+        browser.test.sendMessage("result", tabs[0].windowId);
+      });
+    } else if (msg == kind + "-check-current3") {
+      browser.windows.getCurrent(function(window) {
+        browser.test.sendMessage("result", window.id);
+      });
+    } else if (msg == kind + "-open-page") {
+      browser.tabs.create({windowId: args[0], url: chrome.runtime.getURL("page.html")});
+    } else if (msg == kind + "-close-page") {
+      browser.tabs.query({
+        windowId: args[0],
+      }, tabs => {
+        var tab = tabs.find(tab => tab.url.indexOf("page.html") != -1);
+        browser.tabs.remove(tab.id, () => {
+          browser.test.sendMessage("closed");
+        });
+      });
+    }
+  });
+  browser.test.sendMessage(kind + "-ready");
+}
+
+add_task(function* () {
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  yield focusWindow(win2);
+
+  yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
+  yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser);
+
+  yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:config");
+  yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser);
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+
+      "browser_action": {
+        "default_popup": "popup.html"
+      },
+    },
+
+    files: {
+      "page.html": `
+      <!DOCTYPE html>
+      <html><body>
+      <script src="page.js"></script>
+      </body></html>
+      `,
+
+      "page.js": genericChecker,
+
+      "popup.html": `
+      <!DOCTYPE html>
+      <html><body>
+      <script src="popup.js"></script>
+      </body></html>
+      `,
+
+      "popup.js": genericChecker,
+    },
+
+    background: genericChecker,
+  });
+
+  yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
+
+  let {TabManager, WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+  let winId1 = WindowManager.getId(win1);
+  let winId2 = WindowManager.getId(win2);
+
+  function* checkWindow(kind, winId, name) {
+    extension.sendMessage(kind + "-check-current1");
+    is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
+    extension.sendMessage(kind + "-check-current2");
+    is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
+    extension.sendMessage(kind + "-check-current3");
+    is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
+  }
+
+  yield focusWindow(win1);
+  yield checkWindow("background", winId1, "win1");
+  yield focusWindow(win2);
+  yield checkWindow("background", winId2, "win2");
+
+  function* triggerPopup(win, callback) {
+    let widgetId = makeWidgetId(extension.id) + "-browser-action";
+    let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
+
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true
+    });
+    node.dispatchEvent(evt);
+
+    yield extension.awaitMessage("popup-ready");
+
+    yield callback();
+
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
+  }
+
+  // Set focus to some other window.
+  yield focusWindow(window);
+
+  yield triggerPopup(win1, function*() {
+    yield checkWindow("popup", winId1, "win1");
+  });
+
+  yield triggerPopup(win2, function*() {
+    yield checkWindow("popup", winId2, "win2");
+  });
+
+  function* triggerPage(winId, name) {
+    extension.sendMessage("background-open-page", winId);
+    yield extension.awaitMessage("page-ready");
+    yield checkWindow("page", winId, name);
+    extension.sendMessage("background-close-page", winId);
+    yield extension.awaitMessage("closed");
+  }
+
+  yield triggerPage(winId1, "win1");
+  yield triggerPage(winId2, "win2");
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -0,0 +1,178 @@
+function genericChecker()
+{
+  var kind = "background";
+  var path = window.location.pathname;
+  if (path.indexOf("popup") != -1) {
+    kind = "popup";
+  } else if (path.indexOf("tab") != -1) {
+    kind = "tab";
+  }
+  window.kind = kind;
+
+  browser.test.onMessage.addListener((msg, ...args) => {
+    if (msg == kind + "-check-views") {
+      var views = browser.extension.getViews();
+      var counts = {
+        "background": 0,
+        "tab": 0,
+        "popup": 0
+      };
+      for (var i = 0; i < views.length; i++) {
+        var view = views[i];
+        browser.test.assertTrue(view.kind in counts, "view type is valid");
+        counts[view.kind]++;
+        if (view.kind == "background") {
+          browser.test.assertTrue(view === browser.extension.getBackgroundPage(),
+                                  "background page is correct");
+        }
+      }
+      browser.test.sendMessage("counts", counts);
+    } else if (msg == kind + "-open-tab") {
+      browser.tabs.create({windowId: args[0], url: chrome.runtime.getURL("tab.html")});
+    } else if (msg == kind + "-close-tab") {
+      browser.tabs.query({
+        windowId: args[0],
+      }, tabs => {
+        var tab = tabs.find(tab => tab.url.indexOf("tab.html") != -1);
+        browser.tabs.remove(tab.id, () => {
+          browser.test.sendMessage("closed");
+        });
+      });
+    }
+  });
+  browser.test.sendMessage(kind + "-ready");
+}
+
+add_task(function* () {
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+
+      "browser_action": {
+        "default_popup": "popup.html"
+      },
+    },
+
+    files: {
+      "tab.html": `
+      <!DOCTYPE html>
+      <html><body>
+      <script src="tab.js"></script>
+      </body></html>
+      `,
+
+      "tab.js": genericChecker,
+
+      "popup.html": `
+      <!DOCTYPE html>
+      <html><body>
+      <script src="popup.js"></script>
+      </body></html>
+      `,
+
+      "popup.js": genericChecker,
+    },
+
+    background: genericChecker,
+  });
+
+  yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
+
+  info("started");
+
+  let {TabManager, WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+  let winId1 = WindowManager.getId(win1);
+  let winId2 = WindowManager.getId(win2);
+
+  function* openTab(winId) {
+    extension.sendMessage("background-open-tab", winId);
+    yield extension.awaitMessage("tab-ready");
+  }
+
+  function* checkViews(kind, tabCount, popupCount) {
+    extension.sendMessage(kind + "-check-views");
+    let counts = yield extension.awaitMessage("counts");
+    is(counts.background, 1, "background count correct");
+    is(counts.tab, tabCount, "tab count correct");
+    is(counts.popup, popupCount, "popup count correct");
+  }
+
+  yield checkViews("background", 0, 0);
+
+  yield openTab(winId1);
+
+  yield checkViews("background", 1, 0);
+  yield checkViews("tab", 1, 0);
+
+  yield openTab(winId2);
+
+  yield checkViews("background", 2, 0);
+
+  function* triggerPopup(win, callback) {
+    let widgetId = makeWidgetId(extension.id) + "-browser-action";
+    let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
+
+    let evt = new CustomEvent("command", {
+      bubbles: true,
+      cancelable: true
+    });
+    node.dispatchEvent(evt);
+
+    yield extension.awaitMessage("popup-ready");
+
+    yield callback();
+
+    let panel = node.querySelector("panel");
+    if (panel) {
+      panel.hidePopup();
+    }
+  }
+
+  yield triggerPopup(win1, function*() {
+    yield checkViews("background", 2, 1);
+    yield checkViews("popup", 2, 1);
+  });
+
+  yield triggerPopup(win2, function*() {
+    yield checkViews("background", 2, 1);
+    yield checkViews("popup", 2, 1);
+  });
+
+  info("checking counts after popups");
+
+  yield checkViews("background", 2, 0);
+
+  info("closing one tab");
+
+  extension.sendMessage("background-close-tab", winId1);
+  yield extension.awaitMessage("closed");
+
+  info("one tab closed, one remains");
+
+  yield checkViews("background", 1, 0);
+
+  info("opening win1 popup");
+
+  yield triggerPopup(win1, function*() {
+    yield checkViews("background", 1, 1);
+    yield checkViews("tab", 1, 1);
+    yield checkViews("popup", 1, 1);
+  });
+
+  info("opening win2 popup");
+
+  yield triggerPopup(win2, function*() {
+    yield checkViews("background", 1, 1);
+    yield checkViews("tab", 1, 1);
+    yield checkViews("popup", 1, 1);
+  });
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+});
rename from browser/components/extensions/test/browser/browser_extensions_simple.js
rename to browser/components/extensions/test/browser/browser_ext_simple.js
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/head.js
@@ -0,0 +1,7 @@
+let {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
+
+function makeWidgetId(id)
+{
+  id = id.toLowerCase();
+  return id.replace(/[^a-z0-9_-]/g, "_");
+}
--- a/configure.in
+++ b/configure.in
@@ -7246,16 +7246,19 @@ if test -z "$MOZ_MEMORY"; then
     *-mingw*)
       if test -z "$WIN32_REDIST_DIR" -a -z "$MOZ_DEBUG"; then
         AC_MSG_WARN([When not building jemalloc, you need to set WIN32_REDIST_DIR to the path to the Visual C++ Redist (usually VCINSTALLDIR\redist\x86\Microsoft.VC80.CRT, for VC++ v8) if you intend to distribute your build.])
       fi
       ;;
   esac
 else
   AC_DEFINE(MOZ_MEMORY)
+  if test -n "$NIGHTLY_BUILD"; then
+    MOZ_JEMALLOC4=1
+  fi
   if test -n "$MOZ_JEMALLOC4"; then
     AC_DEFINE(MOZ_JEMALLOC4)
   fi
   if test "x$MOZ_DEBUG" = "x1"; then
     AC_DEFINE(MOZ_MEMORY_DEBUG)
   fi
   dnl The generic feature tests that determine how to compute ncpus are long and
   dnl complicated.  Therefore, simply define special cpp variables for the
--- a/devtools/client/webconsole/test/browser_webconsole_console_logging_workers_api.js
+++ b/devtools/client/webconsole/test/browser_webconsole_console_logging_workers_api.js
@@ -4,27 +4,17 @@
 // Tests that the basic console.log()-style APIs and filtering work for
 // sharedWorkers
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/test-console-workers.html";
 
-function pushPrefEnv() {
-  let deferred = promise.defer();
-  let options = {
-    set: [["dom.workers.sharedWorkers.enabled", true]]
-  };
-  SpecialPowers.pushPrefEnv(options, deferred.resolve);
-  return deferred.promise;
-}
-
 var test = asyncTest(function*() {
-  yield pushPrefEnv();
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "foo-bar-shared-worker"
--- a/devtools/shared/heapsnapshot/DeserializedNode.cpp
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -103,18 +103,18 @@ class DeserializedEdgeRange : public Edg
   EdgeVector edges;
   size_t     i;
 
   void settle() {
     front_ = i < edges.length() ? &edges[i] : nullptr;
   }
 
 public:
-  explicit DeserializedEdgeRange(JSContext* cx)
-    : edges(cx)
+  explicit DeserializedEdgeRange()
+    : edges()
     , i(0)
   {
     settle();
   }
 
   bool init(DeserializedNode& node)
   {
     if (!edges.reserve(node.edges.length()))
@@ -155,20 +155,20 @@ Concrete<DeserializedNode>::allocationSt
   MOZ_ASSERT(ptr);
   // See above comment in DeserializedNode::getEdgeReferent about why this
   // const_cast is needed and safe.
   return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
 }
 
 
 UniquePtr<EdgeRange>
-Concrete<DeserializedNode>::edges(JSContext* cx, bool) const
+Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
 {
   UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range(
-    js_new<DeserializedEdgeRange>(cx));
+    js_new<DeserializedEdgeRange>());
 
   if (!range || !range->init(get()))
     return nullptr;
 
   return UniquePtr<EdgeRange>(range.release());
 }
 
 StackFrame
--- a/devtools/shared/heapsnapshot/DeserializedNode.h
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -260,17 +260,17 @@ public:
   Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override;
   const char* jsObjectClassName() const override { return get().jsObjectClassName.get(); }
 
   bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
   StackFrame allocationStack() const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
-  UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
+  UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override;
 };
 
 template<>
 class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame
 {
 protected:
   explicit ConcreteStackFrame(DeserializedStackFrame* ptr)
     : BaseStackFrame(ptr)
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -379,17 +379,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, 
     return;
   }
 
   JS::ubi::CensusHandler handler(census, rootCount);
 
   {
     JS::AutoCheckCannotGC nogc;
 
-    JS::ubi::CensusTraversal traversal(cx, handler, nogc);
+    JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc);
     if (NS_WARN_IF(!traversal.init())) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
 
     if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
@@ -679,17 +679,17 @@ public:
     }
 
     if (auto className = ubiNode.jsObjectClassName()) {
       size_t length = strlen(className);
       protobufNode.set_jsobjectclassname(className, length);
     }
 
     if (includeEdges) {
-      auto edges = ubiNode.edges(cx, wantNames);
+      auto edges = ubiNode.edges(JS_GetRuntime(cx), wantNames);
       if (NS_WARN_IF(!edges))
         return false;
 
       for ( ; !edges->empty(); edges->popFront()) {
         const ubi::Edge& ubiEdge = edges->front();
 
         protobuf::Edge* protobufEdge = protobufNode.add_edges();
         if (NS_WARN_IF(!protobufEdge)) {
@@ -790,17 +790,17 @@ WriteHeapGraph(JSContext* cx,
   if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
     return false;
   }
 
   // Walk the heap graph starting from the given node and serialize it into the
   // core dump.
 
   HeapSnapshotHandler handler(writer, zones);
-  HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
+  HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC);
   if (!traversal.init())
     return false;
   traversal.wantNames = wantNames;
 
   bool ok = traversal.addStartVisited(node) &&
             traversal.traverse();
 
   if (ok) {
@@ -955,17 +955,17 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(
   StreamWriter writer(cx, gzipStream, wantNames);
   if (NS_WARN_IF(!writer.init())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   {
     Maybe<AutoCheckCannotGC> maybeNoGC;
-    ubi::RootList rootList(cx, maybeNoGC, wantNames);
+    ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames);
     if (!EstablishBoundaries(cx, rv, boundaries, rootList, zones))
       return;
 
     MOZ_ASSERT(maybeNoGC.isSome());
     ubi::Node roots(&rootList);
 
     // Serialize the initial heap snapshot metadata to the core dump.
     if (!writer.writeMetadata(PR_Now()) ||
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -92,10 +92,10 @@ DEF_TEST(DeserializedNodeUbiNodes, {
     edge3.referent = referent3->id;
     mocked.addEdge(Move(edge3));
     EXPECT_CALL(mocked,
                 getEdgeReferent(Field(&DeserializedEdge::referent,
                                       referent3->id)))
       .Times(1)
       .WillOnce(Return(JS::ubi::Node(referent3.get())));
 
-    ubi.edges(cx);
+    ubi.edges(JS_GetRuntime(cx));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -157,18 +157,18 @@ struct DevTools : public ::testing::Test
 class MOZ_STACK_CLASS FakeNode
 {
 public:
   JS::ubi::EdgeVector edges;
   JSCompartment*      compartment;
   JS::Zone*           zone;
   size_t              size;
 
-  explicit FakeNode(JSContext* cx)
-    : edges(cx),
+  explicit FakeNode()
+    : edges(),
     compartment(nullptr),
     zone(nullptr),
     size(1)
   { }
 };
 
 namespace JS {
 namespace ubi {
@@ -177,18 +177,18 @@ using mozilla::UniquePtr;
 
 template<>
 class Concrete<FakeNode> : public Base
 {
   const char16_t* typeName() const override {
     return concreteTypeName;
   }
 
-  UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override {
-    return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(cx, get().edges));
+  UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override {
+    return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
   }
 
   Size size(mozilla::MallocSizeOf) const override {
     return get().size;
   }
 
   JS::Zone* zone() const override {
     return get().zone;
@@ -228,32 +228,32 @@ void AddEdge(FakeNode& node, FakeNode& r
 
 // Custom GMock Matchers
 
 // Use the testing namespace to avoid static analysis failures in the gmock
 // matcher classes that get generated from MATCHER_P macros.
 namespace testing {
 
 // Ensure that given node has the expected number of edges.
-MATCHER_P2(EdgesLength, cx, expectedLength, "") {
-  auto edges = arg.edges(cx);
+MATCHER_P2(EdgesLength, rt, expectedLength, "") {
+  auto edges = arg.edges(rt);
   if (!edges)
     return false;
 
   int actualLength = 0;
   for ( ; !edges->empty(); edges->popFront())
     actualLength++;
 
   return Matcher<int>(Eq(expectedLength))
     .MatchAndExplain(actualLength, result_listener);
 }
 
 // Get the nth edge and match it with the given matcher.
-MATCHER_P3(Edge, cx, n, matcher, "") {
-  auto edges = arg.edges(cx);
+MATCHER_P3(Edge, rt, n, matcher, "") {
+  auto edges = arg.edges(rt);
   if (!edges)
     return false;
 
   int i = 0;
   for ( ; !edges->empty(); edges->popFront()) {
     if (i == n) {
       return Matcher<const JS::ubi::Edge&>(matcher)
         .MatchAndExplain(edges->front(), result_listener);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp
@@ -24,20 +24,20 @@ DEF_TEST(DoesCrossZoneBoundaries, {
     ASSERT_NE(newZone, zone);
 
     // Our set of target zones is both the old and new zones.
     JS::ZoneSet targetZones;
     ASSERT_TRUE(targetZones.init());
     ASSERT_TRUE(targetZones.put(zone));
     ASSERT_TRUE(targetZones.put(newZone));
 
-    FakeNode nodeA(cx);
-    FakeNode nodeB(cx);
-    FakeNode nodeC(cx);
-    FakeNode nodeD(cx);
+    FakeNode nodeA;
+    FakeNode nodeB;
+    FakeNode nodeC;
+    FakeNode nodeD;
 
     nodeA.zone = zone;
     nodeB.zone = nullptr;
     nodeC.zone = newZone;
     nodeD.zone = nullptr;
 
     AddEdge(nodeA, nodeB);
     AddEdge(nodeA, nodeC);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp
@@ -24,19 +24,19 @@ DEF_TEST(DoesntCrossZoneBoundaries, {
     ASSERT_NE(newZone, zone);
 
     // Our set of target zones is only the pre-existing zone and does not
     // include the new zone.
     JS::ZoneSet targetZones;
     ASSERT_TRUE(targetZones.init());
     ASSERT_TRUE(targetZones.put(zone));
 
-    FakeNode nodeA(cx);
-    FakeNode nodeB(cx);
-    FakeNode nodeC(cx);
+    FakeNode nodeA;
+    FakeNode nodeB;
+    FakeNode nodeC;
 
     nodeA.zone = zone;
     nodeB.zone = nullptr;
     nodeC.zone = newZone;
 
     AddEdge(nodeA, nodeB);
     AddEdge(nodeB, nodeC);
 
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
@@ -8,37 +8,37 @@
 #include "DevTools.h"
 
 using testing::Field;
 using testing::IsNull;
 using testing::Property;
 using testing::Return;
 
 DEF_TEST(SerializesEdgeNames, {
-    FakeNode node(cx);
-    FakeNode referent(cx);
+    FakeNode node;
+    FakeNode referent;
 
     const char16_t edgeName[] = MOZ_UTF16("edge name");
     const char16_t emptyStr[] = MOZ_UTF16("");
 
     AddEdge(node, referent, edgeName);
     AddEdge(node, referent, emptyStr);
     AddEdge(node, referent, nullptr);
 
     ::testing::NiceMock<MockWriter> writer;
 
     // Should get the node with edges once.
     EXPECT_CALL(
       writer,
-      writeNode(AllOf(EdgesLength(cx, 3),
-                      Edge(cx, 0, Field(&JS::ubi::Edge::name,
+      writeNode(AllOf(EdgesLength(rt, 3),
+                      Edge(rt, 0, Field(&JS::ubi::Edge::name,
                                         UTF16StrEq(edgeName))),
-                      Edge(cx, 1, Field(&JS::ubi::Edge::name,
+                      Edge(rt, 1, Field(&JS::ubi::Edge::name,
                                         UTF16StrEq(emptyStr))),
-                      Edge(cx, 2, Field(&JS::ubi::Edge::name,
+                      Edge(rt, 2, Field(&JS::ubi::Edge::name,
                                         IsNull()))),
                 _)
     )
       .Times(1)
       .WillOnce(Return(true));
 
     // Should get the referent node that doesn't have any edges once.
     ExpectWriteNode(writer, referent);
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
@@ -3,20 +3,20 @@
  * 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/. */
 
 // Test that everything in the heap graph gets serialized once, and only once.
 
 #include "DevTools.h"
 
 DEF_TEST(SerializesEverythingInHeapGraphOnce, {
-    FakeNode nodeA(cx);
-    FakeNode nodeB(cx);
-    FakeNode nodeC(cx);
-    FakeNode nodeD(cx);
+    FakeNode nodeA;
+    FakeNode nodeB;
+    FakeNode nodeC;
+    FakeNode nodeD;
 
     AddEdge(nodeA, nodeB);
     AddEdge(nodeB, nodeC);
     AddEdge(nodeC, nodeD);
     AddEdge(nodeD, nodeA);
 
     ::testing::NiceMock<MockWriter> writer;
 
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
@@ -6,17 +6,17 @@
 // Test that a ubi::Node's typeName gets properly serialized into a core dump.
 
 #include "DevTools.h"
 
 using testing::Property;
 using testing::Return;
 
 DEF_TEST(SerializesTypeNames, {
-    FakeNode node(cx);
+    FakeNode node;
 
     ::testing::NiceMock<MockWriter> writer;
     EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName,
                                            UTF16StrEq(MOZ_UTF16("FakeNode"))),
                                   _))
       .Times(1)
       .WillOnce(Return(true));
 
--- a/docshell/test/chrome/chrome.ini
+++ b/docshell/test/chrome/chrome.ini
@@ -75,16 +75,17 @@ skip-if = toolkit == "gtk2"
 [test_bug565388.xul]
 skip-if = os == 'linux' || os == 'mac' # Bug 1026815
 [test_bug582176.xul]
 [test_bug608669.xul]
 [test_bug662200.xul]
 [test_bug690056.xul]
 [test_bug789773.xul]
 [test_bug846906.xul]
+skip-if = os == 'linux' && asan # Bug 1207161
 [test_bug89419.xul]
 [test_bug909218.html]
 [test_bug92598.xul]
 [test_mozFrameType.xul]
 [test_principalInherit.xul]
 [test_private_hidden_window.html]
 [test_viewsource_forbidden_in_iframe.xul]
 skip-if = true # bug 1019315
--- a/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html
+++ b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html
@@ -39,14 +39,14 @@ function runTests() {
     bc.close();
     SimpleTest.finish();
   }
 
   worker.port.postMessage('go');
 }
 
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({"set": [["dom.workers.sharedWorkers.enabled", true]]}, runTests);
+runTests();
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -941,16 +941,17 @@ CanvasRenderingContext2D::CanvasRenderin
 #ifdef USE_SKIA_GPU
   , mVideoTexture(0)
 #endif
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
+  , mIsSkiaGL(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false)
   , mIsCapturedFrameInvalid(false)
   , mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
@@ -1371,16 +1372,18 @@ CanvasRenderingContext2D::EnsureTarget(R
     mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), IntSize(mWidth, mHeight)));
     if (mTarget) {
       return mRenderingMode;
     } else {
       mBufferProvider = nullptr;
     }
   }
 
+  mIsSkiaGL = false;
+
    // Check that the dimensions are sane
   IntSize size(mWidth, mHeight);
   if (size.width <= gfxPrefs::MaxCanvasSize() &&
       size.height <= gfxPrefs::MaxCanvasSize() &&
       size.width >= 0 && size.height >= 0) {
     SurfaceFormat format = GetSurfaceFormat();
     nsIDocument* ownerDoc = nullptr;
     if (mCanvasElement) {
@@ -1404,16 +1407,17 @@ CanvasRenderingContext2D::EnsureTarget(R
 #if USE_SKIA_GPU
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
 
         if (glue && glue->GetGrContext() && glue->GetGLContext()) {
           mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
           if (mTarget) {
             AddDemotableContext(this);
             mBufferProvider = new PersistentBufferProviderBasic(mTarget);
+            mIsSkiaGL = true;
           } else {
             printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n");
             mode = RenderingMode::SoftwareBackendMode;
           }
         }
 #endif
       }
 
@@ -5555,19 +5559,21 @@ CanvasRenderingContext2D::GetBufferProvi
   return mBufferProvider;
 }
 
 already_AddRefed<CanvasLayer>
 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          CanvasLayer *aOldLayer,
                                          LayerManager *aManager)
 {
-  if (mOpaque) {
+  if (mOpaque || mIsSkiaGL) {
     // If we're opaque then make sure we have a surface so we paint black
     // instead of transparent.
+    // If we're using SkiaGL, then SkiaGLTex() below needs the target to
+    // be accessible.
     EnsureTarget();
   }
 
   // Don't call EnsureTarget() ... if there isn't already a surface, then
   // we have nothing to paint and there is no need to create a surface just
   // to paint nothing. Also, EnsureTarget() can cause creation of a persistent
   // layer manager which must NOT happen during a paint.
   if ((!mBufferProvider && !mTarget) || !IsTargetValid()) {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -713,16 +713,19 @@ protected:
 
   bool mOpaque;
 
   // This is true when the next time our layer is retrieved we need to
   // recreate it (i.e. our backing surface changed)
   bool mResetLayer;
   // This is needed for drawing in drawAsyncXULElement
   bool mIPC;
+  // True if the current DrawTarget is using skia-gl, used so we can avoid
+  // requesting the DT from mBufferProvider to check.
+  bool mIsSkiaGL;
 
   nsTArray<CanvasRenderingContext2DUserData*> mUserDatas;
 
   // If mCanvasElement is not provided, then a docshell is
   nsCOMPtr<nsIDocShell> mDocShell;
 
   // This is created lazily so it is necessary to call EnsureTarget before
   // accessing it. In the event of an error it will be equal to
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -154,9 +154,9 @@ skip-if(!winWidget) pref(webgl.disable-a
 
 # focus rings
 pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
 pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
 skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
 
-fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
+fuzzy-if(azureSkiaGL,1,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -109,26 +109,28 @@
 #include "nsFrameMessageManager.h"
 #include "nsGeolocationSettings.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
 #include "nsIAppsService.h"
 #include "nsIClipboard.h"
 #include "nsContentPermissionHelper.h"
 #include "nsICycleCollectorListener.h"
+#include "nsIDocShellTreeOwner.h"
 #include "nsIDocument.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMGeoPositionError.h"
 #include "nsIDragService.h"
 #include "mozilla/dom/WakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIExternalProtocolService.h"
 #include "nsIFormProcessor.h"
 #include "nsIGfxInfo.h"
 #include "nsIIdleService.h"
+#include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
@@ -1229,17 +1231,28 @@ ContentParent::CreateBrowserOrApp(const 
                     return nullptr;
                 }
             }
             tabId = AllocateTabId(openerTabId,
                                   aContext.AsIPCTabContext(),
                                   constructorSender->ChildID());
         }
         if (constructorSender) {
+            nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+            docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+            if (!treeOwner) {
+                return nullptr;
+            }
+
+            nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner);
+            if (!wbc) {
+                return nullptr;
+            }
             uint32_t chromeFlags = 0;
+            wbc->GetChromeFlags(&chromeFlags);
 
             nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
             if (loadContext && loadContext->UsePrivateBrowsing()) {
                 chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
             }
             bool affectLifetime;
             docShell->GetAffectPrivateSessionLifetime(&affectLifetime);
             if (affectLifetime) {
@@ -2184,17 +2197,17 @@ ContentParent::NotifyTabDestroying(const
 
 void
 ContentParent::StartForceKillTimer()
 {
     if (mForceKillTimer || !mIPCOpen) {
         return;
     }
 
-    int32_t timeoutSecs = Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 0);
+    int32_t timeoutSecs = Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 5);
     if (timeoutSecs > 0) {
         mForceKillTimer = do_CreateInstance("@mozilla.org/timer;1");
         MOZ_ASSERT(mForceKillTimer);
         mForceKillTimer->InitWithFuncCallback(ContentParent::ForceKillTimerCallback,
                                               this,
                                               timeoutSecs * 1000,
                                               nsITimer::TYPE_ONE_SHOT);
     }
@@ -3466,16 +3479,24 @@ ContentParent::DeallocPRemoteSpellcheckE
 {
     delete parent;
     return true;
 }
 
 /* static */ void
 ContentParent::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure)
 {
+#ifdef ENABLE_TESTS
+    // We don't want to time out the content process during XPCShell tests. This
+    // is the easiest way to ensure that.
+    if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+        return;
+    }
+#endif
+
     auto self = static_cast<ContentParent*>(aClosure);
     self->KillHard("ShutDownKill");
 }
 
 void
 ContentParent::KillHard(const char* aReason)
 {
     // On Windows, calling KillHard multiple times causes problems - the
--- a/dom/media/systemservices/LoadManager.cpp
+++ b/dom/media/systemservices/LoadManager.cpp
@@ -10,16 +10,18 @@
 #include "prtime.h"
 #include "prinrval.h"
 #include "prsystem.h"
 
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsReadableUtils.h"
 #include "nsIObserverService.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ArrayUtils.h"
 
 // NSPR_LOG_MODULES=LoadManager:5
 PRLogModuleInfo *gLoadManagerLog = nullptr;
 #undef LOG
 #undef LOG_ENABLED
 #define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose)
 
@@ -48,16 +50,21 @@ LoadManagerSingleton::LoadManagerSinglet
     gLoadManagerLog = PR_NewLogModule("LoadManager");
   LOG(("LoadManager - Initializing (%dms x %d, %f, %f)",
        mLoadMeasurementInterval, mAveragingMeasurements,
        mHighLoadThreshold, mLowLoadThreshold));
   MOZ_ASSERT(mHighLoadThreshold > mLowLoadThreshold);
   mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval);
   mLoadMonitor->Init(mLoadMonitor);
   mLoadMonitor->SetLoadChangeCallback(this);
+
+  mLastStateChange = TimeStamp::Now();
+  for (auto &in_state : mTimeInState) {
+    in_state = 0;
+  }
 }
 
 LoadManagerSingleton::~LoadManagerSingleton()
 {
   LOG(("LoadManager: shutting down LoadMonitor"));
   MOZ_ASSERT(!mLoadMonitor, "why wasn't the LoadMonitor shut down in xpcom-shutdown?");
   if (mLoadMonitor) {
     mLoadMonitor->Shutdown();
@@ -95,92 +102,130 @@ LoadManagerSingleton::LoadChanged(float 
   MutexAutoLock lock(mLock);
   // Update total load, and total amount of measured seconds.
   mLoadSum += aSystemLoad;
   mLoadSumMeasurements++;
 
   if (mLoadSumMeasurements >= mAveragingMeasurements) {
     double averagedLoad = mLoadSum / (float)mLoadSumMeasurements;
 
-    webrtc::CPULoadState oldState = mCurrentState;
+    webrtc::CPULoadState newState = mCurrentState;
 
     if (mOveruseActive || averagedLoad > mHighLoadThreshold) {
       LOG(("LoadManager - LoadStressed"));
-      mCurrentState = webrtc::kLoadStressed;
+      newState = webrtc::kLoadStressed;
     } else if (averagedLoad < mLowLoadThreshold) {
       LOG(("LoadManager - LoadRelaxed"));
-      mCurrentState = webrtc::kLoadRelaxed;
+      newState = webrtc::kLoadRelaxed;
     } else {
       LOG(("LoadManager - LoadNormal"));
-      mCurrentState = webrtc::kLoadNormal;
+      newState = webrtc::kLoadNormal;
     }
 
-    if (oldState != mCurrentState)
-      LoadHasChanged();
+    if (newState != mCurrentState) {
+      LoadHasChanged(newState);
+    }
 
     mLoadSum = 0;
     mLoadSumMeasurements = 0;
   }
 }
 
 void
 LoadManagerSingleton::OveruseDetected()
 {
   LOG(("LoadManager - Overuse Detected"));
   MutexAutoLock lock(mLock);
   mOveruseActive = true;
   if (mCurrentState != webrtc::kLoadStressed) {
-    mCurrentState = webrtc::kLoadStressed;
-    LoadHasChanged();
+    LoadHasChanged(webrtc::kLoadStressed);
   }
 }
 
 void
 LoadManagerSingleton::NormalUsage()
 {
   LOG(("LoadManager - Overuse finished"));
   MutexAutoLock lock(mLock);
   mOveruseActive = false;
 }
 
 void
-LoadManagerSingleton::LoadHasChanged()
+LoadManagerSingleton::LoadHasChanged(webrtc::CPULoadState aNewState)
 {
   mLock.AssertCurrentThreadOwns();
-  LOG(("LoadManager - Signaling LoadHasChanged to %d listeners", mObservers.Length()));
+  LOG(("LoadManager - Signaling LoadHasChanged from %d to %d to %d listeners",
+       mCurrentState, aNewState, mObservers.Length()));
+
+  // Record how long we spent in this state for later Telemetry or display
+  TimeStamp now = TimeStamp::Now();
+  mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds();
+  mLastStateChange = now;
+
+  mCurrentState = aNewState;
   for (size_t i = 0; i < mObservers.Length(); i++) {
     mObservers.ElementAt(i)->onLoadStateChanged(mCurrentState);
   }
 }
 
 void
 LoadManagerSingleton::AddObserver(webrtc::CPULoadStateObserver * aObserver)
 {
   LOG(("LoadManager - Adding Observer"));
   MutexAutoLock lock(mLock);
   mObservers.AppendElement(aObserver);
   if (mObservers.Length() == 1) {
     if (!mLoadMonitor) {
       mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval);
       mLoadMonitor->Init(mLoadMonitor);
       mLoadMonitor->SetLoadChangeCallback(this);
+      mLastStateChange = TimeStamp::Now();
     }
   }
 }
 
 void
 LoadManagerSingleton::RemoveObserver(webrtc::CPULoadStateObserver * aObserver)
 {
   LOG(("LoadManager - Removing Observer"));
   MutexAutoLock lock(mLock);
   if (!mObservers.RemoveElement(aObserver)) {
     LOG(("LoadManager - Element to remove not found"));
   }
   if (mObservers.Length() == 0) {
     if (mLoadMonitor) {
+      // Record how long we spent in the final state for later Telemetry or display
+      TimeStamp now = TimeStamp::Now();
+      mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds();
+
+      float total = 0;
+      for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mTimeInState); i++) {
+        total += mTimeInState[i];
+      }
+      // Don't include short calls; we don't have reasonable load data, and
+      // such short calls rarely reach a stable state.  Keep relatively
+      // short calls separate from longer ones
+      bool log = total > 5*PR_MSEC_PER_SEC;
+      bool small = log && total < 30*PR_MSEC_PER_SEC;
+      if (log) {
+        // Note: We don't care about rounding here; thus total may be < 100
+        Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_RELAXED_SHORT :
+                                      Telemetry::WEBRTC_LOAD_STATE_RELAXED,
+                              (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadRelaxed]/total * 100));
+        Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_NORMAL_SHORT :
+                                      Telemetry::WEBRTC_LOAD_STATE_NORMAL,
+                              (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadNormal]/total * 100));
+        Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_STRESSED_SHORT :
+                                      Telemetry::WEBRTC_LOAD_STATE_STRESSED,
+                              (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadStressed]/total * 100));
+      }
+      for (auto &in_state : mTimeInState) {
+        in_state = 0;
+      }
+
       // Dance to avoid deadlock on mLock!
       nsRefPtr<LoadMonitor> loadMonitor = mLoadMonitor.forget();
       MutexAutoUnlock unlock(mLock);
 
       loadMonitor->Shutdown();
     }
   }
 }
--- a/dom/media/systemservices/LoadManager.h
+++ b/dom/media/systemservices/LoadManager.h
@@ -45,25 +45,28 @@ public:
 
 private:
     LoadManagerSingleton(int aLoadMeasurementInterval,
                          int aAveragingMeasurements,
                          float aHighLoadThreshold,
                          float aLowLoadThreshold);
     ~LoadManagerSingleton();
 
-    void LoadHasChanged();
+    void LoadHasChanged(webrtc::CPULoadState aNewState);
 
     nsRefPtr<LoadMonitor> mLoadMonitor;
 
     // This protects access to the mObservers list, the current state, and
     // pretty much all the other members (below).
     Mutex mLock;
     nsTArray<webrtc::CPULoadStateObserver*> mObservers;
     webrtc::CPULoadState mCurrentState;
+    TimeStamp mLastStateChange;
+    float mTimeInState[static_cast<int>(webrtc::kLoadLast)];
+
     // Set when overuse was signaled to us, and hasn't been un-signaled yet.
     bool  mOveruseActive;
     float mLoadSum;
     int   mLoadSumMeasurements;
     // Load measurement settings
     int mLoadMeasurementInterval;
     int mAveragingMeasurements;
     float mHighLoadThreshold;
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -1,130 +1,81 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/PresentationBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIPresentationDeviceManager.h"
 #include "nsIPresentationService.h"
-#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "Presentation.h"
-#include "PresentationCallbacks.h"
-#include "PresentationSession.h"
+#include "PresentationReceiver.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultRequest)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
-  tmp->Shutdown();
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultRequest)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper,
+                                   mDefaultRequest, mReceiver)
 
 NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation)
-  NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 /* static */ already_AddRefed<Presentation>
 Presentation::Create(nsPIDOMWindow* aWindow)
 {
   nsRefPtr<Presentation> presentation = new Presentation(aWindow);
   return NS_WARN_IF(!presentation->Init()) ? nullptr : presentation.forget();
 }
 
 Presentation::Presentation(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
 {
 }
 
 Presentation::~Presentation()
 {
-  Shutdown();
 }
 
 bool
 Presentation::Init()
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
   if (NS_WARN_IF(!GetOwner())) {
     return false;
   }
-  mWindowId = GetOwner()->WindowID();
 
-  // Check if a session instance is required now. A session may already be
-  // connecting before the web content gets loaded in a presenting browsing
-  // context (receiver).
+  // Check if a receiver instance is required now. A session may already be
+  // connecting before the web content gets loaded in a receiving browsing
+  // context.
   nsAutoString sessionId;
-  nsresult rv = service->GetExistentSessionIdAtLaunch(mWindowId, sessionId);
+  nsresult rv = service->GetExistentSessionIdAtLaunch(GetOwner()->WindowID(), sessionId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   if (!sessionId.IsEmpty()) {
-    rv = NotifySessionConnect(mWindowId, sessionId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    mReceiver = PresentationReceiver::Create(GetOwner(), sessionId);
+    if (NS_WARN_IF(!mReceiver)) {
       return false;
     }
   }
 
-  // Register listener for incoming sessions.
-  rv = service->RegisterRespondingListener(mWindowId, this);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
   return true;
 }
 
-void Presentation::Shutdown()
-{
-  mDefaultRequest = nullptr;
-  mSessions.Clear();
-  mPendingGetSessionPromises.Clear();
-
-  // Unregister listener for incoming sessions.
-  nsCOMPtr<nsIPresentationService> service =
-    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
-  if (NS_WARN_IF(!service)) {
-    return;
-  }
-
-  nsresult rv = service->UnregisterRespondingListener(mWindowId);
-  NS_WARN_IF(NS_FAILED(rv));
-}
-
-/* virtual */ void
-Presentation::DisconnectFromOwner()
-{
-  Shutdown();
-  DOMEventTargetHelper::DisconnectFromOwner();
-}
-
 /* virtual */ JSObject*
 Presentation::WrapObject(JSContext* aCx,
                          JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
@@ -135,86 +86,14 @@ Presentation::SetDefaultRequest(Presenta
 
 already_AddRefed<PresentationRequest>
 Presentation::GetDefaultRequest() const
 {
   nsRefPtr<PresentationRequest> request = mDefaultRequest;
   return request.forget();
 }
 
-already_AddRefed<Promise>
-Presentation::GetSession(ErrorResult& aRv)
-{
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
-  if (NS_WARN_IF(!global)) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  // If there's no existing session, leave the promise pending until a
-  // connecting request arrives from the controlling browsing context (sender).
-  // http://w3c.github.io/presentation-api/#dom-presentation-getsession
-  if (!mSessions.IsEmpty()) {
-    promise->MaybeResolve(mSessions[0]);
-  } else {
-    mPendingGetSessionPromises.AppendElement(promise);
-  }
-
-  return promise.forget();
-}
-
-already_AddRefed<Promise>
-Presentation::GetSessions(ErrorResult& aRv) const
+already_AddRefed<PresentationReceiver>
+Presentation::GetReceiver() const
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
-  if (NS_WARN_IF(!global)) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  promise->MaybeResolve(mSessions);
-  return promise.forget();
+  nsRefPtr<PresentationReceiver> receiver = mReceiver;
+  return receiver.forget();
 }
-
-NS_IMETHODIMP
-Presentation::NotifySessionConnect(uint64_t aWindowId,
-                                   const nsAString& aSessionId)
-{
-  if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  nsRefPtr<PresentationSession> session =
-    PresentationSession::Create(GetOwner(), aSessionId,
-                                PresentationSessionState::Disconnected);
-  if (NS_WARN_IF(!session)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  mSessions.AppendElement(session);
-
-  // Resolve pending |GetSession| promises if any.
-  if (!mPendingGetSessionPromises.IsEmpty()) {
-    for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
-      mPendingGetSessionPromises[i]->MaybeResolve(session);
-    }
-    mPendingGetSessionPromises.Clear();
-  }
-
-  return DispatchSessionAvailableEvent();
-}
-
-nsresult
-Presentation::DispatchSessionAvailableEvent()
-{
-  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
-    new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false);
-  return asyncDispatcher->PostDOMEvent();
-}
--- a/dom/presentation/Presentation.h
+++ b/dom/presentation/Presentation.h
@@ -3,68 +3,50 @@
 /* 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 mozilla_dom_Presentation_h
 #define mozilla_dom_Presentation_h
 
 #include "mozilla/DOMEventTargetHelper.h"
-#include "nsIPresentationListener.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
+class PresentationReceiver;
 class PresentationRequest;
-class PresentationSession;
 
 class Presentation final : public DOMEventTargetHelper
-                         , public nsIPresentationRespondingListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation,
                                            DOMEventTargetHelper)
-  NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
 
   static already_AddRefed<Presentation> Create(nsPIDOMWindow* aWindow);
 
-  virtual void DisconnectFromOwner() override;
-
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL (public APIs)
   void SetDefaultRequest(PresentationRequest* aRequest);
 
   already_AddRefed<PresentationRequest> GetDefaultRequest() const;
 
-  already_AddRefed<Promise> GetSession(ErrorResult& aRv);
-
-  already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const;
-
-  IMPL_EVENT_HANDLER(sessionavailable);
+  already_AddRefed<PresentationReceiver> GetReceiver() const;
 
 private:
   explicit Presentation(nsPIDOMWindow* aWindow);
 
   ~Presentation();
 
   bool Init();
 
-  void Shutdown();
-
-  nsresult DispatchSessionAvailableEvent();
-
-  // Store the inner window ID for |UnregisterRespondingListener| call in
-  // |Shutdown| since the inner window may not exist at that moment.
-  uint64_t mWindowId;
-
   nsRefPtr<PresentationRequest> mDefaultRequest;
-  nsTArray<nsRefPtr<PresentationSession>> mSessions;
-  nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises;
+  nsRefPtr<PresentationReceiver> mReceiver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Presentation_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/PresentationReceiverBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIPresentationService.h"
+#include "nsServiceManagerUtils.h"
+#include "PresentationReceiver.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationReceiver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+  tmp->Shutdown();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationReceiver)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+/* static */ already_AddRefed<PresentationReceiver>
+PresentationReceiver::Create(nsPIDOMWindow* aWindow,
+                             const nsAString& aSessionId)
+{
+  nsRefPtr<PresentationReceiver> receiver = new PresentationReceiver(aWindow);
+  return NS_WARN_IF(!receiver->Init(aSessionId)) ? nullptr : receiver.forget();
+}
+
+PresentationReceiver::PresentationReceiver(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+PresentationReceiver::~PresentationReceiver()
+{
+  Shutdown();
+}
+
+bool
+PresentationReceiver::Init(const nsAString& aSessionId)
+{
+  if (NS_WARN_IF(!GetOwner())) {
+    return false;
+  }
+  mWindowId = GetOwner()->WindowID();
+
+  if (!aSessionId.IsEmpty()) {
+    nsresult rv = NotifySessionConnect(mWindowId, aSessionId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+    }
+  }
+
+  // Register listener for incoming sessions.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return false;
+  }
+
+  nsresult rv = service->RegisterRespondingListener(mWindowId, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+void PresentationReceiver::Shutdown()
+{
+  mSessions.Clear();
+  mPendingGetSessionPromises.Clear();
+
+  // Unregister listener for incoming sessions.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  nsresult rv = service->UnregisterRespondingListener(mWindowId);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+/* virtual */ void
+PresentationReceiver::DisconnectFromOwner()
+{
+  Shutdown();
+  DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+/* virtual */ JSObject*
+PresentationReceiver::WrapObject(JSContext* aCx,
+                                 JS::Handle<JSObject*> aGivenProto)
+{
+  return PresentationReceiverBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<Promise>
+PresentationReceiver::GetSession(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // If there's no existing session, leave the promise pending until a
+  // connecting request arrives from the controlling browsing context (sender).
+  // http://w3c.github.io/presentation-api/#dom-presentation-getsession
+  if (!mSessions.IsEmpty()) {
+    promise->MaybeResolve(mSessions[0]);
+  } else {
+    mPendingGetSessionPromises.AppendElement(promise);
+  }
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+PresentationReceiver::GetSessions(ErrorResult& aRv) const
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  promise->MaybeResolve(mSessions);
+  return promise.forget();
+}
+
+NS_IMETHODIMP
+PresentationReceiver::NotifySessionConnect(uint64_t aWindowId,
+                                           const nsAString& aSessionId)
+{
+  if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsRefPtr<PresentationSession> session =
+    PresentationSession::Create(GetOwner(), aSessionId,
+                                PresentationSessionState::Disconnected);
+  if (NS_WARN_IF(!session)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  mSessions.AppendElement(session);
+
+  // Resolve pending |GetSession| promises if any.
+  if (!mPendingGetSessionPromises.IsEmpty()) {
+    for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
+      mPendingGetSessionPromises[i]->MaybeResolve(session);
+    }
+    mPendingGetSessionPromises.Clear();
+  }
+
+  return DispatchSessionAvailableEvent();
+}
+
+nsresult
+PresentationReceiver::DispatchSessionAvailableEvent()
+{
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false);
+  return asyncDispatcher->PostDOMEvent();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationReceiver.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_PresentationReceiver_h
+#define mozilla_dom_PresentationReceiver_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIPresentationListener.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class PresentationSession;
+
+class PresentationReceiver final : public DOMEventTargetHelper
+                                 , public nsIPresentationRespondingListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationReceiver,
+                                           DOMEventTargetHelper)
+  NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
+
+  static already_AddRefed<PresentationReceiver> Create(nsPIDOMWindow* aWindow,
+                                                       const nsAString& aSessionId);
+
+  virtual void DisconnectFromOwner() override;
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  // WebIDL (public APIs)
+  already_AddRefed<Promise> GetSession(ErrorResult& aRv);
+
+  already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const;
+
+  IMPL_EVENT_HANDLER(sessionavailable);
+
+private:
+  explicit PresentationReceiver(nsPIDOMWindow* aWindow);
+
+  ~PresentationReceiver();
+
+  bool Init(const nsAString& aSessionId);
+
+  void Shutdown();
+
+  nsresult DispatchSessionAvailableEvent();
+
+  // Store the inner window ID for |UnregisterRespondingListener| call in
+  // |Shutdown| since the inner window may not exist at that moment.
+  uint64_t mWindowId;
+
+  nsTArray<nsRefPtr<PresentationSession>> mSessions;
+  nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationReceiver_h
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -45,16 +45,21 @@ PresentationParent::ActorDestroy(ActorDe
 {
   mActorDestroyed = true;
 
   for (uint32_t i = 0; i < mSessionIds.Length(); i++) {
     NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(mSessionIds[i])));
   }
   mSessionIds.Clear();
 
+  for (uint32_t i = 0; i < mWindowIds.Length(); i++) {
+    NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(mWindowIds[i])));
+  }
+  mWindowIds.Clear();
+
   mService->UnregisterAvailabilityListener(this);
   mService = nullptr;
 }
 
 bool
 PresentationParent::RecvPPresentationRequestConstructor(
   PPresentationRequestParent* aActor,
   const PresentationIPCRequest& aRequest)
@@ -144,24 +149,27 @@ PresentationParent::RecvUnregisterSessio
   NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvRegisterRespondingHandler(const uint64_t& aWindowId)
 {
   MOZ_ASSERT(mService);
+
+  mWindowIds.AppendElement(aWindowId);
   NS_WARN_IF(NS_FAILED(mService->RegisterRespondingListener(aWindowId, this)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvUnregisterRespondingHandler(const uint64_t& aWindowId)
 {
   MOZ_ASSERT(mService);
+  mWindowIds.RemoveElement(aWindowId);
   NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(aWindowId)));
   return true;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyAvailableChange(bool aAvailable)
 {
   if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -59,16 +59,17 @@ public:
   virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override;
 
 private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIds;
+  nsTArray<uint64_t> mWindowIds;
 };
 
 class PresentationRequestParent final : public PPresentationRequestParent
                                       , public nsIPresentationServiceCallback
 {
   friend class PresentationParent;
 
 public:
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -12,31 +12,33 @@ MOCHITEST_MANIFESTS += ['tests/mochitest
 EXPORTS.mozilla.dom += [
     'ipc/PresentationChild.h',
     'ipc/PresentationIPCService.h',
     'ipc/PresentationParent.h',
     'Presentation.h',
     'PresentationAvailability.h',
     'PresentationCallbacks.h',
     'PresentationDeviceManager.h',
+    'PresentationReceiver.h',
     'PresentationRequest.h',
     'PresentationService.h',
     'PresentationSession.h',
     'PresentationSessionInfo.h',
     'PresentationSessionTransport.h',
 ]
 
 UNIFIED_SOURCES += [
     'ipc/PresentationChild.cpp',
     'ipc/PresentationIPCService.cpp',
     'ipc/PresentationParent.cpp',
     'Presentation.cpp',
     'PresentationAvailability.cpp',
     'PresentationCallbacks.cpp',
     'PresentationDeviceManager.cpp',
+    'PresentationReceiver.cpp',
     'PresentationRequest.cpp',
     'PresentationService.cpp',
     'PresentationSession.cpp',
     'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
     'PresentationSessionTransport.cpp',
 ]
 
--- a/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
@@ -24,28 +24,18 @@ function info(msg) {
 
 function finish() {
   alert('DONE');
 }
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available in OOP pages.");
-
-    navigator.presentation.getSessions().then(
-      function(aSessions) {
-        is(aSessions.length, 0, "Non-receiving OOP pages shouldn't get a predefined presentation session instance.");
-        aResolve();
-      },
-      function(aError) {
-        ok(false, "Error occurred when getting sessions: " + aError);
-        teardown();
-        aReject();
-      }
-    );
+    ok(!navigator.presentation.receiver, "Non-receiving OOP pages shouldn't get a presentation receiver instance.");
+    aResolve();
   });
 }
 
 testSessionAvailable().
 then(finish);
 
 </script>
 </body>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -30,18 +30,19 @@ function finish() {
   window.parent.postMessage('DONE', '*');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
@@ -30,18 +30,19 @@ function finish() {
   alert('DONE');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
@@ -30,18 +30,19 @@ function finish() {
   window.parent.postMessage('DONE', '*');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/test_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html
@@ -88,28 +88,18 @@ function setup() {
 
 function testIncomingSessionRequest() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
       gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
       info("Trying to launch receiver page.");
 
       ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
-
-      navigator.presentation.getSessions().then(
-        function(aSessions) {
-          is(aSessions.length, 0, "Non-receiving in-process pages shouldn't get a predefined presentation session instance.");
-          aResolve();
-        },
-        function(aError) {
-          ok(false, "Error occurred when getting sessions: " + aError);
-          teardown();
-          aReject();
-        }
-      );
+      ok(!navigator.presentation.receiver, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
+      aResolve();
     });
 
     gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
   });
 }
 
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
@@ -76,16 +76,18 @@ function testStartSession() {
     });
 
     gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
       gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
       info("Data transport channel is initialized.");
       gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
+    ok(!navigator.presentation.receiver, "Sender shouldn't get a presentation receiver instance.");
+
     navigator.presentation.defaultRequest.onsessionconnect = function(aEvent) {
       navigator.presentation.defaultRequest.onsessionconnect = null;
       session = aEvent.session;
       ok(session, "|sessionconnect| event is fired with a session.");
       ok(session.id, "Session ID should be set.");
       is(session.state, "connected", "Session state at sender side should be connected by default.");
       aResolve();
     };
--- a/dom/tests/mochitest/localstorage/chrome.ini
+++ b/dom/tests/mochitest/localstorage/chrome.ini
@@ -2,10 +2,11 @@
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   frame_clear_browser_data.html
   page_blank.html
 
 [test_app_uninstall.html]
 [test_clear_browser_data.html]
 [test_localStorageBasePrivateBrowsing_perwindowpb.html]
+skip-if = true # bug 1156725
 [test_localStorageFromChrome.xhtml]
 [test_localStorageQuotaPrivateBrowsing_perwindowpb.html]
--- a/dom/webidl/Presentation.webidl
+++ b/dom/webidl/Presentation.webidl
@@ -13,31 +13,14 @@ interface Presentation : EventTarget {
   * controller's behalf, it MUST start a presentation session using the default
   * presentation request (as if the controller had called |defaultRequest.start()|).
   *
   * Only used by controlling browsing context (senders).
   */
   attribute PresentationRequest? defaultRequest;
 
   /*
-   * Get the first connected presentation session in a presenting browsing
-   * context.
-   *
-   * Only used by presenting browsing context (receivers).
+   * This should be available on the receiving browsing context in order to
+   * access the controlling browsing context and communicate with them.
    */
-  [Throws]
-  Promise<PresentationSession> getSession();
-
-  /*
-   * Get all connected presentation sessions in a presenting browsing context.
-   *
-   * Only used by presenting browsing context (receivers).
-   */
-  [Throws]
-  Promise<sequence<PresentationSession>> getSessions();
-
-  /*
-   * It is called when an incoming session is connecting.
-   *
-   * Only used by presenting browsing context (receivers).
-   */
-  attribute EventHandler onsessionavailable;
+  [SameObject]
+  readonly attribute PresentationReceiver? receiver;
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PresentationReceiver.webidl
@@ -0,0 +1,27 @@
+/* -*- 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/.
+ */
+
+[Pref="dom.presentation.enabled",
+ CheckAnyPermissions="presentation"]
+interface PresentationReceiver : EventTarget {
+  /*
+   * Get the first connected presentation session in a receiving browsing
+   * context.
+   */
+  [Throws]
+  Promise<PresentationSession> getSession();
+
+  /*
+   * Get all connected presentation sessions in a receiving browsing context.
+   */
+  [Throws]
+  Promise<sequence<PresentationSession>> getSessions();
+
+  /*
+   * It is called when an incoming session is connecting.
+   */
+  attribute EventHandler onsessionavailable;
+};
--- a/dom/webidl/SharedWorker.webidl
+++ b/dom/webidl/SharedWorker.webidl
@@ -1,13 +1,12 @@
 /* -*- 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/.
  */
 
-[Pref="dom.workers.sharedWorkers.enabled",
- Constructor(DOMString scriptURL, optional DOMString name)]
+[Constructor(DOMString scriptURL, optional DOMString name)]
 interface SharedWorker : EventTarget {
     readonly attribute MessagePort port;
 };
 
 SharedWorker implements AbstractWorker;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -368,16 +368,17 @@ WEBIDL_FILES = [
     'PluginArray.webidl',
     'PointerEvent.webidl',
     'PopupBoxObject.webidl',
     'Position.webidl',
     'PositionError.webidl',
     'Presentation.webidl',
     'PresentationAvailability.webidl',
     'PresentationDeviceInfoManager.webidl',
+    'PresentationReceiver.webidl',
     'PresentationRequest.webidl',
     'PresentationSession.webidl',
     'ProcessingInstruction.webidl',
     'ProfileTimelineMarker.webidl',
     'Promise.webidl',
     'PromiseDebugging.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -3371,17 +3371,16 @@ ServiceWorkerManager::FindScopeForPath(c
       aMatch = current;
       return true;
     }
   }
 
   return false;
 }
 
-#ifdef DEBUG
 /* static */ bool
 ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
                                const nsACString& aScope)
 {
   nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
   nsAutoCString scopeKey;
@@ -3392,17 +3391,16 @@ ServiceWorkerManager::HasScope(nsIPrinci
 
   RegistrationDataPerPrincipal* data;
   if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
     return false;
   }
 
   return data->mOrderedScopes.Contains(aScope);
 }
-#endif
 
 /* static */ void
 ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration)
 {
   nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
   nsAutoCString scopeKey;
@@ -4631,17 +4629,18 @@ ServiceWorkerManager::CreateNewRegistrat
   return registration;
 }
 
 void
 ServiceWorkerManager::MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
 {
   MOZ_ASSERT(aRegistration);
   nsRefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
-  if (!newest) {
+  if (!newest && HasScope(aRegistration->mPrincipal, aRegistration->mScope)) {
+    aRegistration->Clear();
     RemoveRegistration(aRegistration);
   }
 }
 
 void
 ServiceWorkerManager::RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration)
 {
   MOZ_ASSERT(aRegistration);
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -515,20 +515,18 @@ private:
   AddScopeAndRegistration(const nsACString& aScope,
                           ServiceWorkerRegistrationInfo* aRegistation);
 
   static bool
   FindScopeForPath(const nsACString& aScopeKey,
                    const nsACString& aPath,
                    RegistrationDataPerPrincipal** aData, nsACString& aMatch);
 
-#ifdef DEBUG
   static bool
   HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope);
-#endif
 
   static void
   RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration);
 
   void
   QueueFireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration,
                                              const nsAString& aName);
 
--- a/dom/workers/test/test_bug1132395.html
+++ b/dom/workers/test/test_bug1132395.html
@@ -11,32 +11,30 @@
 </head>
 <body>
 
 <script class="testbody" type="text/javascript">
 
 // This test is full of dummy debug messages. This is because I need to follow
 // an hard-to-reproduce timeout failure.
 
-SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
-  info("test started");
-  var sw = new SharedWorker('bug1132395_sharedWorker.js');
-  sw.port.onmessage = function(event) {
-    info("sw.onmessage received");
-    ok(true, "We didn't crash.");
-    SimpleTest.finish();
-  }
+info("test started");
+var sw = new SharedWorker('bug1132395_sharedWorker.js');
+sw.port.onmessage = function(event) {
+  info("sw.onmessage received");
+  ok(true, "We didn't crash.");
+  SimpleTest.finish();
+}
 
-  sw.onerror = function(event) {
-    ok(false, "Failed to create a ServiceWorker");
-    SimpleTest.finish();
-  }
+sw.onerror = function(event) {
+  ok(false, "Failed to create a ServiceWorker");
+  SimpleTest.finish();
+}
 
-  info("sw.postmessage called");
-  sw.port.postMessage('go');
-});
+info("sw.postmessage called");
+sw.port.postMessage('go');
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/test_bug949946.html
+++ b/dom/workers/test/test_bug949946.html
@@ -10,22 +10,17 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
 
-  SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
-    new SharedWorker('sharedWorker_sharedWorker.js');
-    new SharedWorker('sharedWorker_sharedWorker.js', ':');
-    new SharedWorker('sharedWorker_sharedWorker.js', '|||');
-    ok(true, "3 SharedWorkers created!");
-    SimpleTest.finish();
-  });
-
-  SimpleTest.waitForExplicitFinish();
+new SharedWorker('sharedWorker_sharedWorker.js');
+new SharedWorker('sharedWorker_sharedWorker.js', ':');
+new SharedWorker('sharedWorker_sharedWorker.js', '|||');
+ok(true, "3 SharedWorkers created!");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/test_consoleSharedWorkers.html
+++ b/dom/workers/test/test_consoleSharedWorkers.html
@@ -42,18 +42,15 @@
         SpecialPowers.removeObserver(this, "console-api-log-event");
         SimpleTest.finish();
         return;
       }
     }
   }
 
   var cl = new consoleListener();
-
-  SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
-    new SharedWorker('sharedWorker_console.js');
-  });
+  new SharedWorker('sharedWorker_console.js');
 
   SimpleTest.waitForExplicitFinish();
 
     </script>
   </body>
 </html>
--- a/dom/workers/test/test_multi_sharedWorker.html
+++ b/dom/workers/test/test_multi_sharedWorker.html
@@ -6,18 +6,16 @@
 <html>
   <head>
     <title>Test for SharedWorker</title>
     <script src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
       <script class="testbody" type="text/javascript;version=1.7">
         "use strict";
 
-        const swPref = "dom.workers.sharedWorkers.enabled";
-
         const basePath =
           location.pathname.substring(0,
                                       location.pathname.lastIndexOf("/") + 1);
         const baseURL = location.origin + basePath;
 
         const frameRelativeURL = "multi_sharedWorker_frame.html";
         const frameAbsoluteURL = baseURL + frameRelativeURL;
         const workerAbsoluteURL =
@@ -25,23 +23,16 @@
 
         const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
         const errorMessage = "Error: Expected";
         const errorLineno = 34;
 
         let testGenerator = (function() {
           SimpleTest.waitForExplicitFinish();
 
-          if (!SpecialPowers.getBoolPref(swPref)) {
-            ok(!("SharedWorker" in window), "No SharedWorker without pref set");
-          }
-
-          SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, sendToGenerator);
-          yield undefined;
-
           window.addEventListener("message", function(event) {
             if (typeof(event.data) == "string") {
               info(event.data);
             } else {
               sendToGenerator(event);
             }
           });
 
--- a/dom/workers/test/test_multi_sharedWorker_lifetimes.html
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html
@@ -6,38 +6,32 @@
 <html>
   <head>
     <title>Test for SharedWorker</title>
     <script src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
       <script class="testbody" type="text/javascript;version=1.7">
         "use strict";
 
-        const swPref = "dom.workers.sharedWorkers.enabled";
         const scrollbarPref = "layout.testing.overlay-scrollbars.always-visible";
         const bfCacheEnabledPref = "browser.sessionhistory.cache_subframes";
         const bfCacheDepthPref = "browser.sessionhistory.max_total_viewers";
         const bfCacheDepth = 10;
 
         const frameRelativeURL = "multi_sharedWorker_frame.html";
         const storedData = "0123456789abcdefghijklmnopqrstuvwxyz";
 
         let testGenerator = (function() {
           SimpleTest.waitForExplicitFinish();
 
-          if (!SpecialPowers.getBoolPref(swPref)) {
-            ok(!("SharedWorker" in window), "No SharedWorker without pref set");
-          }
-
-          // Enable SharedWorkers and force scrollbar to always be shown.  The
-          // scrollbar setting is necessary to avoid the fade-in/fade-out from
-          // evicting our document from the BF cache below.  If bug 1049277
-          // is fixed, then we can stop setting the scrollbar pref here.
-          SpecialPowers.pushPrefEnv({ set: [[swPref, true],
-                                            [scrollbarPref, true]] },
+	  // Force scrollbar to always be shown.  The scrollbar setting is
+	  // necessary to avoid the fade-in/fade-out from evicting our document
+	  // from the BF cache below.  If bug 1049277 is fixed, then we can
+	  // stop setting the scrollbar pref here.
+          SpecialPowers.pushPrefEnv({ set: [[scrollbarPref, true]] },
                                     sendToGenerator);
           yield undefined;
 
           window.addEventListener("message", function(event) {
             if (typeof(event.data) == "string") {
               info(event.data);
             } else {
               sendToGenerator(event);
--- a/dom/workers/test/test_sharedWorker.html
+++ b/dom/workers/test/test_sharedWorker.html
@@ -12,68 +12,60 @@
   </head>
   <body>
     <p id="display"></p>
     <div id="content" style="display: none"></div>
     <pre id="test">
       <script class="testbody">
         "use strict";
 
-        const swPref = "dom.workers.sharedWorkers.enabled";
-
         const href = window.location.href;
         const filename = "sharedWorker_sharedWorker.js";
         const sentMessage = "ping";
         const errorFilename = href.substring(0, href.lastIndexOf("/") + 1) +
                               filename;
         const errorLine = 86;
         const errorColumn = 0;
 
-        if (!SpecialPowers.getBoolPref(swPref)) {
-          ok(!("SharedWorker" in window), "No SharedWorker without pref set");
-        }
-
-        SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, function() {
-          var worker = new SharedWorker(filename);
+        var worker = new SharedWorker(filename);
 
-          ok(worker instanceof SharedWorker, "Got SharedWorker instance");
-          ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'");
-          ok(worker.port instanceof MessagePort,
-            "Shared worker has MessagePort");
+        ok(worker instanceof SharedWorker, "Got SharedWorker instance");
+        ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'");
+        ok(worker.port instanceof MessagePort,
+          "Shared worker has MessagePort");
 
-          var receivedMessage;
-          var receivedError;
+        var receivedMessage;
+        var receivedError;
 
-          worker.port.onmessage = function(event) {
-            ok(event instanceof MessageEvent, "Got a MessageEvent");
-            ok(event.target === worker.port,
-               "MessageEvent has correct 'target' property");
-            is(event.data, sentMessage, "Got correct message");
-            ok(receivedMessage === undefined, "Haven't gotten message yet");
-            receivedMessage = event.data;
-            if (receivedError) {
-              SimpleTest.finish();
-            }
-          };
+        worker.port.onmessage = function(event) {
+          ok(event instanceof MessageEvent, "Got a MessageEvent");
+          ok(event.target === worker.port,
+             "MessageEvent has correct 'target' property");
+          is(event.data, sentMessage, "Got correct message");
+          ok(receivedMessage === undefined, "Haven't gotten message yet");
+          receivedMessage = event.data;
+          if (receivedError) {
+            SimpleTest.finish();
+          }
+        };
 
-          worker.onerror = function(event) {
-            ok(event instanceof ErrorEvent, "Got an ErrorEvent");
-            is(event.message, "Error: " + sentMessage, "Got correct error");
-            is(event.filename, errorFilename, "Got correct filename");
-            is(event.lineno, errorLine, "Got correct lineno");
-            is(event.colno, errorColumn, "Got correct column");
-            ok(receivedError === undefined, "Haven't gotten error yet");
-            receivedError = event.message;
-            event.preventDefault();
-            if (receivedMessage) {
-              SimpleTest.finish();
-            }
-          };
+        worker.onerror = function(event) {
+          ok(event instanceof ErrorEvent, "Got an ErrorEvent");
+          is(event.message, "Error: " + sentMessage, "Got correct error");
+          is(event.filename, errorFilename, "Got correct filename");
+          is(event.lineno, errorLine, "Got correct lineno");
+          is(event.colno, errorColumn, "Got correct column");
+          ok(receivedError === undefined, "Haven't gotten error yet");
+          receivedError = event.message;
+          event.preventDefault();
+          if (receivedMessage) {
+            SimpleTest.finish();
+          }
+        };
 
-          worker.port.postMessage(sentMessage);
-        });
+        worker.port.postMessage(sentMessage);
 
         SimpleTest.waitForExplicitFinish();
 
       </script>
     </pre>
   </body>
 </html>
--- a/dom/workers/test/test_sharedWorker_lifetime.html
+++ b/dom/workers/test/test_sharedWorker_lifetime.html
@@ -8,25 +8,23 @@
     <title>Test for MessagePort and SharedWorkers</title>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   </head>
   <body>
     <script class="testbody" type="text/javascript">
 
 var gced = false;
-SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]},
-function() {
-  var sw = new SharedWorker('sharedWorker_lifetime.js');
-  sw.port.onmessage = function(event) {
-    ok(gced, "The SW is still alive also after GC");
-    SimpleTest.finish();
-  }
 
-  sw = null;
-  SpecialPowers.forceGC();
-  gced = true;
-});
+var sw = new SharedWorker('sharedWorker_lifetime.js');
+sw.port.onmessage = function(event) {
+  ok(gced, "The SW is still alive also after GC");
+  SimpleTest.finish();
+}
+
+sw = null;
+SpecialPowers.forceGC();
+gced = true;
 
 SimpleTest.waitForExplicitFinish();
     </script>
   </body>
 </html>
--- a/dom/workers/test/test_sharedWorker_performance_user_timing.html
+++ b/dom/workers/test/test_sharedWorker_performance_user_timing.html
@@ -7,24 +7,21 @@
   <head>
     <title>Test for worker performance timing API</title>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   </head>
   <body>
     <script class="testbody" type="text/javascript">
 
-SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]},
-function() {
-  var sw = new SharedWorker('sharedworker_performance_user_timing.js');
-  sw.port.onmessage = function(event) {
-    if (event.data.type == 'finish') {
-      SimpleTest.finish();
-    } else if (event.data.type == 'status') {
-      ok(event.data.status, event.data.msg);
-    }
+var sw = new SharedWorker('sharedworker_performance_user_timing.js');
+sw.port.onmessage = function(event) {
+  if (event.data.type == 'finish') {
+    SimpleTest.finish();
+  } else if (event.data.type == 'status') {
+    ok(event.data.status, event.data.msg);
   }
-});
+}
 
 SimpleTest.waitForExplicitFinish();
     </script>
   </body>
 </html>
--- a/dom/workers/test/test_sharedWorker_ports.html
+++ b/dom/workers/test/test_sharedWorker_ports.html
@@ -7,36 +7,33 @@
   <head>
     <title>Test for MessagePort and SharedWorkers</title>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   </head>
   <body>
     <script class="testbody" type="text/javascript">
 
-SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]},
-function() {
-  var sw1 = new SharedWorker('sharedWorker_ports.js');
-  sw1.port.onmessage = function(event) {
-    if (event.data.type == "connected") {
-      ok(true, "The SharedWorker is alive.");
+var sw1 = new SharedWorker('sharedWorker_ports.js');
+sw1.port.onmessage = function(event) {
+  if (event.data.type == "connected") {
+    ok(true, "The SharedWorker is alive.");
+
+    var sw2 = new SharedWorker('sharedWorker_ports.js');
+    sw1.port.postMessage("Port from the main-thread!", [sw2.port]);
+    return;
+  }
 
-      var sw2 = new SharedWorker('sharedWorker_ports.js');
-      sw1.port.postMessage("Port from the main-thread!", [sw2.port]);
-      return;
-    }
+  if (event.data.type == "status") {
+    ok(event.data.test, event.data.msg);
+    return;
+  }
 
-    if (event.data.type == "status") {
-      ok(event.data.test, event.data.msg);
-      return;
-    }
-
-    if (event.data.type == "finish") {
-      SimpleTest.finish();
-    }
+  if (event.data.type == "finish") {
+    SimpleTest.finish();
   }
-});
+}
 
 SimpleTest.waitForExplicitFinish();
     </script>
   </body>
 </html>
 
--- a/dom/workers/test/test_sharedWorker_privateBrowsing.html
+++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html
@@ -87,16 +87,15 @@ function runTest() {
   }
 
   var step = steps.shift();
   step();
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({"set": [
-  ["dom.workers.sharedWorkers.enabled", true],
   ["browser.startup.page", 0],
   ["browser.startup.homepage_override.mstone", "ignore"],
 ]}, runTest);
 
 </script>
 </body>
 </html>
--- a/dom/workers/test/test_webSocket_sharedWorker.html
+++ b/dom/workers/test/test_webSocket_sharedWorker.html
@@ -8,26 +8,23 @@
   <title>Test for bug 1090183</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <script class="testbody" type="text/javascript">
 
-SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]},
-function() {
-  var sw = new SharedWorker('webSocket_sharedWorker.js');
-  sw.port.onmessage = function(event) {
-    if (event.data.type == 'finish') {
-      SimpleTest.finish();
-    } else if (event.data.type == 'status') {
-      ok(event.data.status, event.data.msg);
-    }
+var sw = new SharedWorker('webSocket_sharedWorker.js');
+sw.port.onmessage = function(event) {
+  if (event.data.type == 'finish') {
+    SimpleTest.finish();
+  } else if (event.data.type == 'status') {
+    ok(event.data.status, event.data.msg);
   }
-});
+}
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/xbl/nsXBLPrototypeBinding.h
+++ b/dom/xbl/nsXBLPrototypeBinding.h
@@ -13,16 +13,17 @@
 #include "nsICSSLoaderObserver.h"
 #include "nsInterfaceHashtable.h"
 #include "nsWeakReference.h"
 #include "nsXBLDocumentInfo.h"
 #include "nsXBLProtoImpl.h"
 #include "nsXBLProtoImplMethod.h"
 #include "nsXBLPrototypeHandler.h"
 #include "nsXBLPrototypeResources.h"
+#include "mozilla/WeakPtr.h"
 
 class nsIAtom;
 class nsIContent;
 class nsIDocument;
 class nsXBLAttributeEntry;
 class nsXBLBinding;
 class nsXBLProtoImplField;
 
@@ -31,19 +32,22 @@ class CSSStyleSheet;
 } // namespace mozilla
 
 // *********************************************************************/
 // The XBLPrototypeBinding class
 
 // Instances of this class are owned by the nsXBLDocumentInfo object returned
 // by XBLDocumentInfo().  Consumers who want to refcount things should refcount
 // that.
-class nsXBLPrototypeBinding final
+class nsXBLPrototypeBinding final :
+  public mozilla::SupportsWeakPtr<nsXBLPrototypeBinding>
 {
 public:
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsXBLPrototypeBinding)
+
   nsIContent* GetBindingElement() const { return mBinding; }
   void SetBindingElement(nsIContent* aElement);
 
   nsIURI* BindingURI() const { return mBindingURI; }
   nsIURI* AlternateBindingURI() const { return mAlternateBindingURI; }
   nsIURI* DocURI() const { return mXBLDocInfoWeak->DocumentURI(); }
   nsIURI* GetBaseBindingURI() const { return mBaseBindingURI; }
 
@@ -285,17 +289,18 @@ protected:
   nsAutoPtr<nsXBLPrototypeHandler> mPrototypeHandler; // Strong. DocInfo owns us, and we own the handlers.
 
   // the url of the base binding
   nsCOMPtr<nsIURI> mBaseBindingURI;
 
   nsXBLProtoImpl* mImplementation; // Our prototype implementation (includes methods, properties, fields,
                                    // the constructor, and the destructor).
 
-  nsXBLPrototypeBinding* mBaseBinding; // Weak.  The docinfo will own our base binding.
+  // Weak.  The docinfo will own our base binding.
+  mozilla::WeakPtr<nsXBLPrototypeBinding> mBaseBinding;
   bool mInheritStyle;
   bool mCheckedBaseProto;
   bool mKeyHandlersRegistered;
   bool mChromeOnlyContent;
   bool mBindToUntrustedContent;
 
   nsAutoPtr<nsXBLPrototypeResources> mResources; // If we have any resources, this will be non-null.
 
--- a/dom/xbl/nsXBLService.cpp
+++ b/dom/xbl/nsXBLService.cpp
@@ -725,17 +725,18 @@ nsXBLService::GetBinding(nsIContent* aBo
   nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
                                         aOriginPrincipal,
                                         false, getter_AddRefs(docInfo));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!docInfo)
     return NS_ERROR_FAILURE;
 
-  nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref);
+  WeakPtr<nsXBLPrototypeBinding> protoBinding =
+    docInfo->GetPrototypeBinding(ref);
 
   if (!protoBinding) {
 #ifdef DEBUG
     nsAutoCString uriSpec;
     aURI->GetSpec(uriSpec);
     nsAutoCString doc;
     boundDocument->GetDocumentURI()->GetSpec(doc);
     nsAutoCString message("Unable to locate an XBL binding for URI ");
@@ -776,17 +777,17 @@ nsXBLService::GetBinding(nsIContent* aBo
     protoBinding->AddResourceListener(aBoundElement);
     return NS_ERROR_FAILURE; // The binding isn't ready yet.
   }
 
   rv = protoBinding->ResolveBaseBinding();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> baseBindingURI;
-  nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype();
+  WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
   if (baseProto) {
     baseBindingURI = baseProto->BindingURI();
   }
   else {
     baseBindingURI = protoBinding->GetBaseBindingURI();
     if (baseBindingURI) {
       uint32_t count = aDontExtendURIs.Length();
       for (uint32_t index = 0; index < count; ++index) {
@@ -821,17 +822,16 @@ nsXBLService::GetBinding(nsIContent* aBo
     if (NS_FAILED(rv))
       return rv; // We aren't ready yet.
   }
 
   *aIsReady = true;
 
   if (!aPeekOnly) {
     // Make a new binding
-    protoBinding = docInfo->GetPrototypeBinding(ref);
     NS_ENSURE_STATE(protoBinding);
     nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
 
     if (baseBinding) {
       if (!baseProto) {
         protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
       }
        newBinding->SetBaseBinding(baseBinding);
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -913,16 +913,33 @@ XULDocument::AttributeWillChange(nsIDocu
     // See if we need to update our ref map.
     if (aAttribute == nsGkAtoms::ref) {
         // Might not need this, but be safe for now.
         nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
         RemoveElementFromRefMap(aElement);
     }
 }
 
+static bool
+ShouldPersistAttribute(nsIDocument* aDocument, Element* aElement)
+{
+    if (aElement->IsXULElement(nsGkAtoms::window)) {
+        if (nsCOMPtr<nsPIDOMWindow> window = aDocument->GetWindow()) {
+            bool isFullscreen;
+            window->GetFullScreen(&isFullscreen);
+            if (isFullscreen) {
+                // Don't persist attributes if it is window element and
+                // we are in fullscreen state.
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 void
 XULDocument::AttributeChanged(nsIDocument* aDocument,
                               Element* aElement, int32_t aNameSpaceID,
                               nsIAtom* aAttribute, int32_t aModType,
                               const nsAttrValue* aOldValue)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
@@ -987,28 +1004,29 @@ XULDocument::AttributeChanged(nsIDocumen
             }
         }
     }
 
     // checks for modifications in broadcasters
     bool listener, resolved;
     CheckBroadcasterHookup(aElement, &listener, &resolved);
 
-    // See if there is anything we need to persist in the localstore.
-    //
-    // XXX Namespace handling broken :-(
-    nsAutoString persist;
-    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
-    if (!persist.IsEmpty()) {
-        // XXXldb This should check that it's a token, not just a substring.
-        if (persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
+    if (ShouldPersistAttribute(aDocument, aElement)) {
+        // See if there is anything we need to persist in the localstore.
+        //
+        // XXX Namespace handling broken :-(
+        nsString persist;
+        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
+        if (!persist.IsEmpty() &&
+            // XXXldb This should check that it's a token, not just a substring.
+            persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
             nsContentUtils::AddScriptRunner(NS_NewRunnableMethodWithArgs
-              <nsIContent*, int32_t, nsIAtom*>
-              (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None,
-               aAttribute));
+                <nsIContent*, int32_t, nsIAtom*>
+                (this, &XULDocument::DoPersist, aElement,
+                 kNameSpaceID_None, aAttribute));
         }
     }
 }
 
 void
 XULDocument::ContentAppended(nsIDocument* aDocument,
                              nsIContent* aContainer,
                              nsIContent* aFirstNewContent,
--- a/gfx/2d/SourceSurfaceSkia.cpp
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -101,17 +101,17 @@ SourceSurfaceSkia::InitFromTexture(DrawT
                                    SurfaceFormat aFormat)
 {
   MOZ_ASSERT(aOwner, "null GrContext");
 #ifdef USE_SKIA_GPU
   GrBackendTextureDesc skiaTexGlue;
   mSize.width = skiaTexGlue.fWidth = aSize.width;
   mSize.height = skiaTexGlue.fHeight = aSize.height;
   skiaTexGlue.fFlags = kNone_GrBackendTextureFlag;
-  skiaTexGlue.fOrigin = kBottomLeft_GrSurfaceOrigin;
+  skiaTexGlue.fOrigin = kTopLeft_GrSurfaceOrigin;
   skiaTexGlue.fConfig = GfxFormatToGrConfig(aFormat);
   skiaTexGlue.fSampleCnt = 0;
   skiaTexGlue.fTextureHandle = aTexture;
 
   GrTexture *skiaTexture = aOwner->mGrContext->wrapBackendTexture(skiaTexGlue);
   SkImageInfo imgInfo = SkImageInfo::Make(aSize.width, aSize.height, GfxFormatToSkiaColorType(aFormat), kOpaque_SkAlphaType);
   SkGrPixelRef *texRef = new SkGrPixelRef(imgInfo, skiaTexture, false);
   mBitmap.setInfo(imgInfo, aSize.width*aSize.height*4);
--- a/gfx/layers/apz/src/WheelScrollAnimation.cpp
+++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp
@@ -73,16 +73,17 @@ WheelScrollAnimation::InitPreferences(Ti
 {
   if (!mIsFirstIteration) {
     return;
   }
 
   mOriginMaxMS = clamped(gfxPrefs::WheelSmoothScrollMaxDurationMs(), 0, 10000);
   mOriginMinMS = clamped(gfxPrefs::WheelSmoothScrollMinDurationMs(), 0, mOriginMaxMS);
 
-  mIntervalRatio = (gfxPrefs::SmoothScrollDurationToIntervalRatio() * 100) / 100.0;
+  // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio
+  mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0;
   mIntervalRatio = std::max(1.0, mIntervalRatio);
 
   InitializeHistory(aTime);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/opengl/OGLShaderProgram.cpp
+++ b/gfx/layers/opengl/OGLShaderProgram.cpp
@@ -164,22 +164,27 @@ ShaderConfigOGL::SetDEAA(bool aEnabled)
 /* static */ ProgramProfileOGL
 ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig)
 {
   ProgramProfileOGL result;
   ostringstream fs, vs;
 
   AddUniforms(result);
 
+  vs << "#ifdef GL_ES" << endl;
+  vs << "#define EDGE_PRECISION mediump" << endl;
+  vs << "#else" << endl;
+  vs << "#define EDGE_PRECISION" << endl;
+  vs << "#endif" << endl;
   vs << "uniform mat4 uMatrixProj;" << endl;
   vs << "uniform vec4 uLayerRects[4];" << endl;
   vs << "uniform mat4 uLayerTransform;" << endl;
   if (aConfig.mFeatures & ENABLE_DEAA) {
     vs << "uniform mat4 uLayerTransformInverse;" << endl;
-    vs << "uniform vec3 uSSEdges[4];" << endl;
+    vs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl;
     vs << "uniform vec2 uVisibleCenter;" << endl;
     vs << "uniform vec2 uViewportSize;" << endl;
   }
   vs << "uniform vec2 uRenderTargetOffset;" << endl;
   vs << "attribute vec4 aCoord;" << endl;
 
   if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
     vs << "uniform mat4 uTextureTransform;" << endl;
@@ -277,18 +282,20 @@ ProgramProfileOGL::GetProfileFor(ShaderC
     fs << "#extension GL_ARB_texture_rectangle : require" << endl;
   }
   if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) {
     fs << "#extension GL_OES_EGL_image_external : require" << endl;
   }
   fs << "#ifdef GL_ES" << endl;
   fs << "precision mediump float;" << endl;
   fs << "#define COLOR_PRECISION lowp" << endl;
+  fs << "#define EDGE_PRECISION mediump" << endl;
   fs << "#else" << endl;
   fs << "#define COLOR_PRECISION" << endl;
+  fs << "#define EDGE_PRECISION" << endl;
   fs << "#endif" << endl;
   if (aConfig.mFeatures & ENABLE_RENDER_COLOR) {
     fs << "uniform COLOR_PRECISION vec4 uRenderColor;" << endl;
   } else {
     // for tiling, texcoord can be greater than the lowfp range
     fs << "varying vec2 vTexCoord;" << endl;
     if (aConfig.mFeatures & ENABLE_BLUR) {
       fs << "uniform bool uBlurAlpha;" << endl;
@@ -339,17 +346,17 @@ ProgramProfileOGL::GetProfileFor(ShaderC
 
   if (aConfig.mFeatures & ENABLE_MASK_2D ||
       aConfig.mFeatures & ENABLE_MASK_3D) {
     fs << "varying vec3 vMaskCoord;" << endl;
     fs << "uniform sampler2D uMaskTexture;" << endl;
   }
 
   if (aConfig.mFeatures & ENABLE_DEAA) {
-    fs << "uniform vec3 uSSEdges[4];" << endl;
+    fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl;
   }
 
   if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) {
     fs << "vec4 sample(vec2 coord) {" << endl;
     fs << "  vec4 color;" << endl;
     if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR ||
         aConfig.mFeatures & ENABLE_TEXTURE_NV12) {
       if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) {
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -61,17 +61,17 @@ private:
         if (aMetrics->GetVertical()) {
             switch (aMetrics->GetTextOrientation()) {
             case NS_STYLE_TEXT_ORIENTATION_MIXED:
                 flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED;
                 break;
             case NS_STYLE_TEXT_ORIENTATION_UPRIGHT:
                 flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
                 break;
-            case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT:
+            case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS:
                 flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
                 break;
             }
         }
         return flags;
     }
 
     nsAutoPtr<gfxTextRun> mTextRun;
--- a/gfx/tests/reftest/1143303-1.svg
+++ b/gfx/tests/reftest/1143303-1.svg
@@ -1,20 +1,14 @@
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <svg version="1.1" xmlns="http://www.w3.org/2000/svg"
      width="100%" height="100%">
-<!-- There is a bug with MultiTiledContentClient that causes improper painting;
-     bug 1204076 is on file for this issue. As a temporary workaround, we set
-     user-scalable=no here so that WantAsyncZoom() returns false and we don't
-     use a MultiTiledContentClient. Once bug 1204076 is fixed, we can remove this. -->
-  <meta xmlns="http://www.w3.org/1999/xhtml" name="viewport" content="user-scalable=no"/>
-
   <title>Testcase for small circles</title>
   <!--From https://bugzilla.mozilla.org/show_bug.cgi?id=1143303 -->
 
   <rect width="100%" height="100%" fill="lime"/>
 
   <circle cx="200" cy="150" r="95" fill="red"/>
   <g transform="translate(200, 150)" fill="lime">
     <g transform="scale(1e8, 1e8)">
--- a/ipc/chromium/moz.build
+++ b/ipc/chromium/moz.build
@@ -1,44 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-os_win = 0
-os_posix = 0
-os_macosx = 0
-os_dragonfly = 0
-os_freebsd = 0
-os_netbsd = 0
-os_openbsd = 0
-os_bsd = 0
-os_linux = 0
-
-if CONFIG['OS_ARCH'] == 'WINNT':
-    os_win = 1
-else:
-    os_posix = 1
-    if CONFIG['OS_ARCH'] == 'Darwin':
-        os_macosx = 1
-    elif CONFIG['OS_ARCH'] == 'DragonFly':
-        os_dragonfly = 1
-        os_bsd = 1
-    elif CONFIG['OS_ARCH'] in ['FreeBSD', 'GNU_kFreeBSD']:
-        os_freebsd = 1
-        os_bsd = 1
-    elif CONFIG['OS_ARCH'] == 'NetBSD':
-        os_netbsd = 1
-        os_bsd = 1
-    elif CONFIG['OS_ARCH'] == 'OpenBSD':
-        os_openbsd = 1
-        os_bsd = 1
-    else:
-        os_linux = 1
+libevent_path_prefix = 'src/third_party'
+include(libevent_path_prefix + '/libeventcommon.mozbuild')
 
 UNIFIED_SOURCES += [
     'src/base/at_exit.cc',
     'src/base/base_switches.cc',
     'src/base/command_line.cc',
     'src/base/file_path.cc',
     'src/base/file_util.cc',
     'src/base/histogram.cc',
@@ -97,46 +69,17 @@ if os_win:
         'src/base/waitable_event_watcher_win.cc',
         'src/base/waitable_event_win.cc',
         'src/base/win_util.cc',
         'src/chrome/common/ipc_channel_win.cc',
         'src/chrome/common/process_watcher_win.cc',
         'src/chrome/common/transport_dib_win.cc',
     ]
 elif not CONFIG['MOZ_NATIVE_LIBEVENT']:
-    UNIFIED_SOURCES += [
-        'src/third_party/libevent/buffer.c',
-        'src/third_party/libevent/bufferevent.c',
-        'src/third_party/libevent/bufferevent_ratelim.c',
-        'src/third_party/libevent/bufferevent_sock.c',
-        'src/third_party/libevent/event.c',
-        'src/third_party/libevent/event_tagging.c',
-        'src/third_party/libevent/evmap.c',
-        'src/third_party/libevent/evrpc.c',
-        'src/third_party/libevent/evthread.c',
-        'src/third_party/libevent/evthread_pthread.c',
-        'src/third_party/libevent/evutil.c',
-        'src/third_party/libevent/evutil_rand.c',
-        'src/third_party/libevent/http.c',
-        'src/third_party/libevent/listener.c',
-        'src/third_party/libevent/log.c',
-        'src/third_party/libevent/poll.c',
-        'src/third_party/libevent/select.c',
-        'src/third_party/libevent/signal.c',
-        'src/third_party/libevent/strlcpy.c',
-    ]
-    SOURCES += [
-        # This file cannot be built in unified mode because of strtotimeval symbol clash.
-        'src/third_party/libevent/evdns.c',
-    ]
-    DEFINES['HAVE_CONFIG_H'] = True
-    LOCAL_INCLUDES += [
-        'src/third_party/libevent',
-        'src/third_party/libevent/include',
-    ]
+    DIRS += ['src/third_party']
 
 if os_posix:
     UNIFIED_SOURCES += [
         'src/base/condition_variable_posix.cc',
         'src/base/file_descriptor_shuffle.cc',
         'src/base/file_util_posix.cc',
         'src/base/lock_impl_posix.cc',
         'src/base/message_pump_libevent.cc',
@@ -151,22 +94,16 @@ if os_posix:
         'src/base/thread_local_posix.cc',
         'src/base/thread_local_storage_posix.cc',
         'src/base/waitable_event_posix.cc',
         'src/base/waitable_event_watcher_posix.cc',
         'src/chrome/common/file_descriptor_set_posix.cc',
         'src/chrome/common/ipc_channel_posix.cc',
         'src/chrome/common/process_watcher_posix_sigchld.cc',
     ]
-    if CONFIG['OS_TARGET'] == 'Android':
-        UNIFIED_SOURCES += [
-            'src/base/message_pump_android.cc',
-        ]
-        DEFINES['ANDROID'] = True
-        DEFINES['_POSIX_MONOTONIC_CLOCK'] = 0
 
 if os_macosx:
     UNIFIED_SOURCES += [
         'src/base/chrome_application_mac.mm',
         'src/base/file_util_mac.mm',
         'src/base/idle_timer.cc',
         'src/base/mac_util.mm',
         'src/base/message_pump_mac.mm',
@@ -175,81 +112,59 @@ if os_macosx:
         'src/base/sys_info_mac.cc',
         'src/base/sys_string_conversions_mac.mm',
         'src/base/time_mac.cc',
         'src/chrome/common/mach_ipc_mac.mm',
         'src/chrome/common/mach_message_source_mac.cc',
         'src/chrome/common/transport_dib_mac.cc',
     ]
     SOURCES += [
-        # This file cannot be built in unified mode because of the redefinition of NoOp.
+        # This file cannot be built in unified mode because of the redefinition
+        # of NoOp.
         'src/base/platform_thread_mac.mm',
     ]
-    if not CONFIG['MOZ_NATIVE_LIBEVENT']:
-        UNIFIED_SOURCES += [
-            'src/third_party/libevent/kqueue.c',
-        ]
-        LOCAL_INCLUDES += ['src/third_party/libevent/mac']
-
-if os_linux:
-    SOURCES += [
-        'src/base/atomicops_internals_x86_gcc.cc',
-        'src/base/idle_timer_none.cc',
-        'src/base/process_util_linux.cc',
-        'src/base/time_posix.cc',
-    ]
-    if CONFIG['MOZ_WIDGET_GTK']:
-        SOURCES += [
-            'src/base/message_pump_glib.cc',
-        ]
-    if CONFIG['MOZ_ENABLE_QT']:
-        SOURCES += [
-            '!moc_message_pump_qt.cc',
-            'src/base/message_pump_qt.cc',
-        ]
-    if not CONFIG['MOZ_NATIVE_LIBEVENT']:
-        if CONFIG['OS_TARGET'] != 'Android':
-            SOURCES += [
-                'src/third_party/libevent/epoll_sub.c',
-            ]
-        SOURCES += [
-            'src/third_party/libevent/epoll.c',
-        ]
-        if CONFIG['OS_TARGET'] == 'Android':
-            LOCAL_INCLUDES += ['src/third_party/libevent/android']
-        else:
-            LOCAL_INCLUDES += ['src/third_party/libevent/linux']
 
 if os_bsd:
     SOURCES += [
         'src/base/atomicops_internals_x86_gcc.cc',
         'src/base/time_posix.cc',
     ]
     if CONFIG['OS_ARCH'] == 'GNU_kFreeBSD':
         SOURCES += [
             'src/base/process_util_linux.cc'
         ]
     else:
         SOURCES += [
             'src/base/process_util_bsd.cc'
         ]
+
+if os_linux:
+    SOURCES += [
+        'src/base/atomicops_internals_x86_gcc.cc',
+        'src/base/idle_timer_none.cc',
+        'src/base/process_util_linux.cc',
+        'src/base/time_posix.cc',
+    ]
+    if CONFIG['OS_TARGET'] == 'Android':
+        UNIFIED_SOURCES += [
+            'src/base/message_pump_android.cc',
+        ]
+        DEFINES['ANDROID'] = True
+        DEFINES['_POSIX_MONOTONIC_CLOCK'] = 0
+
+if os_bsd or os_linux:
     if CONFIG['MOZ_WIDGET_GTK']:
         SOURCES += [
             'src/base/message_pump_glib.cc',
         ]
     if CONFIG['MOZ_ENABLE_QT']:
         SOURCES += [
             '!moc_message_pump_qt.cc',
             'src/base/message_pump_qt.cc',
         ]
-    if not CONFIG['MOZ_NATIVE_LIBEVENT']:
-        SOURCES += [
-            'src/third_party/libevent/kqueue.c',
-        ]
-        LOCAL_INCLUDES += ['src/third_party/libevent/bsd']
 
 ost = CONFIG['OS_TEST']
 if '86' not in ost and 'arm' not in ost and 'mips' not in ost:
     SOURCES += [
         'src/base/atomicops_internals_mutex.cc',
     ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644
--- /dev/null
+++ b/ipc/chromium/src/third_party/libeventcommon.mozbuild
@@ -0,0 +1,37 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+os_win = 0
+os_posix = 0
+os_macosx = 0
+os_bsd = 0
+os_linux = 0
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+    os_win = 1
+else:
+    os_posix = 1
+    if CONFIG['OS_ARCH'] == 'Darwin':
+        os_macosx = 1
+        libevent_include_suffix = 'mac'
+    elif CONFIG['OS_ARCH'] in ['DragonFly', 'FreeBSD', 'GNU_kFreeBSD',
+                               'NetBSD', 'OpenBSD']:
+        os_bsd = 1
+        libevent_include_suffix = 'bsd'
+    else:
+        os_linux = 1
+        if CONFIG['OS_TARGET'] == 'Android':
+            libevent_include_suffix = 'android'
+        else:
+            libevent_include_suffix = 'linux'
+
+if os_posix and not CONFIG['MOZ_NATIVE_LIBEVENT']:
+    DEFINES['HAVE_CONFIG_H'] = True
+    LOCAL_INCLUDES += sorted([
+        libevent_path_prefix + '/libevent',
+        libevent_path_prefix + '/libevent/include',
+        libevent_path_prefix + '/libevent/' + libevent_include_suffix,
+    ])
new file mode 100644
--- /dev/null
+++ b/ipc/chromium/src/third_party/moz.build
@@ -0,0 +1,59 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+libevent_path_prefix = '.'
+include(libevent_path_prefix + '/libeventcommon.mozbuild')
+
+if os_win:
+    error('should not reach here on Windows')
+
+if CONFIG['MOZ_NATIVE_LIBEVENT']:
+    error('should not reach here if we are using a native libevent')
+
+UNIFIED_SOURCES += [
+    'libevent/buffer.c',
+    'libevent/bufferevent.c',
+    'libevent/bufferevent_ratelim.c',
+    'libevent/bufferevent_sock.c',
+    'libevent/event.c',
+    'libevent/event_tagging.c',
+    'libevent/evmap.c',
+    'libevent/evrpc.c',
+    'libevent/evthread.c',
+    'libevent/evthread_pthread.c',
+    'libevent/evutil.c',
+    'libevent/evutil_rand.c',
+    'libevent/http.c',
+    'libevent/listener.c',
+    'libevent/log.c',
+    'libevent/poll.c',
+    'libevent/select.c',
+    'libevent/signal.c',
+    'libevent/strlcpy.c',
+]
+SOURCES += [
+    # This file cannot be built in unified mode because of strtotimeval
+    # symbol clash.
+    'libevent/evdns.c',
+]
+
+if os_macosx or os_bsd:
+    UNIFIED_SOURCES += [
+        'libevent/kqueue.c',
+    ]
+
+if os_linux:
+    SOURCES += [
+        'libevent/epoll.c',
+    ]
+    if CONFIG['OS_TARGET'] != 'Android':
+        SOURCES += [
+            'libevent/epoll_sub.c',
+        ]
+
+ALLOW_COMPILER_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
--- a/ipc/glue/CrossProcessMutex.h
+++ b/ipc/glue/CrossProcessMutex.h
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_CrossProcessMutex_h
 #define mozilla_CrossProcessMutex_h
 
 #include "base/process.h"
 #include "mozilla/Mutex.h"
 
-#if defined(OS_LINUX) || defined(OS_MACOSX)
+#if defined(OS_LINUX) || defined(XP_DARWIN)
 #include <pthread.h>
 #include "SharedMemoryBasic.h"
 #include "mozilla/Atomics.h"
 #include "nsAutoPtr.h"
 #endif
 
 namespace IPC {
 template<typename T>
new file mode 100644
--- /dev/null
+++ b/ipc/glue/Makefile.in
@@ -0,0 +1,1 @@
+%/SharedMemoryBasic_mach.cpp: ;
--- a/ipc/glue/SharedMemoryBasic.h
+++ b/ipc/glue/SharedMemoryBasic.h
@@ -5,15 +5,15 @@
  * 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 mozilla_ipc_SharedMemoryBasic_h
 #define mozilla_ipc_SharedMemoryBasic_h
 
 #ifdef ANDROID
 #  include "mozilla/ipc/SharedMemoryBasic_android.h"
-#elif defined(XP_MACOSX)
+#elif defined(XP_DARWIN)
 #  include "mozilla/ipc/SharedMemoryBasic_mach.h"
 #else
 #  include "mozilla/ipc/SharedMemoryBasic_chromium.h"
 #endif
 
 #endif // ifndef mozilla_ipc_SharedMemoryBasic_h
rename from ipc/glue/SharedMemoryBasic_mach.cpp
rename to ipc/glue/SharedMemoryBasic_mach.mm
--- a/ipc/glue/SharedMemoryBasic_mach.cpp
+++ b/ipc/glue/SharedMemoryBasic_mach.mm
@@ -4,17 +4,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <map>
 
 #include <mach/vm_map.h>
 #include <mach/mach_port.h>
+#if defined(XP_IOS)
+#include <mach/vm_map.h>
+#define mach_vm_address_t vm_address_t
+#define mach_vm_allocate vm_allocate
+#define mach_vm_deallocate vm_deallocate
+#define mach_vm_map vm_map
+#define mach_vm_read vm_read
+#define mach_vm_region_recurse vm_region_recurse_64
+#define mach_vm_size_t vm_size_t
+#else
 #include <mach/mach_vm.h>
+#endif
 #include <pthread.h>
 #include <unistd.h>
 #include "SharedMemoryBasic.h"
 #include "chrome/common/mach_ipc_mac.h"
 
 #include "mozilla/StaticMutex.h"
 
 #ifdef DEBUG
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -75,17 +75,17 @@ else:
 if CONFIG['OS_TARGET'] == 'Android':
     EXPORTS.mozilla.ipc += ['SharedMemoryBasic_android.h']
     UNIFIED_SOURCES += [
         'SharedMemoryBasic_android.cpp',
     ]
 elif CONFIG['OS_ARCH'] == 'Darwin':
     EXPORTS.mozilla.ipc += ['SharedMemoryBasic_mach.h']
     SOURCES += [
-        'SharedMemoryBasic_mach.cpp',
+        'SharedMemoryBasic_mach.mm',
     ]
 else:
     EXPORTS.mozilla.ipc += ['SharedMemoryBasic_chromium.h']
 
 if CONFIG['OS_ARCH'] == 'Linux':
     UNIFIED_SOURCES += [
         'ProcessUtils_linux.cpp',
     ]
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -698,17 +698,18 @@ struct JSClass {
 // Implementing this efficiently requires that global objects have classes
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
-#define JSCLASS_GLOBAL_SLOT_COUNT      (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 32)
+#define JSCLASS_GLOBAL_SLOT_COUNT                                             \
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 33)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -564,17 +564,17 @@ class Base {
     using Size = uint64_t;
     virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; }
 
     // Return an EdgeRange that initially contains all the referent's outgoing
     // edges. The caller takes ownership of the EdgeRange.
     //
     // If wantNames is true, compute names for edges. Doing so can be expensive
     // in time and memory.
-    virtual UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const = 0;
+    virtual UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const = 0;
 
     // Return the Zone to which this node's referent belongs, or nullptr if the
     // referent is not of a type allocated in SpiderMonkey Zones.
     virtual JS::Zone* zone() const { return nullptr; }
 
     // Return the compartment for this node. Some ubi::Node referents are not
     // associated with JSCompartments, such as JSStrings (which are associated
     // with Zones). When the referent is not associated with a compartment,
@@ -751,18 +751,18 @@ class Node {
         return base()->jsObjectConstructorName(cx, outName);
     }
 
     using Size = Base::Size;
     Size size(mozilla::MallocSizeOf mallocSizeof) const {
         return base()->size(mallocSizeof);
     }
 
-    UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const {
-        return base()->edges(cx, wantNames);
+    UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const {
+        return base()->edges(rt, wantNames);
     }
 
     bool hasAllocationStack() const { return base()->hasAllocationStack(); }
     StackFrame allocationStack() const {
         return base()->allocationStack();
     }
 
     using Id = Base::Id;
@@ -863,32 +863,32 @@ class EdgeRange {
     virtual void popFront() = 0;
 
   private:
     EdgeRange(const EdgeRange&) = delete;
     EdgeRange& operator=(const EdgeRange&) = delete;
 };
 
 
-typedef mozilla::Vector<Edge, 8, js::TempAllocPolicy> EdgeVector;
+typedef mozilla::Vector<Edge, 8, js::SystemAllocPolicy> EdgeVector;
 
 // An EdgeRange concrete class that holds a pre-existing vector of
 // Edges. A PreComputedEdgeRange does not take ownership of its
 // EdgeVector; it is up to the PreComputedEdgeRange's consumer to manage
 // that lifetime.
 class PreComputedEdgeRange : public EdgeRange {
     EdgeVector& edges;
     size_t      i;
 
     void settle() {
         front_ = i < edges.length() ? &edges[i] : nullptr;
     }
 
   public:
-    explicit PreComputedEdgeRange(JSContext* cx, EdgeVector& edges)
+    explicit PreComputedEdgeRange(EdgeVector& edges)
       : edges(edges),
         i(0)
     {
         settle();
     }
 
     void popFront() override {
         MOZ_ASSERT(!empty());
@@ -911,36 +911,36 @@ class PreComputedEdgeRange : public Edge
 // has been created, GC must not occur, as the referent ubi::Nodes are not
 // stable across GC. The init calls emplace on |noGC|'s AutoCheckCannotGC, whose
 // lifetime must extend at least as long as the RootList itself.
 //
 // Example usage:
 //
 //    {
 //        mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC;
-//        JS::ubi::RootList rootList(cx, maybeNoGC);
+//        JS::ubi::RootList rootList(rt, maybeNoGC);
 //        if (!rootList.init())
 //            return false;
 //
 //        // The AutoCheckCannotGC is guaranteed to exist if init returned true.
 //        MOZ_ASSERT(maybeNoGC.isSome());
 //
 //        JS::ubi::Node root(&rootList);
 //
 //        ...
 //    }
 class MOZ_STACK_CLASS RootList {
     Maybe<AutoCheckCannotGC>& noGC;
-    JSContext*               cx;
 
   public:
+    JSRuntime* rt;
     EdgeVector edges;
     bool       wantNames;
 
-    RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
+    RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
 
     // Find all GC roots.
     bool init();
     // Find only GC roots in the provided set of |Zone|s.
     bool init(ZoneSet& debuggees);
     // Find only GC roots in the given Debugger object's set of debuggee zones.
     bool init(HandleObject debuggees);
 
@@ -954,34 +954,34 @@ class MOZ_STACK_CLASS RootList {
     bool addRoot(Node node, const char16_t* edgeName = nullptr);
 };
 
 
 /*** Concrete classes for ubi::Node referent types ************************************************/
 
 template<>
 struct Concrete<RootList> : public Base {
-    UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
+    UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
     const char16_t* typeName() const override { return concreteTypeName; }
 
   protected:
     explicit Concrete(RootList* ptr) : Base(ptr) { }
     RootList& get() const { return *static_cast<RootList*>(ptr); }
 
   public:
     static const char16_t concreteTypeName[];
     static void construct(void* storage, RootList* ptr) { new (storage) Concrete(ptr); }
 };
 
 // A reusable ubi::Concrete specialization base class for types supported by
 // JS::TraceChildren.
 template<typename Referent>
 class TracerConcrete : public Base {
     const char16_t* typeName() const override { return concreteTypeName; }
-    UniquePtr<EdgeRange> edges(JSContext*, bool wantNames) const override;
+    UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
     JS::Zone* zone() const override;
 
   protected:
     explicit TracerConcrete(Referent* ptr) : Base(ptr) { }
     Referent& get() const { return *static_cast<Referent*>(ptr); }
 
   public:
     static const char16_t concreteTypeName[];
@@ -1064,17 +1064,17 @@ template<> struct Concrete<JSString> : T
     static void construct(void *storage, JSString *ptr) { new (storage) Concrete(ptr); }
 };
 
 // The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts.
 template<>
 class Concrete<void> : public Base {
     const char16_t* typeName() const override;
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
-    UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
+    UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override;
     JS::Zone* zone() const override;
     JSCompartment* compartment() const override;
     CoarseType coarseType() const final;
 
     explicit Concrete(void* ptr) : Base(ptr) { }
 
   public:
     static void construct(void* storage, void* ptr) { new (storage) Concrete(ptr); }
--- a/js/public/UbiNodeTraverse.h
+++ b/js/public/UbiNodeTraverse.h
@@ -73,23 +73,23 @@ namespace ubi {
 //      The visitor function should return true on success, or false if an
 //      error occurs. A false return value terminates the traversal
 //      immediately, and causes BreadthFirst<Handler>::traverse to return
 //      false.
 template<typename Handler>
 struct BreadthFirst {
 
     // Construct a breadth-first traversal object that reports the nodes it
-    // reaches to |handler|. The traversal object reports OOM on |cx|, and
-    // asserts that no GC happens in |cx|'s runtime during its lifetime.
+    // reaches to |handler|. The traversal asserts that no GC happens in its
+    // runtime during its lifetime.
     //
     // We do nothing with noGC, other than require it to exist, with a lifetime
     // that encloses our own.
-    BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoCheckCannotGC& noGC)
-      : wantNames(true), cx(cx), visited(cx), handler(handler), pending(cx),
+    BreadthFirst(JSRuntime* rt, Handler& handler, const JS::AutoCheckCannotGC& noGC)
+      : wantNames(true), rt(rt), visited(), handler(handler), pending(),
         traversalBegun(false), stopRequested(false), abandonRequested(false)
     { }
 
     // Initialize this traversal object. Return false on OOM.
     bool init() { return visited.init(); }
 
     // Add |node| as a starting point for the traversal. You may add
     // as many starting points as you like. Return false on OOM.
@@ -121,17 +121,17 @@ struct BreadthFirst {
         traversalBegun = true;
 
         // While there are pending nodes, visit them.
         while (!pending.empty()) {
             Node origin = pending.front();
             pending.popFront();
 
             // Get a range containing all origin's outgoing edges.
-            auto range = origin.edges(cx, wantNames);
+            auto range = origin.edges(rt, wantNames);
             if (!range)
                 return false;
 
             // Traverse each edge.
             for (; !range->empty(); range->popFront()) {
                 MOZ_ASSERT(!stopRequested);
 
                 const Edge& edge = range->front();
@@ -176,38 +176,39 @@ struct BreadthFirst {
     // error.
     void stop() { stopRequested = true; }
 
     // Request that the current edge's referent's outgoing edges not be
     // traversed. This must be called the first time that referent is reached.
     // Other edges *to* that referent will still be traversed.
     void abandonReferent() { abandonRequested = true; }
 
-    // The context with which we were constructed.
-    JSContext* cx;
+    // The runtime with which we were constructed.
+    JSRuntime* rt;
 
     // A map associating each node N that we have reached with a
     // Handler::NodeData, for |handler|'s use. This is public, so that
     // |handler| can access it to see the traversal thus far.
-    typedef js::HashMap<Node, typename Handler::NodeData> NodeMap;
+    using NodeMap = js::HashMap<Node, typename Handler::NodeData, js::DefaultHasher<Node>,
+                                js::SystemAllocPolicy>;
     NodeMap visited;
 
   private:
     // Our handler object.
     Handler& handler;
 
     // A queue template. Appending and popping the front are constant time.
     // Wasted space is never more than some recent actual population plus the
     // current population.
     template <typename T>
     class Queue {
-        js::Vector<T, 0> head, tail;
+        js::Vector<T, 0, js::SystemAllocPolicy> head, tail;
         size_t frontIndex;
       public:
-        explicit Queue(JSContext* cx) : head(cx), tail(cx), frontIndex(0) { }
+        Queue() : head(), tail(), frontIndex(0) { }
         bool empty() { return frontIndex >= head.length(); }
         T& front() {
             MOZ_ASSERT(!empty());
             return head[frontIndex];
         }
         void popFront() {
             MOZ_ASSERT(!empty());
             frontIndex++;
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -286,16 +286,17 @@ selfhosting_srcs := \
   $(srcdir)/builtin/Array.js \
   $(srcdir)/builtin/Date.js \
   $(srcdir)/builtin/Error.js \
   $(srcdir)/builtin/Generator.js \
   $(srcdir)/builtin/Intl.js \
   $(srcdir)/builtin/IntlData.js \
   $(srcdir)/builtin/Iterator.js \
   $(srcdir)/builtin/Map.js \
+  $(srcdir)/builtin/Module.js \
   $(srcdir)/builtin/Number.js \
   $(srcdir)/builtin/Object.js \
   $(srcdir)/builtin/Reflect.js \
   $(srcdir)/builtin/RegExp.js \
   $(srcdir)/builtin/String.js \
   $(srcdir)/builtin/Set.js \
   $(srcdir)/builtin/TypedArray.js \
   $(srcdir)/builtin/TypedObject.js \
--- a/js/src/asmjs/AsmJSSignalHandlers.cpp
+++ b/js/src/asmjs/AsmJSSignalHandlers.cpp
@@ -196,16 +196,17 @@ class AutoSetHandlingSignal
 # if defined(__FreeBSD__) && defined(__arm__)
 #  define R15_sig(p) ((p)->uc_mcontext.__gregs[_REG_R15])
 # else
 #  define R15_sig(p) ((p)->uc_mcontext.mc_r15)
 # endif
 #elif defined(XP_DARWIN)
 # define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip)
 # define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip)
+# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc)
 #else
 # error "Don't know how to read/write to the thread state via the mcontext_t."
 #endif
 
 #if defined(XP_WIN)
 # include "jswin.h"
 #else
 # include <signal.h>
@@ -315,22 +316,30 @@ enum { REG_EIP = 14 };
 // sigaction-style signal handler.
 #if defined(XP_DARWIN) && defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
 # if defined(JS_CODEGEN_X64)
 struct macos_x64_context {
     x86_thread_state64_t thread;
     x86_float_state64_t float_;
 };
 #  define EMULATOR_CONTEXT macos_x64_context
-# else
+# elif defined(JS_CODEGEN_X86)
 struct macos_x86_context {
     x86_thread_state_t thread;
     x86_float_state_t float_;
 };
 #  define EMULATOR_CONTEXT macos_x86_context
+# elif defined(JS_CODEGEN_ARM)
+struct macos_arm_context {
+    arm_thread_state_t thread;
+    arm_neon_state_t float_;
+};
+#  define EMULATOR_CONTEXT macos_arm_context
+# else
+#  error Unsupported architecture
 # endif
 #else
 # define EMULATOR_CONTEXT CONTEXT
 #endif
 
 #if defined(JS_CPU_X64)
 # define PC_sig(p) RIP_sig(p)
 #elif defined(JS_CPU_X86)
@@ -798,20 +807,26 @@ AsmJSFaultHandler(LPEXCEPTION_POINTERS e
 
 static uint8_t**
 ContextToPC(EMULATOR_CONTEXT* context)
 {
 # if defined(JS_CPU_X64)
     static_assert(sizeof(context->thread.__rip) == sizeof(void*),
                   "stored IP should be compile-time pointer-sized");
     return reinterpret_cast<uint8_t**>(&context->thread.__rip);
-# else
+# elif defined(JS_CPU_X86)
     static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*),
                   "stored IP should be compile-time pointer-sized");
     return reinterpret_cast<uint8_t**>(&context->thread.uts.ts32.__eip);
+# elif defined(JS_CPU_ARM)
+    static_assert(sizeof(context->thread.__pc) == sizeof(void*),
+                  "stored IP should be compile-time pointer-sized");
+    return reinterpret_cast<uint8_t**>(&context->thread.__pc);
+# else
+#  error Unsupported architecture
 # endif
 }
 
 // This definition was generated by mig (the Mach Interface Generator) for the
 // routine 'exception_raise' (exc.defs).
 #pragma pack(4)
 typedef struct {
     mach_msg_header_t Head;
@@ -847,21 +862,28 @@ HandleMachException(JSRuntime* rt, const
 
     // Read out the JSRuntime thread's register state.
     EMULATOR_CONTEXT context;
 # if defined(JS_CODEGEN_X64)
     unsigned int thread_state_count = x86_THREAD_STATE64_COUNT;
     unsigned int float_state_count = x86_FLOAT_STATE64_COUNT;
     int thread_state = x86_THREAD_STATE64;
     int float_state = x86_FLOAT_STATE64;
-# else
+# elif defined(JS_CODEGEN_X86)
     unsigned int thread_state_count = x86_THREAD_STATE_COUNT;
     unsigned int float_state_count = x86_FLOAT_STATE_COUNT;
     int thread_state = x86_THREAD_STATE;
     int float_state = x86_FLOAT_STATE;
+# elif defined(JS_CODEGEN_ARM)
+    unsigned int thread_state_count = ARM_THREAD_STATE_COUNT;
+    unsigned int float_state_count = ARM_NEON_STATE_COUNT;
+    int thread_state = ARM_THREAD_STATE;
+    int float_state = ARM_NEON_STATE;
+# else
+#  error Unsupported architecture
 # endif
     kern_return_t kret;
     kret = thread_get_state(rtThread, thread_state,
                             (thread_state_t)&context.thread, &thread_state_count);
     if (kret != KERN_SUCCESS)
         return false;
     kret = thread_get_state(rtThread, float_state,
                             (thread_state_t)&context.float_, &float_state_count);
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Module.js
@@ -0,0 +1,171 @@
+/* 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/. */
+
+function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = [])
+{
+    // 15.2.1.16.3 ResolveExport(exportName, resolveSet, exportStarSet)
+
+    if (!IsObject(this) || !IsModule(this)) {
+        return callFunction(CallModuleMethodIfWrapped, this, exportName, resolveSet,
+                            exportStarSet, "ModuleResolveExport");
+    }
+
+    // Step 1
+    let module = this;
+
+    // Step 2
+    for (let i = 0; i < resolveSet.length; i++) {
+        let r = resolveSet[i];
+        if (r.module === module && r.exportName === exportName)
+            return null;
+    }
+
+    // Step 3
+    resolveSet.push({module: module, exportName: exportName});
+
+    // Step 4
+    let localExportEntries = module.localExportEntries;
+    for (let i = 0; i < localExportEntries.length; i++) {
+        let e = localExportEntries[i];
+        if (exportName === e.exportName)
+            return {module: module, bindingName: e.localName};
+    }
+
+    // Step 5
+    let indirectExportEntries = module.indirectExportEntries;
+    for (let i = 0; i < indirectExportEntries.length; i++) {
+        let e = indirectExportEntries[i];
+        if (exportName === e.exportName) {
+            let importedModule = HostResolveImportedModule(module, e.moduleRequest);
+            let indirectResolution = importedModule.resolveExport(e.importName,
+                                                                  resolveSet,
+                                                                  exportStarSet);
+            if (indirectResolution !== null)
+                return indirectResolution;
+        }
+    }
+
+    // Step 6
+    if (exportName === "default") {
+        // A default export cannot be provided by an export *.
+        ThrowSyntaxError(JSMSG_BAD_DEFAULT_EXPORT);
+    }
+
+    // Step 7
+    if (module in exportStarSet)
+        return null;
+
+    // Step 8
+    exportStarSet.push(module);
+
+    // Step 9
+    let starResolution = null;
+
+    // Step 10
+    let starExportEntries = module.starExportEntries;
+    for (let i = 0; i < starExportEntries.length; i++) {
+        let e = starExportEntries[i];
+        let importedModule = HostResolveImportedModule(module, e.moduleRequest);
+        let resolution = importedModule.resolveExport(exportName, resolveSet, exportStarSet);
+        if (resolution === "ambiguous")
+            return resolution;
+
+        if (resolution !== null) {
+            if (starResolution === null) {
+                starResolution = resolution;
+            } else {
+                if (resolution.module !== starResolution.module ||
+                    resolution.exportName !== starResolution.exportName)
+                {
+                    return "ambiguous";
+                }
+            }
+        }
+    }
+
+    return starResolution;
+}
+
+// 15.2.1.16.4 ModuleDeclarationInstantiation()
+function ModuleDeclarationInstantiation()
+{
+    if (!IsObject(this) || !IsModule(this))
+        return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation");
+
+    // Step 1
+    let module = this;
+
+    // Step 5
+    if (module.environment !== undefined)
+        return;
+
+    // Step 7
+    CreateModuleEnvironment(module);
+    let env = module.environment;
+
+    // Step 8
+    let requestedModules = module.requestedModules;
+    for (let i = 0; i < requestedModules.length; i++) {
+        let required = requestedModules[i];
+        let requiredModule = HostResolveImportedModule(module, required);
+        requiredModule.declarationInstantiation();
+    }
+
+    // Step 9
+    let indirectExportEntries = module.indirectExportEntries;
+    for (let i = 0; i < indirectExportEntries.length; i++) {
+        let e = indirectExportEntries[i];
+        let resolution = module.resolveExport(e.exportName);
+        if (resolution === null)
+            ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT);
+        if (resolution === "ambiguous")
+            ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT);
+    }
+
+    // Step 12
+    let importEntries = module.importEntries;
+    for (let i = 0; i < importEntries.length; i++) {
+        let imp = importEntries[i];
+        let importedModule = HostResolveImportedModule(module, imp.moduleRequest);
+        if (imp.importName === "*") {
+            // TODO
+            // let namespace = GetModuleNamespace(importedModule);
+            // CreateNamespaceBinding(module.environment, imp.localName, namespace);
+        } else {
+            let resolution = importedModule.resolveExport(imp.importName);
+            if (resolution === null)
+                ThrowSyntaxError(JSMSG_MISSING_IMPORT);
+            if (resolution === "ambiguous")
+                ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT);
+            CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName);
+        }
+    }
+}
+
+// 15.2.1.16.5 ModuleEvaluation()
+function ModuleEvaluation()
+{
+    if (!IsObject(this) || !IsModule(this))
+        return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation");
+
+    // Step 1
+    let module = this;
+
+    // Step 4
+    if (module.evaluated)
+        return undefined;
+
+    // Step 5
+    SetModuleEvaluated(this);
+
+    // Step 6
+    let requestedModules = module.requestedModules;
+    for (let i = 0; i < requestedModules.length; i++) {
+        let required = requestedModules[i];
+        let requiredModule = HostResolveImportedModule(module, required);
+        requiredModule.evaluation();
+    }
+
+    return EvaluateModule(module);
+}
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -207,33 +207,40 @@ ExportEntryObject::create(JSContext* cx,
     self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName));
     self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest));
     self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName));
     self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName));
     return self;
 }
 
 ///////////////////////////////////////////////////////////////////////////
+// IndirectBinding
+
+IndirectBinding::IndirectBinding(Handle<ModuleEnvironmentObject*> environment, HandleId localName)
+  : environment(environment), localName(localName)
+{}
+
+///////////////////////////////////////////////////////////////////////////
 // ModuleObject
 
 /* static */ const Class
 ModuleObject::class_ = {
     "Module",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Module) |
     JSCLASS_IS_ANONYMOUS,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
     nullptr,        /* setProperty */
     nullptr,        /* enumerate   */
     nullptr,        /* resolve     */
     nullptr,        /* mayResolve  */
     nullptr,        /* convert     */
-    nullptr,        /* finalize    */
+    ModuleObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     ModuleObject::trace
 };
 
 #define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot)                           \
     ArrayObject&                                                              \
@@ -252,24 +259,57 @@ DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject,
 ModuleObject::isInstance(HandleValue value)
 {
     return value.isObject() && value.toObject().is<ModuleObject>();
 }
 
 /* static */ ModuleObject*
 ModuleObject::create(ExclusiveContext* cx)
 {
-    return NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject);
+    Rooted<ModuleObject*> self(cx, NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject));
+
+    IndirectBindingMap* bindings = cx->new_<IndirectBindingMap>();
+    if (!bindings || !bindings->init()) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    self->setReservedSlot(ImportBindingsSlot, PrivateValue(bindings));
+
+    return self;
+}
+
+/* static */ void
+ModuleObject::finalize(js::FreeOp* fop, JSObject* obj)
+{
+    fop->delete_(&obj->as<ModuleObject>().importBindings());
+}
+
+ModuleEnvironmentObject*
+ModuleObject::environment() const
+{
+    Value value = getReservedSlot(EnvironmentSlot);
+    if (value.isUndefined())
+        return nullptr;
+
+    return &value.toObject().as<ModuleEnvironmentObject>();
+}
+
+IndirectBindingMap&
+ModuleObject::importBindings()
+{
+    return *static_cast<IndirectBindingMap*>(getReservedSlot(ImportBindingsSlot).toPrivate());
 }
 
 void
 ModuleObject::init(HandleScript script)
 {
     MOZ_ASSERT(!script->enclosingStaticScope());
     initReservedSlot(ScriptSlot, PrivateValue(script));
+    initReservedSlot(EvaluatedSlot, BooleanValue(false));
 }
 
 void
 ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment)
 {
     initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment));
 }
 
@@ -296,16 +336,22 @@ ModuleObject::hasScript() const
 }
 
 JSScript*
 ModuleObject::script() const
 {
     return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
 }
 
+bool
+ModuleObject::evaluated() const
+{
+    return getReservedSlot(EvaluatedSlot).toBoolean();
+}
+
 ModuleEnvironmentObject&
 ModuleObject::initialEnvironment() const
 {
     return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
 }
 
 JSObject*
 ModuleObject::enclosingStaticScope() const
@@ -320,45 +366,90 @@ ModuleObject::enclosingStaticScope() con
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
 {
     ModuleObject& module = obj->as<ModuleObject>();
     if (module.hasScript()) {
         JSScript* script = module.script();
         TraceManuallyBarrieredEdge(trc, &script, "Module script");
         module.setReservedSlot(ScriptSlot, PrivateValue(script));
     }
+
+    IndirectBindingMap& bindings = module.importBindings();
+    for (IndirectBindingMap::Enum e(bindings); !e.empty(); e.popFront()) {
+        IndirectBinding& b = e.front().value();
+        TraceEdge(trc, &b.environment, "module import environment");
+        TraceEdge(trc, &b.localName, "module import local name");
+        jsid bindingName = e.front().key();
+        TraceManuallyBarrieredEdge(trc, &bindingName, "module import binding name");
+        MOZ_ASSERT(bindingName == e.front().key());
+    }
+}
+
+void
+ModuleObject::createEnvironment()
+{
+    // The environment has already been created, we just neet to set it in the
+    // right slot.
+    MOZ_ASSERT(!getReservedSlot(InitialEnvironmentSlot).isUndefined());
+    MOZ_ASSERT(getReservedSlot(EnvironmentSlot).isUndefined());
+    setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot));
+}
+
+void
+ModuleObject::setEvaluated()
+{
+    MOZ_ASSERT(!evaluated());
+    setReservedSlot(EvaluatedSlot, TrueHandleValue);
+}
+
+bool
+ModuleObject::evaluate(JSContext* cx, MutableHandleValue rval)
+{
+    RootedScript script(cx, this->script());
+    return JS_ExecuteScript(cx, script, rval);
 }
 
 DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, environment, EnvironmentSlot)
+DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
 
 JSObject*
 js::InitModuleClass(JSContext* cx, HandleObject obj)
 {
     static const JSPropertySpec protoAccessors[] = {
         JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0),
+        JS_PSG("environment", ModuleObject_environmentGetter, 0),
+        JS_PSG("evaluated", ModuleObject_evaluatedGetter, 0),
         JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
         JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
         JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
         JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0),
         JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0),
         JS_PS_END
     };
 
+    static const JSFunctionSpec protoFunctions[] = {
+        JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 3, 0),
+        JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0),
+        JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0),
+        JS_FS_END
+    };
+
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
 
     RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
     if (!proto)
         return nullptr;
 
-    if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr))
+    if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, protoFunctions))
         return nullptr;
 
     global->setPrototype(JSProto_Module, ObjectValue(*proto));
     return proto;
 }
 
 #undef DEFINE_GETTER_FUNCTIONS
 #undef DEFINE_STRING_ACCESSOR_METHOD
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef builtin_ModuleObject_h
 #define builtin_ModuleObject_h
 
 #include "jsapi.h"
+#include "jsatom.h"
 
 #include "js/TraceableVector.h"
 
 #include "vm/NativeObject.h"
 
 namespace js {
 
 class ModuleEnvironmentObject;
@@ -65,28 +66,40 @@ class ExportEntryObject : public NativeO
                                      HandleAtom maybeImportName,
                                      HandleAtom maybeLocalName);
     JSAtom* exportName();
     JSAtom* moduleRequest();
     JSAtom* importName();
     JSAtom* localName();
 };
 
+struct IndirectBinding
+{
+    IndirectBinding(Handle<ModuleEnvironmentObject*> environment, HandleId localName);
+    RelocatablePtr<ModuleEnvironmentObject*> environment;
+    RelocatableId localName;
+};
+
+typedef HashMap<jsid, IndirectBinding, JsidHasher, SystemAllocPolicy> IndirectBindingMap;
+
 class ModuleObject : public NativeObject
 {
   public:
     enum
     {
         ScriptSlot = 0,
         InitialEnvironmentSlot,
+        EnvironmentSlot,
+        EvaluatedSlot,
         RequestedModulesSlot,
         ImportEntriesSlot,
         LocalExportEntriesSlot,
         IndirectExportEntriesSlot,
         StarExportEntriesSlot,
+        ImportBindingsSlot,
         SlotCount
     };
 
     static const Class class_;
 
     static bool isInstance(HandleValue value);
 
     static ModuleObject* create(ExclusiveContext* cx);
@@ -95,25 +108,34 @@ class ModuleObject : public NativeObject
     void initImportExportData(HandleArrayObject requestedModules,
                               HandleArrayObject importEntries,
                               HandleArrayObject localExportEntries,
                               HandleArrayObject indiretExportEntries,
                               HandleArrayObject starExportEntries);
 
     JSScript* script() const;
     ModuleEnvironmentObject& initialEnvironment() const;
+    ModuleEnvironmentObject* environment() const;
+    bool evaluated() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
     ArrayObject& indirectExportEntries() const;
     ArrayObject& starExportEntries() const;
     JSObject* enclosingStaticScope() const;
+    IndirectBindingMap& importBindings();
+
+    void createEnvironment();
+
+    void setEvaluated();
+    bool evaluate(JSContext*cx, MutableHandleValue rval);
 
   private:
     static void trace(JSTracer* trc, JSObject* obj);
+    static void finalize(js::FreeOp* fop, JSObject* obj);
 
     bool hasScript() const;
 };
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 
 // Process a module's parse tree to collate the import and export data used when
@@ -130,16 +152,17 @@ class MOZ_STACK_CLASS ModuleBuilder
     using RootedAtomVector = JS::Rooted<AtomVector>;
     using ImportEntryVector = TraceableVector<ImportEntryObject*>;
     using RootedImportEntryVector = JS::Rooted<ImportEntryVector>;
     using ExportEntryVector = TraceableVector<ExportEntryObject*> ;
     using RootedExportEntryVector = JS::Rooted<ExportEntryVector> ;
 
     JSContext* cx_;
     RootedAtomVector requestedModules_;
+
     RootedAtomVector importedBoundNames_;
     RootedImportEntryVector importEntries_;
     RootedExportEntryVector exportEntries_;
     RootedExportEntryVector localExportEntries_;
     RootedExportEntryVector indirectExportEntries_;
     RootedExportEntryVector starExportEntries_;
 
     bool processImport(frontend::ParseNode* pn);
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2122,33 +2122,33 @@ class BackEdge {
     BackEdge& operator=(const BackEdge&) = delete;
 };
 
 // A path-finding handler class for use with JS::ubi::BreadthFirst.
 struct FindPathHandler {
     typedef BackEdge NodeData;
     typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal;
 
-    FindPathHandler(JS::ubi::Node start, JS::ubi::Node target,
+    FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target,
                     AutoValueVector& nodes, Vector<EdgeName>& edges)
-      : start(start), target(target), foundPath(false),
+      : cx(cx), start(start), target(target), foundPath(false),
         nodes(nodes), edges(edges) { }
 
     bool
     operator()(Traversal& traversal, JS::ubi::Node origin, const JS::ubi::Edge& edge,
                BackEdge* backEdge, bool first)
     {
         // We take care of each node the first time we visit it, so there's
         // nothing to be done on subsequent visits.
         if (!first)
             return true;
 
         // Record how we reached this node. This is the last edge on a
         // shortest path to this node.
-        EdgeName edgeName = DuplicateString(traversal.cx, edge.name);
+        EdgeName edgeName = DuplicateString(cx, edge.name);
         if (!edgeName)
             return false;
         *backEdge = mozilla::Move(BackEdge(origin, Move(edgeName)));
 
         // Have we reached our final target node?
         if (edge.referent == target) {
             // Record the path that got us here, which must be a shortest path.
             if (!recordPath(traversal))
@@ -2175,16 +2175,18 @@ struct FindPathHandler {
                 !edges.append(p->value().forgetName()))
                 return false;
             here = predecessor;
         } while (here != start);
 
         return true;
     }
 
+    JSContext* cx;
+
     // The node we're starting from.
     JS::ubi::Node start;
 
     // The node we're looking for.
     JS::ubi::Node target;
 
     // True if we found a path to target, false if we didn't.
     bool foundPath;
@@ -2233,18 +2235,18 @@ FindPath(JSContext* cx, unsigned argc, V
 
     {
         // We can't tolerate the GC moving things around while we're searching
         // the heap. Check that nothing we do causes a GC.
         JS::AutoCheckCannotGC autoCannotGC;
 
         JS::ubi::Node start(args[0]), target(args[1]);
 
-        heaptools::FindPathHandler handler(start, target, nodes, edges);
-        heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC);
+        heaptools::FindPathHandler handler(cx, start, target, nodes, edges);
+        heaptools::FindPathHandler::Traversal traversal(cx->runtime(), handler, autoCannotGC);
         if (!traversal.init() || !traversal.addStart(start))
             return false;
 
         if (!traversal.traverse())
             return false;
 
         if (!handler.foundPath) {
             // We didn't find any paths from the start to the target.
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -668,36 +668,38 @@ class GCRuntime
 
     void requestMinorGC(JS::gcreason::Reason reason);
 
 #ifdef DEBUG
 
     bool onBackgroundThread() { return helperState.onBackgroundThread(); }
 
     bool currentThreadOwnsGCLock() {
-        return lockOwner == PR_GetCurrentThread();
+        return lockOwner.value == PR_GetCurrentThread();
     }
 
 #endif // DEBUG
 
     void assertCanLock() {
         MOZ_ASSERT(!currentThreadOwnsGCLock());
     }
 
     void lockGC() {
         PR_Lock(lock);
-        MOZ_ASSERT(!lockOwner);
 #ifdef DEBUG
-        lockOwner = PR_GetCurrentThread();
+        MOZ_ASSERT(!lockOwner.value);
+        lockOwner.value = PR_GetCurrentThread();
 #endif
     }
 
     void unlockGC() {
-        MOZ_ASSERT(lockOwner == PR_GetCurrentThread());
-        lockOwner = nullptr;
+#ifdef DEBUG
+        MOZ_ASSERT(lockOwner.value == PR_GetCurrentThread());
+        lockOwner.value = nullptr;
+#endif
         PR_Unlock(lock);
     }
 
 #ifdef DEBUG
     bool isAllocAllowed() { return noGCOrAllocationCheck == 0; }
     void disallowAlloc() { ++noGCOrAllocationCheck; }
     void allowAlloc() {
         MOZ_ASSERT(!isAllocAllowed());
@@ -1293,17 +1295,17 @@ class GCRuntime
      */
     int inUnsafeRegion;
 
     size_t noGCOrAllocationCheck;
 #endif
 
     /* Synchronize GC heap access between main thread and GCHelperState. */
     PRLock* lock;
-    mozilla::DebugOnly<PRThread*> lockOwner;
+    mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner;
 
     BackgroundAllocTask allocTask;
     GCHelperState helperState;
 
     /*
      * During incremental sweeping, this field temporarily holds the arenas of
      * the current AllocKind being swept in order of increasing free space.
      */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -365,17 +365,18 @@ AssertRootMarkingPhase(JSTracer* trc)
     D(ArgumentsObject*) \
     D(ArrayBufferObject*) \
     D(ArrayBufferObjectMaybeShared*) \
     D(ArrayBufferViewObject*) \
     D(DebugScopeObject*) \
     D(GlobalObject*) \
     D(JSObject*) \
     D(JSFunction*) \
-    D(ModuleObject*)      \
+    D(ModuleObject*) \
+    D(ModuleEnvironmentObject*) \
     D(NestedScopeObject*) \
     D(PlainObject*) \
     D(SavedFrame*) \
     D(ScopeObject*) \
     D(ScriptSourceObject*) \
     D(SharedArrayBufferObject*) \
     D(SharedTypedArrayObject*) \
     D(ImportEntryObject*) \
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -128,16 +128,24 @@ NativeRegExpMacroAssembler::GenerateCode
 
     // Push non-volatile registers which might be modified by jitcode.
     size_t pushedNonVolatileRegisters = 0;
     for (GeneralRegisterForwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter) {
         masm.Push(*iter);
         pushedNonVolatileRegisters++;
     }
 
+#if defined(XP_IOS) && defined(JS_CODEGEN_ARM)
+    // The stack is 4-byte aligned on iOS, force 8-byte alignment.
+    masm.movePtr(StackPointer, temp0);
+    masm.andPtr(Imm32(~7), StackPointer);
+    masm.push(temp0);
+    masm.push(temp0);
+#endif
+
 #ifndef JS_CODEGEN_X86
     // The InputOutputData* is stored as an argument, save it on the stack
     // above the frame.
     masm.Push(IntArgReg0);
 #endif
 
     size_t frameSize = sizeof(FrameData) + num_registers_ * sizeof(void*);
     frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), ABIStackAlignment) - masm.framePushed();
@@ -364,16 +372,21 @@ NativeRegExpMacroAssembler::GenerateCode
 
 #ifndef JS_CODEGEN_X86
     // Include the InputOutputData* when adjusting the stack size.
     masm.freeStack(frameSize + sizeof(void*));
 #else
     masm.freeStack(frameSize);
 #endif
 
+#if defined(XP_IOS) && defined(JS_CODEGEN_ARM)
+    masm.pop(temp0);
+    masm.movePtr(temp0, StackPointer);
+#endif
+
     // Restore non-volatile registers which were saved on entry.
     for (GeneralRegisterBackwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter)
         masm.Pop(*iter);
 
     masm.abiret();
 
     // Backtrack code (branch target for conditional backtracks).
     if (backtrack_label_.used()) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1207413.js
@@ -0,0 +1,15 @@
+if (typeof oomAfterAllocations !== 'function')
+    quit();
+
+function first(a) {
+    return a[0];
+}
+
+try {
+    first([function() {}]);
+    first([function() {}]);
+    oomAfterAllocations(50);
+    first([function() {}]);
+} catch(e) {
+    // ignore oom
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/ambiguous-star-export.js
@@ -0,0 +1,26 @@
+// Test ambigious export * statements.
+
+"use strict";
+
+load(libdir + "asserts.js");
+
+let moduleRepo = new Map();
+setModuleResolveHook(function(module, specifier) {
+    if (specifier in moduleRepo)
+        return moduleRepo[specifier];
+    throw "Module " + specifier + " not found";
+});
+
+let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;");
+let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
+let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
+let ms = [a, b, c];
+ms.map((m) => m.declarationInstantiation());
+ms.map((m) => m.evaluation(), moduleRepo.values());
+
+let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;");
+d.declarationInstantiation();
+assertEq(d.evaluation(), 1);
+
+let e = moduleRepo['e'] = parseModule("import { b } from 'c';");
+assertThrowsInstanceOf(() => e.declarationInstantiation(), SyntaxError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js
@@ -0,0 +1,35 @@
+// Exercise ModuleDeclarationInstantiation() operation.
+
+function parseAndInstantiate(source) {
+    let m = parseModule(source);
+    m.declarationInstantiation();
+    return m.environment;
+}
+
+function testModuleEnvironment(module, expected) {
+    var actual = Object.keys(module.environment);
+    assertEq(actual.length, expected.length);
+    for (var i = 0; i < actual.length; i++) {
+        assertEq(actual[i], expected[i]);
+    }
+}
+
+// Check the environment of an empty module.
+let e = parseAndInstantiate("");
+assertEq(Object.keys(e).length, 0);
+
+let moduleRepo = new Map();
+setModuleResolveHook(function(module, specifier) {
+    if (specifier in moduleRepo)
+        return moduleRepo[specifier];
+    throw "Module " + specifier + " not found";
+});
+
+a = moduleRepo['a'] = parseModule("var x = 1; export { x };");
+b = moduleRepo['b'] = parseModule("import { x as y } from 'a';");
+
+a.declarationInstantiation();
+b.declarationInstantiation();
+
+testModuleEnvironment(a, ['x']);
+testModuleEnvironment(b, ['y']);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/module-evaluation.js
@@ -0,0 +1,99 @@
+// Exercise ModuleEvaluation() concrete method.
+
+load(libdir + "asserts.js");
+
+let moduleRepo = new Map();
+setModuleResolveHook(function(module, specifier) {
+    if (specifier in moduleRepo)
+        return moduleRepo[specifier];
+    throw "Module " + specifier + " not found";
+});
+
+function parseAndEvaluate(source) {
+    let m = parseModule(source);
+    m.declarationInstantiation();
+    return m.evaluation();
+}
+
+// Check the evaluation of an empty module succeeds.
+assertEq(typeof parseAndEvaluate(""), "undefined");
+
+// Check evaluation returns evaluation result the first time, then undefined.
+let m = parseModule("1");
+m.declarationInstantiation();
+assertEq(m.evaluation(), 1);
+assertEq(typeof m.evaluation(), "undefined");
+
+// Check top level variables are initialized by evaluation.
+m = parseModule("export var x = 2 + 2;");
+assertEq(typeof m.initialEnvironment.x, "undefined");
+m.declarationInstantiation();
+m.evaluation();
+assertEq(m.environment.x, 4);
+
+m = parseModule("export let x = 2 * 3;");
+m.declarationInstantiation();
+m.evaluation();
+assertEq(m.environment.x, 6);
+
+// Set up a module to import from.
+let a = moduleRepo['a'] =
+parseModule(`var x = 1;
+             export { x };
+             export default 2;
+             export function f(x) { return x + 1; }`);
+
+// Check we can evaluate top level definitions.
+parseAndEvaluate("var foo = 1;");
+parseAndEvaluate("let foo = 1;");
+parseAndEvaluate("const foo = 1");
+parseAndEvaluate("function foo() {}");
+parseAndEvaluate("class foo { constructor() {} }");
+
+// Check we can evaluate all module-related syntax.
+parseAndEvaluate("export var foo = 1;");
+parseAndEvaluate("export let foo = 1;");
+parseAndEvaluate("export const foo = 1;");
+parseAndEvaluate("var x = 1; export { x };");
+parseAndEvaluate("export default 1");
+parseAndEvaluate("export default class { constructor() {} };");
+parseAndEvaluate("export default function() {};");
+parseAndEvaluate("export default class foo { constructor() {} };");
+parseAndEvaluate("export default function foo() {};");
+parseAndEvaluate("import a from 'a';");
+parseAndEvaluate("import { x } from 'a';");
+parseAndEvaluate("import * as ns from 'a';");
+parseAndEvaluate("export * from 'a'");
+
+// Test default import
+m = parseModule("import a from 'a'; a;")
+m.declarationInstantiation();
+assertEq(m.evaluation(), 2);
+
+// Test named import
+m = parseModule("import { x as y } from 'a'; y;")
+m.declarationInstantiation();
+assertEq(m.evaluation(), 1);
+
+// Call exported function
+m = parseModule("import { f } from 'a'; f(3);")
+m.declarationInstantiation();
+assertEq(m.evaluation(), 4);
+
+// Test importing an indirect export
+moduleRepo['b'] = parseModule("export { x as z } from 'a';");
+assertEq(parseAndEvaluate("import { z } from 'b'; z"), 1);
+
+// Test cyclic dependencies
+moduleRepo['c1'] = parseModule("export var x = 1; export {y} from 'c2'");
+moduleRepo['c2'] = parseModule("export var y = 2; export {x} from 'c1'");
+assertDeepEq(parseAndEvaluate(`import { x as x1, y as y1 } from 'c1'; 
+                               import { x as x2, y as y2 } from 'c2';
+                               [x1, y1, x2, y2]`),
+             [1, 2, 1, 2]);
+
+// Import access in functions
+m = parseModule("import { x } from 'a'; function f() { return x; }")
+m.declarationInstantiation();
+m.evaluation();
+assertEq(m.environment.f(), 1);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -667,16 +667,21 @@ BaselineCompiler::initScopeChain()
             prepareVMCall();
 
             masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
             pushArg(R0.scratchReg());
 
             if (!callVMNonOp(InitFunctionScopeObjectsInfo, phase))
                 return false;
         }
+    } else if (module()) {
+        // Modules use a pre-created scope object.
+        Register scope = R1.scratchReg();
+        masm.movePtr(ImmGCPtr(&module()->initialEnvironment()), scope);
+        masm.storePtr(scope, frame.addressOfScopeChain());
     } else {
         // ScopeChain pointer in BaselineFrame has already been initialized
         // in prologue.
 
         if (script->isForEval() && script->strict()) {
             // Strict eval needs its own call object.
             prepareVMCall();
 
--- a/js/src/jit/BytecodeAnalysis.cpp
+++ b/js/src/jit/BytecodeAnalysis.cpp
@@ -43,17 +43,18 @@ struct CatchFinallyRange
 bool
 BytecodeAnalysis::init(TempAllocator& alloc, GSNCache& gsn)
 {
     if (!infos_.growByUninitialized(script_->length()))
         return false;
 
     // Initialize the scope chain slot if either the function needs a CallObject
     // or the script uses the scope chain. The latter case is handled below.
-    usesScopeChain_ = (script_->functionDelazifying() &&
+    usesScopeChain_ = script_->module() ||
+                      (script_->functionDelazifying() &&
                        script_->functionDelazifying()->needsCallObject());
     MOZ_ASSERT_IF(script_->hasAnyAliasedBindings(), usesScopeChain_);
 
     jsbytecode* end = script_->codeEnd();
 
     // Clear all BytecodeInfo.
     mozilla::PodZero(infos_.begin(), infos_.length());
     infos_[0].init(/*stackDepth=*/0);
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -242,16 +242,19 @@ class CompileInfo
         return script_;
     }
     bool compilingAsmJS() const {
         return script() == nullptr;
     }
     JSFunction* funMaybeLazy() const {
         return fun_;
     }
+    ModuleObject* module() const {
+        return script_->module();
+    }
     bool constructing() const {
         return constructing_;
     }
     jsbytecode* osrPc() {
         return osrPc_;
     }
     NestedScopeObject* osrStaticScope() const {
         return osrStaticScope_;
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -54,16 +54,20 @@ static void sync_instruction_memory(cadd
 extern  "C" void sync_instruction_memory(caddr_t v, u_int len);
 #endif
 #endif
 
 #if defined(JS_CODEGEN_MIPS32) && defined(__linux__) && !defined(JS_SIMULATOR_MIPS32)
 #include <sys/cachectl.h>
 #endif
 
+#if defined(JS_CODEGEN_ARM) && defined(XP_IOS)
+#include <libkern/OSCacheControl.h>
+#endif
+
 namespace JS {
     struct CodeSizes;
 } // namespace JS
 
 namespace js {
 namespace jit {
   enum CodeKind { ION_CODE = 0, BASELINE_CODE, REGEXP_CODE, OTHER_CODE };
 
@@ -404,16 +408,21 @@ class ExecutableAllocator
         _flush_cache(reinterpret_cast<char*>(code), size, BCACHE);
 #endif
     }
 #elif defined(JS_CODEGEN_ARM) && (defined(__FreeBSD__) || defined(__NetBSD__))
     static void cacheFlush(void* code, size_t size)
     {
         __clear_cache(code, reinterpret_cast<char*>(code) + size);
     }
+#elif defined(JS_CODEGEN_ARM) && defined(XP_IOS)
+    static void cacheFlush(void* code, size_t size)
+    {
+        sys_icache_invalidate(code, size);
+    }
 #elif defined(JS_CODEGEN_ARM) && (defined(__linux__) || defined(ANDROID)) && defined(__GNUC__)
     static void cacheFlush(void* code, size_t size)
     {
         asm volatile (
             "push    {r7}\n"
             "mov     r0, %0\n"
             "mov     r1, %1\n"
             "mov     r7, #0xf0000\n"
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1224,16 +1224,19 @@ IonBuilder::initScopeChain(MDefinition* 
                 if (!scope)
                     return false;
             }
 
             scope = createCallObject(callee, scope);
             if (!scope)
                 return false;
         }
+    } else if (ModuleObject* module = info().module()) {
+        // Modules use a pre-created scope object.
+        scope = constant(ObjectValue(module->initialEnvironment()));
     } else {
         // For global scripts without a non-syntactic global scope, the scope
         // chain is the global object.
         MOZ_ASSERT(!script()->isForEval());
         MOZ_ASSERT(!script()->hasNonSyntacticScope());
         scope = constant(ObjectValue(script()->global()));
     }
 
@@ -9011,16 +9014,18 @@ IonBuilder::computeHeapType(const Tempor
         HeapTypeSetKey property = key->property(id);
         HeapTypeSet* currentSet = property.maybeTypes();
 
         if (!currentSet || currentSet->unknown())
             return nullptr;
 
         properties.infallibleAppend(property);
         acc = TypeSet::unionSets(acc, currentSet, lifoAlloc);
+        if (!acc)
+            return nullptr;
     }
 
     // Freeze all the properties associated with the refined type set.
     for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++)
         i->freeze(constraints());
 
     return acc;
 }
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -4686,16 +4686,19 @@ IsCacheableNameReadSlot(HandleObject sco
     if (!obj->isNative())
         return false;
 
     if (obj->is<GlobalObject>()) {
         // Support only simple property lookups.
         if (!IsCacheableGetPropReadSlotForIon(obj, holder, shape) &&
             !IsCacheableNoProperty(obj, holder, shape, pc, output))
             return false;
+    } else if (obj->is<ModuleEnvironmentObject>()) {
+        // We don't yet support lookups in a module environment.
+        return false;
     } else if (obj->is<CallObject>()) {
         MOZ_ASSERT(obj == holder);
         if (!shape->hasDefaultGetter())
             return false;
     } else {
         // We don't yet support lookups on Block or DeclEnv objects.
         return false;
     }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1856,18 +1856,21 @@ jit::MergeTypes(MIRType* ptype, Temporar
     if (*ptypeSet) {
         LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
         if (!newTypeSet && newType != MIRType_Value) {
             newTypeSet = MakeMIRTypeSet(newType);
             if (!newTypeSet)
                 return false;
         }
         if (newTypeSet) {
-            if (!newTypeSet->isSubset(*ptypeSet))
+            if (!newTypeSet->isSubset(*ptypeSet)) {
                 *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc);
+                if (!*ptypeSet)
+                    return false;
+            }
         } else {
             *ptypeSet = nullptr;
         }
     }
     return true;
 }
 
 // Tests whether 'types' includes all possible values represented by
--- a/js/src/jit/arm/Architecture-arm.cpp
+++ b/js/src/jit/arm/Architecture-arm.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jit/arm/Architecture-arm.h"
 
-#ifndef JS_SIMULATOR_ARM
+#if !defined(JS_ARM_SIMULATOR) && !defined(__APPLE__)
 #include <elf.h>
 #endif
 
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "jit/arm/Assembler-arm.h"
 #include "jit/RegisterSets.h"
@@ -240,16 +240,25 @@ InitARMFlags()
     flags |= HWCAP_VFP;
 #endif
 
 #if defined(__ARM_ARCH_7__) || defined (__ARM_ARCH_7A__)
     // Compiled to use ARMv7 instructions so assume the ARMv7 arch.
     flags |= HWCAP_ARMv7;
 #endif
 
+#if defined(__APPLE__)
+    #if defined(__ARM_NEON__)
+        flags |= HWCAP_NEON;
+    #endif
+    #if defined(__ARMVFPV3__)
+        flags |= HWCAP_VFPv3 | HWCAP_VFPD32
+    #endif
+#endif
+
 #endif // JS_SIMULATOR_ARM
 
     armHwCapFlags = CanonicalizeARMHwCapFlags(flags);
 
     JitSpew(JitSpew_Codegen, "ARM HWCAP: 0x%x\n", armHwCapFlags);
     return;
 }
 
--- a/js/src/jit/arm/Architecture-arm.h
+++ b/js/src/jit/arm/Architecture-arm.h
@@ -10,18 +10,19 @@
 #include "mozilla/MathAlgorithms.h"
 
 #include <limits.h>
 #include <stdint.h>
 
 #include "js/Utility.h"
 
 // GCC versions 4.6 and above define __ARM_PCS_VFP to denote a hard-float
-// ABI target.
-#if defined(__ARM_PCS_VFP)
+// ABI target. The iOS toolchain doesn't define anything specific here,
+// but iOS always supports VFP.
+#if defined(__ARM_PCS_VFP) || defined(XP_IOS)
 #define JS_CODEGEN_ARM_HARDFP
 #endif
 
 namespace js {
 namespace jit {
 
 // In bytes: slots needed for potential memory->memory move spills.
 //   +8 for cycles
@@ -104,25 +105,32 @@ class Registers
 
     static const SetType AllMask = (1 << Total) - 1;
     static const SetType ArgRegMask = (1 << r0) | (1 << r1) | (1 << r2) | (1 << r3);
 
     static const SetType VolatileMask =
         (1 << r0) |
         (1 << r1) |
         (1 << Registers::r2) |
-        (1 << Registers::r3);
+        (1 << Registers::r3)
+#if defined(XP_IOS)
+        // per https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009021-SW4
+        | (1 << Registers::r9)
+#endif
+              ;
 
     static const SetType NonVolatileMask =
         (1 << Registers::r4) |
         (1 << Registers::r5) |
         (1 << Registers::r6) |
         (1 << Registers::r7) |
         (1 << Registers::r8) |
+#if !defined(XP_IOS)
         (1 << Registers::r9) |
+#endif
         (1 << Registers::r10) |
         (1 << Registers::r11) |
         (1 << Registers::r12) |
         (1 << Registers::r14);
 
     static const SetType WrapperMask =
         VolatileMask |         // = arguments
         (1 << Registers::r4) | // = outReg
new file mode 100644
--- /dev/null
+++ b/js/src/jit/arm/llvm-compiler-rt/arm/aeabi_idivmod.S
@@ -0,0 +1,27 @@
+//===-- aeabi_idivmod.S - EABI idivmod implementation ---------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../assembly.h"
+
+// struct { int quot, int rem} __aeabi_idivmod(int numerator, int denominator) {
+//   int rem, quot;
+//   quot = __divmodsi4(numerator, denominator, &rem);
+//   return {quot, rem};
+// }
+
+        .syntax unified
+        .align 2
+DEFINE_COMPILERRT_FUNCTION(__aeabi_idivmod)
+        push    { lr }
+        sub     sp, sp, #4
+        mov     r2, sp
+        bl      SYMBOL_NAME(__divmodsi4)
+        ldr     r1, [sp]
+        add     sp, sp, #4
+        pop     { pc }
new file mode 100644
--- /dev/null
+++ b/js/src/jit/arm/llvm-compiler-rt/arm/aeabi_uidivmod.S
@@ -0,0 +1,28 @@
+//===-- aeabi_uidivmod.S - EABI uidivmod implementation -------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../assembly.h"
+
+// struct { unsigned quot, unsigned rem}
+//        __aeabi_uidivmod(unsigned numerator, unsigned denominator) {
+//   unsigned rem, quot;
+//   quot = __udivmodsi4(numerator, denominator, &rem);
+//   return {quot, rem};
+// }
+
+        .syntax unified
+        .align 2
+DEFINE_COMPILERRT_FUNCTION(__aeabi_uidivmod)
+        push    { lr }
+        sub     sp, sp, #4
+        mov     r2, sp
+        bl      SYMBOL_NAME(__udivmodsi4)
+        ldr     r1, [sp]
+        add     sp, sp, #4
+        pop     { pc }
new file mode 100644
--- /dev/null
+++ b/js/src/jit/arm/llvm-compiler-rt/assembly.h
@@ -0,0 +1,70 @@
+/* ===-- assembly.h - compiler-rt assembler support macros -----------------===
+ *
+ *                     The LLVM Compiler Infrastructure
+ *
+ * This file is dual licensed under the MIT and the University of Illinois Open
+ * Source Licenses. See LICENSE.TXT for details.
+ *
+ * ===----------------------------------------------------------------------===
+ *
+ * This file defines macros for use in compiler-rt assembler source.
+ * This file is not part of the interface of this library.
+ *
+ * ===----------------------------------------------------------------------===
+ */
+
+#ifndef COMPILERRT_ASSEMBLY_H
+#define COMPILERRT_ASSEMBLY_H
+
+#if defined(__POWERPC__) || defined(__powerpc__) || defined(__ppc__)
+#define SEPARATOR @
+#else
+#define SEPARATOR ;
+#endif
+
+#if defined(__APPLE__)
+#define HIDDEN_DIRECTIVE .private_extern
+#define LOCAL_LABEL(name) L_##name
+#else
+#define HIDDEN_DIRECTIVE .hidden
+#define LOCAL_LABEL(name) .L_##name
+#endif
+
+#define GLUE2(a, b) a ## b
+#define GLUE(a, b) GLUE2(a, b)
+#define SYMBOL_NAME(name) GLUE(__USER_LABEL_PREFIX__, name)
+
+#ifdef VISIBILITY_HIDDEN
+#define DECLARE_SYMBOL_VISIBILITY(name)                    \
+  HIDDEN_DIRECTIVE SYMBOL_NAME(name) SEPARATOR
+#else
+#define DECLARE_SYMBOL_VISIBILITY(name)
+#endif
+
+#define DEFINE_COMPILERRT_FUNCTION(name)                   \
+  .globl SYMBOL_NAME(name) SEPARATOR                       \
+  DECLARE_SYMBOL_VISIBILITY(name)                          \
+  SYMBOL_NAME(name):
+
+#define DEFINE_COMPILERRT_PRIVATE_FUNCTION(name)           \
+  .globl SYMBOL_NAME(name) SEPARATOR                       \
+  HIDDEN_DIRECTIVE SYMBOL_NAME(name) SEPARATOR             \
+  SYMBOL_NAME(name):
+
+#define DEFINE_COMPILERRT_PRIVATE_FUNCTION_UNMANGLED(name) \
+  .globl name SEPARATOR                                    \
+  HIDDEN_DIRECTIVE name SEPARATOR                          \
+  name:
+
+#define DEFINE_COMPILERRT_FUNCTION_ALIAS(name, target)     \
+  .globl SYMBOL_NAME(name) SEPARATOR                       \
+  .set SYMBOL_NAME(name), SYMBOL_NAME(target) SEPARATOR
+
+#if defined (__ARM_EABI__)
+# define DEFINE_AEABI_FUNCTION_ALIAS(aeabi_name, name)      \
+  DEFINE_COMPILERRT_FUNCTION_ALIAS(aeabi_name, name)
+#else
+# define DEFINE_AEABI_FUNCTION_ALIAS(aeabi_name, name)
+#endif
+
+#endif /* COMPILERRT_ASSEMBLY_H */
--- a/js/src/jit/shared/BaselineCompiler-shared.h
+++ b/js/src/jit/shared/BaselineCompiler-shared.h
@@ -118,16 +118,20 @@ class BaselineCompilerShared
     }
 
     JSFunction* function() const {
         // Not delazifying here is ok as the function is guaranteed to have
         // been delazified before compilation started.
         return script->functionNonDelazifying();
     }
 
+    ModuleObject* module() const {
+        return script->module();
+    }
+
     PCMappingSlotInfo getStackTopSlotInfo() {
         MOZ_ASSERT(frame.numUnsyncedSlots() <= 2);
         switch (frame.numUnsyncedSlots()) {
           case 0:
             return PCMappingSlotInfo::MakeSlotInfo();
           case 1:
             return PCMappingSlotInfo::MakeSlotInfo(PCMappingSlotInfo::ToSlotLocation(frame.peek(-1)));
           case 2:
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -500,8 +500,15 @@ MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_ELEMENT
 MSG_DEF(JSMSG_CANT_DELETE_WINDOW_ELEMENT, 0, JSEXN_TYPEERR, "can't delete elements from a Window object")
 MSG_DEF(JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY, 1, JSEXN_TYPEERR, "can't delete property {0} from window's named properties object")
 MSG_DEF(JSMSG_CANT_PREVENT_EXTENSIONS,   0, JSEXN_TYPEERR, "can't prevent extensions on this proxy object")
 MSG_DEF(JSMSG_NO_NAMED_SETTER,           2, JSEXN_TYPEERR, "{0} doesn't have a named property setter for '{1}'")
 MSG_DEF(JSMSG_NO_INDEXED_SETTER,         2, JSEXN_TYPEERR, "{0} doesn't have an indexed property setter for '{1}'")
 
 // Super
 MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'")
+
+// Modules
+MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT,        0, JSEXN_SYNTAXERR, "default export cannot be provided by export *")
+MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT,   0, JSEXN_SYNTAXERR, "indirect export not found")
+MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export")
+MSG_DEF(JSMSG_MISSING_IMPORT,            0, JSEXN_SYNTAXERR, "import not found")
+MSG_DEF(JSMSG_AMBIGUOUS_IMPORT,          0, JSEXN_SYNTAXERR, "ambiguous import")
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1171,17 +1171,16 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
     mallocBytesUntilGC(0),
     mallocGCTriggered(false),
     alwaysPreserveCode(false),
 #ifdef DEBUG
     inUnsafeRegion(0),
     noGCOrAllocationCheck(0),
 #endif
     lock(nullptr),
-    lockOwner(nullptr),
     allocTask(rt, emptyChunks_),
     helperState(rt)
 {
     setGCMode(JSGC_MODE_GLOBAL);
 }
 
 #ifdef JS_GC_ZEAL
 
@@ -3404,21 +3403,21 @@ GCHelperState::startBackgroundThread(Sta
 }
 
 void
 GCHelperState::waitForBackgroundThread()
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
 #ifdef DEBUG
-    rt->gc.lockOwner = nullptr;
+    rt->gc.lockOwner.value = nullptr;
 #endif
     PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT);
 #ifdef DEBUG
-    rt->gc.lockOwner = PR_GetCurrentThread();
+    rt->gc.lockOwner.value = PR_GetCurrentThread();
 #endif
 }
 
 void
 GCHelperState::work()
 {
     MOZ_ASSERT(CanUseExtraThreads());
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -4538,16 +4538,27 @@ js::DuplicateString(js::ExclusiveContext
     size_t n = js_strlen(s) + 1;
     auto ret = cx->make_pod_array<char16_t>(n);
     if (!ret)
         return ret;
     PodCopy(ret.get(), s, n);
     return ret;
 }
 
+UniquePtr<char16_t[], JS::FreePolicy>
+js::DuplicateString(const char16_t* s)
+{
+    size_t n = js_strlen(s) + 1;
+    UniquePtr<char16_t[], JS::FreePolicy> ret(js_pod_malloc<char16_t>(n));
+    if (!ret)
+        return nullptr;
+    PodCopy(ret.get(), s, n);
+    return ret;
+}
+
 template <typename CharT>
 const CharT*
 js_strchr_limit(const CharT* s, char16_t c, const CharT* limit)
 {
     while (s < limit) {
         if (*s == c)
             return s;
         s++;
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -124,16 +124,21 @@ extern const char*
 ValueToPrintable(JSContext* cx, const Value&, JSAutoByteString* bytes, bool asSource = false);
 
 extern mozilla::UniquePtr<char[], JS::FreePolicy>
 DuplicateString(ExclusiveContext* cx, const char* s);
 
 extern mozilla::UniquePtr<char16_t[], JS::FreePolicy>
 DuplicateString(ExclusiveContext* cx, const char16_t* s);
 
+// This variant does not report OOMs, you must arrange for OOMs to be reported
+// yourself.
+extern mozilla::UniquePtr<char16_t[], JS::FreePolicy>
+DuplicateString(const char16_t* s);
+
 /*
  * Convert a non-string value to a string, returning null after reporting an
  * error, otherwise returning a new string reference.
  */
 template <AllowGC allowGC>
 extern JSString*
 ToStringSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg);
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -433,16 +433,21 @@ elif CONFIG['JS_CODEGEN_ARM']:
         'jit/arm/MoveEmitter-arm.cpp',
         'jit/arm/SharedIC-arm.cpp',
         'jit/arm/Trampoline-arm.cpp',
     ]
     if CONFIG['JS_SIMULATOR_ARM']:
         UNIFIED_SOURCES += [
             'jit/arm/Simulator-arm.cpp'
         ]
+    elif CONFIG['OS_ARCH'] == 'Darwin':
+        SOURCES += [
+            'jit/arm/llvm-compiler-rt/arm/aeabi_idivmod.S',
+            'jit/arm/llvm-compiler-rt/arm/aeabi_uidivmod.S',
+        ]
 elif CONFIG['JS_CODEGEN_ARM64']:
     UNIFIED_SOURCES += [
         'jit/arm64/Architecture-arm64.cpp',
         'jit/arm64/Assembler-arm64.cpp',
         'jit/arm64/Bailouts-arm64.cpp',
         'jit/arm64/BaselineIC-arm64.cpp',
         'jit/arm64/CodeGenerator-arm64.cpp',
         'jit/arm64/Lowering-arm64.cpp',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3101,16 +3101,39 @@ ParseModule(JSContext* cx, unsigned argc
     if (!module)
         return false;
 
     args.rval().setObject(*module);
     return true;
 }
 
 static bool
+SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 1) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_MORE_ARGS_NEEDED, "setModuleResolveHook", "0", "s");
+        return false;
+    }
+
+    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
+        const char* typeName = InformalValueTypeName(args[0]);
+        JS_ReportError(cx, "expected hook function, got %s", typeName);
+        return false;
+    }
+
+    RootedFunction hook(cx, &args[0].toObject().as<JSFunction>());
+    Rooted<GlobalObject*> global(cx, cx->global());
+    global->setModuleResolveHook(hook);
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 Parse(JSContext* cx, unsigned argc, Value* vp)
 {
     using namespace js::frontend;
 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 1) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
@@ -4662,16 +4685,22 @@ static const JSFunctionSpecWithHelp shel
     JS_FN_HELP("compile", Compile, 1, 0,
 "compile(code)",
 "  Compiles a string to bytecode, potentially throwing."),
 
     JS_FN_HELP("parseModule", ParseModule, 1, 0,
 "parseModule(code)",
 "  Parses source text as a module and returns a Module object."),
 
+    JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0,
+"setModuleResolveHook(function(module, specifier) {})",
+"  Set the HostResolveImportedModule hook to |function|.\n"
+"  This hook is used to look up a previously loaded module object.  It should\n"
+"  be implemented by the module loader."),
+
     JS_FN_HELP("parse", Parse, 1, 0,
 "parse(code)",
 "  Parses a string, potentially throwing."),
 
     JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
 "syntaxParse(code)",
 "  Check the syntax of a string, returning success value"),
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4044,23 +4044,27 @@ class MOZ_STACK_CLASS Debugger::ObjectQu
 
         {
             /*
              * We can't tolerate the GC moving things around while we're
              * searching the heap. Check that nothing we do causes a GC.
              */
             Maybe<JS::AutoCheckCannotGC> maybeNoGC;
             RootedObject dbgObj(cx, dbg->object);
-            JS::ubi::RootList rootList(cx, maybeNoGC);
-            if (!rootList.init(dbgObj))
+            JS::ubi::RootList rootList(cx->runtime(), maybeNoGC);
+            if (!rootList.init(dbgObj)) {
+                ReportOutOfMemory(cx);
                 return false;
-
-            Traversal traversal(cx, *this, maybeNoGC.ref());
-            if (!traversal.init())
+            }
+
+            Traversal traversal(cx->runtime(), *this, maybeNoGC.ref());
+            if (!traversal.init()) {
+                ReportOutOfMemory(cx);
                 return false;
+            }
             traversal.wantNames = false;
 
             return traversal.addStart(JS::ubi::Node(&rootList)) &&
                    traversal.traverse();
         }
     }
 
     /*
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -535,28 +535,33 @@ DebuggerMemory::takeCensus(JSContext* cx
     // Populate our target set of debuggee zones.
     for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
         if (!census.targetZones.put(r.front()->zone()))
             return false;
     }
 
     {
         Maybe<JS::AutoCheckCannotGC> maybeNoGC;
-        JS::ubi::RootList rootList(cx, maybeNoGC);
-        if (!rootList.init(dbgObj))
+        JS::ubi::RootList rootList(cx->runtime(), maybeNoGC);
+        if (!rootList.init(dbgObj)) {
+            ReportOutOfMemory(cx);
             return false;
+        }
 
-        JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref());
-        if (!traversal.init())
+        JS::ubi::CensusTraversal traversal(cx->runtime(), handler, maybeNoGC.ref());
+        if (!traversal.init()) {
+            ReportOutOfMemory(cx);
             return false;
+        }
         traversal.wantNames = false;
 
         if (!traversal.addStart(JS::ubi::Node(&rootList)) ||
             !traversal.traverse())
         {
+            ReportOutOfMemory(cx);
             return false;
         }
     }
 
     return handler.report(args.rval());
 }
 
 
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -106,16 +106,17 @@ class GlobalObject : public NativeObject
         DEBUGGERS,
         INTRINSICS,
         FLOAT32X4_TYPE_DESCR,
         FLOAT64X2_TYPE_DESCR,
         INT8X16_TYPE_DESCR,
         INT16X8_TYPE_DESCR,
         INT32X4_TYPE_DESCR,
         FOR_OF_PIC_CHAIN,
+        MODULE_RESOLVE_HOOK,
 
         /* Total reserved-slot count for global objects. */
         RESERVED_SLOTS
     };
 
     /*
      * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and
      * we won't expose GlobalObject, so just assert that the two values are
@@ -745,16 +746,29 @@ class GlobalObject : public NativeObject
 
     inline NativeObject* getForOfPICObject() {
         Value forOfPIC = getReservedSlot(FOR_OF_PIC_CHAIN);
         if (forOfPIC.isUndefined())
             return nullptr;
         return &forOfPIC.toObject().as<NativeObject>();
     }
     static NativeObject* getOrCreateForOfPICObject(JSContext* cx, Handle<GlobalObject*> global);
+
+    void setModuleResolveHook(HandleFunction hook) {
+        MOZ_ASSERT(hook);
+        setSlot(MODULE_RESOLVE_HOOK, ObjectValue(*hook));
+    }
+
+    JSFunction* moduleResolveHook() {
+        Value value = getSlotRef(MODULE_RESOLVE_HOOK);
+        if (value.isUndefined())
+            return nullptr;
+
+        return &value.toObject().as<JSFunction>();
+    }
 };
 
 template<>
 inline void
 GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun)
 {
     setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun);
 }
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -487,19 +487,16 @@ GlobalHelperThreadState::ensureInitializ
 }
 
 GlobalHelperThreadState::GlobalHelperThreadState()
  : cpuCount(0),
    threadCount(0),
    threads(nullptr),
    asmJSCompilationInProgress(false),
    helperLock(nullptr),
-#ifdef DEBUG
-   lockOwner(nullptr),
-#endif
    consumerWakeup(nullptr),
    producerWakeup(nullptr),
    pauseWakeup(nullptr),
    numAsmJSFailedJobs(0),
    asmJSFailedFunction(nullptr)
 {
     cpuCount = GetCPUCount();
     threadCount = ThreadCountForCPUCount(cpuCount);
@@ -540,51 +537,51 @@ GlobalHelperThreadState::finishThreads()
 
 void
 GlobalHelperThreadState::lock()
 {
     MOZ_ASSERT(!isLocked());
     AssertCurrentThreadCanLock(HelperThreadStateLock);
     PR_Lock(helperLock);
 #ifdef DEBUG
-    lockOwner = PR_GetCurrentThread();
+    lockOwner.value = PR_GetCurrentThread();
 #endif
 }
 
 void
 GlobalHelperThreadState::unlock()
 {
     MOZ_ASSERT(isLocked());
 #ifdef DEBUG
-    lockOwner = nullptr;
+    lockOwner.value = nullptr;
 #endif
     PR_Unlock(helperLock);
 }
 
 #ifdef DEBUG
 bool
 GlobalHelperThreadState::isLocked()
 {
-    return lockOwner == PR_GetCurrentThread();
+    return lockOwner.value == PR_GetCurrentThread();
 }
 #endif
 
 void
 GlobalHelperThreadState::wait(CondVar which, uint32_t millis)
 {
     MOZ_ASSERT(isLocked());
 #ifdef DEBUG
-    lockOwner = nullptr;
+    lockOwner.value = nullptr;
 #endif
     DebugOnly<PRStatus> status =
         PR_WaitCondVar(whichWakeup(which),
                        millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
     MOZ_ASSERT(status == PR_SUCCESS);
 #ifdef DEBUG
-    lockOwner = PR_GetCurrentThread();
+    lockOwner.value = PR_GetCurrentThread();
 #endif
 }
 
 void
 GlobalHelperThreadState::notifyAll(CondVar which)
 {
     MOZ_ASSERT(isLocked());
     PR_NotifyAllCondVar(whichWakeup(which));
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -237,19 +237,17 @@ class GlobalHelperThreadState
 
   private:
 
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     PRLock* helperLock;
-#ifdef DEBUG
-    PRThread* lockOwner;
-#endif
+    mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner;
 
     /* Condvars for threads waiting/notifying each other. */
     PRCondVar* consumerWakeup;
     PRCondVar* producerWakeup;
     PRCondVar* pauseWakeup;
 
     PRCondVar* whichWakeup(CondVar which) {
         switch (which) {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1008,17 +1008,18 @@ js::Execute(JSContext* cx, HandleScript 
 #endif
 
     /* Use the scope chain as 'this', modulo outerization. */
     JSObject* thisObj = GetThisObject(cx, scopeChain);
     if (!thisObj)
         return false;
     Value thisv = ObjectValue(*thisObj);
 
-    return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), EXECUTE_GLOBAL,
+    ExecuteType type = script->module() ? EXECUTE_MODULE : EXECUTE_GLOBAL;
+    return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), type,
                          NullFramePtr() /* evalInFrame */, rval);
 }
 
 bool
 js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
 {
     const Class* clasp = obj->getClass();
     RootedValue local(cx, v);
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -7,16 +7,18 @@
 #include "vm/ScopeObject-inl.h"
 
 #include "mozilla/PodOperations.h"
 #include "mozilla/SizePrintfMacros.h"
 
 #include "jscompartment.h"
 #include "jsiter.h"
 
+#include "builtin/ModuleObject.h"
+
 #include "vm/ArgumentsObject.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/WeakMapObject.h"
 #include "vm/Xdr.h"
 
 #include "jsatominlines.h"
@@ -311,17 +313,45 @@ const Class CallObject::class_ = {
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
 };
 
 /*****************************************************************************/
 
 const Class ModuleEnvironmentObject::class_ = {
     "ModuleEnvironmentObject",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
-    JSCLASS_IS_ANONYMOUS
+    JSCLASS_IS_ANONYMOUS,
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate   */
+    nullptr,        /* resolve     */
+    nullptr,        /* mayResolve  */
+    nullptr,        /* convert     */
+    nullptr,        /* finalize    */
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    nullptr,        /* trace       */
+    JS_NULL_CLASS_SPEC,
+    JS_NULL_CLASS_EXT,
+    {
+        ModuleEnvironmentObject::lookupProperty,
+        nullptr,                                             /* defineProperty */
+        ModuleEnvironmentObject::hasProperty,
+        ModuleEnvironmentObject::getProperty,
+        ModuleEnvironmentObject::setProperty,
+        ModuleEnvironmentObject::getOwnPropertyDescriptor,
+        ModuleEnvironmentObject::deleteProperty,
+        nullptr, nullptr,                                    /* watch/unwatch */
+        nullptr,                                             /* getElements */
+        ModuleEnvironmentObject::enumerate,
+        nullptr                                              /* thisObject */
+    }
 };
 
 /* static */ ModuleEnvironmentObject*
 ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
 {
     RootedScript script(cx, module->script());
     RootedShape shape(cx, script->bindings.callObjShape());
     MOZ_ASSERT(shape->getObjectClass() == &class_);
@@ -333,17 +363,17 @@ ModuleEnvironmentObject::create(Exclusiv
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
     if (!obj)
         return nullptr;
 
-    Rooted<ModuleEnvironmentObject*> scope(cx, &obj->as<ModuleEnvironmentObject>());
+    RootedModuleEnvironmentObject scope(cx, &obj->as<ModuleEnvironmentObject>());
 
     // Set uninitialized lexicals even on template objects, as Ion will use
     // copy over the template object's slot values in the fast path.
     scope->initAliasedLexicalsToThrowOnTouch(script);
 
     scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module));
     if (!JSObject::setSingleton(cx, scope))
         return nullptr;
@@ -351,21 +381,149 @@ ModuleEnvironmentObject::create(Exclusiv
     // Initialize this early so that we can manipulate the scope object without
     // causing assertions.
     scope->setEnclosingScope(cx->global());
 
     return scope;
 }
 
 ModuleObject&
-ModuleEnvironmentObject::module() const
+ModuleEnvironmentObject::module()
 {
     return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
 }
 
+IndirectBindingMap&
+ModuleEnvironmentObject::importBindings()
+{
+    return module().importBindings();
+}
+
+bool
+ModuleEnvironmentObject::createImportBinding(JSContext*cx, HandleAtom importName,
+                                             HandleModuleObject module, HandleAtom exportName)
+{
+    RootedId importNameId(cx, AtomToId(importName));
+    RootedId exportNameId(cx, AtomToId(exportName));
+    Rooted<ModuleEnvironmentObject*> env(cx, module->environment());
+
+#ifdef DEBUG
+    bool found = false;
+    if (!HasProperty(cx, env, exportNameId, &found))
+        return false;
+    MOZ_ASSERT(found);
+#endif
+
+    IndirectBinding binding(env, exportNameId);
+    if (!importBindings().putNew(importNameId, binding)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
+
+/* static */ bool
+ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                        MutableHandleObject objp, MutableHandleShape propp)
+{
+    if (IndirectBindingMap::Ptr p =
+        obj->as<ModuleEnvironmentObject>().importBindings().lookup(id))
+    {
+        RootedObject target(cx, p->value().environment);
+        RootedId name(cx, p->value().localName);
+        return LookupProperty(cx, target, name, objp, propp);
+    }
+
+    RootedNativeObject target(cx, &obj->as<NativeObject>());
+    if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp))
+        return false;
+
+    objp.set(obj);
+    return true;
+}
+
+/* static */ bool
+ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+    if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
+        *foundp = true;
+        return true;
+    }
+
+    RootedNativeObject self(cx, &obj->as<NativeObject>());
+    return NativeHasProperty(cx, self, id, foundp);
+}
+
+/* static */ bool
+ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+                                     HandleId id, MutableHandleValue vp)
+{
+    if (IndirectBindingMap::Ptr p =
+        obj->as<ModuleEnvironmentObject>().importBindings().lookup(id))
+    {
+        RootedObject target(cx, p->value().environment);
+        RootedId name(cx, p->value().localName);
+        return GetProperty(cx, target, target, name, vp);
+    }
+
+    RootedNativeObject self(cx, &obj->as<NativeObject>());
+    return NativeGetProperty(cx, self, receiver, id, vp);
+}
+
+/* static */ bool
+ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+                                     HandleValue receiver, JS::ObjectOpResult& result)
+{
+    RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
+    if (self->importBindings().has(id))
+        return result.failReadOnly();
+
+    return NativeSetProperty(cx, self, id, v, receiver, Qualified, result);
+}
+
+/* static */ bool
+ModuleEnvironmentObject::getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+                                                  MutableHandle<JSPropertyDescriptor> desc)
+{
+    // We never call this hook on scope objects.
+    MOZ_CRASH();
+}
+
+/* static */ bool
+ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                        ObjectOpResult& result)
+{
+    return result.failCantDelete();
+}
+
+/* static */ bool
+ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+                                   bool enumerableOnly)
+{
+    RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
+    const IndirectBindingMap& bs(self->importBindings());
+
+    MOZ_ASSERT(properties.length() == 0);
+    size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
+    if (!properties.reserve(count)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    for (auto r = bs.all(); !r.empty(); r.popFront())
+        properties.infallibleAppend(r.front().key());
+
+    for (Shape::Range<NoGC> r(self->lastProperty()); !r.empty(); r.popFront())
+        properties.infallibleAppend(r.front().propid());
+
+    MOZ_ASSERT(properties.length() == count);
+    return true;
+}
+
 /*****************************************************************************/
 
 const Class DeclEnvObject::class_ = {
     js_Object_str,
     JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object)
 };
 
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -23,16 +23,19 @@ struct Definition;
 class FunctionBox;
 class ModuleBox;
 }
 
 class StaticWithObject;
 class StaticEvalObject;
 class StaticNonSyntacticScopeObjects;
 
+class ModuleObject;
+typedef Handle<ModuleObject*> HandleModuleObject;
+
 /*****************************************************************************/
 
 /*
  * The static scope chain is the canonical truth for lexical scope contour of
  * a program. The dynamic scope chain is derived from the static scope chain:
  * it is the chain of scopes whose static scopes have a runtime
  * representation, for example, due to aliased bindings.
  *
@@ -379,17 +382,36 @@ class CallObject : public ScopeObject
 class ModuleEnvironmentObject : public CallObject
 {
     static const uint32_t MODULE_SLOT = CallObject::CALLEE_SLOT;
 
   public:
     static const Class class_;
 
     static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module);
-    ModuleObject& module() const;
+    ModuleObject& module();
+    IndirectBindingMap& importBindings();
+
+    bool createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module,
+                             HandleAtom exportName);
+
+  private:
+    static bool lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
+                               MutableHandleObject objp, MutableHandleShape propp);
+    static bool hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
+    static bool getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
+                            MutableHandleValue vp);
+    static bool setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+                            HandleValue receiver, JS::ObjectOpResult& result);
+    static bool getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+                                         MutableHandle<JSPropertyDescriptor> desc);
+    static bool deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+                               ObjectOpResult& result);
+    static bool enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+                          bool enumerableOnly);
 };
 
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
 typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
 
 class DeclEnvObject : public ScopeObject
 {
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -16,16 +16,17 @@
 #include "jsfriendapi.h"
 #include "jshashutil.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 #include "selfhosted.out.h"
 
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
+#include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
 #include "builtin/Reflect.h"
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "gc/Marking.h"
 #include "jit/InlinableNatives.h"
@@ -210,16 +211,26 @@ intrinsic_ThrowTypeError(JSContext* cx, 
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() >= 1);
 
     ThrowErrorWithType(cx, JSEXN_TYPEERR, args);
     return false;
 }
 
+static bool
+intrinsic_ThrowSyntaxError(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() >= 1);
+
+    ThrowErrorWithType(cx, JSEXN_SYNTAXERR, args);
+    return false;
+}
+
 /**
  * Handles an assertion failure in self-hosted code just like an assertion
  * failure in C++ code. Information about the failure can be provided in args[0].
  */
 static bool
 intrinsic_AssertionFailed(JSContext* cx, unsigned argc, Value* vp)
 {
 #ifdef DEBUG
@@ -1239,16 +1250,101 @@ intrinsic_ConstructorForTypedArray(JSCon
     MOZ_ASSERT(protoKey);
     RootedValue ctor(cx, cx->global()->getConstructor(protoKey));
     MOZ_ASSERT(ctor.isObject());
 
     args.rval().set(ctor);
     return true;
 }
 
+static bool
+intrinsic_IsModule(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    MOZ_ASSERT(args[0].isObject());
+
+    args.rval().setBoolean(args[0].toObject().is<ModuleObject>());
+    return true;
+}
+
+static bool
+intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args[0].toObject().is<ModuleObject>());
+    MOZ_ASSERT(args[1].isString());
+
+    RootedFunction moduleResolveHook(cx, cx->global()->moduleResolveHook());
+    if (!moduleResolveHook) {
+        JS_ReportError(cx, "Module resolve hook not set");
+        return false;
+    }
+
+    RootedValue result(cx);
+    if (!JS_CallFunction(cx, nullptr, moduleResolveHook, args, &result))
+        return false;
+
+    if (!result.isObject() || !result.toObject().is<ModuleObject>()) {
+        JS_ReportError(cx, "Module resolve hook did not return Module object");
+        return false;
+    }
+
+    args.rval().set(result);
+    return true;
+}
+
+static bool
+intrinsic_CreateModuleEnvironment(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
+    module->createEnvironment();
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+intrinsic_CreateImportBinding(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 4);
+    RootedModuleEnvironmentObject environment(cx, &args[0].toObject().as<ModuleEnvironmentObject>());
+    RootedAtom importedName(cx, &args[1].toString()->asAtom());
+    RootedModuleObject module(cx, &args[2].toObject().as<ModuleObject>());
+    RootedAtom localName(cx, &args[3].toString()->asAtom());
+    if (!environment->createImportBinding(cx, importedName, module, localName))
+        return false;
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+intrinsic_SetModuleEvaluated(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
+    module->setEvaluated();
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+intrinsic_EvaluateModule(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
+    return module->evaluate(cx, args.rval());
+}
+
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -1324,16 +1420,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_INLINABLE_FN("ToInteger",     intrinsic_ToInteger,               1,0, IntrinsicToInteger),
     JS_INLINABLE_FN("ToString",      intrinsic_ToString,                1,0, IntrinsicToString),
     JS_FN("ToPropertyKey",           intrinsic_ToPropertyKey,           1,0),
     JS_INLINABLE_FN("IsCallable",    intrinsic_IsCallable,              1,0, IntrinsicIsCallable),
     JS_FN("IsConstructor",           intrinsic_IsConstructor,           1,0),
     JS_FN("OwnPropertyKeys",         intrinsic_OwnPropertyKeys,         1,0),
     JS_FN("ThrowRangeError",         intrinsic_ThrowRangeError,         4,0),
     JS_FN("ThrowTypeError",          intrinsic_ThrowTypeError,          4,0),
+    JS_FN("ThrowSyntaxError",        intrinsic_ThrowSyntaxError,        4,0),
     JS_FN("AssertionFailed",         intrinsic_AssertionFailed,         1,0),
     JS_FN("MakeConstructible",       intrinsic_MakeConstructible,       2,0),
     JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0),
     JS_FN("DecompileArg",            intrinsic_DecompileArg,            2,0),
     JS_FN("RuntimeDefaultLocale",    intrinsic_RuntimeDefaultLocale,    0,0),
     JS_FN("LocalTZA",                intrinsic_LocalTZA,                0,0),
 
     JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing,        0,0,
@@ -1480,16 +1577,25 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
 
     // See builtin/RegExp.h for descriptions of the regexp_* functions.
     JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
     JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
     JS_FN("regexp_construct_no_statics", regexp_construct_no_statics, 2,0),
 
+    JS_FN("IsModule", intrinsic_IsModule, 1, 0),
+    JS_FN("CallModuleMethodIfWrapped",
+          CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0),
+    JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0),
+    JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 2, 0),
+    JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
+    JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0),
+    JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
+
     JS_FS_END
 };
 
 void
 js::FillSelfHostingCompileOptions(CompileOptions& options)
 {
     /*
      * In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -145,17 +145,17 @@ StackFrame::functionDisplayNameLength()
 
 // All operations on null ubi::Nodes crash.
 CoarseType Concrete<void>::coarseType() const      { MOZ_CRASH("null ubi::Node"); }
 const char16_t* Concrete<void>::typeName() const   { MOZ_CRASH("null ubi::Node"); }
 JS::Zone* Concrete<void>::zone() const             { MOZ_CRASH("null ubi::Node"); }
 JSCompartment* Concrete<void>::compartment() const { MOZ_CRASH("null ubi::Node"); }
 
 UniquePtr<EdgeRange>
-Concrete<void>::edges(JSContext*, bool) const {
+Concrete<void>::edges(JSRuntime*, bool) const {
     MOZ_CRASH("null ubi::Node");
 }
 
 Node::Size
 Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const
 {
     MOZ_CRASH("null ubi::Node");
 }
@@ -250,18 +250,18 @@ class EdgeVectorTracer : public JS::Call
             return;
         }
     }
 
   public:
     // True if no errors (OOM, say) have yet occurred.
     bool okay;
 
-    EdgeVectorTracer(JSContext* cx, EdgeVector* vec, bool wantNames)
-      : JS::CallbackTracer(JS_GetRuntime(cx)),
+    EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames)
+      : JS::CallbackTracer(rt),
         vec(vec),
         wantNames(wantNames),
         okay(true)
     { }
 };
 
 
 // An EdgeRange concrete class that simply holds a vector of Edges,
@@ -270,20 +270,20 @@ class SimpleEdgeRange : public EdgeRange
     EdgeVector edges;
     size_t i;
 
     void settle() {
         front_ = i < edges.length() ? &edges[i] : nullptr;
     }
 
   public:
-    explicit SimpleEdgeRange(JSContext* cx) : edges(cx), i(0) { }
+    explicit SimpleEdgeRange() : edges(), i(0) { }
 
-    bool init(JSContext* cx, void* thing, JS::TraceKind kind, bool wantNames = true) {
-        EdgeVectorTracer tracer(cx, &edges, wantNames);
+    bool init(JSRuntime* rt, void* thing, JS::TraceKind kind, bool wantNames = true) {
+        EdgeVectorTracer tracer(rt, &edges, wantNames);
         js::TraceChildren(&tracer, thing, kind);
         settle();
         return tracer.okay;
     }
 
     void popFront() override { i++; settle(); }
 };
 
@@ -292,23 +292,22 @@ template<typename Referent>
 JS::Zone*
 TracerConcrete<Referent>::zone() const
 {
     return get().zoneFromAnyThread();
 }
 
 template<typename Referent>
 UniquePtr<EdgeRange>
-TracerConcrete<Referent>::edges(JSContext* cx, bool wantNames) const {
-    UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(
-      cx->new_<SimpleEdgeRange>(cx));
+TracerConcrete<Referent>::edges(JSRuntime* rt, bool wantNames) const {
+    UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(js_new<SimpleEdgeRange>());
     if (!range)
         return nullptr;
 
-    if (!range->init(cx, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames))
+    if (!range->init(rt, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames))
         return nullptr;
 
     return UniquePtr<EdgeRange>(range.release());
 }
 
 template<typename Referent>
 JSCompartment*
 TracerConcreteWithCompartment<Referent>::compartment() const
@@ -390,40 +389,40 @@ template class TracerConcreteWithCompart
 template class TracerConcreteWithCompartment<js::BaseShape>;
 template class TracerConcrete<js::ObjectGroup>;
 } // namespace ubi
 } // namespace JS
 
 namespace JS {
 namespace ubi {
 
-RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */)
+RootList::RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */)
   : noGC(noGC),
-    cx(cx),
-    edges(cx),
+    rt(rt),
+    edges(),
     wantNames(wantNames)
 { }
 
 
 bool
 RootList::init()
 {
-    EdgeVectorTracer tracer(cx, &edges, wantNames);
+    EdgeVectorTracer tracer(rt, &edges, wantNames);
     JS_TraceRuntime(&tracer);
     if (!tracer.okay)
         return false;
-    noGC.emplace(cx->runtime());
+    noGC.emplace(rt);
     return true;
 }
 
 bool
 RootList::init(ZoneSet& debuggees)
 {
-    EdgeVector allRootEdges(cx);
-    EdgeVectorTracer tracer(cx, &allRootEdges, wantNames);
+    EdgeVector allRootEdges;
+    EdgeVectorTracer tracer(rt, &allRootEdges, wantNames);
 
     JS_TraceRuntime(&tracer);
     if (!tracer.okay)
         return false;
     JS_TraceIncomingCCWs(&tracer, debuggees);
     if (!tracer.okay)
         return false;
 
@@ -431,17 +430,17 @@ RootList::init(ZoneSet& debuggees)
         Edge& edge = r.front();
         Zone* zone = edge.referent.zone();
         if (zone && !debuggees.has(zone))
             continue;
         if (!edges.append(mozilla::Move(edge)))
             return false;
     }
 
-    noGC.emplace(cx->runtime());
+    noGC.emplace(rt);
     return true;
 }
 
 bool
 RootList::init(HandleObject debuggees)
 {
     MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
     js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
@@ -473,26 +472,26 @@ RootList::init(HandleObject debuggees)
 bool
 RootList::addRoot(Node node, const char16_t* edgeName)
 {
     MOZ_ASSERT(noGC.isSome());
     MOZ_ASSERT_IF(wantNames, edgeName);
 
     UniquePtr<char16_t[], JS::FreePolicy> name;
     if (edgeName) {
-        name = DuplicateString(cx, edgeName);
+        name = DuplicateString(edgeName);
         if (!name)
             return false;
     }
 
     return edges.append(mozilla::Move(Edge(name.release(), node)));
 }
 
 const char16_t Concrete<RootList>::concreteTypeName[] = MOZ_UTF16("RootList");
 
 UniquePtr<EdgeRange>
-Concrete<RootList>::edges(JSContext* cx, bool wantNames) const {
+Concrete<RootList>::edges(JSRuntime* rt, bool wantNames) const {
     MOZ_ASSERT_IF(wantNames, get().wantNames);
-    return UniquePtr<EdgeRange>(cx->new_<PreComputedEdgeRange>(cx, get().edges));
+    return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
 }
 
 } // namespace ubi
 } // namespace JS
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,21 +24,21 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 307;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 309;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 408,
+static_assert(JSErr_Limit == 413,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext* cx)
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -75,18 +75,18 @@ AccessibleCaretManager::~AccessibleCaret
 {
   CancelCaretTimeoutTimer();
 }
 
 nsresult
 AccessibleCaretManager::OnSelectionChanged(nsIDOMDocument* aDoc,
                                            nsISelection* aSel, int16_t aReason)
 {
-  AC_LOG("aSel: %p, GetSelection(): %p, aReason: %d", aSel, GetSelection(),
-         aReason);
+  AC_LOG("%s: aSel: %p, GetSelection(): %p, aReason: %d", __FUNCTION__,
+         aSel, GetSelection(), aReason);
 
   if (aSel != GetSelection()) {
     return NS_OK;
   }
 
   // Move the cursor by Javascript.
   if (aReason == nsISelectionListener::NO_REASON) {
     HideCarets();
@@ -94,16 +94,23 @@ AccessibleCaretManager::OnSelectionChang
   }
 
   // Move cursor by keyboard.
   if (aReason & nsISelectionListener::KEYPRESS_REASON) {
     HideCarets();
     return NS_OK;
   }
 
+  // OnBlur() might be called between mouse down and mouse up, so we hide carets
+  // upon mouse down anyway, and update carets upon mouse up.
+  if (aReason & nsISelectionListener::MOUSEDOWN_REASON) {
+    HideCarets();
+    return NS_OK;
+  }
+
   // Range will collapse after cutting or copying text.
   if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
                  nsISelectionListener::COLLAPSETOEND_REASON)) {
     HideCarets();
     return NS_OK;
   }
 
   UpdateCarets();
@@ -467,30 +474,32 @@ AccessibleCaretManager::OnScrollEnd()
 
 void
 AccessibleCaretManager::OnScrollPositionChanged()
 {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
-  AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
-  UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+  if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+    AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+    UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+  }
 }
 
 void
 AccessibleCaretManager::OnReflow()
 {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
-  if (mFirstCaret->IsVisuallyVisible() || mSecondCaret->IsVisuallyVisible()) {
-    AC_LOG("%s: UpdateCarets()", __FUNCTION__);
-    UpdateCarets();
+  if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+    AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+    UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
   }
 }
 
 void
 AccessibleCaretManager::OnBlur()
 {
   AC_LOG("%s: HideCarets()", __FUNCTION__);
   HideCarets();
--- a/layout/base/gtest/TestAccessibleCaretManager.cpp
+++ b/layout/base/gtest/TestAccessibleCaretManager.cpp
@@ -60,21 +60,16 @@ public:
 
     MockAccessibleCaretManager()
       : AccessibleCaretManager(nullptr)
     {
       mFirstCaret = MakeUnique<MockAccessibleCaret>();
       mSecondCaret = MakeUnique<MockAccessibleCaret>();
     }
 
-    CaretMode LastUpdateCaretMode() const
-    {
-      return mLastUpdateCaretMode;
-    }
-
     MockAccessibleCaret& FirstCaret()
     {
       return static_cast<MockAccessibleCaret&>(*mFirstCaret);
     }
 
     MockAccessibleCaret& SecondCaret()
     {
       return static_cast<MockAccessibleCaret&>(*mSecondCaret);
@@ -112,97 +107,227 @@ public:
 
     EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
       .WillRepeatedly(Return(PositionChangedResult::Changed));
 
     EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
       .WillRepeatedly(Return(PositionChangedResult::Changed));
   }
 
-  void CheckStates(CaretMode aCaretMode,
-                   Appearance aFirstCaretAppearance,
-                   Appearance aSecondCaretAppearance)
+  AccessibleCaret::Appearance FirstCaretAppearance()
   {
-    EXPECT_EQ(mManager.LastUpdateCaretMode(), aCaretMode);
-    EXPECT_EQ(mManager.FirstCaret().GetAppearance(), aFirstCaretAppearance);
-    EXPECT_EQ(mManager.SecondCaret().GetAppearance(), aSecondCaretAppearance);
+    return mManager.FirstCaret().GetAppearance();
+  }
+
+  AccessibleCaret::Appearance SecondCaretAppearance()
+  {
+    return mManager.SecondCaret().GetAppearance();
   }
 
   // Member variables
   MockAccessibleCaretManager mManager;
 
 }; // class AccessibleCaretManagerTester
 
 TEST_F(AccessibleCaretManagerTester, TestUpdatesInSelectionMode)
 {
   EXPECT_CALL(mManager, GetCaretMode())
     .WillRepeatedly(Return(CaretMode::Selection));
 
   EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
                 CaretChangedReason::Updateposition)).Times(3);
 
   mManager.UpdateCarets();
-  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
 
   mManager.OnReflow();
-  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
 
   mManager.OnScrollPositionChanged();
-  CheckStates(CaretMode::Selection, Appearance::Normal, Appearance::Normal);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
 }
 
-TEST_F(AccessibleCaretManagerTester, TestUpdatesInCursorModeOnNonEmptyContent)
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnNonEmptyInput)
 {
   EXPECT_CALL(mManager, GetCaretMode())
     .WillRepeatedly(Return(CaretMode::Cursor));
 
   EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
     .WillRepeatedly(Return(true));
 
   MockFunction<void(std::string aCheckPointName)> check;
   {
     InSequence dummy;
 
     EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
                   CaretChangedReason::Updateposition)).Times(1);
-    EXPECT_CALL(check, Call("mouse down"));
-
-    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
-                  CaretChangedReason::Updateposition)).Times(1);
-    EXPECT_CALL(check, Call("reflow"));
+    EXPECT_CALL(check, Call("update"));
 
     EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
                   CaretChangedReason::Visibilitychange)).Times(1);
+    EXPECT_CALL(check, Call("mouse down"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+    EXPECT_CALL(check, Call("reflow"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
     EXPECT_CALL(check, Call("blur"));
 
     EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
                   CaretChangedReason::Updateposition)).Times(1);
     EXPECT_CALL(check, Call("mouse up"));
 
     EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
                   CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("reflow2"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
   }
 
   // Simulate a single tap on a non-empty input.
+  mManager.UpdateCarets();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  check.Call("update");
+
   mManager.OnSelectionChanged(nullptr, nullptr,
                               nsISelectionListener::DRAG_REASON |
                               nsISelectionListener::MOUSEDOWN_REASON);
-  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
   check.Call("mouse down");
 
   mManager.OnReflow();
-  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
   check.Call("reflow");
 
   mManager.OnBlur();
-  CheckStates(CaretMode::Cursor, Appearance::None, Appearance::None);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
   check.Call("blur");
 
   mManager.OnSelectionChanged(nullptr, nullptr,
                               nsISelectionListener::MOUSEUP_REASON);
-  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
   check.Call("mouse up");
 
+  mManager.OnReflow();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  check.Call("reflow2");
+
   mManager.OnScrollPositionChanged();
-  CheckStates(CaretMode::Cursor, Appearance::Normal, Appearance::None);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnEmptyInput)
+{
+  EXPECT_CALL(mManager, GetCaretMode())
+    .WillRepeatedly(Return(CaretMode::Cursor));
+
+  EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+    .WillRepeatedly(Return(false));
+
+  MockFunction<void(std::string aCheckPointName)> check;
+  {
+    InSequence dummy;
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("update"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Visibilitychange)).Times(1);
+    EXPECT_CALL(check, Call("mouse down"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+    EXPECT_CALL(check, Call("reflow"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+    EXPECT_CALL(check, Call("blur"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("mouse up"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("reflow2"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+  }
+
+  // Simulate a single tap on an empty input.
+  mManager.UpdateCarets();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+  check.Call("update");
+
+  mManager.OnSelectionChanged(nullptr, nullptr,
+                              nsISelectionListener::DRAG_REASON |
+                              nsISelectionListener::MOUSEDOWN_REASON);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+  check.Call("mouse down");
+
+  mManager.OnReflow();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+  check.Call("reflow");
+
+  mManager.OnBlur();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+  check.Call("blur");
+
+  mManager.OnSelectionChanged(nullptr, nullptr,
+                              nsISelectionListener::MOUSEUP_REASON);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+  check.Call("mouse up");
+
+  mManager.OnReflow();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+  check.Call("reflow2");
+
+  mManager.OnScrollPositionChanged();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestTypingAtEndOfInput)
+{
+  EXPECT_CALL(mManager, GetCaretMode())
+    .WillRepeatedly(Return(CaretMode::Cursor));
+
+  EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+    .WillRepeatedly(Return(true));
+
+  MockFunction<void(std::string aCheckPointName)> check;
+  {
+    InSequence dummy;
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Updateposition)).Times(1);
+    EXPECT_CALL(check, Call("update"));
+
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+                  CaretChangedReason::Visibilitychange)).Times(1);
+    EXPECT_CALL(check, Call("keyboard"));
+
+    // No CaretStateChanged events should be dispatched since the caret has
+    // being hidden in cursor mode.
+    EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_)).Times(0);
+  }
+
+  // Simulate typing the end of the input.
+  mManager.UpdateCarets();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+  check.Call("update");
+
+  mManager.OnKeyboardEvent();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+  check.Call("keyboard");
+
+  mManager.OnSelectionChanged(nullptr, nullptr,
+                              nsISelectionListener::NO_REASON);
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+  mManager.OnScrollPositionChanged();
+  EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
 }
 
 } // namespace mozilla
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -6639,46 +6639,46 @@ nsLayoutUtils::GetTextRunFlagsForStyle(n
     break;
   }
   return result | GetTextRunOrientFlagsForStyle(aStyleContext);
 }
 
 /* static */ uint32_t
 nsLayoutUtils::GetTextRunOrientFlagsForStyle(nsStyleContext* aStyleContext)
 {
-  WritingMode wm(aStyleContext);
-  if (wm.IsVertical()) {
+  uint8_t writingMode = aStyleContext->StyleVisibility()->mWritingMode;
+  switch (writingMode) {
+  case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
+    return gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL;
+
+  case NS_STYLE_WRITING_MODE_VERTICAL_LR:
+  case NS_STYLE_WRITING_MODE_VERTICAL_RL:
     switch (aStyleContext->StyleVisibility()->mTextOrientation) {
     case NS_STYLE_TEXT_ORIENTATION_MIXED:
       return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED;
     case NS_STYLE_TEXT_ORIENTATION_UPRIGHT:
       return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
     case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS:
-      // This should depend on writing mode vertical-lr vs vertical-rl,
-      // but until we support SIDEWAYS_LEFT, we'll treat this the same
-      // as SIDEWAYS_RIGHT and simply fall through.
-      /*
-      if (wm.IsVerticalLR()) {
-        return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
-      } else {
-        return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
-      }
-      */
-    case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT:
       return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
-    case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT:
-      // Not yet supported, so fall through to the default (error) case.
-      /*
-      return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
-      */
     default:
       NS_NOTREACHED("unknown text-orientation");
-    }
-  }
-  return 0;
+      return 0;
+    }
+
+  /* not yet implemented:
+  case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
+    return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
+  */
+  case NS_STYLE_WRITING_MODE_SIDEWAYS_RL:
+    return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+
+  default:
+    NS_NOTREACHED("unknown writing-mode");
+    return 0;
+  }
 }
 
 /* static */ void
 nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2,
                                        nsRect* aHStrip, nsRect* aVStrip) {
   NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
                "expected rects at the same position");
   nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -293,17 +293,23 @@ public:
     //   bit 1 = the eBlockFlowMask value
     static const mozilla::css::Side kLogicalBlockSides[][2] = {
       { NS_SIDE_TOP,    NS_SIDE_BOTTOM },  // horizontal-tb
       { NS_SIDE_RIGHT,  NS_SIDE_LEFT   },  // vertical-rl
       { NS_SIDE_BOTTOM, NS_SIDE_TOP    },  // (horizontal-bt)
       { NS_SIDE_LEFT,   NS_SIDE_RIGHT  },  // vertical-lr
     };
 
+    // Ignore the SIDEWAYS_MASK bit of the writing-mode value, as this has no
+    // effect on the side mappings.
+    aWritingModeValue &= ~NS_STYLE_WRITING_MODE_SIDEWAYS_MASK;
+
+    // What's left of the writing-mode should be in the range 0-3:
     NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value");
+
     return kLogicalBlockSides[aWritingModeValue][aEdge];
   }
 
   mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const
   {
     // indexes are four-bit values:
     //   bit 0 = the eOrientationMask value
     //   bit 1 = the eInlineFlowMask value
@@ -449,51 +455,52 @@ public:
         break;
 
       case NS_STYLE_WRITING_MODE_VERTICAL_LR:
       {
         mWritingMode = eBlockFlowMask |
                        eLineOrientMask |
                        eOrientationMask;
         uint8_t textOrientation = aStyleContext->StyleVisibility()->mTextOrientation;
-#if 0 // not yet implemented
-        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
-          mWritingMode &= ~eLineOrientMask;
-        }
-#endif
-        if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
+        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) {
           mWritingMode |= eSidewaysMask;
         }
         break;
       }
 
       case NS_STYLE_WRITING_MODE_VERTICAL_RL:
       {
         mWritingMode = eOrientationMask;
         uint8_t textOrientation = aStyleContext->StyleVisibility()->mTextOrientation;
-#if 0 // not yet implemented
-        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
-          mWritingMode |= eLineOrientMask;
-        }
-#endif
-        if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
+        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) {
           mWritingMode |= eSidewaysMask;
         }
         break;
       }
 
+      case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
+        mWritingMode = eBlockFlowMask |
+                       eInlineFlowMask |
+                       eOrientationMask |
+                       eSidewaysMask;
+        break;
+
+      case NS_STYLE_WRITING_MODE_SIDEWAYS_RL:
+        mWritingMode = eOrientationMask |
+                       eSidewaysMask;
+        break;
+
       default:
         NS_NOTREACHED("unknown writing mode!");
         mWritingMode = 0;
         break;
     }
 
     if (NS_STYLE_DIRECTION_RTL == styleVisibility->mDirection) {
-      mWritingMode |= eInlineFlowMask | //XXX needs update when text-orientation added
-                      eBidiMask;
+      mWritingMode ^= eInlineFlowMask | eBidiMask;
     }
   }
 
   /**
    * This function performs fixup for elements with 'unicode-bidi: plaintext',
    * where inline directionality is derived from the Unicode bidi categories
    * of the element's content, and not the CSS 'direction' property.
    *
@@ -535,16 +542,31 @@ public:
    */
   bool IsOrthogonalTo(const WritingMode& aOther) const
   {
     return IsVertical() != aOther.IsVertical();
   }
 
   uint8_t GetBits() const { return mWritingMode; }
 
+#ifdef DEBUG
+  const char* DebugString() const {
+    return IsVertical()
+      ? IsVerticalLR()
+        ? IsBidiLTR()
+          ? IsSideways() ? "sw-lr-ltr" : "v-lr-ltr"
+          : IsSideways() ? "sw-lr-rtl" : "v-lr-rtl"
+        : IsBidiLTR()
+          ? IsSideways() ? "sw-rl-ltr" : "v-rl-ltr"
+          : IsSideways() ? "sw-rl-rtl" : "v-rl-rtl"
+      : IsBidiLTR() ? "h-ltr" : "h-rtl"
+      ;
+  }
+#endif
+
 private:
   friend class LogicalPoint;
   friend class LogicalSize;
   friend class LogicalMargin;
   friend class LogicalRect;
 
   friend struct IPC::ParamTraits<WritingMode>;
   // IMENotification cannot store this class directly since this has some
@@ -575,17 +597,20 @@ private:
     eOrientationMask = 0x01, // true means vertical text
     eInlineFlowMask  = 0x02, // true means absolute RTL/BTT (against physical coords)
     eBlockFlowMask   = 0x04, // true means vertical-LR (or horizontal-BT if added)
     eLineOrientMask  = 0x08, // true means over != block-start
     eBidiMask        = 0x10, // true means line-relative RTL (bidi RTL)
     // Note: We have one excess bit of info; WritingMode can pack into 4 bits.
     // But since we have space, we're caching interesting things for fast access.
 
-    eSidewaysMask    = 0x20, // true means text-orientation is sideways-*,
+    eSidewaysMask    = 0x20, // true means text is being rendered vertically
+                             // using rotated glyphs (i.e. writing-mode is
+                             // sideways-*, or writing-mode is vertical-* AND
+                             // text-orientation is sideways),
                              // which means we'll use alphabetic instead of
                              // centered default baseline for vertical text
 
     // Masks for output enums
     eInlineMask = 0x03,
     eBlockMask  = 0x05
   };
 };
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5753,35 +5753,29 @@ nsIFrame::ListGeneric(nsACString& aTo, c
   void* IBprevsibling = Properties().Get(IBSplitPrevSibling());
   if (IBprevsibling) {
     aTo += nsPrintfCString(" IBSplitPrevSibling=%p", IBprevsibling);
   }
   aTo += nsPrintfCString(" {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height);
 
   mozilla::WritingMode wm = GetWritingMode();
   if (wm.IsVertical() || !wm.IsBidiLTR()) {
-    aTo += nsPrintfCString(" wm=%s-%s: logical size={%d,%d}",
-                           wm.IsVertical() ? wm.IsVerticalLR() ? "vlr" : "vrl"
-                                           : "htb",
-                           wm.IsBidiLTR() ? "ltr" : "rtl",
+    aTo += nsPrintfCString(" wm=%s: logical size={%d,%d}", wm.DebugString(),
                            ISize(), BSize());
   }
 
   nsIFrame* parent = GetParent();
   if (parent) {
     WritingMode pWM = parent->GetWritingMode();
     if (pWM.IsVertical() || !pWM.IsBidiLTR()) {
       nsSize containerSize = parent->mRect.Size();
       LogicalRect lr(pWM, mRect, containerSize);
-      aTo += nsPrintfCString(" parent wm=%s-%s, cs={%d,%d}, "
+      aTo += nsPrintfCString(" parent wm=%s, cs={%d,%d}, "
                              " logicalRect={%d,%d,%d,%d}",
-                             pWM.IsVertical() ? pWM.IsVerticalLR()
-                                                ? "vlr" : "vrl"
-                                              : "htb",
-                             wm.IsBidiLTR() ? "ltr" : "rtl",
+                             pWM.DebugString(),
                              containerSize.width, containerSize.height,
                              lr.IStart(pWM), lr.BStart(pWM),
                              lr.ISize(pWM), lr.BSize(pWM));
     }
   }
   nsIFrame* f = const_cast<nsIFrame*>(this);
   if (f->HasOverflowAreas()) {
     nsRect vo = f->GetVisualOverflowRect();
--- a/layout/generic/nsLineBox.cpp
+++ b/layout/generic/nsLineBox.cpp
@@ -245,21 +245,18 @@ nsLineBox::List(FILE* out, const char* a
           StateToString(cbuf, sizeof(cbuf)));
   if (IsBlock() && !GetCarriedOutBEndMargin().IsZero()) {
     str += nsPrintfCString("bm=%d ", GetCarriedOutBEndMargin().get());
   }
   nsRect bounds = GetPhysicalBounds();
   str += nsPrintfCString("{%d,%d,%d,%d} ",
           bounds.x, bounds.y, bounds.width, bounds.height);
   if (mWritingMode.IsVertical() || !mWritingMode.IsBidiLTR()) {
-    str += nsPrintfCString("{%s-%s: %d,%d,%d,%d; cs=%d,%d} ",
-                           mWritingMode.IsVertical()
-                             ? mWritingMode.IsVerticalLR() ? "vlr" : "vrl"
-                             : "htb",
-                           mWritingMode.IsBidiLTR() ? "ltr" : "rtl",
+    str += nsPrintfCString("{%s: %d,%d,%d,%d; cs=%d,%d} ",
+                           mWritingMode.DebugString(),
                            IStart(), BStart(), ISize(), BSize(),
                            mContainerSize.width, mContainerSize.height);
   }
   if (mData &&
       (!mData->mOverflowAreas.VisualOverflow().IsEqualEdges(bounds) ||
        !mData->mOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds))) {
     str += nsPrintfCString("vis-overflow=%d,%d,%d,%d scr-overflow=%d,%d,%d,%d ",
             mData->mOverflowAreas.VisualOverflow().x,
--- a/layout/reftests/svg/as-image/reftest.list
+++ b/layout/reftests/svg/as-image/reftest.list
@@ -41,17 +41,17 @@ skip-if(B2G||Mulet) == canvas-drawImage-
 skip-if(B2G||Mulet) == canvas-drawImage-scale-1b.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == canvas-drawImage-scale-1c.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 
 # Fails on Android versions where we apply a zoom by default, because the
 # resolution of a canvas element is fixed regardless of zoom level.
 fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2a.html canvas-drawImage-scale-2-ref.html
 fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2b.html canvas-drawImage-scale-2-ref.html
 
-fuzzy-if(winWidget&&!d2d,1,10000) fuzzy-if(Android||B2G,1,10000) == canvas-drawImage-alpha-1.html canvas-drawImage-alpha-1-ref.html
+fuzzy-if(winWidget&&!d2d,1,10000) fuzzy-if(azureSkia,1,10000) fuzzy-if(Android||B2G,1,10000) == canvas-drawImage-alpha-1.html canvas-drawImage-alpha-1-ref.html
 #Same as scale-2a but with globalAlpha:
 fuzzy(1,2) fuzzy-if(azureSkia,1,40000) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-alpha-2.html canvas-drawImage-alpha-2-ref.html
 
 skip-if(B2G||Mulet) == canvas-drawImage-slice-1a.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == canvas-drawImage-slice-1b.html lime100x100-ref.html
 
 == canvas-drawImage-origin-clean-1.html lime100x100-ref.html
 == canvas-drawImage-transform-restored.html canvas-drawImage-transform-restored-ref.html
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -479,19 +479,20 @@ CSS_KEY(select-before, select_before)
 CSS_KEY(select-menu, select_menu)
 CSS_KEY(select-same, select_same)
 CSS_KEY(semi-condensed, semi_condensed)
 CSS_KEY(semi-expanded, semi_expanded)
 CSS_KEY(separate, separate)
 CSS_KEY(sepia, sepia)
 CSS_KEY(serif, serif)
 CSS_KEY(show, show)
-/* CSS_KEY(sideways, sideways) */
-/* CSS_KEY(sideways-left, sideways_left) */
-CSS_KEY(sideways-right, sideways_right)
+CSS_KEY(sideways, sideways)
+/*CSS_KEY(sideways-lr, sideways_lr)*/
+CSS_KEY(sideways-right, sideways_right) /* alias for 'sideways' */
+CSS_KEY(sideways-rl, sideways_rl)
 CSS_KEY(simp-chinese-formal, simp_chinese_formal)
 CSS_KEY(simp-chinese-informal, simp_chinese_informal)
 CSS_KEY(simplified, simplified)
 CSS_KEY(skew, skew)
 CSS_KEY(skewx, skewx)
 CSS_KEY(skewy, skewy)
 CSS_KEY(slashed-zero, slashed_zero)
 CSS_KEY(slice, slice)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -1770,19 +1770,18 @@ const KTableValue nsCSSProps::kTextDecor
   eCSSKeyword_dashed, NS_STYLE_TEXT_DECORATION_STYLE_DASHED,
   eCSSKeyword_wavy, NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
   eCSSKeyword_UNKNOWN,-1
 };
 
 const KTableValue nsCSSProps::kTextOrientationKTable[] = {
   eCSSKeyword_mixed, NS_STYLE_TEXT_ORIENTATION_MIXED,
   eCSSKeyword_upright, NS_STYLE_TEXT_ORIENTATION_UPRIGHT,
-  eCSSKeyword_sideways_right, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT,
-  /* eCSSKeyword_sideways_left, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT, */
-  /* eCSSKeyword_sideways, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS, */
+  eCSSKeyword_sideways, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS,
+  eCSSKeyword_sideways_right, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS,
   eCSSKeyword_UNKNOWN, -1
 };
 
 const KTableValue nsCSSProps::kTextOverflowKTable[] = {
   eCSSKeyword_clip, NS_STYLE_TEXT_OVERFLOW_CLIP,
   eCSSKeyword_ellipsis, NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
   eCSSKeyword_UNKNOWN, -1
 };
@@ -1940,16 +1939,18 @@ const KTableValue nsCSSProps::kWordWrapK
   eCSSKeyword_break_word, NS_STYLE_WORDWRAP_BREAK_WORD,
   eCSSKeyword_UNKNOWN,-1
 };
 
 const KTableValue nsCSSProps::kWritingModeKTable[] = {
   eCSSKeyword_horizontal_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB,
   eCSSKeyword_vertical_lr, NS_STYLE_WRITING_MODE_VERTICAL_LR,
   eCSSKeyword_vertical_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL,
+/*  eCSSKeyword_sideways_lr, NS_STYLE_WRITING_MODE_SIDEWAYS_LR, */
+  eCSSKeyword_sideways_rl, NS_STYLE_WRITING_MODE_SIDEWAYS_RL,
   eCSSKeyword_lr, NS_STYLE_WRITING_MODE_HORIZONTAL_TB,
   eCSSKeyword_lr_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB,
   eCSSKeyword_rl, NS_STYLE_WRITING_MODE_HORIZONTAL_TB,
   eCSSKeyword_rl_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB,
   eCSSKeyword_tb, NS_STYLE_WRITING_MODE_VERTICAL_RL,
   eCSSKeyword_tb_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL,
   eCSSKeyword_UNKNOWN, -1
 };
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7636,16 +7636,18 @@ nsRuleNode::ComputePositionData(void* aS
     default:
       MOZ_ASSERT(false, "unexpected writing-mode value");
       // fall through
     case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
       vertical = false;
       break;
     case NS_STYLE_WRITING_MODE_VERTICAL_RL:
     case NS_STYLE_WRITING_MODE_VERTICAL_LR:
+    case NS_STYLE_WRITING_MODE_SIDEWAYS_RL:
+    case NS_STYLE_WRITING_MODE_SIDEWAYS_LR:
       vertical = true;
       break;
   }
 
   const nsCSSValue* width = aRuleData->ValueForWidth();
   SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ?
              nsCSSValue(eCSSUnit_Unset) : *width,
            pos->mWidth, parentPos->mWidth,
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -394,22 +394,35 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_CURSOR_EW_RESIZE               35
 #define NS_STYLE_CURSOR_NONE                    36
 
 // See nsStyleVisibility
 #define NS_STYLE_DIRECTION_LTR                  0
 #define NS_STYLE_DIRECTION_RTL                  1
 
 // See nsStyleVisibility
-// WritingModes.h depends on the particular values used here
+// NOTE: WritingModes.h depends on the particular values used here.
 #define NS_STYLE_WRITING_MODE_HORIZONTAL_TB     0
 #define NS_STYLE_WRITING_MODE_VERTICAL_RL       1
 // #define NS_STYLE_WRITING_MODE_HORIZONTAL_BT  2  // hypothetical
 #define NS_STYLE_WRITING_MODE_VERTICAL_LR       3
 
+// Single-bit flag, used in combination with VERTICAL_LR and _RL to specify
+// the corresponding SIDEWAYS_* modes.
+// (To avoid ambiguity, this bit must be high enough such that no other
+// values here accidentally use it in their binary representation.)
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_MASK     4
+
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_RL         \
+          (NS_STYLE_WRITING_MODE_VERTICAL_RL |    \
+           NS_STYLE_WRITING_MODE_SIDEWAYS_MASK)
+#define NS_STYLE_WRITING_MODE_SIDEWAYS_LR         \
+          (NS_STYLE_WRITING_MODE_VERTICAL_LR |    \
+           NS_STYLE_WRITING_MODE_SIDEWAYS_MASK)
+
 // See nsStyleDisplay
 #define NS_STYLE_DISPLAY_NONE                   0
 #define NS_STYLE_DISPLAY_BLOCK                  1
 #define NS_STYLE_DISPLAY_INLINE                 2
 #define NS_STYLE_DISPLAY_INLINE_BLOCK           3
 #define NS_STYLE_DISPLAY_LIST_ITEM              4
 #define NS_STYLE_DISPLAY_TABLE                  8
 #define NS_STYLE_DISPLAY_INLINE_TABLE           9
@@ -874,19 +887,17 @@ static inline mozilla::css::Side operato
 
 // See nsStyleText
 #define NS_STYLE_TEXT_SIZE_ADJUST_NONE          0
 #define NS_STYLE_TEXT_SIZE_ADJUST_AUTO          1
 
 // See nsStyleText
 #define NS_STYLE_TEXT_ORIENTATION_MIXED          0
 #define NS_STYLE_TEXT_ORIENTATION_UPRIGHT        1
-#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT 2
-#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT  3 /* placeholder, not yet parsed */
-#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS       4 /* placeholder, not yet parsed */
+#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS       2
 
 // See nsStyleText
 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE        0
 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL         1
 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2    2
 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3    3
 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4    4
 
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4572,22 +4572,34 @@ var gCSSProperties = {
 function logical_axis_prop_get_computed(cs, property)
 {
   // Use defaults for these two properties in case the vertical text
   // pref (which they live behind) is turned off.
   var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb";
   var orientation = writingMode.substring(0, writingMode.indexOf("-"));
 
   var mappings = {
-    "block-size":      { horizontal: "height",     vertical: "width"      },
-    "inline-size":     { horizontal: "width",      vertical: "height"     },
-    "max-block-size":  { horizontal: "max-height", vertical: "max-width"  },
-    "max-inline-size": { horizontal: "max-width",  vertical: "max-height" },
-    "min-block-size":  { horizontal: "min-height", vertical: "min-width"  },
-    "min-inline-size": { horizontal: "min-width",  vertical: "min-height" },
+    "block-size":      { horizontal: "height",
+                         vertical:   "width",
+                         sideways:   "width"      },
+    "inline-size":     { horizontal: "width",
+                         vertical:   "height",
+                         sideways:   "height"     },
+    "max-block-size":  { horizontal: "max-height",
+                         vertical:   "max-width",
+                         sideways:   "max-width"  },
+    "max-inline-size": { horizontal: "max-width",
+                         vertical:   "max-height",
+                         sideways:   "max-height" },
+    "min-block-size":  { horizontal: "min-height",
+                         vertical:   "min-width",
+                         sideways:   "min-width"  },
+    "min-inline-size": { horizontal: "min-width",
+                         vertical:   "min-height",
+                         sideways:   "min-height" },
   };
 
   if (!mappings[property]) {
     throw "unexpected property " + property;
   }
 
   var prop = mappings[property][orientation];
   if (!prop) {
@@ -4596,61 +4608,52 @@ function logical_axis_prop_get_computed(
 
   return cs.getPropertyValue(prop);
 }
 
 function logical_box_prop_get_computed(cs, property)
 {
   // http://dev.w3.org/csswg/css-writing-modes-3/#logical-to-physical
 
-  // Use defaults for these two properties in case the vertical text
-  // pref (which they live behind) is turned off.
+  // Use default for writing-mode in case the vertical text
+  // pref (which it lives behind) is turned off.
   var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb";
-  var textOrientation = cs.getPropertyValue("text-orientation") || "mixed";
 
   var direction = cs.getPropertyValue("direction");
 
-  // We only need to distinguish between text-orientation values of
-  // sideways-left and {mixed,upright,sideways-right} (which we will
-  // call "others").
-
-  if (textOrientation == "sideways") {
-    // text-orientation does not contribute to the logical to physical
-    // mapping when writing-mode is horizontal-tb, so it doesn't matter
-    // that we convert it to sideways-left in that case.
-    textOrientation = writingMode == "vertical-rl" ? "others" : "sideways-left";
-  } else if (textOrientation != "sideways-left") {
-    textOrientation = "others";
-  }
-
   // keys in blockMappings are writing-mode values
   var blockMappings = {
     "horizontal-tb": { "start": "top",   "end": "bottom" },
     "vertical-rl":   { "start": "right", "end": "left"   },
     "vertical-lr":   { "start": "left",  "end": "right"  },
+    "sideways-rl":   { "start": "right", "end": "left"   },
+    "sideways-lr":   { "start": "left",  "end": "right"  },
   };
 
   // keys in inlineMappings are regular expressions that match against
-  // a {writing-mode,text-orientation,direction} triple as a space-
-  // separated string
+  // a {writing-mode,direction} pair as a space-separated string
   var inlineMappings = {
-    "horizontal-tb \\S+ ltr":        { "start": "left",   "end": "right"  },
-    "horizontal-tb \\S+ rtl":        { "start": "right",  "end": "left"   },
-    "vertical-.. sideways-left ltr": { "start": "bottom", "end": "top"    },
-    "vertical-.. sideways-left rtl": { "start": "top",    "end": "bottom" },
-    "vertical-.. others ltr":        { "start": "top",    "end": "bottom" },
-    "vertical-.. others rtl":        { "start": "bottom", "end": "top"    },
+    "horizontal-tb ltr": { "start": "left",   "end": "right"  },
+    "horizontal-tb rtl": { "start": "right",  "end": "left"   },
+    "vertical-.. ltr":   { "start": "bottom", "end": "top"    },
+    "vertical-.. rtl":   { "start": "top",    "end": "bottom" },
+    "vertical-.. ltr":   { "start": "top",    "end": "bottom" },
+    "vertical-.. rtl":   { "start": "bottom", "end": "top"    },
+    "sideways-lr ltr":   { "start": "bottom", "end": "top"    },
+    "sideways-lr rtl":   { "start": "top",    "end": "bottom" },
+    "sideways-rl ltr":   { "start": "top",    "end": "bottom" },
+    "sideways-rl rtl":   { "start": "bottom", "end": "top"    },
   };
 
   var blockMapping = blockMappings[writingMode];
   var inlineMapping;
 
   // test each regular expression in inlineMappings against the
-  // {writing-mode,text-orientation,direction} triple
-  var key = `${writingMode} ${textOrientation} ${direction}`;
+  // {writing-mode,direction} pair
+  var key = `${writingMode} ${direction}`;
   for (var k in inlineMappings) {
     if (new RegExp(k).test(key)) {
       inlineMapping = inlineMappings[k];
       break;
     }
   }
 
   if (!blockMapping || !inlineMapping) {
@@ -4723,26 +4726,26 @@ if (SpecialPowers.getBoolPref("layout.cs
 
 if (SpecialPowers.getBoolPref("layout.css.vertical-text.enabled")) {
   var verticalTextProperties = {
     "writing-mode": {
       domProp: "writingMode",
       inherited: true,
       type: CSS_TYPE_LONGHAND,
       initial_values: [ "horizontal-tb", "lr", "lr-tb", "rl", "rl-tb" ],
-      other_values: [ "vertical-lr", "vertical-rl", "tb", "tb-rl" ],
-      invalid_values: [ "10px", "30%", "justify", "auto", "1em" ]
+      other_values: [ "vertical-lr", "vertical-rl", "sideways-rl", "tb", "tb-rl" ],
+      invalid_values: [ "10px", "30%", "justify", "auto", "1em", "sideways-lr" ] /* sideways-lr not yet supported */
     },
     "text-orientation": {
       domProp: "textOrientation",
       inherited: true,
       type: CSS_TYPE_LONGHAND,
       initial_values: [ "mixed" ],
-      other_values: [ "upright", "sideways-right" ],
-      invalid_values: [ "none", "3em", "sideways", "sideways-left" ] /* sideways, sideways-left not yet supported */
+      other_values: [ "upright", "sideways", "sideways-right" ], /* sideways-right alias for backward compatibility */
+      invalid_values: [ "none", "3em", "sideways-left" ] /* sideways-left removed from CSS Writing Modes */
     },
     "border-block-end": {
       domProp: "borderBlockEnd",
       inherited: false,
       type: CSS_TYPE_TRUE_SHORTHAND,
       subproperties: [ "border-block-end-color", "border-block-end-style", "border-block-end-width" ],
       initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ],
       other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ],
--- a/layout/style/test/test_logical_properties.html
+++ b/layout/style/test/test_logical_properties.html
@@ -38,70 +38,52 @@ var gAxisPropertyGroups;
 
 // values to use while testing
 var gValues = {
   "length":       ["1px", "2px", "3px", "4px", "5px"],
   "color":        ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
   "border-style": ["solid", "dashed", "dotted", "double", "groove"],
 };
 
-// six unique overall writing modes
+// Six unique overall writing modes for property-mapping purposes.
+// Note that text-orientation does not affect these mappings, now that
+// the proposed sideways-left value no longer exists (superseded in CSS
+// Writing Modes by writing-mode: sideways-lr).
 var gWritingModes = [
   { style: [
-      "writing-mode: horizontal-tb; text-orientation: mixed; direction: ltr; ",
-      "writing-mode: horizontal-tb; text-orientation: upright; direction: ltr; ",
-      "writing-mode: horizontal-tb; text-orientation: sideways-right; direction: ltr; ",
-      // XXX See the todo()s below.
-      // "writing-mode: horizontal-tb; text-orientation: sideways-left; direction: ltr; ",
-      // "writing-mode: horizontal-tb; text-orientation: sideways; direction: ltr; ",
+      "writing-mode: horizontal-tb; direction: ltr; ",
     ],
     blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
     block: "vertical", inline: "horizontal" },
   { style: [
-      "writing-mode: horizontal-tb; text-orientation: mixed; direction: rtl; ",
-      "writing-mode: horizontal-tb; text-orientation: upright; direction: rtl; ",
-      "writing-mode: horizontal-tb; text-orientation: sideways-right; direction: rtl; ",
-      // "writing-mode: horizontal-tb; text-orientation: sideways-left; direction: rtl; ",
-      // "writing-mode: horizontal-tb; text-orientation: sideways; direction: rtl; ",
+      "writing-mode: horizontal-tb; direction: rtl; ",
     ],
     blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
     block: "vertical", inline: "horizontal" },
   { style: [
-      "writing-mode: vertical-rl; text-orientation: mixed; direction: rtl; ",
-      "writing-mode: vertical-rl; text-orientation: upright; direction: rtl; ",
-      "writing-mode: vertical-rl; text-orientation: sideways-right; direction: rtl; ",
-      // "writing-mode: vertical-rl; text-orientation: sideways-left; direction: ltr; ",
-      // "writing-mode: vertical-rl; text-orientation: sideways; direction: rtl; ",
+      "writing-mode: vertical-rl; direction: rtl; ",
+      "writing-mode: sideways-rl; direction: rtl; ",
     ],
     blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
     block: "horizontal", inline: "vertical" },
   { style: [
-      "writing-mode: vertical-rl; text-orientation: mixed; direction: ltr; ",
-      "writing-mode: vertical-rl; text-orientation: upright; direction: ltr; ",
-      "writing-mode: vertical-rl; text-orientation: sideways-right; direction: ltr; ",
-      // "writing-mode: vertical-rl; text-orientation: sideways-left; direction: rtl; ",
-      // "writing-mode: vertical-rl; text-orientation: sideways; direction: ltr; ",
+      "writing-mode: vertical-rl; direction: ltr; ",
+      "writing-mode: sideways-rl; direction: ltr; ",
     ],
     blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
     block: "horizontal", inline: "vertical" },
   { style: [
-      "writing-mode: vertical-lr; text-orientation: mixed; direction: rtl; ",
-      "writing-mode: vertical-lr; text-orientation: upright; direction: rtl; ",
-      "writing-mode: vertical-lr; text-orientation: sideways-right; direction: rtl; ",
-      // "writing-mode: vertical-lr; text-orientation: sideways-left; direction: ltr; ",
-      // "writing-mode: vertical-lr; text-orientation: sideways; direction: ltr; ",
+      "writing-mode: vertical-lr; direction: rtl; ",
+      // "writing-mode: sideways-lr; direction: ltr; ",
     ],
     blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
     block: "horizontal", inline: "vertical" },
   { style: [
-      "writing-mode: vertical-lr; text-orientation: mixed; direction: ltr; ",
-      "writing-mode: vertical-lr; text-orientation: upright; direction: ltr; ",
-      "writing-mode: vertical-lr; text-orientation: sideways-right; direction: ltr; ",
-      // "writing-mode: vertical-lr; text-orientation: sideways-left; direction: rtl; ",
-      // "writing-mode: vertical-lr; text-orientation: sideways; direction: rtl; ",
+      "writing-mode: vertical-lr; direction: ltr; ",
+      // "writing-mode: sideways-lr; direction: rtl; ",
     ],
     blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
     block: "horizontal", inline: "vertical" },
 ];
 
 function init() {
   gBoxPropertyGroups = [];
 
@@ -157,33 +139,26 @@ function init() {
       horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`,
       inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`,
       type: "length",
       prerequisites:
         make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites)
     });
   });
 
-  // Assume that sideways-left and sideways keywords are still not parsed yet
-  // for text-orientation.  When we start supporting these keywords, the
-  // entries in the .style properties of the gWritingModes objects above
-  // should be uncommented.
+  // Assume that the sideways-lr keyword is not parsed yet for writing-mode.
+  // When we start supporting this keyword, the entries in the .style
+  // properties of the gWritingModes objects above should be uncommented.
   var s = document.createElement("style");
   document.body.appendChild(s);
 
   s.textContent = "div { }";
-  s.sheet.cssRules[0].style.textOrientation = "sideways-left";
-  todo(s.sheet.cssRules[0].style.textOrientation, "sideways-left",
-       "uncomment sideways-left cases from gWritingModes and " +
-       "remove this todo()!");
-
-  s.textContent = "div { }";
-  s.sheet.cssRules[0].style.textOrientation = "sideways";
-  todo(s.sheet.cssRules[0].style.textOrientation, "sideways",
-       "uncomment sideways cases from gWritingModes and " +
+  s.sheet.cssRules[0].style.writingMode = "sideways-lr";
+  todo(s.sheet.cssRules[0].style.writingMode, "sideways-lr",
+       "uncomment sideways-lr cases from gWritingModes and " +
        "remove this todo()!");
 
   s.remove();
 }
 
 function test_computed_values(aTestName, aRules, aExpectedValues) {
   gSheet.textContent = aRules;
   var cs = getComputedStyle(gTest);
--- a/media/webrtc/trunk/webrtc/common_types.h
+++ b/media/webrtc/trunk/webrtc/common_types.h
@@ -782,19 +782,20 @@ struct OverUseDetectorOptions {
   double initial_e[2][2];
   double initial_process_noise[2];
   double initial_avg_noise;
   double initial_var_noise;
   double initial_threshold;
 };
 
 enum CPULoadState {
-  kLoadRelaxed,
+  kLoadRelaxed = 0,
   kLoadNormal,
-  kLoadStressed
+  kLoadStressed,
+  kLoadLast,
 };
 
 class CPULoadStateObserver {
 public:
   virtual void onLoadStateChanged(CPULoadState aNewState) = 0;
   virtual ~CPULoadStateObserver() {};
 };
 
--- a/mfbt/WeakPtr.h
+++ b/mfbt/WeakPtr.h
@@ -167,17 +167,23 @@ public:
 
   WeakPtr(const WeakPtr& aOther)
   {
     *this = aOther;
   }
 
   WeakPtr& operator=(T* aOther)
   {
-    return *this = aOther->SelfReferencingWeakPtr();
+    if (aOther) {
+      *this = aOther->SelfReferencingWeakPtr();
+    } else if (!mRef || mRef->get()) {
+      // Ensure that mRef is dereferenceable in the uninitialized state.
+      mRef = new WeakReference(nullptr);
+    }
+    return *this;
   }
 
   MOZ_IMPLICIT WeakPtr(T* aOther)
   {
     *this = aOther;
   }
 
   // Ensure that mRef is dereferenceable in the uninitialized state.
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1707,17 +1707,17 @@ var BrowserApp = {
             Log.e("Browser", "Incorrect index " + index + " truncated to " + historySize - 1);
             index = historySize - 1;
           }
 
           browser.gotoIndex(index);
           break;
 
       case "Session:Reload": {
-        let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+        let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY;
 
         // Check to see if this is a message to enable/disable mixed content blocking.
         if (aData) {
           let data = JSON.parse(aData);
           if (data.contentType === "tracking") {
             // Convert document URI into the format used by
             // nsChannelClassifier::ShouldEnableTrackingProtection
             // (any scheme turned into https is correct)
@@ -3597,18 +3597,17 @@ Tab.prototype = {
     }
 
     // Only reload the page for http/https schemes
     let currentURI = this.browser.currentURI;
     if (!currentURI.schemeIs("http") && !currentURI.schemeIs("https"))
       return;
 
     let url = currentURI.spec;
-    let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
-                Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+    let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
     if (this.originalURI && !this.originalURI.equals(currentURI)) {
       // We were redirected; reload the original URL
       url = this.originalURI.spec;
     }
 
     this.browser.docShell.loadURI(url, flags, null, null, null);
   },
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -141,19 +141,16 @@ pref("dom.select_events.enabled", true);
 pref("dom.select_events.enabled", false);
 #endif
 
 // Whether or not Web Workers are enabled.
 pref("dom.workers.enabled", true);
 // The number of workers per domain allowed to run concurrently.
 pref("dom.workers.maxPerDomain", 20);
 
-// Whether or not Shared Web Workers are enabled.
-pref("dom.workers.sharedWorkers.enabled", true);
-
 pref("dom.serviceWorkers.enabled", false);
 
 // Allow service workers to intercept network requests using the fetch event
 pref("dom.serviceWorkers.interception.enabled", false);
 
 // Allow service workers to intercept opaque (cross origin) responses
 pref("dom.serviceWorkers.interception.opaque.enabled", false);
 
@@ -680,17 +677,17 @@ pref("gfx.font_rendering.opentype_svg.en
 // e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo");
 pref("gfx.canvas.azure.backends", "direct2d1.1,direct2d,skia,cairo");
 pref("gfx.content.azure.backends", "direct2d1.1,direct2d,cairo");
 #else
 #ifdef XP_MACOSX
 pref("gfx.content.azure.backends", "cg");
 pref("gfx.canvas.azure.backends", "skia");
 // Accelerated cg canvas where available (10.7+)
-pref("gfx.canvas.azure.accelerated", false);
+pref("gfx.canvas.azure.accelerated", true);
 #else
 pref("gfx.canvas.azure.backends", "cairo");
 pref("gfx.content.azure.backends", "cairo");
 #endif
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
 pref("gfx.content.azure.backends", "cairo");
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2878,16 +2878,33 @@ nsCookieService::SetCookieInternal(nsIUR
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests");
     return newCookie;
   }
   if (!CheckPath(cookieAttributes, aHostURI)) {
     COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests");
     return newCookie;
   }
 
+  // reject cookie if value contains an RFC 6265 disallowed character - see
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423
+  // NOTE: this is not the full set of characters disallowed by 6265 - notably
+  // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is
+  // for parity with Chrome. This only applies to cookies set via the Set-Cookie
+  // header, as document.cookie is defined to be UTF-8. Hooray for
+  // symmetry!</sarcasm>
+  const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                                     0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+                                     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+                                     0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
+                                     0x1E, 0x1F, 0x3B, 0x00 };
+  if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) {
+    COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character");
+    return newCookie;
+  }
+
   // create a new nsCookie and copy attributes
   nsRefPtr<nsCookie> cookie =
     nsCookie::Create(cookieAttributes.name,
                      cookieAttributes.value,
                      cookieAttributes.host,
                      cookieAttributes.path,
                      cookieAttributes.expiryTime,
                      currentTimeInUsec,
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -0,0 +1,16 @@
+const GOOD_COOKIE = "GoodCookie=OMNOMNOM";
+
+function run_test() {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js",
+                             null, null);
+
+  var cookieService = Cc["@mozilla.org/cookieService;1"]
+                        .getService(Ci.nsICookieService);
+  cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie1=\x01", null, null);
+  cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie2=\v", null, null);
+  cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, GOOD_COOKIE, null, null);
+
+  var storedCookie = cookieService.getCookieString(cookieURI, null);
+  do_check_eq(storedCookie, GOOD_COOKIE);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -324,8 +324,9 @@ skip-if = os == "android"
 [test_packaged_app_service.js]
 [test_packaged_app_verifier.js]
 [test_suspend_channel_before_connect.js]
 [test_inhibit_caching.js]
 [test_dns_disable_ipv4.js]
 [test_dns_disable_ipv6.js]
 [test_packaged_app_service_paths.js]
 [test_bug1195415.js]
+[test_cookie_blacklist.js]
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -2525,16 +2525,19 @@ def run_test_harness(options):
         options.runByDir = False
 
     if mozinfo.isMac and mozinfo.info['debug']:
         options.runByDir = False
 
     if runner.getTestFlavor(options) == 'browser-chrome':
         options.runByDir = True
 
+    if runner.getTestFlavor(options) == 'chrome' and (not mozinfo.info['debug']):
+        options.runByDir = True
+
     if mozinfo.info.get('buildapp') == 'mulet':
         options.runByDir = False
 
     result = runner.runTests(options)
 
     # don't dump failures if running from automation as treeherder already displays them
     if build_obj:
         if runner.message_logger.errors:
--- a/testing/mozharness/configs/merge_day/aurora_to_beta.py
+++ b/testing/mozharness/configs/merge_day/aurora_to_beta.py
@@ -1,15 +1,14 @@
 config = {
     "log_name": "aurora_to_beta",
     "version_files": [
         "browser/config/version.txt",
         "browser/config/version_display.txt",
         "config/milestone.txt",
-        "mobile/android/confvars.sh",  # TODO: remove this line before gecko 43 merge
         "b2g/confvars.sh",
     ],
     "replacements": [
         # File, from, to
         ("{}/{}".format(d, f),
         "ac_add_options --with-branding=mobile/android/branding/aurora",
         "ac_add_options --with-branding=mobile/android/branding/beta")
         for d in ["mobile/android/config/mozconfigs/android-api-11/",
@@ -21,17 +20,26 @@ config = {
         ("{}/{}".format(d, f),
         "ac_add_options --with-branding=browser/branding/aurora",
         "ac_add_options --with-branding=browser/branding/nightly")
         for d in ["browser/config/mozconfigs/linux32",
                   "browser/config/mozconfigs/linux64",
                   "browser/config/mozconfigs/win32",
                   "browser/config/mozconfigs/win64",
                   "browser/config/mozconfigs/macosx64"]
-        for f in ["debug", "nightly", "l10n-mozconfig"]
+        for f in ["debug", "nightly"]
+    ] + [
+        # File, from, to
+        (f, "ac_add_options --with-branding=browser/branding/aurora", "")
+        for f in ["browser/config/mozconfigs/linux32/l10n-mozconfig",
+                  "browser/config/mozconfigs/linux64/l10n-mozconfig",
+                  "browser/config/mozconfigs/win32/l10n-mozconfig",
+                  "browser/config/mozconfigs/win64/l10n-mozconfig",
+                  "browser/config/mozconfigs/macosx-universal/l10n-mozconfig",
+                  "browser/config/mozconfigs/macosx64/l10n-mozconfig"]
     ] + [
         ("browser/config/mozconfigs/macosx-universal/nightly",
          "ac_add_options --with-branding=browser/branding/aurora",
          "ac_add_options --with-branding=browser/branding/nightly"),
         ("browser/confvars.sh",
          "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-aurora",
          "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-beta,firefox-mozilla-release"),
         ("browser/confvars.sh",
--- a/testing/mozharness/mozharness/mozilla/building/buildb2gbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildb2gbase.py
@@ -259,16 +259,28 @@ class B2GBuildBaseScript(BuildbotMixin, 
             self.run_command(['cat', conf_file])
             self.gecko_config = json.load(open(conf_file))
             return self.gecko_config
 
         # The file doesn't exist; let's try loading it remotely
         self.gecko_config = self.query_remote_gecko_config()
         return self.gecko_config
 
+    def symlink_gtk3(self):
+        dirs = self.query_abs_dirs()
+        gtk3_path = os.path.join(dirs['abs_work_dir'], 'gtk3')
+        gtk3_symlink_path = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3')
+
+        if os.path.isdir(gtk3_path) and not os.path.isdir(gtk3_symlink_path):
+            cmd = ["ln", "-sf", gtk3_path, gtk3_symlink_path]
+            retval = self.run_command(cmd)
+            if retval != 0:
+                self.error("failed to create symlink")
+                self.return_code = 2
+
     def query_build_env(self):
         """Retrieves the environment for building"""
         dirs = self.query_abs_dirs()
         gecko_config = self.load_gecko_config()
         env = self.query_env()
         for k, v in gecko_config.get('env', {}).items():
             v = v.format(workdir=dirs['abs_work_dir'],
                          srcdir=os.path.abspath(dirs['gecko_src']))
@@ -280,16 +292,23 @@ class B2GBuildBaseScript(BuildbotMixin, 
             v = str(self.config['variant'])
             env['VARIANT'] = v
         if self.config.get('ccache'):
             env['CCACHE_BASEDIR'] = dirs['work_dir']
         # If we get a buildid from buildbot, pass that in as MOZ_BUILD_DATE
         if self.buildbot_config and 'buildid' in self.buildbot_config.get('properties', {}):
             env['MOZ_BUILD_DATE'] = self.buildbot_config['properties']['buildid']
 
+        self.symlink_gtk3()
+        env['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH')
+        if env['LD_LIBRARY_PATH'] is None:
+            env['LD_LIBRARY_PATH'] = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib')
+        else:
+            env['LD_LIBRARY_PATH'] += ':%s' % os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib')
+
         return env
 
     def query_hgweb_url(self, repo, rev, filename=None):
         if filename:
             url = "{baseurl}/raw-file/{rev}/{filename}".format(
                 baseurl=repo,
                 rev=rev,
                 filename=filename)
--- a/testing/mozharness/scripts/b2g_build.py
+++ b/testing/mozharness/scripts/b2g_build.py
@@ -536,45 +536,27 @@ class B2GBuild(LocalesMixin, PurgeMixin,
             else:
                 # Ensure we always utilize the correct number of cores
                 # regardless of the configuration which may be set by repo
                 # config changes...
                 cmd.append('-j{0}'.format(multiprocessing.cpu_count()))
             cmd.append(target)
         return cmd
 
-    def symlink_gtk3(self):
-        dirs = self.query_abs_dirs()
-        gtk3_path = os.path.join(dirs['abs_work_dir'], 'gtk3')
-        gtk3_symlink_path = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3')
-
-        if os.path.isdir(gtk3_path):
-            cmd = ["ln", "-s", gtk3_path, gtk3_symlink_path]
-            retval = self.run_command(cmd)
-            if retval != 0:
-                self.error("failed to create symlink")
-                self.return_code = 2
-
     def build(self):
         dirs = self.query_abs_dirs()
         gecko_config = self.load_gecko_config()
         build_targets = gecko_config.get('build_targets', [])
         build_targets.extend(self.config.get("build_targets", []))
         if not build_targets:
             cmds = [self.generate_build_command()]
         else:
             cmds = [self.generate_build_command(t) for t in build_targets]
 
-        self.symlink_gtk3()
         env = self.query_build_env()
-        env['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH')
-        if env['LD_LIBRARY_PATH'] is None:
-            env['LD_LIBRARY_PATH'] = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib')
-        else:
-            env['LD_LIBRARY_PATH'] += ':%s' % os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib')
         if self.config.get('gaia_languages_file'):
             env['LOCALE_BASEDIR'] = dirs['gaia_l10n_base_dir']
             env['LOCALES_FILE'] = os.path.join(dirs['abs_work_dir'], 'gaia', self.config['gaia_languages_file'])
         if self.config.get('locales_file'):
             env['L10NBASEDIR'] = dirs['abs_l10n_dir']
             env['MOZ_CHROME_MULTILOCALE'] = " ".join(self.query_locales())
             if 'PATH' not in env:
                 env['PATH'] = os.environ.get('PATH')
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -568,17 +568,17 @@ SpecialPowersObserverAPI.prototype = {
                                         .QueryInterface(Ci.nsISubstitutingProtocolHandler);
           let resURI = Services.io.newURI(target, null, null);
           let uri = Services.io.newURI(resourceHandler.resolveURI(resURI), null, null);
           extension = new Extension({
             id,
             resourceURI: uri
           });
         } else {
-          extension = Extension.generate(ext);
+          extension = Extension.generate(id, ext);
         }
 
         let resultListener = (...args) => {
           this._sendReply(aMessage, "SPExtensionMessage", {id, type: "testResult", args});
         };
 
         let messageListener = (...args) => {
           args.shift();
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -2055,16 +2055,18 @@ SpecialPowersAPI.prototype = {
     });
     let unloadPromise = new Promise(resolve => { resolveUnload = resolve; });
 
     handler = Cu.waiveXrays(handler);
     ext = Cu.waiveXrays(ext);
 
     let sp = this;
     let extension = {
+      id,
+
       startup() {
         sp._sendAsyncMessage("SPStartupExtension", {id});
         return startupPromise;
       },
 
       unload() {
         sp._sendAsyncMessage("SPUnloadExtension", {id});
         return unloadPromise;
@@ -2092,13 +2094,17 @@ SpecialPowersAPI.prototype = {
           dump(`Unexpected: ${msg.data.type}\n`);
         }
       }
     };
 
     this._addMessageListener("SPExtensionMessage", listener);
     return extension;
   },
+
+  invalidateExtensionStorageCache: function() {
+    this.notifyObserversInParentProcess(null, "extension-invalidate-storage-cache", "");
+  },
 };
 
 this.SpecialPowersAPI = SpecialPowersAPI;
 this.bindDOMWindowUtils = bindDOMWindowUtils;
 this.getRawComponents = getRawComponents;
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -9,38 +9,30 @@
     "suites": {
         "chromez": {
             "tests": ["tresize", "tcanvasmark"]
         },
         "chromez-e10s": {
             "tests": ["tresize", "tcanvasmark"],
             "talos_options": ["--e10s"]
         },
-        "chromez-osx-e10s": {
-            "tests": ["tresize", "tcanvasmark"],
-            "talos_options": ["--e10s"]
-        },
         "dromaeojs": {
             "tests": ["dromaeo_css", "kraken", "v8_7"]
         },
         "dromaeojs-e10s": {
             "tests": ["dromaeo_css", "kraken", "v8_7"],
             "talos_options": ["--e10s"]
         },
         "other": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"]
         },
         "other-e10s": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"],
             "talos_options": ["--e10s"]
         },
-        "other-osx-e10s": {
-            "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"],
-            "talos_options": ["--e10s"]
-        },
         "other_nol64": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"]
         },
         "other-e10s_nol64": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"],
             "talos_options": ["--e10s"]
         },
         "other_l64": {
@@ -66,51 +58,42 @@
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest",
             "plugins": {
                 "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip",
                 "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
             }
         },
-        "g1-osx-e10s": {
-            "tests": ["tp5o_scroll", "glterrain"],
-            "talos_options": ["--e10s"]
-        },
         "g2": {
             "tests": ["damp", "tps"],
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest"
         },
         "g2-e10s": {
             "tests": ["damp", "tps"],
             "talos_options": ["--e10s"],
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest"
         },
-        "g2-osx-e10s": {
-            "tests": ["damp", "tps"],
-            "talos_options": ["--e10s"],
-            "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
-            "pagesets_parent_dir_path": "talos/page_load_test/",
-            "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest"
+        "g3": {
+            "tests": ["dromaeo_dom"]
+        },
+        "g3-e10s": {
+            "tests": ["dromaeo_dom"]
         },
         "svgr": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"]
         },
         "svgr-e10s": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"],
             "talos_options": ["--e10s"]
         },
-        "svgr-osx-e10s": {
-            "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"],
-            "talos_options": ["--e10s"]
-        },
         "tp5o": {
             "tests": ["tp5o"],
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest",
             "plugins": {
                 "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip",
                 "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
@@ -122,27 +105,16 @@
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest",
             "plugins": {
                 "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip",
                 "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
             }
         },
-        "tp5o-osx-e10s": {
-            "tests": ["tp5o"],
-            "talos_options": ["--e10s"],
-            "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
-            "pagesets_parent_dir_path": "talos/page_load_test/",
-            "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest",
-            "plugins": {
-                "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip",
-                "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
-            }
-        },
         "xperf": {
             "tests": ["tp5n"],
             "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip",
             "pagesets_parent_dir_path": "talos/page_load_test/",
             "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5n.manifest",
             "plugins": {
                 "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip",
                 "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -282,17 +282,16 @@ xpcshell-tests:
 	  -I$(DEPTH)/build \
 	  -I$(topsrcdir)/build \
 	  -I$(DEPTH)/_tests/mozbase/mozinfo \
 	  $(topsrcdir)/testing/xpcshell/runxpcshelltests.py \
 	  --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \
 	  --build-info-json=$(DEPTH)/mozinfo.json \
 	  --no-logfiles \
 	  --test-plugin-path='$(DIST)/plugins' \
-	  --tests-root-dir=$(abspath _tests/xpcshell) \
 	  --testing-modules-dir=$(abspath _tests/modules) \
           $(SYMBOLS_PATH) \
 	  $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \
 	  $(xpcshell_path)
 
 B2G_XPCSHELL = \
 	rm -f ./@.log && \
 	$(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register-new-script.https.html.ini
+++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register-new-script.https.html.ini
@@ -1,27 +1,8 @@
 [unregister-then-register-new-script.https.html]
-  type: testharness
-  disabled:
-    if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1205675
-  expected:
-    if debug and not e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): CRASH
-    if not debug and e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): OK
-    if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): OK
-    if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): OK
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): OK
-    if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): CRASH
-    if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): OK
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): OK
-    if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): OK
-    if not debug and not e10s and (os == "win") and (version == "6.2.9200") and (processor == "x86_64") and (bits == 64): OK
-    if not debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): OK
-    if debug and not e10s and (os == "win") and (version == "6.2.9200") and (processor == "x86_64") and (bits == 64): CRASH
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): CRASH
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH
-    TIMEOUT
+
   [Registering a new script URL while an unregistered registration is in use]
     expected: FAIL
 
   [Registering a new script URL that fails to install does not resurrect an unregistered registration]
     expected: FAIL
 
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -273,17 +273,19 @@ var GlobalManager = {
     let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIWebNavigation)
                                 .QueryInterface(Ci.nsIDocShellTreeItem)
                                 .sameTypeRootTreeItem
                                 .QueryInterface(Ci.nsIDocShell);
 
     if (this.docShells.has(docShell)) {
       let {extension, context} = this.docShells.get(docShell);
-      inject(extension, context);
+      if (context) {
+        inject(extension, context);
+      }
       return;
     }
 
     // We don't inject into sub-frames of a UI page.
     if (contentWindow != contentWindow.top) {
       return;
     }
 
@@ -362,17 +364,17 @@ this.Extension = function(addonData)
  *     If a manifest file is provided here, it takes precedence over
  *     a generated one. Always use "/" as a directory separator.
  *     Directories should appear here only implicitly (as a prefix
  *     to file names)
  *
  * To make things easier, the value of "background" and "files"[] can
  * be a function, which is converted to source that is run.
  */
-this.Extension.generate = function(data)
+this.Extension.generate = function(id, data)
 {
   let manifest = data.manifest;
   if (!manifest) {
     manifest = {};
   }
 
   let files = data.files;
   if (!files) {
@@ -387,19 +389,17 @@ this.Extension.generate = function(data)
     } else {
       if (!(keys[0] in obj)) {
         obj[keys[0]] = {};
       }
       provide(obj[keys[0]], keys.slice(1), value, override);
     }
   }
 
-  let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
-  let uuid = uuidGenerator.generateUUID().number;
-  provide(manifest, ["applications", "gecko", "id"], uuid);
+  provide(manifest, ["applications", "gecko", "id"], id);
 
   provide(manifest, ["name"], "Generated extension");
   provide(manifest, ["manifest_version"], 2);
   provide(manifest, ["version"], "1.0");
 
   if (data.background) {
     let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
     let bgScript = uuidGenerator.generateUUID().number + ".js";
@@ -453,17 +453,17 @@ this.Extension.generate = function(data)
 
   flushJarCache(file);
   Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path});
 
   let fileURI = Services.io.newFileURI(file);
   let jarURI = Services.io.newURI("jar:" + fileURI.spec + "!/", null, null);
 
   return new Extension({
-    id: uuid,
+    id,
     resourceURI: jarURI,
     cleanupFile: file
   });
 }
 
 Extension.prototype = {
   on(hook, f) {
     return this.emitter.on(hook, f);
@@ -633,16 +633,30 @@ Extension.prototype = {
 
     let locale = Locale.findClosestLocale(locales);
     if (locale) {
       return this.readLocaleFile(locale.name).catch(() => {});
     }
     return {};
   },
 
+  broadcast(msg, data) {
+    return new Promise(resolve => {
+      let count = Services.ppmm.childCount;
+      Services.ppmm.addMessageListener(msg + "Complete", function listener() {
+        count--;
+        if (count == 0) {
+          Services.ppmm.removeMessageListener(msg + "Complete", listener);
+          resolve();
+        }
+      });
+      Services.ppmm.broadcastAsyncMessage(msg, data);
+    });
+  },
+
   runManifest(manifest) {
     let permissions = manifest.permissions || [];
     let webAccessibleResources = manifest.web_accessible_resources || [];
 
     let whitelist = [];
     for (let perm of permissions) {
       if (perm.match(/:\/\//)) {
         whitelist.push(perm);
@@ -663,17 +677,18 @@ Extension.prototype = {
     }
 
     let data = Services.ppmm.initialProcessData;
     if (!data["Extension:Extensions"]) {
       data["Extension:Extensions"] = [];
     }
     let serial = this.serialize();
     data["Extension:Extensions"].push(serial);
-    Services.ppmm.broadcastAsyncMessage("Extension:Startup", serial);
+
+    return this.broadcast("Extension:Startup", serial);
   },
 
   callOnClose(obj) {
     this.onShutdown.add(obj);
   },
 
   forgetOnClose(obj) {
     this.onShutdown.delete(obj);
@@ -693,17 +708,17 @@ Extension.prototype = {
 
       GlobalManager.init(this);
 
       this.manifest = manifest;
       this.localeMessages = messages;
 
       Management.emit("startup", this);
 
-      this.runManifest(manifest);
+      return this.runManifest(manifest);
     }).catch(e => {
       dump(`Extension error: ${e} ${e.fileName}:${e.lineNumber}\n`);
       Cu.reportError(e);
       throw e;
     });
   },
 
   cleanupGeneratedFile() {
@@ -711,29 +726,23 @@ Extension.prototype = {
       return;
     }
 
     let file = this.cleanupFile;
     this.cleanupFile = null;
 
     Services.obs.removeObserver(this, "xpcom-shutdown");
 
-    let count = Services.ppmm.childCount;
-
-    Services.ppmm.addMessageListener("Extension:FlushJarCacheComplete", function listener() {
-      count--;
-      if (count == 0) {
-        // We can't delete this file until everyone using it has
-        // closed it (because Windows is dumb). So we wait for all the
-        // child processes (including the parent) to flush their JAR
-        // caches. These caches may keep the file open.
-        file.remove(false);
-      }
+    this.broadcast("Extension:FlushJarCache", {path: file.path}).then(() => {
+      // We can't delete this file until everyone using it has
+      // closed it (because Windows is dumb). So we wait for all the
+      // child processes (including the parent) to flush their JAR
+      // caches. These caches may keep the file open.
+      file.remove(false);
     });
-    Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path});
   },
 
   shutdown() {
     this.hasShutdown = true;
     if (!this.manifest) {
       return;
     }
 
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -25,17 +25,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  runSafeWithoutClone,
+  runSafeSyncWithoutClone,
   MessageBroker,
   Messenger,
   ignoreEvent,
   injectAPI,
   flushJarCache,
 } = ExtensionUtils;
 
 function isWhenBeforeOrSame(when1, when2)
@@ -46,26 +46,30 @@ function isWhenBeforeOrSame(when1, when2
   return table[when1] <= table[when2];
 }
 
 // This is the fairly simple API that we inject into content
 // scripts.
 var api = context => { return {
   runtime: {
     connect: function(extensionId, connectInfo) {
+      if (!connectInfo) {
+        connectInfo = extensionId;
+        extensionId = null;
+      }
       let name = connectInfo && connectInfo.name || "";
       let recipient = extensionId ? {extensionId} : {extensionId: context.extensionId};
       return context.messenger.connect(context.messageManager, name, recipient);
     },
 
     getManifest: function(context) {
       return context.extension.getManifest();
     },
 
-    getURL: function(path) {
+    getURL: function(url) {
       return context.extension.baseURI.resolve(url);
     },
 
     onConnect: context.messenger.onConnect("runtime.onConnect"),
 
     onMessage: context.messenger.onMessage("runtime.onMessage"),
 
     sendMessage: function(...args) {
@@ -79,17 +83,17 @@ var api = context => { return {
       }
 
       let recipient = extensionId ? {extensionId} : {extensionId: context.extensionId};
       context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
     },
   },
 
   extension: {
-    getURL: function(path) {
+    getURL: function(url) {
       return context.extension.baseURI.resolve(url);
     },
 
     inIncognitoContext: PrivateBrowsingUtils.isContentWindowPrivate(context.contentWindow),
   },
 }};
 
 // Represents a content script.
@@ -132,22 +136,22 @@ Script.prototype = {
     }
 
     if (shouldRun("document_start")) {
       let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
         getInterface(Ci.nsIDOMWindowUtils);
 
       for (let url of this.css) {
         url = extension.baseURI.resolve(url);
-        runSafeWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET);
+        runSafeSyncWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET);
       }
 
       if (this.options.cssCode) {
         let url = "data:text/css;charset=utf-8," + encodeURIComponent(this.options.cssCode);
-        runSafeWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET);
+        runSafeSyncWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET);
       }
     }
 
     let scheduled = this.run_at || "document_idle";
     if (shouldRun(scheduled)) {
       for (let url of this.js) {
         // On gonk we need to load the resources asynchronously because the
         // app: channels only support asyncOpen. This is safe only in the
@@ -157,17 +161,17 @@ Script.prototype = {
         }
         url = extension.baseURI.resolve(url);
 
         let options = {
           target: sandbox,
           charset: "UTF-8",
           async: AppConstants.platform == "gonk"
         }
-        Services.scriptloader.loadSubScriptWithOptions(url, options);
+        runSafeSyncWithoutClone(Services.scriptloader.loadSubScriptWithOptions, url, options);
       }
 
       if (this.options.jsCode) {
         Cu.evalInSandbox(this.options.jsCode, sandbox, "latest");
       }
     }
   },
 };
@@ -189,16 +193,18 @@ function getWindowMessageManager(content
 // Cu.Sandbox to run the code. There is a separate scope for each
 // frame.
 function ExtensionContext(extensionId, contentWindow)
 {
   this.extension = ExtensionManager.get(extensionId);
   this.extensionId = extensionId;
   this.contentWindow = contentWindow;
 
+  this.onClose = new Set();
+
   let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
   let outerWindowId = utils.outerWindowID;
   let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId;
   this.frameId = frameId;
 
   let mm = getWindowMessageManager(contentWindow);
   this.messageManager = mm;
@@ -223,18 +229,16 @@ function ExtensionContext(extensionId, c
                                  {id: extensionId, frameId}, delegate);
 
   let chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"});
 
   // Sandboxes don't get Xrays for some weird compatibility
   // reason. However, we waive here anyway in case that changes.
   Cu.waiveXrays(this.sandbox).chrome = Cu.waiveXrays(this.sandbox).browser;
   injectAPI(api(this), chromeObj);
-
-  this.onClose = new Set();
 }
 
 ExtensionContext.prototype = {
   get cloneScope() {
     return this.sandbox;
   },
 
   execute(script, shouldRun) {
@@ -478,16 +482,17 @@ var ExtensionManager = {
 
   receiveMessage({name, data}) {
     let extension;
     switch (name) {
       case "Extension:Startup": {
         extension = new BrowserExtensionContent(data);
         this.extensions.set(data.id, extension);
         DocumentManager.startupExtension(data.id);
+        Services.cpmm.sendAsyncMessage("Extension:StartupComplete");
         break;
       }
 
       case "Extension:Shutdown": {
         extension = this.extensions.get(data.id);
         extension.shutdown();
         DocumentManager.shutdownExtension(data.id);
         this.extensions.delete(data.id);
--- a/toolkit/components/extensions/ExtensionStorage.jsm
+++ b/toolkit/components/extensions/ExtensionStorage.jsm
@@ -90,17 +90,23 @@ this.ExtensionStorage = {
 
       return this.write(extensionId);
     });
   },
 
   remove(extensionId, items) {
     return this.read(extensionId).then(extData => {
       let changes = {};
-      for (let prop in items) {
+      if (Array.isArray(items)) {
+        for (let prop of items) {
+          changes[prop] = {oldValue: extData[prop]};
+          delete extData[prop];
+        }
+      } else {
+        let prop = items;
         changes[prop] = {oldValue: extData[prop]};
         delete extData[prop];
       }
 
       let listeners = this.listeners.get(extensionId);
       if (listeners) {
         for (let listener of listeners) {
           listener(changes);
@@ -111,29 +117,34 @@ this.ExtensionStorage = {
     });
   },
 
   get(extensionId, keys) {
     return this.read(extensionId).then(extData => {
       let result = {};
       if (keys === null) {
         Object.assign(result, extData);
-      } else if (typeof(keys) == "object") {
+      } else if (typeof(keys) == "object" && !Array.isArray(keys)) {
         for (let prop in keys) {
           if (prop in extData) {
             result[prop] = extData[prop];
           } else {
             result[prop] = keys[prop];
           }
         }
       } else if (typeof(keys) == "string") {
-        result[prop] = extData[prop] || undefined;
+        let prop = keys;
+        if (prop in extData) {
+          result[prop] = extData[prop];
+        }
       } else {
         for (let prop of keys) {
-          result[prop] = extData[prop] || undefined;
+          if (prop in extData) {
+            result[prop] = extData[prop];
+          }
         }
       }
 
       return result;
     });
   },
 
   addOnChangedListener(extensionId, listener) {
@@ -141,9 +152,25 @@ this.ExtensionStorage = {
     listeners.add(listener);
     this.listeners.set(extensionId, listeners);
   },
 
   removeOnChangedListener(extensionId, listener) {
     let listeners = this.listeners.get(extensionId);
     listeners.delete(listener);
   },
+
+  init() {
+    Services.obs.addObserver(this, "extension-invalidate-storage-cache", false);
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+  },
+
+  observe(subject, topic, data) {
+    if (topic == "xpcom-shutdown") {
+      Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+    } else if (topic == "extension-invalidate-storage-cache") {
+      this.cache.clear();
+    }
+  },
 };
+
+ExtensionStorage.init();
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -10,26 +10,51 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 // Run a function and report exceptions.
-function runSafeWithoutClone(f, ...args)
+function runSafeSyncWithoutClone(f, ...args)
 {
   try {
     return f(...args);
   } catch (e) {
-    dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n${e.stack}\n${Error().stack}`);
+    dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${e.stack}Current stack\n${Error().stack}]]\n`);
     Cu.reportError(e);
   }
 }
 
+// Run a function and report exceptions.
+function runSafeWithoutClone(f, ...args)
+{
+  if (typeof(f) != "function") {
+    dump(`Extension error: expected function\n${Error().stack}`);
+    return;
+  }
+
+  Services.tm.currentThread.dispatch(function() {
+    runSafeSyncWithoutClone(f, ...args);
+  }, Ci.nsIEventTarget.DISPATCH_NORMAL);
+}
+
+// Run a function, cloning arguments into context.cloneScope, and
+// report exceptions. |f| is expected to be in context.cloneScope.
+function runSafeSync(context, f, ...args)
+{
+  try {
+    args = Cu.cloneInto(args, context.cloneScope);
+  } catch (e) {
+    dump(`runSafe failure\n${context.cloneScope}\n${Error().stack}`);
+  }
+  return runSafeSyncWithoutClone(f, ...args);
+}
+
 // Run a function, cloning arguments into context.cloneScope, and
 // report exceptions. |f| is expected to be in context.cloneScope.
 function runSafe(context, f, ...args)
 {
   try {
     args = Cu.cloneInto(args, context.cloneScope);
   } catch (e) {
     dump(`runSafe failure\n${context.cloneScope}\n${Error().stack}`);
@@ -91,16 +116,21 @@ function EventManager(context, name, reg
   this.register = register;
   this.unregister = null;
   this.callbacks = new Set();
   this.registered = false;
 }
 
 EventManager.prototype = {
   addListener(callback) {
+    if (typeof(callback) != "function") {
+      dump(`Expected function\n${Error().stack}`);
+      return;
+    }
+
     if (!this.registered) {
       this.context.callOnClose(this);
 
       let fireFunc = this.fire.bind(this);
       let fireWithoutClone = this.fireWithoutClone.bind(this);
       fireFunc.withoutClone = fireWithoutClone;
       this.unregister = this.register(fireFunc);
     }
@@ -127,17 +157,17 @@ EventManager.prototype = {
   fire(...args) {
     for (let callback of this.callbacks) {
       runSafe(this.context, callback, ...args);
     }
   },
 
   fireWithoutClone(...args) {
     for (let callback of this.callbacks) {
-      runSafeWithoutClone(callback, ...args);
+      runSafeSyncWithoutClone(callback, ...args);
     }
   },
 
   close() {
     this.unregister();
   },
 
   api() {
@@ -335,16 +365,19 @@ function Port(context, messageManager, n
   this.context = context;
   this.messageManager = messageManager;
   this.name = name;
   this.id = id;
   this.listenerName = `Extension:Port-${this.id}`;
   this.disconnectName = `Extension:Disconnect-${this.id}`;
   this.sender = sender;
   this.disconnected = false;
+
+  this.messageManager.addMessageListener(this.disconnectName, this, true);
+  this.disconnectListeners = new Set();
 }
 
 Port.prototype = {
   api() {
     let portObj = Cu.createObjectIn(this.context.cloneScope);
 
     // We want a close() notification when the window is destroyed.
     this.context.callOnClose(this);
@@ -362,19 +395,19 @@ Port.prototype = {
       },
       onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
         let listener = () => {
           if (!this.disconnected) {
             fire();
           }
         };
 
-        this.messageManager.addMessageListener(this.disconnectName, listener, true);
+        this.disconnectListeners.add(listener);
         return () => {
-          this.messageManager.removeMessageListener(this.disconnectName, listener);
+          this.disconnectListeners.delete(listener);
         };
       }).api(),
       onMessage: new EventManager(this.context, "Port.onMessage", fire => {
         let listener = ({data}) => {
           if (!this.disconnected) {
             fire(data);
           }
         };
@@ -389,19 +422,41 @@ Port.prototype = {
     if (this.sender) {
       publicAPI.sender = this.sender;
     }
 
     injectAPI(publicAPI, portObj);
     return portObj;
   },
 
-  disconnect() {
+  handleDisconnection() {
+    this.messageManager.removeMessageListener(this.disconnectName, this);
     this.context.forgetOnClose(this);
-    this.disconnect = true;
+    this.disconnected = true;
+  },
+
+  receiveMessage(msg) {
+    if (msg.name == this.disconnectName) {
+      if (this.disconnected) {
+        return;
+      }
+
+      for (let listener of this.disconnectListeners) {
+        listener();
+      }
+
+      this.handleDisconnection();
+    }
+  },
+
+  disconnect() {
+    if (this.disconnected) {
+      throw "Attempt to disconnect() a disconnected port";
+    }
+    this.handleDisconnection();
     this.messageManager.sendAsyncMessage(this.disconnectName);
   },
 
   close() {
     this.disconnect();
   },
 };
 
@@ -457,17 +512,17 @@ Messenger.prototype = {
     };
     if (responseCallback) {
       messageManager.addMessageListener(replyName, listener);
       this.context.callOnClose(onClose);
     }
   },
 
   onMessage(name) {
-    return new EventManager(this.context, name, fire => {
+    return new SingletonEventManager(this.context, name, callback => {
       let listener = (type, target, message, sender, recipient) => {
         message = Cu.cloneInto(message, this.context.cloneScope);
         if (this.delegate) {
           this.delegate.getSender(this.context, target, sender);
         }
         sender = Cu.cloneInto(sender, this.context.cloneScope);
 
         let mm = getMessageManager(target);
@@ -478,22 +533,22 @@ Messenger.prototype = {
           if (!valid) {
             return;
           }
           sent = true;
           mm.sendAsyncMessage(replyName, {data, gotData: true});
         };
         sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
 
-        let result = fire.withoutClone(message, sender, sendResponse);
+        let result = runSafeSyncWithoutClone(callback, message, sender, sendResponse);
         if (result !== true) {
           valid = false;
-        }
-        if (!sent) {
-          mm.sendAsyncMessage(replyName, {gotData: false});
+          if (!sent) {
+            mm.sendAsyncMessage(replyName, {gotData: false});
+          }
         }
       };
 
       this.broker.addListener("message", listener, this.filter);
       return () => {
         this.broker.removeListener("message", listener);
       };
     }).api();
@@ -529,17 +584,19 @@ Messenger.prototype = {
 
 function flushJarCache(jarFile)
 {
   Services.obs.notifyObservers(jarFile, "flush-cache-entry", null);
 }
 
 this.ExtensionUtils = {
   runSafeWithoutClone,
+  runSafeSyncWithoutClone,
   runSafe,
+  runSafeSync,
   DefaultWeakMap,
   EventManager,
   SingletonEventManager,
   ignoreEvent,
   injectAPI,
   MessageBroker,
   Messenger,
   flushJarCache,
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -4,17 +4,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Alarm]]
 var alarmsMap = new WeakMap();
 
-// WeakMap[Extension -> callback]
+// WeakMap[Extension -> Set[callback]]
 var alarmCallbacksMap = new WeakMap();
 
 // Manages an alarm created by the extension (alarms API).
 function Alarm(extension, name, alarmInfo)
 {
   this.extension = extension;
   this.name = name;
   this.when = alarmInfo.when;
@@ -41,18 +41,18 @@ function Alarm(extension, name, alarmInf
 Alarm.prototype = {
   clear() {
     this.timer.cancel();
     alarmsMap.get(this.extension).delete(this);
     this.canceled = true;
   },
 
   observe(subject, topic, data) {
-    if (alarmCallbacksMap.has(this.extension)) {
-      alarmCallbacksMap.get(this.extension)(this);
+    for (let callback in alarmCallbacksMap.get(this.extension)) {
+      callback(this);
     }
     if (this.canceled) {
       return;
     }
 
     if (!this.periodInMinutes) {
       this.clear();
       return;
@@ -69,23 +69,25 @@ Alarm.prototype = {
       scheduledTime: this.scheduledTime,
       periodInMinutes: this.periodInMinutes,
     };
   },
 };
 
 extensions.on("startup", (type, extension) => {
   alarmsMap.set(extension, new Set());
+  alarmCallbacksMap.set(extension, new Set());
 });
 
 extensions.on("shutdown", (type, extension) => {
   for (let alarm of alarmsMap.get(extension)) {
     alarm.clear();
   }
   alarmsMap.delete(extension);
+  alarmCallbacksMap.delete(extension);
 });
 
 extensions.registerAPI((extension, context) => {
   return {
     alarms: {
       create: function(...args) {
         let name = "", alarmInfo;
         if (args.length == 1) {
@@ -155,16 +157,16 @@ extensions.registerAPI((extension, conte
         }
       },
 
       onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
         let callback = alarm => {
           fire(alarm.data);
         };
 
-        alarmCallbacksMap.set(extension, callback);
+        alarmCallbacksMap.get(extension).add(callback);
         return () => {
-          alarmCallbacksMap.delete(extension);
+          alarmCallbacksMap.get(extension).delete(callback);
         };
       }).api(),
     },
   };
 });
--- a/toolkit/components/extensions/ext-extension.js
+++ b/toolkit/components/extensions/ext-extension.js
@@ -1,10 +1,32 @@
 extensions.registerAPI((extension, context) => {
   return {
     extension: {
       getURL: function(url) {
         return extension.baseURI.resolve(url);
       },
+
+      getViews: function(fetchProperties) {
+        let result = Cu.cloneInto([], context.cloneScope);
+
+        for (let view of extension.views) {
+          if (fetchProperties && "type" in fetchProperties) {
+            if (view.type != fetchProperties.type) {
+              continue;
+            }
+          }
+
+          if (fetchProperties && "windowId" in fetchProperties) {
+            if (view.windowId != fetchProperties.windowId) {
+              continue;
+            }
+          }
+
+          result.push(view.contentWindow);
+        }
+
+        return result;
+      },
     },
   };
 });
 
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -4,17 +4,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Notification]]
 var notificationsMap = new WeakMap();
 
-// WeakMap[Extension -> callback]
+// WeakMap[Extension -> Set[callback]]
 var notificationCallbacksMap = new WeakMap();
 
 // Manages a notification popup (notifications API) created by the extension.
 function Notification(extension, id, options)
 {
   this.extension = extension;
   this.id = id;
   this.options = options;
@@ -49,33 +49,35 @@ Notification.prototype = {
     notificationsMap.get(this.extension).delete(this);
   },
 
   observe(subject, topic, data) {
     if (topic != "alertfinished") {
       return;
     }
 
-    if (notificationCallbacksMap.has(this.extension)) {
-      notificationCallbackMap.get(this.extension)(this);
+    for (let callback in notificationCallbacksMap.get(this.extension)) {
+      callback(this);
     }
 
     notificationsMap.get(this.extension).delete(this);
   },
 };
 
 extensions.on("startup", (type, extension) => {
   notificationsMap.set(extension, new Set());
+  notificationCallbacksMap.set(extension, new Set());
 });
 
 extensions.on("shutdown", (type, extension) => {
   for (let notification of notificationsMap.get(extension)) {
     notification.clear();
   }
   notificationsMap.delete(extension);
+  notificationCallbacksMap.delete(extension);
 });
 
 var nextId = 0;
 
 extensions.registerPrivilegedAPI("notifications", (extension, context) => {
   return {
     notifications: {
       create: function(...args) {
@@ -123,19 +125,19 @@ extensions.registerPrivilegedAPI("notifi
       },
 
       onClosed: new EventManager(context, "notifications.onClosed", fire => {
         let listener = notification => {
           // FIXME: Support the byUser argument.
           fire(notification.id, true);
         };
 
-        notificationCallbackMap.set(extension, listener);
+        notificationCallbacksMap.get(extension).add(listener);
         return () => {
-          notificationCallbackMap.delete(extension);
+          notificationCallbacksMap.get(extension).delete(listener);
         };
       }).api(),
 
       // FIXME
       onButtonClicked: ignoreEvent(),
       onClicked: ignoreEvent(),
     },
   };
--- a/toolkit/components/extensions/ext-test.js
+++ b/toolkit/components/extensions/ext-test.js
@@ -1,19 +1,28 @@
 Components.utils.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
 } = ExtensionUtils;
 
+// WeakMap[Extension -> Set(callback)]
 var messageHandlers = new WeakMap();
 
+extensions.on("startup", (type, extension) => {
+  messageHandlers.set(extension, new Set());
+});
+
+extensions.on("shutdown", (type, extension) => {
+  messageHandlers.delete(extension);
+});
+
 extensions.on("test-message", (type, extension, ...args) => {
-  let fire = messageHandlers.get(extension);
-  if (fire) {
-    fire(...args);
+  let handlers = messageHandlers.get(extension);
+  for (let handler of handlers) {
+    handler(...args);
   }
 });
 
 extensions.registerAPI((extension, context) => {
   return {
     test: {
       sendMessage: function(...args) {
         extension.emit("test-message", ...args);
@@ -47,16 +56,18 @@ extensions.registerAPI((extension, conte
         extension.emit("test-result", !value ? true : false, msg);
       },
 
       assertEq: function(expected, actual, msg) {
         extension.emit("test-eq", expected === actual, msg, String(expected), String(actual));
       },
 
       onMessage: new EventManager(context, "test.onMessage", fire => {
-        messageHandlers.set(extension, fire);
+        let handlers = messageHandlers.get(extension);
+        handlers.add(fire);
+
         return () => {
-          messageHandlers.delete(extension);
+          handlers.delete(fire);
         };
       }).api(),
     },
   };
 });
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -40,17 +40,17 @@ function WebNavigationEventManager(conte
 
       // Fills in tabId typically.
       let result = {};
       extensions.emit("fill-browser-data", data.browser, data2, result);
       if (result.cancel) {
         return;
       }
 
-      return runSafe(context, callback, data2);
+      runSafe(context, callback, data2);
     };
 
     WebNavigation[eventName].addListener(listener);
     return () => {
       WebNavigation[eventName].removeListener(listener);
     };
   };
 
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -5,17 +5,17 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                   "resource://gre/modules/WebRequest.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   SingletonEventManager,
-  runSafe,
+  runSafeSync,
 } = ExtensionUtils;
 
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 function WebRequestEventManager(context, eventName)
 {
   let name = `webRequest.${eventName}`;
@@ -48,17 +48,17 @@ function WebRequestEventManager(context,
 
       let optional = ["requestHeaders", "responseHeaders", "statusCode"];
       for (let opt of optional) {
         if (opt in data) {
           data2[opt] = data[opt];
         }
       }
 
-      return runSafe(context, callback, data2);
+      return runSafeSync(context, callback, data2);
     };
 
     let filter2 = {};
     filter2.urls = new MatchPattern(filter.urls);
     if (filter.types) {
       filter2.types = filter.types;
     }
     if (filter.tabId) {
--- a/toolkit/components/extensions/test/extensions/content_script/manifest.json
+++ b/toolkit/components/extensions/test/extensions/content_script/manifest.json
@@ -1,32 +1,32 @@
 {
   "name": "Content script extension test",
   "version": "1.0",
   "manifest_version": 2,
   "description": "",
 
   "content_scripts": [
     {
-      "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"],
+      "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script_start.js"],
       "run_at": "document_start"
     },
     {
-      "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"],
+      "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script_end.js"],
       "run_at": "document_end"
     },
     {
-      "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"],
+      "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script_idle.js"],
       "run_at": "document_idle"
     },
     {
-      "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"],
+      "matches": ["http://mochi.test/*/file_sample.html"],
       "js": ["content_script.js"],
       "run_at": "document_idle"
     }
   ],
 
   "background": {
     "scripts": ["background.js"]
   }
rename from toolkit/components/extensions/test/mochitest/file_contentscript_page1.html
rename to toolkit/components/extensions/test/mochitest/file_sample.html
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/head.js
@@ -0,0 +1,8 @@
+function waitForLoad(win) {
+  return new Promise(resolve => {
+    win.addEventListener("load", function listener() {
+      win.removeEventListener("load", listener, true);
+      resolve();
+    }, true);
+  });
+}
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -1,22 +1,29 @@
 [DEFAULT]
-skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac'
+skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || os == 'mac' || asan
 support-files =
+  head.js
   file_WebRequest_page1.html
   file_WebRequest_page2.html
   file_image_good.png
   file_image_bad.png
   file_image_redirect.png
   file_style_good.css
   file_style_bad.css
   file_style_redirect.css
   file_script_good.js
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
-  file_contentscript_page1.html
+  file_sample.html
 
-[test_simple_extensions.html]
-[test_extension_contentscript.html]
-[test_extension_webrequest.html]
-[test_generate_extension.html]
-[test_sandbox_var.html]
+[test_ext_simple.html]
+[test_ext_geturl.html]
+[test_ext_contentscript.html]
+[test_ext_webrequest.html]
+[test_ext_generate.html]
+[test_ext_runtime_connect.html]
+[test_ext_runtime_disconnect.html]
+[test_ext_sandbox_var.html]
+[test_ext_sendmessage_reply.html]
+[test_ext_sendmessage_doublereply.html]
+[test_ext_storage.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for content script</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+"use strict";
+
+add_task(function* test_contentscript()
+{
+  let extension = ExtensionTestUtils.loadExtension("content_script");
+  yield extension.startup();
+  info("extension loaded");
+
+  let loadingCount = 0;
+  let interactiveCount = 0;
+  let completeCount = 0;
+  extension.onMessage("script-run-loading", () => { loadingCount++; });
+  extension.onMessage("script-run-interactive", () => { interactiveCount++; });
+
+  let completePromise = new Promise(resolve => {
+    extension.onMessage("script-run-complete", () => { completeCount++; resolve(); });
+  });
+
+  let chromeNamespacePromise = extension.awaitMessage("chrome-namespace-ok");
+
+  let win = window.open("file_sample.html");
+
+  yield Promise.all([waitForLoad(win), completePromise, chromeNamespacePromise]);
+  info("test page loaded");
+
+  win.close();
+
+  is(loadingCount, 1, "document_start script ran exactly once");
+  is(interactiveCount, 1, "document_end script ran exactly once");
+  is(completeCount, 1, "document_idle script ran exactly once");
+
+  yield extension.unload();
+  info("extension unloaded");
+});
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_generate.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for generating WebExtensions</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+
+function backgroundScript() {
+  browser.test.log("running background script");
+
+  browser.test.onMessage.addListener((x, y) => {
+    browser.test.assertEq(x, 10, "x is 10");
+    browser.test.assertEq(y, 20, "y is 20");
+
+    browser.test.notifyPass("background test passed");
+  });
+
+  browser.test.sendMessage("running", 1);
+}
+
+let extensionData = {
+  background: "(" + backgroundScript.toString() + ")()"
+};
+
+add_task(function* test_background() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  info("load complete");
+  yield extension.startup();
+  let x = yield extension.awaitMessage("running");
+  is(x, 1, "got correct value from extension");
+  info("startup complete");
+  extension.sendMessage(10, 20);
+  yield extension.awaitFinish();
+  info("test complete");
+  yield extension.unload();
+  info("extension unloaded successfully");
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_geturl.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+
+function backgroundScript() {
+  browser.runtime.onMessage.addListener(([url1, url2]) => {
+    var url3 = browser.runtime.getURL("test_file.html");
+    var url4 = browser.extension.getURL("test_file.html");
+
+    browser.test.assertTrue(url1 !== undefined, "url1 defined");
+
+    browser.test.assertTrue(url1.startsWith("moz-extension://"), "url1 has correct scheme");
+    browser.test.assertTrue(url1.endsWith("test_file.html"), "url1 has correct leaf name");
+
+    browser.test.assertEq(url1, url2, "url2 matches");
+    browser.test.assertEq(url1, url3, "url3 matches");
+    browser.test.assertEq(url1, url4, "url4 matches");
+
+    browser.test.notifyPass("geturl");
+  });
+}
+
+function contentScript() {
+  var url1 = browser.runtime.getURL("test_file.html");
+  var url2 = browser.extension.getURL("test_file.html");
+  browser.runtime.sendMessage([url1, url2]);
+}
+
+let extensionData = {
+  background: "(" + backgroundScript.toString() + ")()",
+  manifest: {
+    "content_scripts": [{
+      "matches": ["http://mochi.test/*/file_sample.html"],
+      "js": ["content_script.js"],
+      "run_at": "document_start"
+    }]
+  },
+
+  files: {
+    "content_script.js": "(" + contentScript.toString() + ")()",
+  },
+};
+
+add_task(function* test_contentscript() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  info("extension loaded");
+
+  let win = window.open("file_sample.html");
+
+  yield Promise.all([waitForLoad(win), extension.awaitFinish("geturl")]);
+
+  win.close();
+
+  yield extension.unload();
+  info("extension unloaded");
+});
+
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+"use strict";
+
+function backgroundScript() {
+  browser.runtime.onConnect.addListener(port => {
+    browser.test.assertEq(port.name, "ernie", "port name correct");
+    browser.test.assertTrue(port.sender.url.endsWith("file_sample.html"), "URL correct");
+    browser.test.assertTrue(port.sender.tab.url.endsWith("file_sample.html"), "tab URL correct");
+
+    var expected = "message 1";
+    port.onMessage.addListener(msg => {
+      browser.test.assertEq(msg, expected, "message is expected");
+      if (expected == "message 1") {
+        port.postMessage("message 2");
+        expected = "message 3";
+      } else if (expected == "message 3") {
+        expected = "disconnect";
+        browser.test.notifyPass("runtime.connect");
+      }
+    });
+    port.onDisconnect.addListener(() => {
+      browser.test.assertEq(expected, "disconnect", "got disconnection at right time");
+    });
+  });
+}
+
+function contentScript() {
+  var port = browser.runtime.connect({name: "ernie"});
+  port.postMessage("message 1");
+  port.onMessage.addListener(msg => {
+    if (msg == "message 2") {
+      port.postMessage("message 3");
+      port.disconnect();
+    }
+  });
+}
+
+let extensionData = {
+  background: "(" + backgroundScript.toString() + ")()",
+  manifest: {
+    "permissions": ["tabs"],
+    "content_scripts": [{
+      "matches": ["http://mochi.test/*/file_sample.html"],
+      "js": ["content_script.js"],
+      "run_at": "document_start"
+    }]
+  },
+
+  files: {
+    "content_script.js": "(" + contentScript.toString() + ")()",
+  },
+};
+
+add_task(function* test_contentscript() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  info("extension loaded");
+
+  let win = window.open("file_sample.html");
+
+  yield Promise.all([waitForLoad(win), extension.awaitFinish("runtime.connect")]);
+
+  win.close();
+
+  yield extension.unload();
+  info("extension unloaded");
+});
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+"use strict";
+
+function backgroundScript() {
+  browser.runtime.onConnect.addListener(port => {
+    browser.test.assertEq(port.name, "ernie", "port name correct");
+    port.onDisconnect.addListener(() => {
+      browser.test.sendMessage("disconnected");
+    });
+    browser.test.sendMessage("connected");
+  });
+}
+
+function contentScript() {
+  bro