Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 22 Jan 2016 14:05:32 +0100
changeset 281356 4d92c4a644138ae9d0194e8f269cde19e6e89434
parent 281355 3b2ed8b2afdcb24a033e8426fe39e2f3d7a0268d (current diff)
parent 281199 7104d650a97d895cbbc64d53462bf86a04658abe (diff)
child 281357 dc7520a50e855c974f74689493af99b937493d20
push id29935
push userphilringnalda@gmail.com
push dateSun, 24 Jan 2016 02:12:02 +0000
treeherdermozilla-central@a2e81822194a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone46.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
--- a/Makefile.in
+++ b/Makefile.in
@@ -29,21 +29,23 @@ DIST_GARBAGE = config.cache config.log c
    .mozconfig.mk
 
 ifdef JS_STANDALONE
 configure_dir = $(topsrcdir)/js/src
 else
 configure_dir = $(topsrcdir)
 endif
 
+BUILD_BACKEND_FILES := $(addprefix backend.,$(addsuffix Backend,$(BUILD_BACKENDS)))
+
 ifndef TEST_MOZBUILD
 ifndef MOZ_PROFILE_USE
-# We need to explicitly put backend.RecursiveMakeBackend here
-# otherwise the rule in rules.mk doesn't run early enough.
-$(TIERS) binaries:: CLOBBER $(configure_dir)/configure config.status backend.RecursiveMakeBackend
+# We need to explicitly put BUILD_BACKEND_FILES here otherwise the rule in
+# rules.mk doesn't run early enough.
+$(TIERS) binaries:: CLOBBER $(configure_dir)/configure config.status $(BUILD_BACKEND_FILES)
 ifndef JS_STANDALONE
 ifdef COMPILE_ENVIRONMENT
 $(TIERS) binaries:: $(topsrcdir)/js/src/configure js/src/config.status
 endif
 endif
 endif
 endif
 
@@ -79,70 +81,113 @@ config.status js/src/config.status:
 # Regenerate the build backend if it is out of date. We only have this rule in
 # this main make file because having it in rules.mk and applied to partial tree
 # builds resulted in a world of hurt. Gory details are in bug 877308.
 #
 # The mach build driver will ensure the backend is up to date for partial tree
 # builds. This cleanly avoids most of the pain.
 
 ifndef TEST_MOZBUILD
-backend.RecursiveMakeBackend:
+
+.PHONY: backend
+backend: $(BUILD_BACKEND_FILES)
+
+# A traditional rule would look like this:
+#    backend.%:
+#        @echo do stuff
+#
+# But with -j<n>, and multiple items in BUILD_BACKEND_FILES, the command would
+# run multiple times in parallel.
+#
+# "Fortunately", make has some weird semantics for pattern rules: if there are
+# multiple targets in a pattern rule and each of them is matched at most once,
+# the command will only run once. So:
+#     backend%RecursiveMakeBackend backend%FasterMakeBackend:
+#         @echo do stuff
+#     backend: backend.RecursiveMakeBackend backend.FasterMakeBackend
+# would only execute the command once.
+#
+# Credit where due: http://stackoverflow.com/questions/2973445/gnu-makefile-rule-generating-a-few-targets-from-a-single-source-file/3077254#3077254
+$(subst .,%,$(BUILD_BACKEND_FILES)):
 	@echo 'Build configuration changed. Regenerating backend.'
 	$(PYTHON) config.status
 
-Makefile: backend.RecursiveMakeBackend
+Makefile: $(BUILD_BACKEND_FILES)
 	@$(TOUCH) $@
 
-include backend.RecursiveMakeBackend.pp
+define build_backend_rule
+$(1): $$(shell cat $(1).in)
 
-default:: backend.RecursiveMakeBackend
+endef
+$(foreach file,$(BUILD_BACKEND_FILES),$(eval $(call build_backend_rule,$(file))))
+
+default:: $(BUILD_BACKEND_FILES)
 endif
 
 install_manifests := \
-  $(addprefix dist/,bin branding idl include public private sdk xpi-stage) \
+  $(addprefix dist/,branding idl include public private sdk xpi-stage) \
   _tests \
   $(NULL)
+# Skip the dist/bin install manifest when using the hybrid
+# FasterMake/RecursiveMake backend. This is a hack until bug 1241744 moves
+# xpidl handling to FasterMake in that case, mechanically making the dist/bin
+# install manifest non-existent (non-existent manifests being skipped)
+ifeq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
+install_manifests += dist/bin
+endif
 install_manifest_depends = \
   CLOBBER \
   $(configure_dir)/configure \
   config.status \
-  backend.RecursiveMakeBackend \
+  $(BUILD_BACKEND_FILES) \
   $(NULL)
 
 ifndef JS_STANDALONE
 ifdef COMPILE_ENVIRONMENT
 install_manifest_depends += \
   $(topsrcdir)/js/src/configure \
   js/src/config.status \
   $(NULL)
 endif
 endif
 
 .PHONY: install-manifests
 install-manifests: $(addprefix install-,$(install_manifests))
 
+# If we're using the hybrid FasterMake/RecursiveMake backend, we want
+# to recurse in the faster/ directory in parallel of install manifests.
+ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
+install-manifests: faster
+.PHONY: faster
+faster:
+	$(MAKE) -C faster FASTER_RECURSIVE_MAKE=1
+endif
+
 # process_install_manifest needs to be invoked with --no-remove when building
 # js as standalone because automated builds are building nspr separately and
 # that would remove the resulting files.
 # Eventually, a standalone js build would just be able to build nspr itself,
 # removing the need for the former.
 ifdef JS_STANDALONE
 NO_REMOVE=1
 endif
 
-.PHONY: $(addprefix install-,$(install_manifests))
-$(addprefix install-,$(filter dist/%,$(install_manifests))): install-dist/%: $(install_manifest_depends)
-	$(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )$(DIST)/$* _build_manifests/install/dist_$*)
+.PHONY: $(addprefix install-,$(subst /,_,$(install_manifests)))
+$(addprefix install-,$(install_manifests)): install-%: $(install_manifest_depends)
+ifneq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
+	@# If we're using the hybrid FasterMake/RecursiveMake backend, we want
+	@# to ensure the FasterMake end doesn't have install manifests for the
+	@# same directory, because that would blow up
+	$(if $(wildcard _build_manifests/install/$(subst /,_,$*)),$(if $(wildcard faster/install_$(subst /,_,$*)*),$(error FasterMake and RecursiveMake ends of the hybrid build system want to handle $*)))
+endif
+	$(addprefix $(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )$*) ,$(wildcard _build_manifests/install/$(subst /,_,$*)))
 
 # Dummy wrapper rule to allow the faster backend to piggy back
 install-dist_%: install-dist/% ;
 
-install-_tests: $(install_manifest_depends)
-	$(call py_action,process_install_manifest,$(if $(NO_REMOVE),--no-remove )_tests _build_manifests/install/_tests)
-
 # For compatibility
 .PHONY: install-tests
 install-tests: install-_tests
 
 include $(topsrcdir)/build/moz-automation.mk
 
 # dist and _tests should be purged during cleaning. However, we don't want them
 # purged during PGO builds because they contain some auto-generated files.
@@ -153,30 +198,30 @@ endif
 # Windows PGO builds don't perform a clean before the 2nd pass. So, we want
 # to preserve content for the 2nd pass on Windows. Everywhere else, we always
 # process the install manifests as part of export.
 # For the binaries rule, not all the install manifests matter, so force only
 # the interesting ones to be done.
 ifdef MOZ_PROFILE_USE
 ifndef NO_PROFILE_GUIDED_OPTIMIZE
 ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
-export:: install-manifests
+recurse_pre-export:: install-manifests
 binaries::
 	@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
 endif
 endif
 else # !MOZ_PROFILE_USE (normal build)
-export:: install-manifests
+recurse_pre-export:: install-manifests
 binaries::
 	@$(MAKE) install-manifests NO_REMOVE=1 install_manifests=dist/include
 endif
 
 # For historical reasons that are unknown, $(DIST)/sdk is always blown away
 # with no regard for PGO passes. This decision could probably be revisited.
-export:: install-dist/sdk
+recurse_pre-export:: install-dist/sdk
 
 ifndef JS_STANDALONE
 ifdef ENABLE_TESTS
 # Additional makefile targets to call automated test suites
 include $(topsrcdir)/testing/testsuite-targets.mk
 endif
 endif
 
--- a/b2g/config/aries-l/sources.xml
+++ b/b2g/config/aries-l/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="be4b291a90b371b41b62ade68c31ad173bb87baa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="d3c9acb642baee501cff89e4efdb16b0c7480760">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="d3c9acb642baee501cff89e4efdb16b0c7480760">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="d3c9acb642baee501cff89e4efdb16b0c7480760">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="be4b291a90b371b41b62ade68c31ad173bb87baa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="d3c9acb642baee501cff89e4efdb16b0c7480760">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "89602d6ae53ecf4129b35a84e49d25b9fd37bc39", 
+        "git_revision": "92ed2a5ffd685bc8797bb15a84307cafe6d04f64", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "ea9b9cc69315e86bf7198ec0d57a267e64345b32", 
+    "revision": "36973054378673c8a4a8bc5904f1c08f800b5c28", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="d3c9acb642baee501cff89e4efdb16b0c7480760">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- 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"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -16,17 +16,17 @@
   <remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!--
     B2G repositories for all targets
     -->
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="89602d6ae53ecf4129b35a84e49d25b9fd37bc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="92ed2a5ffd685bc8797bb15a84307cafe6d04f64"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="be4b291a90b371b41b62ade68c31ad173bb87baa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
@@ -147,17 +147,17 @@
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="479a404164986b3e95212eecdae7e67da4fba9ed"/>
   <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="396b731dbccc62f272f1fdb8228109c3fbd83c25"/>
   <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="174bb44bb9af7583e6337e1e1b6cc18d0217ae82"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="1b8322b228f717ff2a4d48fa8b44240d8e3f62bc"/>
   <project name="platform_external_sepolicy" path="external/sepolicy" remote="b2g" revision="246c603d9fe181fa8893af7293dbc63e870fe5e0"/>
   <default remote="caf" revision="refs/tags/android-5.1.0_r1" sync-j="4"/>
   <!-- Nexus 5 specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="fe7df1bc8dd0fd71571505d7be1c31a4ad1e40fb"/>
-  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="bd18d073ab711f2c9f7f3c352f00b19acd10f5ae"/>
+  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="c4e9ed9053fbd21f57a7f014aaf898ac72ee1077"/>
   <project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="8b3ffcfdd3d3852eca5488628f8bb2a08acbffa7"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="5d0ae53d9588c3d70c005aec9be94af9a534de16"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="c15b6e266136cd0cdd9b94d0bbed1962d9dd6672"/>
   <project name="platform/hardware/broadcom/libbt" path="hardware/broadcom/libbt" revision="399fe3d3c8f38c599a56becddc456133e62a5d70"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="3f3134d5cb19d5ace48d36d0100467a545d430eb"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="810c3dd29d009822a71eba9910e429a9ad114533"/>
   <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="7b5967bbd90cb193b489d8f8668bc945384bd9b1"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="eaede9f8bc206736a889bc57817047c31e205589"/>
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -742,32 +742,36 @@ function saveMedia()
 
       saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
               null, gDocInfo.isContentWindowPrivate);
     }
   } else {
     selectSaveFolder(function(aDirectory) {
       if (aDirectory) {
         var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
+          uniqueFile(aChosenData.file);
           internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
-                       aChosenData, aBaseURI, null, gDocInfo.isContentWindowPrivate);
+                       aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate);
         };
 
         for (var i = 0; i < rowArray.length; i++) {
           var v = rowArray[i];
           var dir = aDirectory.clone();
           var item = gImageView.data[v][COL_IMAGE_NODE];
           var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
           var uri = makeURI(uriString);
 
           try {
             uri.QueryInterface(Components.interfaces.nsIURL);
             dir.append(decodeURIComponent(uri.fileName));
           } catch(ex) {
-            /* data: uris */
+            // data:/blob: uris
+            // Supply a dummy filename, otherwise Download Manager
+            // will try to delete the base directory on failure.
+            dir.append(gImageView.data[v][COL_IMAGE_TYPE]);
           }
 
           if (i == 0) {
             saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
           } else {
             // This delay is a hack which prevents the download manager
             // from opening many times. See bug 377339.
             setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
--- a/build/autoconf/config.status.m4
+++ b/build/autoconf/config.status.m4
@@ -223,17 +223,23 @@ MOZ_RUN_CONFIG_STATUS()],
 )])
 
 define([AC_CONFIG_HEADER],
 [m4_fatal([Use CONFIGURE_DEFINE_FILES in moz.build files to produce header files.])
 ])
 
 define([MOZ_BUILD_BACKEND],
 [
-BUILD_BACKENDS="RecursiveMake"
+dnl For now, only enable the unified hybrid build system on artifact builds,
+dnl otherwise default to RecursiveMake /and/ FasterMake.
+if test -n "$MOZ_ARTIFACT_BUILDS"; then
+    BUILD_BACKENDS="FasterMake+RecursiveMake"
+else
+    BUILD_BACKENDS="RecursiveMake FasterMake"
+fi
 
 MOZ_ARG_ENABLE_STRING(build-backend,
 [  --enable-build-backend={$($(dirname ]$[0)/$1/mach python -c "from mozbuild.backend import backends; print ','.join(sorted(backends))")}
                          Enable additional build backends],
-[ BUILD_BACKENDS="RecursiveMake `echo $enableval | sed 's/,/ /g'`"])
+[ BUILD_BACKENDS="$BUILD_BACKENDS `echo $enableval | sed 's/,/ /g'`"])
 
 AC_SUBST_SET([BUILD_BACKENDS])
 ])
--- a/config/baseconfig.mk
+++ b/config/baseconfig.mk
@@ -40,17 +40,17 @@ ifeq (a,$(firstword a$(subst /, ,$(srcdi
 $(error MSYS-style srcdir are not supported for Windows builds.)
 endif
 endif
 endif # WINNT
 
 ifndef INCLUDED_AUTOCONF_MK
 default::
 else
-TIERS := export $(if $(COMPILE_ENVIRONMENT),compile )misc libs tools
+TIERS := pre-export export $(if $(COMPILE_ENVIRONMENT),compile )misc libs tools
 endif
 
 # These defines are used to support the twin-topsrcdir model for comm-central.
 ifdef MOZILLA_SRCDIR
   MOZILLA_DIR = $(MOZILLA_SRCDIR)
 else
   MOZILLA_DIR = $(topsrcdir)
 endif
--- a/config/faster/rules.mk
+++ b/config/faster/rules.mk
@@ -19,18 +19,16 @@
 # the intent to grow this build system to make it more complete.
 #
 # This file contains rules and dependencies to get things working. The intent
 # is for a Makefile to define some dependencies and variables, and include
 # this file. What needs to be defined there, and ends up being generated by
 # python/mozbuild/mozbuild/backend/fastermake.py is the following:
 # - TOPSRCDIR/TOPOBJDIR, respectively the top source directory and the top
 #   object directory
-# - BACKEND, the path to the file the backend will always update when running
-#   mach build-backend
 # - PYTHON, the path to the python executable
 # - ACDEFINES, which contains a set of -Dvar=name to be used during
 #   preprocessing
 # - INSTALL_MANIFESTS, which defines the list of base directories handled
 #   by install manifests, see further below
 #
 # A convention used between this file and the Makefile including it is that
 # global Make variables names are uppercase, while "local" Make variables
@@ -44,57 +42,52 @@ ifndef TEST_MOZBUILD
 default: $(TOPOBJDIR)/dist/bin/platform.ini
 endif
 
 ifndef NO_XPIDL
 # Targets from the recursive make backend to be built for a default build
 default: $(TOPOBJDIR)/config/makefiles/xpidl/xpidl
 endif
 
-ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 # Mac builds require to copy things in dist/bin/*.app
 # TODO: remove the MOZ_WIDGET_TOOLKIT and MOZ_BUILD_APP variables from
 # faster/Makefile and python/mozbuild/mozbuild/test/backend/test_build.py
 # when this is not required anymore.
+# We however don't need to do this when using the hybrid
+# FasterMake/RecursiveMake backend (FASTER_RECURSIVE_MAKE is set when
+# recursing from the RecursiveMake backend)
+ifndef FASTER_RECURSIVE_MAKE
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 default:
 	$(MAKE) -C $(TOPOBJDIR)/$(MOZ_BUILD_APP)/app repackage
 endif
+endif
 
 .PHONY: FORCE
 
 # Extra define to trigger some workarounds. We should strive to limit the
 # use of those. As of writing the only ones are in
 # toolkit/content/buildconfig.html and browser/locales/jar.mn.
 ACDEFINES += -DBUILD_FASTER
 
 # Generic rule to fall back to the recursive make backend
 $(TOPOBJDIR)/%: FORCE
 	$(MAKE) -C $(dir $@) $(notdir $@)
 
 # Files under the faster/ sub-directory, however, are not meant to use the
 # fallback
 $(TOPOBJDIR)/faster/%: ;
 
-# Files under the python virtualenv, which are dependencies of the BACKEND
-# file, are not meant to use the fallback either.
-$(TOPOBJDIR)/_virtualenv/%: ;
-
 # And files under dist/ are meant to be copied from their first dependency
 # if there is no other rule.
 $(TOPOBJDIR)/dist/%:
 	rm -f $@
 	mkdir -p $(@D)
 	cp $< $@
 
-# Refresh backend
-$(BACKEND):
-	cd $(TOPOBJDIR) && $(PYTHON) config.status --backend FasterMake
-
-$(MAKEFILE_LIST): $(BACKEND)
-
 # Install files using install manifests
 #
 # The list of base directories is given in INSTALL_MANIFESTS. The
 # corresponding install manifests are named correspondingly, with forward
 # slashes replaced with underscores, and prefixed with `install_`. That is,
 # the install manifest for `dist/bin` would be `install_dist_bin`.
 $(addprefix install-,$(INSTALL_MANIFESTS)): install-%: $(TOPOBJDIR)/config/buildid
 	@# For now, force preprocessed files to be reprocessed every time.
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -527,22 +527,27 @@ endif
 ################################################################################
 
 # Ensure the build config is up to date. This is done automatically when builds
 # are performed through |mach build|. The check here is to catch people not
 # using mach. If we ever enforce builds through mach, this code can be removed.
 ifndef MOZBUILD_BACKEND_CHECKED
 ifndef MACH
 ifndef TOPLEVEL_BUILD
-$(DEPTH)/backend.RecursiveMakeBackend:
+BUILD_BACKEND_FILES := $(addprefix $(DEPTH)/backend.,$(addsuffix Backend,$(BUILD_BACKENDS)))
+$(DEPTH)/backend.%Backend:
 	$(error Build configuration changed. Build with |mach build| or run |mach build-backend| to regenerate build config)
 
-include $(DEPTH)/backend.RecursiveMakeBackend.pp
+define build_backend_rule
+$(1): $$(shell cat $(1).in)
 
-default:: $(DEPTH)/backend.RecursiveMakeBackend
+endef
+$(foreach file,$(BUILD_BACKEND_FILES),$(eval $(call build_backend_rule,$(file))))
+
+default:: $(BUILD_BACKEND_FILES)
 
 export MOZBUILD_BACKEND_CHECKED=1
 endif
 endif
 endif
 
 # The root makefile doesn't want to do a plain export/libs, because
 # of the tiers and because of libxul. Suppress the default rules in favor
@@ -1224,22 +1229,16 @@ endif
 libs realchrome:: $(FINAL_TARGET)/chrome
 	$(call py_action,jar_maker,\
 	  $(QUIET) -d $(FINAL_TARGET) \
 	  $(MAKE_JARS_FLAGS) $(DEFINES) $(ACDEFINES) \
 	  $(JAR_MANIFEST))
 
 endif
 
-# This is a temporary check to ensure patches relying on the old behavior
-# of silently picking up jar.mn files continue to work.
-else # No JAR_MANIFEST
-ifneq (,$(wildcard $(srcdir)/jar.mn))
-$(error $(srcdir) contains a jar.mn file but this file is not declared in a JAR_MANIFESTS variable in a moz.build file)
-endif
 endif
 
 # When you move this out of the tools tier, please remove the corresponding
 # hacks in recursivemake.py that check if Makefile.in sets the variable.
 ifneq ($(XPI_PKGNAME),)
 tools realchrome::
 ifdef STRIP_XPI
 ifndef MOZ_DEBUG
--- a/configure.in
+++ b/configure.in
@@ -134,18 +134,16 @@ EOF
   exit 1
   break
 fi
 MOZ_BUILD_ROOT=`pwd -W 2>/dev/null || pwd -P`
 DIST="$MOZ_BUILD_ROOT/dist"
 
 MOZ_PYTHON
 
-MOZ_BUILD_BACKEND(.)
-
 MOZ_DEFAULT_COMPILER
 
 COMPILE_ENVIRONMENT=1
 MOZ_ARG_DISABLE_BOOL(compile-environment,
 [  --disable-compile-environment
                           Disable compiler/library checks.],
     COMPILE_ENVIRONMENT= )
 AC_SUBST(COMPILE_ENVIRONMENT)
@@ -157,16 +155,18 @@ MOZ_ARG_ENABLE_BOOL(artifact-builds,
     MOZ_ARTIFACT_BUILDS= )
 AC_SUBST(MOZ_ARTIFACT_BUILDS)
 
 if test -n "$MOZ_ARTIFACT_BUILDS"; then
     dnl Artifact builds imply --disable-compile-environment.
     COMPILE_ENVIRONMENT=
 fi
 
+MOZ_BUILD_BACKEND(.)
+
 MOZ_ARG_WITH_STRING(l10n-base,
 [  --with-l10n-base=DIR    path to l10n repositories],
     L10NBASEDIR=$withval)
 if test -n "$L10NBASEDIR"; then
     if test "$L10NBASEDIR" = "yes" -o "$L10NBASEDIR" = "no"; then
         AC_MSG_ERROR([--with-l10n-base must specify a path])
     elif test -d "$L10NBASEDIR"; then
         L10NBASEDIR=`cd "$L10NBASEDIR" && pwd -P`
@@ -4057,18 +4057,16 @@ browser)
   AC_DEFINE(MOZ_PHOENIX)
   ;;
 
 xulrunner)
   AC_DEFINE(MOZ_XULRUNNER)
   ;;
 esac
 
-BUILD_BACKENDS="$BUILD_BACKENDS FasterMake"
-
 if test -n "$MOZ_B2G"; then
     AC_DEFINE(MOZ_B2G)
 fi
 
 # Graphene is a desktop runtime for running applications with a HTML UI.
 if test -n "$MOZ_GRAPHENE"; then
     AC_DEFINE(MOZ_GRAPHENE)
 fi
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10761,42 +10761,46 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));
   if (props) {
     // save true referrer for those who need it (e.g. xpinstall whitelisting)
     // Currently only http and ftp channels support this.
     props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"),
                                   aReferrerURI);
   }
 
-  nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
-  nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
-
-
-  /* Get the cache Key from SH */
-  nsCOMPtr<nsISupports> cacheKey;
-  if (mLSHE) {
-    mLSHE->GetCacheKey(getter_AddRefs(cacheKey));
-  } else if (mOSHE) {  // for reload cases
-    mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
-  }
-
-  if (uploadChannel) {
+  //
+  // If this is a HTTP channel, then set up the HTTP specific information
+  // (ie. POST data, referrer, ...)
+  //
+  if (httpChannel) {
+    nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+    /* Get the cache Key from SH */
+    nsCOMPtr<nsISupports> cacheKey;
+    if (mLSHE) {
+      mLSHE->GetCacheKey(getter_AddRefs(cacheKey));
+    } else if (mOSHE) {  // for reload cases
+      mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
+    }
+
     // figure out if we need to set the post data stream on the channel...
     // right now, this is only done for http channels.....
     if (aPostData) {
       // XXX it's a bit of a hack to rewind the postdata stream here but
       // it has to be done in case the post data is being reused multiple
       // times.
       nsCOMPtr<nsISeekableStream> postDataSeekable =
         do_QueryInterface(aPostData);
       if (postDataSeekable) {
         rv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
+      nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+      NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
+
       // we really need to have a content type associated with this stream!!
       uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1);
       /* If there is a valid postdata *and* it is a History Load,
        * set up the cache key on the channel, to retrieve the
        * data *only* from the cache. If it is a normal reload, the
        * cache is free to go to the server for updated postdata.
        */
       if (cacheChannel && cacheKey) {
@@ -10822,19 +10826,16 @@ nsDocShell::DoURILoad(nsIURI* aURI,
       if (mLoadType == LOAD_HISTORY ||
           mLoadType == LOAD_RELOAD_NORMAL ||
           mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
         if (cacheChannel && cacheKey) {
           cacheChannel->SetCacheKey(cacheKey);
         }
       }
     }
-  }
-
-  if (httpChannel) {
     if (aHeadersData) {
       rv = AddHeadersToChannel(aHeadersData, httpChannel);
     }
     // Set the referrer explicitly
     if (aReferrerURI && aSendReferrer) {
       // Referrer is currenly only set for link clicks here.
       httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
     }
--- a/dom/apps/Webapps.js
+++ b/dom/apps/Webapps.js
@@ -568,25 +568,31 @@ WebappsApplication.prototype = {
                             oid: this._id,
                             topId: this._topId,
                             requestID: this.getRequestId(request) });
     return request;
   },
 
   launch: function(aStartPoint) {
     let request = this.createRequest();
-    this.addMessageListeners(["Webapps:Launch:Return:OK",
-                              "Webapps:Launch:Return:KO"]);
     cpmm.sendAsyncMessage("Webapps:Launch", { origin: this.origin,
                                               manifestURL: this.manifestURL,
                                               startPoint: aStartPoint || "",
                                               oid: this._id,
                                               topId: this._topId,
                                               timestamp: Date.now(),
                                               requestID: this.getRequestId(request) });
+
+    let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
+    if (manifestURL != this.manifestURL) {
+      Services.obs.notifyObservers(null, "will-launch-app", null);
+    }
+
+    this.addMessageListeners(["Webapps:Launch:Return:OK",
+                              "Webapps:Launch:Return:KO"]);
     return request;
   },
 
   clearBrowserData: function() {
     let request = this.createRequest();
     let browserChild =
       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
     if (browserChild) {
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1913,16 +1913,17 @@ GK_ATOM(onpointerout, "onpointerout")
 GK_ATOM(onpointerenter, "onpointerenter")
 GK_ATOM(onpointerleave, "onpointerleave")
 GK_ATOM(ongotpointercapture, "ongotpointercapture")
 GK_ATOM(onlostpointercapture, "onlostpointercapture")
 
 // orientation support
 GK_ATOM(ondevicemotion, "ondevicemotion")
 GK_ATOM(ondeviceorientation, "ondeviceorientation")
+GK_ATOM(onabsolutedeviceorientation, "onabsolutedeviceorientation")
 GK_ATOM(ondeviceproximity, "ondeviceproximity")
 GK_ATOM(onmozorientationchange, "onmozorientationchange")
 GK_ATOM(onuserproximity, "onuserproximity")
 
 // light sensor support
 GK_ATOM(ondevicelight, "ondevicelight")
 
 // Audio channel events
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2714,25 +2714,26 @@ nsObjectLoadingContent::NotifyStateChang
   nsIDocument* doc = thisContent->GetComposedDoc();
   if (!doc) {
     return; // Nothing to do
   }
 
   EventStates newState = ObjectState();
 
   if (newState != aOldState) {
+    NS_ASSERTION(thisContent->IsInComposedDoc(), "Something is confused");
     // This will trigger frame construction
-    NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
     EventStates changedBits = aOldState ^ newState;
 
     {
       nsAutoScriptBlocker scriptBlocker;
       doc->ContentStateChanged(thisContent, changedBits);
     }
     if (aSync) {
+      NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
       // Make sure that frames are actually constructed immediately.
       doc->FlushPendingNotifications(Flush_Frames);
     }
   } else if (aOldType != mType) {
     // If our state changed, then we already recreated frames
     // Otherwise, need to do that here
     nsCOMPtr<nsIPresShell> shell = doc->GetShell();
     if (shell) {
new file mode 100644
--- /dev/null
+++ b/dom/base/test/create_file_objects.js
@@ -0,0 +1,10 @@
+Components.utils.importGlobalProperties(['File']);
+
+addMessageListener("create-file-objects", function(message) {
+  let files = []
+  for (fileName of message.fileNames) {
+    files.push(new File(fileName));
+  }
+
+  sendAsyncMessage("created-file-objects", files);
+});
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -49,16 +49,17 @@ support-files =
   bug696301-script-1.js
   bug696301-script-1.js^headers^
   bug696301-script-2.js
   bug704320.sjs
   bug704320_counter.sjs
   bug819051.sjs
   chrome/bug418986-1.js
   copypaste.js
+  create_file_objects.js
   delayedServerEvents.sjs
   echo.sjs
   eventsource.resource
   eventsource.resource^headers^
   eventsource_redirect.resource
   eventsource_redirect.resource^headers^
   eventsource_redirect_to.resource
   eventsource_redirect_to.resource^headers^
@@ -777,17 +778,17 @@ skip-if = (os != 'b2g' && os != 'android
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport5.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport6.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport7.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_mozfiledataurl.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_mozMatchesSelector.html]
 [test_mutationobservers.html]
 skip-if = buildapp == 'b2g' # b2g(bug 901385, showmodaldialog) b2g-debug(bug 901385, showmodaldialog) b2g-desktop(bug 901385, showmodaldialog)
 [test_nodelist_holes.html]
 [test_plugin_freezing.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #CLICK_TO_PLAY
 [test_processing_instruction_update_stylesheet.xhtml]
 [test_progress_events_for_gzip_data.html]
--- a/dom/base/test/test_mozfiledataurl.html
+++ b/dom/base/test/test_mozfiledataurl.html
@@ -1,54 +1,78 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
   <title>Test for File urls</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="gen.next()">
+<body onload="start()">
 <p id="display">
 <iframe id=inner></iframe>
 <iframe id=iframe></iframe>
 <img id=img onload="gen.send(event);">
 <audio id=audio onloadeddata="gen.send(event);">
-<input type=file id=fileList>
 </p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 try {
   URL.createObjectURL(undefined);
 } catch(e) { }
 
 window.addEventListener("message", function(e) {
   gen.send(JSON.parse(e.data));
 }, false);
 
 const innerSameSiteURI = "file_mozfiledataurl_inner.html";
 const innerCrossSiteURI = "http://example.com/tests/dom/base/test/file_mozfiledataurl_inner.html"
 
-gen = runTest();
+var fileNames = ["file_mozfiledataurl_img.jpg",
+                 "file_mozfiledataurl_audio.ogg",
+                 "file_mozfiledataurl_doc.html",
+                 "file_mozfiledataurl_text.txt"];
+
+function start() {
+  let xhr = new XMLHttpRequest;
+  xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+  xhr.send();
+  let basePath = xhr.responseText;
+
+  let fullFileNames = [];
+  for (let name of fileNames) {
+    fullFileNames.push(basePath + name);
+  }
+
+  var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("create_file_objects.js"));
+
+  script.addMessageListener("created-file-objects", function handler(files) {
+    script.removeMessageListener("created-file-objects", handler);
+    gen = runTest(files);
+    gen.next();
+  });
+
+  script.sendAsyncMessage("create-file-objects", {fileNames: fullFileNames});
+};
+
 SimpleTest.waitForExplicitFinish();
 
-function runTest() {
+function runTest([imgFile, audioFile, docFile, xhrFile]) {
   inner = document.getElementById('inner');
   img = document.getElementById('img');
   audio = document.getElementById('audio');
   iframe = document.getElementById('iframe');
   inner.onload = function() { gen.send("inner loaded"); };
 
   // Attempt to load a image in this document
-  var file = getFile("file_mozfiledataurl_img.jpg");
-  var fileurl = URL.createObjectURL(file);
+  var fileurl = URL.createObjectURL(imgFile);
   img.src = fileurl;
   var e = (yield);
   is(e.type, "load", "loaded successfully");
   is(img.width, 120, "correct width");
   is(img.height, 90, "correct height");
 
   // Revoke url and attempt to load a image in this document
   img.src = "file_mozfiledataurl_img.jpg";
@@ -58,40 +82,39 @@ function runTest() {
 /*  img.src = fileurl;
   var e = (yield);
   is(e.type, "error", "failed successfully");
   isnot(img.width, 120, "correct error width");
   isnot(img.height, 90, "correct error height");
 */
   // Generate new fileurl and make sure it's different from the old
   var oldFileurl = fileurl;
-  var fileurl = URL.createObjectURL(file);
+  fileurl = URL.createObjectURL(imgFile);
   isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
 
   // Attempt to load an image in a different same-origin document
   inner.src = innerSameSiteURI;
   yield undefined;
   inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
   var res = (yield);
   is(res.type, "load", "loaded successfully");
   is(res.width, 120, "correct width");
   is(res.height, 90, "correct height");
-  
+
   // Attempt to load an image in a different cross-origin document
   inner.src = innerCrossSiteURI;
   yield undefined;
   inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
   var res = (yield);
   is(res.type, "error", "failed successfully");
   isnot(res.width, 120, "correct error width");
   isnot(res.height, 90, "correct error height");
 
   // Attempt to load an audio in this document
-  var file = getFile("file_mozfiledataurl_audio.ogg");
-  var fileurl = URL.createObjectURL(file);
+  fileurl = URL.createObjectURL(audioFile);
   audio.src = fileurl;
   var e = (yield);
   is(e.type, "loadeddata", "loaded successfully");
 
   // Revoke url and attempt to load a audio in this document
   audio.src = "file_mozfiledataurl_audio.ogg";
   is((yield).type, "loadeddata", "successfully reset audio");
   URL.revokeObjectURL(fileurl);
@@ -99,26 +122,26 @@ function runTest() {
 /*  img.src = fileurl;
   var e = (yield);
   is(e.type, "error", "failed successfully");
   isnot(img.width, 120, "correct error width");
   isnot(img.height, 90, "correct error height");
 */
   // Generate new fileurl and make sure it's different from the old
   var oldFileurl = fileurl;
-  var fileurl = URL.createObjectURL(file);
+  fileurl = URL.createObjectURL(audioFile);
   isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
 
   // Attempt to load an audio in a different same-origin document
   inner.src = innerSameSiteURI;
   yield undefined;
   inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
   var res = (yield);
   is(res.type, "loadeddata", "loaded successfully");
-  
+
   // Attempt to load an audio in a different cross-origin document
   inner.src = innerCrossSiteURI;
   yield undefined;
   inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
   var res = (yield);
   is(res.type, "error", "failed successfully");
 
   // Attempt to load a HTML document in an iframe in this document
@@ -129,18 +152,17 @@ function runTest() {
      "This here is a document!",
      "iframe loaded successfully");
   is(iframe.contentDocument.getElementById("img").width, 120,
      "image in iframe width");
   is(iframe.contentDocument.getElementById("img").height, 90,
      "image in iframe height");
 
   // Attempt to load a HTML document in an iframe in this document, using file url
-  file = getFile("file_mozfiledataurl_doc.html");
-  fileurl = URL.createObjectURL(file);
+  fileurl = URL.createObjectURL(docFile);
   iframe.src = fileurl;
   yield undefined;
   is(iframe.contentDocument.getElementsByTagName("p")[0].textContent,
      "This here is a document!",
      "iframe loaded successfully");
   isnot(iframe.contentDocument.getElementById("img").width, 120,
         "failed image in iframe width");
   isnot(iframe.contentDocument.getElementById("img").height, 90,
@@ -165,18 +187,17 @@ function runTest() {
   // Attempt to load a HTML document in an iframe in inner cross-site document, using file url
   inner.src = innerCrossSiteURI;
   is((yield), "inner loaded", "correct gen.next()");
   inner.contentWindow.postMessage(JSON.stringify({iframe:fileurl}), "*");
   var res = (yield);
   is(res.type, "error", "load failed successfully");
 
   // Attempt to load file url using XHR
-  file = getFile("file_mozfiledataurl_text.txt");
-  fileurl = URL.createObjectURL(file);
+  fileurl = URL.createObjectURL(xhrFile);
   xhr = new XMLHttpRequest;
   xhr.onload = function() { gen.send("XHR finished"); };
   xhr.open("GET", fileurl);
   xhr.send();
   is((yield), "XHR finished", "correct gen.next()");
   xhr.responseText == "Yarr, here be plaintext file, ya landlubber\n";
 
   // Attempt to load file url using XHR in inner document
@@ -193,29 +214,12 @@ function runTest() {
   inner.contentWindow.postMessage(JSON.stringify({xhr:fileurl}), "*");
   var res = (yield);
   is(res.didThrow, true, "load failed successfully");
 
   SimpleTest.finish();
 
   yield undefined;
 }
-
-
-var basePath = "";
-function getFile(name) {
-  if (!basePath) {
-    let xhr = new XMLHttpRequest;
-    xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
-    xhr.send();
-    basePath = xhr.responseText;
-  }
-
-  var fileList = document.getElementById('fileList');
-  SpecialPowers.wrap(fileList).value = basePath + name;
-
-  return fileList.files[0];
-}
-
 </script>
 </pre>
 </body>
 </html>
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -22,16 +22,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
                                   "resource://gre/modules/ManifestFinder.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
                                   "resource://gre/modules/ManifestObtainer.jsm");
 
 
 var kLongestReturnedString = 128;
 
+const Timer = Components.Constructor("@mozilla.org/timer;1",
+                                     "nsITimer",
+                                     "initWithCallback");
+
 function debug(msg) {
   //dump("BrowserElementChildPreload - " + msg + "\n");
 }
 
 function sendAsyncMsg(msg, data) {
   // Ensure that we don't send any messages before BrowserElementChild.js
   // finishes loading.
   if (!BrowserElementIsReady)
@@ -60,17 +64,18 @@ function sendSyncMsg(msg, data) {
 }
 
 var CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';
 
 const OBSERVED_EVENTS = [
   'xpcom-shutdown',
   'audio-playback',
   'activity-done',
-  'invalid-widget'
+  'invalid-widget',
+  'will-launch-app'
 ];
 
 /**
  * The BrowserElementChild implements one half of <iframe mozbrowser>.
  * (The other half is, unsurprisingly, BrowserElementParent.)
  *
  * This script is injected into an <iframe mozbrowser> via
  * nsIMessageManager::LoadFrameScript().
@@ -311,21 +316,24 @@ BrowserElementChild.prototype = {
 
     OBSERVED_EVENTS.forEach((aTopic) => {
       Services.obs.addObserver(this, aTopic, false);
     });
 
     this.forwarder.init();
   },
 
+  _paintFrozenTimer: null,
   observe: function(subject, topic, data) {
     // Ignore notifications not about our document.  (Note that |content| /can/
     // be null; see bug 874900.)
 
-    if (topic !== 'activity-done' && topic !== 'audio-playback' &&
+    if (topic !== 'activity-done' &&
+        topic !== 'audio-playback' &&
+        topic !== 'will-launch-app' &&
         (!content || subject !== content.document)) {
       return;
     }
     if (topic == 'activity-done' && docShell !== subject)
       return;
     switch (topic) {
       case 'activity-done':
         sendAsyncMsg('activitydone', { success: (data == 'activity-success') });
@@ -336,19 +344,41 @@ BrowserElementChild.prototype = {
         }
         break;
       case 'xpcom-shutdown':
         this._shuttingDown = true;
         break;
       case 'invalid-widget':
         sendAsyncMsg('error', { type: 'invalid-widget' });
         break;
+      case 'will-launch-app':
+        // If the launcher is not visible, let's ignore the message.
+        if (!docShell.isActive) {
+          return;
+        }
+
+        // If this is not a content process, let's not freeze painting.
+        if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_CONTENT) {
+          return;
+        }
+
+        docShell.contentViewer.pausePainting();
+
+        this._paintFrozenTimer && this._paintFrozenTimer.cancel();
+        this._paintFrozenTimer = new Timer(this, 3000, Ci.nsITimer.TYPE_ONE_SHOT);
+        break;
     }
   },
 
+  notify: function(timer) {
+    docShell.contentViewer.resumePainting();
+    this._paintFrozenTimer.cancel();
+    this._paintFrozenTimer = null;
+  },
+
   /**
    * Called when our TabChildGlobal starts to die.  This is not called when the
    * page inside |content| unloads.
    */
   _unloadHandler: function() {
     this._shuttingDown = true;
     OBSERVED_EVENTS.forEach((aTopic) => {
       Services.obs.removeObserver(this, aTopic);
@@ -1260,16 +1290,21 @@ BrowserElementChild.prototype = {
     this._updateVisibility();
   },
 
   _updateVisibility: function() {
     var visible = this._forcedVisible && this._ownerVisible;
     if (docShell && docShell.isActive !== visible) {
       docShell.isActive = visible;
       sendAsyncMsg('visibilitychange', {visible: visible});
+
+      // Ensure painting is not frozen if the app goes visible.
+      if (visible && this._paintFrozenTimer) {
+        this.notify();
+      }
     }
   },
 
   _recvSendMouseEvent: function(data) {
     let json = data.json;
     let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIDOMWindowUtils);
     utils.sendMouseEventToWindow(json.type, json.x, json.y, json.button,
--- a/dom/browser-element/mochitest/browserElement_NoAudioTrack.js
+++ b/dom/browser-element/mochitest/browserElement_NoAudioTrack.js
@@ -3,54 +3,60 @@
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
 var fileURL = 'http://example.org/tests/dom/browser-element/mochitest/file_browserElement_NoAudioTrack.html';
 var generator = runTests();
 var testFrame;
 
+function alertListener(e) {
+  var message = e.detail.message;
+  if (/^OK/.exec(message)) {
+    ok(true, "Message from file : " + message);
+    continueTest();
+  } else if (/^KO/.exec(message)) {
+    error(message);
+  } else {
+    error("Undefined event.");
+  }
+}
+
 function error(aMessage) {
   ok(false, "Error : " + aMessage);
   finish();
 }
 
 function continueTest() {
   try {
     generator.next();
   } catch (e if e instanceof StopIteration) {
     error("Stop test because of exception!");
   }
 }
 
 function finish() {
+  testFrame.removeEventListener('mozbrowsershowmodalprompt', alertListener);
+  ok(true, "Remove event-listener.");
   document.body.removeChild(testFrame);
+  ok(true, "Remove test-frame from document.");
   SimpleTest.finish();
 }
 
 function setCommand(aArg) {
   info("# Command = " + aArg);
   testFrame.src = fileURL + '#' + aArg;
-
-  // Yield to the event loop a few times to make sure that onactivestatechanged
-  // is not dispatched.
-  SimpleTest.executeSoon(function() {
-    SimpleTest.executeSoon(function() {
-      SimpleTest.executeSoon(function() {
-        continueTest();
-      });
-    });
-  });
 }
 
 function runTests() {
   setCommand('play');
   yield undefined;
 
-  setCommand('pause');
+  // wait a second to make sure that onactivestatechanged isn't dispatched.
+  setCommand('idle');
   yield undefined;
 
   finish();
   yield undefined;
 }
 
 function setupTestFrame() {
   testFrame = document.createElement('iframe');
@@ -72,17 +78,21 @@ function setupTestFrame() {
       ac.onactivestatechanged = null;
       error("Should not receive onactivestatechanged!");
     };
 
     continueTest();
   }
 
   testFrame.addEventListener('mozbrowserloadend', loadend);
+  testFrame.addEventListener('mozbrowsershowmodalprompt', alertListener);
+  ok(true, "Add event-listeners.");
+
   document.body.appendChild(testFrame);
+  ok(true, "Append test-frame to document.");
 }
 
 addEventListener('testready', function() {
   SpecialPowers.pushPrefEnv({'set': [["b2g.system_manifest_url", "http://mochi.test:8888/manifest.webapp"]]},
                             function() {
     SimpleTest.executeSoon(setupTestFrame);
   });
 });
--- a/dom/browser-element/mochitest/file_browserElement_NoAudioTrack.html
+++ b/dom/browser-element/mochitest/file_browserElement_NoAudioTrack.html
@@ -1,11 +1,32 @@
 <!DOCTYPE HTML>
 <html>
 <body>
 <script type="application/javascript;version=1.7">
 var audio = new Audio();
 audio.src = "noaudio.webm";
+audio.preload = "none";
 audio.loop = true;
-audio.play();
+
+function ok(aVal, aMsg) {
+  alert((!!aVal ? "OK" : "KO") + ", " + aMsg);
+}
+
+function runCommands()
+{
+  switch(location.hash) {
+    case '#play':
+      audio.play();
+      ok(true, "Start playing a video without audio track!");
+      break;
+    case '#idle':
+      ok(!audio.paused, "Video is still playing!");
+      break;
+    default :
+      ok(false, "Undefined command!");
+  }
+}
+
+window.addEventListener('hashchange', runCommands);
 </script>
 </body>
 </html>
\ No newline at end of file
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -207,16 +207,17 @@ skip-if = (toolkit == 'gonk' && !debug)
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_browserElement_inproc_GetScreenshotDppx.html]
 [test_browserElement_inproc_Iconchange.html]
 [test_browserElement_inproc_LoadEvents.html]
 [test_browserElement_inproc_Manifestchange.html]
 [test_browserElement_inproc_Metachange.html]
 [test_browserElement_inproc_NextPaint.html]
 [test_browserElement_inproc_NoAudioTrack.html]
+tags = audiochannel
 [test_browserElement_inproc_OpenNamed.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenTab.html]
 disabled = won't work as Firefox desktop will intercept ctrl-click
 [test_browserElement_inproc_OpenWindow.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenWindowDifferentOrigin.html]
 skip-if = (toolkit == 'gonk' && !debug)
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -807,16 +807,17 @@ WebGLContext::GetRenderbufferParameter(G
     if (!mBoundRenderbuffer) {
         ErrorInvalidOperation("getRenderbufferParameter: no render buffer is bound");
         return JS::NullValue();
     }
 
     MakeContextCurrent();
 
     switch (pname) {
+        case LOCAL_GL_RENDERBUFFER_SAMPLES:
         case LOCAL_GL_RENDERBUFFER_WIDTH:
         case LOCAL_GL_RENDERBUFFER_HEIGHT:
         case LOCAL_GL_RENDERBUFFER_RED_SIZE:
         case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
         case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
         case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
         case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
         case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -352,16 +352,22 @@ WebGLContext::GetVertexAttrib(JSContext*
         return JS::Int32Value(mBoundVertexArray->mAttribs[index].size);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
         if (!mBoundVertexArray->mAttribs[index].enabled)
             return JS::NumberValue(uint32_t(LOCAL_GL_FLOAT));
 
         return JS::NumberValue(uint32_t(mBoundVertexArray->mAttribs[index].type));
 
+    case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
+        if (IsWebGL2())
+            return JS::BooleanValue(mBoundVertexArray->mAttribs[index].integer);
+
+        break;
+
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
         if (IsWebGL2() ||
             IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays))
         {
             return JS::Int32Value(mBoundVertexArray->mAttribs[index].divisor);
         }
         break;
 
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -817,16 +817,19 @@ FormatUsageAuthority::CreateForWebGL2(gl
     ptr->AllowRBFormat(LOCAL_GL_STENCIL_INDEX8, usage);
 
     ////////////////
     // Legacy formats
 
     if (!AddUnsizedFormats(ptr, gl))
         return nullptr;
 
+    ptr->AllowRBFormat(LOCAL_GL_DEPTH_STENCIL,
+                       ptr->GetUsage(EffectiveFormat::DEPTH24_STENCIL8));
+
     if (gfxPrefs::WebGL2CompatMode()) {
         AddSimpleUnsized(ptr, LOCAL_GL_RGBA, LOCAL_GL_FLOAT, EffectiveFormat::RGBA32F);
         AddSimpleUnsized(ptr, LOCAL_GL_RGB , LOCAL_GL_FLOAT, EffectiveFormat::RGB32F );
 
         AddSimpleUnsized(ptr, LOCAL_GL_RGBA, LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGBA16F);
         AddSimpleUnsized(ptr, LOCAL_GL_RGB , LOCAL_GL_HALF_FLOAT_OES, EffectiveFormat::RGB16F );
     }
 
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -20,29 +20,27 @@ DepthStencilDepthFormat(gl::GLContext* g
     // We might not be able to get 24-bit, so let's pretend!
     if (gl->IsGLES() && !gl->IsExtensionSupported(gl::GLContext::OES_depth24))
         return LOCAL_GL_DEPTH_COMPONENT16;
 
     return LOCAL_GL_DEPTH_COMPONENT24;
 }
 
 static bool
-SupportsDepthStencil(gl::GLContext* gl)
-{
-    return gl->IsSupported(gl::GLFeature::packed_depth_stencil);
-}
-
-static bool
 NeedsDepthStencilEmu(gl::GLContext* gl, GLenum internalFormat)
 {
     MOZ_ASSERT(internalFormat != LOCAL_GL_DEPTH_STENCIL);
+
     if (internalFormat != LOCAL_GL_DEPTH24_STENCIL8)
         return false;
 
-    return !SupportsDepthStencil(gl);
+    if (gl->IsSupported(gl::GLFeature::packed_depth_stencil))
+        return false;
+
+    return true;
 }
 
 JSObject*
 WebGLRenderbuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLRenderbufferBinding::Wrap(cx, this, givenProto);
 }
 
@@ -56,18 +54,20 @@ WebGLRenderbuffer::WebGLRenderbuffer(Web
     , mIsUsingSecondary(false)
 #ifdef ANDROID
     , mIsRB(false)
 #endif
 {
     mContext->MakeContextCurrent();
 
     mContext->gl->fGenRenderbuffers(1, &mPrimaryRB);
-    if (!SupportsDepthStencil(mContext->gl))
+
+    if (!mContext->gl->IsSupported(gl::GLFeature::packed_depth_stencil)) {
         mContext->gl->fGenRenderbuffers(1, &mSecondaryRB);
+    }
 
     mContext->mRenderbuffers.insertBack(this);
 }
 
 void
 WebGLRenderbuffer::Delete()
 {
     mContext->MakeContextCurrent();
@@ -145,17 +145,17 @@ RenderbufferStorageMaybeMultisample(gl::
         // the RGB565 format is not supported on desktop GL
         if (!gl->IsGLES())
             internalFormatForGL = LOCAL_GL_RGB8;
         break;
 
     case LOCAL_GL_DEPTH_COMPONENT16:
         if (!gl->IsGLES() || gl->IsExtensionSupported(gl::GLContext::OES_depth24))
             internalFormatForGL = LOCAL_GL_DEPTH_COMPONENT24;
-        else if (gl->IsExtensionSupported(gl::GLContext::OES_packed_depth_stencil))
+        else if (gl->IsSupported(gl::GLFeature::packed_depth_stencil))
             internalFormatForGL = LOCAL_GL_DEPTH24_STENCIL8;
         break;
 
     case LOCAL_GL_DEPTH_STENCIL:
         // We emulate this in WebGLRenderbuffer if we don't have the requisite extension.
         internalFormatForGL = LOCAL_GL_DEPTH24_STENCIL8;
         break;
 
@@ -252,16 +252,17 @@ WebGLRenderbuffer::GetRenderbufferParame
         if (!mFormat)
             return 0;
 
         if (!mFormat->format->hasStencil)
             return 0;
 
         return 8;
 
+    case LOCAL_GL_RENDERBUFFER_SAMPLES:
     case LOCAL_GL_RENDERBUFFER_WIDTH:
     case LOCAL_GL_RENDERBUFFER_HEIGHT:
     case LOCAL_GL_RENDERBUFFER_RED_SIZE:
     case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
     case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
     case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
     case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
         {
--- a/dom/canvas/WebGLStrongTypes.h
+++ b/dom/canvas/WebGLStrongTypes.h
@@ -416,16 +416,17 @@ STRONG_GLENUM_BEGIN(FBStatus)
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_DIMENSIONS),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_READ_BUFFER),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_UNSUPPORTED),
 STRONG_GLENUM_END(FBStatus)
 
 STRONG_GLENUM_BEGIN(RBParam)
+    STRONG_GLENUM_VALUE(RENDERBUFFER_SAMPLES),
     STRONG_GLENUM_VALUE(RENDERBUFFER_WIDTH),
     STRONG_GLENUM_VALUE(RENDERBUFFER_HEIGHT),
     STRONG_GLENUM_VALUE(RENDERBUFFER_INTERNAL_FORMAT),
     STRONG_GLENUM_VALUE(RENDERBUFFER_RED_SIZE),
     STRONG_GLENUM_VALUE(RENDERBUFFER_GREEN_SIZE),
     STRONG_GLENUM_VALUE(RENDERBUFFER_BLUE_SIZE),
     STRONG_GLENUM_VALUE(RENDERBUFFER_ALPHA_SIZE),
     STRONG_GLENUM_VALUE(RENDERBUFFER_DEPTH_SIZE),
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -2,17 +2,17 @@
 # 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/.
 
 TEST_DIRS += ['compiledtest']
 
 # Number changes to this file to avoid bug 1081323 (clobber after changing a manifest):
-# 2
+# 3
 
 MOCHITEST_MANIFESTS += [
     'test/crossorigin/mochitest.ini',
     'test/mochitest-subsuite-webgl.ini',
     'test/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -25,23 +25,23 @@ skip-if = (os == 'b2g') || buildapp == '
 skip-if = android_version == '10' || android_version == '18' #Android 2.3 and 4.3 aws only; bug 1030942
 [webgl-mochitest/test_noprog_draw.html]
 [webgl-mochitest/test_privileged_exts.html]
 [webgl-mochitest/test_renderer_strings.html]
 [webgl-mochitest/test_sab_with_webgl.html]
 [webgl-mochitest/test_texsubimage_float.html]
 [webgl-mochitest/test_uninit_data.html]
 [webgl-mochitest/test_webgl_available.html]
-skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 #[webgl-mochitest/test_webgl_color_buffer_float.html]
 # We haven't cleaned up the Try results yet, but let's get this on the books first.
 [webgl-mochitest/test_webgl_conformance.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl_compressed_texture_es3.html]
 [webgl-mochitest/test_webgl_disjoint_timer_query.html]
+[webgl-mochitest/test_webgl_force_enable.html]
 [webgl-mochitest/test_webgl_request_context.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl_request_mismatch.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl2_not_exposed.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
 [webgl-mochitest/test_webgl2_invalidate_framebuffer.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
--- a/dom/canvas/test/webgl-mochitest/test_webgl_available.html
+++ b/dom/canvas/test/webgl-mochitest/test_webgl_available.html
@@ -1,50 +1,19 @@
 <!DOCTYPE HTML>
 <html>
-<head>
-<title>WebGL test: Check that WebGL works (or not) if it should (or should not).</title>
-<script src="/tests/SimpleTest/SimpleTest.js"></script>
-<link rel="stylesheet" href="/tests/SimpleTest/test.css">
-<script src="webgl-util.js"></script>
-<script src="driver-info.js"></script>
-</head>
-<body>
-<canvas id="c"></canvas>
-<script>
-
-function test() {
-  ok(SpecialPowers.getBoolPref('webgl.force-enabled'), 'WebGL should be force-enabled.');
-
-  var shouldSucceed = true;
-  var shouldFail = false;
+  <head>
+    <meta charset='utf-8'/>
+    <title>WebGL test: Check that WebGL works out-of-the-box.</title>
+    <script src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+  </head>
+  <body>
+    <script>
 
-  if (DriverInfo.getOS() == DriverInfo.OS.ANDROID &&
-      DriverInfo.getOSVersion() < 15)
-  {
-    // Consider 'random'. Actually, ARMv6 fails, and ARMv7 succeeds, but we have
-    // not been successful at determining this from JS. (see bug 917478)
-    shouldSucceed = false;
-    shouldFail = false;
-  }
+'use strict';
+var c = document.createElement('canvas');
+var gl = c.getContext('experimental-webgl');
+ok(gl, 'Expected WebGL creation to succeed.');
 
-  var gl = WebGLUtil.getWebGL('c');
-  if (shouldSucceed) {
-    ok(gl, 'Expected WebGL creation to succeed.');
-  }
-  if (shouldFail) {
-    ok(!gl, 'Expected WebGL creation to fail.');
-  }
-
-  SimpleTest.finish();
-}
-
-SimpleTest.waitForExplicitFinish();
-
-var prefArrArr = [
-  ['webgl.force-enabled', true]
-];
-var prefEnv = {'set': prefArrArr};
-SpecialPowers.pushPrefEnv(prefEnv, test);
-
-</script>
-</body>
+    </script>
+  </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_webgl_force_enable.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebGL test: Check that WebGL works (or not) if it should (or should not).</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script src="webgl-util.js"></script>
+<script src="driver-info.js"></script>
+</head>
+<body>
+<canvas id="c"></canvas>
+<script>
+
+function test() {
+  ok(SpecialPowers.getBoolPref('webgl.force-enabled'), 'WebGL should be force-enabled.');
+
+  var shouldSucceed = true;
+  var shouldFail = false;
+
+  if (DriverInfo.getOS() == DriverInfo.OS.ANDROID &&
+      DriverInfo.getOSVersion() < 15)
+  {
+    // Consider 'random'. Actually, ARMv6 fails, and ARMv7 succeeds, but we have
+    // not been successful at determining this from JS. (see bug 917478)
+    shouldSucceed = false;
+    shouldFail = false;
+  }
+
+  var gl = WebGLUtil.getWebGL('c');
+  if (shouldSucceed) {
+    ok(gl, 'Expected WebGL creation to succeed.');
+  }
+  if (shouldFail) {
+    ok(!gl, 'Expected WebGL creation to fail.');
+  }
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+var prefArrArr = [
+  ['webgl.force-enabled', true]
+];
+var prefEnv = {'set': prefArrArr};
+SpecialPowers.pushPrefEnv(prefEnv, test);
+
+</script>
+</body>
+</html>
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -314,16 +314,18 @@ EventListenerManager::AddEventListenerIn
       // If aEventMessage is eLegacySubtreeModified, we need to listen all
       // mutations. nsContentUtils::HasMutationListeners relies on this.
       window->SetMutationListeners(
         (aEventMessage == eLegacySubtreeModified) ?
           kAllMutationBits : MutationBitForEventType(aEventMessage));
     }
   } else if (aTypeAtom == nsGkAtoms::ondeviceorientation) {
     EnableDevice(eDeviceOrientation);
+  } else if (aTypeAtom == nsGkAtoms::onabsolutedeviceorientation) {
+    EnableDevice(eAbsoluteDeviceOrientation);
   } else if (aTypeAtom == nsGkAtoms::ondeviceproximity || aTypeAtom == nsGkAtoms::onuserproximity) {
     EnableDevice(eDeviceProximity);
   } else if (aTypeAtom == nsGkAtoms::ondevicelight) {
     EnableDevice(eDeviceLight);
   } else if (aTypeAtom == nsGkAtoms::ondevicemotion) {
     EnableDevice(eDeviceMotion);
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   } else if (aTypeAtom == nsGkAtoms::onorientationchange) {
@@ -426,16 +428,17 @@ EventListenerManager::AddEventListenerIn
   }
 }
 
 bool
 EventListenerManager::IsDeviceType(EventMessage aEventMessage)
 {
   switch (aEventMessage) {
     case eDeviceOrientation:
+    case eAbsoluteDeviceOrientation:
     case eDeviceMotion:
     case eDeviceLight:
     case eDeviceProximity:
     case eUserProximity:
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
     case eOrientationChange:
 #endif
       return true;
@@ -451,16 +454,26 @@ EventListenerManager::EnableDevice(Event
   nsCOMPtr<nsPIDOMWindow> window = GetTargetAsInnerWindow();
   if (!window) {
     return;
   }
 
   switch (aEventMessage) {
     case eDeviceOrientation:
 #ifdef MOZ_WIDGET_ANDROID
+      // Falls back to SENSOR_ROTATION_VECTOR and SENSOR_ORIENTATION if
+      // unavailable on device.
+      window->EnableDeviceSensor(SENSOR_GAME_ROTATION_VECTOR);
+#else
+      window->EnableDeviceSensor(SENSOR_ORIENTATION);
+#endif
+      break;
+    case eAbsoluteDeviceOrientation:
+#ifdef MOZ_WIDGET_ANDROID
+      // Falls back to SENSOR_ORIENTATION if unavailable on device.
       window->EnableDeviceSensor(SENSOR_ROTATION_VECTOR);
 #else
       window->EnableDeviceSensor(SENSOR_ORIENTATION);
 #endif
       break;
     case eDeviceProximity:
     case eUserProximity:
       window->EnableDeviceSensor(SENSOR_PROXIMITY);
@@ -490,20 +503,27 @@ EventListenerManager::DisableDevice(Even
   nsCOMPtr<nsPIDOMWindow> window = GetTargetAsInnerWindow();
   if (!window) {
     return;
   }
 
   switch (aEventMessage) {
     case eDeviceOrientation:
 #ifdef MOZ_WIDGET_ANDROID
+      // Disable all potential fallback sensors.
+      window->DisableDeviceSensor(SENSOR_GAME_ROTATION_VECTOR);
       window->DisableDeviceSensor(SENSOR_ROTATION_VECTOR);
-#else
+#endif
       window->DisableDeviceSensor(SENSOR_ORIENTATION);
+      break;
+    case eAbsoluteDeviceOrientation:
+#ifdef MOZ_WIDGET_ANDROID
+      window->DisableDeviceSensor(SENSOR_ROTATION_VECTOR);
 #endif
+      window->DisableDeviceSensor(SENSOR_ORIENTATION);
       break;
     case eDeviceMotion:
       window->DisableDeviceSensor(SENSOR_ACCELERATION);
       window->DisableDeviceSensor(SENSOR_LINEAR_ACCELERATION);
       window->DisableDeviceSensor(SENSOR_GYROSCOPE);
       break;
     case eDeviceProximity:
     case eUserProximity:
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -542,16 +542,20 @@ WINDOW_EVENT(unload,
 WINDOW_ONLY_EVENT(devicemotion,
                   eDeviceMotion,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(deviceorientation,
                   eDeviceOrientation,
                   EventNameType_None,
                   eBasicEventClass)
+WINDOW_ONLY_EVENT(absolutedeviceorientation,
+                  eAbsoluteDeviceOrientation,
+                  EventNameType_None,
+                  eBasicEventClass)
 WINDOW_ONLY_EVENT(deviceproximity,
                   eDeviceProximity,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(userproximity,
                   eUserProximity,
                   EventNameType_None,
                   eBasicEventClass)
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -26,16 +26,17 @@
 #include "nsThreadUtils.h"
 #include "mozilla/HalWakeLock.h"
 #include "mozilla/Hal.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/WeakPtr.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/SettingChangeNotificationBinding.h"
 #include "mozilla/dom/WakeLock.h"
 
 #include "nsJSUtils.h"
 #include "prdtoa.h"
 
 class nsIPrincipal;
@@ -70,48 +71,67 @@ class nsIPrincipal;
 
 using mozilla::Unused;          // <snicker>
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
 class nsGeolocationRequest final
  : public nsIContentPermissionRequest
- , public nsITimerCallback
  , public nsIGeolocationUpdate
+ , public SupportsWeakPtr<nsGeolocationRequest>
 {
  public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSICONTENTPERMISSIONREQUEST
-  NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIGEOLOCATIONUPDATE
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGeolocationRequest, nsIContentPermissionRequest)
 
   nsGeolocationRequest(Geolocation* aLocator,
                        const GeoPositionCallback& aCallback,
                        const GeoPositionErrorCallback& aErrorCallback,
                        PositionOptions* aOptions,
                        uint8_t aProtocolType,
                        bool aWatchPositionRequest = false,
                        int32_t aWatchId = 0);
+
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsGeolocationRequest)
+
   void Shutdown();
 
   void SendLocation(nsIDOMGeoPosition* aLocation);
   bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
   void SetTimeoutTimer();
   void StopTimeoutTimer();
   void NotifyErrorAndShutdown(uint16_t);
   nsIPrincipal* GetPrincipal();
 
   bool IsWatch() { return mIsWatchPositionRequest; }
   int32_t WatchId() { return mWatchId; }
  private:
   virtual ~nsGeolocationRequest();
 
+  class TimerCallbackHolder final : public nsITimerCallback
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSITIMERCALLBACK
+
+    explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
+      : mRequest(aRequest)
+    {}
+
+  private:
+    ~TimerCallbackHolder() {}
+    WeakPtr<nsGeolocationRequest> mRequest;
+  };
+
+  void Notify();
+
   already_AddRefed<nsIDOMGeoPosition> AdjustedLocation(nsIDOMGeoPosition*);
 
   bool mIsWatchPositionRequest;
 
   nsCOMPtr<nsITimer> mTimeoutTimer;
   GeoPositionCallback mCallback;
   GeoPositionErrorCallback mErrorCallback;
   nsAutoPtr<PositionOptions> mOptions;
@@ -374,36 +394,35 @@ nsGeolocationRequest::nsGeolocationReque
     if (window) {
       mRequester = new nsContentPermissionRequester(window);
     }
   }
 }
 
 nsGeolocationRequest::~nsGeolocationRequest()
 {
+  StopTimeoutTimer();
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
-  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGeolocationRequest)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGeolocationRequest)
 
 NS_IMPL_CYCLE_COLLECTION(nsGeolocationRequest, mCallback, mErrorCallback, mLocator)
 
-NS_IMETHODIMP
-nsGeolocationRequest::Notify(nsITimer* aTimer)
+void
+nsGeolocationRequest::Notify()
 {
   StopTimeoutTimer();
   NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT);
-  return NS_OK;
 }
 
 void
 nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode)
 {
   MOZ_ASSERT(!mShutdown, "timeout after shutdown");
 
   if (!mIsWatchPositionRequest) {
@@ -556,17 +575,18 @@ nsGeolocationRequest::SetTimeoutTimer()
 
     if (timeout < 0) {
       timeout = 0;
     } else if (timeout < 10) {
       timeout = 10;
     }
 
     mTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
-    mTimeoutTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+    RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
+    mTimeoutTimer->InitWithCallback(holder, timeout, nsITimer::TYPE_ONE_SHOT);
   }
 }
 
 void
 nsGeolocationRequest::StopTimeoutTimer()
 {
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
@@ -736,31 +756,45 @@ nsGeolocationRequest::NotifyError(uint16
 }
 
 void
 nsGeolocationRequest::Shutdown()
 {
   MOZ_ASSERT(!mShutdown, "request shutdown twice");
   mShutdown = true;
 
-  if (mTimeoutTimer) {
-    mTimeoutTimer->Cancel();
-    mTimeoutTimer = nullptr;
-  }
+  StopTimeoutTimer();
 
   // If there are no other high accuracy requests, the geolocation service will
   // notify the provider to switch to the default accuracy.
   if (mOptions && mOptions->mEnableHighAccuracy) {
     RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
     if (gs) {
       gs->UpdateAccuracy();
     }
   }
 }
 
+
+////////////////////////////////////////////////////
+// nsGeolocationRequest::TimerCallbackHolder
+////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
+
+NS_IMETHODIMP
+nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
+{
+  if (mRequest) {
+    mRequest->Notify();
+  }
+  return NS_OK;
+}
+
+
 ////////////////////////////////////////////////////
 // nsGeolocationService
 ////////////////////////////////////////////////////
 NS_INTERFACE_MAP_BEGIN(nsGeolocationService)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate)
   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_cookiemanager.js
@@ -0,0 +1,20 @@
+let { classes: Cc, interfaces: Ci } = Components;
+addMessageListener("getCookieFromManager", ({ host, path }) => {
+  let cm = Cc["@mozilla.org/cookiemanager;1"]
+             .getService(Ci.nsICookieManager);
+  let values = [];
+  path = path.substring(0, path.lastIndexOf("/") + 1);
+  let e = cm.enumerator;
+  while (e.hasMoreElements()) {
+    let cookie = e.getNext().QueryInterface(Ci.nsICookie);
+    if (!cookie) {
+      break;
+    }
+    if (host != cookie.host || path != cookie.path) {
+      continue;
+    }
+    values.push(cookie.name + "=" + cookie.value);
+  }
+
+  sendAsyncMessage("getCookieFromManager:return", { cookie: values.join("; ") });
+});
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -577,17 +577,18 @@ skip-if = toolkit == 'android'
 [test_bug497242.xhtml]
 [test_bug499092.html]
 [test_bug512367.html]
 [test_bug677495.html]
 [test_bug677495-1.html]
 [test_bug741266.html]
 skip-if = buildapp == "mulet" || buildapp == "b2g" || toolkit == "android" || toolkit == "windows" || e10s # b2g(needs control of popup window size) b2g-debug(needs control of popup window size) b2g-desktop(needs control of popup window size) windows(bug 1234520)
 [test_non-ascii-cookie.html]
-skip-if = buildapp == 'b2g' || e10s
+skip-if = buildapp == 'b2g'
+support-files = file_cookiemanager.js
 [test_bug765780.html]
 [test_bug871161.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 support-files = file_bug871161-1.html file_bug871161-2.html
 [test_bug1013316.html]
 [test_hash_encoded.html]
 [test_bug1081037.html]
 [test_window_open_close.html]
--- a/dom/html/test/test_non-ascii-cookie.html
+++ b/dom/html/test/test_non-ascii-cookie.html
@@ -15,44 +15,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for non-ASCII cookie values **/
 
-var [Cc, Ci] = [SpecialPowers.Cc, SpecialPowers.Ci];
+SimpleTest.waitForExplicitFinish();
 
-var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("file_cookiemanager.js"));
+
 function getCookieFromManager() {
-  var values = [];
-  var host = location.hostname;
-  var path = location.pathname;
-  path = path.substring(0, path.lastIndexOf("/") + 1);
-  var e = cm.enumerator;
-  while (e.hasMoreElements()) {
-    var cookie = e.getNext().QueryInterface(Ci.nsICookie);
-    if (!cookie) {
-      break;
-    }
-    if (host != cookie.host || path != cookie.path) {
-      continue;
-    }
-    values.push(cookie.name + "=" + cookie.value);
-  }
-  return values.join("; ");
+  return new Promise(resolve => {
+    gScript.addMessageListener("getCookieFromManager:return", function gcfm({ cookie }) {
+      gScript.removeMessageListener("getCookieFromManager:return", gcfm);
+      resolve(cookie);
+    });
+    gScript.sendAsyncMessage("getCookieFromManager", { host: location.hostname, path: location.pathname });
+  });
 }
 
 var c = document.cookie;
 is(document.cookie, 'abc=012©ABC\ufffdDEF', "document.cookie should be decoded as UTF-8");
-is(getCookieFromManager(), document.cookie, "nsICookieManager should be consistent with document.cookie");
-var newCookie = 'def=∼≩≭≧∯≳≲≣∽≸≸∺≸∠≯≮≥≲≲≯≲∽≡≬≥≲≴∨∱∩∾';
-document.cookie = newCookie;
-is(document.cookie, c + '; ' + newCookie, "document.cookie should be encoded as UTF-8");
-is(getCookieFromManager(), document.cookie, "nsICookieManager should be consistent with document.cookie");
-var date1 = new Date();
-date1.setTime(0);
-document.cookie = newCookie + 'def=;expires=' + date1.toGMTString();
+
+var newCookie;
+
+getCookieFromManager().then((cookie) => {
+  is(cookie, document.cookie, "nsICookieManager should be consistent with document.cookie");
+  newCookie = 'def=∼≩≭≧∯≳≲≣∽≸≸∺≸∠≯≮≥≲≲≯≲∽≡≬≥≲≴∨∱∩∾';
+  document.cookie = newCookie;
+  is(document.cookie, c + '; ' + newCookie, "document.cookie should be encoded as UTF-8");
+
+  return getCookieFromManager();
+}).then((cookie) => {
+  is(cookie, document.cookie, "nsICookieManager should be consistent with document.cookie");
+  var date1 = new Date();
+  date1.setTime(0);
+  document.cookie = newCookie + 'def=;expires=' + date1.toGMTString();
+  gScript.destroy();
+  SimpleTest.finish();
+});
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -42,28 +42,28 @@ static int gDumpedAudioCount = 0;
  * with the playback rate at the moment. Since the playback rate and number of
  * underrun frames can vary in each callback. We need to keep the whole history
  * in order to calculate the playback position of the audio engine correctly.
  */
 class FrameHistory {
   struct Chunk {
     uint32_t servicedFrames;
     uint32_t totalFrames;
-    int rate;
+    uint32_t rate;
   };
 
   template <typename T>
   static T FramesToUs(uint32_t frames, int rate) {
     return static_cast<T>(frames) * USECS_PER_S / rate;
   }
 public:
   FrameHistory()
     : mBaseOffset(0), mBasePosition(0) {}
 
-  void Append(uint32_t aServiced, uint32_t aUnderrun, int aRate) {
+  void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
     /* In most case where playback rate stays the same and we don't underrun
      * frames, we are able to merge chunks to avoid lose of precision to add up
      * in compressing chunks into |mBaseOffset| and |mBasePosition|.
      */
     if (!mChunks.IsEmpty()) {
       Chunk& c = mChunks.LastElement();
       // 2 chunks (c1 and c2) can be merged when rate is the same and
       // adjacent frames are zero. That is, underrun frames in c1 are zero
@@ -294,23 +294,23 @@ WriteDumpFile(FILE* aDumpFile, AudioStre
   for (uint32_t i = 0; i < samples; ++i) {
     SetUint16LE(output + i*2, int16_t(input[i]*32767.0f));
   }
   fwrite(output, 2, samples, aDumpFile);
   fflush(aDumpFile);
 }
 
 nsresult
-AudioStream::Init(int32_t aNumChannels, int32_t aRate,
+AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
                   const dom::AudioChannel aAudioChannel)
 {
   mStartTime = TimeStamp::Now();
   mIsFirst = CubebUtils::GetFirstStream();
 
-  if (!CubebUtils::GetCubebContext() || aNumChannels < 0 || aRate < 0) {
+  if (!CubebUtils::GetCubebContext()) {
     return NS_ERROR_FAILURE;
   }
 
   MOZ_LOG(gAudioStreamLog, LogLevel::Debug,
     ("%s  channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this));
   mInRate = mOutRate = aRate;
   mChannels = aNumChannels;
   mOutChannels = (aNumChannels > 2) ? 2 : aNumChannels;
@@ -359,17 +359,18 @@ AudioStream::OpenCubeb(cubeb_stream_para
 
   // If the latency pref is set, use it. Otherwise, if this stream is intended
   // for low latency playback, try to get the lowest latency possible.
   // Otherwise, for normal streams, use 100ms.
   uint32_t latency = CubebUtils::GetCubebLatency();
 
   {
     cubeb_stream* stream;
-    if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams,
+    if (cubeb_stream_init(cubebContext, &stream, "AudioStream",
+                          nullptr, nullptr, nullptr, &aParams,
                           latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
       MonitorAutoLock mon(mMonitor);
       MOZ_ASSERT(mState != SHUTDOWN);
       mCubebStream.reset(stream);
     } else {
       MonitorAutoLock mon(mMonitor);
       mState = ERRORED;
       NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
@@ -529,28 +530,35 @@ AudioStream::GetPositionInFramesUnlocked
 bool
 AudioStream::IsPaused()
 {
   MonitorAutoLock mon(mMonitor);
   return mState == STOPPED;
 }
 
 bool
-AudioStream::Downmix(AudioDataValue* aBuffer, uint32_t aFrames)
+AudioStream::Downmix(Chunk* aChunk)
 {
-  if (mChannels > 8) {
+  if (aChunk->Rate() != mInRate) {
+    LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mInRate);
     return false;
   }
 
-  if (mChannels > 2 && mChannels <= 8) {
-    DownmixAudioToStereo(aBuffer, mChannels, aFrames);
+  if (aChunk->Channels() > 8) {
+    return false;
   }
 
-  if (mChannels >= 2 && mIsMonoAudioEnabled) {
-    DownmixStereoToMono(aBuffer, aFrames);
+  if (aChunk->Channels() > 2 && aChunk->Channels() <= 8) {
+    DownmixAudioToStereo(aChunk->GetWritable(),
+                         aChunk->Channels(),
+                         aChunk->Frames());
+  }
+
+  if (aChunk->Channels() >= 2 && mIsMonoAudioEnabled) {
+    DownmixStereoToMono(aChunk->GetWritable(), aChunk->Frames());
   }
 
   return true;
 }
 
 void
 AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
 {
@@ -571,17 +579,17 @@ AudioStream::GetUnprocessed(AudioBufferW
   }
 
   while (aWriter.Available() > 0) {
     UniquePtr<Chunk> c = mDataSource.PopFrames(aWriter.Available());
     if (c->Frames() == 0) {
       break;
     }
     MOZ_ASSERT(c->Frames() <= aWriter.Available());
-    if (Downmix(c->GetWritable(), c->Frames())) {
+    if (Downmix(c.get())) {
       aWriter.Write(c->Data(), c->Frames());
     } else {
       // Write silence if downmixing fails.
       aWriter.WriteZeros(c->Frames());
     }
   }
 }
 
@@ -599,17 +607,17 @@ AudioStream::GetTimeStretched(AudioBuffe
   uint32_t toPopFrames = ceil(aWriter.Available() * playbackRate);
 
   while (mTimeStretcher->numSamples() < aWriter.Available()) {
     UniquePtr<Chunk> c = mDataSource.PopFrames(toPopFrames);
     if (c->Frames() == 0) {
       break;
     }
     MOZ_ASSERT(c->Frames() <= toPopFrames);
-    if (Downmix(c->GetWritable(), c->Frames())) {
+    if (Downmix(c.get())) {
       mTimeStretcher->putSamples(c->Data(), c->Frames());
     } else {
       // Write silence if downmixing fails.
       nsAutoTArray<AudioDataValue, 1000> buf;
       buf.SetLength(mOutChannels * c->Frames());
       memset(buf.Elements(), 0, buf.Length() * sizeof(AudioDataValue));
       mTimeStretcher->putSamples(buf.Elements(), c->Frames());
     }
@@ -715,17 +723,17 @@ int64_t AudioClock::GetPositionUnlocked(
 
 int64_t AudioClock::GetPositionInFrames() const
 {
   return (GetPositionUnlocked() * mInRate) / USECS_PER_S;
 }
 
 void AudioClock::SetPlaybackRateUnlocked(double aPlaybackRate)
 {
-  mOutRate = static_cast<int>(mInRate / aPlaybackRate);
+  mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
 }
 
 double AudioClock::GetPlaybackRate() const
 {
   return static_cast<double>(mInRate) / mOutRate;
 }
 
 void AudioClock::SetPreservesPitch(bool aPreservesPitch)
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -62,19 +62,19 @@ public:
   // Get the current pitch preservation state.
   // Called on the audio thread.
   bool GetPreservesPitch() const;
 private:
   // This AudioStream holds a strong reference to this AudioClock. This
   // pointer is garanteed to always be valid.
   AudioStream* const mAudioStream;
   // Output rate in Hz (characteristic of the playback rate)
-  int mOutRate;
+  uint32_t mOutRate;
   // Input rate in Hz (characteristic of the media being played)
-  int mInRate;
+  uint32_t mInRate;
   // True if the we are timestretching, false if we are resampling.
   bool mPreservesPitch;
   // The history of frames sent to the audio engine in each DataCallback.
   const nsAutoPtr<FrameHistory> mFrameHistory;
 };
 
 class CircularByteBuffer
 {
@@ -220,16 +220,20 @@ public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioStream)
 
   class Chunk {
   public:
     // Return a pointer to the audio data.
     virtual const AudioDataValue* Data() const = 0;
     // Return the number of frames in this chunk.
     virtual uint32_t Frames() const = 0;
+    // Return the number of audio channels.
+    virtual uint32_t Channels() const = 0;
+    // Return the sample rate of this chunk.
+    virtual uint32_t Rate() const = 0;
     // Return a writable pointer for downmixing.
     virtual AudioDataValue* GetWritable() const = 0;
     virtual ~Chunk() {}
   };
 
   class DataSource {
   public:
     // Return a chunk which contains at most aFrames frames or zero if no
@@ -243,17 +247,17 @@ public:
     virtual ~DataSource() {}
   };
 
   explicit AudioStream(DataSource& aSource);
 
   // Initialize the audio stream. aNumChannels is the number of audio
   // channels (1 for mono, 2 for stereo, etc) and aRate is the sample rate
   // (22050Hz, 44100Hz, etc).
-  nsresult Init(int32_t aNumChannels, int32_t aRate,
+  nsresult Init(uint32_t aNumChannels, uint32_t aRate,
                 const dom::AudioChannel aAudioStreamChannel);
 
   // Closes the stream. All future use of the stream is an error.
   void Shutdown();
 
   void Reset();
 
   // Set the current volume of the audio playback. This is a value from
@@ -275,19 +279,19 @@ public:
 
   // Return the position, measured in audio frames played since the stream
   // was opened, of the audio hardware.  Thread-safe.
   int64_t GetPositionInFrames();
 
   // Returns true when the audio stream is paused.
   bool IsPaused();
 
-  int GetRate() { return mOutRate; }
-  int GetChannels() { return mChannels; }
-  int GetOutChannels() { return mOutChannels; }
+  uint32_t GetRate() { return mOutRate; }
+  uint32_t GetChannels() { return mChannels; }
+  uint32_t GetOutChannels() { return mOutChannels; }
 
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
   nsresult SetPreservesPitch(bool aPreservesPitch);
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
@@ -299,49 +303,51 @@ protected:
   // opened, of the audio hardware, not adjusted for the changes of playback
   // rate or underrun frames.
   // Caller must own the monitor.
   int64_t GetPositionInFramesUnlocked();
 
 private:
   nsresult OpenCubeb(cubeb_stream_params &aParams);
 
-  static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
+  static long DataCallback_S(cubeb_stream*, void* aThis,
+                             const void* /* aInputBuffer */, void* aOutputBuffer,
+                             long aFrames)
   {
-    return static_cast<AudioStream*>(aThis)->DataCallback(aBuffer, aFrames);
+    return static_cast<AudioStream*>(aThis)->DataCallback(aOutputBuffer, aFrames);
   }
 
   static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
   {
     static_cast<AudioStream*>(aThis)->StateCallback(aState);
   }
 
 
   long DataCallback(void* aBuffer, long aFrames);
   void StateCallback(cubeb_state aState);
 
   nsresult EnsureTimeStretcherInitializedUnlocked();
 
   // Return true if downmixing succeeds otherwise false.
-  bool Downmix(AudioDataValue* aBuffer, uint32_t aFrames);
+  bool Downmix(Chunk* aChunk);
 
   void GetUnprocessed(AudioBufferWriter& aWriter);
   void GetTimeStretched(AudioBufferWriter& aWriter);
 
   void StartUnlocked();
 
   // The monitor is held to protect all access to member variables.
   Monitor mMonitor;
 
   // Input rate in Hz (characteristic of the media being played)
-  int mInRate;
+  uint32_t mInRate;
   // Output rate in Hz (characteristic of the playback rate)
-  int mOutRate;
-  int mChannels;
-  int mOutChannels;
+  uint32_t mOutRate;
+  uint32_t mChannels;
+  uint32_t mOutChannels;
 #if defined(__ANDROID__)
   dom::AudioChannel mAudioChannel;
 #endif
   AudioClock mAudioClock;
   soundtouch::SoundTouch* mTimeStretcher;
 
   // Stream start time for stream open delay telemetry.
   TimeStamp mStartTime;
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -8,16 +8,18 @@
 #define CubebUtils_h_
 
 #include "cubeb/cubeb.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 
 namespace mozilla {
 namespace CubebUtils {
 
+typedef cubeb_devid AudioDeviceID;
+
 // Initialize Audio Library. Some Audio backends require initializing the
 // library before using it.
 void InitLibrary();
 
 // Shutdown Audio Library. Some Audio backends require shutting down the
 // library after using it.
 void ShutdownLibrary();
 
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; 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/. */
 
 #include <MediaStreamGraphImpl.h>
 #include "mozilla/dom/AudioContext.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "CubebUtils.h"
 
 #ifdef XP_MACOSX
 #include <sys/sysctl.h>
 #endif
 
 extern mozilla::LazyLogModule gMediaStreamGraphLog;
 #define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
@@ -25,16 +27,18 @@ extern mozilla::LazyLogModule gMediaStre
 #define LIFECYCLE_LOG(...) printf(__VA_ARGS__);printf("\n");
 #endif
 #else
 #define LIFECYCLE_LOG(...)
 #endif
 
 namespace mozilla {
 
+StaticRefPtr<nsIThreadPool> AsyncCubebTask::sThreadPool;
+
 struct AutoProfilerUnregisterThread
 {
   // The empty ctor is used to silence a pre-4.8.0 GCC unused variable warning.
   AutoProfilerUnregisterThread()
   {
   }
 
   ~AutoProfilerUnregisterThread()
@@ -43,16 +47,17 @@ struct AutoProfilerUnregisterThread
   }
 };
 
 GraphDriver::GraphDriver(MediaStreamGraphImpl* aGraphImpl)
   : mIterationStart(0),
     mIterationEnd(0),
     mGraphImpl(aGraphImpl),
     mWaitState(WAITSTATE_RUNNING),
+    mAudioInput(nullptr),
     mCurrentTimeStamp(TimeStamp::Now()),
     mPreviousDriver(nullptr),
     mNextDriver(nullptr)
 { }
 
 void GraphDriver::SetGraphTime(GraphDriver* aPreviousDriver,
                                GraphTime aLastSwitchNextIterationStart,
                                GraphTime aLastSwitchNextIterationEnd)
@@ -60,18 +65,24 @@ void GraphDriver::SetGraphTime(GraphDriv
   GraphImpl()->GetMonitor().AssertCurrentThreadOwns();
   // We set mIterationEnd here, because the first thing a driver do when it
   // does an iteration is to update graph times, so we are in fact setting
   // mIterationStart of the next iteration by setting the end of the previous
   // iteration.
   mIterationStart = aLastSwitchNextIterationStart;
   mIterationEnd = aLastSwitchNextIterationEnd;
 
-  STREAM_LOG(LogLevel::Debug, ("Setting previous driver: %p (%s)", PreviousDriver(), PreviousDriver()->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver"));
   MOZ_ASSERT(!PreviousDriver());
+  MOZ_ASSERT(aPreviousDriver);
+
+  STREAM_LOG(LogLevel::Debug, ("Setting previous driver: %p (%s)",
+                               aPreviousDriver,
+                               aPreviousDriver->AsAudioCallbackDriver()
+                                 ? "AudioCallbackDriver"
+                                 : "SystemClockDriver"));
   SetPreviousDriver(aPreviousDriver);
 }
 
 void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver)
 {
   GraphImpl()->GetMonitor().AssertCurrentThreadOwns();
   // This is the situation where `mPreviousDriver` is an AudioCallbackDriver
   // that is switching device, and the graph has found the current driver is not
@@ -83,61 +94,36 @@ void GraphDriver::SwitchAtNextIteration(
       PreviousDriver() &&
       PreviousDriver()->AsAudioCallbackDriver()->IsSwitchingDevice() &&
       PreviousDriver() != aNextDriver) {
     return;
   }
   LIFECYCLE_LOG("Switching to new driver: %p (%s)",
       aNextDriver, aNextDriver->AsAudioCallbackDriver() ?
       "AudioCallbackDriver" : "SystemClockDriver");
+  if (mNextDriver &&
+      mNextDriver != GraphImpl()->CurrentDriver()) {
+    LIFECYCLE_LOG("Discarding previous next driver: %p (%s)",
+                  mNextDriver.get(), mNextDriver->AsAudioCallbackDriver() ?
+                  "AudioCallbackDriver" : "SystemClockDriver");
+  }
   SetNextDriver(aNextDriver);
 }
 
 GraphTime
 GraphDriver::StateComputedTime() const
 {
   return mGraphImpl->mStateComputedTime;
 }
 
 void GraphDriver::EnsureNextIteration()
 {
   mGraphImpl->EnsureNextIteration();
 }
 
-class MediaStreamGraphShutdownThreadRunnable : public nsRunnable {
-public:
-  explicit MediaStreamGraphShutdownThreadRunnable(GraphDriver* aDriver)
-    : mDriver(aDriver)
-  {
-  }
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    LIFECYCLE_LOG("MediaStreamGraphShutdownThreadRunnable for graph %p",
-        mDriver->GraphImpl());
-    // We can't release an audio driver on the main thread, because it can be
-    // blocking.
-    if (mDriver->AsAudioCallbackDriver()) {
-      LIFECYCLE_LOG("Releasing audio driver off main thread.");
-      RefPtr<AsyncCubebTask> releaseEvent =
-        new AsyncCubebTask(mDriver->AsAudioCallbackDriver(),
-                           AsyncCubebOperation::SHUTDOWN);
-      mDriver = nullptr;
-      releaseEvent->Dispatch();
-    } else {
-      LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver.");
-      mDriver = nullptr;
-    }
-    return NS_OK;
-  }
-private:
-  RefPtr<GraphDriver> mDriver;
-};
-
 void GraphDriver::Shutdown()
 {
   if (AsAudioCallbackDriver()) {
     LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n");
     RefPtr<AsyncCubebTask> releaseEvent =
       new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN);
     releaseEvent->Dispatch();
   } else {
@@ -266,16 +252,21 @@ ThreadedDriver::Revive()
     NextDriver()->Start();
   } else {
     nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this);
     mThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
 }
 
 void
+ThreadedDriver::RemoveCallback()
+{
+}
+
+void
 ThreadedDriver::Stop()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
   // mGraph's thread is not running so it's OK to do whatever here
   STREAM_LOG(LogLevel::Debug, ("Stopping threads for MediaStreamGraph %p", this));
 
   if (mThread) {
     mThread->Shutdown();
@@ -332,16 +323,17 @@ ThreadedDriver::RunThread()
                (long)mIterationStart, (long)mIterationEnd,
                (long)stateComputedTime, (long)nextStateComputedTime));
 
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
 
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     if (NextDriver() && stillProcessing) {
       STREAM_LOG(LogLevel::Debug, ("Switching to AudioCallbackDriver"));
+      RemoveCallback();
       NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
       mGraphImpl->SetCurrentDriver(NextDriver());
       NextDriver()->Start();
       return;
     }
   }
 }
 
@@ -416,19 +408,19 @@ void SystemClockDriver::WakeUp()
 
 OfflineClockDriver::OfflineClockDriver(MediaStreamGraphImpl* aGraphImpl, GraphTime aSlice)
   : ThreadedDriver(aGraphImpl),
     mSlice(aSlice)
 {
 
 }
 
-class MediaStreamGraphShutdownThreadRunnable2 : public nsRunnable {
+class MediaStreamGraphShutdownThreadRunnable : public nsRunnable {
 public:
-  explicit MediaStreamGraphShutdownThreadRunnable2(nsIThread* aThread)
+  explicit MediaStreamGraphShutdownThreadRunnable(nsIThread* aThread)
     : mThread(aThread)
   {
   }
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mThread);
 
@@ -440,17 +432,17 @@ private:
   nsCOMPtr<nsIThread> mThread;
 };
 
 OfflineClockDriver::~OfflineClockDriver()
 {
   // transfer the ownership of mThread to the event
   // XXX should use .forget()/etc
   if (mThread) {
-    nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutdownThreadRunnable2(mThread);
+    nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutdownThreadRunnable(mThread);
     mThread = nullptr;
     NS_DispatchToMainThread(event);
   }
 }
 
 MediaTime
 OfflineClockDriver::GetIntervalForIteration()
 {
@@ -476,56 +468,73 @@ AsyncCubebTask::AsyncCubebTask(AudioCall
 {
   NS_WARN_IF_FALSE(mDriver->mAudioStream || aOperation == INIT, "No audio stream !");
 }
 
 AsyncCubebTask::~AsyncCubebTask()
 {
 }
 
+/* static */
+nsresult
+AsyncCubebTask::EnsureThread()
+{
+  if (!sThreadPool) {
+    nsCOMPtr<nsIThreadPool> threadPool =
+      SharedThreadPool::Get(NS_LITERAL_CSTRING("CubebOperation"), 1);
+    sThreadPool = threadPool;
+    // Need to null this out before xpcom-shutdown-threads Observers run
+    // since we don't know the order that the shutdown-threads observers
+    // will run.  ClearOnShutdown guarantees it runs first.
+    if (!NS_IsMainThread()) {
+      NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+            ClearOnShutdown(&sThreadPool, ShutdownPhase::ShutdownThreads);
+      }));
+    } else {
+      ClearOnShutdown(&sThreadPool, ShutdownPhase::ShutdownThreads);
+    }
+
+    const uint32_t kIdleThreadTimeoutMs = 2000;
+
+    nsresult rv = sThreadPool->SetIdleThreadTimeout(PR_MillisecondsToInterval(kIdleThreadTimeoutMs));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 AsyncCubebTask::Run()
 {
-  MOZ_ASSERT(mThread);
-  if (NS_IsMainThread()) {
-    mThread->Shutdown(); // can't shutdown from the thread itself, darn
-    // don't null out mThread!
-    // See bug 999104.  we must hold a ref to the thread across Dispatch()
-    // since the internal mthread ref could be released while processing
-    // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it
-    // assumes the caller does.
-    return NS_OK;
-  }
-
   MOZ_ASSERT(mDriver);
 
   switch(mOperation) {
     case AsyncCubebOperation::INIT: {
-      LIFECYCLE_LOG("AsyncCubebOperation::INIT\n");
+      LIFECYCLE_LOG("AsyncCubebOperation::INIT driver=%p\n", mDriver.get());
       mDriver->Init();
       mDriver->CompleteAudioContextOperations(mOperation);
       break;
     }
     case AsyncCubebOperation::SHUTDOWN: {
-      LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n");
+      LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN driver=%p\n", mDriver.get());
       mDriver->Stop();
 
       mDriver->CompleteAudioContextOperations(mOperation);
 
       mDriver = nullptr;
       mShutdownGrip = nullptr;
       break;
     }
     default:
       MOZ_CRASH("Operation not implemented.");
   }
 
-  // and now kill this thread
-  NS_DispatchToMainThread(this);
-
+  // The thread will kill itself after a bit
   return NS_OK;
 }
 
 StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream,
                                           void* aPromise,
                                           dom::AudioContextOperation aOperation)
   : mStream(aStream)
   , mPromise(aPromise)
@@ -534,17 +543,19 @@ StreamAndPromiseForOperation::StreamAndP
   // MOZ_ASSERT(aPromise);
 }
 
 AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl)
   : GraphDriver(aGraphImpl)
   , mSampleRate(0)
   , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
   , mStarted(false)
+  , mAudioInput(nullptr)
   , mAudioChannel(aGraphImpl->AudioChannel())
+  , mAddedMixer(false)
   , mInCallback(false)
   , mMicrophoneActive(false)
 #ifdef XP_MACOSX
   , mCallbackReceivedWhileSwitching(0)
 #endif
 {
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver ctor for graph %p", aGraphImpl));
 }
@@ -552,64 +563,73 @@ AudioCallbackDriver::AudioCallbackDriver
 AudioCallbackDriver::~AudioCallbackDriver()
 {
   MOZ_ASSERT(mPromisesForOperation.IsEmpty());
 }
 
 void
 AudioCallbackDriver::Init()
 {
-  cubeb_stream_params params;
+  cubeb_stream_params output;
+  cubeb_stream_params input;
   uint32_t latency;
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
-  mSampleRate = params.rate = CubebUtils::PreferredSampleRate();
+  mSampleRate = output.rate = CubebUtils::PreferredSampleRate();
 
 #if defined(__ANDROID__)
 #if defined(MOZ_B2G)
-  params.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel);
+  output.stream_type = CubebUtils::ConvertChannelToCubebType(mAudioChannel);
 #else
-  params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
+  output.stream_type = CUBEB_STREAM_TYPE_MUSIC;
 #endif
-  if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
+  if (output.stream_type == CUBEB_STREAM_TYPE_MAX) {
     NS_WARNING("Bad stream type");
     return;
   }
 #else
   (void)mAudioChannel;
 #endif
 
-  params.channels = mGraphImpl->AudioChannelCount();
+  output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
-    params.format = CUBEB_SAMPLE_S16NE;
+    output.format = CUBEB_SAMPLE_S16NE;
   } else {
-    params.format = CUBEB_SAMPLE_FLOAT32NE;
+    output.format = CUBEB_SAMPLE_FLOAT32NE;
   }
 
-  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), params, &latency) != CUBEB_OK) {
+  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency) != CUBEB_OK) {
     NS_WARNING("Could not get minimal latency from cubeb.");
     return;
   }
 
+  input = output;
+  input.channels = 1; // change to support optional stereo capture
+
   cubeb_stream* stream;
+  // XXX Only pass input input if we have an input listener.  Always
+  // set up output because it's easier, and it will just get silence.
+  // XXX Add support for adding/removing an input listener later.
   if (cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
-                        "AudioCallbackDriver", params, latency,
+                        "AudioCallbackDriver",
+                        mGraphImpl->mInputDeviceID,
+                        mGraphImpl->mInputWanted ? &input : nullptr,
+                        mGraphImpl->mOutputDeviceID,
+                        mGraphImpl->mOutputWanted ? &output : nullptr, latency,
                         DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
     mAudioStream.own(stream);
   } else {
     NS_WARNING("Could not create a cubeb stream for MediaStreamGraph, falling back to a SystemClockDriver");
     // Fall back to a driver using a normal thread.
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     SetNextDriver(new SystemClockDriver(GraphImpl()));
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
-    DebugOnly<bool> found = mGraphImpl->RemoveMixerCallback(this);
-    NS_WARN_IF_FALSE(!found, "Mixer callback not added when switching?");
     NextDriver()->Start();
     return;
   }
 
   cubeb_stream_register_device_changed_callback(mAudioStream,
                                                 AudioCallbackDriver::DeviceChangedCallback_s);
 
   StartStream();
@@ -632,45 +652,35 @@ AudioCallbackDriver::Resume()
   if (cubeb_stream_start(mAudioStream) != CUBEB_OK) {
     NS_WARNING("Could not start cubeb stream for MSG.");
   }
 }
 
 void
 AudioCallbackDriver::Start()
 {
-  // If this is running on the main thread, we can't open the stream directly,
-  // because it is a blocking operation.
-  if (NS_IsMainThread()) {
-    STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl));
-    RefPtr<AsyncCubebTask> initEvent =
-      new AsyncCubebTask(this, AsyncCubebOperation::INIT);
-    initEvent->Dispatch();
-  } else {
-    STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from the previous driver's thread", mGraphImpl));
-    {
-      MonitorAutoUnlock mon(GraphImpl()->GetMonitor());
-      Init();
-    }
-
-    // Check if we need to resolve promises because the driver just got switched
-    // because of a resuming AudioContext
-    if (!mPromisesForOperation.IsEmpty()) {
-      // CompleteAudioContextOperations takes the lock as needed
-      MonitorAutoUnlock mon(GraphImpl()->GetMonitor());
-      CompleteAudioContextOperations(AsyncCubebOperation::INIT);
-    }
-
-    if (PreviousDriver()) {
-      nsCOMPtr<nsIRunnable> event =
-        new MediaStreamGraphShutdownThreadRunnable(PreviousDriver());
-      SetPreviousDriver(nullptr);
-      NS_DispatchToMainThread(event);
+  if (mPreviousDriver) {
+    if (mPreviousDriver->AsAudioCallbackDriver()) {
+      LIFECYCLE_LOG("Releasing audio driver off main thread.");
+      RefPtr<AsyncCubebTask> releaseEvent =
+        new AsyncCubebTask(mPreviousDriver->AsAudioCallbackDriver(),
+                           AsyncCubebOperation::SHUTDOWN);
+      releaseEvent->Dispatch();
+      mPreviousDriver = nullptr;
+    } else {
+      LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver.");
+      mPreviousDriver = nullptr;
     }
   }
+
+  LIFECYCLE_LOG("Starting new audio driver off main thread, "
+                "to ensure it runs after previous shutdown.");
+  RefPtr<AsyncCubebTask> initEvent =
+    new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::INIT);
+  initEvent->Dispatch();
 }
 
 void
 AudioCallbackDriver::StartStream()
 {
   if (cubeb_stream_start(mAudioStream) != CUBEB_OK) {
     MOZ_CRASH("Could not start cubeb stream for MSG.");
   }
@@ -694,45 +704,59 @@ void
 AudioCallbackDriver::Revive()
 {
   // Note: only called on MainThread, without monitor
   // We know were weren't in a running state
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver reviving."));
   // If we were switching, switch now. Otherwise, start the audio thread again.
   MonitorAutoLock mon(mGraphImpl->GetMonitor());
   if (NextDriver()) {
+    RemoveCallback();
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
     NextDriver()->Start();
   } else {
     STREAM_LOG(LogLevel::Debug, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl));
     RefPtr<AsyncCubebTask> initEvent =
       new AsyncCubebTask(this, AsyncCubebOperation::INIT);
     initEvent->Dispatch();
   }
 }
 
-void AudioCallbackDriver::WaitForNextIteration()
+void
+AudioCallbackDriver::RemoveCallback()
+{
+  if (mAddedMixer) {
+    mGraphImpl->mMixer.RemoveCallback(this);
+    mAddedMixer = false;
+  }
+}
+
+void
+AudioCallbackDriver::WaitForNextIteration()
 {
 }
 
 void
 AudioCallbackDriver::WakeUp()
 {
   mGraphImpl->GetMonitor().AssertCurrentThreadOwns();
   mGraphImpl->GetMonitor().Notify();
 }
 
 /* static */ long
 AudioCallbackDriver::DataCallback_s(cubeb_stream* aStream,
-                                    void* aUser, void* aBuffer,
+                                    void* aUser,
+                                    const void* aInputBuffer,
+                                    void* aOutputBuffer,
                                     long aFrames)
 {
   AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
-  return driver->DataCallback(static_cast<AudioDataValue*>(aBuffer), aFrames);
+  return driver->DataCallback(static_cast<const AudioDataValue*>(aInputBuffer),
+                              static_cast<AudioDataValue*>(aOutputBuffer), aFrames);
 }
 
 /* static */ void
 AudioCallbackDriver::StateCallback_s(cubeb_stream* aStream, void * aUser,
                                      cubeb_state aState)
 {
   AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser);
   driver->StateCallback(aState);
@@ -789,23 +813,30 @@ AudioCallbackDriver::OSXDeviceSwitchingW
     return true;
   }
 
   return false;
 }
 #endif // XP_MACOSX
 
 long
-AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames)
+AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
+                                  AudioDataValue* aOutputBuffer, long aFrames)
 {
   bool stillProcessing;
 
+  // Don't add the callback until we're inited and ready
+  if (!mAddedMixer) {
+    mGraphImpl->mMixer.AddCallback(this);
+    mAddedMixer = true;
+  }
+
 #ifdef XP_MACOSX
   if (OSXDeviceSwitchingWorkaround()) {
-    PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount());
+    PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount());
     return aFrames;
   }
 #endif
 
 #ifdef DEBUG
   // DebugOnly<> doesn't work here... it forces an initialization that will cause
   // mInCallback to be set back to false before we exit the statement.  Do it by
   // hand instead.
@@ -815,34 +846,34 @@ AudioCallbackDriver::DataCallback(AudioD
   GraphTime stateComputedTime = StateComputedTime();
   if (stateComputedTime == 0) {
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     // Because this function is called during cubeb_stream_init (to prefill the
     // audio buffers), it can be that we don't have a message here (because this
     // driver is the first one for this graph), and the graph would exit. Simply
     // return here until we have messages.
     if (!mGraphImpl->MessagesQueued()) {
-      PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount());
+      PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount());
       return aFrames;
     }
     mGraphImpl->SwapMessageQueues();
   }
 
   uint32_t durationMS = aFrames * 1000 / mSampleRate;
 
   // For now, simply average the duration with the previous
   // duration so there is some damping against sudden changes.
   if (!mIterationDurationMS) {
     mIterationDurationMS = durationMS;
   } else {
     mIterationDurationMS = (mIterationDurationMS*3) + durationMS;
     mIterationDurationMS /= 4;
   }
 
-  mBuffer.SetBuffer(aBuffer, aFrames);
+  mBuffer.SetBuffer(aOutputBuffer, aFrames);
   // fill part or all with leftover data from last iteration (since we
   // align to Audio blocks)
   mScratchBuffer.Empty(mBuffer);
   // if we totally filled the buffer (and mScratchBuffer isn't empty),
   // we don't need to run an iteration and if we do so we may overflow.
   if (mBuffer.Available()) {
 
     // State computed time is decided by the audio callback's buffer length. We
@@ -859,51 +890,69 @@ AudioCallbackDriver::DataCallback(AudioD
     // We want the interval [mIterationStart; mIterationEnd] to be before the
     // interval [stateComputedTime; nextStateComputedTime]. We also want
     // the distance between these intervals to be roughly equivalent each time, to
     // ensure there is no clock drift between current time and state time. Since
     // we can't act on the state time because we have to fill the audio buffer, we
     // reclock the current time against the state time, here.
     mIterationEnd = mIterationStart + 0.8 * inGraph;
 
-    STREAM_LOG(LogLevel::Debug, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n",
-                              (long)mIterationStart, (long)mIterationEnd,
-                              (long)stateComputedTime, (long)nextStateComputedTime,
-                              (long)aFrames, (uint32_t)durationMS,
-                              (long)(nextStateComputedTime - stateComputedTime)));
+    STREAM_LOG(LogLevel::Verbose, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n",
+                                   (long)mIterationStart, (long)mIterationEnd,
+                                   (long)stateComputedTime, (long)nextStateComputedTime,
+                                   (long)aFrames, (uint32_t)durationMS,
+                                   (long)(nextStateComputedTime - stateComputedTime)));
 
     mCurrentTimeStamp = TimeStamp::Now();
 
     if (stateComputedTime < mIterationEnd) {
       STREAM_LOG(LogLevel::Warning, ("Media graph global underrun detected"));
       mIterationEnd = stateComputedTime;
     }
 
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
   } else {
-    NS_WARNING("DataCallback buffer filled entirely from scratch buffer, skipping iteration.");
+    STREAM_LOG(LogLevel::Verbose, ("DataCallback buffer filled entirely from scratch buffer, skipping iteration."));
     stillProcessing = true;
   }
 
   mBuffer.BufferFilled();
 
+  // Callback any observers for the AEC speaker data.  Note that one
+  // (maybe) of these will be full-duplex, the others will get their input
+  // data off separate cubeb callbacks.  Take care with how stuff is
+  // removed/added to this list and TSAN issues, but input and output will
+  // use separate callback methods.
+  mGraphImpl->NotifyOutputData(aOutputBuffer, static_cast<size_t>(aFrames),
+                               ChannelCount);
+
+  // Process mic data if any/needed -- after inserting far-end data for AEC!
+  if (aInputBuffer) {
+    if (mAudioInput) { // for this specific input-only or full-duplex stream
+      mAudioInput->NotifyInputData(mGraphImpl, aInputBuffer,
+                                   static_cast<size_t>(aFrames),
+                                   ChannelCount);
+    }
+  }
+
   bool switching = false;
   {
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     switching = !!NextDriver();
   }
 
   if (switching && stillProcessing) {
     // If the audio stream has not been started by the previous driver or
     // the graph itself, keep it alive.
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     if (!IsStarted()) {
       return aFrames;
     }
     STREAM_LOG(LogLevel::Debug, ("Switching to system driver."));
+    RemoveCallback();
     NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd);
     mGraphImpl->SetCurrentDriver(NextDriver());
     NextDriver()->Start();
     // Returning less than aFrames starts the draining and eventually stops the
     // audio thread. This function will never get called again.
     return aFrames - 1;
   }
 
@@ -1000,16 +1049,17 @@ AudioCallbackDriver::DeviceChangedCallba
 
   if (mSelfReference) {
     return;
   }
   STREAM_LOG(LogLevel::Error, ("Switching to SystemClockDriver during output switch"));
   mSelfReference.Take(this);
   mCallbackReceivedWhileSwitching = 0;
   SetNextDriver(new SystemClockDriver(GraphImpl()));
+  RemoveCallback();
   mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd);
   mGraphImpl->SetCurrentDriver(mNextDriver);
   mNextDriver->Start();
 #endif
 }
 
 void
 AudioCallbackDriver::SetMicrophoneActive(bool aActive)
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -8,16 +8,18 @@
 
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "AudioBufferUtils.h"
 #include "AudioMixer.h"
 #include "AudioSegment.h"
 #include "SelfRef.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StaticPtr.h"
 
 struct cubeb_stream;
 
 template <>
 class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream>
 {
 public:
   static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
@@ -117,16 +119,18 @@ public:
   /* Start the graph, init the driver, start the thread. */
   virtual void Start() = 0;
   /* Stop the graph, shutting down the thread. */
   virtual void Stop() = 0;
   /* Resume after a stop */
   virtual void Resume() = 0;
   /* Revive this driver, as more messages just arrived. */
   virtual void Revive() = 0;
+  /* Remove Mixer callbacks when switching */
+  virtual void RemoveCallback() = 0;
   void Shutdown();
   /* Rate at which the GraphDriver runs, in ms. This can either be user
    * controlled (because we are using a {System,Offline}ClockDriver, and decide
    * how often we want to wakeup/how much we want to process per iteration), or
    * it can be indirectly set by the latency of the audio backend, and the
    * number of buffers of this audio backend: say we have four buffers, and 40ms
    * latency, we will get a callback approximately every 10ms. */
   virtual uint32_t IterationDuration() = 0;
@@ -148,20 +152,16 @@ public:
   virtual TimeStamp GetCurrentTimeStamp() {
     return mCurrentTimeStamp;
   }
 
   GraphTime IterationEnd() {
     return mIterationEnd;
   }
 
-  virtual void GetAudioBuffer(float** aBuffer, long& aFrames) {
-    MOZ_CRASH("This is not an Audio GraphDriver!");
-  }
-
   virtual AudioCallbackDriver* AsAudioCallbackDriver() {
     return nullptr;
   }
 
   virtual OfflineClockDriver* AsOfflineClockDriver() {
     return nullptr;
   }
 
@@ -191,16 +191,25 @@ public:
   void EnsureNextIterationLocked();
 
   MediaStreamGraphImpl* GraphImpl() {
     return mGraphImpl;
   }
 
   virtual bool OnThread() = 0;
 
+  // These are invoked on the MSG thread (or MainThread in shutdown)
+  virtual void SetInputListener(AudioDataListener *aListener) {
+    mAudioInput = aListener;
+  }
+  // XXX do we need the param?  probably no
+  virtual void RemoveInputListener(AudioDataListener *aListener) {
+    mAudioInput = nullptr;
+  }
+
 protected:
   GraphTime StateComputedTime() const;
 
   // Time of the start of this graph iteration. This must be accessed while
   // having the monitor.
   GraphTime mIterationStart;
   // Time of the end of this graph iteration. This must be accessed while having
   // the monitor.
@@ -221,16 +230,19 @@ protected:
     WAITSTATE_WAITING_INDEFINITELY,
     // Something has signaled RunThread() to wake up immediately,
     // but it hasn't done so yet
     WAITSTATE_WAKING_UP
   };
   // This must be access with the monitor.
   WaitState mWaitState;
 
+  // Callback for mic data, if any
+  AudioDataListener *mAudioInput;
+
   // This is used on the main thread (during initialization), and the graph
   // thread. No monitor needed because we know the graph thread does not run
   // during the initialization.
   TimeStamp mCurrentTimeStamp;
   // This is non-null only when this driver has recently switched from an other
   // driver, and has not cleaned it up yet (for example because the audio stream
   // is currently calling the callback during initialization).
   //
@@ -257,16 +269,17 @@ class ThreadedDriver : public GraphDrive
 {
 public:
   explicit ThreadedDriver(MediaStreamGraphImpl* aGraphImpl);
   virtual ~ThreadedDriver();
   void Start() override;
   void Stop() override;
   void Resume() override;
   void Revive() override;
+  void RemoveCallback() override;
   /**
    * Runs main control loop on the graph thread. Normally a single invocation
    * of this runs for the entire lifetime of the graph thread.
    */
   void RunThread();
   friend class MediaStreamGraphInitThreadRunnable;
   uint32_t IterationDuration() override {
     return MEDIA_GRAPH_TARGET_PERIOD_MS;
@@ -368,32 +381,35 @@ public:
   explicit AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl);
   virtual ~AudioCallbackDriver();
 
   void Destroy() override;
   void Start() override;
   void Stop() override;
   void Resume() override;
   void Revive() override;
+  void RemoveCallback() override;
   void WaitForNextIteration() override;
   void WakeUp() override;
 
   /* Static wrapper function cubeb calls back. */
   static long DataCallback_s(cubeb_stream * aStream,
-                             void * aUser, void * aBuffer,
+                             void * aUser,
+                             const void * aInputBuffer,
+                             void * aOutputBuffer,
                              long aFrames);
   static void StateCallback_s(cubeb_stream* aStream, void * aUser,
                               cubeb_state aState);
   static void DeviceChangedCallback_s(void * aUser);
   /* This function is called by the underlying audio backend when a refill is
    * needed. This is what drives the whole graph when it is used to output
    * audio. If the return value is exactly aFrames, this function will get
    * called again. If it is less than aFrames, the stream will go in draining
    * mode, and this function will not be called again. */
-  long DataCallback(AudioDataValue* aBuffer, long aFrames);
+  long DataCallback(const AudioDataValue* aInputBuffer, AudioDataValue* aOutputBuffer, long aFrames);
   /* This function is called by the underlying audio backend, but is only used
    * for informational purposes at the moment. */
   void StateCallback(cubeb_state aState);
   /* This is an approximation of the number of millisecond there are between two
    * iterations of the graph. */
   uint32_t IterationDuration() override;
 
   /* This function gets called when the graph has produced the audio frames for
@@ -483,31 +499,36 @@ private:
    * This is written on the previous driver's thread (if switching) or main
    * thread (if this driver is the first one).
    * This is read on previous driver's thread (during callbacks from
    * cubeb_stream_init) and the audio thread (when switching away from this
    * driver back to a SystemClockDriver).
    * This is synchronized by the Graph's monitor.
    * */
   bool mStarted;
+  /* Listener for mic input, if any. */
+  RefPtr<AudioDataListener> mAudioInput;
 
   struct AutoInCallback
   {
     explicit AutoInCallback(AudioCallbackDriver* aDriver);
     ~AutoInCallback();
     AudioCallbackDriver* mDriver;
   };
 
   /* Thread for off-main-thread initialization and
    * shutdown of the audio stream. */
   nsCOMPtr<nsIThread> mInitShutdownThread;
   /* This must be accessed with the graph monitor held. */
   nsAutoTArray<StreamAndPromiseForOperation, 1> mPromisesForOperation;
-  /* This is set during initialization, and ca be read safely afterwards. */
+  /* This is set during initialization, and can be read safely afterwards. */
   dom::AudioChannel mAudioChannel;
+  /* Used to queue us to add the mixer callback on first run. */
+  bool mAddedMixer;
+
   /* This is atomic and is set by the audio callback thread. It can be read by
    * any thread safely. */
   Atomic<bool> mInCallback;
   /**
    * True if microphone is being used by this process. This is synchronized by
    * the graph's monitor. */
   bool mMicrophoneActive;
 
@@ -527,31 +548,31 @@ private:
 class AsyncCubebTask : public nsRunnable
 {
 public:
 
   AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation);
 
   nsresult Dispatch()
   {
-    // Can't add 'this' as the event to run, since mThread may not be set yet
-    nsresult rv = NS_NewNamedThread("CubebOperation", getter_AddRefs(mThread));
-    if (NS_SUCCEEDED(rv)) {
-      // Note: event must not null out mThread!
-      rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL);
+    nsresult rv = EnsureThread();
+    if (!NS_FAILED(rv)) {
+      rv = sThreadPool->Dispatch(this, NS_DISPATCH_NORMAL);
     }
     return rv;
   }
 
 protected:
   virtual ~AsyncCubebTask();
 
 private:
+  static nsresult EnsureThread();
+
   NS_IMETHOD Run() override final;
-  nsCOMPtr<nsIThread> mThread;
+  static StaticRefPtr<nsIThreadPool> sThreadPool;
   RefPtr<AudioCallbackDriver> mDriver;
   AsyncCubebOperation mOperation;
   RefPtr<MediaStreamGraphImpl> mShutdownGrip;
 };
 
 } // namespace mozilla
 
 #endif // GRAPHDRIVER_H_
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -840,16 +840,17 @@ public:
   }
 
   RefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   RefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
   RefPtr<VideoDevice> mVideoDevice;
   bool mEchoOn;
   bool mAgcOn;
   bool mNoiseOn;
+  bool mFullDuplex;
   uint32_t mEcho;
   uint32_t mAgc;
   uint32_t mNoise;
   uint32_t mPlayoutDelay;
 };
 
 
 void
@@ -1514,26 +1515,28 @@ MediaManager::EnumerateRawDevices(uint64
 MediaManager::MediaManager()
   : mMediaThread(nullptr)
   , mBackend(nullptr) {
   mPrefs.mFreq   = 1000; // 1KHz test tone
   mPrefs.mWidth  = 0; // adaptive default
   mPrefs.mHeight = 0; // adaptive default
   mPrefs.mFPS    = MediaEngine::DEFAULT_VIDEO_FPS;
   mPrefs.mMinFPS = MediaEngine::DEFAULT_VIDEO_MIN_FPS;
+  mPrefs.mFullDuplex = false;
   nsresult rv;
   nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
     if (branch) {
       GetPrefs(branch, nullptr);
     }
   }
-  LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones", __FUNCTION__,
-       mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq));
+  LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones, %sfull-duplex", __FUNCTION__,
+       mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq,
+       mPrefs.mFullDuplex ? "" : "not "));
 }
 
 NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver)
 
 /* static */ StaticRefPtr<MediaManager> MediaManager::sSingleton;
 
 #ifdef DEBUG
 /* static */ bool
@@ -1585,16 +1588,18 @@ MediaManager::Get() {
     }
     // else MediaManager won't work properly and will leak (see bug 837874)
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (prefs) {
       prefs->AddObserver("media.navigator.video.default_width", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_height", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false);
       prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false);
+      prefs->AddObserver("media.navigator.audio.fake_frequency", sSingleton, false);
+      prefs->AddObserver("media.navigator.audio.full_duplex", sSingleton, false);
     }
 
     // Prepare async shutdown
 
     nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase();
 
     class Blocker : public media::ShutdownBlocker
     {
@@ -2618,16 +2623,17 @@ MediaManager::GetPrefBool(nsIPrefBranch 
 void
 MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData)
 {
   GetPref(aBranch, "media.navigator.video.default_width", aData, &mPrefs.mWidth);
   GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight);
   GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS);
   GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS);
   GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, &mPrefs.mFreq);
+  GetPrefBool(aBranch, "media.navigator.audio.full_duplex", aData, &mPrefs.mFullDuplex);
 }
 
 void
 MediaManager::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (sInShutdown) {
     return;
@@ -2644,16 +2650,17 @@ MediaManager::Shutdown()
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver("media.navigator.video.default_width", this);
     prefs->RemoveObserver("media.navigator.video.default_height", this);
     prefs->RemoveObserver("media.navigator.video.default_fps", this);
     prefs->RemoveObserver("media.navigator.video.default_minfps", this);
     prefs->RemoveObserver("media.navigator.audio.fake_frequency", this);
+    prefs->RemoveObserver("media.navigator.audio.full_duplex", this);
   }
 
   // Close off any remaining active windows.
   GetActiveWindows()->Clear();
   mActiveCallbacks.Clear();
   mCallIds.Clear();
 
   // Because mMediaThread is not an nsThread, we must dispatch to it so it can
@@ -3117,20 +3124,20 @@ MediaManager::IsActivelyCapturingOrHasAP
     }
   }
   return audio == nsIPermissionManager::ALLOW_ACTION ||
          video == nsIPermissionManager::ALLOW_ACTION;
 }
 
 void
 GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn,
-              uint32_t aEcho,
-              bool aAgcOn, uint32_t aAGC,
-              bool aNoiseOn, uint32_t aNoise,
-              int32_t aPlayoutDelay)
+                                                     uint32_t aEcho,
+                                                     bool aAgcOn, uint32_t aAGC,
+                                                     bool aNoiseOn, uint32_t aNoise,
+                                                     int32_t aPlayoutDelay)
 {
   if (mAudioDevice) {
 #ifdef MOZ_WEBRTC
     MediaManager::PostTask(FROM_HERE,
       NewRunnableMethod(mAudioDevice->GetSource(), &MediaEngineSource::Config,
                         aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn,
                         aNoise, aPlayoutDelay));
 #endif
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/dom/AudioContextBinding.h"
 #include <algorithm>
 #include "DOMMediaStream.h"
 #include "GeckoProfiler.h"
 #include "mozilla/unused.h"
 #ifdef MOZ_WEBRTC
 #include "AudioOutputObserver.h"
 #endif
+#include "mtransport/runnable_utils.h"
 
 #include "webaudio/blink/HRTFDatabaseLoader.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 namespace mozilla {
@@ -343,24 +344,25 @@ MediaStreamGraphImpl::UpdateStreamOrder(
       audioTrackPresent = true;
     } else {
       for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
            !tracks.IsEnded(); tracks.Next()) {
         audioTrackPresent = true;
       }
     }
   }
+  // Note that this looks for any audio streams, input or output, and switches to a
+  // SystemClockDriver if there are none
 
   if (!audioTrackPresent && mRealtime &&
       CurrentDriver()->AsAudioCallbackDriver()) {
     MonitorAutoLock mon(mMonitor);
     if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted()) {
       if (mLifecycleState == LIFECYCLE_RUNNING) {
         SystemClockDriver* driver = new SystemClockDriver(this);
-        mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver());
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
     }
   }
 
   bool switching = false;
   {
     MonitorAutoLock mon(mMonitor);
@@ -368,17 +370,16 @@ MediaStreamGraphImpl::UpdateStreamOrder(
   }
 
   if (audioTrackPresent && mRealtime &&
       !CurrentDriver()->AsAudioCallbackDriver() &&
       !switching) {
     MonitorAutoLock mon(mMonitor);
     if (mLifecycleState == LIFECYCLE_RUNNING) {
       AudioCallbackDriver* driver = new AudioCallbackDriver(this);
-      mMixer.AddCallback(driver);
       CurrentDriver()->SwitchAtNextIteration(driver);
     }
   }
 
 #ifdef MOZ_WEBRTC
   if (shouldAEC && !mFarendObserverRef && gFarendObserver) {
     mFarendObserverRef = gFarendObserver;
     mMixer.AddCallback(mFarendObserverRef);
@@ -634,17 +635,16 @@ MediaStreamGraphImpl::CreateOrDestroyAud
         switching = CurrentDriver()->Switching();
       }
 
       if (!CurrentDriver()->AsAudioCallbackDriver() &&
           !switching) {
         MonitorAutoLock mon(mMonitor);
         if (mLifecycleState == LIFECYCLE_RUNNING) {
           AudioCallbackDriver* driver = new AudioCallbackDriver(this);
-          mMixer.AddCallback(driver);
           CurrentDriver()->SwitchAtNextIteration(driver);
         }
       }
     }
   }
 
   for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) {
     if (!audioOutputStreamsFound[i]) {
@@ -917,16 +917,148 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
 
   // If the stream has finished and the timestamps of all frames have expired
   // then no more updates are required.
   if (aStream->mFinished && !haveMultipleImages) {
     aStream->mLastPlayedVideoFrame.SetNull();
   }
 }
 
+void
+MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+                                         AudioDataListener *aListener)
+{
+ // Bug 1238038 Need support for multiple mics at once
+  MOZ_ASSERT(!mInputWanted);
+  if (mInputWanted) {
+    // Need to support separate input-only AudioCallback drivers; they'll
+    // call us back on "other" threads.  We will need to echo-cancel them, though.
+    return;
+  }
+  mInputWanted = true;
+  // aID is a cubeb_devid, and we assume that opaque ptr is valid until
+  // we close cubeb.
+  mInputDeviceID = aID;
+  mAudioInputs.AppendElement(aListener); // always monitor speaker data
+
+  // Switch Drivers since we're adding input (to input-only or full-duplex)
+  MonitorAutoLock mon(mMonitor);
+  if (mLifecycleState == LIFECYCLE_RUNNING) {
+    AudioCallbackDriver* driver = new AudioCallbackDriver(this);
+    CurrentDriver()->SwitchAtNextIteration(driver);
+  }
+}
+
+nsresult
+MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                     AudioDataListener *aListener)
+{
+  // So, so, so annoying.  Can't AppendMessage except on Mainthread
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(WrapRunnable(this,
+                                         &MediaStreamGraphImpl::OpenAudioInput,
+                                         aID, aListener));
+    return NS_OK;
+  }
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStreamGraphImpl *aGraph, CubebUtils::AudioDeviceID aID,
+            AudioDataListener *aListener) :
+      ControlMessage(nullptr), mGraph(aGraph), mID(aID), mListener(aListener) {}
+    virtual void Run()
+    {
+      mGraph->OpenAudioInputImpl(mID, mListener);
+    }
+    MediaStreamGraphImpl *mGraph;
+    // aID is a cubeb_devid, and we assume that opaque ptr is valid until
+    // we close cubeb.
+    CubebUtils::AudioDeviceID mID;
+    RefPtr<AudioDataListener> mListener;
+  };
+  this->AppendMessage(new Message(this, aID, aListener));
+  return NS_OK;
+}
+
+void
+MediaStreamGraphImpl::CloseAudioInputImpl(AudioDataListener *aListener)
+{
+  mInputDeviceID = nullptr;
+  mInputWanted = false;
+  CurrentDriver()->RemoveInputListener(aListener);
+  mAudioInputs.RemoveElement(aListener);
+
+  // Switch Drivers since we're adding or removing an input (to nothing/system or output only)
+  bool audioTrackPresent = false;
+  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+    MediaStream* stream = mStreams[i];
+    // If this is a AudioNodeStream, force a AudioCallbackDriver.
+    if (stream->AsAudioNodeStream()) {
+      audioTrackPresent = true;
+    } else if (CurrentDriver()->AsAudioCallbackDriver()) {
+      // only if there's a real switch!
+      for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
+           !tracks.IsEnded(); tracks.Next()) {
+        audioTrackPresent = true;
+      }
+    }
+  }
+
+  MonitorAutoLock mon(mMonitor);
+  if (mLifecycleState == LIFECYCLE_RUNNING) {
+    GraphDriver* driver;
+    if (audioTrackPresent) {
+      // We still have audio output
+      STREAM_LOG(LogLevel::Debug, ("CloseInput: output present (AudioCallback)"));
+
+      driver = new AudioCallbackDriver(this);
+      CurrentDriver()->SwitchAtNextIteration(driver);
+    } else if (CurrentDriver()->AsAudioCallbackDriver()) {
+      STREAM_LOG(LogLevel::Debug, ("CloseInput: no output present (SystemClockCallback)"));
+
+      driver = new SystemClockDriver(this);
+      CurrentDriver()->SwitchAtNextIteration(driver);
+    } // else SystemClockDriver->SystemClockDriver, no switch
+  }
+}
+
+void
+MediaStreamGraphImpl::CloseAudioInput(AudioDataListener *aListener)
+{
+  // So, so, so annoying.  Can't AppendMessage except on Mainthread
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(WrapRunnable(this,
+                                         &MediaStreamGraphImpl::CloseAudioInput,
+                                         aListener));
+    return;
+  }
+  class Message : public ControlMessage {
+  public:
+    Message(MediaStreamGraphImpl *aGraph, AudioDataListener *aListener) :
+      ControlMessage(nullptr), mGraph(aGraph), mListener(aListener) {}
+    virtual void Run()
+    {
+      mGraph->CloseAudioInputImpl(mListener);
+    }
+    MediaStreamGraphImpl *mGraph;
+    RefPtr<AudioDataListener> mListener;
+  };
+  this->AppendMessage(new Message(this, aListener));
+}
+
+
+// All AudioInput listeners get the same speaker data (at least for now).
+void
+MediaStreamGraph::NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
+                                   uint32_t aChannels)
+{
+  for (auto& listener : mAudioInputs) {
+    listener->NotifyOutputData(this, aBuffer, aFrames, aChannels);
+  }
+}
+
 bool
 MediaStreamGraphImpl::ShouldUpdateMainThread()
 {
   if (mRealtime) {
     return true;
   }
 
   TimeStamp now = TimeStamp::Now();
@@ -1170,35 +1302,16 @@ MediaStreamGraphImpl::Process()
       allBlockedForever = false;
     }
   }
 
   if (CurrentDriver()->AsAudioCallbackDriver() && ticksPlayed) {
     mMixer.FinishMixing();
   }
 
-  // If we are switching away from an AudioCallbackDriver, we don't need the
-  // mixer anymore.
-  bool switching = false;
-  {
-    MonitorAutoLock lock(mMonitor);
-    switching = CurrentDriver()->Switching();
-  }
-  if (CurrentDriver()->AsAudioCallbackDriver() &&
-      switching) {
-    bool isStarted;
-    {
-      MonitorAutoLock mon(mMonitor);
-      isStarted = CurrentDriver()->AsAudioCallbackDriver()->IsStarted();
-    }
-    if (isStarted) {
-      mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver());
-    }
-  }
-
   if (!allBlockedForever) {
     EnsureNextIteration();
   }
 }
 
 void
 MediaStreamGraphImpl::MaybeProduceMemoryReport()
 {
@@ -2626,16 +2739,20 @@ ProcessedMediaStream::DestroyImpl()
   // SetStreamOrderDirty(), for other reasons.
 }
 
 MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested,
                                            TrackRate aSampleRate,
                                            dom::AudioChannel aChannel)
   : MediaStreamGraph(aSampleRate)
   , mPortCount(0)
+  , mInputWanted(false)
+  , mInputDeviceID(nullptr)
+  , mOutputWanted(true)
+  , mOutputDeviceID(nullptr)
   , mNeedAnotherIteration(false)
   , mGraphDriverAsleep(false)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mEndTime(GRAPH_TIME_MAX)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
@@ -2655,17 +2772,16 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mCanRunMessagesSynchronously(false)
 #endif
   , mAudioChannel(aChannel)
 {
   if (mRealtime) {
     if (aDriverRequested == AUDIO_THREAD_DRIVER) {
       AudioCallbackDriver* driver = new AudioCallbackDriver(this);
       mDriver = driver;
-      mMixer.AddCallback(driver);
     } else {
       mDriver = new SystemClockDriver(this);
     }
   } else {
     mDriver = new OfflineClockDriver(this, MEDIA_GRAPH_TARGET_PERIOD_MS);
   }
 
   mLastMainThreadUpdate = TimeStamp::Now();
@@ -3061,17 +3177,16 @@ MediaStreamGraphImpl::ApplyAudioContextO
   if (aOperation == AudioContextOperation::Resume) {
     if (!CurrentDriver()->AsAudioCallbackDriver()) {
       AudioCallbackDriver* driver;
       if (switching) {
         MOZ_ASSERT(nextDriver->AsAudioCallbackDriver());
         driver = nextDriver->AsAudioCallbackDriver();
       } else {
         driver = new AudioCallbackDriver(this);
-        mMixer.AddCallback(driver);
         MonitorAutoLock lock(mMonitor);
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
       driver->EnqueueStreamAndPromiseForOperation(aDestinationStream,
           aPromise, aOperation);
     } else {
       // We are resuming a context, but we are already using an
       // AudioCallbackDriver, we can resolve the promise now.
@@ -3100,17 +3215,16 @@ MediaStreamGraphImpl::ApplyAudioContextO
         EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise,
                                             aOperation);
 
       SystemClockDriver* driver;
       if (nextDriver) {
         MOZ_ASSERT(!nextDriver->AsAudioCallbackDriver());
       } else {
         driver = new SystemClockDriver(this);
-        mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver());
         MonitorAutoLock lock(mMonitor);
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
       // We are closing or suspending an AudioContext, but we just got resumed.
       // Queue the operation on the next driver so that the ordering is
       // preserved.
     } else if (!audioTrackPresent && switching) {
       MOZ_ASSERT(nextDriver->AsAudioCallbackDriver());
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -178,16 +178,49 @@ public:
   /**
    * Notify that all new tracks this iteration have been created.
    * This is to ensure that tracks added atomically to MediaStreamGraph
    * are also notified of atomically to MediaStreamListeners.
    */
   virtual void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) {}
 };
 
+class AudioDataListenerInterface {
+protected:
+  // Protected destructor, to discourage deletion outside of Release():
+  virtual ~AudioDataListenerInterface() {}
+
+public:
+  /* These are for cubeb audio input & output streams: */
+  /**
+   * Output data to speakers, for use as the "far-end" data for echo
+   * cancellation.  This is not guaranteed to be in any particular size
+   * chunks.
+   */
+  virtual void NotifyOutputData(MediaStreamGraph* aGraph,
+                                AudioDataValue* aBuffer, size_t aFrames,
+                                uint32_t aChannels) = 0;
+  /**
+   * Input data from a microphone (or other audio source.  This is not
+   * guaranteed to be in any particular size chunks.
+   */
+  virtual void NotifyInputData(MediaStreamGraph* aGraph,
+                               const AudioDataValue* aBuffer, size_t aFrames,
+                               uint32_t aChannels) = 0;
+};
+
+class AudioDataListener : public AudioDataListenerInterface {
+protected:
+  // Protected destructor, to discourage deletion outside of Release():
+  virtual ~AudioDataListener() {}
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataListener)
+};
+
 /**
  * This is a base class for media graph thread listener direct callbacks
  * from within AppendToTrack().  Note that your regular listener will
  * still get NotifyQueuedTrackChanges() callbacks from the MSG thread, so
  * you must be careful to ignore them if AddDirectListener was successful.
  */
 class MediaStreamDirectListener : public MediaStreamListener
 {
@@ -1170,16 +1203,22 @@ public:
   };
   // Main thread only
   static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested,
                                        dom::AudioChannel aChannel);
   static MediaStreamGraph* CreateNonRealtimeInstance(TrackRate aSampleRate);
   // Idempotent
   static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph);
 
+  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                  AudioDataListener *aListener) {
+    return NS_ERROR_FAILURE;
+  }
+  virtual void CloseAudioInput(AudioDataListener *aListener) {}
+
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
    * media data, such as a camera) can write to.
    */
   SourceMediaStream* CreateSourceStream(DOMMediaStream* aWrapper);
   /**
    * Create a stream that will form the union of the tracks of its input
@@ -1249,16 +1288,23 @@ public:
   TrackRate GraphRate() const { return mSampleRate; }
 
   void RegisterCaptureStreamForWindow(uint64_t aWindowId,
                                       ProcessedMediaStream* aCaptureStream);
   void UnregisterCaptureStreamForWindow(uint64_t aWindowId);
   already_AddRefed<MediaInputPort> ConnectToCaptureStream(
     uint64_t aWindowId, MediaStream* aMediaStream);
 
+  /**
+   * Data going to the speakers from the GraphDriver's DataCallback
+   * to notify any listeners (for echo cancellation).
+   */
+  void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames,
+                        uint32_t aChannels);
+
 protected:
   explicit MediaStreamGraph(TrackRate aSampleRate)
     : mSampleRate(aSampleRate)
   {
     MOZ_COUNT_CTOR(MediaStreamGraph);
   }
   virtual ~MediaStreamGraph()
   {
@@ -1269,13 +1315,19 @@ protected:
   nsTArray<nsCOMPtr<nsIRunnable> > mPendingUpdateRunnables;
 
   /**
    * Sample rate at which this graph runs. For real time graphs, this is
    * the rate of the audio mixer. For offline graphs, this is the rate specified
    * at construction.
    */
   TrackRate mSampleRate;
+
+  /**
+   * Lifetime is controlled by OpenAudioInput/CloseAudioInput.  Destroying the listener
+   * without removing it is an error; callers should assert on that.
+   */
+  nsTArray<AudioDataListener *> mAudioInputs;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -345,16 +345,23 @@ public:
    * Set the correct current video frame for stream aStream.
    */
   void PlayVideo(MediaStream* aStream);
   /**
    * No more data will be forthcoming for aStream. The stream will end
    * at the current buffer end point. The StreamBuffer's tracks must be
    * explicitly set to finished by the caller.
    */
+  void OpenAudioInputImpl(CubebUtils::AudioDeviceID aID,
+                          AudioDataListener *aListener);
+  virtual nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
+                                  AudioDataListener *aListener) override;
+  void CloseAudioInputImpl(AudioDataListener *aListener);
+  virtual void CloseAudioInput(AudioDataListener *aListener) override;
+
   void FinishStream(MediaStream* aStream);
   /**
    * Compute how much stream data we would like to buffer for aStream.
    */
   StreamTime GetDesiredBufferEnd(MediaStream* aStream);
   /**
    * Returns true when there are no active streams.
    */
@@ -572,16 +579,25 @@ public:
    * Date of the last time we updated the main thread with the graph state.
    */
   TimeStamp mLastMainThreadUpdate;
   /**
    * Number of active MediaInputPorts
    */
   int32_t mPortCount;
 
+  /**
+   * Devices to use for cubeb input & output, or NULL for no input (void*),
+   * and boolean to control if we want input/output
+   */
+  bool mInputWanted;
+  CubebUtils::AudioDeviceID mInputDeviceID;
+  bool mOutputWanted;
+  CubebUtils::AudioDeviceID mOutputDeviceID;
+
   // True if the graph needs another iteration after the current iteration.
   Atomic<bool> mNeedAnotherIteration;
   // GraphDriver may need a WakeUp() if something changes
   Atomic<bool> mGraphDriverAsleep;
 
   // mMonitor guards the data below.
   // MediaStreamGraph normally does its work without holding mMonitor, so it is
   // not safe to just grab mMonitor from some thread and start monkeying with
--- a/dom/media/mediasink/DecodedAudioDataSink.cpp
+++ b/dom/media/mediasink/DecodedAudioDataSink.cpp
@@ -167,35 +167,43 @@ DecodedAudioDataSink::PopFrames(uint32_t
 {
   class Chunk : public AudioStream::Chunk {
   public:
     Chunk(AudioData* aBuffer, uint32_t aFrames, AudioDataValue* aData)
       : mBuffer(aBuffer), mFrames(aFrames), mData(aData) {}
     Chunk() : mFrames(0), mData(nullptr) {}
     const AudioDataValue* Data() const { return mData; }
     uint32_t Frames() const { return mFrames; }
+    uint32_t Channels() const { return mBuffer ? mBuffer->mChannels: 0; }
+    uint32_t Rate() const { return mBuffer ? mBuffer->mRate : 0; }
     AudioDataValue* GetWritable() const { return mData; }
   private:
     const RefPtr<AudioData> mBuffer;
     const uint32_t mFrames;
     AudioDataValue* const mData;
   };
 
   class SilentChunk : public AudioStream::Chunk {
   public:
-    SilentChunk(uint32_t aFrames, uint32_t aChannels)
+    SilentChunk(uint32_t aFrames, uint32_t aChannels, uint32_t aRate)
       : mFrames(aFrames)
+      , mChannels(aChannels)
+      , mRate(aRate)
       , mData(MakeUnique<AudioDataValue[]>(aChannels * aFrames)) {
       memset(mData.get(), 0, aChannels * aFrames * sizeof(AudioDataValue));
     }
     const AudioDataValue* Data() const { return mData.get(); }
     uint32_t Frames() const { return mFrames; }
+    uint32_t Channels() const { return mChannels; }
+    uint32_t Rate() const { return mRate; }
     AudioDataValue* GetWritable() const { return mData.get(); }
   private:
     const uint32_t mFrames;
+    const uint32_t mChannels;
+    const uint32_t mRate;
     UniquePtr<AudioDataValue[]> mData;
   };
 
   if (!mCurrentData) {
     // No data in the queue. Return an empty chunk.
     if (AudioQueue().GetSize() == 0) {
       return MakeUnique<Chunk>();
     }
@@ -219,40 +227,32 @@ DecodedAudioDataSink::PopFrames(uint32_t
     if (missingFrames.value() > AUDIO_FUZZ_FRAMES) {
       // The next audio chunk begins some time after the end of the last chunk
       // we pushed to the audio hardware. We must push silence into the audio
       // hardware so that the next audio chunk begins playback at the correct
       // time.
       missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
       auto framesToPop = std::min<uint32_t>(missingFrames.value(), aFrames);
       mWritten += framesToPop;
-      return MakeUnique<SilentChunk>(framesToPop, mInfo.mChannels);
+      return MakeUnique<SilentChunk>(framesToPop, mInfo.mChannels, mInfo.mRate);
     }
 
     mCurrentData = dont_AddRef(AudioQueue().PopFront().take()->As<AudioData>());
     mCursor = MakeUnique<AudioBufferCursor>(mCurrentData->mAudioData.get(),
                                             mCurrentData->mChannels,
                                             mCurrentData->mFrames);
   }
 
   auto framesToPop = std::min(aFrames, mCursor->Available());
 
   SINK_LOG_V("playing audio at time=%lld offset=%u length=%u",
              mCurrentData->mTime, mCurrentData->mFrames - mCursor->Available(), framesToPop);
 
-  UniquePtr<AudioStream::Chunk> chunk;
-
-  if (mCurrentData->mRate == mInfo.mRate &&
-      mCurrentData->mChannels == mInfo.mChannels) {
-    chunk = MakeUnique<Chunk>(mCurrentData, framesToPop, mCursor->Ptr());
-  } else {
-    SINK_LOG_V("mismatched sample format mInfo=[%uHz/%u channels] audio=[%uHz/%u channels]",
-               mInfo.mRate, mInfo.mChannels, mCurrentData->mRate, mCurrentData->mChannels);
-    chunk = MakeUnique<SilentChunk>(framesToPop, mInfo.mChannels);
-  }
+  UniquePtr<AudioStream::Chunk> chunk =
+    MakeUnique<Chunk>(mCurrentData, framesToPop, mCursor->Ptr());
 
   mWritten += framesToPop;
   mCursor->Advance(framesToPop);
 
   // All frames are popped. Reset mCurrentData so we can pop new elements from
   // the audio queue in next calls to PopFrames().
   if (mCursor->Available() == 0) {
     mCurrentData = nullptr;
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -211,23 +211,25 @@ protected:
 class MediaEnginePrefs {
 public:
   MediaEnginePrefs()
     : mWidth(0)
     , mHeight(0)
     , mFPS(0)
     , mMinFPS(0)
     , mFreq(0)
+    , mFullDuplex(false)
   {}
 
   int32_t mWidth;
   int32_t mHeight;
   int32_t mFPS;
   int32_t mMinFPS;
   int32_t mFreq; // for test tones (fake:true)
+  bool mFullDuplex;
 
   // mWidth and/or mHeight may be zero (=adaptive default), so use functions.
 
   int32_t GetWidth(bool aHD = false) const {
     return mWidth? mWidth : (mHeight?
                              (mHeight * GetDefWidth(aHD)) / GetDefHeight(aHD) :
                              GetDefWidth(aHD));
   }
@@ -269,17 +271,18 @@ protected:
     : MediaEngineSource(aState) {}
   MediaEngineVideoSource()
     : MediaEngineSource(kReleased) {}
 };
 
 /**
  * Audio source and friends.
  */
-class MediaEngineAudioSource : public MediaEngineSource
+class MediaEngineAudioSource : public MediaEngineSource,
+                               public AudioDataListenerInterface
 {
 public:
   virtual ~MediaEngineAudioSource() {}
 
 protected:
   explicit MediaEngineAudioSource(MediaEngineState aState)
     : MediaEngineSource(aState) {}
   MediaEngineAudioSource()
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -139,16 +139,24 @@ public:
 #ifdef DEBUG
     StreamBuffer::Track* data = aSource->FindTrack(aId);
     NS_WARN_IF_FALSE(!data || data->IsEnded() ||
                      aDesiredTime <= aSource->GetEndOfAppendedData(aId),
                      "MediaEngineDefaultAudioSource data underrun");
 #endif
   }
 
+  void NotifyOutputData(MediaStreamGraph* aGraph,
+                        AudioDataValue* aBuffer, size_t aFrames,
+                        uint32_t aChannels) override
+  {}
+  void NotifyInputData(MediaStreamGraph* aGraph,
+                       const AudioDataValue* aBuffer, size_t aFrames,
+                       uint32_t aChannels) override
+  {}
   bool IsFake() override {
     return true;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Microphone;
   }
 
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -39,20 +39,25 @@ GetUserMediaLog()
 #include "MediaEngineGonkVideoSource.h"
 #endif
 
 #undef LOG
 #define LOG(args) MOZ_LOG(GetUserMediaLog(), mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 
+cubeb_device_collection* AudioInputCubeb::mDevices = nullptr;
+bool AudioInputCubeb::mAnyInUse = false;
+
 MediaEngineWebRTC::MediaEngineWebRTC(MediaEnginePrefs &aPrefs)
   : mMutex("mozilla::MediaEngineWebRTC"),
     mVoiceEngine(nullptr),
-    mAudioEngineInit(false)
+    mAudioInput(nullptr),
+    mAudioEngineInit(false),
+    mFullDuplex(aPrefs.mFullDuplex)
 {
 #ifndef MOZ_B2G_CAMERA
   nsCOMPtr<nsIComponentRegistrar> compMgr;
   NS_GetComponentRegistrar(getter_AddRefs(compMgr));
   if (compMgr) {
     compMgr->IsContractIDRegistered(NS_TABSOURCESERVICE_CONTRACTID, &mHasTabVideoSource);
   }
 #else
@@ -234,17 +239,16 @@ MediaEngineWebRTC::EnumerateVideoDevices
 #endif
 }
 
 void
 MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
                                          nsTArray<RefPtr<MediaEngineAudioSource> >* aASources)
 {
   ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
-  ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
   // We spawn threads to handle gUM runnables, so we must protect the member vars
   MutexAutoLock lock(mMutex);
 
   if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
     RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
       new MediaEngineWebRTCAudioCaptureSource(nullptr);
     aASources->AppendElement(audioCaptureSource);
     return;
@@ -278,59 +282,71 @@ MediaEngineWebRTC::EnumerateAudioDevices
 
   if (!mAudioEngineInit) {
     if (ptrVoEBase->Init() < 0) {
       return;
     }
     mAudioEngineInit = true;
   }
 
-  ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
-  if (!ptrVoEHw)  {
-    return;
+  if (!mAudioInput) {
+    if (mFullDuplex) {
+      // The platform_supports_full_duplex.
+      mAudioInput = new mozilla::AudioInputCubeb(mVoiceEngine);
+    } else {
+      mAudioInput = new mozilla::AudioInputWebRTC(mVoiceEngine);
+    }
   }
 
   int nDevices = 0;
-  ptrVoEHw->GetNumOfRecordingDevices(nDevices);
+  mAudioInput->GetNumOfRecordingDevices(nDevices);
   int i;
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   i = 0; // Bug 1037025 - let the OS handle defaulting for now on android/b2g
 #else
   // -1 is "default communications device" depending on OS in webrtc.org code
   i = -1;
 #endif
   for (; i < nDevices; i++) {
     // We use constants here because GetRecordingDeviceName takes char[128].
     char deviceName[128];
     char uniqueId[128];
     // paranoia; jingle doesn't bother with this
     deviceName[0] = '\0';
     uniqueId[0] = '\0';
 
-    int error = ptrVoEHw->GetRecordingDeviceName(i, deviceName, uniqueId);
+    int error = mAudioInput->GetRecordingDeviceName(i, deviceName, uniqueId);
     if (error) {
-      LOG((" VoEHardware:GetRecordingDeviceName: Failed %d",
-           ptrVoEBase->LastError() ));
+      LOG((" VoEHardware:GetRecordingDeviceName: Failed %d", error));
       continue;
     }
 
     if (uniqueId[0] == '\0') {
       // Mac and Linux don't set uniqueId!
       MOZ_ASSERT(sizeof(deviceName) == sizeof(uniqueId)); // total paranoia
       strcpy(uniqueId,deviceName); // safe given assert and initialization/error-check
     }
 
     RefPtr<MediaEngineAudioSource> aSource;
     NS_ConvertUTF8toUTF16 uuid(uniqueId);
     if (mAudioSources.Get(uuid, getter_AddRefs(aSource))) {
       // We've already seen this device, just append.
       aASources->AppendElement(aSource.get());
     } else {
-      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, i,
-                                                      deviceName, uniqueId);
+      AudioInput* audioinput = mAudioInput;
+      if (mFullDuplex) {
+        // The platform_supports_full_duplex.
+
+        // For cubeb, it has state (the selected ID)
+        // XXX just use the uniqueID for cubeb and support it everywhere, and get rid of this
+        // XXX Small window where the device list/index could change!
+        audioinput = new mozilla::AudioInputCubeb(mVoiceEngine);
+      }
+      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, audioinput,
+                                                      i, deviceName, uniqueId);
       mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
       aASources->AppendElement(aSource);
     }
   }
 }
 
 void
 MediaEngineWebRTC::Shutdown()
@@ -359,16 +375,17 @@ MediaEngineWebRTC::Shutdown()
   if (mVoiceEngine) {
     mVoiceEngine->SetTraceCallback(nullptr);
     webrtc::VoiceEngine::Delete(mVoiceEngine);
   }
 
   mVoiceEngine = nullptr;
 
   mozilla::camera::Shutdown();
+  AudioInputCubeb::CleanupGlobalData();
 
   if (mThread) {
     mThread->Shutdown();
     mThread = nullptr;
   }
 }
 
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -2,16 +2,17 @@
  * 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 MEDIAENGINEWEBRTC_H_
 #define MEDIAENGINEWEBRTC_H_
 
 #include "prcvar.h"
 #include "prthread.h"
+#include "prprf.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 
 #include "mozilla/dom/File.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
 #include "nsCOMPtr.h"
 #include "nsThreadUtils.h"
@@ -21,16 +22,19 @@
 #include "nsRefPtrHashtable.h"
 
 #include "VideoUtils.h"
 #include "MediaEngineCameraVideoSource.h"
 #include "VideoSegment.h"
 #include "AudioSegment.h"
 #include "StreamBuffer.h"
 #include "MediaStreamGraph.h"
+#include "cubeb/cubeb.h"
+#include "CubebUtils.h"
+#include "AudioPacketizer.h"
 
 #include "MediaEngineWrapper.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 // WebRTC library includes follow
 #include "webrtc/common.h"
 // Audio Engine
 #include "webrtc/voice_engine/include/voe_base.h"
 #include "webrtc/voice_engine/include/voe_codec.h"
@@ -90,16 +94,24 @@ public:
   void SetDirectListeners(bool aDirect) override
   {}
   nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn,
                   uint32_t aAGC, bool aNoiseOn, uint32_t aNoise,
                   int32_t aPlayoutDelay) override
   {
     return NS_OK;
   }
+  void NotifyOutputData(MediaStreamGraph* aGraph,
+                        AudioDataValue* aBuffer, size_t aFrames,
+                        uint32_t aChannels) override
+  {}
+  void NotifyInputData(MediaStreamGraph* aGraph,
+                       const AudioDataValue* aBuffer, size_t aFrames,
+                       uint32_t aChannels) override
+  {}
   void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource,
                   TrackID aID, StreamTime aDesiredTime) override
   {}
   dom::MediaSourceEnum GetMediaSource() const override
   {
     return dom::MediaSourceEnum::AudioCapture;
   }
   bool IsFake() override
@@ -114,45 +126,279 @@ public:
     const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId) override;
 
 protected:
   virtual ~MediaEngineWebRTCAudioCaptureSource() { Shutdown(); }
   nsCString mUUID;
 };
 
+// Small subset of VoEHardware
+class AudioInput
+{
+public:
+  AudioInput(webrtc::VoiceEngine* aVoiceEngine) : mVoiceEngine(aVoiceEngine) {};
+  // Threadsafe because it's referenced from an MicrophoneSource, which can
+  // had references to it on other threads.
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioInput)
+
+  virtual int GetNumOfRecordingDevices(int& aDevices) = 0;
+  virtual int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
+                                     char aStrGuidUTF8[128]) = 0;
+  virtual int GetRecordingDeviceStatus(bool& aIsAvailable) = 0;
+  virtual void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) = 0;
+  virtual void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) = 0;
+  virtual int SetRecordingDevice(int aIndex) = 0;
+
+protected:
+  // Protected destructor, to discourage deletion outside of Release():
+  virtual ~AudioInput() {}
+
+  webrtc::VoiceEngine* mVoiceEngine;
+};
+
+class AudioInputCubeb final : public AudioInput
+{
+public:
+  explicit AudioInputCubeb(webrtc::VoiceEngine* aVoiceEngine) :
+    AudioInput(aVoiceEngine), mSelectedDevice(0), mInUse(false)
+  {
+    // Force calculation of the indexes.  We could keep them global
+    // too... cleanup would be annoying
+    int devices;
+    GetNumOfRecordingDevices(devices);
+  }
+
+  static void CleanupGlobalData()
+  {
+    if (mDevices) {
+      // This doesn't require anything more than support for free()
+      cubeb_device_collection_destroy(mDevices);
+      mDevices = nullptr;
+    }
+  }
+
+  int GetNumOfRecordingDevices(int& aDevices)
+  {
+    if (!mDevices) {
+      if (CUBEB_OK != cubeb_enumerate_devices(CubebUtils::GetCubebContext(),
+                                              CUBEB_DEVICE_TYPE_INPUT,
+                                              &mDevices)) {
+        return 0;
+      }
+    }
+    // Calculate translation once (per AudioInput)
+    if (mDeviceIndexes.Length() == 0) {
+      for (uint32_t i = 0; i < mDevices->count; i++) {
+        if (mDevices->device[i]->type == CUBEB_DEVICE_TYPE_INPUT && // paranoia
+            (mDevices->device[i]->state == CUBEB_DEVICE_STATE_ENABLED ||
+             mDevices->device[i]->state == CUBEB_DEVICE_STATE_UNPLUGGED))
+        {
+          mDeviceIndexes.AppendElement(i);
+          // XXX to support device changes, we need to identify by name/devid not index
+        }
+      }
+    }
+    aDevices = mDeviceIndexes.Length();
+    return 0;
+  }
+
+  int32_t DeviceIndex(int aIndex)
+  {
+    if (aIndex == -1) {
+      aIndex = 0; // -1 = system default
+    }
+    if (aIndex >= (int) mDeviceIndexes.Length()) {
+      return -1;
+    }
+    return mDeviceIndexes[aIndex]; // translate to mDevices index
+  }
+
+  int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
+                             char aStrGuidUTF8[128])
+  {
+    int32_t devindex = DeviceIndex(aIndex);
+    if (!mDevices || devindex < 0) {
+      return 1;
+    }
+    PR_snprintf(aStrNameUTF8, 128, "%s%s", aIndex == -1 ? "default: " : "",
+                mDevices->device[devindex]->friendly_name);
+    aStrGuidUTF8[0] = '\0';
+    return 0;
+  }
+
+  int GetRecordingDeviceStatus(bool& aIsAvailable)
+  {
+    // With cubeb, we only expose devices of type CUBEB_DEVICE_TYPE_INPUT
+    aIsAvailable = true;
+    return 0;
+  }
+
+  void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
+  {
+    MOZ_ASSERT(mDevices);
+
+    ScopedCustomReleasePtr<webrtc::VoEExternalMedia> ptrVoERender;
+    ptrVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
+    if (ptrVoERender) {
+      ptrVoERender->SetExternalRecordingStatus(true);
+    }
+    aGraph->OpenAudioInput(mDevices->device[mSelectedDevice]->devid, aListener);
+    mInUse = true;
+    mAnyInUse = true;
+  }
+
+  void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener)
+  {
+    aGraph->CloseAudioInput(aListener);
+    mInUse = false;
+    mAnyInUse = false;
+  }
+
+  int SetRecordingDevice(int aIndex)
+  {
+    int32_t devindex = DeviceIndex(aIndex);
+    if (!mDevices || devindex < 0) {
+      return 1;
+    }
+    mSelectedDevice = devindex;
+    return 0;
+  }
+
+protected:
+  ~AudioInputCubeb() {
+    MOZ_RELEASE_ASSERT(!mInUse);
+  }
+
+private:
+  nsTArray<int> mDeviceIndexes;
+  int mSelectedDevice;
+  static cubeb_device_collection *mDevices;
+  bool mInUse; // for assertions about listener lifetime
+  static bool mAnyInUse;
+};
+
+class AudioInputWebRTC final : public AudioInput
+{
+public:
+  explicit AudioInputWebRTC(webrtc::VoiceEngine* aVoiceEngine) : AudioInput(aVoiceEngine) {}
+
+  int GetNumOfRecordingDevices(int& aDevices)
+  {
+    ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
+    ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
+    if (!ptrVoEHw)  {
+      return 1;
+    }
+    return ptrVoEHw->GetNumOfRecordingDevices(aDevices);
+  }
+
+  int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
+                             char aStrGuidUTF8[128])
+  {
+    ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
+    ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
+    if (!ptrVoEHw)  {
+      return 1;
+    }
+    return ptrVoEHw->GetRecordingDeviceName(aIndex, aStrNameUTF8,
+                                            aStrGuidUTF8);
+  }
+
+  int GetRecordingDeviceStatus(bool& aIsAvailable)
+  {
+    ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
+    ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
+    if (!ptrVoEHw)  {
+      return 1;
+    }
+    ptrVoEHw->GetRecordingDeviceStatus(aIsAvailable);
+    return 0;
+  }
+
+  void StartRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) {}
+  void StopRecording(MediaStreamGraph *aGraph, AudioDataListener *aListener) {}
+
+  int SetRecordingDevice(int aIndex)
+  {
+    ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
+    ptrVoEHw = webrtc::VoEHardware::GetInterface(mVoiceEngine);
+    if (!ptrVoEHw)  {
+      return 1;
+    }
+    return ptrVoEHw->SetRecordingDevice(aIndex);
+  }
+
+protected:
+  // Protected destructor, to discourage deletion outside of Release():
+  ~AudioInputWebRTC() {}
+};
+
+class WebRTCAudioDataListener : public AudioDataListener
+{
+protected:
+  // Protected destructor, to discourage deletion outside of Release():
+  virtual ~WebRTCAudioDataListener() {}
+
+public:
+  explicit WebRTCAudioDataListener(MediaEngineAudioSource* aAudioSource) :
+    mAudioSource(aAudioSource)
+  {}
+
+  // AudioDataListenerInterface methods
+  virtual void NotifyOutputData(MediaStreamGraph* aGraph,
+                                AudioDataValue* aBuffer, size_t aFrames,
+                                uint32_t aChannels) override
+  {
+    mAudioSource->NotifyOutputData(aGraph, aBuffer, aFrames, aChannels);
+  }
+  virtual void NotifyInputData(MediaStreamGraph* aGraph,
+                               const AudioDataValue* aBuffer, size_t aFrames,
+                               uint32_t aChannels) override
+  {
+    mAudioSource->NotifyInputData(aGraph, aBuffer, aFrames, aChannels);
+  }
+
+private:
+  RefPtr<MediaEngineAudioSource> mAudioSource;
+};
+
 class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource,
                                           public webrtc::VoEMediaProcess,
                                           private MediaConstraintsHelper
 {
 public:
   MediaEngineWebRTCMicrophoneSource(nsIThread* aThread,
                                     webrtc::VoiceEngine* aVoiceEnginePtr,
+                                    mozilla::AudioInput* aAudioInput,
                                     int aIndex,
                                     const char* name,
                                     const char* uuid)
     : MediaEngineAudioSource(kReleased)
     , mVoiceEngine(aVoiceEnginePtr)
+    , mAudioInput(aAudioInput)
     , mMonitor("WebRTCMic.Monitor")
     , mThread(aThread)
     , mCapIndex(aIndex)
     , mChannel(-1)
     , mNrAllocations(0)
     , mInitDone(false)
     , mStarted(false)
     , mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
     , mEchoOn(false), mAgcOn(false), mNoiseOn(false)
     , mEchoCancel(webrtc::kEcDefault)
     , mAGC(webrtc::kAgcDefault)
     , mNoiseSuppress(webrtc::kNsDefault)
     , mPlayoutDelay(0)
     , mNullTransport(nullptr) {
     MOZ_ASSERT(aVoiceEnginePtr);
+    MOZ_ASSERT(aAudioInput);
     mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
     mDeviceUUID.Assign(uuid);
+    mListener = new mozilla::WebRTCAudioDataListener(this);
     Init();
   }
 
   void GetName(nsAString& aName) override;
   void GetUUID(nsACString& aUUID) override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
@@ -169,16 +415,24 @@ public:
                   bool aNoiseOn, uint32_t aNoise,
                   int32_t aPlayoutDelay) override;
 
   void NotifyPull(MediaStreamGraph* aGraph,
                   SourceMediaStream* aSource,
                   TrackID aId,
                   StreamTime aDesiredTime) override;
 
+  // AudioDataListenerInterface methods
+  void NotifyOutputData(MediaStreamGraph* aGraph,
+                        AudioDataValue* aBuffer, size_t aFrames,
+                        uint32_t aChannels) override;
+  void NotifyInputData(MediaStreamGraph* aGraph,
+                       const AudioDataValue* aBuffer, size_t aFrames,
+                       uint32_t aChannels) override;
+
   bool IsFake() override {
     return false;
   }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Microphone;
   }
 
@@ -202,21 +456,26 @@ public:
 
 protected:
   ~MediaEngineWebRTCMicrophoneSource() { Shutdown(); }
 
 private:
   void Init();
 
   webrtc::VoiceEngine* mVoiceEngine;
+  RefPtr<mozilla::AudioInput> mAudioInput;
+  RefPtr<WebRTCAudioDataListener> mListener;
+
   ScopedCustomReleasePtr<webrtc::VoEBase> mVoEBase;
   ScopedCustomReleasePtr<webrtc::VoEExternalMedia> mVoERender;
   ScopedCustomReleasePtr<webrtc::VoENetwork> mVoENetwork;
   ScopedCustomReleasePtr<webrtc::VoEAudioProcessing> mVoEProcessing;
 
+  nsAutoPtr<AudioPacketizer<AudioDataValue, int16_t>> mPacketizer;
+
   // mMonitor protects mSources[] access/changes, and transitions of mState
   // from kStarted to kStopped (which are combined with EndTrack()).
   // mSources[] is accessed from webrtc threads.
   Monitor mMonitor;
   nsTArray<RefPtr<SourceMediaStream>> mSources;
   nsCOMPtr<nsIThread> mThread;
   int mCapIndex;
   int mChannel;
@@ -260,18 +519,19 @@ private:
     gFarendObserver = nullptr;
   }
 
   nsCOMPtr<nsIThread> mThread;
 
   // gUM runnables can e.g. Enumerate from multiple threads
   Mutex mMutex;
   webrtc::VoiceEngine* mVoiceEngine;
+  RefPtr<mozilla::AudioInput> mAudioInput;
   bool mAudioEngineInit;
-
+  bool mFullDuplex;
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineVideoSource> mVideoSources;
   nsRefPtrHashtable<nsStringHashKey, MediaEngineAudioSource> mAudioSources;
 };
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -197,20 +197,21 @@ MediaEngineWebRTCMicrophoneSource::GetUU
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Config(bool aEchoOn, uint32_t aEcho,
                                           bool aAgcOn, uint32_t aAGC,
                                           bool aNoiseOn, uint32_t aNoise,
                                           int32_t aPlayoutDelay)
 {
-  LOG(("Audio config: aec: %d, agc: %d, noise: %d",
+  LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d",
        aEchoOn ? aEcho : -1,
        aAgcOn ? aAGC : -1,
-       aNoiseOn ? aNoise : -1));
+       aNoiseOn ? aNoise : -1,
+       aPlayoutDelay));
 
   bool update_echo = (mEchoOn != aEchoOn);
   bool update_agc = (mAgcOn != aAgcOn);
   bool update_noise = (mNoiseOn != aNoiseOn);
   mEchoOn = aEchoOn;
   mAgcOn = aAgcOn;
   mNoiseOn = aNoiseOn;
 
@@ -283,18 +284,17 @@ uint32_t MediaEngineWebRTCMicrophoneSour
 nsresult
 MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                             const MediaEnginePrefs &aPrefs,
                                             const nsString& aDeviceId)
 {
   AssertIsOnOwningThread();
   if (mState == kReleased) {
     if (mInitDone) {
-      ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine));
-      if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) {
+      if (mAudioInput->SetRecordingDevice(mCapIndex)) {
         return NS_ERROR_FAILURE;
       }
       mState = kAllocated;
       LOG(("Audio device %d allocated", mCapIndex));
     } else {
       LOG(("Audio device is not initalized"));
       return NS_ERROR_FAILURE;
     }
@@ -377,16 +377,18 @@ MediaEngineWebRTCMicrophoneSource::Start
   }
   if (mVoEBase->StartSend(mChannel)) {
     return NS_ERROR_FAILURE;
   }
 
   // Attach external media processor, so this::Process will be called.
   mVoERender->RegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel, *this);
 
+  mAudioInput->StartRecording(aStream->Graph(), mListener);
+
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
 {
   AssertIsOnOwningThread();
   {
@@ -407,16 +409,18 @@ MediaEngineWebRTCMicrophoneSource::Stop(
     }
     if (!mVoEBase) {
       return NS_ERROR_FAILURE;
     }
 
     mState = kStopped;
   }
 
+  mAudioInput->StopRecording(aSource->Graph(), mListener);
+
   mVoERender->DeRegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel);
 
   if (mVoEBase->StopSend(mChannel)) {
     return NS_ERROR_FAILURE;
   }
   if (mVoEBase->StopReceive(mChannel)) {
     return NS_ERROR_FAILURE;
   }
@@ -437,16 +441,49 @@ MediaEngineWebRTCMicrophoneSource::Notif
                                               TrackID aID,
                                               StreamTime aDesiredTime)
 {
   // Ignore - we push audio data
   LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime));
 }
 
 void
+MediaEngineWebRTCMicrophoneSource::NotifyOutputData(MediaStreamGraph* aGraph,
+                                                    AudioDataValue* aBuffer,
+                                                    size_t aFrames,
+                                                    uint32_t aChannels)
+{
+}
+
+// Called back on GraphDriver thread
+void
+MediaEngineWebRTCMicrophoneSource::NotifyInputData(MediaStreamGraph* aGraph,
+                                                   const AudioDataValue* aBuffer,
+                                                   size_t aFrames,
+                                                   uint32_t aChannels)
+{
+  // This will call Process() with data coming out of the AEC/NS/AGC/etc chain
+  if (!mPacketizer ||
+      mPacketizer->PacketSize() != mSampleFrequency/100 ||
+      mPacketizer->Channels() != aChannels) {
+    // It's ok to drop the audio still in the packetizer here.
+    mPacketizer = new AudioPacketizer<AudioDataValue, int16_t>(mSampleFrequency/100, aChannels);
+   }
+
+  mPacketizer->Input(aBuffer, static_cast<uint32_t>(aFrames));
+
+  while (mPacketizer->PacketsAvailable()) {
+    uint32_t samplesPerPacket = mPacketizer->PacketSize() *
+                                mPacketizer->Channels();
+    int16_t* packet = mPacketizer->Output();
+    mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, mSampleFrequency, 0);
+  }
+}
+
+void
 MediaEngineWebRTCMicrophoneSource::Init()
 {
   mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
 
   mVoEBase->Init();
 
   mVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
   if (!mVoERender) {
@@ -470,26 +507,25 @@ MediaEngineWebRTCMicrophoneSource::Init(
   if (mVoENetwork->RegisterExternalTransport(mChannel, *mNullTransport)) {
     return;
   }
 
   mSampleFrequency = MediaEngine::DEFAULT_SAMPLE_RATE;
   LOG(("%s: sampling rate %u", __FUNCTION__, mSampleFrequency));
 
   // Check for availability.
-  ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine));
-  if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) {
+  if (mAudioInput->SetRecordingDevice(mCapIndex)) {
     return;
   }
 
 #ifndef MOZ_B2G
   // Because of the permission mechanism of B2G, we need to skip the status
   // check here.
   bool avail = false;
-  ptrVoEHw->GetRecordingDeviceStatus(avail);
+  mAudioInput->GetRecordingDeviceStatus(avail);
   if (!avail) {
     return;
   }
 #endif // MOZ_B2G
 
   // Set "codec" to PCM, 32kHz on 1 channel
   ScopedCustomReleasePtr<webrtc::VoECodec> ptrVoECodec(webrtc::VoECodec::GetInterface(mVoiceEngine));
   if (!ptrVoECodec) {
@@ -554,16 +590,19 @@ MediaEngineWebRTCMicrophoneSource::Shutd
   delete mNullTransport;
   mNullTransport = nullptr;
 
   mVoEProcessing = nullptr;
   mVoENetwork = nullptr;
   mVoERender = nullptr;
   mVoEBase = nullptr;
 
+  mAudioInput = nullptr;
+  mListener = nullptr; // breaks a cycle, since the WebRTCAudioDataListener has a RefPtr to us
+
   mState = kReleased;
   mInitDone = false;
 }
 
 typedef int16_t sample;
 
 void
 MediaEngineWebRTCMicrophoneSource::Process(int channel,
@@ -634,16 +673,17 @@ MediaEngineWebRTCMicrophoneSource::Proce
   return;
 }
 
 void
 MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName)
 {
   aName.AssignLiteral("AudioCapture");
 }
+
 void
 MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID)
 {
   nsID uuid;
   char uuidBuffer[NSID_LENGTH];
   nsCString asciiString;
   ErrorResult rv;
 
@@ -691,9 +731,10 @@ MediaEngineWebRTCAudioCaptureSource::Res
 uint32_t
 MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
     const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   // There is only one way of capturing audio for now, and it's always adequate.
   return 0;
 }
+
 }
--- a/dom/security/test/csp/test_child-src_worker.html
+++ b/dom/security/test/csp/test_child-src_worker.html
@@ -74,16 +74,34 @@
           policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
         },
         'other-src-shared_worker': {
           id: "other-src-shared_worker",
           file: SHARED_WORKER_TEST_FILE,
           result : "blocked",
           policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
         },
+        'script-src-worker': {
+          id: "script-src-worker",
+          file: WORKER_TEST_FILE,
+          result : "blocked",
+          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+        },
+        'script-src-service_worker': {
+          id: "script-src-service_worker",
+          file: SERVICE_WORKER_TEST_FILE,
+          result : "blocked",
+          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+        },
+        'script-src-self-shared_worker': {
+          id: "script-src-self-shared_worker",
+          file: SHARED_WORKER_TEST_FILE,
+          result : "blocked",
+          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+        },
       };
 
       finished = {};
 
       function recvMessage(ev) {
         is(ev.data.message, tests[ev.data.id].result, "CSP child-src worker test " + ev.data.id);
         finished[ev.data.id] = ev.data.message;
 
--- a/dom/system/nsDeviceSensors.cpp
+++ b/dom/system/nsDeviceSensors.cpp
@@ -193,16 +193,22 @@ WindowCannotReceiveSensorEvent (nsPIDOMW
   }
 
   return false;
 }
 
 // Holds the device orientation in Euler angle degrees (azimuth, pitch, roll).
 struct Orientation
 {
+  enum OrientationReference
+  {
+    kRelative = 0,
+    kAbsolute
+  };
+
   static Orientation RadToDeg(const Orientation& aOrient)
   {
     const static double kRadToDeg = 180.0 / M_PI;
     return { aOrient.alpha * kRadToDeg,
              aOrient.beta * kRadToDeg,
              aOrient.gamma * kRadToDeg };
   }
 
@@ -277,20 +283,25 @@ nsDeviceSensors::Notify(const mozilla::h
 
     if (nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(pwindow->GetDoc())) {
       nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(windowListeners[i]);
       if (type == nsIDeviceSensorData::TYPE_ACCELERATION ||
         type == nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION ||
         type == nsIDeviceSensorData::TYPE_GYROSCOPE) {
         FireDOMMotionEvent(domDoc, target, type, x, y, z);
       } else if (type == nsIDeviceSensorData::TYPE_ORIENTATION) {
-        FireDOMOrientationEvent(target, x, y, z);
+        FireDOMOrientationEvent(target, x, y, z, Orientation::kAbsolute);
       } else if (type == nsIDeviceSensorData::TYPE_ROTATION_VECTOR) {
         const Orientation orient = RotationVectorToOrientation(x, y, z, w);
-        FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma);
+        FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma,
+                                Orientation::kAbsolute);
+      } else if (type == nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR) {
+        const Orientation orient = RotationVectorToOrientation(x, y, z, w);
+        FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma,
+                                Orientation::kRelative);
       } else if (type == nsIDeviceSensorData::TYPE_PROXIMITY) {
         FireDOMProximityEvent(target, x, y, z);
       } else if (type == nsIDeviceSensorData::TYPE_LIGHT) {
         FireDOMLightEvent(target, x);
       }
     }
   }
 }
@@ -363,37 +374,56 @@ nsDeviceSensors::FireDOMUserProximityEve
   bool defaultActionEnabled;
   aTarget->DispatchEvent(event, &defaultActionEnabled);
 }
 
 void
 nsDeviceSensors::FireDOMOrientationEvent(EventTarget* aTarget,
                                          double aAlpha,
                                          double aBeta,
-                                         double aGamma)
+                                         double aGamma,
+                                         bool aIsAbsolute)
 {
   DeviceOrientationEventInit init;
   init.mBubbles = true;
   init.mCancelable = false;
   init.mAlpha.SetValue(aAlpha);
   init.mBeta.SetValue(aBeta);
   init.mGamma.SetValue(aGamma);
-  init.mAbsolute = true;
+  init.mAbsolute = aIsAbsolute;
+
+  auto Dispatch = [&](EventTarget* aEventTarget, const nsAString& aType)
+  {
+    RefPtr<DeviceOrientationEvent> event =
+      DeviceOrientationEvent::Constructor(aEventTarget, aType, init);
+    event->SetTrusted(true);
+    bool dummy;
+    aEventTarget->DispatchEvent(event, &dummy);
+  };
+
+  Dispatch(aTarget, aIsAbsolute ? NS_LITERAL_STRING("absolutedeviceorientation") :
+                                  NS_LITERAL_STRING("deviceorientation"));
 
-  RefPtr<DeviceOrientationEvent> event =
-    DeviceOrientationEvent::Constructor(aTarget,
-                                        NS_LITERAL_STRING("deviceorientation"),
-                                        init);
-  event->SetTrusted(true);
+  // This is used to determine whether relative events have been dispatched
+  // during the current session, in which case we don't dispatch the additional
+  // compatibility events.
+  static bool sIsDispatchingRelativeEvents = false;
+  sIsDispatchingRelativeEvents = sIsDispatchingRelativeEvents || !aIsAbsolute;
 
-  bool dummy;
-  aTarget->DispatchEvent(event, &dummy);
+  // Android devices with SENSOR_GAME_ROTATION_VECTOR support dispatch
+  // relative events for "deviceorientation" by default, while other platforms
+  // and devices without such support dispatch absolute events by default.
+  if (aIsAbsolute && !sIsDispatchingRelativeEvents) {
+    // For absolute events on devices without support for relative events,
+    // we need to additionally dispatch type "deviceorientation" to keep
+    // backwards-compatibility.
+    Dispatch(aTarget, NS_LITERAL_STRING("deviceorientation"));
+  }
 }
 
-
 void
 nsDeviceSensors::FireDOMMotionEvent(nsIDOMDocument *domdoc,
                                     EventTarget* target,
                                     uint32_t type,
                                     double x,
                                     double y,
                                     double z)
 {
--- a/dom/system/nsDeviceSensors.h
+++ b/dom/system/nsDeviceSensors.h
@@ -50,19 +50,20 @@ private:
                              double aValue,
                              double aMin,
                              double aMax);
 
   void FireDOMUserProximityEvent(mozilla::dom::EventTarget* aTarget,
                                  bool aNear);
 
   void FireDOMOrientationEvent(mozilla::dom::EventTarget* target,
-                               double alpha,
-                               double beta,
-                               double gamma);
+                               double aAlpha,
+                               double aBeta,
+                               double aGamma,
+                               bool aIsAbsolute);
 
   void FireDOMMotionEvent(class nsIDOMDocument *domDoc,
                           mozilla::dom::EventTarget* target,
                           uint32_t type,
                           double x,
                           double y,
                           double z);
 
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -2,29 +2,31 @@
 skip-if = e10s # Bug ?????? - most of these tests fail for currently unknown reasons.
 support-files =
   browser_frame_elements.html
   page_privatestorageevent.html
   position.html
   test-console-api.html
   test_bug1004814.html
   worker_bug1004814.js
+  geo_leak_test.html
 
 [browser_test_toolbars_visibility.js]
 support-files =
   test_new_window_from_content_child.html
 [browser_bug1008941_dismissGeolocationHanger.js]
 skip-if = buildapp == 'mulet'
 [browser_test__content.js]
 [browser_ConsoleAPITests.js]
 [browser_ConsoleStorageAPITests.js]
 [browser_ConsoleStoragePBTest_perwindowpb.js]
 [browser_autofocus_background.js]
 skip-if= buildapp == 'mulet'
 [browser_autofocus_preference.js]
+[browser_bug1238427.js]
 [browser_bug396843.js]
 [browser_focus_steal_from_chrome.js]
 [browser_focus_steal_from_chrome_during_mousedown.js]
 [browser_frame_elements.js]
 [browser_localStorage_privatestorageevent.js]
 [browser_test_new_window_from_content.js]
 skip-if = (toolkit == 'android' || buildapp == 'b2g' || buildapp == 'mulet')
 support-files =
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_bug1238427.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const TEST_URI = "http://example.com/" +
+                 "browser/dom/tests/browser/geo_leak_test.html";
+
+const BASE_GEO_URL = "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs";
+
+add_task(function* () {
+  Services.prefs.setBoolPref("geo.prompt.testing", true);
+  Services.prefs.setBoolPref("geo.prompt.testing.allow", true);
+
+  // Make the geolocation provider responder very slowly to ensure that
+  // it does not reply before we close the tab.
+  Services.prefs.setCharPref("geo.wifi.uri", BASE_GEO_URL + "?delay=100000");
+
+  // Open the test URI and close it. The test harness will make sure that the
+  // page is cleaned up after some GCs. If geolocation is not shut down properly,
+  // it will show up as a non-shutdown leak.
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: TEST_URI
+  }, function* (browser) { /* ... */ });
+
+  ok(true, "Need to do something in this test");
+});
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/geo_leak_test.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>Geolocation incomplete position leak test</title>
+<script type="text/javascript">
+
+function successCallback(position) {}
+function errorCallback() {}
+
+function init() {
+  navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
+}
+</script>
+</head>
+<body onload="init()">
+</body>
+</html>
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -71,17 +71,16 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug396843.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug400204.html]
 [test_bug404748.html]
 [test_bug406375.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s
 [test_bug411103.html]
 [test_bug414291.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog)
 [test_bug427744.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug42976.html]
 [test_bug430276.html]
 [test_bug437361.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-debug(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-desktop(dom.disable_open_during_load not implemented in b2g, showmodaldialog)
 [test_bug440572.html]
 [test_bug456151.html]
@@ -113,17 +112,17 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug583225.html]
 [test_bug585240.html]
 [test_bug585819.html]
 [test_bug593174.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug597809.html]
 skip-if = toolkit == 'android'
 [test_bug61098.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(showmodaldialog) b2g-debug(showmodaldialog) b2g-desktop(showmodaldialog)
 [test_bug612267.html]
 [test_bug617296.html]
 [test_bug620947.html]
 [test_bug622361.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug633133.html]
 [test_bug641552.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android'
--- a/dom/tests/mochitest/bugs/test_bug414291.html
+++ b/dom/tests/mochitest/bugs/test_bug414291.html
@@ -14,20 +14,22 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 414291 **/
 
 var result1 = 0;
 var result2 = 0;
 var result3 = 0;
 
 window.open("data:text/html,<html><body onload='close(); opener.result1 = 1;'>", "w1");
-is(result1, 0, "window either opened as modal or loaded synchronously.");
+is(result1, 0, "window should not be opened either as modal or loaded synchronously.");
 
 window.open("data:text/html,<html><body onload='close(); opener.result2 = 2;'>", "w2", "modal=yes");
-is(result2, 0, "window either opened as modal or data loaded synchronously.");
+is(result2, 0, "window should not be opened either as modal or data loaded synchronously.");
 
-result3 = window.showModalDialog("data:text/html,<html><body onload='close(); returnValue = 3;'>");
-is(result3, 3, "window didn't open as modal.");
+if (window.showModalDialog) {
+  result3 = window.showModalDialog("data:text/html,<html><body onload='close(); returnValue = 3;'>");
+  is(result3, 3, "window should be opened as modal.");
+}
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/bugs/test_bug61098.html
+++ b/dom/tests/mochitest/bugs/test_bug61098.html
@@ -9,17 +9,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/MockObjects.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="runtests();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=61098">Mozilla Bug 61098</a>
 <p id="display">
 </p>
-<div id="content" style="display: none"> 
+<div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 <script class="testbody" type="text/javascript">
 /** Test for Bug 61098 **/
 
 SimpleTest.waitForExplicitFinish();
 
@@ -212,38 +212,27 @@ function registerMockPromptService()
   mockPromptFactoryRegisterer =
     new MockObjectRegisterer("@mozilla.org/prompter;1",
                              MockPromptService);
 
   mockPromptServiceRegisterer.register();
   mockPromptFactoryRegisterer.register();
 };
 
-function enableDialogLoopBlocking()
-{
-  var prefs = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].
-    getService(SpecialPowers.Ci.nsIPrefBranch);
-
-  prefs.setIntPref("dom.successive_dialog_time_limit", 3);
-}
-
-function resetDialogLoopBlocking()
-{
-  var prefs = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].
-    getService(SpecialPowers.Ci.nsIPrefBranch);
-
-  prefs.setIntPref("dom.successive_dialog_time_limit", 0);
-}
-
 var expectedState;
 
 function runtests()
 {
+  SpecialPowers.pushPrefEnv({'set': [["dom.successive_dialog_time_limit", 3]]},
+                            runtestsInner);
+}
+
+function runtestsInner()
+{
   registerMockPromptService();
-  enableDialogLoopBlocking();
 
   // Test that alert() works normally and then gets blocked on the
   // second call.
   w = window.open();
   w.alert("alert message 1");
   is (promptState.method, "alert", "Wrong prompt method called");
   is (promptState.parent, w, "Wrong alert parent");
   is (promptState.msg, "alert message 1", "Wrong alert message");
@@ -314,36 +303,35 @@ function runtests()
   }
 
   is (promptState, void(0), "Wrong prompt state after blocked prompt()");
 
   w.close();
 
   // Test that showModalDialog() works normally and then gets blocked
   // on the second call.
-  w = window.open();
-  w.showModalDialog("data:text/html,%3Cscript>window.close();%3C/script>")
-  is (promptState, void(0), "Wrong prompt state");
-
-  // Test that showModalDialog() works normally and then gets blocked
-  // on the second call.
-  try {
+  if (window.showModalDialog) {
+    w = window.open();
     w.showModalDialog("data:text/html,%3Cscript>window.close();%3C/script>")
-  } catch(e) {
-    is(e.name, "NS_ERROR_NOT_AVAILABLE", "Wrong exception");
+    is (promptState, void(0), "Wrong prompt state");
+
+    try {
+      w.showModalDialog("data:text/html,%3Cscript>window.close();%3C/script>")
+      ok(false, "showModalDialog call should throw an exception");
+    } catch(e) {
+      is(e.name, "NS_ERROR_NOT_AVAILABLE", "Wrong exception");
+    }
+    is (promptState.method, "confirm", "Wrong prompt method called");
+    is (promptState.parent, w, "Wrong confirm parent");
+    is (promptState.msg, "Prevent this page from creating additional dialogs",
+        "Wrong confirm message");
+    promptState = void(0);
+
+    w.close();
   }
-  is (promptState.method, "confirm", "Wrong prompt method called");
-  is (promptState.parent, w, "Wrong confirm parent");
-  is (promptState.msg, "Prevent this page from creating additional dialogs",
-      "Wrong confirm message");
-  promptState = void(0);
-
-  w.close();
-
-  resetDialogLoopBlocking();
 
   mockPromptFactoryRegisterer.unregister();
   mockPromptServiceRegisterer.unregister();
 
   SimpleTest.finish();
 }
 
 </script>
--- a/dom/tests/mochitest/fetch/mochitest.ini
+++ b/dom/tests/mochitest/fetch/mochitest.ini
@@ -38,17 +38,17 @@ skip-if = (e10s && debug && os == 'win')
 skip-if = buildapp == 'b2g' || (e10s && debug && os == 'win') # Bug 1137683
 [test_fetch_basic_http_sw_empty_reroute.html]
 skip-if = buildapp == 'b2g'
 [test_fetch_cors.html]
 skip-if = buildapp == 'b2g' || (toolkit == 'android' && debug) || (e10s && debug && os == 'win') # Bug 1210552 && 1210282
 [test_fetch_cors_sw_reroute.html]
 skip-if = buildapp == 'b2g' || (toolkit == 'android' && debug) || (e10s && debug && os == 'win') # Bug 1137683 && 1210282
 [test_fetch_cors_sw_empty_reroute.html]
-skip-if = buildapp == 'b2g'
+skip-if = buildapp == 'b2g' || (toolkit == 'android' && debug) # Bug 1210282
 [test_formdataparsing.html]
 skip-if = (e10s && debug && os == 'win')
 [test_formdataparsing_sw_reroute.html]
 skip-if = buildapp == 'b2g' || (e10s && debug && os == 'win') # Bug 1137683
 [test_request.html]
 skip-if = (e10s && debug && os == 'win')
 [test_request_cache.html]
 skip-if = (e10s && debug && os == 'win')
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -349,16 +349,17 @@ partial interface Window {
    */
   [ChromeOnly, Throws] readonly attribute MozSelfSupport MozSelfSupport;
 
   [Pure]
            attribute EventHandler onwheel;
 
            attribute EventHandler ondevicemotion;
            attribute EventHandler ondeviceorientation;
+           attribute EventHandler onabsolutedeviceorientation;
            attribute EventHandler ondeviceproximity;
            attribute EventHandler onuserproximity;
            attribute EventHandler ondevicelight;
 
 #ifdef MOZ_B2G
            attribute EventHandler onmoztimechange;
            attribute EventHandler onmoznetworkupload;
            attribute EventHandler onmoznetworkdownload;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -163,33 +163,42 @@ public:
     RegisterJob,
     UpdateJob,
     InstallJob,
     UnregisterJob
   };
 
   virtual void Start() = 0;
 
+  void
+  Cancel()
+  {
+    mQueue = nullptr;
+    mCanceled = true;
+  }
+
   bool
   IsRegisterOrInstallJob() const
   {
     return mJobType == RegisterJob || mJobType == UpdateJob ||
       mJobType == InstallJob;
   }
 
 protected:
   // The queue keeps the jobs alive, so they can hold a rawptr back to the
   // queue.
   ServiceWorkerJobQueue* mQueue;
 
   Type mJobType;
+  bool mCanceled;
 
   explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue, Type aJobType)
     : mQueue(aQueue)
     , mJobType(aJobType)
+    , mCanceled(false)
   {}
 
   virtual ~ServiceWorkerJob()
   {}
 
   void
   Done(nsresult aStatus);
 };
@@ -293,16 +302,19 @@ private:
 
   void
   Done(ServiceWorkerJob* aJob)
   {
     MOZ_ASSERT(aJob);
     QueueData& queue = GetQueue(aJob->mJobType);
     MOZ_ASSERT(!queue.mJobs.IsEmpty());
     MOZ_ASSERT(queue.mJobs[0] == aJob);
+    if (NS_WARN_IF(queue.mJobs[0] != aJob)) {
+      return;
+    }
     Pop(queue);
   }
 };
 
 namespace {
 
 nsresult
 PopulateRegistrationData(nsIPrincipal* aPrincipal,
@@ -944,37 +956,28 @@ public:
                        ServiceWorkerUpdateFinishCallback* aCallback,
                        ServiceWorkerInfo* aServiceWorkerInfo)
     : ServiceWorkerJob(aQueue, aJobType)
     , mPrincipal(aPrincipal)
     , mScope(aScope)
     , mScriptSpec(aScriptSpec)
     , mCallback(aCallback)
     , mUpdateAndInstallInfo(aServiceWorkerInfo)
-    , mCanceled(false)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aPrincipal);
   }
 
-  void
-  Cancel()
-  {
-    mQueue = nullptr;
-    mCanceled = true;
-  }
-
 protected:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   const nsCString mScope;
   const nsCString mScriptSpec;
   RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
   RefPtr<ServiceWorkerInfo> mUpdateAndInstallInfo;
-  bool mCanceled;
 
   ~ServiceWorkerJobBase()
   { }
 
   // Ensure that mRegistration is set for the job.  Also, if mRegistration was
   // already set, ensure that a new registration object has not replaced it in
   // the ServiceWorkerManager.  This can happen when jobs race such that the
   // registration is cleared and recreated while an update job is executing.
@@ -1606,23 +1609,20 @@ ServiceWorkerJobQueue::CancelJobs()
 void
 ServiceWorkerJobQueue::CancelJobs(QueueData& aQueue)
 {
   if (aQueue.mJobs.IsEmpty()) {
     return;
   }
 
   // We have to treat the first job specially. It is the running job and needs
-  // to be notified correctly.
-  RefPtr<ServiceWorkerJob> runningJob = aQueue.mJobs[0];
-  // We can just let an Unregister job run to completion.
-  if (runningJob->IsRegisterOrInstallJob()) {
-    ServiceWorkerJobBase* job = static_cast<ServiceWorkerJobBase*>(runningJob.get());
-    job->Cancel();
-  }
+  // to be notified correctly.  Even if the job continues some work in the
+  // background, this still needs to be done to let the job know its no longer
+  // in the queue.
+  aQueue.mJobs[0]->Cancel();
 
   // Get rid of everything. Non-main thread objects may still be holding a ref
   // to the running register job. Since we called Cancel() on it, the job's
   // main thread functions will just exit.
   aQueue.mJobs.Clear();
 }
 
 NS_IMETHODIMP
@@ -2456,16 +2456,20 @@ public:
 
 private:
   // You probably want UnregisterAndDone().
   nsresult
   Unregister()
   {
     AssertIsOnMainThread();
 
+    if (mCanceled) {
+      return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK;
+    }
+
     PrincipalInfo principalInfo;
     if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(mPrincipal,
                                                       &principalInfo)))) {
       return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK;
     }
 
     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/file_chromecommon.js
@@ -0,0 +1,15 @@
+let { classes: Cc, utils: Cu, interfaces: Ci } = Components;
+
+let cs = Cc["@mozilla.org/cookiemanager;1"]
+           .getService(Ci.nsICookieManager2);
+
+addMessageListener("getCookieCountAndClear", () => {
+  let count = 0;
+  for (let list = cs.enumerator; list.hasMoreElements(); list.getNext())
+    ++count;
+  cs.removeAll();
+
+  sendAsyncMessage("getCookieCountAndClear:return", { count });
+});
+
+cs.removeAll();
--- a/extensions/cookie/test/file_testcommon.js
+++ b/extensions/cookie/test/file_testcommon.js
@@ -1,50 +1,48 @@
+const SCRIPT_URL = SimpleTest.getTestFileURL("file_chromecommon.js");
+
 var gExpectedCookies;
 var gExpectedLoads;
 
 var gPopup;
 
+var gScript;
+
 var gLoads = 0;
 
 function setupTest(uri, cookies, loads) {
   SimpleTest.waitForExplicitFinish();
 
-  SpecialPowers.Cc["@mozilla.org/preferences-service;1"]
-               .getService(SpecialPowers.Ci.nsIPrefBranch)
-               .setIntPref("network.cookie.cookieBehavior", 1);
+  var prefSet = new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({ set: [["network.cookie.cookieBehavior", 1]] }, resolve);
+  });
 
-  var cs = SpecialPowers.Cc["@mozilla.org/cookiemanager;1"]
-                        .getService(SpecialPowers.Ci.nsICookieManager2);
-  cs.removeAll();
-
+  gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
   gExpectedCookies = cookies;
   gExpectedLoads = loads;
 
   // Listen for MessageEvents.
   window.addEventListener("message", messageReceiver, false);
 
-  // load a window which contains an iframe; each will attempt to set
-  // cookies from their respective domains.
-  gPopup = window.open(uri, 'hai', 'width=100,height=100');
+  prefSet.then(() => {
+    // load a window which contains an iframe; each will attempt to set
+    // cookies from their respective domains.
+    gPopup = window.open(uri, 'hai', 'width=100,height=100');
+  });
 }
 
-function finishTest()
-{
-  SpecialPowers.Cc["@mozilla.org/preferences-service;1"]
-               .getService(SpecialPowers.Ci.nsIPrefBranch)
-               .clearUserPref("network.cookie.cookieBehavior");
-
+function finishTest() {
+  gScript.destroy();
   SimpleTest.finish();
 }
 
 /** Receives MessageEvents to this window. */
 // Count and check loads.
-function messageReceiver(evt)
-{
+function messageReceiver(evt) {
   is(evt.data, "message", "message data received from popup");
   if (evt.data != "message") {
     gPopup.close();
     window.removeEventListener("message", messageReceiver, false);
 
     finishTest();
     return;
   }
@@ -59,18 +57,14 @@ function messageReceiver(evt)
 }
 
 // runTest() is run by messageReceiver().
 // Count and check cookies.
 function runTest() {
   // set a cookie from a domain of "localhost"
   document.cookie = "oh=hai";
 
-  var cs = SpecialPowers.Cc["@mozilla.org/cookiemanager;1"]
-                        .getService(SpecialPowers.Ci.nsICookieManager);
-  var count = 0;
-  for(var list = cs.enumerator; list.hasMoreElements(); list.getNext())
-    ++count;
-  is(count, gExpectedCookies, "total number of cookies");
-  cs.removeAll();
-
-  finishTest();
+  gScript.addMessageListener("getCookieCountAndClear:return", ({ count }) => {
+    is(count, gExpectedCookies, "total number of cookies");
+    finishTest();
+  });
+  gScript.sendAsyncMessage("getCookieCountAndClear");
 }
--- a/extensions/cookie/test/file_testloadflags.js
+++ b/extensions/cookie/test/file_testloadflags.js
@@ -1,143 +1,82 @@
+const SCRIPT_URL = SimpleTest.getTestFileURL('file_testloadflags_chromescript.js');
+
 var gExpectedCookies;
 var gExpectedHeaders;
 var gExpectedLoads;
 
 var gObs;
 var gPopup;
 
 var gHeaders = 0;
 var gLoads = 0;
 
 // setupTest() is run from 'onload='.
 function setupTest(uri, domain, cookies, loads, headers) {
-  ok(true, "setupTest uri: " + uri + " domain: " + domain + " cookies: " + cookies +
-           " loads: " + loads + " headers: " + headers);
+  info("setupTest uri: " + uri + " domain: " + domain + " cookies: " + cookies +
+       " loads: " + loads + " headers: " + headers);
 
   SimpleTest.waitForExplicitFinish();
 
-  SpecialPowers.Cc["@mozilla.org/preferences-service;1"]
-               .getService(SpecialPowers.Ci.nsIPrefBranch)
-               .setIntPref("network.cookie.cookieBehavior", 1);
-
-  var cs = SpecialPowers.Cc["@mozilla.org/cookiemanager;1"]
-                        .getService(SpecialPowers.Ci.nsICookieManager2);
-
-  ok(true, "we are going to remove these cookies");
-  var count = 0;
-  var list = cs.enumerator;
-  while (list.hasMoreElements()) {
-    var cookie = list.getNext().QueryInterface(SpecialPowers.Ci.nsICookie);
-    ok(true, "cookie: " + cookie);
-    ok(true, "cookie host " + cookie.host + " path " + cookie.path + " name " + cookie.name +
-       " value " + cookie.value + " isSecure " + cookie.isSecure + " expires " + cookie.expires);
-    ++count;
-  }
-  ok(true, count + " cookies");
-
-  cs.removeAll();
-  cs.add(domain, "", "oh", "hai", false, false, true, Math.pow(2, 62));
-  is(cs.countCookiesFromHost(domain), 1, "number of cookies for domain " + domain);
+  var prefSet = new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({ set: [["network.cookie.cookieBehavior", 1]] }, resolve);
+  });
 
   gExpectedCookies = cookies;
   gExpectedLoads = loads;
   gExpectedHeaders = headers;
 
-  gObs = new obs();
+  gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
+  gScript.addMessageListener("info", ({ str }) => info(str));
+  gScript.addMessageListener("ok", ({ c, m }) => ok(c, m));
+  gScript.addMessageListener("observer:gotCookie", ({ cookie, uri }) => {
+    isnot(cookie.indexOf("oh=hai"), -1,
+          "cookie 'oh=hai' is in header for " + uri);
+    ++gHeaders;
+  });
+
+  var scriptReady = new Promise(resolve => {
+    gScript.addMessageListener("init:return", resolve);
+    gScript.sendAsyncMessage("init", { domain });
+  });
+
   // Listen for MessageEvents.
   window.addEventListener("message", messageReceiver, false);
 
-  // load a window which contains an iframe; each will attempt to set
-  // cookies from their respective domains.
-  gPopup = window.open(uri, 'hai', 'width=100,height=100');
+  Promise.all([ prefSet, scriptReady ]).then(() => {
+    // load a window which contains an iframe; each will attempt to set
+    // cookies from their respective domains.
+    gPopup = window.open(uri, 'hai', 'width=100,height=100');
+  });
 }
 
 function finishTest()
 {
-  gObs.remove();
-
-  SpecialPowers.Cc["@mozilla.org/preferences-service;1"]
-               .getService(SpecialPowers.Ci.nsIPrefBranch)
-               .clearUserPref("network.cookie.cookieBehavior");
-
-  SimpleTest.finish();
-}
-
-// Count headers.
-function obs () {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-
-  ok(true, "adding observer");
-
-  this.window = window;
-  this.os = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
-                         .getService(SpecialPowers.Ci.nsIObserverService);
-  this.os.addObserver(this, "http-on-modify-request", false);
-}
-
-obs.prototype = {
-  observe: function obs_observe (theSubject, theTopic, theData)
-  {
-    ok(true, "theSubject " + theSubject);
-    ok(true, "theTopic " + theTopic);
-    ok(true, "theData " + theData);
-
-    var channel = theSubject.QueryInterface(
-                    this.window.SpecialPowers.Ci.nsIHttpChannel);
-    ok(true, "channel " + channel);
-    try {
-      ok(true, "channel.URI " + channel.URI);
-      ok(true, "channel.URI.spec " + channel.URI.spec);
-      channel.visitRequestHeaders({
-        visitHeader: function(aHeader, aValue) {
-          ok(true, aHeader + ": " + aValue);
-        }});
-    } catch (err) {
-      ok(false, "catch error " + err);
-    }
-
-    // Ignore notifications we don't care about (like favicons)
-    if (channel.URI.spec.indexOf(
-          "http://example.org/tests/extensions/cookie/test/") == -1) {
-      ok(true, "ignoring this one");
-      return;
-    }
-
-    this.window.isnot(channel.getRequestHeader("Cookie").indexOf("oh=hai"), -1,
-                      "cookie 'oh=hai' is in header for " + channel.URI.spec);
-    ++gHeaders;
-  },
-
-  remove: function obs_remove()
-  {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-
-    ok(true, "removing observer");
-
-    this.os.removeObserver(this, "http-on-modify-request");
-    this.os = null;
-    this.window = null;
-  }
+  gScript.addMessageListener("shutdown:return", () => {
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+  gScript.sendAsyncMessage("shutdown");
 }
 
 /** Receives MessageEvents to this window. */
 // Count and check loads.
 function messageReceiver(evt)
 {
   ok(evt.data == "f_lf_i msg data img" || evt.data == "f_lf_i msg data page",
      "message data received from popup");
   if (evt.data == "f_lf_i msg data img") {
-    ok(true, "message data received from popup for image");
+    info("message data received from popup for image");
   }
   if (evt.data == "f_lf_i msg data page") {
-    ok(true, "message data received from popup for page");
+    info("message data received from popup for page");
   }
   if (evt.data != "f_lf_i msg data img" && evt.data != "f_lf_i msg data page") {
-    ok(true, "got this message but don't know what it is " + evt.data);
+    info("got this message but don't know what it is " + evt.data);
     gPopup.close();
     window.removeEventListener("message", messageReceiver, false);
 
     finishTest();
     return;
   }
 
   // only run the test when all our children are done loading & setting cookies
@@ -151,25 +90,15 @@ function messageReceiver(evt)
 
 // runTest() is run by messageReceiver().
 // Check headers, and count and check cookies.
 function runTest() {
   // set a cookie from a domain of "localhost"
   document.cookie = "o=noes";
 
   is(gHeaders, gExpectedHeaders, "number of observed request headers");
+  gScript.addMessageListener("getCookieCount:return", ({ count }) => {
+    is(count, gExpectedCookies, "total number of cookies");
+    finishTest();
+  });
 
-  var cs = SpecialPowers.Cc["@mozilla.org/cookiemanager;1"]
-                        .getService(SpecialPowers.Ci.nsICookieManager);
-  var count = 0;
-  var list = cs.enumerator;
-  while (list.hasMoreElements()) {
-    var cookie = list.getNext().QueryInterface(SpecialPowers.Ci.nsICookie);
-    ok(true, "cookie: " + cookie);
-    ok(true, "cookie host " + cookie.host + " path " + cookie.path + " name " + cookie.name +
-       " value " + cookie.value + " isSecure " + cookie.isSecure + " expires " + cookie.expires);
-    ++count;
-  }
-  is(count, gExpectedCookies, "total number of cookies");
-  cs.removeAll();
-
-  finishTest();
+  gScript.sendAsyncMessage("getCookieCount");
 }
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/file_testloadflags_chromescript.js
@@ -0,0 +1,112 @@
+let { classes: Cc, interfaces: Ci } = Components;
+
+var gObs;
+
+function info(s) {
+  sendAsyncMessage("info", { str: String(s) });
+}
+
+function ok(c, m) {
+  sendAsyncMessage("ok", { c, m });
+}
+
+function is(a, b, m) {
+  ok(Object.is(a, b), m + " (" + a + " === " + b + ")");
+}
+
+// Count headers.
+function obs() {
+  info("adding observer");
+
+  this.os = Cc["@mozilla.org/observer-service;1"]
+              .getService(Ci.nsIObserverService);
+  this.os.addObserver(this, "http-on-modify-request", false);
+}
+
+obs.prototype = {
+  observe(theSubject, theTopic, theData) {
+    info("theSubject " + theSubject);
+    info("theTopic " + theTopic);
+    info("theData " + theData);
+
+    var channel = theSubject.QueryInterface(Ci.nsIHttpChannel);
+    info("channel " + channel);
+    try {
+      info("channel.URI " + channel.URI);
+      info("channel.URI.spec " + channel.URI.spec);
+      channel.visitRequestHeaders({
+        visitHeader: function(aHeader, aValue) {
+          info(aHeader + ": " + aValue);
+        }});
+    } catch (err) {
+      ok(false, "catch error " + err);
+    }
+
+    // Ignore notifications we don't care about (like favicons)
+    if (channel.URI.spec.indexOf(
+          "http://example.org/tests/extensions/cookie/test/") == -1) {
+      info("ignoring this one");
+      return;
+    }
+
+    sendAsyncMessage("observer:gotCookie",
+                     { cookie: channel.getRequestHeader("Cookie"),
+                       uri: channel.URI.spec });
+  },
+
+  remove() {
+    info("removing observer");
+
+    this.os.removeObserver(this, "http-on-modify-request");
+    this.os = null;
+  }
+}
+
+function getCookieCount(cs) {
+  let count = 0;
+  let list = cs.enumerator;
+  while (list.hasMoreElements()) {
+    let cookie = list.getNext().QueryInterface(Ci.nsICookie);
+    info("cookie: " + cookie);
+    info("cookie host " + cookie.host + " path " + cookie.path + " name " + cookie.name +
+         " value " + cookie.value + " isSecure " + cookie.isSecure + " expires " + cookie.expires);
+    ++count;
+  }
+
+  return count;
+}
+
+addMessageListener("init", ({ domain }) => {
+  let cs = Cc["@mozilla.org/cookiemanager;1"]
+             .getService(Ci.nsICookieManager2);
+
+  info("we are going to remove these cookies");
+
+  let count = getCookieCount(cs);
+  info(count + " cookies");
+
+  cs.removeAll();
+  cs.add(domain, "", "oh", "hai", false, false, true, Math.pow(2, 62));
+  is(cs.countCookiesFromHost(domain), 1, "number of cookies for domain " + domain);
+
+  gObs = new obs();
+  sendAsyncMessage("init:return");
+});
+
+addMessageListener("getCookieCount", () => {
+  let cs = Cc["@mozilla.org/cookiemanager;1"]
+             .getService(Ci.nsICookieManager);
+  let count = getCookieCount(cs);
+
+  cs.removeAll();
+  sendAsyncMessage("getCookieCount:return", { count });
+});
+
+addMessageListener("shutdown", () => {
+  gObs.remove();
+
+  let cs = Cc["@mozilla.org/cookiemanager;1"]
+             .getService(Ci.nsICookieManager2);
+  cs.removeAll();
+  sendAsyncMessage("shutdown:return");
+});
--- a/extensions/cookie/test/mochitest.ini
+++ b/extensions/cookie/test/mochitest.ini
@@ -1,28 +1,30 @@
 [DEFAULT]
-skip-if = buildapp == 'b2g' || e10s
+skip-if = buildapp == 'b2g'
 support-files =
   beltzner.jpg
   beltzner.jpg^headers^
   damonbowling.jpg
   damonbowling.jpg^headers^
+  file_chromecommon.js
   file_domain_hierarchy_inner.html
   file_domain_hierarchy_inner_inner.html
   file_domain_hierarchy_inner_inner_inner.html
   file_domain_inner.html
   file_domain_inner_inner.html
   file_image_inner.html
   file_image_inner_inner.html
   file_loadflags_inner.html
   file_localhost_inner.html
   file_loopback_inner.html
   file_subdomain_inner.html
   file_testcommon.js
   file_testloadflags.js
+  file_testloadflags_chromescript.js
   image1.png
   image1.png^headers^
   image2.png
   image2.png^headers^
   test1.css
   test1.css^headers^
   test2.css
   test2.css^headers^
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -608,17 +608,17 @@ DrawTargetCairo::~DrawTargetCairo()
     mSurface = nullptr;
   }
   MOZ_ASSERT(!mLockedBits);
 }
 
 bool
 DrawTargetCairo::IsValid() const
 {
-  return mSurface && !cairo_surface_status(mSurface);
+  return mSurface && !cairo_surface_status(mSurface) && !cairo_surface_status(cairo_get_group_target(mContext));
 }
 
 DrawTargetType
 DrawTargetCairo::GetType() const
 {
   if (mContext) {
     cairo_surface_type_t type = cairo_surface_get_type(mSurface);
     if (type == CAIRO_SURFACE_TYPE_TEE) {
@@ -807,17 +807,17 @@ DrawTargetCairo::DrawSurface(SourceSurfa
                              const DrawSurfaceOptions &aSurfOptions,
                              const DrawOptions &aOptions)
 {
   if (mTransformSingular) {
     return;
   }
 
   if (!IsValid() || !aSurface) {
-    gfxCriticalNote << "DrawSurface with bad surface " << cairo_surface_status(mSurface);
+    gfxCriticalNote << "DrawSurface with bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
     return;
   }
 
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aSurface);
 
   float sx = aSource.Width() / aDest.Width();
   float sy = aSource.Height() / aDest.Height();
@@ -1261,34 +1261,34 @@ DrawTargetCairo::IsCurrentGroupOpaque()
   return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR;
 }
 
 void
 DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA)
 {
   DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
 #ifdef MOZ_TREE_CAIRO
-  cairo_surface_set_subpixel_antialiasing(mSurface,
+  cairo_surface_set_subpixel_antialiasing(cairo_get_group_target(mContext),
     aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
 #endif
 }
 
 void
 DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
                             const GlyphBuffer &aBuffer,
                             const Pattern &aPattern,
                             const DrawOptions &aOptions,
                             const GlyphRenderingOptions*)
 {
   if (mTransformSingular) {
     return;
   }
 
   if (!IsValid()) {
-    gfxDebug() << "FillGlyphs bad surface " << cairo_surface_status(mSurface);
+    gfxDebug() << "FillGlyphs bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
     return;
   }
 
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aPattern);
 
   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
@@ -1316,18 +1316,18 @@ DrawTargetCairo::FillGlyphs(ScaledFont *
   for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) {
     glyphs[i].index = aBuffer.mGlyphs[i].mIndex;
     glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x;
     glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y;
   }
 
   cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
 
-  if (mSurface && cairo_surface_status(mSurface)) {
-    gfxDebug() << "Ending FillGlyphs with a bad surface " << cairo_surface_status(mSurface);
+  if (mContext && cairo_surface_status(cairo_get_group_target(mContext))) {
+    gfxDebug() << "Ending FillGlyphs with a bad surface " << cairo_surface_status(cairo_get_group_target(mContext));
   }
 }
 
 void
 DrawTargetCairo::Mask(const Pattern &aSource,
                       const Pattern &aMask,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
@@ -1731,17 +1731,17 @@ already_AddRefed<SourceSurface>
 DrawTargetCairo::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
 {
   return nullptr;
 }
 
 already_AddRefed<DrawTarget>
 DrawTargetCairo::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
 {
-  if (cairo_surface_status(mSurface)) {
+  if (cairo_surface_status(cairo_get_group_target(mContext))) {
     RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
     if (target->Init(aSize, aFormat)) {
       return target.forget();
     }
   }
 
   cairo_surface_t* similar = cairo_surface_create_similar(mSurface,
                                                           GfxFormatToCairoContent(aFormat),
@@ -1749,17 +1749,17 @@ DrawTargetCairo::CreateSimilarDrawTarget
 
   if (!cairo_surface_status(similar)) {
     RefPtr<DrawTargetCairo> target = new DrawTargetCairo();
     if (target->InitAlreadyReferenced(similar, aSize)) {
       return target.forget();
     }
   }
 
-  gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "Failed to create similar cairo surface! Size: " << aSize << " Status: " << cairo_surface_status(similar) << cairo_surface_status(mSurface) << " format " << (int)aFormat;
+  gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) << "Failed to create similar cairo surface! Size: " << aSize << " Status: " << cairo_surface_status(similar) << cairo_surface_status(cairo_get_group_target(mContext)) << " format " << (int)aFormat;
 
   return nullptr;
 }
 
 bool
 DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
 {
   if (cairo_surface_status(aSurface)) {
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -2078,17 +2078,17 @@ GLContext::ChooseGLFormats(const Surface
     if (WorkAroundDriverBugs() && samples == 1) {
         samples = 0;
     }
     formats.samples = samples;
 
 
     // Be clear that these are 0 if unavailable.
     formats.depthStencil = 0;
-    if (!IsGLES() || IsExtensionSupported(OES_packed_depth_stencil)) {
+    if (IsSupported(GLFeature::packed_depth_stencil)) {
         formats.depthStencil = LOCAL_GL_DEPTH24_STENCIL8;
     }
 
     formats.depth = 0;
     if (IsGLES()) {
         if (IsExtensionSupported(OES_depth24)) {
             formats.depth = LOCAL_GL_DEPTH_COMPONENT24;
         } else {
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -148,16 +148,44 @@ GetAndInitDisplay(GLLibraryEGL& egl, voi
         return EGL_NO_DISPLAY;
 
     if (!egl.fInitialize(display, nullptr, nullptr))
         return EGL_NO_DISPLAY;
 
     return display;
 }
 
+static EGLDisplay
+GetAndInitDisplayForAccelANGLE(GLLibraryEGL& egl)
+{
+    EGLDisplay ret = 0;
+
+    // D3D11 ANGLE only works with OMTC; there's a bug in the non-OMTC layer
+    // manager, and it's pointless to try to fix it.  We also don't try
+    // D3D11 ANGLE if the layer manager is prefering D3D9 (hrm, do we care?)
+    if (gfxPrefs::LayersOffMainThreadCompositionEnabled() &&
+        !gfxPrefs::LayersPreferD3D9())
+    {
+        if (gfxPrefs::WebGLANGLEForceD3D11())
+            return GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ONLY_DISPLAY_ANGLE);
+
+        if (gfxPrefs::WebGLANGLETryD3D11() &&
+            gfxPlatform::CanUseDirect3D11ANGLE())
+        {
+            ret = GetAndInitDisplay(egl, LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE);
+        }
+    }
+
+    if (!ret) {
+        ret = GetAndInitDisplay(egl, EGL_DEFAULT_DISPLAY);
+    }
+
+    return ret;
+}
+
 bool
 GLLibraryEGL::ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface)
 {
     StaticMutexAutoUnlock lock(sMutex);
     if (!mReadbackGL) {
         mReadbackGL = gl::GLContextProvider::CreateHeadless(gl::CreateContextFlags::NONE);
     }
 
@@ -362,40 +390,24 @@ GLLibraryEGL::EnsureInitialized(bool for
             chosenDisplay = GetAndInitWARPDisplay(*this, EGL_DEFAULT_DISPLAY);
             if (chosenDisplay) {
                 mIsWARP = true;
             }
         }
 
         if (!chosenDisplay) {
             // If falling back to WARP did not work and we don't want to try
-            // using HW accelerated ANGLE, then fail
+            // using HW accelerated ANGLE, then fail.
             if (!shouldTryAccel) {
                 NS_ERROR("Fallback WARP ANGLE context failed to initialize.");
                 return false;
             }
 
             // Hardware accelerated ANGLE path
-
-            // D3D11 ANGLE only works with OMTC; there's a bug in the non-OMTC layer
-            // manager, and it's pointless to try to fix it.  We also don't try
-            // D3D11 ANGLE if the layer manager is prefering D3D9 (hrm, do we care?)
-            if (gfxPrefs::LayersOffMainThreadCompositionEnabled() &&
-                !gfxPrefs::LayersPreferD3D9())
-            {
-                if (gfxPrefs::WebGLANGLEForceD3D11()) {
-                    chosenDisplay = GetAndInitDisplay(*this,
-                                                      LOCAL_EGL_D3D11_ONLY_DISPLAY_ANGLE);
-                } else if (gfxPrefs::WebGLANGLETryD3D11() &&
-                           gfxPlatform::CanUseDirect3D11ANGLE())
-                {
-                    chosenDisplay = GetAndInitDisplay(*this,
-                                                      LOCAL_EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE);
-                }
-            }
+            chosenDisplay = GetAndInitDisplayForAccelANGLE(*this);
         }
     } else {
         chosenDisplay = GetAndInitDisplay(*this, EGL_DEFAULT_DISPLAY);
     }
 
     if (!chosenDisplay) {
         NS_WARNING("Failed to initialize a display.");
         return false;
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -360,27 +360,28 @@ DecomposeIntoNoRepeatRects(const gfx::Re
 gfx::IntRect
 Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
                                     const gfx::Rect& aClipRect,
                                     const gfx::Matrix4x4& aTransform)
 {
   gfx::Rect renderBounds = mRenderBounds;
 
   // Compute the clip.
+  gfx::IntPoint offset = GetCurrentRenderTarget()->GetOrigin();
   renderBounds.IntersectRect(renderBounds, aClipRect);
+  renderBounds.MoveBy(offset);
 
   // Apply the layer transform.
   gfx::Rect dest = aTransform.TransformAndClipBounds(aRect, renderBounds);
-  dest.RoundOut();
+  dest -= offset;
 
+  // Round out to integer.
   gfx::IntRect result;
+  dest.RoundOut();
   dest.ToIntRect(&result);
-
-  gfx::IntPoint offset = GetCurrentRenderTarget()->GetOrigin();
-  result.MoveBy(-offset);
   return result;
 }
 
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 void
 Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
 {
   // OpenGL does not provide ReleaseFence for rendering.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/MacIOSurfaceHelpers.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#include "MacIOSurfaceHelpers.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "YCbCrUtils.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+already_AddRefed<SourceSurface>
+CreateSourceSurfaceFromMacIOSurface(MacIOSurface* aSurface)
+{
+  RefPtr<DataSourceSurface> dataSurface;
+  aSurface->Lock();
+  size_t bytesPerRow = aSurface->GetBytesPerRow();
+  size_t ioWidth = aSurface->GetDevicePixelWidth();
+  size_t ioHeight = aSurface->GetDevicePixelHeight();
+
+  SurfaceFormat format = aSurface->GetFormat() == SurfaceFormat::NV12 ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
+
+  dataSurface = Factory::CreateDataSourceSurface(IntSize(ioWidth, ioHeight), format);
+  if (NS_WARN_IF(!dataSurface)) {
+    return nullptr;
+  }
+
+  DataSourceSurface::MappedSurface mappedSurface;
+  if (!dataSurface->Map(DataSourceSurface::WRITE, &mappedSurface))
+    return nullptr;
+
+  if (aSurface->GetFormat() == SurfaceFormat::NV12) {
+    if (aSurface->GetDevicePixelWidth() > PlanarYCbCrImage::MAX_DIMENSION ||
+        aSurface->GetDevicePixelHeight() > PlanarYCbCrImage::MAX_DIMENSION) {
+      return nullptr;
+    }
+
+    /* Extract and separate the CbCr planes */
+    size_t cbCrStride = aSurface->GetBytesPerRow(1);
+    size_t cbCrWidth = aSurface->GetDevicePixelWidth(1);
+    size_t cbCrHeight = aSurface->GetDevicePixelHeight(1);
+
+    auto cbPlane = MakeUnique<uint8_t[]>(cbCrWidth * cbCrHeight);
+    auto crPlane = MakeUnique<uint8_t[]>(cbCrWidth * cbCrHeight);
+
+    uint8_t* src = (uint8_t*)aSurface->GetBaseAddressOfPlane(1);
+    uint8_t* cbDest = cbPlane.get();
+    uint8_t* crDest = crPlane.get();
+
+    for (size_t i = 0; i < cbCrHeight; i++) {
+      uint8_t* rowSrc = src + cbCrStride * i;
+      for (size_t j = 0; j < cbCrWidth; j++) {
+        *cbDest = *rowSrc;
+        cbDest++;
+        rowSrc++;
+        *crDest = *rowSrc;
+        crDest++;
+        rowSrc++;
+      }
+    }
+
+    /* Convert to RGB */
+    PlanarYCbCrData data;
+    data.mYChannel = (uint8_t*)aSurface->GetBaseAddressOfPlane(0);
+    data.mYStride = aSurface->GetBytesPerRow(0);
+    data.mYSize = IntSize(aSurface->GetDevicePixelWidth(0), aSurface->GetDevicePixelHeight(0));
+    data.mCbChannel = cbPlane.get();
+    data.mCrChannel = crPlane.get();
+    data.mCbCrStride = cbCrWidth;
+    data.mCbCrSize = IntSize(cbCrWidth, cbCrHeight);
+    data.mPicSize = data.mYSize;
+
+    ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, IntSize(ioWidth, ioHeight), mappedSurface.mData, mappedSurface.mStride);
+  } else {
+    unsigned char* ioData = (unsigned char*)aSurface->GetBaseAddress();
+
+    for (size_t i = 0; i < ioHeight; ++i) {
+      memcpy(mappedSurface.mData + i * mappedSurface.mStride,
+             ioData + i * bytesPerRow,
+             ioWidth * 4);
+    }
+  }
+
+  dataSurface->Unmap();
+  aSurface->Unlock();
+
+  return dataSurface.forget();
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/MacIOSurfaceHelpers.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef GFX_MACIOSURFACEHELPERS_H
+#define GFX_MACIOSURFACEHELPERS_H
+
+class MacIOSurface;
+template<class T> struct already_AddRefed;
+
+namespace mozilla {
+
+namespace gfx {
+class SourceSurface;
+}
+
+namespace layers {
+
+// Unlike MacIOSurface::GetAsSurface, this also handles IOSurface formats
+// with multiple planes and does YCbCr to RGB conversion, if necessary.
+already_AddRefed<gfx::SourceSurface>
+CreateSourceSurfaceFromMacIOSurface(MacIOSurface* aSurface);
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // GFX_MACIOSURFACEHELPERS_H
--- a/gfx/layers/MacIOSurfaceImage.cpp
+++ b/gfx/layers/MacIOSurfaceImage.cpp
@@ -1,19 +1,19 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
+#include "MacIOSurfaceHelpers.h"
 #include "MacIOSurfaceImage.h"
 #include "mozilla/layers/CompositableClient.h"
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/MacIOSurfaceTextureClientOGL.h"
 #include "mozilla/UniquePtr.h"
-#include "YCbCrUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 TextureClient*
 MacIOSurfaceImage::GetTextureClient(CompositableClient* aClient)
 {
@@ -25,82 +25,10 @@ MacIOSurfaceImage::GetTextureClient(Comp
     );
   }
   return mTextureClient;
 }
 
 already_AddRefed<SourceSurface>
 MacIOSurfaceImage::GetAsSourceSurface()
 {
-  RefPtr<DataSourceSurface> dataSurface;
-  mSurface->Lock();
-  size_t bytesPerRow = mSurface->GetBytesPerRow();
-  size_t ioWidth = mSurface->GetDevicePixelWidth();
-  size_t ioHeight = mSurface->GetDevicePixelHeight();
-
-  SurfaceFormat format = mSurface->GetFormat() == SurfaceFormat::NV12 ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
-
-  dataSurface = Factory::CreateDataSourceSurface(IntSize(ioWidth, ioHeight), format);
-  if (NS_WARN_IF(!dataSurface)) {
-    return nullptr;
-  }
-
-  DataSourceSurface::MappedSurface mappedSurface;
-  if (!dataSurface->Map(DataSourceSurface::WRITE, &mappedSurface))
-    return nullptr;
-
-  if (mSurface->GetFormat() == SurfaceFormat::NV12) {
-    if (mSurface->GetDevicePixelWidth() > PlanarYCbCrImage::MAX_DIMENSION ||
-        mSurface->GetDevicePixelHeight() > PlanarYCbCrImage::MAX_DIMENSION) {
-      return nullptr;
-    }
-
-    /* Extract and separate the CbCr planes */
-    size_t cbCrStride = mSurface->GetBytesPerRow(1);
-    size_t cbCrWidth = mSurface->GetDevicePixelWidth(1);
-    size_t cbCrHeight = mSurface->GetDevicePixelHeight(1);
-
-    auto cbPlane = MakeUnique<uint8_t[]>(cbCrWidth * cbCrHeight);
-    auto crPlane = MakeUnique<uint8_t[]>(cbCrWidth * cbCrHeight);
-
-    uint8_t* src = (uint8_t*)mSurface->GetBaseAddressOfPlane(1);
-    uint8_t* cbDest = cbPlane.get();
-    uint8_t* crDest = crPlane.get();
-
-    for (size_t i = 0; i < cbCrHeight; i++) {
-      uint8_t* rowSrc = src + cbCrStride * i;
-      for (size_t j = 0; j < cbCrWidth; j++) {
-        *cbDest = *rowSrc;
-        cbDest++;
-        rowSrc++;
-        *crDest = *rowSrc;
-        crDest++;
-        rowSrc++;
-      }
-    }
-
-    /* Convert to RGB */
-    PlanarYCbCrData data;
-    data.mYChannel = (uint8_t*)mSurface->GetBaseAddressOfPlane(0);
-    data.mYStride = mSurface->GetBytesPerRow(0);
-    data.mYSize = IntSize(mSurface->GetDevicePixelWidth(0), mSurface->GetDevicePixelHeight(0));
-    data.mCbChannel = cbPlane.get();
-    data.mCrChannel = crPlane.get();
-    data.mCbCrStride = cbCrWidth;
-    data.mCbCrSize = IntSize(cbCrWidth, cbCrHeight);
-    data.mPicSize = data.mYSize;
-
-    ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, IntSize(ioWidth, ioHeight), mappedSurface.mData, mappedSurface.mStride);
-  } else {
-    unsigned char* ioData = (unsigned char*)mSurface->GetBaseAddress();
-
-    for (size_t i = 0; i < ioHeight; ++i) {
-      memcpy(mappedSurface.mData + i * mappedSurface.mStride,
-             ioData + i * bytesPerRow,
-             ioWidth * 4);
-    }
-  }
-
-  dataSurface->Unmap();
-  mSurface->Unlock();
-
-  return dataSurface.forget();
+  return CreateSourceSurfaceFromMacIOSurface(mSurface);
 }
--- a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp
+++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
 #include "MacIOSurfaceTextureHostBasic.h"
 #include "mozilla/gfx/MacIOSurface.h"
+#include "MacIOSurfaceHelpers.h"
 
 namespace mozilla {
 namespace layers {
 
 MacIOSurfaceTextureSourceBasic::MacIOSurfaceTextureSourceBasic(
                                 BasicCompositor* aCompositor,
                                 MacIOSurface* aSurface)
   : mCompositor(aCompositor)
@@ -28,17 +29,19 @@ MacIOSurfaceTextureSourceBasic::GetSize(
 {
   return gfx::IntSize(mSurface->GetDevicePixelWidth(),
                       mSurface->GetDevicePixelHeight());
 }
 
 gfx::SurfaceFormat
 MacIOSurfaceTextureSourceBasic::GetFormat() const
 {
-  return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::B8G8R8X8;
+  // Set the format the same way as CreateSourceSurfaceFromMacIOSurface.
+  return mSurface->GetFormat() == gfx::SurfaceFormat::NV12
+    ? gfx::SurfaceFormat::B8G8R8X8 : gfx::SurfaceFormat::B8G8R8A8;
 }
 
 MacIOSurfaceTextureHostBasic::MacIOSurfaceTextureHostBasic(
     TextureFlags aFlags,
     const SurfaceDescriptorMacIOSurface& aDescriptor
 )
   : TextureHost(aFlags)
 {
@@ -46,17 +49,17 @@ MacIOSurfaceTextureHostBasic::MacIOSurfa
                                          aDescriptor.scaleFactor(),
                                          !aDescriptor.isOpaque());
 }
 
 gfx::SourceSurface*
 MacIOSurfaceTextureSourceBasic::GetSurface(gfx::DrawTarget* aTarget)
 {
   if (!mSourceSurface) {
-    mSourceSurface = mSurface->GetAsSurface();
+    mSourceSurface = CreateSourceSurfaceFromMacIOSurface(mSurface);
   }
   return mSourceSurface;
 }
 
 void
 MacIOSurfaceTextureSourceBasic::SetCompositor(Compositor* aCompositor)
 {
   mCompositor = static_cast<BasicCompositor*>(aCompositor);
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -201,23 +201,25 @@ if CONFIG['MOZ_X11']:
         'opengl/X11TextureSourceOGL.cpp',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     EXPORTS.mozilla.layers += [
         'opengl/GLManager.h',
     ]
     EXPORTS += [
+        'MacIOSurfaceHelpers.h',
         'MacIOSurfaceImage.h',
     ]
     UNIFIED_SOURCES += [
         'opengl/GLManager.cpp',
     ]
     SOURCES += [
         'ipc/ShadowLayerUtilsMac.cpp',
+        'MacIOSurfaceHelpers.cpp',
         'MacIOSurfaceImage.cpp',
     ]
 
 # NB: Gralloc is available on other platforms that use the android GL
 # libraries, but only Gonk is able to use it reliably because Gecko
 # has full system permissions there.
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     EXPORTS.mozilla.layers += [
--- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
 #include "MacIOSurfaceTextureClientOGL.h"
 #include "mozilla/gfx/MacIOSurface.h" 
+#include "MacIOSurfaceHelpers.h"
 
 namespace mozilla {
 namespace layers {
 
 MacIOSurfaceTextureData::MacIOSurfaceTextureData(MacIOSurface* aSurface)
 : mSurface(aSurface)
 {
   MOZ_ASSERT(mSurface);
@@ -48,14 +49,14 @@ gfx::SurfaceFormat
 MacIOSurfaceTextureData::GetFormat() const
 {
   return mSurface->GetFormat();
 }
 
 already_AddRefed<gfx::DataSourceSurface>
 MacIOSurfaceTextureData::GetAsSurface()
 {
-  RefPtr<gfx::SourceSurface> surf = mSurface->GetAsSurface();
+  RefPtr<gfx::SourceSurface> surf = CreateSourceSurfaceFromMacIOSurface(mSurface);
   return surf->GetDataSurface();
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/tests/mochitest/test_bug513439.html
+++ b/gfx/tests/mochitest/test_bug513439.html
@@ -14,31 +14,24 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 513439 **/
 
-var prefService = SpecialPowers.Cc["@mozilla.org/preferences-service;1"]
-                               .getService(SpecialPowers.Ci.nsIPrefService);
-var layoutCSSBranch = prefService.getBranch("layout.css.");
-var oldVal = layoutCSSBranch.getCharPref("devPixelsPerPx");
+SimpleTest.waitForExplicitFinish();
 
-try {
-  var domWindowUtils = SpecialPowers.DOMWindowUtils;
-  var devPxPerCSSPx = domWindowUtils.screenPixelsPerCSSPixel;
-
-  layoutCSSBranch.setCharPref("devPixelsPerPx", "2");
+var domWindowUtils = SpecialPowers.DOMWindowUtils;
+SpecialPowers.pushPrefEnv({set: [["layout.css.devPixelsPerPx", "2"]]}, () => {
   is(domWindowUtils.screenPixelsPerCSSPixel, 2, "devPixelsPerPx wasn't set correctly");
 
-  layoutCSSBranch.setCharPref("devPixelsPerPx", "1.5");
-  is(domWindowUtils.screenPixelsPerCSSPixel, 1.5, "devPixelsPerPx wasn't set correctly");
-
-} finally {
-  layoutCSSBranch.setCharPref("devPixelsPerPx", oldVal);
-}
+  SpecialPowers.pushPrefEnv({set: [["layout.css.devPixelsPerPx", "1.5"]]}, () => {
+    is(domWindowUtils.screenPixelsPerCSSPixel, 1.5, "devPixelsPerPx wasn't set correctly");
+    SimpleTest.finish();
+  });
+});
 
 </script>
 </pre>
 </body>
 </html>
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -136,17 +136,17 @@ private:
     {
         INTR_SEMS,
         SYNC_SEMS,
         ASYNC_SEMS
     };
 
 public:
     InterruptFrame(Direction direction, const Message* msg)
-      : mMessageName(strdup(msg->name())),
+      : mMessageName(msg->name()),
         mMessageRoutingId(msg->routing_id()),
         mMesageSemantics(msg->is_interrupt() ? INTR_SEMS :
                           msg->is_sync() ? SYNC_SEMS :
                           ASYNC_SEMS),
         mDirection(direction),
         mMoved(false)
     {
         MOZ_ASSERT(mMessageName);
@@ -162,19 +162,16 @@ public:
         mMessageRoutingId = aOther.mMessageRoutingId;
         mMesageSemantics = aOther.mMesageSemantics;
         mDirection = aOther.mDirection;
     }
 
     ~InterruptFrame()
     {
         MOZ_ASSERT_IF(!mMessageName, mMoved);
-
-        if (mMessageName)
-            free(const_cast<char*>(mMessageName));
     }
 
     InterruptFrame& operator=(InterruptFrame&& aOther)
     {
         MOZ_RELEASE_ASSERT(&aOther != this);
         this->~InterruptFrame();
         new (this) InterruptFrame(mozilla::Move(aOther));
         return *this;
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -2269,27 +2269,34 @@ class MOZ_STACK_CLASS ModuleValidator
 
         CacheableChars filename;
         if (parser_.ss->filename()) {
             filename = DuplicateString(parser_.ss->filename());
             if (!filename)
                 return false;
         }
 
+        CacheableCharsVector funcNames;
+        for (const Func* func : functions_) {
+            CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func->name());
+            if (!funcName || !funcNames.emplaceBack(Move(funcName)))
+                return false;
+        }
+
         uint32_t endBeforeCurly = tokenStream().currentToken().pos.end;
         module_->srcLength = endBeforeCurly - module_->srcStart;
 
         TokenPos pos;
         JS_ALWAYS_TRUE(tokenStream().peekTokenPos(&pos, TokenStream::Operand));
         uint32_t endAfterCurly = pos.end;
         module_->srcLengthWithRightBrace = endAfterCurly - module_->srcStart;
 
         UniqueModuleData base;
         UniqueStaticLinkData link;
-        if (!mg_.finish(heap, Move(filename), &base, &link, slowFuncs))
+        if (!mg_.finish(heap, Move(filename), Move(funcNames), &base, &link, slowFuncs))
             return false;
 
         moduleObj.set(WasmModuleObject::create(cx_));
         if (!moduleObj)
             return false;
 
         return moduleObj->init(cx_->new_<AsmJSModule>(Move(base), Move(link), Move(module_)));
     }
@@ -2628,21 +2635,21 @@ class MOZ_STACK_CLASS FunctionValidator
         labels_(m.cx()),
         hasAlreadyReturned_(false)
     {}
 
     ModuleValidator& m() const        { return m_; }
     ExclusiveContext* cx() const      { return m_.cx(); }
     ParseNode* fn() const             { return fn_; }
 
-    bool init(PropertyName* name, unsigned line, unsigned column) {
+    bool init(PropertyName* name, unsigned line) {
         if (!locals_.init() || !labels_.init())
             return false;
 
-        if (!m_.mg().startFuncDef(name, line, column, &fg_))
+        if (!m_.mg().startFuncDef(line, &fg_))
             return false;
 
         encoder_.emplace(fg_.bytecode());
         return true;
     }
 
     bool finish(uint32_t funcIndex, unsigned generateTime) {
         return m_.mg().finishFuncDef(funcIndex, generateTime, &fg_);
@@ -2760,17 +2767,17 @@ class MOZ_STACK_CLASS FunctionValidator
         return encoder().writeU32(u32);
     }
     MOZ_WARN_UNUSED_RESULT
     bool writeI32(int32_t i32) {
         return encoder().writeI32(i32);
     }
     MOZ_WARN_UNUSED_RESULT
     bool writeInt32Lit(int32_t i32) {
-        return writeOp(Expr::I32Const) && encoder().writeI32(i32);
+        return writeOp(Expr::I32Const) && encoder().writeVarU32(i32);
     }
 
     MOZ_WARN_UNUSED_RESULT
     bool writeLit(NumLit lit) {
         switch (lit.which()) {
           case NumLit::Fixnum:
           case NumLit::NegativeInt:
           case NumLit::BigUnsigned:
@@ -3422,25 +3429,21 @@ IsLiteralOrConst(FunctionValidator& f, P
     return true;
 }
 
 static bool
 CheckFinalReturn(FunctionValidator& f, ParseNode* lastNonEmptyStmt)
 {
     if (!f.hasAlreadyReturned()) {
         f.setReturnedType(ExprType::Void);
-        return f.writeOp(Expr::Return);
-    }
-
-    if (!lastNonEmptyStmt->isKind(PNK_RETURN)) {
-        if (!IsVoid(f.returnedType()))
-            return f.fail(lastNonEmptyStmt, "void incompatible with previous return type");
-
-        return f.writeOp(Expr::Return);
-    }
+        return true;
+    }
+
+    if (!lastNonEmptyStmt->isKind(PNK_RETURN) && !IsVoid(f.returnedType()))
+        return f.fail(lastNonEmptyStmt, "void incompatible with previous return type");
 
     return true;
 }
 
 static bool
 SetLocal(FunctionValidator& f, NumLit lit)
 {
     return f.writeOp(Expr::SetLocal) &&
@@ -6701,22 +6704,22 @@ CheckStatement(FunctionValidator& f, Par
                                                           Expr::Continue, Expr::ContinueLabel);
       default:;
     }
 
     return f.fail(stmt, "unexpected statement kind");
 }
 
 static bool
-ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line, unsigned* column)
+ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line)
 {
     TokenStream& tokenStream = m.tokenStream();
 
     tokenStream.consumeKnownToken(TOK_FUNCTION, TokenStream::Operand);
-    tokenStream.srcCoords.lineNumAndColumnIndex(tokenStream.currentToken().pos.end, line, column);
+    *line = tokenStream.srcCoords.lineNum(tokenStream.currentToken().pos.end);
 
     RootedPropertyName name(m.cx());
 
     TokenKind tk;
     if (!tokenStream.getToken(&tk, TokenStream::Operand))
         return false;
     if (tk == TOK_NAME) {
         name = tokenStream.currentName();
@@ -6773,25 +6776,25 @@ CheckFunction(ModuleValidator& m)
 {
     // asm.js modules can be quite large when represented as parse trees so pop
     // the backing LifoAlloc after parsing/compiling each function.
     AsmJSParser::Mark mark = m.parser().mark();
 
     int64_t before = PRMJ_Now();
 
     ParseNode* fn = nullptr;
-    unsigned line = 0, column = 0;
-    if (!ParseFunction(m, &fn, &line, &column))
+    unsigned line = 0;
+    if (!ParseFunction(m, &fn, &line))
         return false;
 
     if (!CheckFunctionHead(m, fn))
         return false;
 
     FunctionValidator f(m, fn);
-    if (!f.init(FunctionName(fn), line, column))
+    if (!f.init(FunctionName(fn), line))
         return m.fail(fn, "internal compiler failure (probably out of memory)");
 
     ParseNode* stmtIter = ListHead(FunctionStatementList(fn));
 
     if (!CheckProcessingDirectives(m, &stmtIter))
         return false;
 
     ValTypeVector args;
@@ -8274,22 +8277,21 @@ BuildConsoleMessage(ExclusiveContext* cx
     UniqueChars slowText;
     if (!slowFuncs.empty()) {
         slowText.reset(JS_smprintf("; %d functions compiled slowly: ", slowFuncs.length()));
         if (!slowText)
             return nullptr;
 
         for (unsigned i = 0; i < slowFuncs.length(); i++) {
             const SlowFunction& func = slowFuncs[i];
-            JSAutoByteString name;
-            if (!AtomToPrintableString(cx, func.name, &name))
-                return nullptr;
-
-            slowText.reset(JS_smprintf("%s%s:%u:%u (%ums)%s", slowText.get(),
-                                       name.ptr(), func.line, func.column, func.ms,
+            slowText.reset(JS_smprintf("%s%s:%u (%ums)%s",
+                                       slowText.get(),
+                                       module.prettyFuncName(func.index),
+                                       func.lineOrBytecode,
+                                       func.ms,
                                        i+1 < slowFuncs.length() ? ", " : ""));
             if (!slowText)
                 return nullptr;
         }
     }
 
     const char* cacheString = "";
     switch (cacheResult) {
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -598,58 +598,47 @@ struct SourceCoords {
 typedef Vector<SourceCoords, 0, SystemAllocPolicy> SourceCoordsVector;
 
 // The FuncBytecode class contains the intermediate representation of a
 // parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
 // lives only until it is fully compiled.
 class FuncBytecode
 {
     // Function metadata
-    SourceCoordsVector callSourceCoords_;
     const DeclaredSig& sig_;
     ValTypeVector locals_;
-
-    // Note: this unrooted field assumes AutoKeepAtoms via TokenStream via
-    // asm.js compilation.
-    PropertyName* name_;
-    unsigned line_;
-    unsigned column_;
+    uint32_t lineOrBytecode_;
+    SourceCoordsVector callSourceCoords_;
 
     // Compilation bookkeeping
     uint32_t index_;
     unsigned generateTime_;
 
     UniqueBytecode bytecode_;
 
   public:
-    FuncBytecode(PropertyName* name,
-                 unsigned line,
-                 unsigned column,
-                 SourceCoordsVector&& sourceCoords,
-                 uint32_t index,
+    FuncBytecode(uint32_t index,
                  const DeclaredSig& sig,
                  UniqueBytecode bytecode,
                  ValTypeVector&& locals,
+                 uint32_t lineOrBytecode,
+                 SourceCoordsVector&& sourceCoords,
                  unsigned generateTime)
-      : callSourceCoords_(Move(sourceCoords)),
-        sig_(sig),
+      : sig_(sig),
         locals_(Move(locals)),
-        name_(name),
-        line_(line),
-        column_(column),
+        lineOrBytecode_(lineOrBytecode),
+        callSourceCoords_(Move(sourceCoords)),
         index_(index),
         generateTime_(generateTime),
         bytecode_(Move(bytecode))
     {}
 
     UniqueBytecode recycleBytecode() { return Move(bytecode_); }
 
-    PropertyName* name() const { return name_; }
-    unsigned line() const { return line_; }
-    unsigned column() const { return column_; }
+    uint32_t lineOrBytecode() const { return lineOrBytecode_; }
     const SourceCoords& sourceCoords(size_t i) const { return callSourceCoords_[i]; }
 
     uint32_t index() const { return index_; }
     const DeclaredSig& sig() const { return sig_; }
     const Bytecode& bytecode() const { return *bytecode_; }
 
     size_t numLocals() const { return locals_.length(); }
     ValType localType(size_t i) const { return locals_[i]; }
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -101,17 +101,23 @@ FrameIterator::settle()
     }
 }
 
 JSAtom*
 FrameIterator::functionDisplayAtom() const
 {
     MOZ_ASSERT(!done());
 
-    const char* chars = module_->functionName(codeRange_->funcNameIndex());
+    UniqueChars owner;
+    const char* chars = module_->getFuncName(cx_, codeRange_->funcIndex(), &owner);
+    if (!chars) {
+        cx_->clearPendingException();
+        return cx_->names().empty;
+    }
+
     JSAtom* atom = AtomizeUTF8Chars(cx_, chars, strlen(chars));
     if (!atom) {
         cx_->clearPendingException();
         return cx_->names().empty;
     }
 
     return atom;
 }
@@ -688,17 +694,17 @@ ProfilingFrameIterator::label() const
         return importJitDescription;
       case ExitReason::ImportInterp:
         return importInterpDescription;
       case ExitReason::Native:
         return nativeDescription;
     }
 
     switch (codeRange_->kind()) {
-      case CodeRange::Function:         return module_->profilingLabel(codeRange_->funcNameIndex());
+      case CodeRange::Function:         return module_->profilingLabel(codeRange_->funcIndex());
       case CodeRange::Entry:            return "entry trampoline (in asm.js)";
       case CodeRange::ImportJitExit:    return importJitDescription;
       case CodeRange::ImportInterpExit: return importInterpDescription;
       case CodeRange::Interrupt:        return nativeDescription;
       case CodeRange::Inline:           return "inline stub (in asm.js)";
     }
 
     MOZ_CRASH("bad code range kind");
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -33,17 +33,16 @@ using namespace js::wasm;
 static const unsigned GENERATOR_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
 static const unsigned COMPILATION_LIFO_DEFAULT_CHUNK_SIZE = 64 * 1024;
 
 ModuleGenerator::ModuleGenerator(ExclusiveContext* cx)
   : cx_(cx),
     jcx_(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread())),
     slowFuncs_(cx),
     numSigs_(0),
-    numFuncSigs_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     alloc_(&lifo_),
     masm_(MacroAssembler::AsmJSToken(), alloc_),
     funcIndexToExport_(cx),
     parallel_(false),
     outstanding_(0),
     tasks_(cx),
     freeTasks_(cx),
@@ -120,17 +119,17 @@ ModuleGenerator::init(UniqueModuleGenera
 
     // For asm.js, the Vectors in ModuleGeneratorData are max-sized reservations
     // and will be initialized in a linear order via init* functions as the
     // module is generated. For wasm, the Vectors are correctly-sized and
     // already initialized.
     shared_ = Move(shared);
     if (kind == ModuleKind::Wasm) {
         numSigs_ = shared_->sigs.length();
-        numFuncSigs_ = shared_->funcSigs.length();
+        module_->numFuncs = shared_->funcSigs.length();
         for (uint32_t i = 0; i < shared_->imports.length(); i++) {
             if (!addImport(*shared_->imports[i].sig, shared_->imports[i].globalDataOffset))
                 return false;
         }
     }
 
     return true;
 }
@@ -184,29 +183,23 @@ ModuleGenerator::finishTask(IonCompileTa
 
     // Merge the compiled results into the whole-module masm.
     DebugOnly<size_t> sizeBefore = masm_.size();
     if (!masm_.asmMergeWith(results.masm()))
         return false;
     MOZ_ASSERT(masm_.size() == offsetInWhole + results.masm().size());
 
     // Add the CodeRange for this function.
-    CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func.name());
-    if (!funcName)
-        return false;
-    uint32_t nameIndex = module_->funcNames.length();
-    if (!module_->funcNames.emplaceBack(Move(funcName)))
-        return false;
-    if (!module_->codeRanges.emplaceBack(nameIndex, func.line(), results.offsets()))
+    if (!module_->codeRanges.emplaceBack(func.index(), func.lineOrBytecode(), results.offsets()))
         return false;
 
     // Keep a record of slow functions for printing in the final console message.
     unsigned totalTime = func.generateTime() + results.compileTime();
     if (totalTime >= SlowFunction::msThreshold) {
-        if (!slowFuncs_.emplaceBack(func.name(), totalTime, func.line(), func.column()))
+        if (!slowFuncs_.emplaceBack(func.index(), totalTime, func.lineOrBytecode()))
             return false;
     }
 
     freeTasks_.infallibleAppend(task);
     return true;
 }
 
 bool
@@ -282,20 +275,20 @@ ModuleGenerator::sig(uint32_t index) con
     MOZ_ASSERT(index < numSigs_);
     return shared_->sigs[index];
 }
 
 bool
 ModuleGenerator::initFuncSig(uint32_t funcIndex, uint32_t sigIndex)
 {
     MOZ_ASSERT(module_->kind == ModuleKind::AsmJS);
-    MOZ_ASSERT(funcIndex == numFuncSigs_);
+    MOZ_ASSERT(funcIndex == module_->numFuncs);
     MOZ_ASSERT(!shared_->funcSigs[funcIndex]);
 
-    numFuncSigs_++;
+    module_->numFuncs++;
     shared_->funcSigs[funcIndex] = &shared_->sigs[sigIndex];
     return true;
 }
 
 const DeclaredSig&
 ModuleGenerator::funcSig(uint32_t funcIndex) const
 {
     MOZ_ASSERT(shared_->funcSigs[funcIndex]);
@@ -425,18 +418,17 @@ ModuleGenerator::startFuncDefs()
     for (size_t i = 0; i < numTasks; i++)
         freeTasks_.infallibleAppend(&tasks_[i]);
 
     MOZ_ASSERT(startedFuncDefs());
     return true;
 }
 
 bool
-ModuleGenerator::startFuncDef(PropertyName* name, unsigned line, unsigned column,
-                              FunctionGenerator* fg)
+ModuleGenerator::startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg)
 {
     MOZ_ASSERT(startedFuncDefs());
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(!finishedFuncs_);
 
     if (freeTasks_.empty() && !finishOutstandingTask())
         return false;
 
@@ -446,39 +438,35 @@ ModuleGenerator::startFuncDef(PropertyNa
     if (fg->bytecode_) {
         fg->bytecode_->clear();
     } else {
         fg->bytecode_ = MakeUnique<Bytecode>();
         if (!fg->bytecode_)
             return false;
     }
 
-    fg->name_= name;
-    fg->line_ = line;
-    fg->column_ = column;
+    fg->lineOrBytecode_ = lineOrBytecode;
     fg->m_ = this;
     fg->task_ = task;
     activeFunc_ = fg;
     return true;
 }
 
 bool
 ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg)
 {
     MOZ_ASSERT(activeFunc_ == fg);
 
     UniqueFuncBytecode func =
-        js::MakeUnique<FuncBytecode>(fg->name_,
-                                     fg->line_,
-                                     fg->column_,
-                                     Move(fg->callSourceCoords_),
-                                     funcIndex,
+        js::MakeUnique<FuncBytecode>(funcIndex,
                                      funcSig(funcIndex),
                                      Move(fg->bytecode_),
                                      Move(fg->localVars_),
+                                     fg->lineOrBytecode_,
+                                     Move(fg->callSourceCoords_),
                                      generateTime);
     if (!func)
         return false;
 
     fg->task_->init(Move(func));
 
     if (parallel_) {
         if (!StartOffThreadWasmCompile(cx_, fg->task_))
@@ -602,25 +590,27 @@ ModuleGenerator::defineOutOfBoundsStub(O
     MOZ_ASSERT(finishedFuncs_);
     link_->pod.outOfBoundsOffset = offsets.begin;
     return module_->codeRanges.emplaceBack(CodeRange::Inline, offsets);
 }
 
 bool
 ModuleGenerator::finish(HeapUsage heapUsage,
                         CacheableChars filename,
+                        CacheableCharsVector&& prettyFuncNames,
                         UniqueModuleData* module,
                         UniqueStaticLinkData* linkData,
                         SlowFunctionVector* slowFuncs)
 {
     MOZ_ASSERT(!activeFunc_);
     MOZ_ASSERT(finishedFuncs_);
 
     module_->heapUsage = heapUsage;
     module_->filename = Move(filename);
+    module_->prettyFuncNames = Move(prettyFuncNames);
 
     if (!GenerateStubs(*this, UsesHeap(heapUsage)))
         return false;
 
     masm_.finish();
     if (masm_.oom())
         return false;
 
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -30,26 +30,25 @@ namespace wasm {
 class FunctionGenerator;
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
 
 // A slow function describes a function that took longer than msThreshold to
 // validate and compile.
 
 struct SlowFunction
 {
-    SlowFunction(PropertyName* name, unsigned ms, unsigned line, unsigned column)
-     : name(name), ms(ms), line(line), column(column)
+    SlowFunction(uint32_t index, unsigned ms, unsigned lineOrBytecode)
+     : index(index), ms(ms), lineOrBytecode(lineOrBytecode)
     {}
 
     static const unsigned msThreshold = 250;
 
-    PropertyName* name;
+    uint32_t index;
     unsigned ms;
-    unsigned line;
-    unsigned column;
+    unsigned lineOrBytecode;
 };
 typedef Vector<SlowFunction> SlowFunctionVector;
 
 // The ModuleGeneratorData holds all the state shared between the
 // ModuleGenerator and ModuleGeneratorThreadView. The ModuleGeneratorData is
 // encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
 // present a race-free interface to the code in each thread assuming any given
 // element is initialized by the ModuleGenerator thread before an index to that
@@ -133,17 +132,16 @@ class MOZ_STACK_CLASS ModuleGenerator
     // Data handed back to the caller in finish()
     UniqueModuleData                module_;
     UniqueStaticLinkData            link_;
     SlowFunctionVector              slowFuncs_;
 
     // Data scoped to the ModuleGenerator's lifetime
     UniqueModuleGeneratorData       shared_;
     uint32_t                        numSigs_;
-    uint32_t                        numFuncSigs_;
     LifoAlloc                       lifo_;
     jit::TempAllocator              alloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcEntryOffsets_;
     Uint32Vector                    exportFuncIndices_;
     FuncIndexMap                    funcIndexToExport_;
 
     // Parallel compilation
@@ -155,16 +153,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Assertions
     DebugOnly<FunctionGenerator*>   activeFunc_;
     DebugOnly<bool>                 finishedFuncs_;
 
     bool finishOutstandingTask();
     bool finishTask(IonCompileTask* task);
     bool addImport(const Sig& sig, uint32_t globalDataOffset);
+    bool startedFuncDefs() const { return !!threadView_; }
 
   public:
     explicit ModuleGenerator(ExclusiveContext* cx);
     ~ModuleGenerator();
 
     bool init(UniqueModuleGeneratorData shared, ModuleKind = ModuleKind::Wasm);
 
     CompileArgs args() const { return module_->compileArgs; }
@@ -178,17 +177,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Signatures:
     void initSig(uint32_t sigIndex, Sig&& sig);
     uint32_t numSigs() const { return numSigs_; }
     const DeclaredSig& sig(uint32_t sigIndex) const;
 
     // Function declarations:
     bool initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
-    uint32_t numFuncSigs() const { return numFuncSigs_; }
+    uint32_t numFuncSigs() const { return module_->numFuncs; }
     const DeclaredSig& funcSig(uint32_t funcIndex) const;
 
     // Imports:
     bool initImport(uint32_t importIndex, uint32_t sigIndex, uint32_t globalDataOffset);
     uint32_t numImports() const;
     const ModuleImportGeneratorData& import(uint32_t index) const;
     bool defineImport(uint32_t index, ProfilingOffsets interpExit, ProfilingOffsets jitExit);
 
@@ -196,18 +195,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     bool declareExport(uint32_t funcIndex, uint32_t* exportIndex);
     uint32_t numExports() const;
     uint32_t exportFuncIndex(uint32_t index) const;
     const Sig& exportSig(uint32_t index) const;
     bool defineExport(uint32_t index, Offsets offsets);
 
     // Function definitions:
     bool startFuncDefs();
-    bool startedFuncDefs() const { return !!threadView_; }
-    bool startFuncDef(PropertyName* name, unsigned line, unsigned column, FunctionGenerator* fg);
+    bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     bool finishFuncDef(uint32_t funcIndex, unsigned generateTime, FunctionGenerator* fg);
     bool finishFuncDefs();
 
     // Function-pointer tables:
     bool declareFuncPtrTable(uint32_t numElems, uint32_t* index);
     uint32_t funcPtrTableGlobalDataOffset(uint32_t index) const;
     void defineFuncPtrTable(uint32_t index, const Vector<uint32_t>& elemFuncIndices);
 
@@ -217,16 +215,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     bool defineAsyncInterruptStub(Offsets offsets);
     bool defineOutOfBoundsStub(Offsets offsets);
 
     // Return a ModuleData object which may be used to construct a Module, the
     // StaticLinkData required to call Module::staticallyLink, and the list of
     // functions that took a long time to compile.
     bool finish(HeapUsage heapUsage,
                 CacheableChars filename,
+                CacheableCharsVector&& prettyFuncNames,
                 UniqueModuleData* module,
                 UniqueStaticLinkData* staticLinkData,
                 SlowFunctionVector* slowFuncs);
 };
 
 // A FunctionGenerator encapsulates the generation of a single function body.
 // ModuleGenerator::startFunc must be called after construction and before doing
 // anything else. After the body is complete, ModuleGenerator::finishFunc must
@@ -241,29 +240,21 @@ class MOZ_STACK_CLASS FunctionGenerator
     IonCompileTask*    task_;
 
     // Data created during function generation, then handed over to the
     // FuncBytecode in ModuleGenerator::finishFunc().
     UniqueBytecode     bytecode_;
     SourceCoordsVector callSourceCoords_;
     ValTypeVector      localVars_;
 
-    // Note: this unrooted field assumes AutoKeepAtoms via TokenStream via
-    // asm.js compilation.
-    PropertyName* name_;
-    unsigned line_;
-    unsigned column_;
+    uint32_t lineOrBytecode_;
 
   public:
     FunctionGenerator()
-      : m_(nullptr),
-        task_(nullptr),
-        name_(nullptr),
-        line_(0),
-        column_(0)
+      : m_(nullptr), task_(nullptr), lineOrBytecode_(0)
     {}
 
     Bytecode& bytecode() const {
         return *bytecode_;
     }
     bool addSourceCoords(size_t byteOffset, uint32_t line, uint32_t column) {
         SourceCoords sc = { byteOffset, line, column };
         return callSourceCoords_.append(sc);
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -1309,17 +1309,17 @@ class MaybeType
     MOZ_IMPLICIT operator ExprType() { return maybe_.value(); }
 };
 
 static bool
 EmitLiteral(FunctionCompiler& f, ExprType type, MDefinition**def)
 {
     switch (type) {
       case ExprType::I32: {
-        int32_t val = f.readI32();
+        int32_t val = f.readVarU32();
         *def = f.constant(Int32Value(val), MIRType_Int32);
         return true;
       }
       case ExprType::I64: {
         MOZ_CRASH("int64");
       }
       case ExprType::F32: {
         float val = f.readF32();
@@ -2960,22 +2960,27 @@ wasm::IonCompileFunction(IonCompileTask*
                      task->args().useSignalHandlersForOOB);
 
     // Build MIR graph
     {
         FunctionCompiler f(task->mg(), func, mir, results);
         if (!f.init())
             return false;
 
+        MDefinition* last = nullptr;
         while (!f.done()) {
-            MDefinition* _;
-            if (!EmitExprStmt(f, &_))
+            if (!EmitExprStmt(f, &last))
                 return false;
         }
 
+        if (IsVoid(f.sig().ret()))
+            f.returnVoid();
+        else
+            f.returnExpr(last);
+
         f.checkPostconditions();
     }
 
     // Compile MIR graph
     {
         jit::SpewBeginFunction(&mir, nullptr);
         jit::AutoSpewEndFunction spewEndFunction(&mir);
 
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -346,47 +346,47 @@ Import::clone(JSContext* cx, Import* out
 
 size_t
 Import::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfSigExcludingThis(sig_, mallocSizeOf);
 }
 
 CodeRange::CodeRange(Kind kind, Offsets offsets)
-  : nameIndex_(0),
-    lineNumber_(0),
+  : funcIndex_(0),
+    funcLineOrBytecode_(0),
     begin_(offsets.begin),
     profilingReturn_(0),
     end_(offsets.end)
 {
     PodZero(&u);  // zero padding for Valgrind
     u.kind_ = kind;
 
     MOZ_ASSERT(begin_ <= end_);
     MOZ_ASSERT(u.kind_ == Entry || u.kind_ == Inline);
 }
 
 CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
-  : nameIndex_(0),
-    lineNumber_(0),
+  : funcIndex_(0),
+    funcLineOrBytecode_(0),
     begin_(offsets.begin),
     profilingReturn_(offsets.profilingReturn),
     end_(offsets.end)
 {
     PodZero(&u);  // zero padding for Valgrind
     u.kind_ = kind;
 
     MOZ_ASSERT(begin_ < profilingReturn_);
     MOZ_ASSERT(profilingReturn_ < end_);
     MOZ_ASSERT(u.kind_ == ImportJitExit || u.kind_ == ImportInterpExit || u.kind_ == Interrupt);
 }
 
-CodeRange::CodeRange(uint32_t nameIndex, uint32_t lineNumber, FuncOffsets offsets)
-  : nameIndex_(nameIndex),
-    lineNumber_(lineNumber)
+CodeRange::CodeRange(uint32_t funcIndex, uint32_t funcLineOrBytecode, FuncOffsets offsets)
+  : funcIndex_(funcIndex),
+    funcLineOrBytecode_(funcLineOrBytecode)
 {
     PodZero(&u);  // zero padding for Valgrind
     u.kind_ = Function;
 
     MOZ_ASSERT(offsets.nonProfilingEntry - offsets.begin <= UINT8_MAX);
     begin_ = offsets.begin;
     u.func.beginToEntry_ = offsets.nonProfilingEntry - begin_;
 
@@ -396,26 +396,32 @@ CodeRange::CodeRange(uint32_t nameIndex,
     profilingReturn_ = offsets.profilingReturn;
     u.func.profilingJumpToProfilingReturn_ = profilingReturn_ - offsets.profilingJump;
     u.func.profilingEpilogueToProfilingReturn_ = profilingReturn_ - offsets.profilingEpilogue;
 
     MOZ_ASSERT(offsets.nonProfilingEntry < offsets.end);
     end_ = offsets.end;
 }
 
+static size_t
+NullableStringLength(const char* chars)
+{
+    return chars ? strlen(chars) : 0;
+}
+
 size_t
 CacheableChars::serializedSize() const
 {
-    return sizeof(uint32_t) + strlen(get());
+    return sizeof(uint32_t) + NullableStringLength(get());
 }
 
 uint8_t*
 CacheableChars::serialize(uint8_t* cursor) const
 {
-    uint32_t length = strlen(get());
+    uint32_t length = NullableStringLength(get());
     cursor = WriteBytes(cursor, &length, sizeof(uint32_t));
     cursor = WriteBytes(cursor, get(), length);
     return cursor;
 }
 
 const uint8_t*
 CacheableChars::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
 {
@@ -428,17 +434,17 @@ CacheableChars::deserialize(ExclusiveCon
 
     cursor = ReadBytes(cursor, get(), length);
     return cursor;
 }
 
 bool
 CacheableChars::clone(JSContext* cx, CacheableChars* out) const
 {
-    uint32_t length = strlen(get());
+    uint32_t length = NullableStringLength(get());
 
     UniqueChars chars(cx->pod_calloc<char>(length + 1));
     if (!chars)
         return false;
 
     PodCopy(chars.get(), get(), length);
 
     *out = Move(chars);
@@ -498,31 +504,31 @@ ModuleData::serializedSize() const
 {
     return sizeof(pod()) +
            codeBytes +
            SerializedVectorSize(imports) +
            SerializedVectorSize(exports) +
            SerializedPodVectorSize(heapAccesses) +
            SerializedPodVectorSize(codeRanges) +
            SerializedPodVectorSize(callSites) +
-           SerializedVectorSize(funcNames) +
+           SerializedVectorSize(prettyFuncNames) +
            filename.serializedSize();
 }
 
 uint8_t*
 ModuleData::serialize(uint8_t* cursor) const
 {
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = WriteBytes(cursor, code.get(), codeBytes);
     cursor = SerializeVector(cursor, imports);
     cursor = SerializeVector(cursor, exports);
     cursor = SerializePodVector(cursor, heapAccesses);
     cursor = SerializePodVector(cursor, codeRanges);
     cursor = SerializePodVector(cursor, callSites);
-    cursor = SerializeVector(cursor, funcNames);
+    cursor = SerializeVector(cursor, prettyFuncNames);
     cursor = filename.serialize(cursor);
     return cursor;
 }
 
 /* static */ const uint8_t*
 ModuleData::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
 {
     cursor = ReadBytes(cursor, &pod(), sizeof(pod()));
@@ -532,17 +538,17 @@ ModuleData::deserialize(ExclusiveContext
         return nullptr;
     cursor = ReadBytes(cursor, code.get(), codeBytes);
 
     (cursor = DeserializeVector(cx, cursor, &imports)) &&
     (cursor = DeserializeVector(cx, cursor, &exports)) &&
     (cursor = DeserializePodVector(cx, cursor, &heapAccesses)) &&
     (cursor = DeserializePodVector(cx, cursor, &codeRanges)) &&
     (cursor = DeserializePodVector(cx, cursor, &callSites)) &&
-    (cursor = DeserializeVector(cx, cursor, &funcNames)) &&
+    (cursor = DeserializeVector(cx, cursor, &prettyFuncNames)) &&
     (cursor = filename.deserialize(cx, cursor));
     return cursor;
 }
 
 bool
 ModuleData::clone(JSContext* cx, ModuleData* out) const
 {
     out->pod() = pod();
@@ -552,30 +558,30 @@ ModuleData::clone(JSContext* cx, ModuleD
         return false;
     memcpy(out->code.get(), code.get(), codeBytes);
 
     return CloneVector(cx, imports, &out->imports) &&
            CloneVector(cx, exports, &out->exports) &&
            ClonePodVector(cx, heapAccesses, &out->heapAccesses) &&
            ClonePodVector(cx, codeRanges, &out->codeRanges) &&
            ClonePodVector(cx, callSites, &out->callSites) &&
-           CloneVector(cx, funcNames, &out->funcNames) &&
+           CloneVector(cx, prettyFuncNames, &out->prettyFuncNames) &&
            filename.clone(cx, &out->filename);
 }
 
 size_t
 ModuleData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     // Module::addSizeOfMisc takes care of code and global memory.
     return SizeOfVectorExcludingThis(imports, mallocSizeOf) +
            SizeOfVectorExcludingThis(exports, mallocSizeOf) +
            heapAccesses.sizeOfExcludingThis(mallocSizeOf) +
            codeRanges.sizeOfExcludingThis(mallocSizeOf) +
            callSites.sizeOfExcludingThis(mallocSizeOf) +
-           funcNames.sizeOfExcludingThis(mallocSizeOf) +
+           prettyFuncNames.sizeOfExcludingThis(mallocSizeOf) +
            filename.sizeOfExcludingThis(mallocSizeOf);
 }
 
 uint8_t*
 Module::rawHeapPtr() const
 {
     return const_cast<Module*>(this)->rawHeapPtr();
 }
@@ -669,96 +675,115 @@ Module::despecializeFromHeap(ArrayBuffer
             X86Encoding::AddInt32(access.patchLengthAt(code()), -heapLength);
     }
 #endif
 
     heap_ = nullptr;
     rawHeapPtr() = nullptr;
 }
 
-void
+bool
 Module::sendCodeRangesToProfiler(JSContext* cx)
 {
+    bool enabled = false;
 #ifdef JS_ION_PERF
-    if (PerfFuncEnabled()) {
-        for (const CodeRange& codeRange : module_->codeRanges) {
-            if (!codeRange.isFunction())
-                continue;
+    enabled |= PerfFuncEnabled();
+#endif
+#ifdef MOZ_VTUNE
+    enabled |= IsVTuneProfilingActive();
+#endif
+    if (!enabled)
+        return true;
+
+    for (const CodeRange& codeRange : module_->codeRanges) {
+        if (!codeRange.isFunction())
+            continue;
 
-            uintptr_t start = uintptr_t(code() + codeRange.begin());
-            uintptr_t end = uintptr_t(code() + codeRange.end());
-            uintptr_t size = end - start;
+        uintptr_t start = uintptr_t(code() + codeRange.begin());
+        uintptr_t end = uintptr_t(code() + codeRange.end());
+        uintptr_t size = end - start;
+
+        UniqueChars owner;
+        const char* name = getFuncName(cx, codeRange.funcIndex(), &owner);
+        if (!name)
+            return false;
+
+        // Avoid "unused" warnings
+        (void)start;
+        (void)size;
+        (void)name;
+
+#ifdef JS_ION_PERF
+        if (PerfFuncEnabled()) {
             const char* file = module_->filename.get();
-            unsigned line = codeRange.funcLineNumber();
+            unsigned line = codeRange.funcLineOrBytecode();
             unsigned column = 0;
-            const char* name = module_->funcNames[codeRange.funcNameIndex()].get();
-
             writePerfSpewerAsmJSFunctionMap(start, size, file, line, column, name);
         }
-    }
 #endif
 #ifdef MOZ_VTUNE
-    if (IsVTuneProfilingActive()) {
-        for (const CodeRange& codeRange : module_->codeRanges) {
-            if (!codeRange.isFunction())
-                continue;
-
-            uintptr_t start = uintptr_t(code() + codeRange.begin());
-            uintptr_t end = uintptr_t(code() + codeRange.end());
-            uintptr_t size = end - start;
-            const char* name = module_->funcNames[codeRange.funcNameIndex()].get();
-
+        if (IsVTuneProfilingActive()) {
             unsigned method_id = iJIT_GetNewMethodID();
             if (method_id == 0)
-                return;
+                return true;
             iJIT_Method_Load method;
             method.method_id = method_id;
             method.method_name = const_cast<char*>(name);
             method.method_load_address = (void*)start;
             method.method_size = size;
             method.line_number_size = 0;
             method.line_number_table = nullptr;
             method.class_id = 0;
             method.class_file_name = nullptr;
             method.source_file_name = nullptr;
             iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&method);
         }
+#endif
     }
-#endif
+
+    return true;
 }
 
 bool
 Module::setProfilingEnabled(JSContext* cx, bool enabled)
 {
     MOZ_ASSERT(dynamicallyLinked_);
     MOZ_ASSERT(!activation());
 
     if (profilingEnabled_ == enabled)
         return true;
 
     // When enabled, generate profiling labels for every name in funcNames_
     // that is the name of some Function CodeRange. This involves malloc() so
     // do it now since, once we start sampling, we'll be in a signal-handing
     // context where we cannot malloc.
     if (enabled) {
-        if (!funcLabels_.resize(module_->funcNames.length())) {
+        if (!funcLabels_.resize(module_->numFuncs)) {
             ReportOutOfMemory(cx);
             return false;
         }
         for (const CodeRange& codeRange : module_->codeRanges) {
             if (!codeRange.isFunction())
                 continue;
-            unsigned lineno = codeRange.funcLineNumber();
-            const char* name = module_->funcNames[codeRange.funcNameIndex()].get();
-            UniqueChars label(JS_smprintf("%s (%s:%u)", name, module_->filename.get(), lineno));
+
+            UniqueChars owner;
+            const char* funcName = getFuncName(cx, codeRange.funcIndex(), &owner);
+            if (!funcName)
+                return false;
+
+            UniqueChars label(JS_smprintf("%s (%s:%u)",
+                                          funcName,
+                                          module_->filename.get(),
+                                          codeRange.funcLineOrBytecode()));
             if (!label) {
                 ReportOutOfMemory(cx);
                 return false;
             }
-            funcLabels_[codeRange.funcNameIndex()] = Move(label);
+
+            funcLabels_[codeRange.funcIndex()] = Move(label);
         }
     } else {
         funcLabels_.clear();
     }
 
     // Patch callsites and returns to execute profiling prologues/epilogues.
     {
         AutoWritableJitCode awjc(cx->runtime(), code(), codeBytes());
@@ -1071,18 +1096,17 @@ Module::dynamicallyLink(JSContext* cx, H
         specializeToHeap(heap);
 
     // See AllocateCode comment above.
     if (!ExecutableAllocator::makeExecutable(code(), codeBytes())) {
         ReportOutOfMemory(cx);
         return false;
     }
 
-    sendCodeRangesToProfiler(cx);
-    return true;
+    return sendCodeRangesToProfiler(cx);
 }
 
 static bool
 WasmCall(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &args.callee().as<JSFunction>());
 
@@ -1398,16 +1422,38 @@ Module::callImport(JSContext* cx, uint32
         return false;
 
     exit.code = jitExitCode;
     exit.baselineScript = script->baselineScript();
     return true;
 }
 
 const char*
+Module::prettyFuncName(uint32_t funcIndex) const
+{
+    return module_->prettyFuncNames[funcIndex].get();
+}
+
+const char*
+Module::getFuncName(JSContext* cx, uint32_t funcIndex, UniqueChars* owner) const
+{
+    if (!module_->prettyFuncNames.empty())
+        return prettyFuncName(funcIndex);
+
+    char* chars = JS_smprintf("wasm-function[%u]", funcIndex);
+    if (!chars) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    owner->reset(chars);
+    return chars;
+}
+
+const char*
 Module::profilingLabel(uint32_t funcIndex) const
 {
     MOZ_ASSERT(dynamicallyLinked_);
     MOZ_ASSERT(profilingEnabled_);
     return funcLabels_[funcIndex].get();
 }
 
 const Class WasmModuleObject::class_ = {
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -192,18 +192,18 @@ typedef Vector<Import, 0, SystemAllocPol
 
 // A CodeRange describes a single contiguous range of code within a wasm
 // module's code segment. A CodeRange describes what the code does and, for
 // function bodies, the name and source coordinates of the function.
 
 class CodeRange
 {
     // All fields are treated as cacheable POD:
-    uint32_t nameIndex_;
-    uint32_t lineNumber_;
+    uint32_t funcIndex_;
+    uint32_t funcLineOrBytecode_;
     uint32_t begin_;
     uint32_t profilingReturn_;
     uint32_t end_;
     union {
         struct {
             uint8_t kind_;
             uint8_t beginToEntry_;
             uint8_t profilingJumpToProfilingReturn_;
@@ -215,17 +215,17 @@ class CodeRange
     void assertValid();
 
   public:
     enum Kind { Function, Entry, ImportJitExit, ImportInterpExit, Interrupt, Inline };
 
     CodeRange() = default;
     CodeRange(Kind kind, Offsets offsets);
     CodeRange(Kind kind, ProfilingOffsets offsets);
-    CodeRange(uint32_t nameIndex, uint32_t lineNumber, FuncOffsets offsets);
+    CodeRange(uint32_t funcIndex, uint32_t lineOrBytecode, FuncOffsets offsets);
 
     // All CodeRanges have a begin and end.
 
     uint32_t begin() const {
         return begin_;
     }
     uint32_t end() const {
         return end_;
@@ -260,23 +260,23 @@ class CodeRange
     uint32_t functionProfilingJump() const {
         MOZ_ASSERT(isFunction());
         return profilingReturn_ - u.func.profilingJumpToProfilingReturn_;
     }
     uint32_t funcProfilingEpilogue() const {
         MOZ_ASSERT(isFunction());
         return profilingReturn_ - u.func.profilingEpilogueToProfilingReturn_;
     }
-    uint32_t funcNameIndex() const {
+    uint32_t funcIndex() const {
         MOZ_ASSERT(isFunction());
-        return nameIndex_;
+        return funcIndex_;
     }
-    uint32_t funcLineNumber() const {
+    uint32_t funcLineOrBytecode() const {
         MOZ_ASSERT(isFunction());
-        return lineNumber_;
+        return funcLineOrBytecode_;
     }
 
     // A sorted array of CodeRanges can be looked up via BinarySearch and PC.
 
     struct PC {
         size_t offset;
         explicit PC(size_t offset) : offset(offset) {}
         bool operator==(const CodeRange& rhs) const {
@@ -361,16 +361,17 @@ enum ModuleKind
 // ModuleCacheablePod holds the trivially-memcpy()able serializable portion of
 // ModuleData.
 
 struct ModuleCacheablePod
 {
     uint32_t              functionBytes;
     uint32_t              codeBytes;
     uint32_t              globalBytes;
+    uint32_t              numFuncs;
     ModuleKind            kind;
     HeapUsage             heapUsage;
     CompileArgs           compileArgs;
 
     uint32_t totalBytes() const { return codeBytes + globalBytes; }
 };
 
 // ModuleData holds the guts of a Module. ModuleData is mutably built up by
@@ -384,17 +385,17 @@ struct ModuleData : ModuleCacheablePod
     const ModuleCacheablePod& pod() const { return *this; }
 
     UniqueCodePtr         code;
     ImportVector          imports;
     ExportVector          exports;
     HeapAccessVector      heapAccesses;
     CodeRangeVector       codeRanges;
     CallSiteVector        callSites;
-    CacheableCharsVector  funcNames;
+    CacheableCharsVector  prettyFuncNames;
     CacheableChars        filename;
     bool                  loadedFromCache;
 
     WASM_DECLARE_SERIALIZABLE(ModuleData);
 };
 
 typedef UniquePtr<ModuleData> UniqueModuleData;
 
@@ -461,17 +462,17 @@ class Module
     bool                         profilingEnabled_;
     FuncLabelVector              funcLabels_;
 
     uint8_t* rawHeapPtr() const;
     uint8_t*& rawHeapPtr();
     WasmActivation*& activation();
     void specializeToHeap(ArrayBufferObjectMaybeShared* heap);
     void despecializeFromHeap(ArrayBufferObjectMaybeShared* heap);
-    void sendCodeRangesToProfiler(JSContext* cx);
+    bool sendCodeRangesToProfiler(JSContext* cx);
     MOZ_WARN_UNUSED_RESULT bool setProfilingEnabled(JSContext* cx, bool enabled);
     ImportExit& importToExit(const Import& import);
 
     friend class js::WasmActivation;
 
   protected:
     const ModuleData& base() const { return *module_; }
     bool clone(JSContext* cx, const StaticLinkData& link, Module* clone) const;
@@ -491,17 +492,16 @@ class Module
     uint8_t* globalData() const { return code() + module_->codeBytes; }
     uint32_t globalBytes() const { return module_->globalBytes; }
     HeapUsage heapUsage() const { return module_->heapUsage; }
     bool usesHeap() const { return UsesHeap(module_->heapUsage); }
     bool hasSharedHeap() const { return module_->heapUsage == HeapUsage::Shared; }
     CompileArgs compileArgs() const { return module_->compileArgs; }
     const ImportVector& imports() const { return module_->imports; }
     const ExportVector& exports() const { return module_->exports; }
-    const char* functionName(uint32_t i) const { return module_->funcNames[i].get(); }
     const char* filename() const { return module_->filename.get(); }
     bool loadedFromCache() const { return module_->loadedFromCache; }
     bool staticallyLinked() const { return staticallyLinked_; }
     bool dynamicallyLinked() const { return dynamicallyLinked_; }
 
     // Some wasm::Module's have the most-derived type AsmJSModule. The
     // AsmJSModule stores the extra metadata necessary to implement asm.js (JS)
     // semantics. The asAsmJS() member may be used as a checked downcast when
@@ -568,16 +568,23 @@ class Module
 
     // At runtime, when $pc is in wasm function code (containsFunctionPC($pc)),
     // $pc may be moved abruptly to interrupt() or outOfBounds() by a signal
     // handler or SetContext() from another thread.
 
     uint8_t* interrupt() const { MOZ_ASSERT(staticallyLinked_); return interrupt_; }
     uint8_t* outOfBounds() const { MOZ_ASSERT(staticallyLinked_); return outOfBounds_; }
 
+    // Every function has an associated display atom which is either the pretty
+    // name given by the asm.js function name or wasm symbols or something
+    // generated from the function index.
+
+    const char* prettyFuncName(uint32_t funcIndex) const;
+    const char* getFuncName(JSContext* cx, uint32_t funcIndex, UniqueChars* owner) const;
+
     // Each Module has a profilingEnabled state which is updated to match
     // SPSProfiler::enabled() on the next Module::callExport when there are no
     // frames from the Module on the stack. The ProfilingFrameIterator only
     // shows frames for Module activations that have profilingEnabled.
 
     bool profilingEnabled() const { return profilingEnabled_; }
     const char* profilingLabel(uint32_t funcIndex) const;
 };
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -209,18 +209,17 @@ TryEvalJSON(JSContext* cx, JSLinearStrin
     if (!linearChars.init(cx, str))
         return EvalJSON_Failure;
 
     return linearChars.isLatin1()
            ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
            : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
 }
 
-// Define subset of ExecuteType so that casting performs the injection.
-enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
+enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
 
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
 // must be a global object.
 //
@@ -325,17 +324,17 @@ EvalKernel(JSContext* cx, const CallArgs
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
     }
 
     // Look up the newTarget from the frame iterator.
     Value newTargetVal = NullValue();
-    return ExecuteKernel(cx, esg.script(), *scopeobj, newTargetVal, ExecuteType(evalType),
+    return ExecuteKernel(cx, esg.script(), *scopeobj, newTargetVal,
                          NullFramePtr() /* evalInFrame */, args.rval().address());
 }
 
 bool
 js::DirectEvalStringFromIon(JSContext* cx,
                             HandleObject scopeobj, HandleScript callerScript,
                             HandleValue newTargetValue, HandleString str,
                             jsbytecode* pc, MutableHandleValue vp)
@@ -406,17 +405,17 @@ js::DirectEvalStringFromIon(JSContext* c
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, newTargetValue,
-                         ExecuteType(DIRECT_EVAL), NullFramePtr() /* evalInFrame */, vp.address());
+                         NullFramePtr() /* evalInFrame */, vp.address());
 }
 
 bool
 js::IndirectEval(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Rooted<GlobalObject*> global(cx, &args.callee().global());
     RootedObject globalLexical(cx, &global->lexicalScope());
@@ -478,17 +477,17 @@ js::ExecuteInGlobalAndReturnScope(JSCont
     // Unlike the non-syntactic scope chain API used by the subscript loader,
     // this API creates a fresh block scope each time.
     RootedObject enclosingStaticScope(cx, script->enclosingStaticScope());
     scope = ClonedBlockObject::createNonSyntactic(cx, enclosingStaticScope, scope);
     if (!scope)
         return false;
 
     RootedValue rval(cx);
-    if (!ExecuteKernel(cx, script, *scope, UndefinedValue(), EXECUTE_GLOBAL_OR_MODULE,
+    if (!ExecuteKernel(cx, script, *scope, UndefinedValue(),
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
         return false;
     }
 
     scopeArg.set(scope);
     return true;
 }
--- a/js/src/jit-test/tests/asm.js/testCloning.js
+++ b/js/src/jit-test/tests/asm.js/testCloning.js
@@ -1,15 +1,21 @@
 // |jit-test| test-also-noasmjs
 load(libdir + "asm.js");
 
+setCachingEnabled(true);
+
 var code = asmCompile(USE_ASM + "function g() { return 42 } return g");
 assertEq(asmLink(code)(), 42);
 assertEq(asmLink(code)(), 42);
 
+var code = evaluate("(function() { " + USE_ASM + " function g() { return 43 } return g})", {fileName: null});
+assertEq(asmLink(code)(), 43);
+assertEq(asmLink(code)(), 43);
+
 var code = asmCompile('glob', 'ffis', 'buf', USE_ASM + 'var i32=new glob.Int32Array(buf); function g() { return i32[0]|0 } return g');
 var i32_1 = new Int32Array(BUF_MIN/4);
 i32_1[0] = 42;
 var i32_2 = new Int32Array(BUF_MIN/4);
 i32_2[0] = 13;
 assertEq(asmLink(code, this, null, i32_1.buffer)(), 42);
 assertEq(asmLink(code, this, null, i32_2.buffer)(), 13);
 var i32_3 = new Int32Array(4097);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240546.js
@@ -0,0 +1,12 @@
+// |jit-test| allow-oom
+
+if (!('oomAfterAllocations' in this))
+  quit();
+
+var g = newGlobal();
+g.debuggeeGlobal = this;
+g.eval("(" + function() {
+    oomAfterAllocations(100);
+    var dbg = Debugger(debuggeeGlobal);
+    dbg.onEnterFrame = function(frame) {}
+} + ")()");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1240803.js
@@ -0,0 +1,24 @@
+// |jit-test| allow-oom
+
+
+if (!('oomAfterAllocations' in this))
+  quit();
+
+(function() {
+    g = newGlobal()
+    dbg = new Debugger
+    g.toggle = function(d) {
+        if (d) {
+            dbg.addDebuggee(g);
+            dbg.getNewestFrame();
+            oomAfterAllocations(2);
+            setBreakpoint;
+        }
+    }
+    g.eval("" + function f(d) toggle(d))
+    g.eval("(" + function() {
+        f(false);
+        f(true);
+    } + ")()")
+})();
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-03.js
@@ -0,0 +1,31 @@
+// Test that eval-in-frame throws on accessing optimized out values.
+
+load(libdir + "jitopts.js");
+
+if (!jitTogglesMatch(Opts_IonEagerNoOffthreadCompilation))
+  quit(0);
+
+withJitOptions(Opts_IonEagerNoOffthreadCompilation, function() {
+  var dbgGlobal = newGlobal();
+  var dbg = new dbgGlobal.Debugger();
+  dbg.addDebuggee(this);
+
+  function f() {
+    assertEq(dbg.getNewestFrame().older.eval("print(a)").throw.unsafeDereference().toString(),
+             "Error: variable `a' has been optimized out");
+  }
+
+  // Test optimized out binding in function scope.
+  (function () {
+    function a() {}
+    for (var i = 0; i < 1; i++) f();
+  })();
+
+  // Test optimized out binding in block scope.
+  (function () {
+    {
+      function a() {}
+      for (var i = 0; i < 1; i++) f();
+    }
+  })();
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-1236473.js
@@ -0,0 +1,7 @@
+if (!('oomTest' in this))
+  quit();
+
+oomTest(() => {
+    offThreadCompileScript(`try {} catch (NaN) {}`);
+    runOffThreadScript();
+});
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -374,19 +374,17 @@ jit::CanEnterBaselineMethod(JSContext* c
         if (!state.maybeCreateThisForConstructor(cx)) {
             if (cx->isThrowingOutOfMemory()) {
                 cx->recoverFromOutOfMemory();
                 return Method_Skipped;
             }
             return Method_Error;
         }
     } else {
-        MOZ_ASSERT(state.isExecute());
-        ExecuteType type = state.asExecute()->type();
-        if (type == EXECUTE_DEBUG) {
+        if (state.asExecute()->isDebuggerEval()) {
             JitSpew(JitSpew_BaselineAbort, "debugger frame");
             return Method_CantCompile;
         }
     }
 
     RootedScript script(cx, state.script());
     return CanEnterBaselineJIT(cx, script, /* osrFrame = */ nullptr);
 };
--- a/js/src/jit/ICStubSpace.h
+++ b/js/src/jit/ICStubSpace.h
@@ -39,33 +39,33 @@ class ICStubSpace
         return allocator_.sizeOfExcludingThis(mallocSizeOf);
     }
 };
 
 // Space for optimized stubs. Every JitCompartment has a single
 // OptimizedICStubSpace.
 struct OptimizedICStubSpace : public ICStubSpace
 {
-    static const size_t STUB_DEFAULT_CHUNK_SIZE = 4 * 1024;
+    static const size_t STUB_DEFAULT_CHUNK_SIZE = 4096;
 
   public:
     OptimizedICStubSpace()
       : ICStubSpace(STUB_DEFAULT_CHUNK_SIZE)
     {}
 
     void free() {
         allocator_.freeAll();
     }
 };
 
 // Space for fallback stubs. Every BaselineScript has a
 // FallbackICStubSpace.
 struct FallbackICStubSpace : public ICStubSpace
 {
-    static const size_t STUB_DEFAULT_CHUNK_SIZE = 256;
+    static const size_t STUB_DEFAULT_CHUNK_SIZE = 4096;
 
   public:
     FallbackICStubSpace()
       : ICStubSpace(STUB_DEFAULT_CHUNK_SIZE)
     {}
 
     inline void adoptFrom(FallbackICStubSpace* other) {
         allocator_.steal(&(other->allocator_));
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -411,17 +411,17 @@ MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH
 MSG_DEF(JSMSG_DEBUG_LOOP,              0, JSEXN_TYPEERR, "cannot debug an object in same compartment as debugger or a compartment that is already debugging the debugger")
 MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGEE,      2, JSEXN_ERR, "{0} is not a debuggee {1}")
 MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGING,     0, JSEXN_ERR, "can't set breakpoint: script global is not a debuggee")
 MSG_DEF(JSMSG_DEBUG_NOT_IDLE,          0, JSEXN_ERR, "can't start debugging: a debuggee script is on the stack")
 MSG_DEF(JSMSG_DEBUG_NOT_LIVE,          1, JSEXN_ERR, "{0} is not live")
 MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT,   0, JSEXN_TYPEERR, "declarative Environments don't have binding objects")
 MSG_DEF(JSMSG_DEBUG_OBJECT_PROTO,      0, JSEXN_TYPEERR, "Debugger.Object.prototype is not a valid Debugger.Object")
 MSG_DEF(JSMSG_DEBUG_OBJECT_WRONG_OWNER,0, JSEXN_TYPEERR, "Debugger.Object belongs to a different Debugger")
-MSG_DEF(JSMSG_DEBUG_OPTIMIZED_OUT,     0, JSEXN_ERR, "variable has been optimized out")
+MSG_DEF(JSMSG_DEBUG_OPTIMIZED_OUT,     1, JSEXN_ERR, "variable `{0}' has been optimized out")
 MSG_DEF(JSMSG_DEBUG_RESUMPTION_VALUE_DISALLOWED, 0, JSEXN_TYPEERR, "resumption values are disallowed in this hook")
 MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND,0, JSEXN_TYPEERR, "variable not found in environment")
 MSG_DEF(JSMSG_DEBUG_WRAPPER_IN_WAY,    3, JSEXN_TYPEERR, "{0} is {1}{2}a global object, but a direct reference is required")
 MSG_DEF(JSMSG_NOT_CALLABLE_OR_UNDEFINED, 0, JSEXN_TYPEERR, "value is not a function or undefined")
 MSG_DEF(JSMSG_NOT_TRACKING_ALLOCATIONS, 1, JSEXN_ERR, "Cannot call {0} without setting trackingAllocationSites to true")
 MSG_DEF(JSMSG_NOT_TRACKING_TENURINGS,  1, JSEXN_ERR, "Cannot call {0} without setting trackingTenurePromotions to true")
 MSG_DEF(JSMSG_OBJECT_METADATA_CALLBACK_ALREADY_SET, 0, JSEXN_ERR, "Cannot track object allocation, because other tools are already doing so")
 MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 0, JSEXN_TYPEERR, "findScripts query object with 'innermost' property must have 'line' and either 'displayURL', 'url', or 'source'")
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6858,16 +6858,20 @@ gc::MergeCompartments(JSCompartment* sou
     targetStaticGlobalLexicalScope = &target->maybeGlobal()->lexicalScope().staticBlock();
 
     for (ZoneCellIter iter(source->zone(), AllocKind::SCRIPT); !iter.done(); iter.next()) {
         JSScript* script = iter.get<JSScript>();
         MOZ_ASSERT(script->compartment() == source);
         script->compartment_ = target;
         script->setTypesGeneration(target->zone()->types.generation);
 
+        // If the script failed to compile, no need to fix up.
+        if (!script->code())
+            continue;
+
         // See warning in handleParseWorkload. If we start optimizing global
         // lexicals, we would need to merge the contents of the static global
         // lexical scope.
         if (JSObject* enclosing = script->enclosingStaticScope()) {
             if (IsStaticGlobalLexicalScope(enclosing))
                 script->fixEnclosingStaticGlobalLexicalScope();
         }
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2288,21 +2288,26 @@ Debugger::updateObservesAllExecutionOnDe
 
         if (comp->debuggerObservesAllExecution() == observing)
             continue;
 
         // It's expensive to eagerly invalidate and recompile a compartment,
         // so add the compartment to the set only if we are observing.
         if (observing && !obs.add(comp))
             return false;
-
-        comp->updateDebuggerObservesAllExecution();
-    }
-
-    return updateExecutionObservability(cx, obs, observing);
+    }
+
+    if (!updateExecutionObservability(cx, obs, observing))
+        return false;
+
+    typedef ExecutionObservableCompartments::CompartmentRange CompartmentRange;
+    for (CompartmentRange r = obs.compartments()->all(); !r.empty(); r.popFront())
+        r.front()->updateDebuggerObservesAllExecution();
+
+    return true;
 }
 
 bool
 Debugger::updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing)
 {
     ExecutionObservableCompartments obs(cx);
     if (!obs.init())
         return false;
@@ -2852,20 +2857,24 @@ Debugger::setHookImpl(JSContext* cx, Cal
         return false;
     if (args[0].isObject()) {
         if (!args[0].toObject().isCallable())
             return ReportIsNotFunction(cx, args[0], args.length() - 1);
     } else if (!args[0].isUndefined()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
         return false;
     }
-    dbg.object->setReservedSlot(JSSLOT_DEBUG_HOOK_START + which, args[0]);
+    uint32_t slot = JSSLOT_DEBUG_HOOK_START + which;
+    RootedValue oldHook(cx, dbg.object->getReservedSlot(slot));
+    dbg.object->setReservedSlot(slot, args[0]);
     if (hookObservesAllExecution(which)) {
-        if (!dbg.updateObservesAllExecutionOnDebuggees(cx, dbg.observesAllExecution()))
-            return false;
+        if (!dbg.updateObservesAllExecutionOnDebuggees(cx, dbg.observesAllExecution())) {
+            dbg.object->setReservedSlot(slot, oldHook);
+            return false;
+        }
     }
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 Debugger::getOnDebuggerStatement(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -5396,44 +5405,52 @@ Debugger::observesScript(JSScript* scrip
     // self-hosted invariants.
     return observesGlobal(&script->global()) && !script->selfHosted();
 }
 
 /* static */ bool
 Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
                            ScriptFrameIter& iter)
 {
+    auto removeFromDebuggerFramesOnExit = MakeScopeExit([&] {
+        // Remove any remaining old entries on exit, as the 'from' frame will
+        // be gone. On success, the range will be empty.
+        for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
+            r.frontFrame()->setPrivate(nullptr);
+            r.removeFrontFrame();
+        }
+
+        // Rekey missingScopes to maintain Debugger.Environment identity and
+        // forward liveScopes to point to the new frame.
+        DebugScopes::forwardLiveFrame(cx, from, to);
+    });
+
     // Forward live Debugger.Frame objects.
     for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
         RootedNativeObject frameobj(cx, r.frontFrame());
         Debugger* dbg = r.frontDebugger();
         MOZ_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
 
         // Update frame object's ScriptFrameIter::data pointer.
         DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj);
         ScriptFrameIter::Data* data = iter.copyData();
         if (!data)
             return false;
         frameobj->setPrivate(data);
 
-        // Remove the old entry before mutating the HashMap.
+        // Remove the old frame.
         r.removeFrontFrame();
 
         // Add the frame object with |to| as key.
         if (!dbg->frames.putNew(to, frameobj)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
-    // Rekey missingScopes to maintain Debugger.Environment identity and
-    // forward liveScopes to point to the new frame, as the old frame will be
-    // gone.
-    DebugScopes::forwardLiveFrame(cx, from, to);
-
     return true;
 }
 
 /* static */ bool
 Debugger::inFrameMaps(AbstractFramePtr frame)
 {
     FrameRange r(frame);
     return !r.empty();
@@ -6727,18 +6744,17 @@ EvaluateInEnv(JSContext* cx, Handle<Env*
 
     // Again, executeInGlobal is not considered eval.
     if (frame) {
         if (script->strict())
             staticScope->as<StaticEvalObject>().setStrict();
         script->setActiveEval();
     }
 
-    ExecuteType type = !frame ? EXECUTE_GLOBAL_OR_MODULE : EXECUTE_DEBUG;
-    return ExecuteKernel(cx, script, *env, NullValue(), type, frame, rval.address());
+    return ExecuteKernel(cx, script, *env, NullValue(), frame, rval.address());
 }
 
 enum EvalBindings { EvalHasExtraBindings = true, EvalWithDefaultBindings = false };
 
 static bool
 DebuggerGenericEval(JSContext* cx, const char* fullMethodName, const Value& code,
                     EvalBindings evalWithBindings, HandleValue bindings, HandleValue options,
                     MutableHandleValue vp, Debugger* dbg, HandleObject scope,
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -364,24 +364,24 @@ RunState::maybeCreateThisForConstructor(
 }
 
 static MOZ_NEVER_INLINE bool
 Interpret(JSContext* cx, RunState& state);
 
 InterpreterFrame*
 InvokeState::pushInterpreterFrame(JSContext* cx)
 {
-    return cx->runtime()->interpreterStack().pushInvokeFrame(cx, args_, initial_);
+    return cx->runtime()->interpreterStack().pushInvokeFrame(cx, args_, construct_);
 }
 
 InterpreterFrame*
 ExecuteState::pushInterpreterFrame(JSContext* cx)
 {
     return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, newTargetValue_,
-                                                              scopeChain_, type_, evalInFrame_);
+                                                              scopeChain_, evalInFrame_);
 }
 // MSVC with PGO inlines a lot of functions in RunScript, resulting in large
 // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
 // avoid this.
 #ifdef _MSC_VER
 # pragma optimize("g", off)
 #endif
 bool
@@ -445,19 +445,16 @@ bool
 js::Invoke(JSContext* cx, const CallArgs& args, MaybeConstruct construct)
 {
     MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
     MOZ_ASSERT(!cx->zone()->types.activeAnalysis);
 
     /* Perform GC if necessary on exit from the function. */
     AutoGCIfRequested gcIfRequested(cx->runtime());
 
-    /* MaybeConstruct is a subset of InitialFrameFlags */
-    InitialFrameFlags initial = (InitialFrameFlags) construct;
-
     unsigned skipForCallee = args.length() + 1 + (construct == CONSTRUCT);
     if (args.calleev().isPrimitive())
         return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct);
 
     /* Invoke non-functions. */
     if (MOZ_UNLIKELY(!args.callee().is<JSFunction>())) {
         MOZ_ASSERT_IF(construct, !args.callee().constructHook());
         JSNative call = args.callee().callHook();
@@ -477,17 +474,17 @@ js::Invoke(JSContext* cx, const CallArgs
         MOZ_ASSERT_IF(construct, !fun->isConstructor());
         return CallJSNative(cx, fun->native(), args);
     }
 
     if (!fun->getOrCreateScript(cx))
         return false;
 
     /* Run function until JSOP_RETRVAL, JSOP_RETURN or error. */
-    InvokeState state(cx, args, initial);
+    InvokeState state(cx, args, construct);
 
     // Check to see if createSingleton flag should be set for this frame.
     if (construct) {
         jsbytecode* pc;
         if (JSScript* script = cx->currentScript(&pc)) {
             if (ObjectGroup::useSingletonForNewObject(cx, script, pc))
                 state.setCreateSingleton();
         }
@@ -645,21 +642,20 @@ js::InvokeSetter(JSContext* cx, const Va
     JS_CHECK_RECURSION(cx, return false);
 
     RootedValue ignored(cx);
     return Invoke(cx, thisv, fval, 1, v.address(), &ignored);
 }
 
 bool
 js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg,
-                  const Value& newTargetValue, ExecuteType type, AbstractFramePtr evalInFrame,
+                  const Value& newTargetValue, AbstractFramePtr evalInFrame,
                   Value* result)
 {
-    MOZ_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG);
-    MOZ_ASSERT_IF(type == EXECUTE_GLOBAL_OR_MODULE && !script->module(),
+    MOZ_ASSERT_IF(script->isGlobalCode(),
                   IsGlobalLexicalScope(&scopeChainArg) || !IsSyntacticScope(&scopeChainArg));
 #ifdef DEBUG
     RootedObject terminatingScope(cx, &scopeChainArg);
     while (IsSyntacticScope(terminatingScope))
         terminatingScope = terminatingScope->enclosingScope();
     MOZ_ASSERT(terminatingScope->is<GlobalObject>() ||
                script->hasNonSyntacticScope());
 #endif
@@ -675,17 +671,17 @@ js::ExecuteKernel(JSContext* cx, HandleS
 
     if (script->isEmpty()) {
         if (result)
             result->setUndefined();
         return true;
     }
 
     probes::StartExecution(script);
-    ExecuteState state(cx, script, newTargetValue, scopeChainArg, type, evalInFrame, result);
+    ExecuteState state(cx, script, newTargetValue, scopeChainArg, evalInFrame, result);
     bool ok = RunScript(cx, state);
     probes::StopExecution(script);
 
     return ok;
 }
 
 bool
 js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value* rval)
@@ -708,17 +704,17 @@ js::Execute(JSContext* cx, HandleScript 
 #ifdef DEBUG
     JSObject* s = scopeChain;
     do {
         assertSameCompartment(cx, s);
         MOZ_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
     } while ((s = s->enclosingScope()));
 #endif
 
-    return ExecuteKernel(cx, script, *scopeChain, NullValue(), EXECUTE_GLOBAL_OR_MODULE,
+    return ExecuteKernel(cx, script, *scopeChain, NullValue(),
                          NullFramePtr() /* evalInFrame */, rval);
 }
 
 bool
 js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
 {
     const Class* clasp = obj->getClass();
     RootedValue local(cx, v);
@@ -2772,17 +2768,17 @@ CASE(JSOP_NEW)
 CASE(JSOP_CALL)
 CASE(JSOP_CALLITER)
 CASE(JSOP_SUPERCALL)
 CASE(JSOP_FUNCALL)
 {
     if (REGS.fp()->hasPushedSPSFrame())
         cx->runtime()->spsProfiler.updatePC(script, REGS.pc);
 
-    bool construct = (*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
+    MaybeConstruct construct = MaybeConstruct(*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
     unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
 
     MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
     CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct);
 
     JSFunction* maybeFun;
     bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
 
@@ -2810,23 +2806,22 @@ CASE(JSOP_FUNCALL)
 
     {
         MOZ_ASSERT(maybeFun);
         ReservedRooted<JSFunction*> fun(&rootFunction0, maybeFun);
         ReservedRooted<JSScript*> funScript(&rootScript0, fun->getOrCreateScript(cx));
         if (!funScript)
             goto error;
 
-        InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE;
         bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, REGS.pc);
 
         TypeMonitorCall(cx, args, construct);
 
         mozilla::Maybe<InvokeState> state;
-        state.emplace(cx, args, initial);
+        state.emplace(cx, args, construct);
 
         if (createSingleton)
             state->setCreateSingleton();
 
         if (!createSingleton && jit::IsIonEnabled(cx)) {
             jit::MethodStatus status = jit::CanEnter(cx, state.ref());
             if (status == jit::Method_Error)
                 goto error;
@@ -2850,17 +2845,17 @@ CASE(JSOP_FUNCALL)
                 interpReturnOK = !IsErrorStatus(exec);
                 goto jit_return;
             }
         }
 
         state.reset();
         funScript = fun->nonLazyScript();
 
-        if (!activation.pushInlineFrame(args, funScript, initial))
+        if (!activation.pushInlineFrame(args, funScript, construct))
             goto error;
 
         if (createSingleton)
             REGS.fp()->setCreateSingleton();
     }
 
     SET_SCRIPT(REGS.fp()->script());
 
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -35,21 +35,16 @@ extern bool
 BoxNonStrictThis(JSContext* cx, HandleValue thisv, MutableHandleValue vp);
 
 extern bool
 GetFunctionThis(JSContext* cx, AbstractFramePtr frame, MutableHandleValue res);
 
 extern bool
 GetNonSyntacticGlobalThis(JSContext* cx, HandleObject scopeChain, MutableHandleValue res);
 
-enum MaybeConstruct {
-    NO_CONSTRUCT = INITIAL_NONE,
-    CONSTRUCT = INITIAL_CONSTRUCT
-};
-
 /*
  * numToSkip is the number of stack values the expression decompiler should skip
  * before it reaches |v|. If it's -1, the decompiler will search the stack.
  */
 extern bool
 ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip,
                     MaybeConstruct construct = NO_CONSTRUCT);
 
@@ -108,18 +103,17 @@ InternalConstructWithProvidedThis(JSCont
 /*
  * Executes a script with the given scopeChain/this. The 'type' indicates
  * whether this is eval code or global code. To support debugging, the
  * evalFrame parameter can point to an arbitrary frame in the context's call
  * stack to simulate executing an eval in that frame.
  */
 extern bool
 ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChain,
-              const Value& newTargetVal, ExecuteType type, AbstractFramePtr evalInFrame,
-              Value* result);
+              const Value& newTargetVal, AbstractFramePtr evalInFrame, Value* result);
 
 /* Execute a script with the given scopeChain as global code. */
 extern bool
 Execute(JSContext* cx, HandleScript script, JSObject& scopeChain, Value* rval);
 
 class ExecuteState;
 class InvokeState;
 
@@ -164,67 +158,63 @@ class RunState
     RunState(const ExecuteState& other) = delete;
     RunState(const InvokeState& other) = delete;
     void operator=(const RunState& other) = delete;
 };
 
 // Eval or global script.
 class ExecuteState : public RunState
 {
-    ExecuteType type_;
-
     RootedValue newTargetValue_;
     RootedObject scopeChain_;
 
     AbstractFramePtr evalInFrame_;
     Value* result_;
 
   public:
     ExecuteState(JSContext* cx, JSScript* script, const Value& newTargetValue,
-                 JSObject& scopeChain, ExecuteType type, AbstractFramePtr evalInFrame,
-                 Value* result)
+                 JSObject& scopeChain, AbstractFramePtr evalInFrame, Value* result)
       : RunState(cx, Execute, script),
-        type_(type),
         newTargetValue_(cx, newTargetValue),
         scopeChain_(cx, &scopeChain),
         evalInFrame_(evalInFrame),
         result_(result)
     { }
 
     Value newTarget() { return newTargetValue_; }
     JSObject* scopeChain() const { return scopeChain_; }
-    ExecuteType type() const { return type_; }
+    bool isDebuggerEval() const { return !!evalInFrame_; }
 
     virtual InterpreterFrame* pushInterpreterFrame(JSContext* cx);
 
     virtual void setReturnValue(Value v) {
         if (result_)
             *result_ = v;
     }
 };
 
 // Data to invoke a function.
 class InvokeState : public RunState
 {
     const CallArgs& args_;
-    InitialFrameFlags initial_;
+    MaybeConstruct construct_;
     bool createSingleton_;
 
   public:
-    InvokeState(JSContext* cx, const CallArgs& args, InitialFrameFlags initial)
+    InvokeState(JSContext* cx, const CallArgs& args, MaybeConstruct construct)
       : RunState(cx, Invoke, args.callee().as<JSFunction>().nonLazyScript()),
         args_(args),
-        initial_(initial),
+        construct_(construct),
         createSingleton_(false)
     { }
 
     bool createSingleton() const { return createSingleton_; }
     void setCreateSingleton() { createSingleton_ = true; }
 
-    bool constructing() const { return InitialFrameFlagsAreConstructing(initial_); }
+    bool constructing() const { return construct_; }
     const CallArgs& args() const { return args_; }
 
     virtual InterpreterFrame* pushInterpreterFrame(JSContext* cx);
 
     virtual void setReturnValue(Value v) {
         args_.rval().set(v);
     }
 };
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -481,17 +481,18 @@ SavedFrame::create(JSContext* cx)
     // accidentally cause O(n^2) behavior.
     SavedStacks::AutoReentrancyGuard guard(cx->compartment()->savedStacks());
 
     RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
     if (!proto)
         return nullptr;
     assertSameCompartment(cx, proto);
 
-    RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto));
+    RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto,
+                                                      TenuredObject));
     if (!frameObj)
         return nullptr;
 
     return &frameObj->as<SavedFrame>();
 }
 
 bool
 SavedFrame::isSelfHosted()
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1568,16 +1568,26 @@ LiveScopeVal::staticAsserts()
     static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_),
                   "LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_");
 }
 
 /*****************************************************************************/
 
 namespace {
 
+static void
+ReportOptimizedOut(JSContext* cx, HandleId id)
+{
+    JSAutoByteString printable;
+    if (ValueToPrintable(cx, IdToValue(id), &printable)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT,
+                             printable.ptr());
+    }
+}
+
 /*
  * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
  * custom handler (rather than trying to reuse js::Wrapper) gives us several
  * important abilities:
  *  - We want to pass the ScopeObject as the receiver to forwarded scope
  *    property ops on aliased variables so that Call/Block/With ops do not all
  *    require a 'normalization' step.
  *  - The debug scope proxy can directly manipulate the stack frame to allow
@@ -1707,17 +1717,25 @@ class DebugScopeProxy : public BaseProxy
                         return true;
                     }
                 }
 
                 if (action == SET)
                     TypeScript::SetArgument(cx, script, i, vp);
             }
 
-            *accessResult = ACCESS_UNALIASED;
+            // It is possible that an optimized out value flows to this
+            // location due to Debugger.Frame.prototype.eval operating on a
+            // live bailed-out Baseline frame. In that case, treat the access
+            // as lost.
+            if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
+                *accessResult = ACCESS_LOST;
+            else
+                *accessResult = ACCESS_UNALIASED;
+
             return true;
         }
 
         /* Handle unaliased let and catch bindings at block scope. */
         if (scope->is<ClonedBlockObject>()) {
             Rooted<ClonedBlockObject*> block(cx, &scope->as<ClonedBlockObject>());
             Shape* shape = block->lastProperty()->search(cx, id);
             if (!shape)
@@ -1752,17 +1770,22 @@ class DebugScopeProxy : public BaseProxy
                         return true;
                     }
                     vp.set(block->var(i, DONT_CHECK_ALIASING));
                 } else {
                     block->setVar(i, vp, DONT_CHECK_ALIASING);
                 }
             }
 
-            *accessResult = ACCESS_UNALIASED;
+            // See comment above in analogous CallObject case.
+            if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
+                *accessResult = ACCESS_LOST;
+            else
+                *accessResult = ACCESS_UNALIASED;
+
             return true;
         }
 
         /* The rest of the internal scopes do not have unaliased vars. */
         MOZ_ASSERT(scope->is<DeclEnvObject>() || scope->is<DynamicWithObject>() ||
                    scope->as<CallObject>().isForEval());
         return true;
     }
@@ -1983,17 +2006,17 @@ class DebugScopeProxy : public BaseProxy
             desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
             desc.value().set(v);
             desc.setGetter(nullptr);
             desc.setSetter(nullptr);
             return true;
           case ACCESS_GENERIC:
             return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc);
           case ACCESS_LOST:
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
+            ReportOptimizedOut(cx, id);
             return false;
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
     bool getMissingArguments(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const
     {
@@ -2047,17 +2070,17 @@ class DebugScopeProxy : public BaseProxy
         switch (access) {
           case ACCESS_UNALIASED:
             if (isMagicMissingArgumentsValue(cx, *scope, vp))
                 return getMissingArguments(cx, *scope, vp);
             return true;
           case ACCESS_GENERIC:
             return GetProperty(cx, scope, scope, id, vp);
           case ACCESS_LOST:
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
+            ReportOptimizedOut(cx, id);
             return false;
           default:
             MOZ_CRASH("bad AccessResult");
         }
     }
 
     bool getMissingArgumentsMaybeSentinelValue(JSContext* cx, ScopeObject& scope,
                                                MutableHandleValue vp) const
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -39,21 +39,16 @@ IsCacheableNonGlobalScope(JSObject* obj)
 
     MOZ_ASSERT_IF(cacheable, !obj->getOps()->lookupProperty);
     return cacheable;
 }
 
 inline HandleObject
 InterpreterFrame::scopeChain() const
 {
-    MOZ_ASSERT_IF(!(flags_ & HAS_SCOPECHAIN), isFunctionFrame());
-    if (!(flags_ & HAS_SCOPECHAIN)) {
-        scopeChain_ = callee().environment();
-        flags_ |= HAS_SCOPECHAIN;
-    }
     return HandleObject::fromMarkedLocation(&scopeChain_);
 }
 
 inline GlobalObject&
 InterpreterFrame::global() const
 {
     return scopeChain()->global();
 }
@@ -71,23 +66,24 @@ inline ClonedBlockObject&
 InterpreterFrame::extensibleLexicalScope() const
 {
     return NearestEnclosingExtensibleLexicalScope(scopeChain());
 }
 
 inline void
 InterpreterFrame::initCallFrame(JSContext* cx, InterpreterFrame* prev, jsbytecode* prevpc,
                                 Value* prevsp, JSFunction& callee, JSScript* script, Value* argv,
-                                uint32_t nactual, InterpreterFrame::Flags flagsArg)
+                                uint32_t nactual, MaybeConstruct constructing)
 {
-    MOZ_ASSERT((flagsArg & ~CONSTRUCTING) == 0);
     MOZ_ASSERT(callee.nonLazyScript() == script);
 
     /* Initialize stack frame members. */
-    flags_ = HAS_SCOPECHAIN | flagsArg;
+    flags_ = 0;
+    if (constructing)
+        flags_ |= CONSTRUCTING;
     argv_ = argv;
     script_ = script;
     nactual_ = nactual;
     scopeChain_ = callee.environment();
     prev_ = prev;
     prevpc_ = prevpc;
     prevsp_ = prevsp;
 
@@ -192,30 +188,27 @@ InterpreterFrame::aliasedVarScope(ScopeC
 }
 
 inline void
 InterpreterFrame::pushOnScopeChain(ScopeObject& scope)
 {
     MOZ_ASSERT(*scopeChain() == scope.enclosingScope() ||
                *scopeChain() == scope.as<CallObject>().enclosingScope().as<DeclEnvObject>().enclosingScope());
     scopeChain_ = &scope;
-    flags_ |= HAS_SCOPECHAIN;
 }
 
 inline void
 InterpreterFrame::popOffScopeChain()
 {
-    MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
     scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope();
 }
 
 inline void
 InterpreterFrame::replaceInnermostScope(ScopeObject& scope)
 {
-    MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
     MOZ_ASSERT(scope.enclosingScope() == scopeChain_->as<ScopeObject>().enclosingScope());
     scopeChain_ = &scope;
 }
 
 bool
 InterpreterFrame::hasCallObj() const
 {
     MOZ_ASSERT(isStrictEvalFrame() || callee().needsCallObject());
@@ -269,81 +262,80 @@ InterpreterStack::allocateFrame(JSContex
     }
 
     frameCount_++;
     return buffer;
 }
 
 MOZ_ALWAYS_INLINE InterpreterFrame*
 InterpreterStack::getCallFrame(JSContext* cx, const CallArgs& args, HandleScript script,
-                               InterpreterFrame::Flags* flags, Value** pargv)
+                               MaybeConstruct constructing, Value** pargv)
 {
     JSFunction* fun = &args.callee().as<JSFunction>();
 
     MOZ_ASSERT(fun->nonLazyScript() == script);
     unsigned nformal = fun->nargs();
     unsigned nvals = script->nslots();
 
     if (args.length() >= nformal) {
         *pargv = args.array();
         uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvals * sizeof(Value));
         return reinterpret_cast<InterpreterFrame*>(buffer);
     }
 
     // Pad any missing arguments with |undefined|.
     MOZ_ASSERT(args.length() < nformal);
 
-    bool isConstructing = *flags & InterpreterFrame::CONSTRUCTING;
-    unsigned nfunctionState = 2 + isConstructing; // callee, |this|, |new.target|
+    unsigned nfunctionState = 2 + constructing; // callee, |this|, |new.target|
 
     nvals += nformal + nfunctionState;
     uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvals * sizeof(Value));
     if (!buffer)
         return nullptr;
 
     Value* argv = reinterpret_cast<Value*>(buffer);
     unsigned nmissing = nformal - args.length();
 
     mozilla::PodCopy(argv, args.base(), 2 + args.length());
     SetValueRangeToUndefined(argv + 2 + args.length(), nmissing);
 
-    if (isConstructing)
+    if (constructing)
         argv[2 + nformal] = args.newTarget();
 
     *pargv = argv + 2;
     return reinterpret_cast<InterpreterFrame*>(argv + nfunctionState + nformal);
 }
 
 MOZ_ALWAYS_INLINE bool
 InterpreterStack::pushInlineFrame(JSContext* cx, InterpreterRegs& regs, const CallArgs& args,
-                                  HandleScript script, InitialFrameFlags initial)
+                                  HandleScript script, MaybeConstruct constructing)
 {
     RootedFunction callee(cx, &args.callee().as<JSFunction>());
     MOZ_ASSERT(regs.sp == args.end());
     MOZ_ASSERT(callee->nonLazyScript() == script);
 
     script->ensureNonLazyCanonicalFunction(cx);
 
     InterpreterFrame* prev = regs.fp();
     jsbytecode* prevpc = regs.pc;
     Value* prevsp = regs.sp;
     MOZ_ASSERT(prev);
 
     LifoAlloc::Mark mark = allocator_.mark();
 
-    InterpreterFrame::Flags flags = ToFrameFlags(initial);
     Value* argv;
-    InterpreterFrame* fp = getCallFrame(cx, args, script, &flags, &argv);
+    InterpreterFrame* fp = getCallFrame(cx, args, script, constructing, &argv);
     if (!fp)
         return false;
 
     fp->mark_ = mark;
 
     /* Initialize frame, locals, regs. */
-    fp->initCallFrame(cx, prev, prevpc, prevsp, *callee, script, argv, args.length(), flags);
+    fp->initCallFrame(cx, prev, prevpc, prevsp, *callee, script, argv, args.length(),
+                      constructing);
 
     regs.prepareToRun(*fp, script);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 InterpreterStack::resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs,
                                            HandleFunction callee, HandleValue newTarget,
@@ -355,17 +347,17 @@ InterpreterStack::resumeGeneratorCallFra
     jsbytecode* prevpc = regs.pc;
     Value* prevsp = regs.sp;
     MOZ_ASSERT(prev);
 
     script->ensureNonLazyCanonicalFunction(cx);
 
     LifoAlloc::Mark mark = allocator_.mark();
 
-    bool constructing = newTarget.isObject();
+    MaybeConstruct constructing = MaybeConstruct(newTarget.isObject());
 
     // Include callee, |this|, and maybe |new.target|
     unsigned nformal = callee->nargs();
     unsigned nvals = 2 + constructing + nformal + script->nslots();
 
     uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvals * sizeof(Value));
     if (!buffer)
         return false;
@@ -373,20 +365,18 @@ InterpreterStack::resumeGeneratorCallFra
     Value* argv = reinterpret_cast<Value*>(buffer) + 2;
     argv[-2] = ObjectValue(*callee);
     argv[-1] = UndefinedValue();
     SetValueRangeToUndefined(argv, nformal);
     if (constructing)
         argv[nformal] = newTarget;
 
     InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(argv + nformal + constructing);
-    InterpreterFrame::Flags flags = constructing ? ToFrameFlags(INITIAL_CONSTRUCT)
-                                                 : ToFrameFlags(INITIAL_NONE);
     fp->mark_ = mark;
-    fp->initCallFrame(cx, prev, prevpc, prevsp, *callee, script, argv, 0, flags);
+    fp->initCallFrame(cx, prev, prevpc, prevsp, *callee, script, argv, 0, constructing);
     fp->resumeGeneratorFrame(scopeChain);
 
     regs.prepareToRun(*fp, script);
     return true;
 }
 
 MOZ_ALWAYS_INLINE void
 InterpreterStack::popInlineFrame(InterpreterRegs& regs)
@@ -940,51 +930,49 @@ InterpreterActivation::InterpreterActiva
 }
 
 InterpreterActivation::~InterpreterActivation()
 {
     // Pop all inline frames.
     while (regs_.fp() != entryFrame_)
         popInlineFrame(regs_.fp());
 
-    JSContext* cx = cx_->asJSContext();
-    MOZ_ASSERT(oldFrameCount_ == cx->runtime()->interpreterStack().frameCount_);
-    MOZ_ASSERT_IF(oldFrameCount_ == 0, cx->runtime()->interpreterStack().allocator_.used() == 0);
+    MOZ_ASSERT(oldFrameCount_ == cx_->runtime()->interpreterStack().frameCount_);
+    MOZ_ASSERT_IF(oldFrameCount_ == 0, cx_->runtime()->interpreterStack().allocator_.used() == 0);
 
     if (entryFrame_)
-        cx->runtime()->interpreterStack().releaseFrame(entryFrame_);
+        cx_->runtime()->interpreterStack().releaseFrame(entryFrame_);
 }
 
 inline bool
 InterpreterActivation::pushInlineFrame(const CallArgs& args, HandleScript script,
-                                       InitialFrameFlags initial)
+                                       MaybeConstruct constructing)
 {
-    JSContext* cx = cx_->asJSContext();
-    if (!cx->runtime()->interpreterStack().pushInlineFrame(cx, regs_, args, script, initial))
+    if (!cx_->runtime()->interpreterStack().pushInlineFrame(cx_, regs_, args, script, constructing))
         return false;
     MOZ_ASSERT(regs_.fp()->script()->compartment() == compartment());
     return true;
 }
 
 inline void
 InterpreterActivation::popInlineFrame(InterpreterFrame* frame)
 {
     (void)frame; // Quell compiler warning.
     MOZ_ASSERT(regs_.fp() == frame);
     MOZ_ASSERT(regs_.fp() != entryFrame_);
 
-    cx_->asJSContext()->runtime()->interpreterStack().popInlineFrame(regs_);
+    cx_->runtime()->interpreterStack().popInlineFrame(regs_);
 }
 
 inline bool
 InterpreterActivation::resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget,
                                             HandleObject scopeChain)
 {
-    InterpreterStack& stack = cx_->asJSContext()->runtime()->interpreterStack();
-    if (!stack.resumeGeneratorCallFrame(cx_->asJSContext(), regs_, callee, newTarget, scopeChain))
+    InterpreterStack& stack = cx_->runtime()->interpreterStack();
+    if (!stack.resumeGeneratorCallFrame(cx_, regs_, callee, newTarget, scopeChain))
         return false;
 
     MOZ_ASSERT(regs_.fp()->script()->compartment() == compartment_);
     return true;
 }
 
 inline bool
 FrameIter::hasCachedSavedFrame() const
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -28,45 +28,38 @@ using namespace js;
 
 using mozilla::Maybe;
 using mozilla::PodCopy;
 
 /*****************************************************************************/
 
 void
 InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr evalInFramePrev,
-                                   const Value& newTargetValue, HandleObject scopeChain,
-                                   ExecuteType type)
+                                   const Value& newTargetValue, HandleObject scopeChain)
 {
-    /*
-     * See encoding of ExecuteType. When GLOBAL isn't set, we are executing a
-     * script in the context of another frame and the frame type is determined
-     * by the context.
-     */
-    flags_ = type | HAS_SCOPECHAIN;
+    flags_ = 0;
     script_ = script;
 
     // newTarget = NullValue is an initial sentinel for "please fill me in from the stack".
     // It should never be passed from Ion code.
     RootedValue newTarget(cx, newTargetValue);
-    if (!(flags_ & GLOBAL_OR_MODULE)) {
+    if (script->isDirectEvalInFunction()) {
         if (evalInFramePrev) {
             if (newTarget.isNull() && evalInFramePrev.script()->functionOrCallerFunction())
                 newTarget = evalInFramePrev.newTarget();
         } else {
             FrameIter iter(cx);
             MOZ_ASSERT(!iter.isWasm());
             if (newTarget.isNull() && iter.script()->functionOrCallerFunction())
                 newTarget = iter.newTarget();
         }
     }
 
-    Value* dstvp = (Value*)this - 2;
+    Value* dstvp = (Value*)this - 1;
     dstvp[0] = newTarget;
-    dstvp[1] = NullValue(); //XXX remove, unused callee.
 
     scopeChain_ = scopeChain.get();
     prev_ = nullptr;
     prevpc_ = nullptr;
     prevsp_ = nullptr;
 
     MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame());
     evalInFramePrev_ = evalInFramePrev;
@@ -331,17 +324,16 @@ InterpreterFrame::pushBlock(JSContext* c
     pushOnScopeChain(*clone);
 
     return true;
 }
 
 bool
 InterpreterFrame::freshenBlock(JSContext* cx)
 {
-    MOZ_ASSERT(flags_ & HAS_SCOPECHAIN);
     Rooted<ClonedBlockObject*> block(cx, &scopeChain_->as<ClonedBlockObject>());
     ClonedBlockObject* fresh = ClonedBlockObject::clone(cx, block);
     if (!fresh)
         return false;
 
     replaceInnermostScope(*fresh);
     return true;
 }
@@ -366,18 +358,17 @@ InterpreterFrame::popWith(JSContext* cx)
 void
 InterpreterFrame::mark(JSTracer* trc)
 {
     /*
      * Normally we would use MarkRoot here, except that generators also take
      * this path. However, generators use a special write barrier when the stack
      * frame is copied to the floating frame. Therefore, no barrier is needed.
      */
-    if (flags_ & HAS_SCOPECHAIN)
-        TraceManuallyBarrieredEdge(trc, &scopeChain_, "scope chain");
+    TraceManuallyBarrieredEdge(trc, &scopeChain_, "scope chain");
     if (flags_ & HAS_ARGS_OBJ)
         TraceManuallyBarrieredEdge(trc, &argsObj_, "arguments");
     TraceManuallyBarrieredEdge(trc, &script_, "script");
     if (trc->isMarkingTracer())
         script()->compartment()->zone()->active = true;
     if (hasReturnValue())
         TraceManuallyBarrieredEdge(trc, &rval_, "rval");
 }
@@ -399,18 +390,18 @@ InterpreterFrame::markValues(JSTracer* t
         // need to fix up the callee pointer before we use it below, under
         // numFormalArgs() and script().
         TraceRootRange(trc, 2, argv_ - 2, "fp callee and this");
 
         // Trace arguments.
         unsigned argc = Max(numActualArgs(), numFormalArgs());
         TraceRootRange(trc, argc + isConstructing(), argv_, "fp argv");
     } else {
-        // Mark callee and newTarget
-        TraceRootRange(trc, 2, ((Value*)this) - 2, "stack callee and newTarget");
+        // Mark newTarget.
+        TraceRoot(trc, ((Value*)this) - 1, "stack newTarget");
     }
 
     JSScript* script = this->script();
     size_t nfixed = script->nfixed();
     size_t nlivefixed = script->calculateLiveFixed(pc);
 
     if (nfixed == nlivefixed) {
         // All locals are live.
@@ -457,49 +448,48 @@ InterpreterRegs::setToEndOfScript()
 {
     sp = fp()->base();
     pc = fp()->script()->lastPC();
 }
 
 /*****************************************************************************/
 
 InterpreterFrame*
-InterpreterStack::pushInvokeFrame(JSContext* cx, const CallArgs& args, InitialFrameFlags initial)
+InterpreterStack::pushInvokeFrame(JSContext* cx, const CallArgs& args, MaybeConstruct constructing)
 {
     LifoAlloc::Mark mark = allocator_.mark();
 
     RootedFunction fun(cx, &args.callee().as<JSFunction>());
     RootedScript script(cx, fun->nonLazyScript());
 
-    InterpreterFrame::Flags flags = ToFrameFlags(initial);
     Value* argv;
-    InterpreterFrame* fp = getCallFrame(cx, args, script, &flags, &argv);
+    InterpreterFrame* fp = getCallFrame(cx, args, script, constructing, &argv);
     if (!fp)
         return nullptr;
 
     fp->mark_ = mark;
-    fp->initCallFrame(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(), flags);
+    fp->initCallFrame(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(),
+                      constructing);
     return fp;
 }
 
 InterpreterFrame*
 InterpreterStack::pushExecuteFrame(JSContext* cx, HandleScript script, const Value& newTargetValue,
-                                   HandleObject scopeChain, ExecuteType type,
-                                   AbstractFramePtr evalInFrame)
+                                   HandleObject scopeChain, AbstractFramePtr evalInFrame)
 {
     LifoAlloc::Mark mark = allocator_.mark();
 
-    unsigned nvars = 2 /* callee, newTarget */ + script->nslots();
+    unsigned nvars = 1 /* newTarget */ + script->nslots();
     uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
     if (!buffer)
         return nullptr;
 
-    InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 2 * sizeof(Value));
+    InterpreterFrame* fp = reinterpret_cast<InterpreterFrame*>(buffer + 1 * sizeof(Value));
     fp->mark_ = mark;
-    fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, scopeChain, type);
+    fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, scopeChain);
     fp->initLocals();
 
     return fp;
 }
 
 /*****************************************************************************/
 
 void
@@ -546,17 +536,17 @@ FrameIter::settleOnActivation()
         if (data_.contextOption_ == CURRENT_CONTEXT && activation->cx() != data_.cx_) {
             ++data_.activations_;
             continue;
         }
 
         // If the caller supplied principals, only show activations which are subsumed (of the same
         // origin or of an origin accessible) by these principals.
         if (data_.principals_) {
-            JSContext* cx = data_.cx_->asJSContext();
+            JSContext* cx = data_.cx_;
             if (JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes) {
                 if (!subsumes(data_.principals_, activation->compartment()->principals())) {
                     ++data_.activations_;
                     continue;
                 }
             }
         }
 
@@ -1700,20 +1690,19 @@ WasmActivation::~WasmActivation()
     // Hide this activation from the profiler before is is destroyed.
     unregisterProfiling();
 
     MOZ_ASSERT(fp_ == nullptr);
 
     MOZ_ASSERT(module_.activation() == this);
     module_.activation() = prevWasmForModule_;
 
-    JSContext* cx = cx_->asJSContext();
-    MOZ_ASSERT(cx->runtime()->wasmActivationStack_ == this);
+    MOZ_ASSERT(cx_->runtime()->wasmActivationStack_ == this);
 
-    cx->runtime()->wasmActivationStack_ = prevWasm_;
+    cx_->runtime()->wasmActivationStack_ = prevWasm_;
 }
 
 InterpreterFrameIterator&
 InterpreterFrameIterator::operator++()
 {
     MOZ_ASSERT(!done());
     if (fp_ != activation_->entryFrame_) {
         pc_ = fp_->prevpc();
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -263,104 +263,67 @@ class AbstractFramePtr
 class NullFramePtr : public AbstractFramePtr
 {
   public:
     NullFramePtr()
       : AbstractFramePtr()
     { }
 };
 
-/*****************************************************************************/
-
-/* Flags specified for a frame as it is constructed. */
-enum InitialFrameFlags {
-    INITIAL_NONE           =          0,
-    INITIAL_CONSTRUCT      =       0x20, /* == InterpreterFrame::CONSTRUCTING, asserted below */
-};
-
-enum ExecuteType {
-    EXECUTE_GLOBAL_OR_MODULE =      0x1, /* == InterpreterFrame::GLOBAL_OR_MODULE */
-    EXECUTE_DIRECT_EVAL    =        0x8, /* == InterpreterFrame::EVAL */
-    EXECUTE_INDIRECT_EVAL  =        0x9, /* == InterpreterFrame::GLOBAL | EVAL */
-    EXECUTE_DEBUG          =       0x18, /* == InterpreterFrame::EVAL | DEBUGGER_EVAL */
-};
+enum MaybeConstruct { NO_CONSTRUCT = false, CONSTRUCT = true };
 
 /*****************************************************************************/
 
 class InterpreterFrame
 {
-  public:
     enum Flags : uint32_t {
-        /* Primary frame type */
-        GLOBAL_OR_MODULE       =        0x1,  /* frame pushed for a global script */
-        /* (0x2, 0x4, 0x8 are unused) */
+        CONSTRUCTING           =        0x1,  /* frame is for a constructor invocation */
 
-        /*
-         * Frame pushed for debugger eval.
-         * - Don't bother to JIT it, because it's probably short-lived.
-         * - It is required to have a scope chain object outside the
-         *   js::ScopeObject hierarchy: either a global object, or a
-         *   DebugScopeObject (not a ScopeObject, despite the name)
-         * - If evalInFramePrev_ is set, then this frame was created for an
-         *   "eval in frame" call, which can push a successor to any live
-         *   frame; so its logical "prev" frame is not necessarily the
-         *   previous frame in memory. Iteration should treat
-         *   evalInFramePrev_ as this frame's previous frame.
-         */
-        DEBUGGER_EVAL          =       0x10,
-
-        CONSTRUCTING           =       0x20,  /* frame is for a constructor invocation */
-
-        RESUMED_GENERATOR      =       0x40,  /* frame is for a resumed generator invocation */
-
-        /* (0x80 is unused) */
+        RESUMED_GENERATOR      =        0x2,  /* frame is for a resumed generator invocation */
 
         /* Function prologue state */
-        HAS_CALL_OBJ           =      0x100,  /* CallObject created for needsCallObject function */
-        HAS_ARGS_OBJ           =      0x200,  /* ArgumentsObject created for needsArgsObj script */
+        HAS_CALL_OBJ           =        0x4,  /* CallObject created for needsCallObject function */
+        HAS_ARGS_OBJ           =        0x8,  /* ArgumentsObject created for needsArgsObj script */
 
         /* Lazy frame initialization */
-        HAS_RVAL               =      0x800,  /* frame has rval_ set */
-        HAS_SCOPECHAIN         =     0x1000,  /* frame has scopeChain_ set */
+        HAS_RVAL               =       0x10,  /* frame has rval_ set */
 
         /* Debugger state */
-        PREV_UP_TO_DATE        =     0x4000,  /* see DebugScopes::updateLiveScopes */
+        PREV_UP_TO_DATE        =       0x20,  /* see DebugScopes::updateLiveScopes */
 
         /*
          * See comment above 'isDebuggee' in jscompartment.h for explanation of
          * invariants of debuggee compartments, scripts, and frames.
          */
-        DEBUGGEE               =     0x8000,  /* Execution is being observed by Debugger */
+        DEBUGGEE               =       0x40,  /* Execution is being observed by Debugger */
 
         /* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */
-        HAS_PUSHED_SPS_FRAME   =    0x10000, /* SPS was notified of enty */
-
+        HAS_PUSHED_SPS_FRAME   =       0x80,  /* SPS was notified of enty */
 
         /*
          * If set, we entered one of the JITs and ScriptFrameIter should skip
          * this frame.
          */
-        RUNNING_IN_JIT         =    0x20000,
+        RUNNING_IN_JIT         =      0x100,
 
         /* Miscellaneous state. */
-        CREATE_SINGLETON       =    0x40000,   /* Constructed |this| object should be singleton. */
+        CREATE_SINGLETON       =      0x200,  /* Constructed |this| object should be singleton. */
 
         /*
          * If set, this frame has been on the stack when
          * |js::SavedStacks::saveCurrentStack| was called, and so there is a
          * |js::SavedFrame| object cached for this frame.
          */
-        HAS_CACHED_SAVED_FRAME =    0x80000,
+        HAS_CACHED_SAVED_FRAME =      0x400,
     };
 
-  private:
     mutable uint32_t    flags_;         /* bits described by Flags */
+    uint32_t            nactual_;       /* number of actual arguments, for function frames */
     JSScript*           script_;        /* the script we're executing */
-    unsigned            nactual_;       /* number of actual arguments, for function frames */
-    mutable JSObject*   scopeChain_;    /* if HAS_SCOPECHAIN, current scope chain */
+    JSObject*           scopeChain_;    /* current scope chain */
     Value               rval_;          /* if HAS_RVAL, return value of the frame */
     ArgumentsObject*    argsObj_;       /* if HAS_ARGS_OBJ, the call's arguments object */
 
     /*
      * Previous frame and its pc and sp. Always nullptr for
      * InterpreterActivation's entry frame, always non-nullptr for inline
      * frames.
      */
@@ -401,21 +364,21 @@ class InterpreterFrame
     /*
      * Frame initialization, called by InterpreterStack operations after acquiring
      * the raw memory for the frame:
      */
 
     /* Used for Invoke and Interpret. */
     void initCallFrame(JSContext* cx, InterpreterFrame* prev, jsbytecode* prevpc, Value* prevsp,
                        JSFunction& callee, JSScript* script, Value* argv, uint32_t nactual,
-                       InterpreterFrame::Flags flags);
+                       MaybeConstruct constructing);
 
     /* Used for global and eval frames. */
     void initExecuteFrame(JSContext* cx, HandleScript script, AbstractFramePtr prev,
-                          const Value& newTargetValue, HandleObject scopeChain, ExecuteType type);
+                          const Value& newTargetValue, HandleObject scopeChain);
 
   public:
     /*
      * Frame prologue/epilogue
      *
      * Every stack frame must have 'prologue' called before executing the
      * first op and 'epilogue' called after executing the last op and before
      * popping the frame (whether the exit is exceptional or not).
@@ -665,17 +628,17 @@ class InterpreterFrame
      * New Target
      *
      * Only function frames have a meaningful newTarget. An eval frame in a
      * function will have a copy of the newTarget of the enclosing function
      * frame.
      */
     Value newTarget() const {
         if (isEvalFrame())
-            return ((Value*)this)[-2];
+            return ((Value*)this)[-1];
 
         MOZ_ASSERT(isFunctionFrame());
 
         if (callee().isArrow())
             return callee().getExtendedSlot(FunctionExtended::ARROW_NEWTARGET_SLOT);
 
         if (isConstructing()) {
             unsigned pushedArgs = Max(numFormalArgs(), numActualArgs());
@@ -722,36 +685,24 @@ class InterpreterFrame
     void clearReturnValue() {
         rval_.setUndefined();
         markReturnValue();
     }
 
     void resumeGeneratorFrame(JSObject* scopeChain) {
         MOZ_ASSERT(script()->isGenerator());
         MOZ_ASSERT(isFunctionFrame());
-        flags_ |= HAS_CALL_OBJ | HAS_SCOPECHAIN;
+        flags_ |= HAS_CALL_OBJ;
         scopeChain_ = scopeChain;
     }
 
     /*
      * Other flags
      */
 
-    InitialFrameFlags initialFlags() const {
-        JS_STATIC_ASSERT((int)INITIAL_NONE == 0);
-        JS_STATIC_ASSERT((int)INITIAL_CONSTRUCT == (int)CONSTRUCTING);
-        uint32_t mask = CONSTRUCTING;
-        MOZ_ASSERT((flags_ & mask) != mask);
-        return InitialFrameFlags(flags_ & mask);
-    }
-
-    void setConstructing() {
-        flags_ |= CONSTRUCTING;
-    }
-
     bool isConstructing() const {
         return !!(flags_ & CONSTRUCTING);
     }
 
     void setResumedGenerator() {
         flags_ |= RESUMED_GENERATOR;
     }
     bool isResumedGenerator() const {
@@ -781,18 +732,32 @@ class InterpreterFrame
         MOZ_ASSERT(isConstructing());
         flags_ |= CREATE_SINGLETON;
     }
     bool createSingleton() const {
         MOZ_ASSERT(isConstructing());
         return flags_ & CREATE_SINGLETON;
     }
 
+    /*
+     * Debugger eval frames.
+     *
+     * - If evalInFramePrev_ is non-null, frame was created for an "eval in
+     *   frame" call, which can push a successor to any live frame; so its
+     *   logical "prev" frame is not necessarily the previous frame in memory.
+     *   Iteration should treat evalInFramePrev_ as this frame's previous frame.
+     *
+     * - Don't bother to JIT it, because it's probably short-lived.
+     *
+     * - It is required to have a scope chain object outside the
+     *   js::ScopeObject hierarchy: either a global object, or a
+     *   DebugScopeObject (not a ScopeObject, despite the name)
+     */
     bool isDebuggerEvalFrame() const {
-        return !!(flags_ & DEBUGGER_EVAL);
+        return isEvalFrame() && !!evalInFramePrev_;
     }
 
     bool prevUpToDate() const {
         return !!(flags_ & PREV_UP_TO_DATE);
     }
 
     void setPrevUpToDate() {
         flags_ |= PREV_UP_TO_DATE;
@@ -831,36 +796,16 @@ class InterpreterFrame
     void setRunningInJit() {
         flags_ |= RUNNING_IN_JIT;
     }
     void clearRunningInJit() {
         flags_ &= ~RUNNING_IN_JIT;
     }
 };
 
-static const size_t VALUES_PER_STACK_FRAME = sizeof(InterpreterFrame) / sizeof(Value);
-
-static inline InterpreterFrame::Flags
-ToFrameFlags(InitialFrameFlags initial)
-{
-    return InterpreterFrame::Flags(initial);
-}
-
-static inline InitialFrameFlags
-InitialFrameFlagsFromConstructing(bool b)
-{
-    return b ? INITIAL_CONSTRUCT : INITIAL_NONE;
-}
-
-static inline bool
-InitialFrameFlagsAreConstructing(InitialFrameFlags initial)
-{
-    return !!(initial & INITIAL_CONSTRUCT);
-}
-
 /*****************************************************************************/
 
 class InterpreterRegs
 {
   public:
     Value* sp;
     jsbytecode* pc;
   private:
@@ -926,17 +871,17 @@ class InterpreterStack
     static const size_t MAX_FRAMES = 50 * 1000;
     static const size_t MAX_FRAMES_TRUSTED = MAX_FRAMES + 1000;
     size_t frameCount_;
 
     inline uint8_t* allocateFrame(JSContext* cx, size_t size);
 
     inline InterpreterFrame*
     getCallFrame(JSContext* cx, const CallArgs& args, HandleScript script,
-                 InterpreterFrame::Flags* pflags, Value** pargv);
+                 MaybeConstruct constructing, Value** pargv);
 
     void releaseFrame(InterpreterFrame* fp) {
         frameCount_--;
         allocator_.release(fp->mark_);
     }
 
   public:
     InterpreterStack()
@@ -946,26 +891,26 @@ class InterpreterStack
 
     ~InterpreterStack() {
         MOZ_ASSERT(frameCount_ == 0);
     }
 
     // For execution of eval or global code.
     InterpreterFrame* pushExecuteFrame(JSContext* cx, HandleScript script,
                                        const Value& newTargetValue, HandleObject scopeChain,
-                                       ExecuteType type, AbstractFramePtr evalInFrame);
+                                       AbstractFramePtr evalInFrame);
 
     // Called to invoke a function.
     InterpreterFrame* pushInvokeFrame(JSContext* cx, const CallArgs& args,
-                                      InitialFrameFlags initial);
+                                      MaybeConstruct constructing);
 
     // The interpreter can push light-weight, "inline" frames without entering a
     // new InterpreterActivation or recursively calling Interpret.
     bool pushInlineFrame(JSContext* cx, InterpreterRegs& regs, const CallArgs& args,
-                         HandleScript script, InitialFrameFlags initial);
+                         HandleScript script, MaybeConstruct constructing);
 
     void popInlineFrame(InterpreterRegs& regs);
 
     bool resumeGeneratorCallFrame(JSContext* cx, InterpreterRegs& regs,
                                   HandleFunction callee, HandleValue newTarget,
                                   HandleObject scopeChain);
 
     inline void purge(JSRuntime* rt);
@@ -1359,17 +1304,17 @@ class InterpreterActivation : public Act
     size_t oldFrameCount_;
 #endif
 
   public:
     inline InterpreterActivation(RunState& state, JSContext* cx, InterpreterFrame* entryFrame);
     inline ~InterpreterActivation();
 
     inline bool pushInlineFrame(const CallArgs& args, HandleScript script,
-                                InitialFrameFlags initial);
+                                MaybeConstruct constructing);
     inline void popInlineFrame(InterpreterFrame* frame);
 
     inline bool resumeGeneratorFrame(HandleFunction callee, HandleValue newTarget,
                                      HandleObject scopeChain);
 
     InterpreterFrame* current() const {
         return regs_.fp();
     }
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -93,16 +93,30 @@ public:
 private:
     nsCOMPtr<nsIFile> mGREDir;
     nsCOMPtr<nsIFile> mGREBinDir;
     nsCOMPtr<nsIFile> mAppDir;
     nsCOMPtr<nsIFile> mPluginDir;
     nsCOMPtr<nsIFile> mAppFile;
 };
 
+#ifdef XP_WIN
+class MOZ_STACK_CLASS AutoAudioSession
+{
+public:
+    AutoAudioSession() {
+        widget::StartAudioSession();
+    }
+
+    ~AutoAudioSession() {
+        widget::StopAudioSession();
+    }
+};
+#endif
+
 static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1";
 
 #define EXITCODE_RUNTIME_ERROR 3
 #define EXITCODE_FILE_NOT_FOUND 4
 
 static FILE* gOutFile = nullptr;
 static FILE* gErrFile = nullptr;
 static FILE* gInFile = nullptr;
@@ -1496,17 +1510,17 @@ XRE_XPCShellMain(int argc, char** argv, 
 
         // Initialize graphics prefs on the main thread, if not already done
         gfxPrefs::GetSingleton();
         // Initialize e10s check on the main thread, if not already done
         BrowserTabsRemoteAutostart();
 #ifdef XP_WIN
         // Plugin may require audio session if installed plugin can initialize
         // asynchronized.
-        widget::StartAudioSession();
+        AutoAudioSession audioSession;
 #endif
 
         {
             JS::Rooted<JSObject*> glob(cx, holder->GetJSObject());
             if (!glob) {
                 return 1;
             }
 
@@ -1566,19 +1580,16 @@ XRE_XPCShellMain(int argc, char** argv, 
             }
 
             JS_DropPrincipals(rt, gJSPrincipals);
             JS_SetAllNonReservedSlotsToUndefined(cx, glob);
             JS_SetAllNonReservedSlotsToUndefined(cx, JS_GlobalLexicalScope(glob));
             JS_GC(rt);
         }
         JS_GC(rt);
-#ifdef XP_WIN
-        widget::StopAudioSession();
-#endif
     } // this scopes the nsCOMPtrs
 
     if (!XRE_ShutdownTestShell())
         NS_ERROR("problem shutting down testshell");
 
     // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM
     rv = NS_ShutdownXPCOM( nullptr );
     MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1171,18 +1171,55 @@ nsLayoutUtils::SetDisplayPortMargins(nsI
   if (aRepaintMode == RepaintMode::Repaint) {
     nsIFrame* frame = aContent->GetPrimaryFrame();
     if (frame) {
       frame->SchedulePaint();
     }
   }
 
   // Display port margins changing means that the set of visible images may
-  // have drastically changed. Schedule an update.
-  aPresShell->ScheduleImageVisibilityUpdate();
+  // have drastically changed. Check if we should schedule an update.
+  nsIFrame* frame = GetScrollFrameFromContent(aContent);
+  nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
+  if (!scrollableFrame) {
+    return true;
+  }
+
+  nsRect oldDisplayPort;
+  bool hadDisplayPort =
+    scrollableFrame->GetDisplayPortAtLastImageVisibilityUpdate(&oldDisplayPort);
+
+  nsRect newDisplayPort;
+  Unused << GetDisplayPort(aContent, &newDisplayPort);
+
+  bool needImageVisibilityUpdate = !hadDisplayPort;
+  // Check if the total size has changed by a large factor.
+  if (!needImageVisibilityUpdate) {
+    if ((newDisplayPort.width > 2 * oldDisplayPort.width) ||
+        (oldDisplayPort.width > 2 * newDisplayPort.width) ||
+        (newDisplayPort.height > 2 * oldDisplayPort.height) ||
+        (oldDisplayPort.height > 2 * newDisplayPort.height)) {
+      needImageVisibilityUpdate = true;
+    }
+  }
+  // Check if it's moved by a significant amount.
+  if (!needImageVisibilityUpdate) {
+    if (nsRect* baseData = static_cast<nsRect*>(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
+      nsRect base = *baseData;
+      if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) ||
+          (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > base.width) ||
+          (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
+          (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > base.height)) {
+        needImageVisibilityUpdate = true;
+      }
+    }
+  }
+  if (needImageVisibilityUpdate) {
+    aPresShell->ScheduleImageVisibilityUpdate();
+  }
 
   return true;
 }
 
 void
 nsLayoutUtils::SetDisplayPortBase(nsIContent* aContent, const nsRect& aBase)
 {
   aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -146,17 +146,17 @@ nsPresContext::MakeColorPref(const nsStr
 }
 
 bool
 nsPresContext::IsDOMPaintEventPending()
 {
   if (mFireAfterPaintEvents) {
     return true;
   }
-  nsRootPresContext* drpc = GetDisplayRootPresContext();
+  nsRootPresContext* drpc = GetRootPresContext();
   if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
     // Since we're promising that there will be a MozAfterPaint event
     // fired, we record an empty invalidation in case display list
     // invalidation doesn't invalidate anything further.
     NotifyInvalidation(nsRect(0, 0, 0, 0), 0);
     NS_ASSERTION(mFireAfterPaintEvents, "Why aren't we planning to fire the event?");
     return true;
   }
@@ -1328,43 +1328,16 @@ nsPresContext::GetRootPresContext()
     nsPresContext* parent = pc->GetParentPresContext();
     if (!parent)
       break;
     pc = parent;
   }
   return pc->IsRoot() ? static_cast<nsRootPresContext*>(pc) : nullptr;
 }
 
-nsRootPresContext*
-nsPresContext::GetDisplayRootPresContext()
-{
-  nsPresContext* pc = this;
-  for (;;) {
-    nsPresContext* parent = pc->GetParentPresContext();
-    if (!parent) {
-      // Not sure if this is always strictly the parent, but it works for GetRootPresContext
-      // where the current pres context has no frames.
-      nsIDocument *doc = pc->Document();
-      if (doc) {
-        doc = doc->GetParentDocument();
-        if (doc) {
-          nsIPresShell* shell = doc->GetShell();
-          if (shell) {
-            parent = shell->GetPresContext();
-          }
-        }
-      }
-    }
-    if (!parent || parent == pc)
-      break;
-    pc = parent;
-  }
-  return pc->IsRoot() ? static_cast<nsRootPresContext*>(pc) : nullptr;
-}
-
 void
 nsPresContext::CompatibilityModeChanged()
 {
   if (!mShell) {
     return;
   }
 
   nsIDocument* doc = mShell->GetDocument();
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -203,17 +203,17 @@ public:
   nsIWidget* GetRootWidget();
 
   /**
    * Return the presentation context for the root of the view manager
    * hierarchy that contains this presentation context, or nullptr if it can't
    * be found (e.g. it's detached).
    */
   nsRootPresContext* GetRootPresContext();
-  nsRootPresContext* GetDisplayRootPresContext();
+
   virtual bool IsRoot() { return false; }
 
   nsIDocument* Document() const
   {
       NS_ASSERTION(!mShell || !mShell->GetDocument() ||
                    mShell->GetDocument() == mDocument,
                    "nsPresContext doesn't have the same document as nsPresShell!");
       return mDocument;
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5572,16 +5572,17 @@ PresShell::MarkImagesInSubtreeVisible(ns
     }
     return;
   }
 
   nsRect rect = aRect;
 
   nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
   if (scrollFrame) {
+    scrollFrame->NotifyImageVisibilityUpdate();
     nsRect displayPort;
     bool usingDisplayport =
       nsLayoutUtils::GetDisplayPortForVisibilityTesting(
         aFrame->GetContent(), &displayPort, RelativeTo::ScrollFrame);
     if (usingDisplayport) {
       rect = displayPort;
     } else {
       rect = rect.Intersect(scrollFrame->GetScrollPortRect());
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -143,21 +143,21 @@ public:
   virtual void RemoveRefreshDriver(nsRefreshDriver* aDriver)
   {
     LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
 
     if (IsRootRefreshDriver(aDriver)) {
       NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver), "RemoveRefreshDriver for a refresh driver that's not in the root refresh list!");
       mRootRefreshDrivers.RemoveElement(aDriver);
     } else {
-      nsPresContext* displayRoot = aDriver->PresContext()->GetDisplayRootPresContext();
+      nsPresContext* rootContext = aDriver->PresContext()->GetRootPresContext();
       // During PresContext shutdown, we can't accurately detect
       // if a root refresh driver exists or not. Therefore, we have to
       // search and find out which list this driver exists in.
-      if (!displayRoot) {
+      if (!rootContext) {
         if (mRootRefreshDrivers.Contains(aDriver)) {
           mRootRefreshDrivers.RemoveElement(aDriver);
         } else {
           NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
                        "RemoveRefreshDriver without a display root for a driver that is not in the content refresh list");
           mContentRefreshDrivers.RemoveElement(aDriver);
         }
       } else {
@@ -197,23 +197,22 @@ public:
 
 protected:
   virtual void StartTimer() = 0;
   virtual void StopTimer() = 0;
   virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
 
   bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
   {
-    nsPresContext* displayRoot = aDriver->PresContext()->GetDisplayRootPresContext();
-    if (!displayRoot) {
+    nsPresContext* rootContext = aDriver->PresContext()->GetRootPresContext();
+    if (!rootContext) {
       return false;
     }
 
-    nsRefreshDriver* rootRefreshDriver = displayRoot->GetRootPresContext()->RefreshDriver();
-    return aDriver == rootRefreshDriver;
+    return aDriver == rootContext->RefreshDriver();
   }
 
   /*
    * Actually runs a tick, poking all the attached RefreshDrivers.
    * Grabs the "now" time via JS_Now and TimeStamp::Now().
    */
   void Tick()
   {
@@ -2025,19 +2024,19 @@ nsRefreshDriver::IsWaitingForPaint(mozil
   }
   if (mWaitingForTransaction) {
     mSkippedPaints = true;
     return true;
   }
 
   // Try find the 'root' refresh driver for the current window and check
   // if that is waiting for a paint.
-  nsPresContext *displayRoot = PresContext()->GetDisplayRootPresContext();
-  if (displayRoot) {
-    nsRefreshDriver *rootRefresh = displayRoot->GetRootPresContext()->RefreshDriver();
+  nsPresContext *rootContext = PresContext()->GetRootPresContext();
+  if (rootContext) {
+    nsRefreshDriver *rootRefresh = rootContext->RefreshDriver();
     if (rootRefresh && rootRefresh != this) {
       if (rootRefresh->IsWaitingForPaint(aTime)) {
         if (mRootRefresh != rootRefresh) {
           if (mRootRefresh) {
             mRootRefresh->RemoveRefreshObserver(this, Flush_Style);
           }
           rootRefresh->AddRefreshObserver(this, Flush_Style);
           mRootRefresh = rootRefresh;
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1834,16 +1834,18 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
   , mLastSmoothScrollOrigin(nullptr)
   , mScrollGeneration(++sScrollGenerationCounter)
   , mDestination(0, 0)
   , mScrollPosAtLastPaint(0, 0)
   , mRestorePos(-1, -1)
   , mLastPos(-1, -1)
   , mScrollPosForLayerPixelAlignment(-1, -1)
   , mLastUpdateImagesPos(-1, -1)
+  , mHadDisplayPortAtLastImageUpdate(false)
+  , mDisplayPortAtLastImageUpdate()
   , mNeverHasVerticalScrollbar(false)
   , mNeverHasHorizontalScrollbar(false)
   , mHasVerticalScrollbar(false)
   , mHasHorizontalScrollbar(false)
   , mFrameIsUpdatingScrollbar(false)
   , mDidHistoryRestore(false)
   , mIsRoot(aIsRoot)
   , mClipAllDescendants(aIsRoot)
@@ -2470,16 +2472,33 @@ ScrollFrameHelper::ScheduleSyntheticMous
       return;
   }
 
   mScrollActivityTimer->InitWithFuncCallback(
         ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT);
 }
 
 void
+ScrollFrameHelper::NotifyImageVisibilityUpdate()
+{
+  mLastUpdateImagesPos = GetScrollPosition();
+  mHadDisplayPortAtLastImageUpdate =
+    nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &mDisplayPortAtLastImageUpdate);
+}
+
+bool
+ScrollFrameHelper::GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort)
+{
+  if (mHadDisplayPortAtLastImageUpdate) {
+    *aDisplayPort = mDisplayPortAtLastImageUpdate;
+  }
+  return mHadDisplayPortAtLastImageUpdate;
+}
+
+void
 ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin)
 {
   if (aOrigin == nullptr) {
     // If no origin was specified, we still want to set it to something that's
     // non-null, so that we can use nullness to distinguish if the frame was scrolled
     // at all. Default it to some generic placeholder.
     aOrigin = nsGkAtoms::other;
   }
@@ -2891,17 +2910,17 @@ ClipListsExceptCaret(nsDisplayListCollec
 }
 
 void
 ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                     const nsRect&           aDirtyRect,
                                     const nsDisplayListSet& aLists)
 {
   if (aBuilder->IsForImageVisibility()) {
-    mLastUpdateImagesPos = GetScrollPosition();
+    NotifyImageVisibilityUpdate();
   }
 
   mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
 
   if (aBuilder->IsPaintingToWindow()) {
     mScrollPosAtLastPaint = GetScrollPosition();
     if (IsMaybeScrollingActive() && NeedToInvalidateOnScroll(mOuter)) {
       MarkNotRecentlyScrolled();
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -362,16 +362,18 @@ public:
   }
   void SetZoomableByAPZ(bool aZoomable);
 
   bool UsesContainerScrolling() const;
 
   bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                              nsRect* aDirtyRect,
                              bool aAllowCreateDisplayPort);
+  void NotifyImageVisibilityUpdate();
+  bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort);
 
   void ScheduleSyntheticMouseMove();
   static void ScrollActivityCallback(nsITimer *aTimer, void* anInstance);
 
   void HandleScrollbarStyleSwitching();
 
   nsIAtom* LastScrollOrigin() const { return mLastScrollOrigin; }
   nsIAtom* LastSmoothScrollOrigin() const { return mLastSmoothScrollOrigin; }
@@ -454,16 +456,18 @@ public:
 
   nsExpirationState mActivityExpirationState;
 
   nsCOMPtr<nsITimer> mScrollActivityTimer;
   nsPoint mScrollPosForLayerPixelAlignment;
 
   // The scroll position where we last updated image visibility.
   nsPoint mLastUpdateImagesPos;
+  bool mHadDisplayPortAtLastImageUpdate;
+  nsRect mDisplayPortAtLastImageUpdate;
 
   nsRect mPrevScrolledRect;
 
   FrameMetrics::ViewID mScrollParentID;
 
   bool mNeverHasVerticalScrollbar:1;
   bool mNeverHasHorizontalScrollbar:1;
   bool mHasVerticalScrollbar:1;
@@ -844,16 +848,22 @@ public:
   virtual bool UsesContainerScrolling() const override {
     return mHelper.UsesContainerScrolling();
   }
   virtual bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                                      nsRect* aDirtyRect,
                                      bool aAllowCreateDisplayPort) override {
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
+  virtual void NotifyImageVisibilityUpdate() override {
+    mHelper.NotifyImageVisibilityUpdate();
+  }
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
+    return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
+  }
 
   // nsIStatefulFrame
   NS_IMETHOD SaveState(nsPresState** aState) override {
     NS_ENSURE_ARG_POINTER(aState);
     *aState = mHelper.SaveState();
     return NS_OK;
   }
   NS_IMETHOD RestoreState(nsPresState* aState) override {
@@ -1311,17 +1321,22 @@ public:
   void SetZoomableByAPZ(bool aZoomable) override {
     mHelper.SetZoomableByAPZ(aZoomable);
   }
   virtual bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                                      nsRect* aDirtyRect,
                                      bool aAllowCreateDisplayPort) override {
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
-
+  virtual void NotifyImageVisibilityUpdate() override {
+    mHelper.NotifyImageVisibilityUpdate();
+  }
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
+    return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
+  }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
                    bool aClipAllDescendants);
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -436,11 +436,23 @@ public:
    * displayport if there is one (ie the dirty rect that should be used).
    * This function may create a display port where one did not exist before if
    * aAllowCreateDisplayPort is true. It is only allowed to be false if there
    * has been a call with it set to true before on the same paint.
    */
   virtual bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                                      nsRect* aDirtyRect,
                                      bool aAllowCreateDisplayPort) = 0;
+
+  /**
+   * Notification that this scroll frame is getting its image visibility updated.
+   */
+  virtual void NotifyImageVisibilityUpdate() = 0;
+
+  /**
+   * Returns true if this scroll frame had a display port at the last image
+   * visibility update and fills in aDisplayPort with that displayport. Returns
+   * false otherwise, and doesn't touch aDisplayPort.
+   */
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) = 0;
 };
 
 #endif
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -686,17 +686,17 @@ nsStyleSet::DirtyRuleProcessors(SheetTyp
   if (!mBatching)
     return GatherRuleProcessors(aType);
 
   mDirty |= DirtyBit(aType);
   return NS_OK;
 }
 
 bool
-nsStyleSet::GetAuthorStyleDisabled()
+nsStyleSet::GetAuthorStyleDisabled() const
 {
   return mAuthorStyleDisabled;
 }
 
 nsresult
 nsStyleSet::SetAuthorStyleDisabled(bool aStyleDisabled)
 {
   if (aStyleDisabled == !mAuthorStyleDisabled) {
@@ -1957,17 +1957,17 @@ nsStyleSet::ResolveAnonymousBoxStyle(nsI
   AnonBoxRuleProcessorData data(PresContext(), aPseudoTag, &ruleWalker);
   FileRules(EnumRulesMatching<AnonBoxRuleProcessorData>, &data, nullptr,
             &ruleWalker);
 
   if (aPseudoTag == nsCSSAnonBoxes::pageContent) {
     // Add any @page rules that are specified.
     nsTArray<nsCSSPageRule*> rules;
     nsTArray<css::ImportantStyleData*> importantRules;
-    PresContext()->StyleSet()->AppendPageRules(rules);
+    AppendPageRules(rules);
     for (uint32_t i = 0, i_end = rules.Length(); i != i_end; ++i) {
       css::Declaration* declaration = rules[i]->Declaration();
       declaration->SetImmutable();
       ruleWalker.Forward(declaration);
       css::ImportantStyleData* importantRule =
         declaration->GetImportantStyleData();
       if (importantRule) {
         importantRules.AppendElement(importantRule);
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -321,17 +321,17 @@ class nsStyleSet final
                             mozilla::CSSStyleSheet* aSheet);
   nsresult ReplaceSheets(mozilla::SheetType aType,
                          const nsTArray<RefPtr<mozilla::CSSStyleSheet>>& aNewSheets);
   nsresult InsertStyleSheetBefore(mozilla::SheetType aType,
                                   mozilla::CSSStyleSheet* aNewSheet,
                                   mozilla::CSSStyleSheet* aReferenceSheet);
 
   // Enable/Disable entire author style level (Doc, ScopedDoc & PresHint levels)
-  bool GetAuthorStyleDisabled();
+  bool GetAuthorStyleDisabled() const;
   nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
 
   int32_t SheetCount(mozilla::SheetType aType) const {
     return mSheets[aType].Length();
   }
 
   mozilla::CSSStyleSheet* StyleSheetAt(mozilla::SheetType aType,
                                        int32_t aIndex) const {
--- a/media/libcubeb/AUTHORS
+++ b/media/libcubeb/AUTHORS
@@ -1,8 +1,15 @@
 Matthew Gregan <kinetik@flim.org>
 Alexandre Ratchov <alex@caoua.org>
 Michael Wu <mwu@mozilla.com>
 Paul Adenot <paul@paul.cx>
 David Richards <drichards@mozilla.com>
 Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
 KO Myung-Hun <komh@chollian.net>
 Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
+Alex Chronopoulos <achronop@gmail.com>
+Jan Beich <jbeich@FreeBSD.org>
+Vito Caputo <vito.caputo@coreos.com>
+Landry Breuil <landry@openbsd.org>
+Jacek Caban <jacek@codeweavers.com>
+Paul Hancock <Paul.Hancock.17041993@live.com>
+Ted Mielczarek <ted@mielczarek.org>
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 23a17cb4f9ed2de78a0df5ecdfefbbe47dc83c35.
+The git commit ID used was 2de1ab50e16763fd88ec1b5bae30422de598eed0.
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -113,16 +113,20 @@ typedef enum {
     CUBEB_STREAM_TYPE_DTMF = 8,
     CUBEB_STREAM_TYPE_TTS = 9,
     CUBEB_STREAM_TYPE_FM = 10,
 
     CUBEB_STREAM_TYPE_MAX
 } cubeb_stream_type;
 #endif
 
+/** An opaque handle used to refer a particular input or output device
+ *  across calls. */
+typedef void * cubeb_devid;
+
 /** Stream format initialization parameters. */
 typedef struct {
   cubeb_sample_format format; /**< Requested sample format.  One of
                                    #cubeb_sample_format. */
   unsigned int rate;          /**< Requested sample rate.  Valid range is [1000, 192000]. */
   unsigned int channels;      /**< Requested channel count.  Valid range is [1, 8]. */
 #if defined(__ANDROID__)
   cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
@@ -144,99 +148,127 @@ typedef enum {
 } cubeb_state;
 
 /** Result code enumeration. */
 enum {
   CUBEB_OK = 0,                       /**< Success. */
   CUBEB_ERROR = -1,                   /**< Unclassified error. */
   CUBEB_ERROR_INVALID_FORMAT = -2,    /**< Unsupported #cubeb_stream_params requested. */
   CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
-  CUBEB_ERROR_NOT_SUPPORTED = -4      /**< Optional function not implemented in current backend. */
+  CUBEB_ERROR_NOT_SUPPORTED = -4,     /**< Optional function not implemented in current backend. */
+  CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */
 };
 
+/**
+ * Whether a particular device is an input device (e.g. a microphone), or an
+ * output device (e.g. headphones). */
 typedef enum {
   CUBEB_DEVICE_TYPE_UNKNOWN,
   CUBEB_DEVICE_TYPE_INPUT,
   CUBEB_DEVICE_TYPE_OUTPUT
 } cubeb_device_type;
 
+/**
+ * The state of a device.
+ */
 typedef enum {
-  CUBEB_DEVICE_STATE_DISABLED,
-  CUBEB_DEVICE_STATE_UNPLUGGED,
-  CUBEB_DEVICE_STATE_ENABLED
+  CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */
+  CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */
+  CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
 } cubeb_device_state;
 
-typedef void * cubeb_devid;
-
+/**
+ * Architecture specific sample type.
+ */
 typedef enum {
-  CUBEB_DEVICE_FMT_S16LE          = 0x0010,
-  CUBEB_DEVICE_FMT_S16BE          = 0x0020,
-  CUBEB_DEVICE_FMT_F32LE          = 0x1000,
-  CUBEB_DEVICE_FMT_F32BE          = 0x2000
+  CUBEB_DEVICE_FMT_S16LE          = 0x0010, /**< 16-bit integers, Little Endian. */
+  CUBEB_DEVICE_FMT_S16BE          = 0x0020, /**< 16-bit integers, Big Endian. */
+  CUBEB_DEVICE_FMT_F32LE          = 0x1000, /**< 32-bit floating point, Little Endian. */
+  CUBEB_DEVICE_FMT_F32BE          = 0x2000  /**< 32-bit floating point, Big Endian. */
 } cubeb_device_fmt;
 
 #if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
+/** 16-bit integers, native endianess, when on a Big Endian environment. */
 #define CUBEB_DEVICE_FMT_S16NE     CUBEB_DEVICE_FMT_S16BE
+/** 32-bit floating points, native endianess, when on a Big Endian environment. */
 #define CUBEB_DEVICE_FMT_F32NE     CUBEB_DEVICE_FMT_F32BE
 #else
+/** 16-bit integers, native endianess, when on a Little Endian environment. */
 #define CUBEB_DEVICE_FMT_S16NE     CUBEB_DEVICE_FMT_S16LE
+/** 32-bit floating points, native endianess, when on a Little Endian
+ *  environment. */
 #define CUBEB_DEVICE_FMT_F32NE     CUBEB_DEVICE_FMT_F32LE
 #endif
+/** All the 16-bit integers types. */
 #define CUBEB_DEVICE_FMT_S16_MASK  (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
+/** All the 32-bit floating points types. */
 #define CUBEB_DEVICE_FMT_F32_MASK  (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
+/** All the device formats types. */
 #define CUBEB_DEVICE_FMT_ALL       (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
 
+/** Channel type for a `cubeb_stream`. Depending on the backend and platform
+ * used, this can control inter-stream interruption, ducking, and volume
+ * control.
+ */
 typedef enum {
   CUBEB_DEVICE_PREF_NONE          = 0x00,
   CUBEB_DEVICE_PREF_MULTIMEDIA    = 0x01,
   CUBEB_DEVICE_PREF_VOICE         = 0x02,
   CUBEB_DEVICE_PREF_NOTIFICATION  = 0x04,
   CUBEB_DEVICE_PREF_ALL           = 0x0F
 } cubeb_device_pref;
 
+/** This structure holds the characteristics
+ *  of an input or output audio device. It can be obtained using
+ *  `cubeb_enumerate_devices`, and must be destroyed using
+ *  `cubeb_device_info_destroy`. */
 typedef struct {
-  cubeb_devid devid;          /* Device identifier handle */
-  char * device_id;           /* Device identifier which might be presented in a UI */
-  char * friendly_name;       /* Friendly device name which might be presented in a UI */
-  char * group_id;            /* Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
-  char * vendor_name;         /* Optional vendor name, may be NULL */
+  cubeb_devid devid;          /**< Device identifier handle. */
+  char * device_id;           /**< Device identifier which might be presented in a UI. */
+  char * friendly_name;       /**< Friendly device name which might be presented in a UI. */
+  char * group_id;            /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
+  char * vendor_name;         /**< Optional vendor name, may be NULL. */
 
-  cubeb_device_type type;     /* Type of device (Input/Output) */
-  cubeb_device_state state;   /* State of device disabled/enabled/unplugged */
-  cubeb_device_pref preferred;/* Preferred device */
+  cubeb_device_type type;     /**< Type of device (Input/Output). */
+  cubeb_device_state state;   /**< State of device disabled/enabled/unplugged. */
+  cubeb_device_pref preferred;/**< Preferred device. */
 
-  cubeb_device_fmt format;    /* Sample format supported */
-  cubeb_device_fmt default_format;
-  unsigned int max_channels;  /* Channels */
-  unsigned int default_rate;  /* Default/Preferred sample rate */
-  unsigned int max_rate;      /* Maximum sample rate supported */
-  unsigned int min_rate;      /* Minimum sample rate supported */
+  cubeb_device_fmt format;    /**< Sample format supported. */
+  cubeb_device_fmt default_format; /**< The default sample format for this device. */
+  unsigned int max_channels;  /**< Channels. */
+  unsigned int default_rate;  /**< Default/Preferred sample rate. */
+  unsigned int max_rate;      /**< Maximum sample rate supported. */
+  unsigned int min_rate;      /**< Minimum sample rate supported. */
 
-  unsigned int latency_lo_ms; /* Lowest possible latency in milliseconds  */
-  unsigned int latency_hi_ms; /* Higest possible latency in milliseconds  */
+  unsigned int latency_lo_ms; /**< Lowest possible latency in milliseconds. */
+  unsigned int latency_hi_ms; /**< Higest possible latency in milliseconds. */
 } cubeb_device_info;
 
 /** Device collection. */
 typedef struct {
   uint32_t count;                 /**< Device count in collection. */
   cubeb_device_info * device[1];   /**< Array of pointers to device info. */
 } cubeb_device_collection;
 
 /** User supplied data callback.
-    @param stream
-    @param user_ptr
-    @param buffer
-    @param nframes
-    @retval Number of frames written to buffer, which must equal nframes except
-            at end of stream.
+    @param stream The stream for which this callback fired
+    @param user_ptr The pointer passed to cubeb_stream_create
+    @param input_buffer A pointer containing the input data, or nullptr
+                        if this is an output-only stream.
+    @param output_buffer A pointer containing the output data, or nullptr
+                         if this is an input -only stream.
+    @param nframes The number of frames of the two buffer.
+    @retval Number of frames written to the output buffer, which must equal
+            nframes except at end of stream.
     @retval CUBEB_ERROR on error, in which case the data callback will stop
             and the stream will enter a shutdown state. */
 typedef long (* cubeb_data_callback)(cubeb_stream * stream,
                                      void * user_ptr,
-                                     void * buffer,
+                                     const void * input_buffer,
+                                     void * output_buffer,
                                      long nframes);
 
 /** User supplied state callback.
     @param stream
     @param user_ptr
     @param state */
 typedef void (* cubeb_state_callback)(cubeb_stream * stream,
                                       void * user_ptr,
@@ -301,30 +333,41 @@ int cubeb_get_preferred_sample_rate(cube
 /** Destroy an application context.
     @param context */
 void cubeb_destroy(cubeb * context);
 
 /** Initialize a stream associated with the supplied application context.
     @param context
     @param stream
     @param stream_name
-    @param stream_params
+    @param input_device Device for the input side of the stream. If NULL
+                        default input device is used.
+    @param input_stream_params Parameters for the input side of the stream, or
+                               NULL if this stream is output only.
+    @param output_device Device for the output side of the stream. If NULL
+                         default output device is used.
+    @param output_stream_params Parameters for the output side of the stream, or
+                                NULL if this stream is input only.
     @param latency Approximate stream latency in milliseconds.  Valid range
                    is [1, 2000].
     @param data_callback Will be called to preroll data before playback is
                          started by cubeb_stream_start.
     @param state_callback
     @param user_ptr
     @retval CUBEB_OK
     @retval CUBEB_ERROR
-    @retval CUBEB_ERROR_INVALID_FORMAT */
+    @retval CUBEB_ERROR_INVALID_FORMAT
+    @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
 int cubeb_stream_init(cubeb * context,
                       cubeb_stream ** stream,
                       char const * stream_name,
-                      cubeb_stream_params stream_params,
+                      cubeb_devid input_device,
+                      cubeb_stream_params * input_stream_params,
+                      cubeb_devid output_device,
+                      cubeb_stream_params * output_stream_params,
                       unsigned int latency,
                       cubeb_data_callback data_callback,
                       cubeb_state_callback state_callback,
                       void * user_ptr);
 
 /** Destroy a stream.
     @param stream */
 void cubeb_stream_destroy(cubeb_stream * stream);
@@ -404,50 +447,52 @@ int cubeb_stream_device_destroy(cubeb_st
     @param stream the stream for which to set the callback.
     @param device_changed_callback a function called whenever the device has
            changed. Passing NULL allow to unregister a function
     @retval CUBEB_OK
     @retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
             device_changed_callback are invalid pointers.
     @retval CUBEB_ERROR_NOT_SUPPORTED */
 int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
-                                                  cubeb_device_changed_callback  device_changed_callback);
+                                                  cubeb_device_changed_callback device_changed_callback);
 
 /** Returns enumerated devices.
     @param context
     @param devtype device type to include
     @param collection output collection. Must be destroyed with cubeb_device_collection_destroy
     @retval CUBEB_OK in case of success
     @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
     @retval CUBEB_ERROR_NOT_SUPPORTED */
 int cubeb_enumerate_devices(cubeb * context,
                             cubeb_device_type devtype,
                             cubeb_device_collection ** collection);
 
-/** Destroy a cubeb_device_collection.