Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Wed, 02 Nov 2016 19:28:38 -0700
changeset 347426 ade8d4a63e57560410de106450f37b50ed71cca5
parent 347228 ac55a6776435142feebf3c20bbabfee100686416 (current diff)
parent 347425 439965937366d0c2a504f8ca8cab1ed07be24fe7 (diff)
child 347427 e6ade90721b518675f3bad9a05ea83e3c3bfd286
child 347470 ae743ad1f3260fc23be090535ff86f7097e144bb
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
Merge m-i to m-c, a=merge MozReview-Commit-ID: 48WAQwKUCpw
browser/base/content/browser.js
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/Utils.jsm
devtools/client/inspector/components/box-model.js
devtools/server/actors/layout.js
devtools/shared/fronts/layout.js
devtools/shared/specs/layout.js
dom/canvas/CanvasRenderingContext2D.cpp
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLMediaElement.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/media/MediaManager.cpp
dom/webidl/IDBEnvironment.webidl
dom/webidl/moz.build
editor/libeditor/tests/test_bug1140617.xul
editor/libeditor/tests/test_bug490879.xul
editor/libeditor/tests/test_bug636465.xul
editor/libeditor/tests/test_bug646194.xul
editor/libeditor/tests/test_selection_move_commands.xul
gfx/thebes/MathTableStructures.h
gfx/thebes/gfxPlatform.cpp
js/src/jit/MIR.h
layout/build/nsLayoutStatics.cpp
modules/libpref/init/all.js
netwerk/build/nsNetModule.cpp
netwerk/dns/DNS.cpp
security/nss/automation/travis/validate-formatting.sh
security/nss/external_tests/.clang-format
security/nss/external_tests/Makefile
security/nss/external_tests/README
security/nss/external_tests/common/Makefile
security/nss/external_tests/common/common.gyp
security/nss/external_tests/common/gtest.gypi
security/nss/external_tests/common/gtest.mk
security/nss/external_tests/common/gtests.cc
security/nss/external_tests/common/manifest.mn
security/nss/external_tests/common/scoped_ptrs.h
security/nss/external_tests/der_gtest/Makefile
security/nss/external_tests/der_gtest/der_getint_unittest.cc
security/nss/external_tests/der_gtest/der_gtest.gyp
security/nss/external_tests/der_gtest/der_private_key_import_unittest.cc
security/nss/external_tests/der_gtest/manifest.mn
security/nss/external_tests/google_test/Makefile
security/nss/external_tests/google_test/google_test.gyp
security/nss/external_tests/google_test/gtest/CHANGES
security/nss/external_tests/google_test/gtest/CMakeLists.txt
security/nss/external_tests/google_test/gtest/CONTRIBUTORS
security/nss/external_tests/google_test/gtest/LICENSE
security/nss/external_tests/google_test/gtest/Makefile.am
security/nss/external_tests/google_test/gtest/README
security/nss/external_tests/google_test/gtest/build-aux/.keep
security/nss/external_tests/google_test/gtest/cmake/internal_utils.cmake
security/nss/external_tests/google_test/gtest/codegear/gtest.cbproj
security/nss/external_tests/google_test/gtest/codegear/gtest.groupproj
security/nss/external_tests/google_test/gtest/codegear/gtest_all.cc
security/nss/external_tests/google_test/gtest/codegear/gtest_link.cc
security/nss/external_tests/google_test/gtest/codegear/gtest_main.cbproj
security/nss/external_tests/google_test/gtest/codegear/gtest_unittest.cbproj
security/nss/external_tests/google_test/gtest/configure.ac
security/nss/external_tests/google_test/gtest/include/gtest/gtest-death-test.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-message.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-param-test.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-param-test.h.pump
security/nss/external_tests/google_test/gtest/include/gtest/gtest-printers.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-spi.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-test-part.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest-typed-test.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest_pred_impl.h
security/nss/external_tests/google_test/gtest/include/gtest/gtest_prod.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-death-test-internal.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-filepath.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-internal.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-linked_ptr.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-param-util-generated.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-param-util-generated.h.pump
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-param-util.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-port.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-string.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-tuple.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-tuple.h.pump
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-type-util.h
security/nss/external_tests/google_test/gtest/include/gtest/internal/gtest-type-util.h.pump
security/nss/external_tests/google_test/gtest/m4/acx_pthread.m4
security/nss/external_tests/google_test/gtest/m4/gtest.m4
security/nss/external_tests/google_test/gtest/make/Makefile
security/nss/external_tests/google_test/gtest/msvc/gtest-md.sln
security/nss/external_tests/google_test/gtest/msvc/gtest-md.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest.sln
security/nss/external_tests/google_test/gtest/msvc/gtest.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_main-md.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_main.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_prod_test-md.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_prod_test.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_unittest-md.vcproj
security/nss/external_tests/google_test/gtest/msvc/gtest_unittest.vcproj
security/nss/external_tests/google_test/gtest/samples/prime_tables.h
security/nss/external_tests/google_test/gtest/samples/sample1.cc
security/nss/external_tests/google_test/gtest/samples/sample1.h
security/nss/external_tests/google_test/gtest/samples/sample10_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample1_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample2.cc
security/nss/external_tests/google_test/gtest/samples/sample2.h
security/nss/external_tests/google_test/gtest/samples/sample2_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample3-inl.h
security/nss/external_tests/google_test/gtest/samples/sample3_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample4.cc
security/nss/external_tests/google_test/gtest/samples/sample4.h
security/nss/external_tests/google_test/gtest/samples/sample4_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample5_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample6_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample7_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample8_unittest.cc
security/nss/external_tests/google_test/gtest/samples/sample9_unittest.cc
security/nss/external_tests/google_test/gtest/scripts/common.py
security/nss/external_tests/google_test/gtest/scripts/fuse_gtest_files.py
security/nss/external_tests/google_test/gtest/scripts/gen_gtest_pred_impl.py
security/nss/external_tests/google_test/gtest/scripts/gtest-config.in
security/nss/external_tests/google_test/gtest/scripts/pump.py
security/nss/external_tests/google_test/gtest/scripts/release_docs.py
security/nss/external_tests/google_test/gtest/scripts/test/Makefile
security/nss/external_tests/google_test/gtest/scripts/upload.py
security/nss/external_tests/google_test/gtest/scripts/upload_gtest.py
security/nss/external_tests/google_test/gtest/src/gtest-all.cc
security/nss/external_tests/google_test/gtest/src/gtest-death-test.cc
security/nss/external_tests/google_test/gtest/src/gtest-filepath.cc
security/nss/external_tests/google_test/gtest/src/gtest-internal-inl.h
security/nss/external_tests/google_test/gtest/src/gtest-port.cc
security/nss/external_tests/google_test/gtest/src/gtest-printers.cc
security/nss/external_tests/google_test/gtest/src/gtest-test-part.cc
security/nss/external_tests/google_test/gtest/src/gtest-typed-test.cc
security/nss/external_tests/google_test/gtest/src/gtest.cc
security/nss/external_tests/google_test/gtest/src/gtest_main.cc
security/nss/external_tests/google_test/gtest/test/gtest-death-test_ex_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-death-test_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-filepath_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-linked_ptr_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-listener_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-message_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-options_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-param-test2_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-param-test_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-param-test_test.h
security/nss/external_tests/google_test/gtest/test/gtest-port_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-printers_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-test-part_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-tuple_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-typed-test2_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-typed-test_test.cc
security/nss/external_tests/google_test/gtest/test/gtest-typed-test_test.h
security/nss/external_tests/google_test/gtest/test/gtest-unittest-api_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_all_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_break_on_failure_unittest.py
security/nss/external_tests/google_test/gtest/test/gtest_break_on_failure_unittest_.cc
security/nss/external_tests/google_test/gtest/test/gtest_catch_exceptions_test.py
security/nss/external_tests/google_test/gtest/test/gtest_catch_exceptions_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_color_test.py
security/nss/external_tests/google_test/gtest/test/gtest_color_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_env_var_test.py
security/nss/external_tests/google_test/gtest/test/gtest_env_var_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_environment_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_filter_unittest.py
security/nss/external_tests/google_test/gtest/test/gtest_filter_unittest_.cc
security/nss/external_tests/google_test/gtest/test/gtest_help_test.py
security/nss/external_tests/google_test/gtest/test/gtest_help_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_list_tests_unittest.py
security/nss/external_tests/google_test/gtest/test/gtest_list_tests_unittest_.cc
security/nss/external_tests/google_test/gtest/test/gtest_main_unittest.cc
security/nss/external_tests/google_test/gtest/test/gtest_no_test_unittest.cc
security/nss/external_tests/google_test/gtest/test/gtest_output_test.py
security/nss/external_tests/google_test/gtest/test/gtest_output_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_output_test_golden_lin.txt
security/nss/external_tests/google_test/gtest/test/gtest_pred_impl_unittest.cc
security/nss/external_tests/google_test/gtest/test/gtest_premature_exit_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_prod_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_repeat_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_shuffle_test.py
security/nss/external_tests/google_test/gtest/test/gtest_shuffle_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_sole_header_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_stress_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_test_utils.py
security/nss/external_tests/google_test/gtest/test/gtest_throw_on_failure_ex_test.cc
security/nss/external_tests/google_test/gtest/test/gtest_throw_on_failure_test.py
security/nss/external_tests/google_test/gtest/test/gtest_throw_on_failure_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_uninitialized_test.py
security/nss/external_tests/google_test/gtest/test/gtest_uninitialized_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_unittest.cc
security/nss/external_tests/google_test/gtest/test/gtest_xml_outfile1_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_xml_outfile2_test_.cc
security/nss/external_tests/google_test/gtest/test/gtest_xml_outfiles_test.py
security/nss/external_tests/google_test/gtest/test/gtest_xml_output_unittest.py
security/nss/external_tests/google_test/gtest/test/gtest_xml_output_unittest_.cc
security/nss/external_tests/google_test/gtest/test/gtest_xml_test_utils.py
security/nss/external_tests/google_test/gtest/test/production.cc
security/nss/external_tests/google_test/gtest/test/production.h
security/nss/external_tests/google_test/gtest/xcode/Config/DebugProject.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Config/FrameworkTarget.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Config/General.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Config/ReleaseProject.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Config/StaticLibraryTarget.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Config/TestTarget.xcconfig
security/nss/external_tests/google_test/gtest/xcode/Resources/Info.plist
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/Info.plist
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/WidgetFramework.xcodeproj/project.pbxproj
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/runtests.sh
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/widget.cc
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/widget.h
security/nss/external_tests/google_test/gtest/xcode/Samples/FrameworkSample/widget_test.cc
security/nss/external_tests/google_test/gtest/xcode/Scripts/runtests.sh
security/nss/external_tests/google_test/gtest/xcode/Scripts/versiongenerate.py
security/nss/external_tests/google_test/gtest/xcode/gtest.xcodeproj/project.pbxproj
security/nss/external_tests/google_test/manifest.mn
security/nss/external_tests/manifest.mn
security/nss/external_tests/nss_bogo_shim/Makefile
security/nss/external_tests/nss_bogo_shim/config.cc
security/nss/external_tests/nss_bogo_shim/config.h
security/nss/external_tests/nss_bogo_shim/config.json
security/nss/external_tests/nss_bogo_shim/manifest.mn
security/nss/external_tests/nss_bogo_shim/nss_bogo_shim.cc
security/nss/external_tests/nss_bogo_shim/nss_bogo_shim.gyp
security/nss/external_tests/nss_bogo_shim/nsskeys.cc
security/nss/external_tests/nss_bogo_shim/nsskeys.h
security/nss/external_tests/pk11_gtest/Makefile
security/nss/external_tests/pk11_gtest/manifest.mn
security/nss/external_tests/pk11_gtest/pk11_aeskeywrap_unittest.cc
security/nss/external_tests/pk11_gtest/pk11_chacha20poly1305_unittest.cc
security/nss/external_tests/pk11_gtest/pk11_export_unittest.cc
security/nss/external_tests/pk11_gtest/pk11_gtest.gyp
security/nss/external_tests/pk11_gtest/pk11_pbkdf2_unittest.cc
security/nss/external_tests/pk11_gtest/pk11_prf_unittest.cc
security/nss/external_tests/pk11_gtest/pk11_rsapss_unittest.cc
security/nss/external_tests/ssl_gtest/Makefile
security/nss/external_tests/ssl_gtest/databuffer.h
security/nss/external_tests/ssl_gtest/gtest_utils.h
security/nss/external_tests/ssl_gtest/libssl_internals.c
security/nss/external_tests/ssl_gtest/libssl_internals.h
security/nss/external_tests/ssl_gtest/manifest.mn
security/nss/external_tests/ssl_gtest/ssl_0rtt_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_agent_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_auth_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_cert_ext_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_ciphersuite_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_damage_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_dhe_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_drop_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_ecdh_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_ems_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_extension_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_gtest.cc
security/nss/external_tests/ssl_gtest/ssl_gtest.gyp
security/nss/external_tests/ssl_gtest/ssl_hrr_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_loopback_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_record_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_resumption_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_skip_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_staticrsa_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_v2_client_hello_unittest.cc
security/nss/external_tests/ssl_gtest/ssl_version_unittest.cc
security/nss/external_tests/ssl_gtest/test_io.cc
security/nss/external_tests/ssl_gtest/test_io.h
security/nss/external_tests/ssl_gtest/tls_agent.cc
security/nss/external_tests/ssl_gtest/tls_agent.h
security/nss/external_tests/ssl_gtest/tls_connect.cc
security/nss/external_tests/ssl_gtest/tls_connect.h
security/nss/external_tests/ssl_gtest/tls_filter.cc
security/nss/external_tests/ssl_gtest/tls_filter.h
security/nss/external_tests/ssl_gtest/tls_hkdf_unittest.cc
security/nss/external_tests/ssl_gtest/tls_parser.cc
security/nss/external_tests/ssl_gtest/tls_parser.h
security/nss/external_tests/util_gtest/Makefile
security/nss/external_tests/util_gtest/manifest.mn
security/nss/external_tests/util_gtest/util_gtest.gyp
security/nss/external_tests/util_gtest/util_utf8_unittest.cc
security/nss/test.sh
testing/web-platform/meta/MANIFEST.json
toolkit/components/extensions/test/mochitest/mochitest.ini
toolkit/components/telemetry/Histograms.json
toolkit/mozapps/extensions/internal/XPIProvider.jsm
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
tools/lint/eslint/eslint-plugin-mozilla/package.json
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -1449,16 +1449,20 @@ GetProxiedAccessibleInSubtree(const DocA
   }
 
   MOZ_ASSERT(aDoc->IsTopLevel());
   if (!aDoc->IsTopLevel()) {
     return nullptr;
   }
 
   wrapper->GetNativeInterface(getter_AddRefs(comProxy));
+  MOZ_ASSERT(comProxy);
+  if (!comProxy) {
+    return nullptr;
+  }
 
   RefPtr<IDispatch> disp;
   if (FAILED(comProxy->get_accChild(aVarChild, getter_AddRefs(disp)))) {
     return nullptr;
   }
 
   return disp.forget();
 }
--- a/browser/base/content/browser-devedition.js
+++ b/browser/base/content/browser-devedition.js
@@ -20,16 +20,17 @@ var DevEdition = {
     let theme = LightweightThemeManager.currentTheme;
     return theme && theme.id == "firefox-devedition@mozilla.org";
   },
 
   init: function () {
     this.initialized = true;
     Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
     Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+    Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
     this._updateDevtoolsThemeAttribute();
 
     if (this.isThemeCurrentlyApplied) {
       this._toggleStyleSheet(true);
     }
   },
 
   createStyleSheet: function() {
@@ -44,16 +45,18 @@ var DevEdition = {
   observe: function (subject, topic, data) {
     if (topic == "lightweight-theme-styling-update") {
       let newTheme = JSON.parse(data);
       if (newTheme && newTheme.id == "firefox-devedition@mozilla.org") {
         this._toggleStyleSheet(true);
       } else {
         this._toggleStyleSheet(false);
       }
+    } else if (topic == "lightweight-theme-window-updated" && subject == window) {
+      this._updateLWTBrightness();
     }
 
     if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) {
       this._updateDevtoolsThemeAttribute();
     }
   },
 
   _inferBrightness: function() {
@@ -62,24 +65,33 @@ var DevEdition = {
     if (this.isStyleSheetEnabled &&
         document.documentElement.getAttribute("devtoolstheme") == "dark") {
       document.documentElement.setAttribute("brighttitlebarforeground", "true");
     } else {
       document.documentElement.removeAttribute("brighttitlebarforeground");
     }
   },
 
+  _updateLWTBrightness() {
+    if (this.isThemeCurrentlyApplied) {
+      let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
+      let textColor = devtoolsTheme == "dark" ? "bright" : "dark";
+      document.documentElement.setAttribute("lwthemetextcolor", textColor);
+    }
+  },
+
   _updateDevtoolsThemeAttribute: function() {
     // Set an attribute on root element to make it possible
     // to change colors based on the selected devtools theme.
     let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
     if (devtoolsTheme != "dark") {
       devtoolsTheme = "light";
     }
     document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
+    this._updateLWTBrightness();
     this._inferBrightness();
   },
 
   handleEvent: function(e) {
     if (e.type === "load") {
       this.styleSheet.removeEventListener("load", this);
       this.refreshBrowserDisplay();
     }
@@ -108,16 +120,17 @@ var DevEdition = {
       this.styleSheet.sheet.disabled = true;
       this.refreshBrowserDisplay();
     }
   },
 
   uninit: function () {
     Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
     Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
+    Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
     if (this.styleSheet) {
       this.styleSheet.removeEventListener("load", this);
     }
     this.styleSheet = null;
   }
 };
 
 // If the DevEdition theme is going to be applied in gBrowserInit.onLoad,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3570,36 +3570,28 @@ const BrowserSearch = {
         }
         win = window.openDialog(getBrowserURL(), "_blank",
                                 "chrome,all,dialog=no", "about:blank");
         Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
       }
       return;
     }
 
-    let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
+    let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
       if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
-        let url = gBrowser.currentURI.spec.toLowerCase();
-        let mm = gBrowser.selectedBrowser.messageManager;
-        let newTabRemoted = Services.prefs.getBoolPref("browser.newtabpage.remote");
-        let localNewTabEnabled = url === "about:newtab" && !newTabRemoted && NewTabUtils.allPages.enabled;
-        if (url === "about:home" || localNewTabEnabled) {
-          ContentSearch.focusInput(mm);
-        } else {
-          openUILinkIn("about:home", "current");
-        }
+        focusAndSelectUrlBar();
       }
     };
 
     let searchBar = this.searchBar;
     let placement = CustomizableUI.getPlacementOfWidget("search-container");
     let focusSearchBar = () => {
       searchBar = this.searchBar;
       searchBar.select();
-      openSearchPageIfFieldIsNotActive(searchBar);
+      focusUrlBarIfSearchFieldIsNotActive(searchBar);
     };
     if (placement && placement.area == CustomizableUI.AREA_PANEL) {
       // The panel is not constructed until the first time it is shown.
       PanelUI.show().then(focusSearchBar);
       return;
     }
     if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
         searchBar.parentNode.getAttribute("overflowedItem") == "true") {
@@ -3609,17 +3601,17 @@ const BrowserSearch = {
       });
       return;
     }
     if (searchBar) {
       if (window.fullScreen)
         FullScreen.showNavToolbox();
       searchBar.select();
     }
-    openSearchPageIfFieldIsNotActive(searchBar);
+    focusUrlBarIfSearchFieldIsNotActive(searchBar);
   },
 
   /**
    * Loads a search results page, given a set of search terms. Uses the current
    * engine if the search bar is visible, or the default engine otherwise.
    *
    * @param searchText
    *        The search terms to use for the search.
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -471,44 +471,16 @@ add_task(function* () {
       yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
         "Search input should be the active element.");
       is(searchInput.value, "a", "Search input should be 'a'.");
     });
   });
 });
 
 add_task(function* () {
-  info("Cmd+k should focus the search box in the page when the search box in the toolbar is absent");
-
-  // Remove the search bar from toolbar
-  CustomizableUI.removeWidgetFromArea("search-container");
-
-  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
-    yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
-    yield ContentTask.spawn(browser, null, function* () {
-      let doc = content.document;
-      isnot(doc.getElementById("searchText"), doc.activeElement,
-        "Search input should not be the active element.");
-    });
-
-    EventUtils.synthesizeKey("k", { accelKey: true });
-
-    yield ContentTask.spawn(browser, null, function* () {
-      let doc = content.document;
-      let searchInput = doc.getElementById("searchText");
-
-      yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
-        "Search input should be the active element.");
-    });
-  });
-
-  CustomizableUI.reset();
-});
-
-add_task(function* () {
   info("Cmd+k should focus the search box in the toolbar when it's present");
 
   yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
     yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
 
     let doc = window.document;
     let searchInput = doc.getElementById("searchbar").textbox.inputField;
     isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -40,11 +40,12 @@ support-files =
   searchEngineNoLogo.xml
   searchEngineFavicon.xml
   searchEngine1xLogo.xml
   searchEngine2xLogo.xml
   searchEngine1x2xLogo.xml
   ../general/searchSuggestionEngine.xml
   ../general/searchSuggestionEngine.sjs
 [browser_newtab_sponsored_icon_click.js]
+skip-if = true # Bug 1314619
 [browser_newtab_undo.js]
 [browser_newtab_unpin.js]
 [browser_newtab_update.js]
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -173,75 +173,16 @@ add_task(function* () {
   EventUtils.synthesizeKey("a", { accelKey: true });
   EventUtils.synthesizeKey("VK_DELETE", {});
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
     Assert.ok(content.document.getElementById("searchSuggestionTable").hidden,
       "Search suggestion table hidden");
   });
 
-  // Remove the search bar from toolbar
-  CustomizableUI.removeWidgetFromArea("search-container");
-  // Focus a different element than the search input from the page.
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-customize-button", { }, gBrowser.selectedBrowser);
-
-  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
-    let input = content.document.getElementById("newtab-search-text");
-    Assert.notEqual(input, content.document.activeElement, "Search input should not be focused");
-  });
-
-  // Test that Ctrl/Cmd + K will focus the input field from the page.
-  let focusPromise = promiseSearchEvents(["FocusInput"]);
-  EventUtils.synthesizeKey("k", { accelKey: true });
-  yield focusPromise;
-
-  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
-    let input = content.document.getElementById("newtab-search-text");
-    Assert.equal(input, content.document.activeElement, "Search input should be focused");
-  });
-
-  // Reset changes made to toolbar
-  CustomizableUI.reset();
-
-  // Test that Ctrl/Cmd + K will focus the search bar from toolbar.
-  EventUtils.synthesizeKey("k", { accelKey: true });
-  let searchBar = document.getElementById("searchbar");
-  is(searchBar.textbox.inputField, document.activeElement, "Toolbar's search bar should be focused");
-
-  // Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
-  // the newtab is disabled from `NewTabUtils.allPages.enabled`.
-  let tab = yield* addNewTabPageTab();
-  // Remove the search bar from toolbar
-  CustomizableUI.removeWidgetFromArea("search-container");
-  NewTabUtils.allPages.enabled = false;
-  EventUtils.synthesizeKey("k", { accelKey: true });
-
-
-  let aboutHomeLoaded = new Promise(resolve => {
-    tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
-      tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
-      resolve();
-    }, true, true);
-  });
-
-  tab.linkedBrowser.loadURI("about:home");
-  yield aboutHomeLoaded;
-
-  yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
-    Assert.equal(content.document.documentURI.toLowerCase(), "about:home",
-      "New tab's uri should be about:home");
-    let searchInput = content.document.getElementById("searchText");
-    Assert.equal(searchInput, content.document.activeElement,
-      "Search input must be the selected element");
-  });
-
-  NewTabUtils.allPages.enabled = true;
-  CustomizableUI.reset();
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-
   // Done.  Revert the current engine and remove the new engines.
   searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
   Services.search.currentEngine = oldCurrentEngine;
   yield searchEventsPromise;
 
   let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length);
   searchEventsPromise = promiseSearchEvents(events);
 
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -50,16 +50,17 @@ skip-if = os == 'linux' # Bug 1104755
 subsuite = clipboard
 support-files =
   authenticate.sjs
 [browser_urlbarDecode.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
+[browser_urlbarFocusedCmdK.js]
 [browser_urlbarHashChangeProxyState.js]
 [browser_urlbarKeepStateAcrossTabSwitches.js]
 [browser_urlbarOneOffs.js]
 [browser_urlbarPrivateBrowsingWindowChange.js]
 [browser_urlbarRaceWithTabs.js]
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarSearchSuggestions.js]
--- a/browser/base/content/test/urlbar/browser_urlbarCopying.js
+++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js
@@ -189,16 +189,11 @@ function testCopy(copyVal, targetValue, 
       gURLBar.select();
     }
 
     goDoCommand("cmd_copy");
   }, cb, cb);
 }
 
 function loadURL(aURL, aCB) {
-  gBrowser.selectedBrowser.addEventListener("load", function () {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    is(gBrowser.currentURI.spec, aURL, "loaded expected URL");
-    aCB();
-  }, true);
-
-  gBrowser.loadURI(aURL);
+  BrowserTestUtils.loadURI(gBrowser.selectedBrowser, aURL);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, aURL).then(aCB);
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function*() {
+  // Remove the search bar from toolbar
+  CustomizableUI.removeWidgetFromArea("search-container");
+
+  // Test that Ctrl/Cmd + K will focus the url bar
+  let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+  EventUtils.synthesizeKey("k", { accelKey: true });
+  yield focusPromise;
+  Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused");
+
+  // Reset changes made to toolbar
+  CustomizableUI.reset();
+});
+
--- a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
+++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
@@ -1,25 +1,14 @@
 /* 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";
 
-var openUILinkInCalled = false;
-var expectOpenUILinkInCall = false;
-this.originalOpenUILinkIn = openUILinkIn;
-openUILinkIn = (aUrl, aWhichTab) => {
-  is(aUrl, "about:home", "about:home should be requested to open.");
-  is(aWhichTab, "current", "Should use the current tab for the search page.");
-  openUILinkInCalled = true;
-  if (!expectOpenUILinkInCall) {
-    ok(false, "OpenUILinkIn was called when it shouldn't have been.");
-  }
-};
 logActiveElement();
 
 function* waitForSearchBarFocus()
 {
   let searchbar = document.getElementById("searchbar");
   yield waitForCondition(function () {
     logActiveElement();
     return document.activeElement === searchbar.textbox.inputField;
@@ -100,40 +89,16 @@ add_task(function*() {
   let placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
 
   sendWebSearchKeyCommand();
 
   yield waitForSearchBarFocus();
 });
 
-// Ctrl+K should open the search page if the search bar has been customized out.
-add_task(function*() {
-  try {
-    expectOpenUILinkInCall = true;
-    CustomizableUI.removeWidgetFromArea("search-container");
-    let placement = CustomizableUI.getPlacementOfWidget("search-container");
-    is(placement, null, "Search container should be in palette");
-
-    openUILinkInCalled = false;
-
-    sendWebSearchKeyCommand();
-    yield waitForCondition(() => openUILinkInCalled);
-    ok(openUILinkInCalled, "The search page should have been opened.")
-    expectOpenUILinkInCall = false;
-  } catch (e) {
-    ok(false, e);
-  }
-  CustomizableUI.reset();
-});
-
-registerCleanupFunction(function() {
-  openUILinkIn = this.originalOpenUILinkIn;
-  delete this.originalOpenUILinkIn;
-});
 
 function sendWebSearchKeyCommand() {
   if (Services.appinfo.OS === "Darwin")
     EventUtils.synthesizeKey("k", { accelKey: true });
   else
     EventUtils.synthesizeKey("k", { ctrlKey: true });
 }
 
--- a/browser/components/preferences/in-content/tests/browser_security.js
+++ b/browser/components/preferences/in-content/tests/browser_security.js
@@ -118,13 +118,13 @@ add_task(function*() {
        "malware table doesn't include test-unwanted-simple");
     let sortedMalware = malwareTable.slice(0);
     sortedMalware.sort();
     Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted");
 
     yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   }
 
-  yield checkPrefSwitch(true, true);
-  yield checkPrefSwitch(false, true);
-  yield checkPrefSwitch(true, false);
-  yield checkPrefSwitch(false, false);
+  yield* checkPrefSwitch(true, true);
+  yield* checkPrefSwitch(false, true);
+  yield* checkPrefSwitch(true, false);
+  yield* checkPrefSwitch(false, false);
 });
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -19,17 +19,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/sessionstore/PageStyle.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
   "resource://gre/modules/ScrollPosition.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
   "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
+  "resource://gre/modules/sessionstore/Utils.jsm");
 
 /**
  * This module implements the content side of session restoration. The chrome
  * side is handled by SessionStore.jsm. The functions in this module are called
  * by content-sessionStore.js based on messages received from SessionStore.jsm
  * (or, in one case, based on a "load" event). Each tab has its own
  * ContentRestore instance, constructed by content-sessionStore.js.
  *
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -8,17 +8,17 @@ this.EXPORTED_SYMBOLS = ["SessionCookies
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
+  "resource://gre/modules/sessionstore/Utils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
   "resource:///modules/sessionstore/PrivacyLevel.jsm");
 
 // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
 const MAX_EXPIRY = Math.pow(2, 62);
 
 /**
  * The external API implemented by the SessionCookies module.
--- a/browser/components/sessionstore/SessionHistory.jsm
+++ b/browser/components/sessionstore/SessionHistory.jsm
@@ -9,17 +9,17 @@ this.EXPORTED_SYMBOLS = ["SessionHistory
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
+  "resource://gre/modules/sessionstore/Utils.jsm");
 
 function debug(msg) {
   Services.console.logStringMessage("SessionHistory: " + msg);
 }
 
 /**
  * The external API exported by this module.
  */
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -175,17 +175,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource:///modules/ContentCrashHandlers.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabState",
   "resource:///modules/sessionstore/TabState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
   "resource:///modules/sessionstore/TabStateFlusher.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
+  "resource://gre/modules/sessionstore/Utils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
   "resource://gre/modules/ViewSourceBrowser.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
   "resource://gre/modules/AsyncShutdown.jsm");
 
 /**
  * |true| if we are in debug mode, |false| otherwise.
  * Debug mode is controlled by preference browser.sessionstore.debug
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -12,17 +12,17 @@ Cu.import("resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource:///modules/sessionstore/Utils.jsm");
+  "resource://gre/modules/sessionstore/Utils.jsm");
 
 /**
  * Module that contains tab state collection methods.
  */
 this.TabState = Object.freeze({
   update: function (browser, data) {
     TabStateInternal.update(browser, data);
   },
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -41,13 +41,12 @@ EXTRA_JS_MODULES.sessionstore = [
     'SessionStore.jsm',
     'SessionWorker.js',
     'SessionWorker.jsm',
     'StartupPerformance.jsm',
     'TabAttributes.jsm',
     'TabState.jsm',
     'TabStateCache.jsm',
     'TabStateFlusher.jsm',
-    'Utils.jsm',
 ]
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Session Restore')
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -12,22 +12,19 @@ def test(mod, path, entity = None):
                  "other-licenses/branding/firefox",
                  "browser/branding/official",
                  "services/sync"):
     return "ignore"
   if mod not in ("browser", "extensions/spellcheck"):
     # we only have exceptions for browser and extensions/spellcheck
     return "error"
   if not entity:
-    # the only files to ignore are spell checkers and search
+    # the only files to ignore are spell checkers
     if mod == "extensions/spellcheck":
       return "ignore"
-    # browser
-    if (re.match(r"searchplugins\/.+\.xml", path)):
-      return "ignore"
     return "error"
   if mod == "extensions/spellcheck":
     # l10n ships en-US dictionary or something, do compare
     return "error"
   if path == "defines.inc":
     return "ignore" if entity == "MOZ_LANGPACK_CONTRIBUTORS" else "error"
 
   if mod == "browser" and path == "chrome/browser-region/region.properties":
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -85,17 +85,17 @@
     locale/browser/syncSetup.dtd                (%chrome/browser/syncSetup.dtd)
     locale/browser/syncSetup.properties         (%chrome/browser/syncSetup.properties)
     locale/browser/syncGenericChange.properties         (%chrome/browser/syncGenericChange.properties)
     locale/browser/syncKey.dtd                  (%chrome/browser/syncKey.dtd)
     locale/browser/syncQuota.dtd                (%chrome/browser/syncQuota.dtd)
     locale/browser/syncQuota.properties         (%chrome/browser/syncQuota.properties)
 % resource search-plugins chrome://browser/locale/searchplugins/
 #if BUILD_FASTER
-    locale/browser/searchplugins/               (%searchplugins/*.xml)
+    locale/browser/searchplugins/               (searchplugins/*.xml)
     locale/browser/searchplugins/list.json      (search/list.json)
 #else
     locale/browser/searchplugins/               (.deps/generated_@AB_CD@/*.xml)
     locale/browser/searchplugins/list.json      (.deps/generated_@AB_CD@/list.json)
 #endif
 % locale browser-region @AB_CD@ %locale/browser-region/
     locale/browser-region/region.properties        (%chrome/browser-region/region.properties)
 # the following files are browser-specific overrides
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -741,17 +741,20 @@ this.UnsubmittedCrashHandler = {
    * YYYYMMDD.
    *
    * @param someDate (Date, optional)
    *        The Date to convert to the string. If not provided,
    *        defaults to today's date.
    * @returns String
    */
   dateString(someDate = new Date()) {
-    return someDate.toLocaleFormat("%Y%m%d");
+    let year = String(someDate.getFullYear()).padStart(4, "0");
+    let month = String(someDate.getMonth() + 1).padStart(2, "0");
+    let day = String(someDate.getDate()).padStart(2, "0");
+    return year + month + day;
   },
 
   /**
    * Attempts to show a notification bar to the user in the most
    * recent browser window asking them to submit some crash report
    * IDs. If a notification cannot be shown (for example, there
    * is no browser window), this method exits silently.
    *
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -32,12 +32,9 @@ support-files =
   usageTelemetrySearchSuggestions.sjs
   usageTelemetrySearchSuggestions.xml
 [browser_UsageTelemetry_searchbar.js]
 support-files =
   usageTelemetrySearchSuggestions.sjs
   usageTelemetrySearchSuggestions.xml
 [browser_UsageTelemetry_content.js]
 [browser_UsageTelemetry_content_aboutHome.js]
-# Disabled for intermittent failures.
-# Re-enabling this test is tracked in bug 1313825.
-skip-if = true
 [browser_urlBar_zoom.js]
--- a/browser/modules/test/browser_UnsubmittedCrashHandler.js
+++ b/browser/modules/test/browser_UnsubmittedCrashHandler.js
@@ -441,17 +441,17 @@ add_task(function* test_can_ignore() {
  * lastShownDate is set for today.
  */
 add_task(function* test_last_shown_date() {
   yield createPendingCrashReports(1);
   let notification =
     yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(notification, "There should be a notification");
 
-  let today = new Date().toLocaleFormat("%Y%m%d");
+  let today = UnsubmittedCrashHandler.dateString(new Date());
   let lastShownDate =
     UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
   Assert.equal(today, lastShownDate,
                "Last shown date should be today.");
 
   UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
   gNotificationBox.removeNotification(notification, true);
   clearPendingCrashReports();
@@ -536,17 +536,17 @@ add_task(function* test_dont_decrement_c
   gNotificationBox.removeNotification(notification, true);
 
   let shutdownWhileShowing =
     UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
   Assert.ok(shutdownWhileShowing,
             "We should have noticed that we uninitted while showing " +
             "the notification.");
 
-  let today = new Date().toLocaleFormat("%Y%m%d");
+  let today = UnsubmittedCrashHandler.dateString(new Date());
   let lastShownDate =
     UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
   Assert.equal(today, lastShownDate,
                "Last shown date should be today.");
 
   UnsubmittedCrashHandler.init();
 
   notification =
@@ -585,17 +585,17 @@ add_task(function* test_decrement_chance
 
   let shutdownWhileShowing =
     UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
   Assert.ok(shutdownWhileShowing,
             "We should have noticed that we uninitted while showing " +
             "the notification.");
 
   // Now pretend that the notification was shown yesterday.
-  let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d");
+  let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
   UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
 
   UnsubmittedCrashHandler.init();
 
   notification =
     yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(notification, "There should still be a notification");
 
@@ -613,47 +613,47 @@ add_task(function* test_decrement_chance
 /**
  * Tests that if we've shutdown too many times showing the
  * notification, and we've run out of chances, then
  * browser.crashReports.unsubmittedCheck.suppressUntilDate is
  * set for some days into the future.
  */
 add_task(function* test_can_suppress_after_chances() {
   // Pretend that a notification was shown yesterday.
-  let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d");
+  let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
   UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
   UnsubmittedCrashHandler.prefs.setBoolPref("shutdownWhileShowing", true);
   UnsubmittedCrashHandler.prefs.setIntPref("chancesUntilSuppress", 0);
 
   yield createPendingCrashReports(1);
   let notification =
     yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.equal(notification, null,
                "There should be no notification if we've run out of chances");
 
   // We should have set suppressUntilDate into the future
   let suppressUntilDate =
     UnsubmittedCrashHandler.prefs.getCharPref("suppressUntilDate");
 
-  let today = new Date().toLocaleFormat("%Y%m%d");
+  let today = UnsubmittedCrashHandler.dateString(new Date());
   Assert.ok(suppressUntilDate > today,
             "We should be suppressing until some days into the future.");
 
   UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress");
   UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
   UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
   clearPendingCrashReports();
 });
 
 /**
  * Tests that if there's a suppression date set, then no notification
  * will be shown even if there are pending crash reports.
  */
 add_task(function* test_suppression() {
-  let future = new Date(Date.now() + (DAY * 5)).toLocaleFormat("%Y%m%d");
+  let future = UnsubmittedCrashHandler.dateString(new Date(Date.now() + (DAY * 5)));
   UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", future);
   UnsubmittedCrashHandler.uninit();
   UnsubmittedCrashHandler.init();
 
   Assert.ok(UnsubmittedCrashHandler.suppressed,
             "The UnsubmittedCrashHandler should be suppressed.");
   UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
 
@@ -661,17 +661,17 @@ add_task(function* test_suppression() {
   UnsubmittedCrashHandler.init();
 });
 
 /**
  * Tests that if there's a suppression date set, but we've exceeded
  * it, then we can show the notification again.
  */
 add_task(function* test_end_suppression() {
-  let yesterday = new Date(Date.now() - DAY).toLocaleFormat("%Y%m%d");
+  let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
   UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", yesterday);
   UnsubmittedCrashHandler.uninit();
   UnsubmittedCrashHandler.init();
 
   Assert.ok(!UnsubmittedCrashHandler.suppressed,
             "The UnsubmittedCrashHandler should not be suppressed.");
   Assert.ok(!UnsubmittedCrashHandler.prefs.prefHasUserValue("suppressUntilDate"),
             "The suppression date should been cleared from preferences.");
--- a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
@@ -34,24 +34,31 @@ add_task(function* setup() {
     Services.search.removeEngine(engineOneOff);
   });
 });
 
 add_task(function* test_abouthome_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
 
-  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
-  yield new Promise(resolve => {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+  info("Setup waiting for AboutHomeLoadSnippetsCompleted.");
+  let promiseAboutHomeLoaded = new Promise(resolve => {
     tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
       tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
       resolve();
     }, true, true);
   });
 
+  info("Load about:home.");
+  tab.linkedBrowser.loadURI("about:home");
+  info("Wait for AboutHomeLoadSnippetsCompleted.");
+  yield promiseAboutHomeLoaded;
+
   info("Trigger a simple serch, just test + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield typeInSearchField(tab.linkedBrowser, "test query", "searchText");
   yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   yield p;
 
   // Check if the scalars contain the expected values.
   const scalars =
--- a/browser/themes/osx/devedition.css
+++ b/browser/themes/osx/devedition.css
@@ -82,21 +82,16 @@
 
 /* Use smaller back button icon */
 @media (min-resolution: 2dppx) {
   #back-button:hover:active:not([disabled="true"]) {
     -moz-image-region: rect(36px, 108px, 72px, 72px);
   }
 }
 
-#forward-button:hover:active:not(:-moz-lwtheme) {
-  background-image: none;
-  box-shadow: none;
-}
-
 /* Don't use the default background for tabs toolbar */
 #TabsToolbar {
   -moz-appearance: none !important;
 }
 
 /* Prevent the hover styling from on the identity icon from overlapping the
    urlbar border. */
 #identity-box {
--- a/browser/themes/osx/places/organizer.css
+++ b/browser/themes/osx/places/organizer.css
@@ -44,85 +44,69 @@
 }
 
 #placesList > treechildren::-moz-tree-twisty {
   -moz-appearance: none;
   padding: 0 2px;
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
 }
 
-#placesList > treechildren::-moz-tree-twisty(selected) {
+#placesList > treechildren::-moz-tree-twisty(closed, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
 }
 
 #placesList > treechildren::-moz-tree-twisty(open) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
 }
 
 #placesList > treechildren::-moz-tree-twisty(open, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
 }
 
-#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty {
+#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
 }
 
-#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) {
+#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
 }
 
-#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open) {
-  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
-}
-
-#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) {
-  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
-}
-
 @media (-moz-mac-yosemite-theme) {
   #placesList > treechildren::-moz-tree-cell-text(selected) {
     color: -moz-dialogtext;
     font-weight: 500;
   }
 
   #placesList > treechildren::-moz-tree-cell-text(selected, focus) {
     color: #fff;
   }
 
-  #placesList > treechildren::-moz-tree-twisty(selected) {
+  #placesList > treechildren::-moz-tree-twisty(closed, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
   }
 
-  #placesList > treechildren::-moz-tree-twisty(selected, focus) {
+  #placesList > treechildren::-moz-tree-twisty(closed, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
   }
 
   #placesList > treechildren::-moz-tree-twisty(open, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
   }
 
   #placesList > treechildren::-moz-tree-twisty(open, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
   }
 
-  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) {
+  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
   }
 
-  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected, focus) {
+  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
   }
-
-  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) {
-    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
-  }
-
-  #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected, focus) {
-    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
-  }
 }
 
 #placesToolbar {
   padding: 0 4px 3px;
 }
 
 #placesView {
   border-top: none !important;
--- a/browser/themes/osx/places/places.css
+++ b/browser/themes/osx/places/places.css
@@ -62,85 +62,69 @@
 }
 
 .sidebar-placesTreechildren::-moz-tree-twisty {
   -moz-appearance: none;
   padding: 0 2px;
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
 }
 
-.sidebar-placesTreechildren::-moz-tree-twisty(selected) {
+.sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
 }
 
 .sidebar-placesTreechildren::-moz-tree-twisty(open) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
 }
 
 .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
 }
 
-.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty {
+.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
 }
 
-.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) {
+.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
 }
 
-.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open) {
-  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
-}
-
-.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) {
-  list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
-}
-
 @media (-moz-mac-yosemite-theme) {
   .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
     color: -moz-dialogtext;
     font-weight: 500;
   }
 
   .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
     color: #fff;
   }
 
-  .sidebar-placesTreechildren::-moz-tree-twisty(selected) {
+  .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
   }
 
-  .sidebar-placesTreechildren::-moz-tree-twisty(selected, focus) {
+  .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
   }
 
   .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
   }
 
   .sidebar-placesTreechildren::-moz-tree-twisty(open, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
   }
 
-  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected) {
+  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
   }
 
-  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(selected, focus) {
+  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl")
   }
-
-  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected) {
-    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
-  }
-
-  .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(open, selected, focus) {
-    list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
-  }
 }
 
 #viewButton {
   -moz-appearance: none;
   padding-bottom: 1px;
   padding-inline-start: 5px;
   padding-inline-end: 0px;
   margin: 0;
--- a/browser/themes/shared/devedition.inc.css
+++ b/browser/themes/shared/devedition.inc.css
@@ -179,17 +179,16 @@
 /* Using toolbar[brighttext] instead of important to override linux */
 toolbar[brighttext] #downloads-indicator-counter {
   text-shadow: var(--toolbarbutton-text-shadow);
   color: var(--chrome-color);
 }
 
 #TabsToolbar {
   text-shadow: none !important;
-  color: var(--chrome-color) !important; /* Make sure that the brighttext attribute is added */
 }
 
 /* URL bar and search bar*/
 #urlbar,
 #navigator-toolbox .searchbar-textbox {
   background-color: var(--url-and-searchbar-background-color) !important;
   background-image: none !important;
   color: inherit !important;
--- a/build/gyp.mozbuild
+++ b/build/gyp.mozbuild
@@ -104,16 +104,17 @@ flavors = {
     'NetBSD': 'netbsd',
     'OpenBSD': 'openbsd',
 }
 gyp_vars['OS'] = flavors.get(os)
 
 arches = {
     'x86_64': 'x64',
     'x86': 'ia32',
+    'aarch64': 'arm64',
 }
 
 gyp_vars['target_arch'] = arches.get(CONFIG['CPU_ARCH'], CONFIG['CPU_ARCH'])
 
 if CONFIG['ARM_ARCH']:
     if int(CONFIG['ARM_ARCH']) < 7:
         gyp_vars['armv7'] = 0
         gyp_vars['arm_neon_optional'] = 0
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -18,16 +18,17 @@
 
 #include "nsPrincipal.h"
 #include "nsNetUtil.h"
 #include "nsIURIWithPrincipal.h"
 #include "nsNullPrincipal.h"
 #include "nsScriptSecurityManager.h"
 #include "nsServiceManagerUtils.h"
 
+#include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/CSPDictionariesBinding.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/URLSearchParams.h"
 
 namespace mozilla {
 
 using dom::URLParams;
@@ -390,16 +391,26 @@ BasePrincipal::GetOriginNoSuffix(nsACStr
 {
   return GetOriginInternal(aOrigin);
 }
 
 bool
 BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration)
 {
   MOZ_ASSERT(aOther);
+
+  // Expanded principals handle origin attributes for each of their
+  // sub-principals individually, null principals do only simple checks for
+  // pointer equality, and system principals are immune to origin attributes
+  // checks, so only do this check for codebase principals.
+  if (Kind() == eCodebasePrincipal &&
+      OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) {
+    return false;
+  }
+
   return SubsumesInternal(aOther, aConsideration);
 }
 
 NS_IMETHODIMP
 BasePrincipal::Equals(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
   *aResult = Subsumes(aOther, DontConsiderDocumentDomain) &&
@@ -411,16 +422,32 @@ NS_IMETHODIMP
 BasePrincipal::EqualsConsideringDomain(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
   *aResult = Subsumes(aOther, ConsiderDocumentDomain) &&
              Cast(aOther)->Subsumes(this, ConsiderDocumentDomain);
   return NS_OK;
 }
 
+bool
+BasePrincipal::EqualsIgnoringAddonId(nsIPrincipal *aOther)
+{
+  MOZ_ASSERT(aOther);
+
+  // Note that this will not work for expanded principals, nor is it intended
+  // to.
+  if (!dom::ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(
+          OriginAttributesRef(), Cast(aOther)->OriginAttributesRef())) {
+    return false;
+  }
+
+  return SubsumesInternal(aOther, DontConsiderDocumentDomain) &&
+         Cast(aOther)->SubsumesInternal(this, DontConsiderDocumentDomain);
+}
+
 NS_IMETHODIMP
 BasePrincipal::Subsumes(nsIPrincipal *aOther, bool *aResult)
 {
   NS_ENSURE_TRUE(aOther, NS_ERROR_INVALID_ARG);
   *aResult = Subsumes(aOther, DontConsiderDocumentDomain);
   return NS_OK;
 }
 
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -284,16 +284,18 @@ public:
   NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
   NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
   NS_IMETHOD GetAddonId(nsAString& aAddonId) final;
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) final;
   NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
   NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
   NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
 
+  bool EqualsIgnoringAddonId(nsIPrincipal *aOther);
+
   virtual bool AddonHasPermission(const nsAString& aPerm);
 
   virtual bool IsOnCSSUnprefixingWhitelist() override { return false; }
 
   virtual bool IsCodebasePrincipal() const { return false; };
 
   static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }
   static already_AddRefed<BasePrincipal>
@@ -316,16 +318,18 @@ public:
   virtual PrincipalKind Kind() = 0;
 
   already_AddRefed<BasePrincipal> CloneStrippingUserContextIdAndFirstPartyDomain();
 
 protected:
   virtual ~BasePrincipal();
 
   virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0;
+  // Note that this does not check OriginAttributes. Callers that depend on
+  // those must call Subsumes instead.
   virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0;
 
   // Internal, side-effect-free check to determine whether the concrete
   // principal would allow the load ignoring any common behavior implemented in
   // BasePrincipal::CheckMayLoad.
   virtual bool MayLoadInternal(nsIURI* aURI) = 0;
   friend class ::nsExpandedPrincipal;
 
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -192,20 +192,16 @@ nsPrincipal::SubsumesInternal(nsIPrincip
 {
   MOZ_ASSERT(aOther);
 
   // For nsPrincipal, Subsumes is equivalent to Equals.
   if (aOther == this) {
     return true;
   }
 
-  if (OriginAttributesRef() != Cast(aOther)->OriginAttributesRef()) {
-    return false;
-  }
-
   // If either the subject or the object has changed its principal by
   // explicitly setting document.domain then the other must also have
   // done so in order to be considered the same origin. This prevents
   // DNS spoofing based on document.domain (154930)
   nsresult rv;
   if (aConsideration == ConsiderDocumentDomain) {
     // Get .domain on each principal.
     nsCOMPtr<nsIURI> thisDomain, otherDomain;
@@ -726,16 +722,19 @@ nsExpandedPrincipal::SubsumesInternal(ns
 {
   // If aOther is an ExpandedPrincipal too, we break it down into its component
   // nsIPrincipals, and check subsumes on each one.
   nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aOther);
   if (expanded) {
     nsTArray< nsCOMPtr<nsIPrincipal> >* otherList;
     expanded->GetWhiteList(&otherList);
     for (uint32_t i = 0; i < otherList->Length(); ++i){
+      // Use SubsumesInternal rather than Subsumes here, since OriginAttribute
+      // checks are only done between non-expanded sub-principals, and we don't
+      // need to incur the extra virtual call overhead.
       if (!SubsumesInternal((*otherList)[i], aConsideration)) {
         return false;
       }
     }
     return true;
   }
 
   // We're dealing with a regular principal. One of our principals must subsume
--- a/devtools/client/inspector/components/box-model.js
+++ b/devtools/client/inspector/components/box-model.js
@@ -4,17 +4,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/. */
 
 "use strict";
 
 const {Task} = require("devtools/shared/task");
 const {InplaceEditor, editableItem} =
       require("devtools/client/shared/inplace-editor");
-const {ReflowFront} = require("devtools/shared/fronts/layout");
+const {ReflowFront} = require("devtools/shared/fronts/reflow");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 const STRINGS_URI = "devtools/locale/shared.properties";
 const STRINGS_INSPECTOR = "devtools-shared/locale/styleinspector.properties";
 const SHARED_L10N = new LocalizationHelper(STRINGS_URI);
 const INSPECTOR_L10N = new LocalizationHelper(STRINGS_INSPECTOR);
 const NUMERIC = /^-?[\d\.]+$/;
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -50,46 +50,52 @@ exports.viewSourceInStyleEditor = Task.a
  *
  * @return {Promise<boolean>}
  */
 exports.viewSourceInDebugger = Task.async(function* (toolbox, sourceURL, sourceLine) {
   // If the Debugger was already open, switch to it and try to show the
   // source immediately. Otherwise, initialize it and wait for the sources
   // to be added first.
   let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
-  let { panelWin: dbg } = yield toolbox.loadTool("jsdebugger");
+  let dbg = yield toolbox.loadTool("jsdebugger");
 
   // New debugger frontend
   if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
     yield toolbox.selectTool("jsdebugger");
-    // TODO: Properly handle case where source will never exist in the
-    // debugger
-    dbg.actions.selectSourceURL(sourceURL, { line: sourceLine });
-    return true;
+    const source = dbg._selectors().getSourceByURL(dbg._getState(), sourceURL);
+    if (source) {
+      dbg._actions().selectSourceByURL(sourceURL, { line: sourceLine });
+      return true;
+    }
+
+    exports.viewSource(toolbox, sourceURL, sourceLine);
+    return false;
   }
 
+  const win = dbg.panelWin;
+
   // Old debugger frontend
   if (!debuggerAlreadyOpen) {
-    yield dbg.DebuggerController.waitForSourcesLoaded();
+    yield win.DebuggerController.waitForSourcesLoaded();
   }
 
-  let { DebuggerView } = dbg;
+  let { DebuggerView } = win;
   let { Sources } = DebuggerView;
 
   let item = Sources.getItemForAttachment(a => a.source.url === sourceURL);
   if (item) {
     yield toolbox.selectTool("jsdebugger");
 
     // Determine if the source has already finished loading. There's two cases
     // in which we need to wait for the source to be shown:
     // 1) The requested source is not yet selected and will be shown once it is
     //    selected and loaded
     // 2) The requested source is selected BUT the source text is still loading.
     const { actor } = item.attachment.source;
-    const state = dbg.DebuggerController.getState();
+    const state = win.DebuggerController.getState();
 
     // (1) Is the source selected?
     const selected = state.sources.selectedSource;
     const isSelected = selected === actor;
 
     // (2) Has the source text finished loading?
     let isLoading = false;
 
@@ -101,17 +107,17 @@ exports.viewSourceInDebugger = Task.asyn
       isLoading = sourceTextInfo && sourceTextInfo.loading;
     }
 
     // Select the requested source
     DebuggerView.setEditorLocation(actor, sourceLine, { noDebug: true });
 
     // Wait for it to load
     if (!isSelected || isLoading) {
-      yield dbg.DebuggerController.waitForSourceShown(sourceURL);
+      yield win.DebuggerController.waitForSourceShown(sourceURL);
     }
     return true;
   }
 
   // If not found, still attempt to open in View Source
   exports.viewSource(toolbox, sourceURL, sourceLine);
   return false;
 });
--- a/devtools/client/webconsole/test/browser_webconsole_bug_646025_console_file_location.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_646025_console_file_location.js
@@ -22,36 +22,36 @@ add_task(function* () {
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_URI2);
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "message for level log",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
-      source: { url: "test-file-location.js", line: 6 },
+      source: { url: "test-file-location.js", line: 8 },
     },
     {
       text: "message for level info",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_INFO,
-      source: { url: "test-file-location.js", line: 7 },
+      source: { url: "test-file-location.js", line: 9 },
     },
     {
       text: "message for level warn",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_WARNING,
-      source: { url: "test-file-location.js", line: 8 },
+      source: { url: "test-file-location.js", line: 10 },
     },
     {
       text: "message for level error",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_ERROR,
-      source: { url: "test-file-location.js", line: 9 },
+      source: { url: "test-file-location.js", line: 11 },
     },
     {
       text: "message for level debug",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
-      source: { url: "test-file-location.js", line: 10 },
+      source: { url: "test-file-location.js", line: 12 },
     }],
   });
 });
--- a/devtools/client/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js
+++ b/devtools/client/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js
@@ -36,19 +36,19 @@ function test() {
         category: CATEGORY_NETWORK
       }]
     });
 
     let networkEventMessage = messages[0].matched.values().next().value;
     let urlNode = networkEventMessage.querySelector(".url");
 
     let deferred = promise.defer();
-    urlNode.addEventListener("click", function onClick(aEvent) {
+    urlNode.addEventListener("click", function onClick(event) {
       urlNode.removeEventListener("click", onClick);
-      ok(aEvent.defaultPrevented, "The default action was prevented.");
+      ok(event.defaultPrevented, "The default action was prevented.");
 
       deferred.resolve();
     });
 
     EventUtils.synthesizeMouseAtCenter(urlNode, {clickCount: 2},
                                        hud.iframeWindow);
 
     yield deferred.promise;
--- a/devtools/client/webconsole/test/browser_webconsole_view_source.js
+++ b/devtools/client/webconsole/test/browser_webconsole_view_source.js
@@ -8,23 +8,27 @@
 // have their locations opened in Debugger, we need to test a security message in
 // order to have it opened in the standard View Source window.
 
 "use strict";
 
 const TEST_URI = "https://example.com/browser/devtools/client/webconsole/" +
                  "test/test-mixedcontent-securityerrors.html";
 
-// Force the old debugger UI since it's directly used (see Bug 1301705)
-Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
-registerCleanupFunction(function* () {
+add_task(function* () {
+  yield actuallyTest();
+});
+
+add_task(function* () {
+  Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
+  yield actuallyTest();
   Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
 });
 
-add_task(function* () {
+var actuallyTest = Task.async(function*() {
   yield loadTab(TEST_URI);
   let hud = yield openConsole(null);
   info("console opened");
 
   let [result] = yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "Blocked loading mixed active content",
--- a/devtools/client/webconsole/test/test-file-location.js
+++ b/devtools/client/webconsole/test/test-file-location.js
@@ -1,10 +1,12 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 console.log("message for level log");
 console.info("message for level info");
 console.warn("message for level warn");
 console.error("message for level error");
 console.debug("message for level debug");
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -68,17 +68,17 @@ const {
 const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper");
 const {
   isAnonymous,
   isNativeAnonymous,
   isXBLAnonymous,
   isShadowAnonymous,
   getFrameElement
 } = require("devtools/shared/layout/utils");
-const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/layout");
+const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/reflow");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
 
 const {EventParsers} = require("devtools/server/event-parsers");
 const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -32,28 +32,28 @@ DevToolsModules(
     'eventlooplag.js',
     'frame.js',
     'framerate.js',
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.css',
     'highlighters.js',
     'inspector.js',
-    'layout.js',
     'memory.js',
     'monitor.js',
     'object.js',
     'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'pretty-print-worker.js',
     'process.js',
     'profiler.js',
     'promises.js',
+    'reflow.js',
     'root.js',
     'script.js',
     'settings.js',
     'source.js',
     'storage.js',
     'string.js',
     'styleeditor.js',
     'styles.js',
rename from devtools/server/actors/layout.js
rename to devtools/server/actors/reflow.js
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/reflow.js
@@ -26,17 +26,17 @@
 
 const {Ci} = require("chrome");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const protocol = require("devtools/shared/protocol");
 const {method, Arg} = protocol;
 const events = require("sdk/event/core");
 const Heritage = require("sdk/core/heritage");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {reflowSpec} = require("devtools/shared/specs/layout");
+const {reflowSpec} = require("devtools/shared/specs/reflow");
 
 /**
  * The reflow actor tracks reflows and emits events about them.
  */
 var ReflowActor = exports.ReflowActor = protocol.ActorClassWithSpec(reflowSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -502,17 +502,17 @@ var DebuggerServer = {
       constructor: "FramerateActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/eventlooplag", {
       prefix: "eventLoopLag",
       constructor: "EventLoopLagActor",
       type: { tab: true }
     });
-    this.registerModule("devtools/server/actors/layout", {
+    this.registerModule("devtools/server/actors/reflow", {
       prefix: "reflow",
       constructor: "ReflowActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/css-properties", {
       prefix: "cssProperties",
       constructor: "CssPropertiesActor",
       type: { tab: true }
--- a/devtools/server/tests/unit/test_layout-reflows-observer.js
+++ b/devtools/server/tests/unit/test_layout-reflows-observer.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test the LayoutChangesObserver
 
 var {
   getLayoutChangesObserver,
   releaseLayoutChangesObserver,
   LayoutChangesObserver
-} = require("devtools/server/actors/layout");
+} = require("devtools/server/actors/reflow");
 
 // Override set/clearTimeout on LayoutChangesObserver to avoid depending on
 // time in this unit test. This means that LayoutChangesObserver.eventLoopTimer
 // will be the timeout callback instead of the timeout itself, so test cases
 // will need to execute it to fake a timeout
 LayoutChangesObserver.prototype._setTimeout = cb => cb;
 LayoutChangesObserver.prototype._clearTimeout = function () {};
 
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -16,24 +16,24 @@ DevToolsModules(
     'director-manager.js',
     'director-registry.js',
     'emulation.js',
     'eventlooplag.js',
     'framerate.js',
     'gcli.js',
     'highlighters.js',
     'inspector.js',
-    'layout.js',
     'memory.js',
     'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'profiler.js',
     'promises.js',
+    'reflow.js',
     'settings.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'timeline.js',
     'webaudio.js',
     'webgl.js'
rename from devtools/shared/fronts/layout.js
rename to devtools/shared/fronts/reflow.js
--- a/devtools/shared/fronts/layout.js
+++ b/devtools/shared/fronts/reflow.js
@@ -1,14 +1,15 @@
 /* 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 {reflowSpec} = require("devtools/shared/specs/layout");
+const {reflowSpec} = require("devtools/shared/specs/reflow");
 const protocol = require("devtools/shared/protocol");
 
 /**
  * Usage example of the reflow front:
  *
  * let front = ReflowFront(toolbox.target.client, toolbox.target.form);
  * front.on("reflows", this._onReflows);
  * front.start();
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -2,17 +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/. */
 
 "use strict";
 
 const { Ci, Cc } = require("chrome");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
 
-loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/layout", true);
+loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/reflow", true);
 exports.setIgnoreLayoutChanges = (...args) =>
   this.setIgnoreLayoutChanges(...args);
 
 /**
  * Returns the `DOMWindowUtils` for the window given.
  *
  * @param {DOMWindow} win
  * @returns {DOMWindowUtils}
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -20,25 +20,25 @@ DevToolsModules(
     'environment.js',
     'eventlooplag.js',
     'frame.js',
     'framerate.js',
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.js',
     'inspector.js',
-    'layout.js',
     'memory.js',
     'node.js',
     'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'profiler.js',
     'promises.js',
+    'reflow.js',
     'script.js',
     'settings.js',
     'source.js',
     'storage.js',
     'string.js',
     'styleeditor.js',
     'styles.js',
     'stylesheets.js',
rename from devtools/shared/specs/layout.js
rename to devtools/shared/specs/reflow.js
--- a/devtools/shared/specs/layout.js
+++ b/devtools/shared/specs/reflow.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Arg, generateActorSpec} = require("devtools/shared/protocol");
 
 const reflowSpec = generateActorSpec({
   typeName: "reflow",
 
   events: {
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -12,17 +12,16 @@
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsICharsetDetector.h"
 #include "nsIConverterInputStream.h"
 #include "nsIDocument.h"
 #include "nsIFileStreams.h"
 #include "nsIInputStream.h"
 #include "nsIIPCSerializableInputStream.h"
-#include "nsIMemoryReporter.h"
 #include "nsIMIMEService.h"
 #include "nsISeekableStream.h"
 #include "nsIUnicharInputStream.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIRemoteBlob.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIUUIDGenerator.h"
@@ -187,18 +186,18 @@ Blob::Create(nsISupports* aParent, const
   MOZ_ASSERT(!blob->mImpl->IsFile());
   return blob.forget();
 }
 
 /* static */ already_AddRefed<Blob>
 Blob::CreateStringBlob(nsISupports* aParent, const nsACString& aData,
                        const nsAString& aContentType)
 {
-  RefPtr<Blob> blob = Blob::Create(aParent,
-    new BlobImplString(aData, aContentType));
+  RefPtr<BlobImpl> blobImpl = BlobImplString::Create(aData, aContentType);
+  RefPtr<Blob> blob = Blob::Create(aParent, blobImpl);
   MOZ_ASSERT(!blob->mImpl->IsFile());
   return blob.forget();
 }
 
 /* static */ already_AddRefed<Blob>
 Blob::CreateMemoryBlob(nsISupports* aParent, void* aMemoryBuffer,
                        uint64_t aLength, const nsAString& aContentType)
 {
@@ -1046,17 +1045,37 @@ EmptyBlobImpl::GetInternalStream(nsIInpu
     aRv.Throw(rv);
     return;
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////
 // BlobImplString implementation
 
-NS_IMPL_ISUPPORTS_INHERITED0(BlobImplString, BlobImpl)
+NS_IMPL_ISUPPORTS_INHERITED(BlobImplString, BlobImpl, nsIMemoryReporter)
+
+/* static */ already_AddRefed<BlobImplString>
+BlobImplString::Create(const nsACString& aData, const nsAString& aContentType)
+{
+  RefPtr<BlobImplString> blobImpl = new BlobImplString(aData, aContentType);
+  RegisterWeakMemoryReporter(blobImpl);
+  return blobImpl.forget();
+}
+
+BlobImplString::BlobImplString(const nsACString& aData,
+                               const nsAString& aContentType)
+  : BlobImplBase(aContentType, aData.Length())
+  , mData(aData)
+{
+}
+
+BlobImplString::~BlobImplString()
+{
+  UnregisterWeakMemoryReporter(this);
+}
 
 already_AddRefed<BlobImpl>
 BlobImplString::CreateSlice(uint64_t aStart, uint64_t aLength,
                             const nsAString& aContentType,
                             ErrorResult& aRv)
 {
   RefPtr<BlobImpl> impl =
     new BlobImplString(Substring(mData, aStart, aLength),
@@ -1065,16 +1084,27 @@ BlobImplString::CreateSlice(uint64_t aSt
 }
 
 void
 BlobImplString::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
 {
   aRv = NS_NewCStringInputStream(aStream, mData);
 }
 
+NS_IMETHODIMP
+BlobImplString::CollectReports(nsIHandleReportCallback* aHandleReport,
+                               nsISupports* aData, bool aAnonymize)
+{
+  MOZ_COLLECT_REPORT(
+    "explicit/dom/memory-file-data/string", KIND_HEAP, UNITS_BYTES,
+    mData.SizeOfExcludingThisIfUnshared(MallocSizeOf),
+    "Memory used to back a File/Blob based on a string.");
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////
 // BlobImplMemory implementation
 
 NS_IMPL_ISUPPORTS_INHERITED0(BlobImplMemory, BlobImpl)
 
 already_AddRefed<BlobImpl>
 BlobImplMemory::CreateSlice(uint64_t aStart, uint64_t aLength,
                             const nsAString& aContentType,
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -14,16 +14,17 @@
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Date.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMBlob.h"
 #include "nsIFile.h"
+#include "nsIMemoryReporter.h"
 #include "nsIMutable.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsString.h"
 #include "nsTemporaryFileInputStream.h"
 #include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 
 class nsIFile;
@@ -520,37 +521,42 @@ protected:
   uint64_t mLength;
 
   int64_t mLastModificationDate;
 
   const uint64_t mSerialNumber;
 };
 
 class BlobImplString final : public BlobImplBase
+                           , public nsIMemoryReporter
 {
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
 public:
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIMEMORYREPORTER
 
-  BlobImplString(const nsACString& aData, const nsAString& aContentType)
-    : BlobImplBase(aContentType, aData.Length())
-    , mData(aData)
-  {}
+  static already_AddRefed<BlobImplString>
+  Create(const nsACString& aData, const nsAString& aContentType);
 
   virtual void GetInternalStream(nsIInputStream** aStream,
                                  ErrorResult& aRv) override;
 
   virtual already_AddRefed<BlobImpl>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType, ErrorResult& aRv) override;
 
 private:
-  ~BlobImplString() {}
+  BlobImplString(const nsACString& aData, const nsAString& aContentType);
+
+  ~BlobImplString();
 
   nsCString mData;
 };
+
 /**
  * This class may be used off the main thread, and in particular, its
  * constructor and destructor may not run on the same thread.  Be careful!
  */
 class BlobImplMemory final : public BlobImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -102,23 +102,29 @@ PostMessageEvent::Run()
     if (NS_WARN_IF(!targetPrin))
       return NS_OK;
 
     // Note: This is contrary to the spec with respect to file: URLs, which
     //       the spec groups into a single origin, but given we intentionally
     //       don't do that in other places it seems better to hold the line for
     //       now.  Long-term, we want HTML5 to address this so that we can
     //       be compliant while being safer.
-    if (!targetPrin->Equals(mProvidedPrincipal)) {
+    if (!BasePrincipal::Cast(targetPrin)->EqualsIgnoringAddonId(mProvidedPrincipal)) {
       nsAutoString providedOrigin, targetOrigin;
       nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      MOZ_DIAGNOSTIC_ASSERT(providedOrigin != targetOrigin ||
+                            (BasePrincipal::Cast(mProvidedPrincipal)->OriginAttributesRef() ==
+                              BasePrincipal::Cast(targetPrin)->OriginAttributesRef()),
+                            "Unexpected postMessage call to a window with mismatched "
+                            "origin attributes");
+
       const char16_t* params[] = { providedOrigin.get(), targetOrigin.get() };
 
       nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
         NS_LITERAL_CSTRING("DOM Window"), sourceDocument,
         nsContentUtils::eDOM_PROPERTIES,
         "TargetPrincipalDoesNotMatch",
         params, ArrayLength(params));
 
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -2016,44 +2016,112 @@ public:
   nsCOMPtr<nsISupports>   mTarget;
   RefPtr<nsPresContext> mContext;
   EventMessage            mEventMessage;
   bool                    mWindowRaised;
   bool                    mIsRefocus;
   nsCOMPtr<EventTarget>   mRelatedTarget;
 };
 
+class FocusInOutEvent : public Runnable
+{
+public:
+  FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage,
+                 nsPresContext* aContext,
+                 nsPIDOMWindowOuter* aOriginalFocusedWindow,
+                 nsIContent* aOriginalFocusedContent,
+                 EventTarget* aRelatedTarget)
+    : mTarget(aTarget)
+    , mContext(aContext)
+    , mEventMessage(aEventMessage)
+    , mOriginalFocusedWindow(aOriginalFocusedWindow)
+    , mOriginalFocusedContent(aOriginalFocusedContent)
+    , mRelatedTarget(aRelatedTarget)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ?
+        mOriginalFocusedWindow->GetFocusedNode() :
+        nullptr;
+    // Blink does not check that focus is the same after blur, but WebKit does.
+    // Opt to follow Blink's behavior (see bug 687787).
+    if (mEventMessage == eFocusOut ||
+        originalWindowFocus == mOriginalFocusedContent) {
+      InternalFocusEvent event(true, mEventMessage);
+      event.mFlags.mBubbles = true;
+      event.mFlags.mCancelable = false;
+      event.mRelatedTarget = mRelatedTarget;
+      return EventDispatcher::Dispatch(mTarget, mContext, &event);
+    }
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISupports>        mTarget;
+  RefPtr<nsPresContext>        mContext;
+  EventMessage                 mEventMessage;
+  nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
+  nsCOMPtr<nsIContent>         mOriginalFocusedContent;
+  nsCOMPtr<EventTarget>        mRelatedTarget;
+};
+
 static nsIDocument*
 GetDocumentHelper(EventTarget* aTarget)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
   if (!node) {
     nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
     return win ? win->GetExtantDoc() : nullptr;
   }
 
   return node->OwnerDoc();
 }
 
+void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage,
+                                     nsIPresShell* aPresShell,
+                                     nsISupports* aTarget,
+                                     nsPIDOMWindowOuter* aCurrentFocusedWindow,
+                                     nsIContent* aCurrentFocusedContent,
+                                     EventTarget* aRelatedTarget)
+{
+  NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+      "Wrong event type for SendFocusInOrOutEvent");
+
+  nsContentUtils::AddScriptRunner(
+      new FocusInOutEvent(
+        aTarget,
+        aEventMessage,
+        aPresShell->GetPresContext(),
+        aCurrentFocusedWindow,
+        aCurrentFocusedContent,
+        aRelatedTarget));
+}
+
 void
 nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
                                      nsIPresShell* aPresShell,
                                      nsIDocument* aDocument,
                                      nsISupports* aTarget,
                                      uint32_t aFocusMethod,
                                      bool aWindowRaised,
                                      bool aIsRefocus,
                                      EventTarget* aRelatedTarget)
 {
   NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
                "Wrong event type for SendFocusOrBlurEvent");
 
   nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
   nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
   nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
+  nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
+  nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
+  nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget);
+  nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ?
+      currentWindow->GetFocusedNode() : nullptr;
 
   // set aRelatedTarget to null if it's not in the same document as eventTarget
   if (eventTargetDoc != relatedTargetDoc) {
     aRelatedTarget = nullptr;
   }
 
   bool dontDispatchEvent =
     eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
@@ -2094,16 +2162,29 @@ nsFocusManager::SendFocusOrBlurEvent(Eve
     }
   }
 #endif
 
   if (!dontDispatchEvent) {
     nsContentUtils::AddScriptRunner(
       new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
                          aWindowRaised, aIsRefocus, aRelatedTarget));
+
+    // Check that the target is not a window or document before firing
+    // focusin/focusout. Other browsers do not fire focusin/focusout on window,
+    // despite being required in the spec, so follow their behavior.
+    //
+    // As for document, we should not even fire focus/blur, but until then, we
+    // need this check. targetDocument should be removed once bug 1228802 is
+    // resolved.
+    if (!targetWindow && !targetDocument) {
+      EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut;
+      SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+          currentWindow, currentFocusedContent, aRelatedTarget);
+    }
   }
 }
 
 void
 nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
                                nsIContent* aContent,
                                uint32_t aFlags)
 {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -292,16 +292,40 @@ protected:
                             nsIDocument* aDocument,
                             nsISupports* aTarget,
                             uint32_t aFocusMethod,
                             bool aWindowRaised,
                             bool aIsRefocus = false,
                             mozilla::dom::EventTarget* aRelatedTarget = nullptr);
 
   /**
+   *  Send a focusin or focusout event
+   *
+   *  aEventMessage should be either eFocusIn or eFocusOut.
+   *
+   *  aTarget is the content the event will fire on (the object that gained
+   *  focus for focusin, the object blurred for focusout).
+   *
+   *  aCurrentFocusedWindow is the window focused before the focus/blur event
+   *  was fired.
+   *
+   *  aCurrentFocusedContent is the content focused before the focus/blur event
+   *  was fired.
+   *
+   *  aRelatedTarget is the content related to the event (the object
+   *  losing focus for focusin, the object getting focus for focusout).
+   */
+  void SendFocusInOrOutEvent(mozilla::EventMessage aEventMessage,
+                             nsIPresShell* aPresShell,
+                             nsISupports* aTarget,
+                             nsPIDOMWindowOuter* aCurrentFocusedWindow,
+                             nsIContent* aCurrentFocusedContent,
+                             mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+  /**
    * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set.
    */
   void ScrollIntoView(nsIPresShell* aPresShell,
                       nsIContent* aContent,
                       uint32_t aFlags);
 
   /**
    * Raises the top-level window aWindow at the widget level.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -789,16 +789,18 @@ GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfetch, "onfetch")
 GK_ATOM(onfinish, "onfinish")
 GK_ATOM(onfocus, "onfocus")
+GK_ATOM(onfocusin, "onfocusin")
+GK_ATOM(onfocusout, "onfocusout")
 GK_ATOM(onfrequencychange, "onfrequencychange")
 GK_ATOM(onfullscreenchange, "onfullscreenchange")
 GK_ATOM(onfullscreenerror, "onfullscreenerror")
 GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
 GK_ATOM(onget, "onget")
 GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(onheadphoneschange, "onheadphoneschange")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1316,30 +1316,32 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
   }
 
   // We could have failed the first time through trying
   // to create the entropy collector, so we should
   // try to get one until we succeed.
 
   gRefCnt++;
 
-  if (gRefCnt == 1) {
+  static bool sFirstTime = true;
+  if (sFirstTime) {
     Preferences::AddIntVarCache(&gMinTimeoutValue,
                                 "dom.min_timeout_value",
                                 DEFAULT_MIN_TIMEOUT_VALUE);
     Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
                                 "dom.min_background_timeout_value",
                                 DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE);
     Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled,
                                  "dom.idle-observers-api.fuzz_time.disabled",
                                  false);
 
     Preferences::AddUintVarCache(&gThrottledIdlePeriodLength,
                                  "dom.idle_period.throttled_length",
                                  DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH);
+    sFirstTime = false;
   }
 
   if (gDumpFile == nullptr) {
     const nsAdoptingCString& fname =
       Preferences::GetCString("browser.dom.window.dump.file");
     if (!fname.IsEmpty()) {
       // if this fails to open, Dump() knows to just go to stdout
       // on null.
--- a/dom/cache/test/mochitest/browser_cache_pb_window.js
+++ b/dom/cache/test/mochitest/browser_cache_pb_window.js
@@ -69,14 +69,13 @@ function test() {
     privateWin.addEventListener('load', function() {
       Promise.all([
         testMatch(privateWin),
         testHas(privateWin),
         testOpen(privateWin),
         testDelete(privateWin),
         testKeys(privateWin)
       ]).then(function() {
-        privateWin.close();
-        finish();
+        BrowserTestUtils.closeWindow(privateWin).then(finish);
       });
     });
   });
 }
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4938,16 +4938,21 @@ CanvasRenderingContext2D::DrawImage(cons
 
   gfx::Rect bounds;
 
   if (NeedToCalculateBounds()) {
     bounds = gfx::Rect(aDx, aDy, aDw, aDh);
     bounds = mTarget->GetTransform().TransformBounds(bounds);
   }
 
+  if (!IsTargetValid()) {
+    gfxCriticalError() << "Unexpected invalid target in a Canvas2d.";
+    return;
+  }
+
   if (srcSurf) {
     gfx::Rect sourceRect(aSx, aSy, aSw, aSh);
     if (element == mCanvasElement) {
       // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll
       // trigger a COW copy of the whole canvas into srcSurf. That's a huge
       // waste if sourceRect doesn't cover the whole canvas.
       // We avoid copying the whole canvas by manually copying just the part
       // that we need.
--- a/dom/canvas/test/test_capture.html
+++ b/dom/canvas/test/test_capture.html
@@ -93,16 +93,29 @@ function checkDrawImageNotCleanRed() {
     .then(() => h.waitForPixelColor(vmanual, h.green, 0, "should still be green"))
     .then(() => h.requestFrame(vmanual))
     .then(() => h.waitForPixelColorTimeout(vmanual, h.red, 0, 1000,
                                            "should not become red"))
     .catch(err => ok(false, "checkDrawImageNotCleanRed failed: ", err))
     .then(() => drawing.stop());
 }
 
+function checkEndedOnStop() {
+  let promises = [vauto, vmanual, vrate].map(elem => {
+    elem.srcObject.getTracks()[0].stop();
+    return new Promise(resolve =>
+      elem.addEventListener("ended", function endedListener(event) {
+        ok(true, "Element " + elem.id + " ended.");
+        resolve();
+        elem.removeEventListener("ended", endedListener);
+      }));
+  });
+  return Promise.all(promises);
+}
+
 function finish() {
   ok(true, 'Test complete.');
   SimpleTest.finish();
 }
 
 function beginTest() {
   h = new CaptureStreamTestHelper2D();
 
@@ -112,16 +125,17 @@ function beginTest() {
   vrate = h.createAndAppendElement('video', 'vrate');
 
   Promise.resolve()
     .then(checkDrawColorInitialRed)
     .then(checkDrawColorGreen)
     .then(checkRequestFrameOrderGuarantee)
     .then(checkDrawColorGreen) // Restore video elements to green.
     .then(checkDrawImageNotCleanRed)
+    .then(checkEndedOnStop)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 beginTest();
 </script>
 
--- a/dom/canvas/test/webgl-mochitest/test_capture.html
+++ b/dom/canvas/test/webgl-mochitest/test_capture.html
@@ -110,16 +110,30 @@ function checkRequestFrameOrderGuarantee
   return Promise.resolve()
     .then(() => h.waitForPixelColor(vmanual, h.red, 0, "should still be red"))
     .then(() => h.drawColor(c, h.green)) // 1. Draw canvas green
     .then(() => h.requestFrame(vmanual)) // 2. Immediately request a frame
     .then(() => h.waitForPixelColor(vmanual, h.green, 0,
                                     "should become green after call order test"))
 }
 
+function checkEndedOnStop() {
+  let promises = [vauto, vmanual, vrate].map(elem => {
+    elem.srcObject.getTracks()[0].stop();
+    return new Promise(resolve =>
+      elem.addEventListener("ended", function endedListener(event) {
+        ok(true, "Element " + elem.id + " ended.");
+        resolve();
+        elem.removeEventListener("ended", endedListener);
+      }));
+  });
+  return Promise.all(promises);
+}
+
+
 function finish() {
   ok(true, 'Test complete.');
   SimpleTest.finish();
 }
 
 function beginTest() {
   h = new CaptureStreamTestHelperWebGL();
 
@@ -177,16 +191,17 @@ function beginTest() {
 
   // Run tests.
 
   Promise.resolve()
     .then(checkClearColorInitialRed)
     .then(checkDrawColorGreen)
     .then(checkClearColorRed)
     .then(checkRequestFrameOrderGuarantee)
+    .then(checkEndedOnStop)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 beginTest();
 </script>
 
--- a/dom/events/ClipboardEvent.cpp
+++ b/dom/events/ClipboardEvent.cpp
@@ -48,16 +48,18 @@ ClipboardEvent::InitClipboardEvent(const
   return rv.StealNSResult();
 }
 
 void
 ClipboardEvent::InitClipboardEvent(const nsAString& aType, bool aCanBubble,
                                    bool aCancelable,
                                    DataTransfer* aClipboardData)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Event::InitEvent(aType, aCanBubble, aCancelable);
   mEvent->AsClipboardEvent()->mClipboardData = aClipboardData;
 }
 
 already_AddRefed<ClipboardEvent>
 ClipboardEvent::Constructor(const GlobalObject& aGlobal,
                             const nsAString& aType,
                             const ClipboardEventInit& aParam,
--- a/dom/events/CommandEvent.cpp
+++ b/dom/events/CommandEvent.cpp
@@ -46,16 +46,18 @@ CommandEvent::GetCommand(nsAString& aCom
 }
 
 NS_IMETHODIMP
 CommandEvent::InitCommandEvent(const nsAString& aTypeArg,
                                bool aCanBubbleArg,
                                bool aCancelableArg,
                                const nsAString& aCommand)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
 
   mEvent->AsCommandEvent()->mCommand = NS_Atomize(aCommand);
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/CompositionEvent.cpp
+++ b/dom/events/CompositionEvent.cpp
@@ -59,16 +59,18 @@ CompositionEvent::GetLocale(nsAString& a
 void
 CompositionEvent::InitCompositionEvent(const nsAString& aType,
                                        bool aCanBubble,
                                        bool aCancelable,
                                        nsGlobalWindow* aView,
                                        const nsAString& aData,
                                        const nsAString& aLocale)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
   mData = aData;
   mLocale = aLocale;
 }
 
 void
 CompositionEvent::GetRanges(TextClauseArray& aRanges)
 {
--- a/dom/events/CustomEvent.cpp
+++ b/dom/events/CustomEvent.cpp
@@ -71,16 +71,18 @@ CustomEvent::WrapObjectInternal(JSContex
 }
 
 NS_IMETHODIMP
 CustomEvent::InitCustomEvent(const nsAString& aType,
                              bool aCanBubble,
                              bool aCancelable,
                              nsIVariant* aDetail)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   AutoJSAPI jsapi;
   NS_ENSURE_STATE(jsapi.Init(GetParentObject()));
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> detail(cx);
 
   if (!aDetail) {
     detail = JS::NullValue();
   } else if (NS_WARN_IF(!VariantToJsval(cx, aDetail, &detail))) {
@@ -97,16 +99,18 @@ CustomEvent::InitCustomEvent(const nsASt
 void
 CustomEvent::InitCustomEvent(JSContext* aCx,
                              const nsAString& aType,
                              bool aCanBubble,
                              bool aCancelable,
                              JS::Handle<JS::Value> aDetail,
                              ErrorResult& aRv)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Event::InitEvent(aType, aCanBubble, aCancelable);
   mDetail = aDetail;
 }
 
 NS_IMETHODIMP
 CustomEvent::GetDetail(nsIVariant** aDetail)
 {
   if (mDetail.isNull()) {
--- a/dom/events/DeviceMotionEvent.cpp
+++ b/dom/events/DeviceMotionEvent.cpp
@@ -46,16 +46,18 @@ DeviceMotionEvent::InitDeviceMotionEvent
                      bool aCanBubble,
                      bool aCancelable,
                      const DeviceAccelerationInit& aAcceleration,
                      const DeviceAccelerationInit& aAccelIncludingGravity,
                      const DeviceRotationRateInit& aRotationRate,
                      Nullable<double> aInterval,
                      Nullable<uint64_t> aTimeStamp)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Event::InitEvent(aType, aCanBubble, aCancelable);
 
   mAcceleration = new DeviceAcceleration(this, aAcceleration.mX,
                                          aAcceleration.mY,
                                          aAcceleration.mZ);
 
   mAccelerationIncludingGravity =
     new DeviceAcceleration(this, aAccelIncludingGravity.mX,
--- a/dom/events/DragEvent.cpp
+++ b/dom/events/DragEvent.cpp
@@ -50,16 +50,18 @@ DragEvent::InitDragEvent(const nsAString
                          bool aCtrlKey,
                          bool aAltKey,
                          bool aShiftKey,
                          bool aMetaKey,
                          uint16_t aButton,
                          EventTarget* aRelatedTarget,
                          DataTransfer* aDataTransfer)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable,
                              aView, aDetail, aScreenX, aScreenY,
                              aClientX, aClientY, aCtrlKey, aAltKey,
                              aShiftKey, aMetaKey, aButton, aRelatedTarget);
   if (mEventIsInternal) {
     mEvent->AsDragEvent()->mDataTransfer = aDataTransfer;
   }
 }
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -498,16 +498,24 @@ FORWARDED_EVENT(blur,
 ERROR_EVENT(error,
             eLoadError,
             EventNameType_All,
             eBasicEventClass)
 FORWARDED_EVENT(focus,
                 eFocus,
                 EventNameType_HTMLXUL,
                 eFocusEventClass)
+FORWARDED_EVENT(focusin,
+                eFocusIn,
+                EventNameType_HTMLXUL,
+                eFocusEventClass)
+FORWARDED_EVENT(focusout,
+                eFocusOut,
+                EventNameType_HTMLXUL,
+                eFocusEventClass)
 FORWARDED_EVENT(load,
                 eLoad,
                 EventNameType_All,
                 eBasicEventClass)
 FORWARDED_EVENT(resize,
                 eResize,
                 EventNameType_All,
                 eBasicEventClass)
--- a/dom/events/FocusEvent.cpp
+++ b/dom/events/FocusEvent.cpp
@@ -44,16 +44,18 @@ FocusEvent::GetRelatedTarget()
 void
 FocusEvent::InitFocusEvent(const nsAString& aType,
                            bool aCanBubble,
                            bool aCancelable,
                            nsGlobalWindow* aView,
                            int32_t aDetail,
                            EventTarget* aRelatedTarget)
 {
+  MOZ_ASSERT(!mEvent->mFlags.mIsBeingDispatched);
+
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
   mEvent->AsFocusEvent()->mRelatedTarget = aRelatedTarget;
 }
 
 already_AddRefed<FocusEvent>
 FocusEvent::Constructor(const GlobalObject& aGlobal,
                         const nsAString& aType,
                         const FocusEventInit& aParam,
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -332,16 +332,18 @@ KeyboardEvent::InitKeyEvent(const nsAStr
                             mozIDOMWindow* aView,
                             bool aCtrlKey,
                             bool aAltKey,
                             bool aShiftKey,
                             bool aMetaKey,
                             uint32_t aKeyCode,
                             uint32_t aCharCode)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
 
   WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
   keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
   keyEvent->mKeyCode = aKeyCode;
   keyEvent->mCharCode = aCharCode;
 
   return NS_OK;
--- a/dom/events/MessageEvent.cpp
+++ b/dom/events/MessageEvent.cpp
@@ -141,16 +141,18 @@ void
 MessageEvent::InitMessageEvent(JSContext* aCx, const nsAString& aType,
                                bool aCanBubble, bool aCancelable,
                                JS::Handle<JS::Value> aData,
                                const nsAString& aOrigin,
                                const nsAString& aLastEventId,
                                const Nullable<WindowProxyOrMessagePort>& aSource,
                                const Sequence<OwningNonNull<MessagePort>>& aPorts)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Event::InitEvent(aType, aCanBubble, aCancelable);
   mData = aData;
   mozilla::HoldJSObjects(this);
   mOrigin = aOrigin;
   mLastEventId = aLastEventId;
 
   mWindowSource = nullptr;
   mPortSource = nullptr;
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -62,16 +62,18 @@ MouseEvent::InitMouseEvent(const nsAStri
                            int32_t aClientY,
                            bool aCtrlKey,
                            bool aAltKey,
                            bool aShiftKey,
                            bool aMetaKey,
                            uint16_t aButton,
                            EventTarget* aRelatedTarget)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
 
   switch(mEvent->mClass) {
     case eMouseEventClass:
     case eMouseScrollEventClass:
     case eWheelEventClass:
     case eDragEventClass:
     case ePointerEventClass:
@@ -133,16 +135,18 @@ MouseEvent::InitMouseEvent(const nsAStri
                            int32_t aScreenX,
                            int32_t aScreenY,
                            int32_t aClientX,
                            int32_t aClientY,
                            int16_t aButton,
                            EventTarget* aRelatedTarget,
                            const nsAString& aModifiersList)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Modifiers modifiers = ComputeModifierState(aModifiersList);
 
   InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail,
                  aScreenX, aScreenY, aClientX, aClientY,
                  (modifiers & MODIFIER_CONTROL) != 0,
                  (modifiers & MODIFIER_ALT) != 0,
                  (modifiers & MODIFIER_SHIFT) != 0,
                  (modifiers & MODIFIER_META) != 0,
@@ -205,16 +209,18 @@ MouseEvent::InitNSMouseEvent(const nsASt
                              bool aAltKey,
                              bool aShiftKey,
                              bool aMetaKey,
                              uint16_t aButton,
                              EventTarget* aRelatedTarget,
                              float aPressure,
                              uint16_t aInputSource)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable,
                              aView, aDetail, aScreenX, aScreenY,
                              aClientX, aClientY,
                              aCtrlKey, aAltKey, aShiftKey,
                              aMetaKey, aButton, aRelatedTarget);
 
   WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase();
   mouseEventBase->pressure = aPressure;
--- a/dom/events/MouseScrollEvent.cpp
+++ b/dom/events/MouseScrollEvent.cpp
@@ -51,16 +51,18 @@ MouseScrollEvent::InitMouseScrollEvent(c
                                        bool aCtrlKey,
                                        bool aAltKey,
                                        bool aShiftKey,
                                        bool aMetaKey,
                                        uint16_t aButton,
                                        EventTarget* aRelatedTarget,
                                        int32_t aAxis)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail,
                              aScreenX, aScreenY, aClientX, aClientY,
                              aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton,
                              aRelatedTarget);
   mEvent->AsMouseScrollEvent()->mIsHorizontal =
     (aAxis == nsIDOMMouseScrollEvent::HORIZONTAL_AXIS);
 }
 
--- a/dom/events/MutationEvent.cpp
+++ b/dom/events/MutationEvent.cpp
@@ -91,16 +91,18 @@ MutationEvent::InitMutationEvent(const n
                                  bool aCanBubbleArg,
                                  bool aCancelableArg,
                                  nsIDOMNode* aRelatedNodeArg,
                                  const nsAString& aPrevValueArg,
                                  const nsAString& aNewValueArg,
                                  const nsAString& aAttrNameArg,
                                  uint16_t aAttrChangeArg)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   Event::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
 
   InternalMutationEvent* mutation = mEvent->AsMutationEvent();
   mutation->mRelatedNode = aRelatedNodeArg;
   if (!aPrevValueArg.IsEmpty())
     mutation->mPrevAttrValue = NS_Atomize(aPrevValueArg);
   if (!aNewValueArg.IsEmpty())
     mutation->mNewAttrValue = NS_Atomize(aNewValueArg);
--- a/dom/events/ScrollAreaEvent.cpp
+++ b/dom/events/ScrollAreaEvent.cpp
@@ -34,16 +34,18 @@ ScrollAreaEvent::InitScrollAreaEvent(con
                                      bool aCancelable,
                                      nsGlobalWindow* aView,
                                      int32_t aDetail,
                                      float aX,
                                      float aY,
                                      float aWidth,
                                      float aHeight)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   UIEvent::InitUIEvent(aEventType, aCanBubble, aCancelable, aView, aDetail);
   mClientArea->SetRect(aX, aY, aWidth, aHeight);
 }
 
 NS_IMETHODIMP_(void)
 ScrollAreaEvent::Serialize(IPC::Message* aMsg,
                            bool aSerializeInterfaceType)
 {
--- a/dom/events/SimpleGestureEvent.cpp
+++ b/dom/events/SimpleGestureEvent.cpp
@@ -119,16 +119,18 @@ SimpleGestureEvent::InitSimpleGestureEve
                                            bool aMetaKeyArg,
                                            uint16_t aButton,
                                            EventTarget* aRelatedTarget,
                                            uint32_t aAllowedDirectionsArg,
                                            uint32_t aDirectionArg,
                                            double aDeltaArg,
                                            uint32_t aClickCountArg)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   MouseEvent::InitMouseEvent(aTypeArg, aCanBubbleArg, aCancelableArg,
                              aViewArg, aDetailArg,
                              aScreenX, aScreenY, aClientX, aClientY,
                              aCtrlKeyArg, aAltKeyArg, aShiftKeyArg,
                              aMetaKeyArg, aButton, aRelatedTarget);
 
   WidgetSimpleGestureEvent* simpleGestureEvent = mEvent->AsSimpleGestureEvent();
   simpleGestureEvent->mAllowedDirections = aAllowedDirectionsArg;
--- a/dom/events/StorageEvent.cpp
+++ b/dom/events/StorageEvent.cpp
@@ -82,16 +82,18 @@ StorageEvent::Constructor(const GlobalOb
 void
 StorageEvent::InitStorageEvent(const nsAString& aType, bool aCanBubble,
                                bool aCancelable, const nsAString& aKey,
                                const nsAString& aOldValue,
                                const nsAString& aNewValue,
                                const nsAString& aURL,
                                DOMStorage* aStorageArea)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   InitEvent(aType, aCanBubble, aCancelable);
   mKey = aKey;
   mOldValue = aOldValue;
   mNewValue = aNewValue;
   mUrl = aURL;
   mStorageArea = aStorageArea;
 }
 
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -89,16 +89,18 @@ TouchEvent::InitTouchEvent(const nsAStri
                            bool aCtrlKey,
                            bool aAltKey,
                            bool aShiftKey,
                            bool aMetaKey,
                            TouchList* aTouches,
                            TouchList* aTargetTouches,
                            TouchList* aChangedTouches)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
   mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey,
                                              aShiftKey, aMetaKey);
   mTouches = aTouches;
   mTargetTouches = aTargetTouches;
   mChangedTouches = aChangedTouches;
 }
 
--- a/dom/events/UIEvent.cpp
+++ b/dom/events/UIEvent.cpp
@@ -164,16 +164,18 @@ UIEvent::InitUIEvent(const nsAString& ty
 
 NS_IMETHODIMP
 UIEvent::InitUIEvent(const nsAString& typeArg,
                      bool canBubbleArg,
                      bool cancelableArg,
                      mozIDOMWindow* viewArg,
                      int32_t detailArg)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   Event::InitEvent(typeArg, canBubbleArg, cancelableArg);
 
   mDetail = detailArg;
   mView = viewArg ? nsPIDOMWindowInner::From(viewArg)->GetOuterWindow() :
                     nullptr;
 
   return NS_OK;
 }
--- a/dom/events/WheelEvent.cpp
+++ b/dom/events/WheelEvent.cpp
@@ -55,16 +55,18 @@ WheelEvent::InitWheelEvent(const nsAStri
                            uint16_t aButton,
                            EventTarget* aRelatedTarget,
                            const nsAString& aModifiersList,
                            double aDeltaX,
                            double aDeltaY,
                            double aDeltaZ,
                            uint32_t aDeltaMode)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   MouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail,
                              aScreenX, aScreenY, aClientX, aClientY, aButton,
                              aRelatedTarget, aModifiersList);
 
   WidgetWheelEvent* wheelEvent = mEvent->AsWheelEvent();
   wheelEvent->mDeltaX = aDeltaX;
   wheelEvent->mDeltaY = aDeltaY;
   wheelEvent->mDeltaZ = aDeltaZ;
--- a/dom/events/XULCommandEvent.cpp
+++ b/dom/events/XULCommandEvent.cpp
@@ -108,16 +108,18 @@ XULCommandEvent::InitCommandEvent(const 
                                   mozIDOMWindow* aView,
                                   int32_t aDetail,
                                   bool aCtrlKey,
                                   bool aAltKey,
                                   bool aShiftKey,
                                   bool aMetaKey,
                                   nsIDOMEvent* aSourceEvent)
 {
+  NS_ENSURE_TRUE(!mEvent->mFlags.mIsBeingDispatched, NS_OK);
+
   auto* view = nsGlobalWindow::Cast(nsPIDOMWindowInner::From(aView));
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, view, aDetail);
 
   mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey,
                                              aShiftKey, aMetaKey);
   mSourceEvent = aSourceEvent;
 
   return NS_OK;
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -197,8 +197,9 @@ support-files =
 [test_bug1013412.html]
 skip-if = buildapp == 'b2g' # no wheel events on b2g
 [test_dom_activate_event.html]
 [test_bug1264380.html]
 run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode.
 [test_passive_listeners.html]
 [test_paste_image.html]
 [test_messageEvent_init.html]
+[test_bug687787.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug687787.html
@@ -0,0 +1,617 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+-->
+<head>
+  <title>Test for Bug 687787</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=687787">Mozilla Bug 687787</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var content = document.getElementById('content');
+var eventStack = [];
+
+function _callback(e){
+    var event = {'type' : e.type, 'target' : e.target, 'relatedTarget' : e.relatedTarget }
+    eventStack.push(event);
+}
+
+function clearEventStack(){
+    eventStack = [];
+}
+
+window.addEventListener("focus", _callback, true);
+window.addEventListener("focusin", _callback, true);
+window.addEventListener("focusout", _callback, true);
+window.addEventListener("blur", _callback, true);
+
+function CompareEventToExpected(e, expected) {
+    if (expected == null || e == null)
+        return false;
+    if (e.type == expected.type && e.target == expected.target && e.relatedTarget == expected.relatedTarget)
+        return true;
+    return false;
+}
+
+function TestEventOrderNormal() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(input3);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'focusin',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'blur',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focusout',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focus',
+        'target' : input3,
+        'relatedTarget' : input2},
+        {'type' : 'focusin',
+        'target' : input3,
+        'relatedTarget' : input2},
+    ]
+
+    input1.focus();
+    clearEventStack();
+
+    input2.focus();
+    input3.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestEventOrderNormalFiresAtRightTime() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+
+    input1.onblur = function(e)
+    {
+        ok(document.activeElement == document.body, 'input1: not focused when blur fires')
+    }
+
+    input1.addEventListener('focusout', function(e)
+    {
+        ok(document.activeElement == document.body, 'input1: not focused when focusout fires')
+    });
+
+    input2.onfocus = function(e)
+    {
+        ok(document.activeElement == input2, 'input2: focused when focus fires')
+    }
+
+    input2.addEventListener('focusin', function(e)
+    {
+        ok(document.activeElement == input2, 'input2: focused when focusin fires')
+    });
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'focusin',
+        'target' : input2,
+        'relatedTarget' : input1},
+    ]
+
+    input1.focus();
+    clearEventStack();
+
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestFocusOutRedirectsFocus() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+    input1.addEventListener('focusout', function () {
+        input3.focus();
+    });
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(input3);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input3,
+        'relatedTarget' : null},
+        {'type' : 'focusin',
+        'target' : input3,
+        'relatedTarget' : null},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestFocusInRedirectsFocus() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+    input2.addEventListener('focusin', function () {
+        input3.focus();
+    });
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(input3);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'focusin',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'blur',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focusout',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focus',
+        'target' : input3,
+        'relatedTarget' : input2},
+        {'type' : 'focusin',
+        'target' : input3,
+        'relatedTarget' : input2},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestBlurRedirectsFocus() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+    input1.onblur = function () {
+        input3.focus();
+    }
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(input3);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input3,
+        'relatedTarget' : null},
+        {'type' : 'focusin',
+        'target' : input3,
+        'relatedTarget' : null},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestFocusRedirectsFocus() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var input3 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    input3.setAttribute('id', 'input3');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+    input3.setAttribute('type', 'text');
+    input2.onfocus = function () {
+        input3.focus();
+    }
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(input3);
+    content.style.display = 'block'
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : input1},
+        {'type' : 'blur',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focusout',
+        'target' : input2,
+        'relatedTarget' : input3},
+        {'type' : 'focus',
+        'target' : input3,
+        'relatedTarget' : input2},
+        {'type' : 'focusin',
+        'target' : input3,
+        'relatedTarget' : input2},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestEventOrderDifferentDocument() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var iframe1 = document.createElement('iframe');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    iframe1.setAttribute('id', 'iframe1');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+
+    content.appendChild(input1);
+    content.appendChild(iframe1);
+    iframe1.contentDocument.body.appendChild(input2);
+    content.style.display = 'block'
+
+    iframe1.contentDocument.addEventListener("focus", _callback, true);
+    iframe1.contentDocument.addEventListener("focusin", _callback, true);
+    iframe1.contentDocument.addEventListener("focusout", _callback, true);
+    iframe1.contentDocument.addEventListener("blur", _callback, true);
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : null},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : null},
+        {'type' : 'blur',
+        'target' : document,
+        'relatedTarget' : null},
+        {'type' : 'blur',
+        'target' : window,
+        'relatedTarget' : null},
+        {'type' : 'focus',
+        'target' : iframe1.contentDocument,
+        'relatedTarget' : null},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : null},
+        {'type' : 'focusin',
+        'target' : input2,
+        'relatedTarget' : null},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+
+function TestFocusOutMovesTarget() {
+
+    var input1 = document.createElement('input');
+    var input2 = document.createElement('input');
+    var iframe1 = document.createElement('iframe');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input2.setAttribute('id', 'input2');
+    iframe1.setAttribute('id', 'iframe1');
+    input1.setAttribute('type', 'text');
+    input2.setAttribute('type', 'text');
+
+    input1.addEventListener('focusout', function () {
+        iframe1.contentDocument.body.appendChild(input2);
+    });
+
+    content.appendChild(input1);
+    content.appendChild(input2);
+    content.appendChild(iframe1);
+    content.style.display = 'block'
+
+    iframe1.contentDocument.addEventListener("focus", _callback, true);
+    iframe1.contentDocument.addEventListener("focusin", _callback, true);
+    iframe1.contentDocument.addEventListener("focusout", _callback, true);
+    iframe1.contentDocument.addEventListener("blur", _callback, true);
+
+    expectedEventOrder = [
+        {'type' : 'blur',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focusout',
+        'target' : input1,
+        'relatedTarget' : input2},
+        {'type' : 'focus',
+        'target' : input2,
+        'relatedTarget' : null},
+        {'type' : 'focusin',
+        'target' : input2,
+        'relatedTarget' : null},
+    ]
+
+    input1.focus();
+    clearEventStack();
+    input2.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+function TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput() {
+
+    var input1 = document.createElement('input');
+    var content = document.getElementById('content');
+
+    input1.setAttribute('id', 'input1');
+    input1.setAttribute('type', 'text');
+
+    content.appendChild(input1);
+
+    expectedEventOrder = [
+        {'type' : 'focus',
+        'target' : document,
+        'relatedTarget' : null},
+        {'type' : 'focus',
+        'target' : window,
+        'relatedTarget' : null},
+        {'type' : 'focus',
+        'target' : input1,
+        'relatedTarget' : null},
+        {'type' : 'focusin',
+        'target' : input1,
+        'relatedTarget' : null},
+    ]
+
+    window.blur();
+    clearEventStack();
+    input1.focus();
+
+    for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+        ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+                + 'Expected ('
+                    + expectedEventOrder[i].type + ','
+                    + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+                    + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+                + 'Actual ('
+                    + eventStack[i].type + ','
+                    + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+                    + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+    }
+
+    content.innerHTML = '';
+}
+
+TestEventOrderNormal();
+TestEventOrderNormalFiresAtRightTime();
+TestFocusOutRedirectsFocus();
+TestFocusInRedirectsFocus();
+TestBlurRedirectsFocus();
+TestFocusRedirectsFocus();
+TestFocusOutMovesTarget();
+TestEventOrderDifferentDocument();
+TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -115,16 +115,18 @@ public:
     if (mOwningElement->IsWriteOnly()) {
       return;
     }
 
     if (mOwningElement->IsContextCleanForFrameCapture()) {
       return;
     }
 
+    mOwningElement->ProcessDestroyedFrameListeners();
+
     if (!mOwningElement->IsFrameCaptureRequested()) {
       return;
     }
 
     RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
     if (!snapshot) {
       return;
     }
@@ -645,16 +647,59 @@ PrintCallback*
 HTMLCanvasElement::GetMozPrintCallback() const
 {
   if (mOriginalCanvas) {
     return mOriginalCanvas->GetMozPrintCallback();
   }
   return mPrintCallback;
 }
 
+class CanvasCaptureTrackSource : public MediaStreamTrackSource
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CanvasCaptureTrackSource,
+                                           MediaStreamTrackSource)
+
+  CanvasCaptureTrackSource(nsIPrincipal* aPrincipal,
+                           CanvasCaptureMediaStream* aCaptureStream)
+    : MediaStreamTrackSource(aPrincipal, nsString())
+    , mCaptureStream(aCaptureStream) {}
+
+  MediaSourceEnum GetMediaSource() const override
+  {
+    return MediaSourceEnum::Other;
+  }
+
+  void Stop() override
+  {
+    if (!mCaptureStream) {
+      NS_ERROR("No stream");
+      return;
+    }
+
+    mCaptureStream->StopCapture();
+  }
+
+private:
+  virtual ~CanvasCaptureTrackSource() {}
+
+  RefPtr<CanvasCaptureMediaStream> mCaptureStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(CanvasCaptureTrackSource,
+                         MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(CanvasCaptureTrackSource,
+                          MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureTrackSource,
+                                   MediaStreamTrackSource,
+                                   mCaptureStream)
+
 already_AddRefed<CanvasCaptureMediaStream>
 HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
                                  ErrorResult& aRv)
 {
   if (IsWriteOnly()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
@@ -683,17 +728,17 @@ HTMLCanvasElement::CaptureStream(const O
     stream->Init(aFrameRate, videoTrackId, principal);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   RefPtr<MediaStreamTrack> track =
   stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
-                         new BasicUnstoppableTrackSource(principal));
+                         new CanvasCaptureTrackSource(principal, stream));
   stream->AddTrackInternal(track);
 
   rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
@@ -1194,39 +1239,49 @@ HTMLCanvasElement::IsFrameCaptureRequest
     if (listener->FrameCaptureRequested()) {
       return true;
     }
   }
   return false;
 }
 
 void
-HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
+HTMLCanvasElement::ProcessDestroyedFrameListeners()
 {
-  RefPtr<SourceSurface> surface = aSurface;
-  RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface);
-
   // Loop backwards to allow removing elements in the loop.
   for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
     WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
     if (!listener) {
       // listener was destroyed. Remove it from the list.
       mRequestedFrameListeners.RemoveElementAt(i);
       continue;
     }
-
-    RefPtr<Image> imageRefCopy = image.get();
-    listener->NewFrame(imageRefCopy.forget());
   }
 
   if (mRequestedFrameListeners.IsEmpty()) {
     mRequestedFrameRefreshObserver->Unregister();
   }
 }
 
+void
+HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
+{
+  RefPtr<SourceSurface> surface = aSurface;
+  RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), surface);
+
+  for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
+    if (!listener) {
+      continue;
+    }
+
+    RefPtr<Image> imageRefCopy = image.get();
+    listener->NewFrame(imageRefCopy.forget());
+  }
+}
+
 already_AddRefed<SourceSurface>
 HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
 {
   if (!mCurrentContext)
     return nullptr;
 
   return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
 }
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -272,16 +272,22 @@ public:
 
   /*
    * Returns true when there is at least one registered FrameCaptureListener
    * that has requested a frame capture.
    */
   bool IsFrameCaptureRequested() const;
 
   /*
+   * Processes destroyed FrameCaptureListeners and removes them if necessary.
+   * Should there be none left, the FrameRefreshObserver will be unregistered.
+   */
+  void ProcessDestroyedFrameListeners();
+
+  /*
    * Called by the RefreshDriver hook when a frame has been captured.
    * Makes a copy of the provided surface and hands it to all
    * FrameCaptureListeners having requested frame capture.
    */
   void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface);
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -863,21 +863,18 @@ NS_IMETHODIMP HTMLMediaElement::GetMozAu
   *aAutoplayEnabled = mAutoplayEnabled;
 
   return NS_OK;
 }
 
 bool
 HTMLMediaElement::Ended()
 {
-  if (MediaStream* stream = GetSrcMediaStream()) {
-    return stream->IsFinished();
-  }
-
-  return mDecoder && mDecoder->IsEnded();
+  return (mDecoder && mDecoder->IsEnded()) ||
+         (mSrcStream && !mSrcStream->Active());
 }
 
 NS_IMETHODIMP HTMLMediaElement::GetEnded(bool* aEnded)
 {
   *aEnded = Ended();
   return NS_OK;
 }
 
@@ -1357,17 +1354,17 @@ void HTMLMediaElement::NotifyMediaTrackD
         // for instance if the MediaInputPort was destroyed in the same
         // iteration as it was added.
         MediaStreamTrack* outputTrack = ms.mStream->FindOwnedDOMTrack(
             ms.mTrackPorts[i].second()->GetDestination(),
             ms.mTrackPorts[i].second()->GetDestinationTrackId());
         MOZ_ASSERT(outputTrack);
         if (outputTrack) {
           NS_DispatchToMainThread(
-            NewRunnableMethod(outputTrack, &MediaStreamTrack::NotifyEnded));
+            NewRunnableMethod(outputTrack, &MediaStreamTrack::OverrideEnded));
         }
 
         ms.mTrackPorts[i].second()->Destroy();
         ms.mTrackPorts.RemoveElementAt(i);
         break;
       }
     }
 #ifdef DEBUG
@@ -1395,16 +1392,45 @@ void HTMLMediaElement::NotifyMediaStream
     // We are a video element and HasVideo() changed so update the screen
     // wakelock
     NotifyOwnerDocumentActivityChanged();
   }
 
   mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 }
 
+void
+HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
+                                           TrackID aDestinationTrackID)
+{
+  for (OutputMediaStream& ms : mOutputStreams) {
+    if (!ms.mCapturingMediaStream) {
+      continue;
+    }
+
+    if (ms.mStream != aOwningStream) {
+      continue;
+    }
+
+    for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
+      MediaInputPort* port = ms.mTrackPorts[i].second();
+      if (port->GetDestinationTrackId() != aDestinationTrackID) {
+        continue;
+      }
+
+      port->Destroy();
+      ms.mTrackPorts.RemoveElementAt(i);
+      return;
+    }
+  }
+
+  // An output track ended but its port is already gone.
+  // It was probably cleared by the removal of the source MediaTrack.
+}
+
 void HTMLMediaElement::LoadFromSourceChildren()
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   NS_ASSERTION(mIsLoadingFromSourceChildren,
                "Must remember we're loading from source children");
 
   nsIDocument* parentDoc = OwnerDoc()->GetParentDocument();
@@ -2201,90 +2227,98 @@ class HTMLMediaElement::StreamCaptureTra
   public MediaStreamTrackSource,
   public MediaStreamTrackSource::Sink
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
                                            MediaStreamTrackSource)
 
-  explicit StreamCaptureTrackSource(MediaStreamTrackSource* aCapturedTrackSource)
+  StreamCaptureTrackSource(HTMLMediaElement* aElement,
+                           MediaStreamTrackSource* aCapturedTrackSource,
+                           DOMMediaStream* aOwningStream,
+                           TrackID aDestinationTrackID)
     : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(),
-                             true,
                              nsString())
+    , mElement(aElement)
     , mCapturedTrackSource(aCapturedTrackSource)
+    , mOwningStream(aOwningStream)
+    , mDestinationTrackID(aDestinationTrackID)
   {
   }
 
   void Destroy() override
   {
-    MOZ_ASSERT(mCapturedTrackSource);
+    if (mCapturedTrackSource) {
+      mCapturedTrackSource->UnregisterSink(this);
+      mCapturedTrackSource = nullptr;
+    }
   }
 
   MediaSourceEnum GetMediaSource() const override
   {
     return MediaSourceEnum::Other;
   }
 
   CORSMode GetCORSMode() const override
   {
     return mCapturedTrackSource->GetCORSMode();
   }
 
-  already_AddRefed<PledgeVoid>
-  ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints) override
-  {
-    RefPtr<PledgeVoid> p = new PledgeVoid();
-    p->Reject(new dom::MediaStreamError(aWindow,
-                                        NS_LITERAL_STRING("OverconstrainedError"),
-                                        NS_LITERAL_STRING("")));
-    return p.forget();
-  }
-
   void Stop() override
   {
-    NS_ERROR("We're reporting remote=true to not be stoppable. "
-             "Stop() should not be called.");
+    if (mElement && mElement->mSrcStream) {
+      // Only notify if we're still playing the source stream. GC might have
+      // cleared it before the track sources.
+      mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID);
+    }
+    mElement = nullptr;
+    mOwningStream = nullptr;
+
+    Destroy();
   }
 
   void PrincipalChanged() override
   {
     mPrincipal = mCapturedTrackSource->GetPrincipal();
     MediaStreamTrackSource::PrincipalChanged();
   }
 
 private:
   virtual ~StreamCaptureTrackSource() {}
 
+  RefPtr<HTMLMediaElement> mElement;
   RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
+  RefPtr<DOMMediaStream> mOwningStream;
+  TrackID mDestinationTrackID;
 };
 
 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
                          MediaStreamTrackSource)
 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
                           MediaStreamTrackSource)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource)
 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
                                    MediaStreamTrackSource,
-                                   mCapturedTrackSource)
+                                   mElement,
+                                   mCapturedTrackSource,
+                                   mOwningStream)
 
 class HTMLMediaElement::DecoderCaptureTrackSource :
   public MediaStreamTrackSource,
   public DecoderPrincipalChangeObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource,
                                            MediaStreamTrackSource)
 
   explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement)
     : MediaStreamTrackSource(nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(),
-                             true,
                              nsString())
     , mElement(aElement)
   {
     MOZ_ASSERT(mElement);
     mElement->AddDecoderPrincipalChangeObserver(this);
   }
 
   void Destroy() override
@@ -2300,31 +2334,22 @@ public:
     return MediaSourceEnum::Other;
   }
 
   CORSMode GetCORSMode() const override
   {
     return mElement->GetCORSMode();
   }
 
-  already_AddRefed<PledgeVoid>
-  ApplyConstraints(nsPIDOMWindowInner* aWindow,
-                   const dom::MediaTrackConstraints& aConstraints) override
-  {
-    RefPtr<PledgeVoid> p = new PledgeVoid();
-    p->Reject(new dom::MediaStreamError(aWindow,
-                                        NS_LITERAL_STRING("OverconstrainedError"),
-                                        NS_LITERAL_STRING("")));
-    return p.forget();
-  }
-
   void Stop() override
   {
-    NS_ERROR("We're reporting remote=true to not be stoppable. "
-             "Stop() should not be called.");
+    // We don't notify the source that a track was stopped since it will keep
+    // producing tracks until the element ends. The decoder also needs the
+    // tracks it created to be live at the source since the decoder's clock is
+    // based on MediaStreams during capture.
   }
 
   void NotifyDecoderPrincipalChanged() override
   {
     nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
     if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
       PrincipalChanged();
     }
@@ -2457,17 +2482,20 @@ HTMLMediaElement::AddCaptureMediaTrackTo
   for (auto pair : aOutputStream.mTrackPorts) {
     MOZ_ASSERT(pair.first() != aTrack->GetId(),
                "Captured track already captured to output stream");
   }
 #endif
 
   TrackID destinationTrackID = aOutputStream.mNextAvailableTrackID++;
   RefPtr<MediaStreamTrackSource> source =
-    new StreamCaptureTrackSource(&inputTrack->GetSource());
+    new StreamCaptureTrackSource(this,
+                                 &inputTrack->GetSource(),
+                                 aOutputStream.mStream,
+                                 destinationTrackID);
 
   MediaSegment::Type type = inputTrack->AsAudioStreamTrack()
                           ? MediaSegment::AUDIO
                           : MediaSegment::VIDEO;
 
   RefPtr<MediaStreamTrack> track =
     aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
 
@@ -2521,16 +2549,17 @@ HTMLMediaElement::CaptureStreamInternal(
   if (!mOutputStreams.IsEmpty() &&
       aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
     return nullptr;
   }
 
   OutputMediaStream* out = mOutputStreams.AppendElement();
   MediaStreamTrackSourceGetter* getter = new CaptureStreamTrackSourceGetter(this);
   out->mStream = DOMMediaStream::CreateTrackUnionStreamAsInput(window, aGraph, getter);
+  out->mStream->SetInactiveOnFinish();
   out->mFinishWhenEnded = aFinishWhenEnded;
   out->mCapturingAudioOnly = aCaptureAudio;
 
   if (aCaptureAudio) {
     if (mSrcStream) {
       // We don't support applying volume and mute to the captured stream, when
       // capturing a MediaStream.
       nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
@@ -3865,35 +3894,23 @@ public:
     WatchTarget(aName),
     mElement(aElement),
     mHaveCurrentData(false),
     mBlocked(false),
     mFinished(false),
     mMutex(aName),
     mPendingNotifyOutput(false)
   {}
-  void Forget() { mElement = nullptr; }
+  void Forget()
+  {
+    mElement = nullptr;
+    NotifyWatchers();
+  }
 
   // Main thread
-  void DoNotifyFinished()
-  {
-    mFinished = true;
-    if (mElement) {
-      RefPtr<HTMLMediaElement> deathGrip = mElement;
-
-      // Update NextFrameStatus() to move to NEXT_FRAME_UNAVAILABLE and
-      // HAVE_CURRENT_DATA.
-      mElement = nullptr;
-      // NotifyWatchers before calling PlaybackEnded since PlaybackEnded
-      // can remove watchers.
-      NotifyWatchers();
-
-      deathGrip->PlaybackEnded();
-    }
-  }
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus()
   {
     if (!mElement || !mHaveCurrentData || mFinished) {
       return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
     }
     return mBlocked
         ? MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
@@ -3939,25 +3956,16 @@ public:
     nsCOMPtr<nsIRunnable> event;
     if (aBlocked == BLOCKED) {
       event = NewRunnableMethod(this, &StreamListener::DoNotifyBlocked);
     } else {
       event = NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
     }
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
-  virtual void NotifyEvent(MediaStreamGraph* aGraph,
-                           MediaStreamGraphEvent event) override
-  {
-    if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
-      nsCOMPtr<nsIRunnable> event =
-        NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
-    }
-  }
   virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) override
   {
     MutexAutoLock lock(mMutex);
     nsCOMPtr<nsIRunnable> event =
       NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   virtual void NotifyOutput(MediaStreamGraph* aGraph,
@@ -4014,16 +4022,34 @@ public:
     mElement->NotifyMediaStreamTrackAdded(aTrack);
   }
 
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
   {
     mElement->NotifyMediaStreamTrackRemoved(aTrack);
   }
 
+  void NotifyActive() override
+  {
+    LOG(LogLevel::Debug, ("%p, mSrcStream %p became active",
+                          mElement, mElement->mSrcStream.get()));
+    mElement->CheckAutoplayDataReady();
+  }
+
+  void NotifyInactive() override
+  {
+    LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive",
+                          mElement, mElement->mSrcStream.get()));
+    MOZ_ASSERT(!mElement->mSrcStream->Active());
+    if (mElement->mMediaStreamListener) {
+      mElement->mMediaStreamListener->Forget();
+    }
+    mElement->PlaybackEnded();
+  }
+
 protected:
   HTMLMediaElement* const mElement;
 };
 
 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
 {
   if (!mSrcStream) {
     return;
@@ -4242,20 +4268,20 @@ HTMLMediaElement::NotifyMediaStreamTrack
                         this, aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
                         NS_ConvertUTF16toUTF8(id).get()));
 
   if (MediaTrack* t = AudioTracks()->GetTrackById(id)) {
     AudioTracks()->RemoveTrack(t);
   } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) {
     VideoTracks()->RemoveTrack(t);
   } else {
-    // XXX (bug 1208328) Uncomment this when DOMMediaStream doesn't call
-    // NotifyTrackRemoved multiple times for the same track, i.e., when it
-    // implements the "addtrack" and "removetrack" events.
-    // NS_ASSERTION(false, "MediaStreamTrack ended but did not exist in track lists");
+    NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(),
+                 "MediaStreamTrack ended but did not exist in track lists. "
+                 "This is only allowed if a video element ends and we are an "
+                 "audio element.");
     return;
   }
 }
 
 
 void HTMLMediaElement::ProcessMediaFragmentURI()
 {
   nsMediaFragmentURIParser parser(mLoadingSrc);
@@ -4475,16 +4501,22 @@ void HTMLMediaElement::PlaybackEnded()
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
     SetCurrentTime(0);
     return;
   }
 
   Pause();
 
+  if (mSrcStream) {
+    // A MediaStream that goes from inactive to active shall be eligible for
+    // autoplay again according to the mediacapture-main spec.
+    mAutoplaying = true;
+  }
+
   FireTimeUpdate(false);
   DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
 }
 
 void HTMLMediaElement::SeekStarted()
 {
   DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
 }
@@ -4916,19 +4948,20 @@ void HTMLMediaElement::ChangeNetworkStat
   // Changing mNetworkState affects AddRemoveSelfReference().
   AddRemoveSelfReference();
 }
 
 bool HTMLMediaElement::CanActivateAutoplay()
 {
   // For stream inputs, we activate autoplay on HAVE_NOTHING because
   // this element itself might be blocking the stream from making progress by
-  // being paused. We also activate autopaly when playing a media source since
-  // the data download is controlled by the script and there is no way to
-  // evaluate MediaDecoder::CanPlayThrough().
+  // being paused. We only check that it has data by checking its active state.
+  // We also activate autoplay when playing a media source since the data
+  // download is controlled by the script and there is no way to evaluate
+  // MediaDecoder::CanPlayThrough().
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) || !mAutoplayEnabled) {
     return false;
   }
 
   if (!mAutoplaying) {
     return false;
   }
@@ -4942,17 +4975,18 @@ bool HTMLMediaElement::CanActivateAutopl
   }
 
   if (mPausedForInactiveDocumentOrChannel) {
     return false;
   }
 
   bool hasData =
     (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
-    mSrcStream || mMediaSource;
+    (mSrcStream && mSrcStream->Active()) ||
+    mMediaSource;
 
   return hasData;
 }
 
 void HTMLMediaElement::CheckAutoplayDataReady()
 {
   if (!CanActivateAutoplay()) {
     return;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -350,16 +350,23 @@ public:
    */
   void NotifyMediaTrackDisabled(MediaTrack* aTrack);
 
   /**
    * Called when tracks become available to the source media stream.
    */
   void NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream);
 
+  /**
+   * Called when a captured MediaStreamTrack is stopped so we can clean up its
+   * MediaInputPort.
+   */
+  void NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
+                                TrackID aDestinationTrackID);
+
   virtual bool IsNodeOfType(uint32_t aFlags) const override;
 
   /**
    * Returns the current load ID. Asynchronous events store the ID that was
    * current when they were enqueued, and if it has changed when they come to
    * fire, they consider themselves cancelled, and don't fire.
    */
   uint32_t GetCurrentLoadID() { return mCurrentLoadID; }
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -967,17 +967,18 @@ protected:
   void SetHTMLBoolAttr(nsIAtom* aName, bool aValue, mozilla::ErrorResult& aError)
   {
     if (aValue) {
       SetHTMLAttr(aName, EmptyString(), aError);
     } else {
       UnsetHTMLAttr(aName, aError);
     }
   }
-  void SetHTMLIntAttr(nsIAtom* aName, int32_t aValue, mozilla::ErrorResult& aError)
+  template<typename T>
+  void SetHTMLIntAttr(nsIAtom* aName, T aValue, mozilla::ErrorResult& aError)
   {
     nsAutoString value;
     value.AppendInt(aValue);
 
     SetHTMLAttr(aName, value, aError);
   }
 
   /**
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -1424,16 +1424,44 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
   StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
   nsTArray<IndexUpdateInfo> updateInfo;
 
   aRv = GetAddInfo(aCx, value, aKey, cloneWriteInfo, key, updateInfo);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  // Check the size limit of the serialized message which mainly consists of
+  // a StructuredCloneBuffer, an encoded object key, and the encoded index keys.
+  // kMaxIDBMsgOverhead covers the minor stuff not included in this calculation
+  // because the precise calculation would slow down this AddOrPut operation.
+  static const size_t kMaxIDBMsgOverhead = 1024 * 1024; // 1MB
+  const uint32_t maximalSizeFromPref =
+    IndexedDatabaseManager::MaxSerializedMsgSize();
+  MOZ_ASSERT(maximalSizeFromPref > kMaxIDBMsgOverhead);
+  const size_t kMaxMessageSize = maximalSizeFromPref - kMaxIDBMsgOverhead;
+
+  size_t indexUpdateInfoSize = 0;
+  for (size_t i = 0; i < updateInfo.Length(); i++) {
+    indexUpdateInfoSize += updateInfo[i].value().GetBuffer().Length();
+    indexUpdateInfoSize += updateInfo[i].localizedValue().GetBuffer().Length();
+  }
+
+  size_t messageSize = cloneWriteInfo.mCloneBuffer.data().Size() +
+    key.GetBuffer().Length() + indexUpdateInfoSize;
+
+  if (messageSize > kMaxMessageSize) {
+    IDB_REPORT_INTERNAL_ERR();
+    aRv.ThrowDOMException(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
+      nsPrintfCString("The serialized value is too large"
+                      " (size=%zu bytes, max=%zu bytes).",
+                      messageSize, kMaxMessageSize));
+    return nullptr;
+  }
+
   ObjectStoreAddPutParams commonParams;
   commonParams.objectStoreId() = Id();
   commonParams.cloneInfo().data().data = Move(cloneWriteInfo.mCloneBuffer.data());
   commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
   commonParams.key() = key;
   commonParams.indexUpdateInfos().SwapElements(updateInfo);
 
   // Convert any blobs or mutable files into FileAddInfo.
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IndexedDatabaseManager.h"
 
+#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
 #include "nsIConsoleService.h"
 #include "nsIDiskSpaceWatcher.h"
 #include "nsIDOMWindow.h"
 #include "nsIEventTarget.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIScriptError.h"
 #include "nsIScriptGlobalObject.h"
@@ -133,22 +134,26 @@ NS_DEFINE_IID(kIDBRequestIID, PRIVATE_ID
 
 const uint32_t kDeleteTimeoutMs = 1000;
 
 // The threshold we use for structured clone data storing.
 // Anything smaller than the threshold is compressed and stored in the database.
 // Anything larger is compressed and stored outside the database.
 const int32_t kDefaultDataThresholdBytes = 1024 * 1024; // 1MB
 
+// The maximal size of a serialized object to be transfered through IPC.
+const int32_t kDefaultMaxSerializedMsgSize = IPC::Channel::kMaximumMessageSize;
+
 #define IDB_PREF_BRANCH_ROOT "dom.indexedDB."
 
 const char kTestingPref[] = IDB_PREF_BRANCH_ROOT "testing";
 const char kPrefExperimental[] = IDB_PREF_BRANCH_ROOT "experimental";
 const char kPrefFileHandle[] = "dom.fileHandle.enabled";
 const char kDataThresholdPref[] = IDB_PREF_BRANCH_ROOT "dataThreshold";
+const char kPrefMaxSerilizedMsgSize[] = IDB_PREF_BRANCH_ROOT "maxSerializedMsgSize";
 
 #define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging."
 
 const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled";
 const char kPrefLoggingDetails[] = IDB_PREF_LOGGING_BRANCH_ROOT "details";
 
 #if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS)
 const char kPrefLoggingProfiler[] =
@@ -161,16 +166,17 @@ const char kPrefLoggingProfiler[] =
 StaticRefPtr<IndexedDatabaseManager> gDBManager;
 
 Atomic<bool> gInitialized(false);
 Atomic<bool> gClosed(false);
 Atomic<bool> gTestingMode(false);
 Atomic<bool> gExperimentalFeaturesEnabled(false);
 Atomic<bool> gFileHandleEnabled(false);
 Atomic<int32_t> gDataThresholdBytes(0);
+Atomic<int32_t> gMaxSerializedMsgSize(0);
 
 class DeleteFilesRunnable final
   : public nsIRunnable
   , public OpenDirectoryListener
 {
   typedef mozilla::dom::quota::DirectoryLock DirectoryLock;
 
   enum State
@@ -264,16 +270,28 @@ DataThresholdPrefChangedCallback(const c
   // The magic -1 is for use only by tests that depend on stable blob file id's.
   if (dataThresholdBytes == -1) {
     dataThresholdBytes = INT32_MAX;
   }
 
   gDataThresholdBytes = dataThresholdBytes;
 }
 
+void
+MaxSerializedMsgSizePrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aPrefName, kPrefMaxSerilizedMsgSize));
+  MOZ_ASSERT(!aClosure);
+
+  gMaxSerializedMsgSize =
+    Preferences::GetInt(aPrefName, kDefaultMaxSerializedMsgSize);
+  MOZ_ASSERT(gMaxSerializedMsgSize > 0);
+}
+
 } // namespace
 
 IndexedDatabaseManager::IndexedDatabaseManager()
   : mFileMutex("IndexedDatabaseManager.mFileMutex")
   , mBackgroundActor(nullptr)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
@@ -404,16 +422,19 @@ IndexedDatabaseManager::Init()
                                 kPrefLoggingProfiler);
 #endif
   Preferences::RegisterCallbackAndCall(LoggingModePrefChangedCallback,
                                        kPrefLoggingEnabled);
 
   Preferences::RegisterCallbackAndCall(DataThresholdPrefChangedCallback,
                                        kDataThresholdPref);
 
+  Preferences::RegisterCallbackAndCall(MaxSerializedMsgSizePrefChangeCallback,
+                                       kPrefMaxSerilizedMsgSize);
+
 #ifdef ENABLE_INTL_API
   const nsAdoptingCString& acceptLang =
     Preferences::GetLocalizedCString("intl.accept_languages");
 
   // Split values on commas.
   nsCCharSeparatedTokenizer langTokenizer(acceptLang, ',');
   while (langTokenizer.hasMoreTokens()) {
     nsAutoCString lang(langTokenizer.nextToken());
@@ -467,16 +488,19 @@ IndexedDatabaseManager::Destroy()
                                   kPrefLoggingProfiler);
 #endif
   Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
                                   kPrefLoggingEnabled);
 
   Preferences::UnregisterCallback(DataThresholdPrefChangedCallback,
                                   kDataThresholdPref);
 
+  Preferences::UnregisterCallback(MaxSerializedMsgSizePrefChangeCallback,
+                                  kPrefMaxSerilizedMsgSize);
+
   delete this;
 }
 
 // static
 nsresult
 IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor,
                                               IDBFactory* aFactory)
 {
@@ -778,16 +802,27 @@ uint32_t
 IndexedDatabaseManager::DataThreshold()
 {
   MOZ_ASSERT(gDBManager,
              "DataThreshold() called before indexedDB has been initialized!");
 
   return gDataThresholdBytes;
 }
 
+// static
+uint32_t
+IndexedDatabaseManager::MaxSerializedMsgSize()
+{
+  MOZ_ASSERT(gDBManager,
+             "MaxSerializedMsgSize() called before indexedDB has been initialized!");
+  MOZ_ASSERT(gMaxSerializedMsgSize > 0);
+
+  return gMaxSerializedMsgSize;
+}
+
 void
 IndexedDatabaseManager::ClearBackgroundActor()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mBackgroundActor = nullptr;
 }
 
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -129,16 +129,19 @@ public:
   ExperimentalFeaturesEnabled(JSContext* aCx, JSObject* aGlobal);
 
   static bool
   IsFileHandleEnabled();
 
   static uint32_t
   DataThreshold();
 
+  static uint32_t
+  MaxSerializedMsgSize();
+
   void
   ClearBackgroundActor();
 
   void
   NoteLiveQuotaManager(QuotaManager* aQuotaManager);
 
   void
   NoteShuttingDownQuotaManager();
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -62,16 +62,17 @@ support-files =
   unit/test_invalid_version.js
   unit/test_invalidate.js
   unit/test_key_requirements.js
   unit/test_keys.js
   unit/test_locale_aware_indexes.js
   unit/test_locale_aware_index_getAll.js
   unit/test_locale_aware_index_getAllObjects.js
   unit/test_lowDiskSpace.js
+  unit/test_maximal_serialized_object_size.js
   unit/test_multientry.js
   unit/test_names_sorted.js
   unit/test_objectCursors.js
   unit/test_objectStore_getAllKeys.js
   unit/test_objectStore_inline_autoincrement_key_added_on_put.js
   unit/test_objectStore_openKeyCursor.js
   unit/test_objectStore_remove_values.js
   unit/test_object_identity.js
@@ -290,16 +291,17 @@ skip-if = true
 [test_key_requirements.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_keys.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_leaving_page.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_lowDiskSpace.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
+[test_maximal_serialized_object_size.html]
 [test_message_manager_ipc.html]
 # This test is only supposed to run in the main process.
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s
 [test_multientry.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_names_sorted.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_objectCursors.html]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_maximal_serialized_object_size.html
@@ -0,0 +1,18 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Test Maximal Size of a Serialized Object</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7" src="unit/test_maximal_serialized_object_size.js"></script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_maximal_serialized_object_size.js
@@ -0,0 +1,95 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var disableWorkerTest = "Need a way to set temporary prefs from a worker";
+
+var testGenerator = testSteps();
+
+function testSteps()
+{
+  const name = this.window ?
+    window.location.pathname : "test_maximal_serialized_object_size.js";
+  const megaBytes = 1024 * 1024;
+  const kMessageOverhead = 1; // in MB
+  const kMaxIpcMessageSize = 20; // in MB
+  const kMaxIdbMessageSize = kMaxIpcMessageSize - kMessageOverhead;
+
+  let chunks = new Array(kMaxIdbMessageSize);
+  for (let i = 0; i < kMaxIdbMessageSize; i++) {
+    chunks[i] = new ArrayBuffer(1 * megaBytes);
+  }
+
+  if (this.window) {
+    SpecialPowers.pushPrefEnv(
+      { "set": [["dom.indexedDB.maxSerializedMsgSize",
+                 kMaxIpcMessageSize * megaBytes ]]
+      },
+      continueToNextStep
+    );
+    yield undefined;
+  } else {
+    setMaxSerializedMsgSize(kMaxIpcMessageSize * megaBytes);
+  }
+
+  let openRequest = indexedDB.open(name, 1);
+  openRequest.onerror = errorHandler;
+  openRequest.onupgradeneeded = grabEventAndContinueHandler;
+  openRequest.onsuccess = unexpectedSuccessHandler;
+  let event = yield undefined;
+
+  let db = event.target.result;
+  let txn = event.target.transaction;
+
+  is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
+
+  let objectStore = db.createObjectStore("test store", { keyPath: "id" });
+  is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
+  is(db.objectStoreNames.item(0), objectStore.name, "Correct object store name");
+
+  function testTooLargeError(aOperation, aObject) {
+    try {
+      objectStore[aOperation](aObject).onerror = errorHandler;
+      ok(false, "UnknownError is expected to be thrown!");
+    } catch (e) {
+      ok(e instanceof DOMException, "got a DOM exception");
+      is(e.name, "UnknownError", "correct error");
+      ok(!!e.message, "Error message: " + e.message);
+      ok(e.message.startsWith("The serialized value is too large"),
+         "Correct error message prefix.");
+    }
+  }
+
+  info("Verify IDBObjectStore.add() - object is too large");
+  testTooLargeError("add", { id: 1, data: chunks });
+
+  info("Verify IDBObjectStore.add() - object size is closed to the maximal size.");
+  chunks.length = chunks.length - 1;
+  let request = objectStore.add({ id: 1, data: chunks });
+  request.onerror = errorHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+  yield undefined;
+
+  info("Verify IDBObjectStore.add() - object key is too large");
+  chunks.length = 10;
+  testTooLargeError("add", { id: chunks });
+
+  objectStore.createIndex("index name", "index");
+  ok(objectStore.index("index name"), "Index created.");
+
+  info("Verify IDBObjectStore.add() - index key is too large");
+  testTooLargeError("add", { id: 2, index: chunks });
+
+  info("Verify IDBObjectStore.add() - object key and index key are too large");
+  let indexChunks = chunks.splice(0, 5);
+  testTooLargeError("add", { id: chunks, index: indexChunks });
+
+  openRequest.onsuccess = continueToNextStep;
+  yield undefined;
+
+  db.close();
+
+  finishTest();
+  yield undefined;
+}
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -525,16 +525,22 @@ function setTemporaryStorageLimit(limit)
 }
 
 function setDataThreshold(threshold)
 {
   info("Setting data threshold to " + threshold);
   SpecialPowers.setIntPref("dom.indexedDB.dataThreshold", threshold);
 }
 
+function setMaxSerializedMsgSize(aSize)
+{
+  info("Setting maximal size of a serialized message to " + aSize);
+  SpecialPowers.setIntPref("dom.indexedDB.maxSerializedMsgSize", aSize);
+}
+
 function getPrincipal(url)
 {
   let uri = Cc["@mozilla.org/network/io-service;1"]
               .getService(Ci.nsIIOService)
               .newURI(url, null, null);
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
   return ssm.createCodebasePrincipal(uri, {});
--- a/dom/indexedDB/test/unit/xpcshell-parent-process.ini
+++ b/dom/indexedDB/test/unit/xpcshell-parent-process.ini
@@ -39,16 +39,17 @@ support-files =
 [test_idbSubdirUpgrade.js]
 [test_globalObjects_ipc.js]
 skip-if = toolkit == 'android'
 [test_idle_maintenance.js]
 [test_invalidate.js]
 # disabled for the moment.
 skip-if = true
 [test_lowDiskSpace.js]
+[test_maximal_serialized_object_size.js]
 [test_metadata2Restore.js]
 [test_metadataRestore.js]
 [test_mutableFileUpgrade.js]
 [test_oldDirectories.js]
 [test_quotaExceeded_recovery.js]
 [test_readwriteflush_disabled.js]
 [test_schema18upgrade.js]
 [test_schema21upgrade.js]
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -737,24 +737,24 @@ ContentParent::JoinAllSubprocesses()
 
   sCanLaunchSubprocesses = false;
 }
 
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::GetNewOrUsedBrowserProcess(bool aForBrowserElement,
                                           ProcessPriority aPriority,
                                           ContentParent* aOpener,
-                                          bool aFreshProcess)
+                                          bool aLargeAllocationProcess)
 {
   nsTArray<ContentParent*>* contentParents;
   int32_t maxContentParents;
 
   // Decide which pool of content parents we are going to be pulling from based
-  // on the aFreshProcess flag.
-  if (aFreshProcess) {
+  // on the aLargeAllocationProcess flag.
+  if (aLargeAllocationProcess) {
     if (!sLargeAllocationContentParents) {
       sLargeAllocationContentParents = new nsTArray<ContentParent*>();
     }
     contentParents = sLargeAllocationContentParents;
 
     maxContentParents = Preferences::GetInt("dom.ipc.dedicatedProcessCount", 2);
   } else {
     if (!sNonAppContentParents) {
@@ -765,25 +765,27 @@ ContentParent::GetNewOrUsedBrowserProces
     maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1);
   }
 
   if (maxContentParents < 1) {
     maxContentParents = 1;
   }
 
   if (contentParents->Length() >= uint32_t(maxContentParents)) {
-    uint32_t startIdx = rand() % contentParents->Length();
+    uint32_t maxSelectable = std::min(static_cast<uint32_t>(contentParents->Length()),
+                                      static_cast<uint32_t>(maxContentParents));
+    uint32_t startIdx = rand() % maxSelectable;
     uint32_t currIdx = startIdx;
     do {
       RefPtr<ContentParent> p = (*contentParents)[currIdx];
       NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sNonAppContntParents?");
       if (p->mOpener == aOpener) {
         return p.forget();
       }
-      currIdx = (currIdx + 1) % contentParents->Length();
+      currIdx = (currIdx + 1) % maxSelectable;
     } while (currIdx != startIdx);
   }
 
   // Try to take and transform the preallocated process into browser.
   RefPtr<ContentParent> p = PreallocatedProcessManager::Take();
   if (p) {
     p->TransformPreallocatedIntoBrowser(aOpener);
   } else {
@@ -794,16 +796,19 @@ ContentParent::GetNewOrUsedBrowserProces
                           /* isForPreallocated = */ false);
 
     if (!p->LaunchSubprocess(aPriority)) {
       return nullptr;
     }
 
     p->Init();
   }
+
+  p->mLargeAllocationProcess = aLargeAllocationProcess;
+
   p->ForwardKnownInfo();
 
   contentParents->AppendElement(p);
   return p.forget();
 }
 
 /*static*/ ProcessPriority
 ContentParent::GetInitialProcessPriority(Element* aFrameElement)
@@ -1740,23 +1745,23 @@ ContentParent::RecvAllocateLayerTreeId(c
   return AllocateLayerTreeId(contentParent, browserParent, aTabId, aId);
 }
 
 bool
 ContentParent::RecvDeallocateLayerTreeId(const uint64_t& aId)
 {
   GPUProcessManager* gpu = GPUProcessManager::Get();
 
-  if (!gpu->IsLayerTreeIdMapped(aId, this->OtherPid()))
+  if (!gpu->IsLayerTreeIdMapped(aId, OtherPid()))
   {
     // You can't deallocate layer tree ids that you didn't allocate
     KillHard("DeallocateLayerTreeId");
   }
 
-  gpu->DeallocateLayerTreeId(aId);
+  gpu->UnmapLayerTreeId(aId, OtherPid());
 
   return true;
 }
 
 namespace {
 
 void
 DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess)
@@ -1953,16 +1958,22 @@ ContentParent::NotifyTabDestroying(const
         return;
     }
     ++cp->mNumDestroyingTabs;
     nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(aCpId);
     if (static_cast<size_t>(cp->mNumDestroyingTabs) != tabIds.Length()) {
         return;
     }
 
+    uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0;
+    int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0);
+    if (!cp->mLargeAllocationProcess && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive) {
+      return;
+    }
+
     // We're dying now, so prevent this content process from being
     // recycled during its shutdown procedure.
     cp->MarkAsDead();
     cp->StartForceKillTimer();
   } else {
     ContentChild::GetSingleton()->SendNotifyTabDestroying(aTabId, aCpId);
   }
 }
@@ -2001,17 +2012,25 @@ ContentParent::NotifyTabDestroyed(const 
     Unused << PContentPermissionRequestParent::Send__delete__(permissionRequestParent);
   }
 
   // There can be more than one PBrowser for a given app process
   // because of popup windows.  When the last one closes, shut
   // us down.
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
   nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(this->ChildID());
-  if (tabIds.Length() == 1) {
+
+  // We might want to keep alive some content processes for testing, because of performance
+  // reasons, but we don't want to alter behavior if the pref is not set.
+  uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0;
+  int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0);
+  bool shouldKeepAliveAny = !mLargeAllocationProcess && processesToKeepAlive > 0;
+  bool shouldKeepAliveThis = shouldKeepAliveAny && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive;
+
+  if (tabIds.Length() == 1 && !shouldKeepAliveThis) {
     // In the case of normal shutdown, send a shutdown message to child to
     // allow it to perform shutdown tasks.
     MessageLoop::current()->PostTask(NewRunnableMethod
                                      <ShutDownMethod>(this,
                                                       &ContentParent::ShutDownProcess,
                                                       SEND_SHUTDOWN_MESSAGE));
   }
 }
@@ -2092,16 +2111,17 @@ ContentParent::LaunchSubprocess(ProcessP
 
 ContentParent::ContentParent(mozIApplication* aApp,
                              ContentParent* aOpener,
                              bool aIsForBrowser,
                              bool aIsForPreallocated)
   : nsIContentParent()
   , mOpener(aOpener)
   , mIsForBrowser(aIsForBrowser)
+  , mLargeAllocationProcess(false)
 {
   InitializeMembers();  // Perform common initialization.
 
   // No more than one of !!aApp, aIsForBrowser, aIsForPreallocated should be
   // true.
   MOZ_ASSERT(!!aApp + aIsForBrowser + aIsForPreallocated <= 1);
 
   mMetamorphosed = true;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -131,17 +131,17 @@ public:
    * 2. remote xul <browser>
    * 3. normal iframe
    */
   static already_AddRefed<ContentParent>
   GetNewOrUsedBrowserProcess(bool aForBrowserElement = false,
                              hal::ProcessPriority aPriority =
                              hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
                              ContentParent* aOpener = nullptr,
-                             bool aFreshProcess = false);
+                             bool aLargeAllocationProcess = false);
 
   /**
    * Create a subprocess suitable for use as a preallocated app process.
    */
   static already_AddRefed<ContentParent> PreallocateAppProcess();
 
   /**
    * Get or create a content process for the given TabContext.  aFrameElement
@@ -1185,16 +1185,17 @@ private:
   RefPtr<embedding::PrintingParent> mPrintingParent;
 #endif
 
   // This hashtable is used to run GetFilesHelper objects in the parent process.
   // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest.
   nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests;
 
   nsTArray<nsCString> mBlobURLs;
+  bool mLargeAllocationProcess;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver
 {
   friend class mozilla::dom::ContentParent;
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1835,17 +1835,17 @@ TabChild::RecvPluginEvent(const WidgetPl
     // If not consumed, we should call default action
     SendDefaultProcOfPluginEvent(aEvent);
   }
   return true;
 }
 
 void
 TabChild::RequestNativeKeyBindings(AutoCacheNativeKeyCommands* aAutoCache,
-                                   WidgetKeyboardEvent* aEvent)
+                                   const WidgetKeyboardEvent* aEvent)
 {
   MaybeNativeKeyBinding maybeBindings;
   if (!SendRequestNativeKeyBindings(*aEvent, &maybeBindings)) {
     return;
   }
 
   if (maybeBindings.type() == MaybeNativeKeyBinding::TNativeKeyBinding) {
     const NativeKeyBinding& bindings = maybeBindings;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -508,17 +508,17 @@ public:
 
   ScreenOrientationInternal GetOrientation() const { return mOrientation; }
 
   void SetBackgroundColor(const nscolor& aColor);
 
   void NotifyPainted();
 
   void RequestNativeKeyBindings(mozilla::widget::AutoCacheNativeKeyCommands* aAutoCache,
-                                WidgetKeyboardEvent* aEvent);
+                                const WidgetKeyboardEvent* aEvent);
 
   /**
    * Signal to this TabChild that it should be made visible:
    * activated widget, retained layer tree, etc.  (Respectively,
    * made not visible.)
    */
   void MakeVisible();
   void MakeHidden();
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -933,17 +933,19 @@ TabParent::RecvPDocAccessibleConstructor
     MOZ_ASSERT(aParentID);
     if (!aParentID) {
       return false;
     }
 
     auto parentDoc = static_cast<a11y::DocAccessibleParent*>(aParentDoc);
     bool added = parentDoc->AddChildDoc(doc, aParentID);
 #ifdef XP_WIN
-    a11y::WrapperFor(doc)->SetID(aMsaaID);
+    if (added) {
+      a11y::WrapperFor(doc)->SetID(aMsaaID);
+    }
 #endif
     return added;
   } else {
     // null aParentDoc means this document is at the top level in the child
     // process.  That means it makes no sense to get an id for an accessible
     // that is its parent.
     MOZ_ASSERT(!aParentID);
     if (aParentID) {
--- a/dom/media/CanvasCaptureMediaStream.cpp
+++ b/dom/media/CanvasCaptureMediaStream.cpp
@@ -233,17 +233,16 @@ JSObject*
 CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::CanvasCaptureMediaStreamBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 CanvasCaptureMediaStream::RequestFrame()
 {
-  MOZ_ASSERT(mOutputStreamDriver);
   if (mOutputStreamDriver) {
     mOutputStreamDriver->RequestFrameCapture();
   }
 }
 
 nsresult
 CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
                                const TrackID& aTrackId,
@@ -278,11 +277,22 @@ CanvasCaptureMediaStream::CreateSourceSt
 }
 
 FrameCaptureListener*
 CanvasCaptureMediaStream::FrameCaptureListener()
 {
   return mOutputStreamDriver;
 }
 
+void
+CanvasCaptureMediaStream::StopCapture()
+{
+  if (!mOutputStreamDriver) {
+    return;
+  }
+
+  mOutputStreamDriver->Forget();
+  mOutputStreamDriver = nullptr;
+}
+
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/media/CanvasCaptureMediaStream.h
+++ b/dom/media/CanvasCaptureMediaStream.h
@@ -107,19 +107,25 @@ public:
   nsresult Init(const dom::Optional<double>& aFPS, const TrackID& aTrackId,
                 nsIPrincipal* aPrincipal);
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL
   HTMLCanvasElement* Canvas() const { return mCanvas; }
   void RequestFrame();
+
   dom::FrameCaptureListener* FrameCaptureListener();
 
   /**
+   * Stops capturing for this stream at mCanvas.
+   */
+  void StopCapture();
+
+  /**
    * Create a CanvasCaptureMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<CanvasCaptureMediaStream>
   CreateSourceStream(nsPIDOMWindowInner* aWindow,
                      HTMLCanvasElement* aCanvas);
 
 protected:
   ~CanvasCaptureMediaStream();
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -47,16 +47,27 @@ using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla::media;
 
 static LazyLogModule gMediaStreamLog("MediaStream");
 #define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg)
 
 const TrackID TRACK_VIDEO_PRIMARY = 1;
 
+static bool
+ContainsLiveTracks(nsTArray<RefPtr<DOMMediaStream::TrackPort>>& aTracks)
+{
+  for (auto& port : aTracks) {
+    if (port->GetTrack()->ReadyState() == MediaStreamTrackState::Live) {
+      return true;
+    }
+  }
+
+  return false;
+}
 
 DOMMediaStream::TrackPort::TrackPort(MediaInputPort* aInputPort,
                                      MediaStreamTrack* aTrack,
                                      const InputPortOwnership aOwnership)
   : mInputPort(aInputPort)
   , mTrack(aTrack)
   , mOwnership(aOwnership)
 {
@@ -160,17 +171,17 @@ public:
     if (mStream->mTrackSourceGetter) {
       source = mStream->mTrackSourceGetter->GetMediaStreamTrackSource(aTrackID);
     }
     if (!source) {
       NS_ASSERTION(false, "Dynamic track created without an explicit TrackSource");
       nsPIDOMWindowInner* window = mStream->GetParentObject();
       nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
       nsIPrincipal* principal = doc ? doc->NodePrincipal() : nullptr;
-      source = new BasicUnstoppableTrackSource(principal);
+      source = new BasicTrackSource(principal);
     }
 
     RefPtr<MediaStreamTrack> newTrack =
       mStream->CreateDOMTrack(aTrackID, aType, source);
     NS_DispatchToMainThread(NewRunnableMethod<RefPtr<MediaStreamTrack>>(
         mStream, &DOMMediaStream::AddTrackInternal, newTrack));
   }
 
@@ -184,17 +195,17 @@ public:
     }
 
     RefPtr<MediaStreamTrack> track =
       mStream->FindOwnedDOMTrack(aInputStream, aInputTrackID, aTrackID);
     NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream");
     if (track) {
       LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.",
                             mStream, track.get()));
-      track->NotifyEnded();
+      track->OverrideEnded();
     }
   }
 
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
                                 const MediaSegment& aQueuedMedia,
                                 MediaStream* aInputStream,
                                 TrackID aInputTrackID) override
@@ -231,114 +242,131 @@ public:
   {}
 
   void Forget()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mStream = nullptr;
   }
 
-  void DoNotifyTrackEnded(MediaStream* aInputStream,
-                          TrackID aInputTrackID)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (!mStream) {
-      return;
-    }
-
-    LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended",
-                          mStream, aInputTrackID, aInputStream));
-
-    RefPtr<MediaStreamTrack> track =
-      mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID);
-    if (!track) {
-      LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream));
-      return;
-    }
-
-    LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.",
-                           mStream));
-    mStream->NotifyTrackRemoved(track);
-
-    RefPtr<TrackPort> endedPort = mStream->FindPlaybackTrackPort(*track);
-    NS_ASSERTION(endedPort, "Playback track should have a TrackPort");
-    if (endedPort && IsTrackIDExplicit(endedPort->GetSourceTrackId())) {
-      // If a track connected to a locked-track input port ends, we destroy the
-      // port to allow our playback stream to finish.
-      // XXX (bug 1208316) This should not be necessary when MediaStreams don't
-      // finish but instead become inactive.
-      endedPort->DestroyInputPort();
-    }
-  }
-
   void DoNotifyFinishedTrackCreation()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
     }
 
     // The owned stream listener adds its tracks after another main thread
     // dispatch. We have to do the same to notify of created tracks to stay
     // in sync. (Or NotifyTracksCreated is called before tracks are added).
     NS_DispatchToMainThread(
         NewRunnableMethod(mStream, &DOMMediaStream::NotifyTracksCreated));
   }
 
-  // The methods below are called on the MediaStreamGraph thread.
-
-  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
-                                StreamTime aTrackOffset, TrackEventCommand aTrackEvents,
-                                const MediaSegment& aQueuedMedia,
-                                MediaStream* aInputStream,
-                                TrackID aInputTrackID) override
+  void DoNotifyFinished()
   {
-    if (aTrackEvents & TrackEventCommand::TRACK_EVENT_ENDED) {
-      nsCOMPtr<nsIRunnable> runnable =
-        NewRunnableMethod<RefPtr<MediaStream>, TrackID>(
-          this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (!mStream) {
+      return;
     }
+
+    mStream->NotifyFinished();
   }
 
+  // The methods below are called on the MediaStreamGraph thread.
+
   void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override
   {
     nsCOMPtr<nsIRunnable> runnable =
       NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation);
     aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
   }
 
+
+  void NotifyEvent(MediaStreamGraph* aGraph,
+                   MediaStreamGraphEvent event) override
+  {
+    if (event == MediaStreamGraphEvent::EVENT_FINISHED) {
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(
+        NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinished));
+    }
+  }
+
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
 
+class DOMMediaStream::PlaybackTrackListener : public MediaStreamTrackConsumer
+{
+public:
+  explicit PlaybackTrackListener(DOMMediaStream* aStream) :
+    mStream(aStream) {}
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PlaybackTrackListener,
+                                           MediaStreamTrackConsumer)
+
+  void NotifyEnded(MediaStreamTrack* aTrack) override
+  {
+    if (!mStream) {
+      MOZ_ASSERT(false);
+      return;
+    }
+
+    if (!aTrack) {
+      MOZ_ASSERT(false);
+      return;
+    }
+
+    MOZ_ASSERT(mStream->HasTrack(*aTrack));
+    mStream->NotifyTrackRemoved(aTrack);
+  }
+
+protected:
+  virtual ~PlaybackTrackListener() {}
+
+  RefPtr<DOMMediaStream> mStream;
+};
+
+NS_IMPL_ADDREF_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                         MediaStreamTrackConsumer)
+NS_IMPL_RELEASE_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                          MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMMediaStream::PlaybackTrackListener,
+                                   MediaStreamTrackConsumer,
+                                   mStream)
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMMediaStream)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMMediaStream,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTracks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumersToKeepAlive)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackTrackListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMMediaStream,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwnedTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTracks)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumersToKeepAlive)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTrackSourceGetter)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackTrackListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMMediaStream)
@@ -361,17 +389,19 @@ NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMe
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
 
 DOMMediaStream::DOMMediaStream(nsPIDOMWindowInner* aWindow,
                                MediaStreamTrackSourceGetter* aTrackSourceGetter)
   : mLogicalStreamStartTime(0), mWindow(aWindow),
     mInputStream(nullptr), mOwnedStream(nullptr), mPlaybackStream(nullptr),
     mTracksPendingRemoval(0), mTrackSourceGetter(aTrackSourceGetter),
-    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false)
+    mPlaybackTrackListener(MakeAndAddRef<PlaybackTrackListener>(this)),
+    mTracksCreated(false), mNotifiedOfMediaStreamGraphShutdown(false),
+    mActive(false), mSetInactiveOnFinish(false)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   if (NS_SUCCEEDED(rv) && uuidgen) {
     nsID uuid;
     memset(&uuid, 0, sizeof(uuid));
@@ -399,18 +429,22 @@ DOMMediaStream::Destroy()
   }
   if (mPlaybackListener) {
     mPlaybackListener->Forget();
     mPlaybackListener = nullptr;
   }
   for (const RefPtr<TrackPort>& info : mTracks) {
     // We must remove ourselves from each track's principal change observer list
     // before we die. CC may have cleared info->mTrack so guard against it.
-    if (info->GetTrack()) {
-      info->GetTrack()->RemovePrincipalChangeObserver(this);
+    MediaStreamTrack* track = info->GetTrack();
+    if (track) {
+      track->RemovePrincipalChangeObserver(this);
+      if (!track->Ended()) {
+        track->RemoveConsumer(mPlaybackTrackListener);
+      }
     }
   }
   if (mPlaybackPort) {
     mPlaybackPort->Destroy();
     mPlaybackPort = nullptr;
   }
   if (mOwnedPort) {
     mOwnedPort->Destroy();
@@ -613,23 +647,23 @@ DOMMediaStream::RemoveTrack(MediaStreamT
   // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need
   // to block it in the port. Doing this for a locked track is still OK as it
   // will first block the track, then destroy the port. Both cause the track to
   // end.
   // If the track has already ended, it's input port might be gone, so in those
   // cases blocking the underlying track should be avoided.
   if (!aTrack.Ended()) {
     BlockPlaybackTrack(toRemove);
+
+    bool removed = mTracks.RemoveElement(toRemove);
+    if (removed) {
+      NotifyTrackRemoved(&aTrack);
+    }
   }
 
-  DebugOnly<bool> removed = mTracks.RemoveElement(toRemove);
-  MOZ_ASSERT(removed);
-
-  NotifyTrackRemoved(&aTrack);
-
   LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack));
 }
 
 class ClonedStreamSourceGetter :
   public MediaStreamTrackSourceGetter
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -725,16 +759,22 @@ DOMMediaStream::CloneInternal(TrackForwa
                                                    TRACK_ANY, TRACK_ANY, 0, 0,
                                                    &tracksToBlock);
     }
   }
 
   return newStream.forget();
 }
 
+bool
+DOMMediaStream::Active() const
+{
+  return mActive;
+}
+
 MediaStreamTrack*
 DOMMediaStream::GetTrackById(const nsAString& aId) const
 {
   for (const RefPtr<TrackPort>& info : mTracks) {
     nsString id;
     info->GetTrack()->GetId(id);
     if (id == aId) {
       return info->GetTrack();
@@ -782,22 +822,28 @@ void
 DOMMediaStream::RemoveDirectListener(DirectMediaStreamListener* aListener)
 {
   if (GetInputStream() && GetInputStream()->AsSourceStream()) {
     GetInputStream()->AsSourceStream()->RemoveDirectListener(aListener);
   }
 }
 
 bool
-DOMMediaStream::IsFinished()
+DOMMediaStream::IsFinished() const
 {
   return !mPlaybackStream || mPlaybackStream->IsFinished();
 }
 
 void
+DOMMediaStream::SetInactiveOnFinish()
+{
+  mSetInactiveOnFinish = true;
+}
+
+void
 DOMMediaStream::InitSourceStream(MediaStreamGraph* aGraph)
 {
   InitInputStreamCommon(aGraph->CreateSourceStream(), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
@@ -808,18 +854,18 @@ DOMMediaStream::InitTrackUnionStream(Med
   InitPlaybackStreamCommon(aGraph);
 }
 
 void
 DOMMediaStream::InitAudioCaptureStream(nsIPrincipal* aPrincipal, MediaStreamGraph* aGraph)
 {
   const TrackID AUDIO_TRACK = 1;
 
-  RefPtr<BasicUnstoppableTrackSource> audioCaptureSource =
-    new BasicUnstoppableTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
+  RefPtr<BasicTrackSource> audioCaptureSource =
+    new BasicTrackSource(aPrincipal, MediaSourceEnum::AudioCapture);
 
   AudioCaptureStream* audioCaptureStream =
     static_cast<AudioCaptureStream*>(aGraph->CreateAudioCaptureStream(AUDIO_TRACK));
   InitInputStreamCommon(audioCaptureStream, aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
   RefPtr<MediaStreamTrack> track =
     CreateDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, audioCaptureSource);
@@ -1175,16 +1221,55 @@ DOMMediaStream::OnTracksAvailable(OnTrac
 void
 DOMMediaStream::NotifyTracksCreated()
 {
   mTracksCreated = true;
   CheckTracksAvailable();
 }
 
 void
+DOMMediaStream::NotifyFinished()
+{
+  if (!mSetInactiveOnFinish) {
+    return;
+  }
+
+  if (!mActive) {
+    // This can happen if the stream never became active.
+    return;
+  }
+
+  MOZ_ASSERT(!ContainsLiveTracks(mTracks));
+  mActive = false;
+  NotifyInactive();
+}
+
+void
+DOMMediaStream::NotifyActive()
+{
+  LOG(LogLevel::Info, ("DOMMediaStream %p NotifyActive(). ", this));
+
+  MOZ_ASSERT(mActive);
+  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
+    mTrackListeners[i]->NotifyActive();
+  }
+}
+
+void
+DOMMediaStream::NotifyInactive()
+{
+  LOG(LogLevel::Info, ("DOMMediaStream %p NotifyInactive(). ", this));
+
+  MOZ_ASSERT(!mActive);
+  for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
+    mTrackListeners[i]->NotifyInactive();
+  }
+}
+
+void
 DOMMediaStream::CheckTracksAvailable()
 {
   if (!mTracksCreated) {
     return;
   }
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks;
   callbacks.SwapElements(mRunOnTracksAvailable);
 
@@ -1234,36 +1319,66 @@ DOMMediaStream::NotifyTrackAdded(const R
     }
   } else {
     LOG(LogLevel::Debug, ("DOMMediaStream %p saw a track get added. "
                           "Recomputing principal.", this));
     RecomputePrincipal();
   }
 
   aTrack->AddPrincipalChangeObserver(this);
+  aTrack->AddConsumer(mPlaybackTrackListener);
 
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
     mTrackListeners[i]->NotifyTrackAdded(aTrack);
   }
+
+  if (mActive) {
+    return;
+  }
+
+  // Check if we became active.
+  if (ContainsLiveTracks(mTracks)) {
+    mActive = true;
+    NotifyActive();
+  }
 }
 
 void
 DOMMediaStream::NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  aTrack->RemoveConsumer(mPlaybackTrackListener);
   aTrack->RemovePrincipalChangeObserver(this);
 
   for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) {
     mTrackListeners[i]->NotifyTrackRemoved(aTrack);
+
   }
 
   // Don't call RecomputePrincipal here as the track may still exist in the
   // playback stream in the MediaStreamGraph. It will instead be called when the
   // track has been confirmed removed by the graph. See BlockPlaybackTrack().
+
+  if (!mActive) {
+    NS_ASSERTION(false, "Shouldn't remove a live track if already inactive");
+    return;
+  }
+
+  if (mSetInactiveOnFinish) {
+    // For compatibility with mozCaptureStream we in some cases do not go
+    // inactive until the playback stream finishes.
+    return;
+  }
+
+  // Check if we became inactive.
+  if (!ContainsLiveTracks(mTracks)) {
+    mActive = false;
+    NotifyInactive();
+  }
 }
 
 nsresult
 DOMMediaStream::DispatchTrackEvent(const nsAString& aName,
                                    const RefPtr<MediaStreamTrack>& aTrack)
 {
   MOZ_ASSERT(aName == NS_LITERAL_STRING("addtrack"),
              "Only 'addtrack' is supported at this time");
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -221,28 +221,40 @@ class DOMMediaStream : public DOMEventTa
 public:
   typedef dom::MediaTrackConstraints MediaTrackConstraints;
 
   class TrackListener {
   public:
     virtual ~TrackListener() {}
 
     /**
-     * Called when the DOMMediaStream has a new track added, either by
-     * JS (addTrack()) or the source creating one.
+     * Called when the DOMMediaStream has a live track added, either by
+     * script (addTrack()) or the source creating one.
      */
     virtual void
     NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) {};
 
     /**
-     * Called when the DOMMediaStream removes a track, either by
-     * JS (removeTrack()) or the source ending it.
+     * Called when the DOMMediaStream removes a live track from playback, either
+     * by script (removeTrack(), track.stop()) or the source ending it.
      */
     virtual void
     NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) {};
+
+    /**
+     * Called when the DOMMediaStream has become active.
+     */
+    virtual void
+    NotifyActive() {};
+
+    /**
+     * Called when the DOMMediaStream has become inactive.
+     */
+    virtual void
+    NotifyInactive() {};
   };
 
   /**
    * TrackPort is a representation of a MediaStreamTrack-MediaInputPort pair
    * that make up a link between the Owned stream and the Playback stream.
    *
    * Semantically, the track is the identifier/key and the port the value of this
    * connection.
@@ -358,16 +370,18 @@ public:
   void GetTracks(nsTArray<RefPtr<MediaStreamTrack> >& aTracks) const;
   MediaStreamTrack* GetTrackById(const nsAString& aId) const;
   void AddTrack(MediaStreamTrack& aTrack);
   void RemoveTrack(MediaStreamTrack& aTrack);
 
   /** Identical to CloneInternal(TrackForwardingOption::EXPLICIT) */
   already_AddRefed<DOMMediaStream> Clone();
 
+  bool Active() const;
+
   IMPL_EVENT_HANDLER(addtrack)
 
   // NON-WebIDL
 
   /**
    * Option to provide to CloneInternal() of which tracks should be forwarded
    * from the source stream (`this`) to the returned stream clone.
    *
@@ -440,17 +454,26 @@ public:
    * queuing. Returns a bool to let us know if direct data will be delivered.
    */
   bool AddDirectListener(DirectMediaStreamListener *aListener);
   void RemoveDirectListener(DirectMediaStreamListener *aListener);
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
   virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
 
-  bool IsFinished();
+  /**
+   * Legacy method that returns true when the playback stream has finished.
+   */
+  bool IsFinished() const;
+
+  /**
+   * Becomes inactive only when the playback stream has finished.
+   */
+  void SetInactiveOnFinish();
+
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
   /**
    * Returns a principal indicating who may access video data of this stream.
@@ -594,32 +617,44 @@ protected:
   void InitPlaybackStreamCommon(MediaStreamGraph* aGraph);
 
   void CheckTracksAvailable();
 
   // Called when MediaStreamGraph has finished an iteration where tracks were
   // created.
   void NotifyTracksCreated();
 
+  // Called when our playback stream has finished in the MediaStreamGraph.
+  void NotifyFinished();
+
+  // Dispatches NotifyActive() to all registered track listeners.
+  void NotifyActive();
+
+  // Dispatches NotifyInactive() to all registered track listeners.
+  void NotifyInactive();
+
   // Dispatches NotifyTrackAdded() to all registered track listeners.
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack);
 
   // Dispatches NotifyTrackRemoved() to all registered track listeners.
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack);
 
   // Dispatches "addtrack" or "removetrack".
   nsresult DispatchTrackEvent(const nsAString& aName,
                               const RefPtr<MediaStreamTrack>& aTrack);
 
   class OwnedStreamListener;
   friend class OwnedStreamListener;
 
   class PlaybackStreamListener;
   friend class PlaybackStreamListener;
 
+  class PlaybackTrackListener;
+  friend class PlaybackTrackListener;
+
   /**
    * Block a track in our playback stream. Calls NotifyPlaybackTrackBlocked()
    * after the MediaStreamGraph has applied the block and the track is no longer
    * live.
    */
   void BlockPlaybackTrack(TrackPort* aTrack);
 
   /**
@@ -679,31 +714,42 @@ protected:
   // Listener tracking changes to mOwnedStream. We use this to notify the
   // MediaStreamTracks we own about state changes.
   RefPtr<OwnedStreamListener> mOwnedListener;
 
   // Listener tracking changes to mPlaybackStream. This drives state changes
   // in this DOMMediaStream and notifications to mTrackListeners.
   RefPtr<PlaybackStreamListener> mPlaybackListener;
 
+  // Listener tracking when live MediaStreamTracks in mTracks end.
+  RefPtr<PlaybackTrackListener> mPlaybackTrackListener;
+
   nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
 
   // Set to true after MediaStreamGraph has created tracks for mPlaybackStream.
   bool mTracksCreated;
 
   nsString mID;
 
   // Keep these alive while the stream is alive.
   nsTArray<nsCOMPtr<nsISupports>> mConsumersToKeepAlive;
 
   bool mNotifiedOfMediaStreamGraphShutdown;
 
   // The track listeners subscribe to changes in this stream's track set.
   nsTArray<TrackListener*> mTrackListeners;
 
+  // True if this stream has live tracks.
+  bool mActive;
+
+  // True if this stream only sets mActive to false when its playback stream
+  // finishes. This is a hack to maintain legacy functionality for playing a
+  // HTMLMediaElement::MozCaptureStream(). See bug 1302379.
+  bool mSetInactiveOnFinish;
+
 private:
   void NotifyPrincipalChanged();
   // Principal identifying who may access the collected contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
   // Video principal is used by video element as access is requested to its
   // image data.
   nsCOMPtr<nsIPrincipal> mVideoPrincipal;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -129,17 +129,17 @@ namespace mozilla {
 LogModule*
 GetMediaManagerLog()
 {
   static LazyLogModule sLog("MediaManager");
   return sLog;
 }
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
-using dom::BasicUnstoppableTrackSource;
+using dom::BasicTrackSource;
 using dom::ConstrainDOMStringParameters;
 using dom::File;
 using dom::GetUserMediaRequest;
 using dom::MediaSourceEnum;
 using dom::MediaStreamConstraints;
 using dom::MediaStreamError;
 using dom::MediaStreamTrack;
 using dom::MediaStreamTrackSource;
@@ -1017,17 +1017,17 @@ public:
 
   already_AddRefed<dom::MediaStreamTrackSource>
   GetMediaStreamTrackSource(TrackID aInputTrackID) override
   {
     NS_ASSERTION(kAudioTrack != aInputTrackID,
                  "Only fake tracks should appear dynamically");
     NS_ASSERTION(kVideoTrack != aInputTrackID,
                  "Only fake tracks should appear dynamically");
-    return do_AddRef(new BasicUnstoppableTrackSource(mPrincipal));
+    return do_AddRef(new BasicTrackSource(mPrincipal));
   }
 
 protected:
   virtual ~FakeTrackSourceGetter() {}
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
@@ -1165,17 +1165,17 @@ public:
       {
       public:
         LocalTrackSource(nsIPrincipal* aPrincipal,
                          const nsString& aLabel,
                          GetUserMediaCallbackMediaStreamListener* aListener,
                          const MediaSourceEnum aSource,
                          const TrackID aTrackID,
                          const PeerIdentity* aPeerIdentity)
-          : MediaStreamTrackSource(aPrincipal, false, aLabel), mListener(aListener),
+          : MediaStreamTrackSource(aPrincipal, aLabel), mListener(aListener),
             mSource(aSource), mTrackID(aTrackID), mPeerIdentity(aPeerIdentity) {}
 
         MediaSourceEnum GetMediaSource() const override
         {
           return mSource;
         }
 
         const PeerIdentity* GetPeerIdentity() const override
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -119,21 +119,17 @@ private:
 #endif
 #ifdef MOZ_FFVPX
   DECL_MEDIA_PREF("media.ffvpx.enabled",                      PDMFFVPXEnabled, bool, true);
 #endif
 #ifdef XP_WIN
   DECL_MEDIA_PREF("media.wmf.enabled",                        PDMWMFEnabled, bool, true);
   DECL_MEDIA_PREF("media.decoder-doctor.wmf-disabled-is-failure", DecoderDoctorWMFDisabledIsFailure, bool, false);
   DECL_MEDIA_PREF("media.wmf.vp9.enabled",                    PDMWMFVP9DecoderEnabled, bool, true);
-  DECL_MEDIA_PREF("media.wmf.low-latency.enabled",            PDMWMFLowLatencyEnabled, bool, false);
   DECL_MEDIA_PREF("media.wmf.decoder.thread-count",           PDMWMFThreadCount, int32_t, -1);
-  DECL_MEDIA_PREF("media.wmf.skip-blacklist",                 PDMWMFSkipBlacklist, bool, false);
-  DECL_MEDIA_PREF("media.windows-media-foundation.max-dxva-videos", PDMWMFMaxDXVAVideos, uint32_t, 8);
-  DECL_MEDIA_PREF("media.windows-media-foundation.allow-d3d11-dxva", PDMWMFAllowD3D11, bool, true);
 #endif
   DECL_MEDIA_PREF("media.decoder.fuzzing.enabled",            PDMFuzzingEnabled, bool, false);
   DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0);
   DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true);
   DECL_MEDIA_PREF("media.gmp.decoder.enabled",                PDMGMPEnabled, bool, true);
   DECL_MEDIA_PREF("media.gmp.decoder.aac",                    GMPAACPreferred, uint32_t, 0);
   DECL_MEDIA_PREF("media.gmp.decoder.h264",                   GMPH264Preferred, uint32_t, 0);
 
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -48,16 +48,24 @@ MediaStreamTrackSource::ApplyConstraints
 {
   RefPtr<PledgeVoid> p = new PledgeVoid();
   p->Reject(new MediaStreamError(aWindow,
                                  NS_LITERAL_STRING("OverconstrainedError"),
                                  NS_LITERAL_STRING("")));
   return p.forget();
 }
 
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamTrackConsumer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamTrackConsumer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackConsumer)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_0(MediaStreamTrackConsumer)
+
 /**
  * PrincipalHandleListener monitors changes in PrincipalHandle of the media flowing
  * through the MediaStreamGraph.
  *
  * When the main thread principal for a MediaStreamTrack changes, its principal
  * will be set to the combination of the previous principal and the new one.
  *
  * As a PrincipalHandle change later happens on the MediaStreamGraph thread, we will
@@ -111,18 +119,17 @@ protected:
 MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
                                    TrackID aInputTrackID,
                                    MediaStreamTrackSource* aSource,
                                    const MediaTrackConstraints& aConstraints)
   : mOwningStream(aStream), mTrackID(aTrackID),
     mInputTrackID(aInputTrackID), mSource(aSource),
     mPrincipal(aSource->GetPrincipal()),
     mReadyState(MediaStreamTrackState::Live),
-    mEnabled(true), mRemote(aSource->IsRemote()),
-    mConstraints(aConstraints)
+    mEnabled(true), mConstraints(aConstraints)
 {
 
   GetSource().RegisterSink(this);
 
   mPrincipalHandleListener = new PrincipalHandleListener(this);
   AddListener(mPrincipalHandleListener);
 
   nsresult rv;
@@ -166,25 +173,27 @@ MediaStreamTrack::Destroy()
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrack)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaStreamTrack,
                                                 DOMEventTargetHelper)
   tmp->Destroy();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamTrack,
                                                   DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsumers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningStream)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalTrack)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPrincipal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamTrack, DOMEventTargetHelper)
@@ -221,35 +230,32 @@ MediaStreamTrack::Stop()
 {
   LOG(LogLevel::Info, ("MediaStreamTrack %p Stop()", this));
 
   if (Ended()) {
     LOG(LogLevel::Warning, ("MediaStreamTrack %p Already ended", this));
     return;
   }
 
-  if (mRemote) {
-    LOG(LogLevel::Warning, ("MediaStreamTrack %p is remote. Can't be stopped.", this));
-    return;
-  }
-
   if (!mSource) {
     MOZ_ASSERT(false);
     return;
   }
 
   mSource->UnregisterSink(this);
 
   MOZ_ASSERT(mOwningStream, "Every MediaStreamTrack needs an owning DOMMediaStream");
   DOMMediaStream::TrackPort* port = mOwningStream->FindOwnedTrackPort(*this);
   MOZ_ASSERT(port, "A MediaStreamTrack must exist in its owning DOMMediaStream");
   RefPtr<Pledge<bool>> p = port->BlockSourceTrackId(mInputTrackID, BlockingMode::CREATION);
   Unused << p;
 
   mReadyState = MediaStreamTrackState::Ended;
+
+  NotifyEnded();
 }
 
 void
 MediaStreamTrack::GetConstraints(dom::MediaTrackConstraints& aResult)
 {
   aResult = mConstraints;
 }
 
@@ -352,30 +358,55 @@ MediaStreamTrack::NotifyPrincipalHandleC
                        this, GetPrincipalFromHandle(handle),
                        mPrincipal.get(), mPendingPrincipal.get()));
   if (PrincipalHandleMatches(handle, mPendingPrincipal)) {
     SetPrincipal(mPendingPrincipal);
     mPendingPrincipal = nullptr;
   }
 }
 
+void
+MediaStreamTrack::NotifyEnded()
+{
+  MOZ_ASSERT(mReadyState == MediaStreamTrackState::Ended);
+
+  for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
+    // Loop backwards by index in case the consumer removes itself in the
+    // callback.
+    mConsumers[i]->NotifyEnded(this);
+  }
+}
+
 bool
 MediaStreamTrack::AddPrincipalChangeObserver(
   PrincipalChangeObserver<MediaStreamTrack>* aObserver)
 {
   return mPrincipalChangeObservers.AppendElement(aObserver) != nullptr;
 }
 
 bool
 MediaStreamTrack::RemovePrincipalChangeObserver(
   PrincipalChangeObserver<MediaStreamTrack>* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
+void
+MediaStreamTrack::AddConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+  MOZ_ASSERT(!mConsumers.Contains(aConsumer));
+  mConsumers.AppendElement(aConsumer);
+}
+
+void
+MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer)
+{
+  mConsumers.RemoveElement(aConsumer);
+}
+
 already_AddRefed<MediaStreamTrack>
 MediaStreamTrack::Clone()
 {
   // MediaStreamTracks are currently governed by streams, so we need a dummy
   // DOMMediaStream to own our track clone. The dummy will never see any
   // dynamically created tracks (no input stream) so no need for a SourceGetter.
   RefPtr<DOMMediaStream> newStream =
     new DOMMediaStream(mOwningStream->GetParentObject(), nullptr);
@@ -399,17 +430,17 @@ MediaStreamTrack::SetReadyState(MediaStr
       mSource) {
     mSource->UnregisterSink(this);
   }
 
   mReadyState = aState;
 }
 
 void
-MediaStreamTrack::NotifyEnded()
+MediaStreamTrack::OverrideEnded()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (Ended()) {
     return;
   }
 
   LOG(LogLevel::Info, ("MediaStreamTrack %p ended", this));
@@ -418,16 +449,18 @@ MediaStreamTrack::NotifyEnded()
     MOZ_ASSERT(false);
     return;
   }
 
   mSource->UnregisterSink(this);
 
   mReadyState = MediaStreamTrackState::Ended;
 
+  NotifyEnded();
+
   DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
 }
 
 DOMMediaStream*
 MediaStreamTrack::GetInputDOMStream()
 {
   MediaStreamTrack* originalTrack =
     mOriginalTrack ? mOriginalTrack.get() : this;
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -56,20 +56,18 @@ class MediaStreamTrackSource : public ns
 public:
   class Sink
   {
   public:
     virtual void PrincipalChanged() = 0;
   };
 
   MediaStreamTrackSource(nsIPrincipal* aPrincipal,
-                         const bool aIsRemote,
                          const nsString& aLabel)
     : mPrincipal(aPrincipal),
-      mIsRemote(aIsRemote),
       mLabel(aLabel),
       mStopped(false)
   {
     MOZ_COUNT_CTOR(MediaStreamTrackSource);
   }
 
   /**
    * Use to clean up any resources that have to be cleaned before the
@@ -102,22 +100,16 @@ public:
    * nsNullPrincipal.
    *
    * A track's PeerIdentity is immutable and will not change during the track's
    * lifetime.
    */
   virtual const PeerIdentity* GetPeerIdentity() const { return nullptr; }
 
   /**
-   * Indicates whether the track is remote or not per the MediaCapture and
-   * Streams spec.
-   */
-  virtual bool IsRemote() const { return mIsRemote; }
-
-  /**
    * MediaStreamTrack::GetLabel (see spec) calls through to here.
    */
   void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); }
 
   /**
    * Forwards a photo request to backends that support it. Other backends return
    * NS_ERROR_NOT_IMPLEMENTED to indicate that a MediaStreamGraph-based fallback
    * should be used.
@@ -159,17 +151,18 @@ public:
 
   /**
    * Called by each MediaStreamTrack clone on Stop() if supported by the
    * source (us) or destruction.
    */
   void UnregisterSink(Sink* aSink)
   {
     MOZ_ASSERT(NS_IsMainThread());
-    if (mSinks.RemoveElement(aSink) && mSinks.IsEmpty() && !IsRemote()) {
+    if (mSinks.RemoveElement(aSink) && mSinks.IsEmpty()) {
+      MOZ_ASSERT(!mStopped);
       Stop();
       mStopped = true;
     }
   }
 
 protected:
   virtual ~MediaStreamTrackSource()
   {
@@ -188,54 +181,69 @@ protected:
   }
 
   // Principal identifying who may access the contents of this source.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // Currently registered sinks.
   nsTArray<Sink*> mSinks;
 
-  // True if this is a remote track source, i.e., a PeerConnection.
-  const bool mIsRemote;
-
   // The label of the track we are the source of per the MediaStreamTrack spec.
   const nsString mLabel;
 
-  // True if this source is not remote, all MediaStreamTrack users have
-  // unregistered from this source and Stop() has been called.
+  // True if all MediaStreamTrack users have unregistered from this source and
+  // Stop() has been called.
   bool mStopped;
 };
 
 /**
- * Basic implementation of MediaStreamTrackSource that ignores Stop().
+ * Basic implementation of MediaStreamTrackSource that doesn't forward Stop().
  */
-class BasicUnstoppableTrackSource : public MediaStreamTrackSource
+class BasicTrackSource : public MediaStreamTrackSource
 {
 public:
-  explicit BasicUnstoppableTrackSource(nsIPrincipal* aPrincipal,
-                                       const MediaSourceEnum aMediaSource =
-                                         MediaSourceEnum::Other)
-    : MediaStreamTrackSource(aPrincipal, true, nsString())
+  explicit BasicTrackSource(nsIPrincipal* aPrincipal,
+                            const MediaSourceEnum aMediaSource =
+                            MediaSourceEnum::Other)
+    : MediaStreamTrackSource(aPrincipal, nsString())
     , mMediaSource(aMediaSource)
   {}
 
   MediaSourceEnum GetMediaSource() const override { return mMediaSource; }
 
-  void
-  GetSettings(dom::MediaTrackSettings& aResult) override {}
-
   void Stop() override {}
 
 protected:
-  ~BasicUnstoppableTrackSource() {}
+  ~BasicTrackSource() {}
 
   const MediaSourceEnum mMediaSource;
 };
 
 /**
+ * Base class that consumers of a MediaStreamTrack can use to get notifications
+ * about state changes in the track.
+ */
+class MediaStreamTrackConsumer : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(MediaStreamTrackConsumer)
+
+  /**
+   * Called when the track's readyState transitions to "ended".
+   * Unlike the "ended" event exposed to script this is called for any reason,
+   * including MediaStreamTrack::Stop().
+   */
+  virtual void NotifyEnded(MediaStreamTrack* aTrack) {};
+
+protected:
+  virtual ~MediaStreamTrackConsumer() {}
+};
+
+/**
  * Class representing a track in a DOMMediaStream.
  */
 class MediaStreamTrack : public DOMEventTargetHelper,
                          public MediaStreamTrackSource::Sink
 {
   // DOMMediaStream owns MediaStreamTrack instances, and requires access to
   // some internal state, e.g., GetInputStream(), GetOwnedStream().
   friend class mozilla::DOMMediaStream;
@@ -301,31 +309,37 @@ public:
 
   /**
    * Notified by the MediaStreamGraph, through our owning MediaStream on the
    * main thread.
    *
    * Note that this sets the track to ended and raises the "ended" event
    * synchronously.
    */
-  void NotifyEnded();
+  void OverrideEnded();
 
   /**
    * Get this track's principal.
    */
   nsIPrincipal* GetPrincipal() const { return mPrincipal; }
 
   /**
    * Called by the PrincipalHandleListener when this track's PrincipalHandle changes on
    * the MediaStreamGraph thread. When the PrincipalHandle matches the pending
    * principal we know that the principal change has propagated to consumers.
    */
   void NotifyPrincipalHandleChanged(const PrincipalHandle& aPrincipalHandle);
 
   /**
+   * Called when this track's readyState transitions to "ended".
+   * Notifies all MediaStreamTrackConsumers that this track ended.
+   */
+  void NotifyEnded();
+
+  /**
    * Get this track's CORS mode.
    */
   CORSMode GetCORSMode() const { return GetSource().GetCORSMode(); }
 
   /**
    * Get this track's PeerIdentity.
    */
   const PeerIdentity* GetPeerIdentity() const { return GetSource().GetPeerIdentity(); }
@@ -359,16 +373,28 @@ public:
   /**
    * Remove an added PrincipalChangeObserver from this track.
    *
    * Returns true if it was successfully removed.
    */
   bool RemovePrincipalChangeObserver(PrincipalChangeObserver<MediaStreamTrack>* aObserver);
 
   /**
+   * Add a MediaStreamTrackConsumer to this track.
+   *
+   * Adding the same consumer multiple times is prohibited.
+   */
+  void AddConsumer(MediaStreamTrackConsumer* aConsumer);
+
+  /**
+   * Remove an added MediaStreamTrackConsumer from this track.
+   */
+  void RemoveConsumer(MediaStreamTrackConsumer* aConsumer);
+
+  /**
    * Adds a MediaStreamTrackListener to the MediaStreamGraph representation of
    * this track.
    */
   void AddListener(MediaStreamTrackListener* aListener);
 
   /**
    * Removes a MediaStreamTrackListener from the MediaStreamGraph representation
    * of this track.
@@ -424,31 +450,32 @@ protected:
    * source as this MediaStreamTrack.
    * aTrackID is the TrackID the new track will have in its owned stream.
    */
   virtual already_AddRefed<MediaStreamTrack> CloneInternal(DOMMediaStream* aOwningStream,
                                                            TrackID aTrackID) = 0;
 
   nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers;
 
+  nsTArray<RefPtr<MediaStreamTrackConsumer>> mConsumers;
+
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   TrackID mInputTrackID;
   RefPtr<MediaStreamTrackSource> mSource;
   RefPtr<MediaStreamTrack> mOriginalTrack;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mPendingPrincipal;
   RefPtr<PrincipalHandleListener> mPrincipalHandleListener;
   // Keep tracking MediaStreamTrackListener and DirectMediaStreamTrackListener,
   // so we can remove them in |Destory|.
   nsTArray<RefPtr<MediaStreamTrackListener>> mTrackListeners;
   nsTArray<RefPtr<DirectMediaStreamTrackListener>> mDirectTrackListeners;
   nsString mID;
   MediaStreamTrackState mReadyState;
   bool mEnabled;
-  const bool mRemote;
   dom::MediaTrackConstraints mConstraints;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/ipc/RemoteVideoDecoder.cpp
+++ b/dom/media/ipc/RemoteVideoDecoder.cpp
@@ -120,17 +120,17 @@ RemoteVideoDecoder::SetSeekThreshold(con
     self->mActor->SetSeekThreshold(time);
   }), NS_DISPATCH_NORMAL);
 
 }
 
 nsresult
 RemoteDecoderModule::Startup()
 {
-  if (!VideoDecoderManagerChild::GetSingleton()) {
+  if (!VideoDecoderManagerChild::GetManagerThread()) {
     return NS_ERROR_FAILURE;
   }
   return mWrapped->Startup();
 }
 
 bool
 RemoteDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                       DecoderDoctorDiagnostics* aDiagnostics) const
@@ -142,17 +142,18 @@ PlatformDecoderModule::ConversionRequire
 RemoteDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   return mWrapped->DecoderNeedsConversion(aConfig);
 }
 
 already_AddRefed<MediaDataDecoder>
 RemoteDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  if (!aParams.mKnowsCompositor) {
+  if (!aParams.mKnowsCompositor ||
+      aParams.mKnowsCompositor->GetTextureFactoryIdentifier().mParentProcessType != GeckoProcessType_GPU) {
     return nullptr;
   }
 
   MediaDataDecoderCallback* callback = aParams.mCallback;
   MOZ_ASSERT(callback->OnReaderTaskQueue());
   RefPtr<RemoteVideoDecoder> object = new RemoteVideoDecoder(callback);
 
   VideoInfo info = aParams.VideoConfig();
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -16,17 +16,17 @@ namespace dom {
 
 using base::Thread;
 using namespace ipc;
 using namespace layers;
 using namespace gfx;
 
 VideoDecoderChild::VideoDecoderChild()
   : mThread(VideoDecoderManagerChild::GetManagerThread())
-  , mCanSend(true)
+  , mCanSend(false)
   , mInitialized(false)
   , mIsHardwareAccelerated(false)
 {
 }
 
 VideoDecoderChild::~VideoDecoderChild()
 {
   AssertOnManagerThread();
@@ -37,17 +37,17 @@ bool
 VideoDecoderChild::RecvOutput(const VideoDataIPDL& aData)
 {
   AssertOnManagerThread();
   VideoInfo info(aData.display().width, aData.display().height);
 
   // The Image here creates a TextureData object that takes ownership
   // of the SurfaceDescriptor, and is responsible for making sure that
   // it gets deallocated.
-  RefPtr<Image> image = new GPUVideoImage(aData.sd(), aData.display());
+  RefPtr<Image> image = new GPUVideoImage(GetManager(), aData.sd(), aData.display());
 
   RefPtr<VideoData> video = VideoData::CreateFromImage(info,
                                                        aData.base().offset(),
                                                        aData.base().time(),
                                                        aData.base().duration(),
                                                        image,
                                                        aData.base().keyframe(),
                                                        aData.base().timecode(),
@@ -112,21 +112,27 @@ VideoDecoderChild::ActorDestroy(ActorDes
   mCanSend = false;
 }
 
 void
 VideoDecoderChild::InitIPDL(MediaDataDecoderCallback* aCallback,
                             const VideoInfo& aVideoInfo,
                             layers::KnowsCompositor* aKnowsCompositor)
 {
-  VideoDecoderManagerChild::GetSingleton()->SendPVideoDecoderConstructor(this);
+  RefPtr<VideoDecoderManagerChild> manager = VideoDecoderManagerChild::GetSingleton();
+  if (!manager) {
+    return;
+  }
   mIPDLSelfRef = this;
   mCallback = aCallback;
   mVideoInfo = aVideoInfo;
   mKnowsCompositor = aKnowsCompositor;
+  if (manager->SendPVideoDecoderConstructor(this)) {
+    mCanSend = true;
+  }
 }
 
 void
 VideoDecoderChild::DestroyIPDL()
 {
   if (mCanSend) {
     PVideoDecoderChild::Send__delete__(this);
   }
@@ -228,10 +234,19 @@ VideoDecoderChild::SetSeekThreshold(cons
 }
 
 void
 VideoDecoderChild::AssertOnManagerThread()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mThread);
 }
 
+VideoDecoderManagerChild*
+VideoDecoderChild::GetManager()
+{
+  if (!mCanSend) {
+    return nullptr;
+  }
+  return static_cast<VideoDecoderManagerChild*>(Manager());
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -11,16 +11,17 @@
 #include "MediaData.h"
 #include "PlatformDecoderModule.h"
 
 namespace mozilla {
 namespace dom {
 
 class RemoteVideoDecoder;
 class RemoteDecoderModule;
+class VideoDecoderManagerChild;
 
 class VideoDecoderChild final : public PVideoDecoderChild
 {
 public:
   explicit VideoDecoderChild();
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderChild)
 
@@ -46,16 +47,18 @@ public:
   void InitIPDL(MediaDataDecoderCallback* aCallback,
                 const VideoInfo& aVideoInfo,
                 layers::KnowsCompositor* aKnowsCompositor);
   void DestroyIPDL();
 
   // Called from IPDL when our actor has been destroyed
   void IPDLActorDestroyed();
 
+  VideoDecoderManagerChild* GetManager();
+
 private:
   ~VideoDecoderChild();
 
   void AssertOnManagerThread();
 
   RefPtr<VideoDecoderChild> mIPDLSelfRef;
   RefPtr<nsIThread> mThread;
 
--- a/dom/media/ipc/VideoDecoderManagerChild.cpp
+++ b/dom/media/ipc/VideoDecoderManagerChild.cpp
@@ -3,26 +3,30 @@
 /* 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 "VideoDecoderManagerChild.h"
 #include "VideoDecoderChild.h"
 #include "mozilla/dom/ContentChild.h"
 #include "MediaPrefs.h"
 #include "nsThreadUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace ipc;
 using namespace layers;
 using namespace gfx;
 
+// Only modified on the main-thread
 StaticRefPtr<nsIThread> sVideoDecoderChildThread;
 StaticRefPtr<AbstractThread> sVideoDecoderChildAbstractThread;
+
+// Only accessed from sVideoDecoderChildThread
 static StaticRefPtr<VideoDecoderManagerChild> sDecoderManager;
 
 /* static */ void
 VideoDecoderManagerChild::Initialize()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MediaPrefs::GetSingleton();
@@ -41,60 +45,68 @@ VideoDecoderManagerChild::Initialize()
     RefPtr<nsIThread> childThread;
     nsresult rv = NS_NewNamedThread("VideoChild", getter_AddRefs(childThread));
     NS_ENSURE_SUCCESS_VOID(rv);
     sVideoDecoderChildThread = childThread;
 
     sVideoDecoderChildAbstractThread =
       AbstractThread::CreateXPCOMThreadWrapper(childThread, false);
   }
-
-  Endpoint<PVideoDecoderManagerChild> endpoint;
-  if (!ContentChild::GetSingleton()->SendInitVideoDecoderManager(&endpoint)) {
-    return;
-  }
-
-  if (!endpoint.IsValid()) {
-    return;
-  }
-
-  sDecoderManager = new VideoDecoderManagerChild();
-
-  RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PVideoDecoderManagerChild>&&>(
-    sDecoderManager, &VideoDecoderManagerChild::Open, Move(endpoint));
-  sVideoDecoderChildThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
 #else
   return;
 #endif
 
 }
 
 /* static */ void
 VideoDecoderManagerChild::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sVideoDecoderChildThread) {
-    MOZ_ASSERT(sDecoderManager);
-
     sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([]() {
-      sDecoderManager->Close();
-    }), NS_DISPATCH_SYNC);
-
-    sDecoderManager = nullptr;
+      if (sDecoderManager) {
+        sDecoderManager->Close();
+        sDecoderManager = nullptr;
+      }
+    }), NS_DISPATCH_NORMAL);
 
     sVideoDecoderChildAbstractThread = nullptr;
     sVideoDecoderChildThread->Shutdown();
     sVideoDecoderChildThread = nullptr;
   }
 }
 
 /* static */ VideoDecoderManagerChild*
 VideoDecoderManagerChild::GetSingleton()
 {
+  MOZ_ASSERT(NS_GetCurrentThread() == GetManagerThread());
+
+  if (!sDecoderManager || !sDecoderManager->mCanSend) {
+    RefPtr<VideoDecoderManagerChild> manager;
+      
+    NS_DispatchToMainThread(NS_NewRunnableFunction([&]() {
+      Endpoint<PVideoDecoderManagerChild> endpoint;
+      if (!ContentChild::GetSingleton()->SendInitVideoDecoderManager(&endpoint)) {
+        return;
+      }
+
+      if (!endpoint.IsValid()) {
+        return;
+      }
+
+      manager = new VideoDecoderManagerChild();
+
+      RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PVideoDecoderManagerChild>&&>(
+        manager, &VideoDecoderManagerChild::Open, Move(endpoint));
+      sVideoDecoderChildThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+    }), NS_DISPATCH_SYNC);
+
+    sDecoderManager = manager;
+  }
   return sDecoderManager;
 }
 
 /* static */ nsIThread*
 VideoDecoderManagerChild::GetManagerThread()
 {
   return sVideoDecoderChildThread;
 }
@@ -118,35 +130,43 @@ VideoDecoderManagerChild::DeallocPVideoD
   child->IPDLActorDestroyed();
   return true;
 }
 
 void
 VideoDecoderManagerChild::Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint)
 {
   if (!aEndpoint.Bind(this)) {
-    // We can't recover from this.
-    MOZ_CRASH("Failed to bind VideoDecoderChild to endpoint");
+    return;
   }
   AddRef();
+  mCanSend = true;
+}
+
+void
+VideoDecoderManagerChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mCanSend = false;
 }
 
 void
 VideoDecoderManagerChild::DeallocPVideoDecoderManagerChild()
 {
   Release();
 }
 
 void
 VideoDecoderManagerChild::DeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD)
 {
   RefPtr<VideoDecoderManagerChild> ref = this;
   SurfaceDescriptorGPUVideo sd = Move(aSD);
   sVideoDecoderChildThread->Dispatch(NS_NewRunnableFunction([ref, sd]() {
-    ref->SendDeallocateSurfaceDescriptorGPUVideo(sd);
+    if (ref->mCanSend) {
+      ref->SendDeallocateSurfaceDescriptorGPUVideo(sd);
+    }
   }), NS_DISPATCH_NORMAL);
 }
 
 void
 VideoDecoderManagerChild::FatalError(const char* const aName, const char* const aMsg) const
 {
   dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aName, aMsg, OtherPid());
 }
--- a/dom/media/ipc/VideoDecoderManagerChild.h
+++ b/dom/media/ipc/VideoDecoderManagerChild.h
@@ -12,39 +12,48 @@
 namespace mozilla {
 namespace dom {
 
 class VideoDecoderManagerChild final : public PVideoDecoderManagerChild
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderManagerChild)
 
+  // Can only be called from the manager thread
   static VideoDecoderManagerChild* GetSingleton();
+
+  // Can be called from any thread.
   static nsIThread* GetManagerThread();
   static AbstractThread* GetManagerAbstractThread();
 
-  // Can be called from any thread, dispatches the request to the IPDL thread internally.
+  // Can be called from any thread, dispatches the request to the IPDL thread internally
+  // and will be ignored if the IPDL actor has been destroyed.
   void DeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD);
 
-  void DeallocPVideoDecoderManagerChild() override;
-
-  void FatalError(const char* const aName, const char* const aMsg) const override;
-
   // Main thread only
   static void Initialize();
   static void Shutdown();
 
 protected:
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+  void DeallocPVideoDecoderManagerChild() override;
+
+  void FatalError(const char* const aName, const char* const aMsg) const override;
+
   PVideoDecoderChild* AllocPVideoDecoderChild() override;
   bool DeallocPVideoDecoderChild(PVideoDecoderChild* actor) override;
 
 private:
   VideoDecoderManagerChild()
+    : mCanSend(false)
   {}
   ~VideoDecoderManagerChild() {}
 
   void Open(Endpoint<PVideoDecoderManagerChild>&& aEndpoint);
+
+  // Should only ever be accessed on the manager thread.
+  bool mCanSend;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // include_dom_ipc_VideoDecoderManagerChild_h
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -369,20 +369,18 @@ PDMFactory::CreatePDMs()
     // compatibility mode on Windows 7 (it does happen!) we may crash trying
     // to startup WMF. So we need to detect the OS version here, as in
     // compatibility mode IsVistaOrLater() and friends behave as if we're on
     // the emulated version of Windows. See bug 1279171.
     // Additionally, we don't want to start the RemoteDecoderModule if we
     // expect it's not going to work (i.e. on Windows older than Vista).
     m = new WMFDecoderModule();
     RefPtr<PlatformDecoderModule> remote = new dom::RemoteDecoderModule(m);
-    mWMFFailedToLoad = !StartupPDM(remote);
-    if (mWMFFailedToLoad) {
-      mWMFFailedToLoad = !StartupPDM(m);
-    }
+    StartupPDM(remote);
+    mWMFFailedToLoad = !StartupPDM(m);
   } else {
     mWMFFailedToLoad = MediaPrefs::DecoderDoctorWMFDisabledIsFailure();
   }
 #endif
 #ifdef MOZ_FFVPX
   if (MediaPrefs::PDMFFVPXEnabled()) {
     m = FFVPXRuntimeLinker::CreateDecoderModule();
     StartupPDM(m);
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -12,17 +12,17 @@
 #include "D3D9SurfaceImage.h"
 #include "mozilla/gfx/DeviceManagerDx.h"
 #include "mozilla/layers/D3D11ShareHandleImage.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/TextureForwarder.h"
 #include "mozilla/Telemetry.h"
 #include "MediaTelemetryConstants.h"
 #include "mfapi.h"
-#include "MediaPrefs.h"
+#include "gfxPrefs.h"
 #include "MFTDecoder.h"
 #include "DriverCrashGuard.h"
 #include "nsPrintfCString.h"
 #include "gfxCrashReporterUtils.h"
 
 const CLSID CLSID_VideoProcessorMFT =
 {
   0x88753b26,
@@ -403,17 +403,17 @@ D3D9DXVA2Manager::Init(layers::KnowsComp
 
   D3DADAPTER_IDENTIFIER9 adapter;
   hr = d3d9Ex->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapter);
   if (!SUCCEEDED(hr)) {
     aFailureReason = nsPrintfCString("IDirect3D9Ex::GetAdapterIdentifier failed with error %X", hr);
     return hr;
   }
 
-  if (adapter.VendorId == 0x1022 && !MediaPrefs::PDMWMFSkipBlacklist()) {
+  if (adapter.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) {
     for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sAMDPreUVD4); i++) {
       if (adapter.DeviceId == sAMDPreUVD4[i]) {
         mIsAMDPreUVD4 = true;
         break;
       }
     }
   }
 
@@ -498,21 +498,17 @@ DXVA2Manager*
 DXVA2Manager::CreateD3D9DXVA(layers::KnowsCompositor* aKnowsCompositor,
                              nsACString& aFailureReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
   HRESULT hr;
 
   // DXVA processing takes up a lot of GPU resources, so limit the number of
   // videos we use DXVA with at any one time.
-  uint32_t dxvaLimit = 4;
-  // TODO: Sync this value across to the GPU process.
-  if (XRE_GetProcessType() != GeckoProcessType_GPU) {
-    dxvaLimit = MediaPrefs::PDMWMFMaxDXVAVideos();
-  }
+  uint32_t dxvaLimit = gfxPrefs::PDMWMFMaxDXVAVideos();
 
   if (sDXVAVideosCount == dxvaLimit) {
     aFailureReason.AssignLiteral("Too many DXVA videos playing");
     return nullptr;
   }
 
   nsAutoPtr<D3D9DXVA2Manager> d3d9Manager(new D3D9DXVA2Manager());
   hr = d3d9Manager->Init(aKnowsCompositor, aFailureReason);
@@ -740,17 +736,17 @@ D3D11DXVA2Manager::Init(layers::KnowsCom
 
   DXGI_ADAPTER_DESC adapterDesc;
   hr = adapter->GetDesc(&adapterDesc);
   if (!SUCCEEDED(hr)) {
     aFailureReason = nsPrintfCString("IDXGIAdapter::GetDesc failed with code %X", hr);
     return hr;
   }
 
-  if (adapterDesc.VendorId == 0x1022 && !MediaPrefs::PDMWMFSkipBlacklist()) {
+  if (adapterDesc.VendorId == 0x1022 && !gfxPrefs::PDMWMFSkipBlacklist()) {
     for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sAMDPreUVD4); i++) {
       if (adapterDesc.DeviceId == sAMDPreUVD4[i]) {
         mIsAMDPreUVD4 = true;
         break;
       }
     }
   }
 
@@ -919,21 +915,17 @@ D3D11DXVA2Manager::ConfigureForSize(uint
 
 /* static */
 DXVA2Manager*
 DXVA2Manager::CreateD3D11DXVA(layers::KnowsCompositor* aKnowsCompositor,
                               nsACString& aFailureReason)
 {
   // DXVA processing takes up a lot of GPU resources, so limit the number of
   // videos we use DXVA with at any one time.
-  uint32_t dxvaLimit = 4;
-  // TODO: Sync this value across to the GPU process.
-  if (XRE_GetProcessType() != GeckoProcessType_GPU) {
-    dxvaLimit = MediaPrefs::PDMWMFMaxDXVAVideos();
-  }
+  uint32_t dxvaLimit = gfxPrefs::PDMWMFMaxDXVAVideos();
 
   if (sDXVAVideosCount == dxvaLimit) {
     aFailureReason.AssignLiteral("Too many DXVA videos playing");
     return nullptr;
   }
 
   nsAutoPtr<D3D11DXVA2Manager> manager(new D3D11DXVA2Manager());
   HRESULT hr = manager->Init(aKnowsCompositor, aFailureReason);
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -3,29 +3,27 @@
 /* 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 <algorithm>
 #include <winsdkver.h>
 #include "WMFVideoMFTManager.h"
 #include "MediaDecoderReader.h"
-#include "MediaPrefs.h"
+#include "gfxPrefs.h"
 #include "WMFUtils.h"
 #include "ImageContainer.h"
 #include "VideoUtils.h"
 #include "DXVA2Manager.h"
 #include "nsThreadUtils.h"
 #include "Layers.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "MediaInfo.h"
-#include "MediaPrefs.h"
 #include "mozilla/Logging.h"
-#include "mozilla/Preferences.h"
 #include "nsWindowsHelpers.h"
 #include "gfx2DGlue.h"
 #include "gfxWindowsPlatform.h"
 #include "IMFYCbCrImage.h"
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/Telemetry.h"
 #include "nsPrintfCString.h"
 #include "MediaTelemetryConstants.h"
@@ -165,55 +163,47 @@ struct D3DDLLBlacklistingCache
   nsCString mBlacklistedDLL;
 };
 StaticAutoPtr<D3DDLLBlacklistingCache> sD3D11BlacklistingCache;
 StaticAutoPtr<D3DDLLBlacklistingCache> sD3D9BlacklistingCache;
 
 // If a blacklisted DLL is found, return its information, otherwise "".
 static const nsCString&
 FindDXVABlacklistedDLL(StaticAutoPtr<D3DDLLBlacklistingCache>& aDLLBlacklistingCache,
+                       const nsCString& aBlacklist,
                        const char* aDLLBlacklistPrefName)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
 
   if (!aDLLBlacklistingCache) {
     // First time here, create persistent data that will be reused in all
     // D3D11-blacklisting checks.
     aDLLBlacklistingCache = new D3DDLLBlacklistingCache();
     ClearOnShutdown(&aDLLBlacklistingCache);
   }
 
-  if (XRE_GetProcessType() == GeckoProcessType_GPU) {
-    // The blacklist code doesn't support running in
-    // the GPU process yet.
-    aDLLBlacklistingCache->mBlacklistPref.SetLength(0);
-    aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
-    return aDLLBlacklistingCache->mBlacklistedDLL;
-  }
-
-  nsAdoptingCString blacklist = Preferences::GetCString(aDLLBlacklistPrefName);
-  if (blacklist.IsEmpty()) {
+  if (aBlacklist.IsEmpty()) {
     // Empty blacklist -> No blacklisting.
     aDLLBlacklistingCache->mBlacklistPref.SetLength(0);
     aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
     return aDLLBlacklistingCache->mBlacklistedDLL;
   }
 
   // Detect changes in pref.
-  if (aDLLBlacklistingCache->mBlacklistPref.Equals(blacklist)) {
+  if (aDLLBlacklistingCache->mBlacklistPref.Equals(aBlacklist)) {
     // Same blacklist -> Return same result (i.e., don't check DLLs again).
     return aDLLBlacklistingCache->mBlacklistedDLL;
   }
   // Adopt new pref now, so we don't work on it again.
-  aDLLBlacklistingCache->mBlacklistPref = blacklist;
+  aDLLBlacklistingCache->mBlacklistPref = aBlacklist;
 
   // media.wmf.disable-d3d*-for-dlls format: (whitespace is trimmed)
   // "dll1.dll: 1.2.3.4[, more versions...][; more dlls...]"
   nsTArray<nsCString> dlls;
-  SplitAt(";", blacklist, dlls);
+  SplitAt(";", aBlacklist, dlls);
   for (const auto& dll : dlls) {
     nsTArray<nsCString> nameAndVersions;
     SplitAt(":", dll, nameAndVersions);
     if (nameAndVersions.Length() != 2) {
       NS_WARNING(nsPrintfCString("Skipping incorrect '%s' dll:versions format",
                                  aDLLBlacklistPrefName).get());
       continue;
     }
@@ -288,22 +278,24 @@ FindDXVABlacklistedDLL(StaticAutoPtr<D3D
   // No blacklisted DLL.
   aDLLBlacklistingCache->mBlacklistedDLL.SetLength(0);
   return aDLLBlacklistingCache->mBlacklistedDLL;
 }
 
 static const nsCString&
 FindD3D11BlacklistedDLL() {
   return FindDXVABlacklistedDLL(sD3D11BlacklistingCache,
+                                gfx::gfxVars::PDMWMFDisableD3D11Dlls(),
                                 "media.wmf.disable-d3d11-for-dlls");
 }
 
 static const nsCString&
 FindD3D9BlacklistedDLL() {
   return FindDXVABlacklistedDLL(sD3D9BlacklistingCache,
+                                gfx::gfxVars::PDMWMFDisableD3D9Dlls(),
                                 "media.wmf.disable-d3d9-for-dlls");
 }
 
 class CreateDXVAManagerEvent : public Runnable {
 public:
   CreateDXVAManagerEvent(LayersBackend aBackend,
                          layers::KnowsCompositor* aKnowsCompositor,
                          nsCString& aFailureReason)
@@ -311,20 +303,18 @@ public:
     , mKnowsCompositor(aKnowsCompositor)
     , mFailureReason(aFailureReason)
   {}
 
   NS_IMETHOD Run() override {
     NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
     nsACString* failureReason = &mFailureReason;
     nsCString secondFailureReason;
-    bool allowD3D11 = (XRE_GetProcessType() == GeckoProcessType_GPU) ||
-                      MediaPrefs::PDMWMFAllowD3D11();
     if (mBackend == LayersBackend::LAYERS_D3D11 &&
-        allowD3D11 && IsWin8OrLater()) {
+      gfxPrefs::PDMWMFAllowD3D11() && IsWin8OrLater()) {
       const nsCString& blacklistedDLL = FindD3D11BlacklistedDLL();
       if (!blacklistedDLL.IsEmpty()) {
         failureReason->AppendPrintf("D3D11 blacklisted with DLL %s",
                                     blacklistedDLL.get());
       } else {
         mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(mKnowsCompositor, *failureReason);
         if (mDXVA2Manager) {
           return NS_OK;
@@ -442,18 +432,17 @@ WMFVideoMFTManager::InitInternal(bool aF
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   RefPtr<IMFAttributes> attr(decoder->GetAttributes());
   UINT32 aware = 0;
   if (attr) {
     attr->GetUINT32(MF_SA_D3D_AWARE, &aware);
     attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
       WMFDecoderModule::GetNumDecoderThreads());
-    if ((XRE_GetProcessType() != GeckoProcessType_GPU) &&
-        MediaPrefs::PDMWMFLowLatencyEnabled()) {
+    if (gfxPrefs::PDMWMFLowLatencyEnabled()) {
       hr = attr->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE);
       if (SUCCEEDED(hr)) {
         LOG("Enabling Low Latency Mode");
       } else {
         LOG("Couldn't enable Low Latency Mode");
       }
     }
   }
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -228,57 +228,68 @@ function realCreateHTML(meta) {
   document.body.insertBefore(display, test);
 
   var content = document.createElement('div');
   content.setAttribute('id', 'content');
   content.style.display = meta.visible ? 'block' : "none";
   document.body.appendChild(content);
 }
 
-function getMediaElement(label, direction, streamId) {
-  var id = label + '_' + direction + '_' + streamId;
-  return document.getElementById(id);
-}
-
 /**
- * Create the HTML element if it doesn't exist yet and attach
- * it to the content node.
+ * Creates an element of the given type, assigns the given id, sets the controls
+ * and autoplay attributes and adds it to the content node.
  *
- * @param {string} label
- *        Prefix to use for the element
- * @param {direction} "local" or "remote"
- * @param {stream} A MediaStream id.
- * @param {audioOnly} Use <audio> element instead of <video>
- * @return {HTMLMediaElement} The created HTML media element
+ * @param {string} type
+ *        Defining if we should create an "audio" or "video" element
+ * @param {string} id
+ *        A string to use as the element id.
  */
-function createMediaElement(label, direction, streamId, audioOnly) {
-  var id = label + '_' + direction + '_' + streamId;
-  var element = document.getElementById(id);
-
-  // Sanity check that we haven't created the element already
-  if (element) {
-    return element;
-  }
-
-  if (!audioOnly) {
-    // Even if this is just audio now, we might add video later.
-    element = document.createElement('video');
-  } else {
-    element = document.createElement('audio');
-  }
+function createMediaElement(type, id) {
+  const element = document.createElement(type);
   element.setAttribute('id', id);
   element.setAttribute('height', 100);
   element.setAttribute('width', 150);
   element.setAttribute('controls', 'controls');
   element.setAttribute('autoplay', 'autoplay');
   document.getElementById('content').appendChild(element);
 
   return element;
 }
 
+/**
+ * Returns an existing element for the given track with the given idPrefix,
+ * as it was added by createMediaElementForTrack().
+ *
+ * @param {MediaStreamTrack} track
+ *        Track used as the element's source.
+ * @param {string} idPrefix
+ *        A string to use as the element id. The track id will also be appended.
+ */
+function getMediaElementForTrack(track, idPrefix) {
+  return document.getElementById(idPrefix + '_' + track.id);
+}
+
+/**
+ * Create a media element with a track as source and attach it to the content
+ * node.
+ *
+ * @param {MediaStreamTrack} track
+ *        Track for use as source.
+ * @param {string} idPrefix
+ *        A string to use as the element id. The track id will also be appended.
+ * @return {HTMLMediaElement} The created HTML media element
+ */
+function createMediaElementForTrack(track, idPrefix) {
+  const id = idPrefix + '_' + track.id;
+  const element = createMediaElement(track.kind, id);
+  element.srcObject = new MediaStream([track]);
+
+  return element;
+}
+
 
 /**
  * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether
  * to use fake devices or not is now determined in pref further below instead.
  *
  * @param {Dictionary} constraints
  *        The constraints for this mozGetUserMedia callback
  */
@@ -425,32 +436,36 @@ function checkMediaStreamCloneAgainstOri
   isnot(clone, original,
         "Stream clone should be different from the original");
   isnot(clone.id, original.id,
         "Stream clone's id should be different from the original's");
   is(clone.getAudioTracks().length, original.getAudioTracks().length,
      "All audio tracks should get cloned");
   is(clone.getVideoTracks().length, original.getVideoTracks().length,
      "All video tracks should get cloned");
+  is(clone.active, original.active,
+     "Active state should be preserved");
   original.getTracks()
           .forEach(t => ok(!clone.getTrackById(t.id),
                            "The clone's tracks should be originals"));
 }
 
 function checkMediaStreamTrackCloneAgainstOriginal(clone, original) {
   isnot(clone.id.length, 0,
         "Track clone should have an id string");
   isnot(clone, original,
         "Track clone should be different from the original");
   isnot(clone.id, original.id,
         "Track clone's id should be different from the original's");
   is(clone.kind, original.kind,
      "Track clone's kind should be same as the original's");
   is(clone.enabled, original.enabled,
      "Track clone's kind should be same as the original's");
+  is(clone.readyState, original.readyState,
+     "Track clone's readyState should be same as the original's");
 }
 
 /*** Utility methods */
 
 /** The dreadful setTimeout, use sparingly */
 function wait(time, message) {
   return new Promise(r => setTimeout(() => r(message), time));
 }
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -43,44 +43,20 @@ MediaStreamPlayback.prototype = {
    * Stops the local media stream's tracks while it's currently in playback in
    * a media element.
    *
    * Precondition: The media stream and element should both be actively
    *               being played. All the stream's tracks must be local.
    */
   stopTracksForStreamInMediaPlayback : function () {
     var elem = this.mediaElement;
-    var waitForEnded = () => new Promise(resolve => {
-      elem.addEventListener('ended', function ended() {
-        elem.removeEventListener('ended', ended);
-        resolve();
-      });
-    });
-
-    var noTrackEnded = Promise.all(this.mediaStream.getTracks().map(t => {
-      let onNextLoop = wait(0);
-      let p = Promise.race([
-        onNextLoop,
-        haveEvent(t, "ended", onNextLoop)
-          .then(() => Promise.reject("Unexpected ended event for track " + t.id),
-                () => Promise.resolve())
-      ]);
-      t.stop();
-      return p;
-    }));
-
-    // XXX (bug 1208316) When we implement MediaStream.active, do not stop
-    // the stream. We just do it now so the media element will raise 'ended'.
-    if (!this.mediaStream.stop) {
-      return;
-    }
-    this.mediaStream.stop();
-    return timeout(waitForEnded(), ENDED_TIMEOUT_LENGTH, "ended event never fired")
-             .then(() => ok(true, "ended event successfully fired"))
-             .then(() => noTrackEnded);
+    return Promise.all([
+      haveEvent(elem, "ended", wait(ENDED_TIMEOUT_LENGTH, new Error("Timeout"))),
+      ...this.mediaStream.getTracks().map(t => (t.stop(), haveNoEvent(t, "ended")))
+    ]);
   },
 
   /**
    * Starts media with a media stream, runs it until a canplaythrough and
    * timeupdate event fires, and detaches from the element without stopping media.
    *
    * @param {Boolean} isResume specifies if this media element is being resumed
    *                           from a previous run
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -36,16 +36,17 @@ skip-if = android_version == '18' # andr
 [test_dataChannel_basicVideo.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_bug1013809.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
 [test_ondevicechange.html]
 skip-if = os == 'android'
+[test_getUserMedia_active_autoplay.html]
 [test_getUserMedia_audioCapture.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_addTrackRemoveTrack.html]
 [test_getUserMedia_addtrack_removetrack_events.html]
 [test_getUserMedia_basicAudio.html]
 [test_getUserMedia_basicVideo.html]
 [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
 [test_getUserMedia_basicScreenshare.html]
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -857,33 +857,33 @@ PeerConnectionWrapper.prototype = {
   get iceConnectionState() {
     return this._pc.iceConnectionState;
   },
 
   setIdentityProvider: function(provider, protocol, identity) {
     this._pc.setIdentityProvider(provider, protocol, identity);
   },
 
-  ensureMediaElement : function(track, stream, direction) {
-    var element = getMediaElement(this.label, direction, stream.id);
+  ensureMediaElement : function(track, direction) {
+    const idPrefix = [this.label, direction].join('_');
+    var element = getMediaElementForTrack(track, idPrefix);
 
     if (!element) {
-      element = createMediaElement(this.label, direction, stream.id,
-                                   this.audioElementsOnly);
+      element = createMediaElementForTrack(track, idPrefix);
       if (direction == "local") {
         this.localMediaElements.push(element);
       } else if (direction == "remote") {
         this.remoteMediaElements.push(element);
       }
     }
 
     // We do this regardless, because sometimes we end up with a new stream with
     // an old id (ie; the rollback tests cause the same stream to be added
     // twice)
-    element.srcObject = stream;
+    element.srcObject = new MediaStream([track]);
     element.play();
   },
 
   /**
    * Attaches a local track to this RTCPeerConnection using
    * RTCPeerConnection.addTrack().
    *
    * Also creates a media element playing a MediaStream containing all
@@ -907,24 +907,24 @@ PeerConnectionWrapper.prototype = {
     this.expectedLocalTrackInfoById[track.id] = {
       type: track.kind,
       streamId: stream.id,
     };
 
     // This will create one media element per track, which might not be how
     // we set up things with the RTCPeerConnection. It's the only way
     // we can ensure all sent tracks are flowing however.
-    this.ensureMediaElement(track, new MediaStream([track]), "local");
+    this.ensureMediaElement(track, "local");
 
     return this.observedNegotiationNeeded;
   },
 
   /**
    * Callback when we get local media. Also an appropriate HTML media element
-   * will be created, which may be obtained later with |getMediaElement|.
+   * will be created and added to the content node.
    *
    * @param {MediaStream} stream
    *        Media stream to handle
    */
   attachLocalStream : function(stream) {
     info("Got local media stream: (" + stream.id + ")");
 
     this.expectNegotiationNeeded();
@@ -945,17 +945,17 @@ PeerConnectionWrapper.prototype = {
 
     stream.getTracks().forEach(track => {
       ok(track.id, "track has id");
       ok(track.kind, "track has kind");
       this.expectedLocalTrackInfoById[track.id] = {
           type: track.kind,
           streamId: stream.id
         };
-      this.ensureMediaElement(track, stream, "local");
+      this.ensureMediaElement(track, "local");
     });
   },
 
   removeSender : function(index) {
     var sender = this._pc.getSenders()[index];
     delete this.expectedLocalTrackInfoById[sender.track.id];
     this.expectNegotiationNeeded();
     this._pc.removeTrack(sender);
@@ -1176,17 +1176,17 @@ PeerConnectionWrapper.prototype = {
     this._pc.addEventListener('track', event => {
       info(this + ": 'ontrack' event fired for " + JSON.stringify(event.track));
 
       this.checkTrackIsExpected(event.track,
                                 this.expectedRemoteTrackInfoById,
                                 this.observedRemoteTrackInfoById);
       ok(this.isTrackOnPC(event.track), "Found track " + event.track.id);
 
-      this.ensureMediaElement(event.track, event.streams[0], 'remote');
+      this.ensureMediaElement(event.track, 'remote');
     });
   },
 
   /**
    * Either adds a given ICE candidate right away or stores it to be added
    * later, depending on the state of the PeerConnection.
    *
    * @param {object} candidate
@@ -1363,55 +1363,52 @@ PeerConnectionWrapper.prototype = {
         id => {
           if (!this.observedRemoteTrackInfoById[id].negotiated) {
             delete this.observedRemoteTrackInfoById[id];
           }
         });
   },
 
   /**
-   * Check that media flow is present on the given media element by waiting for
-   * it to reach ready state HAVE_ENOUGH_DATA and progress time further than
-   * the start of the check.
+   * Check that media flow is present for the given media element by checking
+   * that it reaches ready state HAVE_ENOUGH_DATA and progresses time further
+   * than the start of the check.
    *
    * This ensures, that the stream being played is producing
-   * data and that at least one video frame has been displayed.
+   * data and, in case it contains a video track, that at least one video frame
+   * has been displayed.
    *
-   * @param {object} element
-   *        A media element to wait for data flow on.
+   * @param {HTMLMediaElement} track
+   *        The media element to check
    * @returns {Promise}
-   *        A promise that resolves when media is flowing.
+   *        A promise that resolves when media data is flowing.
    */
   waitForMediaElementFlow : function(element) {
-    return new Promise(resolve => {
-      info("Checking data flow to element: " + element.id);
-      if (element.ended && element.readyState >= element.HAVE_CURRENT_DATA) {
-        resolve();
-        return;
-      }
-      var haveEnoughData = false;
-      var oncanplay = () => {
-        info("Element " + element.id + " saw 'canplay', " +
-             "meaning HAVE_ENOUGH_DATA was just reached.");
-        haveEnoughData = true;
-        element.removeEventListener("canplay", oncanplay);
-      };
-      var ontimeupdate = () => {
-        info("Element " + element.id + " saw 'timeupdate'" +
-             ", currentTime=" + element.currentTime +
-             "s, readyState=" + element.readyState);
-        if (haveEnoughData || element.readyState == element.HAVE_ENOUGH_DATA) {
-          element.removeEventListener("timeupdate", ontimeupdate);
-          ok(true, "Media flowing for element: " + element.id);
-          resolve();
-        }
-      };
-      element.addEventListener("canplay", oncanplay);
-      element.addEventListener("timeupdate", ontimeupdate);
-    });
+    info("Checking data flow for element: " + element.id);
+    is(element.ended, !element.srcObject.active,
+       "Element ended should be the inverse of the MediaStream's active state");
+    if (element.ended) {
+      is(element.readyState, element.HAVE_CURRENT_DATA,
+         "Element " + element.id + " is ended and should have had data");
+      return Promise.resolve();
+    }
+
+    const haveEnoughData = (element.readyState == element.HAVE_ENOUGH_DATA ?
+        Promise.resolve() :
+        haveEvent(element, "canplay", wait(60000,
+            new Error("Timeout for element " + element.id))))
+      .then(_ => info("Element " + element.id + " has enough data."));
+
+    const startTime = element.currentTime;
+    const timeProgressed = timeout(
+        listenUntil(element, "timeupdate", _ => element.currentTime > startTime),
+        60000, "Element " + element.id + " should progress currentTime")
+      .then();
+
+    return Promise.all([haveEnoughData, timeProgressed]);
   },
 
   /**
    * Wait for RTP packet flow for the given MediaStreamTrack.
    *
    * @param {object} track
    *        A MediaStreamTrack to wait for data flow on.
    * @returns {Promise}
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_active_autoplay.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<video id="testAutoplay" autoplay></video>
+<script type="application/javascript">
+"use strict";
+
+const video = document.getElementById("testAutoplay");
+var stream;
+var otherVideoTrack;
+var otherAudioTrack;
+
+createHTML({
+  title: "MediaStream can be autoplayed in media element after going inactive and then active",
+  bug: "1208316"
+});
+
+runTest(() => getUserMedia({audio: true, video: true}).then(s => {
+  stream = s;
+  otherVideoTrack = stream.getVideoTracks()[0].clone();
+  otherAudioTrack = stream.getAudioTracks()[0].clone();
+
+  video.srcObject = stream;
+  return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(!video.ended, "Video element should be playing after adding a gUM stream");
+  stream.getTracks().forEach(t => t.stop());
+  return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(video.ended, "Video element should be ended");
+  stream.addTrack(otherVideoTrack);
+  return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(!video.ended, "Video element should be playing after adding a video track");
+  stream.getTracks().forEach(t => t.stop());
+  return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(video.ended, "Video element should be ended");
+  stream.addTrack(otherAudioTrack);
+  return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(!video.ended, "Video element should be playing after adding a audio track");
+  stream.getTracks().forEach(t => t.stop());
+  return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+  ok(video.ended, "Video element should be ended");
+}));
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_audio.html
@@ -14,17 +14,17 @@ createHTML({
   visible: true
 });
 
 var audioContext;
 var gUMAudioElement;
 var analyser;
 runTest(() => getUserMedia({audio: true})
   .then(stream => {
-    gUMAudioElement = createMediaElement("gUMAudio", "local", "gUMAudio", true);
+    gUMAudioElement = createMediaElement("audio", "gUMAudio");
     gUMAudioElement.srcObject = stream;
 
     audioContext = new AudioContext();
     info("Capturing");
 
     analyser = new AudioStreamAnalyser(audioContext,
                                        gUMAudioElement.mozCaptureStream());
     analyser.enableDebugCanvas();
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
@@ -21,34 +21,34 @@ var videoCaptureStream;
 var untilEndedElement;
 var streamUntilEnded;
 var tracks = [];
 runTest(() => getUserMedia({audio: true, video: true})
   .then(stream => {
     // We need to test with multiple tracks. We add an extra of each kind.
     stream.getTracks().forEach(t => stream.addTrack(t.clone()));
 
-    audioElement = createMediaElement("gUMAudio", "local", "gUMAudio", true);
+    audioElement = createMediaElement("audio", "gUMAudio");
     audioElement.srcObject = stream;
 
     return haveEvent(audioElement, "loadedmetadata", wait(50000, new Error("Timeout")));
   })
   .then(() => {
     info("Capturing audio element (loadedmetadata -> captureStream)");
     audioCaptureStream = audioElement.mozCaptureStream();
 
     is(audioCaptureStream.getAudioTracks().length, 2,
        "audio element should capture two audio tracks");
     is(audioCaptureStream.getVideoTracks().length, 0,
        "audio element should not capture any video tracks");
 
     return haveNoEvent(audioCaptureStream, "addtrack");
   })
   .then(() => {
-    videoElement = createMediaElement("gUMVideo", "local", "gUMVideo", false);
+    videoElement = createMediaElement("video", "gUMVideo");
 
     info("Capturing video element (captureStream -> loadedmetadata)");
     videoCaptureStream = videoElement.mozCaptureStream();
     videoElement.srcObject = audioElement.srcObject.clone();
 
     is(videoCaptureStream.getTracks().length, 0,
        "video element should have no tracks before metadata known");
 
@@ -138,17 +138,17 @@ runTest(() => getUserMedia({audio: true,
         .filter(t => t.readyState == "ended").length, 1,
        "Captured video stream should have one ended video tracks");
     is(videoCaptureStream.getVideoTracks()
         .filter(t => t.readyState == "live").length, 1,
        "Captured video stream should have one live video track");
 
     info("Testing CaptureStreamUntilEnded");
     untilEndedElement =
-      createMediaElement("gUMVideoUntilEnded", "local", "gUMVideoUntilEnded", false);
+      createMediaElement("video", "gUMVideoUntilEnded");
     untilEndedElement.srcObject = audioElement.srcObject;
 
     return haveEvent(untilEndedElement, "loadedmetadata",
                      wait(50000, new Error("Timeout")));
   })
   .then(() => {
     streamUntilEnded = untilEndedElement.mozCaptureStreamUntilEnded();
 
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_video.html
@@ -57,26 +57,34 @@ var checkVideoPaused = video => checkHas
            Array.slice(startPixel.data) + "]. Pass=" + result);
       return result;
     }, pausedTimeout);
   }).then(result => ok(!result, "Frame shouldn't change within " + pausedTimeout / 1000 + " seconds."));
 
 runTest(() => getUserMedia({video: true, fake: true})
   .then(stream => {
     gUMVideoElement =
-      createMediaElement("gUMVideo", "local", "gUMVideo", false);
+      createMediaElement("video", "gUMVideo");
     gUMVideoElement.srcObject = stream;
     gUMVideoElement.play();
 
     info("Capturing");
     captureStreamElement =
-      createMediaElement("captureStream", "local", "captureStream", false);
+      createMediaElement("video", "captureStream");
     captureStreamElement.srcObject = gUMVideoElement.mozCaptureStream();
     captureStreamElement.play();
 
+    // Adding a dummy audio track to the stream will keep a consuming media
+    // element from ending.
+    // We could also solve it by repeatedly play()ing or autoplay, but then we
+    // wouldn't be sure the media element stopped rendering video because it
+    // went to the ended state or because there were no frames for the track.
+    let osc = createOscillatorStream(new AudioContext(), 1000);
+    captureStreamElement.srcObject.addTrack(osc.getTracks()[0]);
+
     return checkVideoPlaying(captureStreamElement);
   })
   .then(() => {
     info("Video flowing. Pausing.");
     gUMVideoElement.pause();
 
     return checkVideoPaused(captureStreamElement);
   })
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamConstructors.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamConstructors.html
@@ -15,74 +15,104 @@
 
   var audioContext = new AudioContext();
   var videoElement;
 
   runTest(() => Promise.resolve()
     .then(() => videoElement = createMediaElement('video', 'constructorsTest'))
     .then(() => getUserMedia({video: true})).then(gUMStream => {
       info("Test default constructor with video");
+      ok(gUMStream.active, "gUMStream with one track should be active");
       var track = gUMStream.getTracks()[0];
 
       var stream = new MediaStream();
+      ok(!stream.active, "New MediaStream should be inactive");
       checkMediaStreamContains(stream, [], "Default constructed stream");
 
       stream.addTrack(track);
+      ok(stream.active, "MediaStream should be active after adding a track");
       checkMediaStreamContains(stream, [track], "Added video track");
 
       var playback = new MediaStreamPlayback(videoElement, stream);
-      return playback.playMedia(false).then(() => gUMStream.stop());
+      return playback.playMedia(false).then(() => {
+        ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+        ok(!stream.active, "stream with stopped tracks should be inactive");
+      });
     })
     .then(() => getUserMedia({video: true})).then(gUMStream => {
       info("Test copy constructor with gUM stream");
+      ok(gUMStream.active, "gUMStream with one track should be active");
       var track = gUMStream.getTracks()[0];
 
       var stream = new MediaStream(gUMStream);
+      ok(stream.active, "List constructed MediaStream should be active");
       checkMediaStreamContains(stream, [track], "Copy constructed video track");
 
       var playback = new MediaStreamPlayback(videoElement, stream);
-      return playback.playMedia(false).then(() => gUMStream.stop());
+      return playback.playMedia(false).then(() => {
+        ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+        ok(!stream.active, "stream with stopped tracks should be inactive");
+      });
     })
     .then(() => getUserMedia({video: true})).then(gUMStream => {
       info("Test list constructor with empty list");
+      ok(gUMStream.active, "gUMStream with one track should be active");
       var track = gUMStream.getTracks()[0];
 
       var stream = new MediaStream([]);
+      ok(!stream.active, "Empty-list constructed MediaStream should be inactive");
       checkMediaStreamContains(stream, [], "Empty-list constructed stream");
 
       stream.addTrack(track);
+      ok(stream.active, "MediaStream should be active after adding a track");
       checkMediaStreamContains(stream, [track], "Added video track");
 
       var playback = new MediaStreamPlayback(videoElement, stream);
-      return playback.playMedia(false).then(() => gUMStream.stop());
+      return playback.playMedia(false).then(() => {
+        ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+        ok(!stream.active, "stream with stopped tracks should be inactive");
+      });
     })
     .then(() => getUserMedia({audio: true, video: true})).then(gUMStream => {
       info("Test list constructor with a gUM audio/video stream");
+      ok(gUMStream.active, "gUMStream with two tracks should be active");
       var audioTrack = gUMStream.getAudioTracks()[0];
       var videoTrack = gUMStream.getVideoTracks()[0];
 
       var stream = new MediaStream([audioTrack, videoTrack]);
+      ok(stream.active, "List constructed MediaStream should be active");
       checkMediaStreamContains(stream, [audioTrack, videoTrack],
                                "List constructed audio and video tracks");
 
       var playback = new MediaStreamPlayback(videoElement, stream);
-      return playback.playMedia(false).then(() => gUMStream.stop());
+      return playback.playMedia(false).then(() => {
+        ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+        ok(!stream.active, "stream with stopped tracks should be inactive");
+      });
     })
     .then(() => getUserMedia({video: true})).then(gUMStream => {
       info("Test list constructor with gUM-video and WebAudio tracks");
+      ok(gUMStream.active, "gUMStream with one track should be active");
       var audioStream = createOscillatorStream(audioContext, 2000);
+      ok(audioStream.active, "WebAudio stream should be active");
+
       var audioTrack = audioStream.getTracks()[0];
       var videoTrack = gUMStream.getTracks()[0];
 
       var stream = new MediaStream([audioTrack, videoTrack]);
+      ok(stream.active, "List constructed MediaStream should be active");
       checkMediaStreamContains(stream, [audioTrack, videoTrack],
                                "List constructed WebAudio and gUM-video tracks");
 
       var playback = new MediaStreamPlayback(videoElement, stream);
-      return playback.playMedia(false).then(() => gUMStream.stop());
+      return playback.playMedia(false).then(() => {
+        gUMStream.getTracks().forEach(t => t.stop());
+        ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+        ok(!stream.active, "stream with stopped tracks should be inactive");
+      });
     })
     .then(() => {
       var osc1k = createOscillatorStream(audioContext, 1000);
       var audioTrack1k = osc1k.getTracks()[0];
 
       var osc5k = createOscillatorStream(audioContext, 5000);
       var audioTrack5k = osc5k.getTracks()[0];
 
@@ -96,16 +126,18 @@
         return analyser.waitForAnalysisSuccess(array =>
           array[analyser.binIndexForFrequency(1000)]  < 50 &&
           array[analyser.binIndexForFrequency(5000)]  < 50 &&
           array[analyser.binIndexForFrequency(10000)] < 50)
           .then(() => analyser.disconnect());
       }).then(() => {
         info("Analysing audio output with copy constructed 5k stream");
         var stream = new MediaStream(osc5k);
+        is(stream.active, osc5k.active,
+           "Copy constructed MediaStream should preserve active state");
         var analyser = new AudioStreamAnalyser(audioContext, stream);
         return analyser.waitForAnalysisSuccess(array =>
           array[analyser.binIndexForFrequency(1000)]  < 50 &&
           array[analyser.binIndexForFrequency(5000)]  > 200 &&
           array[analyser.binIndexForFrequency(10000)] < 50)
           .then(() => analyser.disconnect());
       }).then(() => {
         info("Analysing audio output with empty-list constructed stream");
@@ -114,16 +146,18 @@
         return analyser.waitForAnalysisSuccess(array =>
           array[analyser.binIndexForFrequency(1000)]  < 50 &&
           array[analyser.binIndexForFrequency(5000)]  < 50 &&
           array[analyser.binIndexForFrequency(10000)] < 50)
           .then(() => analyser.disconnect());
       }).then(() => {
         info("Analysing audio output with list constructed 1k, 5k and 10k tracks");
         var stream = new MediaStream([audioTrack1k, audioTrack5k, audioTrack10k]);
+        ok(stream.active,
+           "List constructed MediaStream from WebAudio should be active");
         var analyser = new AudioStreamAnalyser(audioContext, stream);
         return analyser.waitForAnalysisSuccess(array =>
           array[analyser.binIndexForFrequency(50)]    < 50 &&
           array[analyser.binIndexForFrequency(1000)]  > 200 &&
           array[analyser.binIndexForFrequency(2500)]  < 50 &&
           array[analyser.binIndexForFrequency(5000)]  > 200 &&
           array[analyser.binIndexForFrequency(7500)]  < 50 &&
           array[analyser.binIndexForFrequency(10000)] > 200 &&
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamTrackClone.html
@@ -89,54 +89,16 @@
       var audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
       var audioTrack1kClone = audioTrack1kOriginal.clone();
 
       var osc5kOriginal = createOscillatorStream(ac, 5000);
       var audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
       var audioTrack5kClone = audioTrack5kOriginal.clone();
 
       return Promise.resolve().then(() => {
-        info("Analysing audio output of 1k original and 5k clone.");
-        var stream = new MediaStream();
-        stream.addTrack(audioTrack1kOriginal);
-        stream.addTrack(audioTrack5kClone);
-
-        var analyser = new AudioStreamAnalyser(ac, stream);
-        return analyser.waitForAnalysisSuccess(array =>
-                 array[analyser.binIndexForFrequency(50)] < 50 &&
-                 array[analyser.binIndexForFrequency(1000)] > 200 &&
-                 array[analyser.binIndexForFrequency(3000)] < 50 &&
-                 array[analyser.binIndexForFrequency(5000)] > 200 &&
-                 array[analyser.binIndexForFrequency(10000)] < 50)
-          .then(() => {
-            info("Waiting for original tracks to stop");
-            stream.getTracks().forEach(t => t.stop());
-            return analyser.waitForAnalysisSuccess(array =>
-                     array[analyser.binIndexForFrequency(50)] < 50 &&
-                     // WebAudioDestination streams do not handle stop()
-                     // XXX Should they? Plan to resolve that in bug 1208384.
-                     // array[analyser.binIndexForFrequency(1000)] < 50 &&
-                     array[analyser.binIndexForFrequency(3000)] < 50 &&
-                     // array[analyser.binIndexForFrequency(5000)] < 50 &&
-                     array[analyser.binIndexForFrequency(10000)] < 50);
-          }).then(() => analyser.disconnect());
-      }).then(() => {
-        info("Analysing audio output of clones of clones (1kx2 + 5kx5)");
-        var stream = new MediaStream([audioTrack1kClone.clone(),
-                                      audioTrack5kClone.clone().clone().clone().clone()]);
-
-        var analyser = new AudioStreamAnalyser(ac, stream);
-        return analyser.waitForAnalysisSuccess(array =>
-                 array[analyser.binIndexForFrequency(50)] < 50 &&
-                 array[analyser.binIndexForFrequency(1000)] > 200 &&
-                 array[analyser.binIndexForFrequency(3000)] < 50 &&
-                 array[analyser.binIndexForFrequency(5000)] > 200 &&
-                 array[analyser.binIndexForFrequency(10000)] < 50)
-          .then(() => analyser.disconnect());
-      }).then(() => {
         info("Analysing audio output enabled and disabled tracks that don't affect each other");
         audioTrack1kOriginal.enabled = true;
         audioTrack5kOriginal.enabled = false;
 
         audioTrack1kClone.enabled = false;
         audioTrack5kClone.enabled = true;
 
         var analyser =
@@ -159,14 +121,50 @@
                      array[cloneAnalyser.binIndexForFrequency(10000)] < 50)
               .then(() => cloneAnalyser.disconnect());
           })
           // Restore original tracks
           .then(() => [audioTrack1kOriginal,
                        audioTrack5kOriginal,
                        audioTrack1kClone,
                        audioTrack5kClone].forEach(t => t.enabled = true));
+      }).then(() => {
+        info("Analysing audio output of 1k original and 5k clone.");
+        var stream = new MediaStream();
+        stream.addTrack(audioTrack1kOriginal);
+        stream.addTrack(audioTrack5kClone);
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return analyser.waitForAnalysisSuccess(array =>
+                 array[analyser.binIndexForFrequency(50)] < 50 &&
+                 array[analyser.binIndexForFrequency(1000)] > 200 &&
+                 array[analyser.binIndexForFrequency(3000)] < 50 &&
+                 array[analyser.binIndexForFrequency(5000)] > 200 &&
+                 array[analyser.binIndexForFrequency(10000)] < 50)
+          .then(() => {
+            info("Waiting for tracks to stop");
+            stream.getTracks().forEach(t => t.stop());
+            return analyser.waitForAnalysisSuccess(array =>
+                     array[analyser.binIndexForFrequency(50)] < 50 &&
+                     array[analyser.binIndexForFrequency(1000)] < 50 &&
+                     array[analyser.binIndexForFrequency(3000)] < 50 &&
+                     array[analyser.binIndexForFrequency(5000)] < 50 &&
+                     array[analyser.binIndexForFrequency(10000)] < 50);
+          }).then(() => analyser.disconnect());
+      }).then(() => {
+        info("Analysing audio output of clones of clones (1kx2 + 5kx4)");
+        var stream = new MediaStream([audioTrack1kClone.clone(),
+                                      audioTrack5kOriginal.clone().clone().clone().clone()]);
+
+        var analyser = new AudioStreamAnalyser(ac, stream);
+        return analyser.waitForAnalysisSuccess(array =>
+                 array[analyser.binIndexForFrequency(50)] < 50 &&
+                 array[analyser.binIndexForFrequency(1000)] > 200 &&
+                 array[analyser.binIndexForFrequency(3000)] < 50 &&
+                 array[analyser.binIndexForFrequency(5000)] > 200 &&
+                 array[analyser.binIndexForFrequency(10000)] < 50)
+          .then(() => analyser.disconnect());
       });
     }));
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html
+++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html
@@ -15,29 +15,31 @@ createHTML({
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
 
   // Always use fake tracks since we depend on video to be somewhat green and
   // audio to have a large 1000Hz component (or 440Hz if using fake devices).
   test.setMediaConstraints([{audio: true, video: true, fake: true}], []);
   test.chain.append([
     function CHECK_ASSUMPTIONS() {
-      is(test.pcLocal.localMediaElements.length, 1,
+      is(test.pcLocal.localMediaElements.length, 2,
          "pcLocal should have one media element");
-      is(test.pcRemote.remoteMediaElements.length, 1,
+      is(test.pcRemote.remoteMediaElements.length, 2,
          "pcRemote should have one media element");
       is(test.pcLocal._pc.getLocalStreams().length, 1,
          "pcLocal should have one stream");
       is(test.pcRemote._pc.getRemoteStreams().length, 1,
          "pcRemote should have one stream");
     },
     function CHECK_VIDEO() {
       var h = new CaptureStreamTestHelper2D();
-      var localVideo = test.pcLocal.localMediaElements[0];
-      var remoteVideo = test.pcRemote.remoteMediaElements[0];
+      var localVideo = test.pcLocal.localMediaElements
+        .find(e => e instanceof HTMLVideoElement);
+      var remoteVideo = test.pcRemote.remoteMediaElements
+        .find(e => e instanceof HTMLVideoElement);
       // We check a pixel somewhere away from the top left corner since
       // MediaEngineDefault puts semi-transparent time indicators there.
       const offsetX = 50;
       const offsetY = 50;
       const threshold = 128;
 
       // We're regarding black as disabled here, and we're setting the alpha
       // channel of the pixel to 255 to disregard alpha when testing.
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html
+++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html
@@ -21,38 +21,40 @@ runNetworkTest(() => {
   // Always use fake tracks since we depend on audio to have a large 1000Hz
   // component.
   test.setMediaConstraints([{audio: true, video: true, fake: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
     function PC_LOCAL_GUM_CLONE() {
       return getUserMedia(test.pcLocal.constraints[0]).then(stream => {
         originalStream = stream;
         localVideoOriginal =
-          createMediaElement("audiovideo", "local-original");
+          createMediaElement("video", "local-original");
         localVideoOriginal.srcObject = stream;
         test.pcLocal.attachLocalStream(originalStream.clone());
       });
     }
   ]);
   test.chain.append([
     function CHECK_ASSUMPTIONS() {
-      is(test.pcLocal.localMediaElements.length, 1,
+      is(test.pcLocal.localMediaElements.length, 2,
          "pcLocal should have one media element");
-      is(test.pcRemote.remoteMediaElements.length, 1,
+      is(test.pcRemote.remoteMediaElements.length, 2,
          "pcRemote should have one media element");
       is(test.pcLocal._pc.getLocalStreams().length, 1,
          "pcLocal should have one stream");
       is(test.pcRemote._pc.getRemoteStreams().length, 1,
          "pcRemote should have one stream");
     },
     function CHECK_VIDEO() {
       info("Checking video");
       var h = new CaptureStreamTestHelper2D();
-      var localVideoClone = test.pcLocal.localMediaElements[0];
-      var remoteVideoClone = test.pcRemote.remoteMediaElements[0];
+      var localVideoClone = test.pcLocal.localMediaElements
+        .find(e => e instanceof HTMLVideoElement);
+      var remoteVideoClone = test.pcRemote.remoteMediaElements
+        .find(e => e instanceof HTMLVideoElement);
 
       // We check a pixel somewhere away from the top left corner since
       // MediaEngineDefault puts semi-transparent time indicators there.
       const offsetX = 50;
       const offsetY = 50;
       const threshold = 128;
       const remoteDisabledColor = h.black;
 
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -11,16 +11,65 @@
 #include "AudioNodeStream.h"
 #include "DOMMediaStream.h"
 #include "MediaStreamTrack.h"
 #include "TrackUnionStream.h"
 
 namespace mozilla {
 namespace dom {
 
+class AudioDestinationTrackSource :
+  public MediaStreamTrackSource
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationTrackSource,
+                                           MediaStreamTrackSource)
+
+  AudioDestinationTrackSource(MediaStreamAudioDestinationNode* aNode,
+                              nsIPrincipal* aPrincipal)
+    : MediaStreamTrackSource(aPrincipal, nsString())
+    , mNode(aNode)
+  {
+  }
+
+  void Destroy() override
+  {
+    if (mNode) {
+      mNode->DestroyMediaStream();
+      mNode = nullptr;
+    }
+  }
+
+  MediaSourceEnum GetMediaSource() const override
+  {
+    return MediaSourceEnum::AudioCapture;
+  }
+
+  void Stop() override
+  {
+    Destroy();
+  }
+
+private:
+  virtual ~AudioDestinationTrackSource() {}
+
+  RefPtr<MediaStreamAudioDestinationNode> mNode;
+};
+
+NS_IMPL_ADDREF_INHERITED(AudioDestinationTrackSource,
+                         MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(AudioDestinationTrackSource,
+                          MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioDestinationTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationTrackSource,
+                                   MediaStreamTrackSource,
+                                   mNode)
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode, AudioNode, mDOMStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
 
@@ -32,18 +81,17 @@ MediaStreamAudioDestinationNode::MediaSt
   , mDOMStream(
       DOMAudioNodeMediaStream::CreateTrackUnionStreamAsInput(GetOwner(),
                                                              this,
                                                              aContext->Graph()))
 {
   // Ensure an audio track with the correct ID is exposed to JS
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   RefPtr<MediaStreamTrackSource> source =
-    new BasicUnstoppableTrackSource(doc->NodePrincipal(),
-                                    MediaSourceEnum::AudioCapture);
+    new AudioDestinationTrackSource(this, doc->NodePrincipal());
   RefPtr<MediaStreamTrack> track =
     mDOMStream->CreateDOMTrack(AudioNodeStream::AUDIO_TRACK,
                                MediaSegment::AUDIO, source,
                                MediaTrackConstraints());
   mDOMStream->AddTrackInternal(track);
 
   ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
   MOZ_ASSERT(!!outputStream);
--- a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
+++ b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
@@ -29,16 +29,22 @@ addLoadEvent(function() {
   var elem = document.getElementById('audioelem');
   elem.srcObject = dest.stream;
   elem.onloadedmetadata = function() {
     ok(true, "got metadata event");
     setTimeout(function() {
       is(elem.played.length, 1, "should have a played interval");
       is(elem.played.start(0), 0, "should have played immediately");
       isnot(elem.played.end(0), 0, "should have played for a non-zero interval");
-      SimpleTest.finish();
+
+      // This will end the media element.
+      dest.stream.getTracks()[0].stop();
     }, 2000);
   };
+  elem.onended = function() {
+    ok(true, "media element ended after destination track.stop()");
+    SimpleTest.finish();
+  };
 
   source.start(0);
   elem.play();
 });
 </script>
--- a/dom/smil/TimeEvent.cpp
+++ b/dom/smil/TimeEvent.cpp
@@ -51,16 +51,18 @@ TimeEvent::GetDetail(int32_t* aDetail)
   *aDetail = mDetail;
   return NS_OK;
 }
 
 void
 TimeEvent::InitTimeEvent(const nsAString& aType, nsGlobalWindow* aView,
                          int32_t aDetail)
 {
+  NS_ENSURE_TRUE_VOID(!mEvent->mFlags.mIsBeingDispatched);
+
   Event::InitEvent(aType, false /*doesn't bubble*/, false /*can't cancel*/);
   mDetail = aDetail;
   mView = aView ? aView->GetOuterWindow() : nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
 
deleted file mode 100644
--- a/dom/webidl/IDBEnvironment.webidl
+++ /dev/null
@@ -1,14 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * The origin of this IDL file is:
- * https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
- */
-
-[Exposed=(Window,Worker), NoInterfaceObject]
-interface IDBEnvironment {
-    //[Throws] readonly    attribute IDBFactory indexedDB;
-    [Throws] readonly    attribute IDBFactory? indexedDB;
-};
--- a/dom/webidl/MediaStream.webidl
+++ b/dom/webidl/MediaStream.webidl
@@ -31,15 +31,13 @@ interface MediaStream : EventTarget {
     readonly    attribute DOMString    id;
     sequence<AudioStreamTrack> getAudioTracks ();
     sequence<VideoStreamTrack> getVideoTracks ();
     sequence<MediaStreamTrack> getTracks ();
     MediaStreamTrack?          getTrackById (DOMString trackId);
     void                       addTrack (MediaStreamTrack track);
     void                       removeTrack (MediaStreamTrack track);
     MediaStream                clone ();
-    // readonly    attribute boolean      active;
-    //             attribute EventHandler onactive;
-    //             attribute EventHandler oninactive;
+    readonly    attribute boolean      active;
                 attribute EventHandler onaddtrack;
     //             attribute EventHandler onremovetrack;
     readonly attribute double currentTime;
 };
--- a/dom/webidl/URL.webidl
+++ b/dom/webidl/URL.webidl
@@ -46,19 +46,19 @@ interface URL {
            attribute USVString search;
   readonly attribute URLSearchParams searchParams;
   [Throws]
            attribute USVString hash;
 };
 
 partial interface URL {
   [Throws]
-  static DOMString? createObjectURL(Blob blob, optional objectURLOptions options);
+  static DOMString createObjectURL(Blob blob, optional objectURLOptions options);
   [Throws]
-  static DOMString? createObjectURL(MediaStream stream, optional objectURLOptions options);
+  static DOMString createObjectURL(MediaStream stream, optional objectURLOptions options);
   [Throws]
   static void revokeObjectURL(DOMString url);
   [ChromeOnly, Throws]
   static boolean isValidURL(DOMString url);
 };
 
 dictionary objectURLOptions
 {
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -118,19 +118,16 @@ partial interface Window {
 };
 
 // https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html
 partial interface Window {
   //[Throws] Selection getSelection();
   [Throws] Selection? getSelection();
 };
 
-// https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
-Window implements IDBEnvironment;
-
 // http://dev.w3.org/csswg/cssom/
 partial interface Window {
   //[NewObject, Throws] CSSStyleDeclaration getComputedStyle(Element elt, optional DOMString pseudoElt = "");
   [NewObject, Throws] CSSStyleDeclaration? getComputedStyle(Element elt, optional DOMString pseudoElt = "");
 };
 
 // http://dev.w3.org/csswg/cssom-view/
 enum ScrollBehavior { "auto", "instant", "smooth" };
--- a/dom/webidl/WindowOrWorkerGlobalScope.webidl
+++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl
@@ -48,16 +48,23 @@ partial interface WindowOrWorkerGlobalSc
   [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
 };
 
 // https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
 partial interface WindowOrWorkerGlobalScope {
   readonly attribute boolean isSecureContext;
 };
 
+// http://w3c.github.io/IndexedDB/#factory-interface
+partial interface WindowOrWorkerGlobalScope {
+   // readonly attribute IDBFactory indexedDB;
+   [Throws]
+   readonly attribute IDBFactory? indexedDB;
+};
+
 // Mozilla extensions
 partial interface WindowOrWorkerGlobalScope {
   // Extensions to ImageBitmap bits.
   // Bug 1141979 - [FoxEye] Extend ImageBitmap with interfaces to access its
   // underlying image data
   //
   // Note:
   // Overloaded functions cannot have different "extended attributes",
--- a/dom/webidl/WorkerGlobalScope.webidl
+++ b/dom/webidl/WorkerGlobalScope.webidl
@@ -37,17 +37,16 @@ partial interface WorkerGlobalScope {
 
 // https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#self-caches
 partial interface WorkerGlobalScope {
 [Throws, Func="mozilla::dom::cache::CacheStorage::PrefEnabled", SameObject]
 readonly attribute CacheStorage caches;
 };
 
 WorkerGlobalScope implements GlobalCrypto;
-WorkerGlobalScope implements IDBEnvironment;
 WorkerGlobalScope implements WindowOrWorkerGlobalScope;
 
 // Not implemented yet: bug 1072107.
 // WorkerGlobalScope implements FontFaceSource;
 
 // Mozilla extensions
 partial interface WorkerGlobalScope {
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -252,17 +252,16 @@ WEBIDL_FILES = [
     'HTMLTimeElement.webidl',
     'HTMLTitleElement.webidl',
     'HTMLTrackElement.webidl',
     'HTMLUListElement.webidl',
     'HTMLVideoElement.webidl',
     'IccCardLockError.webidl',
     'IDBCursor.webidl',
     'IDBDatabase.webidl',
-    'IDBEnvironment.webidl',
     'IDBFactory.webidl',
     'IDBFileHandle.webidl',
     'IDBFileRequest.webidl',
     'IDBIndex.webidl',
     'IDBKeyRange.webidl',
     'IDBMutableFile.webidl',
     'IDBObjectStore.webidl',
     'IDBOpenDBRequest.webidl',
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -188,18 +188,19 @@ ServiceWorkerContainer::Register(const n
       aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, wSpec);
       return nullptr;
     }
   } else {
     // Step 5. Parse against entry settings object's base URL.
     rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(),
                    nullptr, baseURI);
     if (NS_WARN_IF(NS_FAILED(rv))) {
+      nsIURI* uri = baseURI ? baseURI : scriptURI;
       nsAutoCString spec;
-      baseURI->GetSpec(spec);
+      uri->GetSpec(spec);
       NS_ConvertUTF8toUTF16 wSpec(spec);
       aRv.ThrowTypeError<MSG_INVALID_SCOPE>(aOptions.mScope.Value(), wSpec);
       return nullptr;
     }
 
     aRv = CheckForSlashEscapedCharsInPath(scopeURI);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -3105,17 +3105,17 @@ ServiceWorkerManager::RemoveRegistration
   // object may be held by content script.
   aRegistration->Clear();
 
   RemoveScopeAndRegistration(aRegistration);
 }
 
 namespace {
 /**
- * See browser/components/sessionstore/Utils.jsm function hasRootDomain().
+ * See toolkit/modules/sessionstore/Utils.jsm function hasRootDomain().
  *
  * Returns true if the |url| passed in is part of the given root |domain|.
  * For example, if |url| is "www.mozilla.org", and we pass in |domain| as
  * "mozilla.org", this will return true. It would return false the other way
  * around.
  */
 bool
 HasRootDomain(nsIURI* aURI, const nsACString& aDomain)
--- a/editor/composer/test/chrome.ini
+++ b/editor/composer/test/chrome.ini
@@ -1,15 +1,5 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 
-[test_async_UpdateCurrentDictionary.html]
-[test_bug338427.html]
 [test_bug434998.xul]
-[test_bug678842.html]
-[test_bug697981.html]
-[test_bug717433.html]
-[test_bug1204147.html]
-[test_bug1200533.html]
-[test_bug1205983.html]
-[test_bug1209414.html]
-[test_bug1219928.html]
 [test_bug1266815.html]
--- a/editor/composer/test/mochitest.ini
+++ b/editor/composer/test/mochitest.ini
@@ -8,14 +8,34 @@ support-files =
   bug1204147_subframe2.html
   en-GB/en_GB.dic
   en-GB/en_GB.aff
   en-AU/en_AU.dic
   en-AU/en_AU.aff
   de-DE/de_DE.dic
   de-DE/de_DE.aff
 
+[test_async_UpdateCurrentDictionary.html]
+skip-if = os == 'android'
+[test_bug338427.html]
+skip-if = os == 'android'
 [test_bug348497.html]
 [test_bug384147.html]
 [test_bug389350.html]
 skip-if = toolkit == 'android'
 [test_bug519928.html]
+[test_bug678842.html]
+skip-if = os == 'android'
+[test_bug697981.html]
+skip-if = os == 'android'
+[test_bug717433.html]
+skip-if = os == 'android'
 [test_bug738440.html]
+[test_bug1200533.html]
+skip-if = os == 'android'
+[test_bug1204147.html]
+skip-if = os == 'android'
+[test_bug1205983.html]
+skip-if = os == 'android'
+[test_bug1209414.html]
+skip-if = os == 'android'
+[test_bug1219928.html]
+skip-if = e10s || os == 'android'
--- a/editor/composer/test/test_async_UpdateCurrentDictionary.html
+++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html
@@ -1,37 +1,37 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=856270
 -->
 <head>
   <title>Test for Bug 856270 - Async UpdateCurrentDictionary</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a>
 <p id="display"></p>
 <div id="content">
 <textarea id="editor" spellcheck="true"></textarea>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript;version=1.8">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(start);
 
 function start() {
-  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
   var textarea = document.getElementById("editor");
   textarea.focus();
 
-  onSpellCheck(textarea, function () {
-    var isc = textarea.editor.getInlineSpellChecker(false);
+  SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
+  .onSpellCheck(textarea, function () {
+    var isc = SpecialPowers.wrap(textarea).editor.getInlineSpellChecker(false);
     ok(isc, "Inline spell checker should exist after focus and spell check");
     var sc = isc.spellChecker;
     isnot(sc.GetCurrentDictionary(), lang,
           "Current dictionary should not be set yet.");
 
     // First, set the lang attribute on the textarea, call Update, and make
     // sure the spell checker's language was updated appropriately.
     var lang = "en-US";
--- a/editor/composer/test/test_bug1200533.html
+++ b/editor/composer/test/test_bug1200533.html
@@ -1,17 +1,17 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1200533
 -->
 <head>
   <title>Test for Bug 1200533</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1200533">Mozilla Bug 1200533</a>
 <p id="display"></p>
 <iframe id="content"></iframe>
 
 </div>
 <pre id="test">
@@ -43,61 +43,71 @@ var tests = [
     // Result: Random en-*.
     [ "en-ZA-not-avail", "de-DE", "*" ],
     [ "en-generic",      "de-DE", "*" ],
     // Result: Preference value.
     [ "ko-not-avail",    "de-DE", "de-DE" ],
   ];
 
 var loadCount = 0;
-var en_GB;
-var en_AU;
-var en_DE;
-var hunspell;
+var script;
 
 var loadListener = function(evt) {
-  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
-  Components.utils.import("resource://gre/modules/Services.jsm");
+  if (loadCount == 0) {
+    script = SpecialPowers.loadChromeScript(function() {
+      var dir = Components.classes["@mozilla.org/file/directory_service;1"]
+                          .getService(Components.interfaces.nsIProperties)
+                          .get("CurWorkD", Components.interfaces.nsIFile);
+      dir.append("tests");
+      dir.append("editor");
+      dir.append("composer");
+      dir.append("test");
 
-  if (loadCount == 0) {
-    var dir = Components.classes["@mozilla.org/file/directory_service;1"]
-                        .getService(Components.interfaces.nsIProperties)
-                        .get("CurWorkD", Components.interfaces.nsIFile);
-    dir.append("tests");
-    dir.append("editor");
-    dir.append("composer");
-    dir.append("test");
+      var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
+                               .getService(Components.interfaces.mozISpellCheckingEngine);
 
-    hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
-                         .getService(Components.interfaces.mozISpellCheckingEngine);
+      // Install en-GB, en-AU and de-DE dictionaries.
+      var en_GB = dir.clone();
+      var en_AU = dir.clone();
+      var de_DE = dir.clone();
+      en_GB.append("en-GB");
+      en_AU.append("en-AU");
+      de_DE.append("de-DE");
+      hunspell.addDirectory(en_GB);
+      hunspell.addDirectory(en_AU);
+      hunspell.addDirectory(de_DE);
 
-    // Install en-GB, en-AU and de-DE dictionaries.
-    en_GB = dir.clone();
-    en_AU = dir.clone();
-    de_DE = dir.clone();
-    en_GB.append("en-GB");
-    en_AU.append("en-AU");
-    de_DE.append("de-DE");
-    is(en_GB.exists(), true, "true expected (en-GB directory should exist)");
-    is(en_AU.exists(), true, "true expected (en-AU directory should exist)");
-    is(de_DE.exists(), true, "true expected (de-DE directory should exist)");
-    hunspell.addDirectory(en_GB);
-    hunspell.addDirectory(en_AU);
-    hunspell.addDirectory(de_DE);
+      addMessageListener("check-existence",
+                         () => [en_GB.exists(), en_AU.exists(),
+                                de_DE.exists()]);
+      addMessageListener("destroy", () => {
+        hunspell.removeDirectory(en_GB);
+        hunspell.removeDirectory(en_AU);
+        hunspell.removeDirectory(de_DE);
+      });
+    });
+    var existenceChecks = script.sendSyncMessage("check-existence")[0][0];
+    is(existenceChecks[0], true, "true expected (en-GB directory should exist)");
+    is(existenceChecks[1], true, "true expected (en-AU directory should exist)");
+    is(existenceChecks[2], true, "true expected (de-DE directory should exist)");
   }
 
-  Services.prefs.setCharPref("spellchecker.dictionary", tests[loadCount][1]);
+  SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]},
+                            function() { continueTest(evt) });
+}
 
+function continueTest(evt) {
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById(tests[loadCount][0]);
-  var editor = elem.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
+  var editor = SpecialPowers.wrap(elem).QueryInterface(SpecialPowers.Ci.nsIDOMNSEditableElement).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
 
-  onSpellCheck(elem, function () {
+  SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm")
+  .onSpellCheck(elem, function () {
     var spellchecker = inlineSpellChecker.spellChecker;
     try {
       var dict = spellchecker.GetCurrentDictionary();
     } catch(e) {}
 
     if (tests[loadCount][2] != "*") {
       is (dict, tests[loadCount][2], "expected " + tests[loadCount][2]);
     } else {
@@ -106,22 +116,17 @@ var loadListener = function(evt) {
     }
 
     loadCount++;
     if (loadCount < tests.length) {
       // Load the iframe again.
       content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug1200533_subframe.html?firstload=false';
     } else {
       // Remove the fake  dictionaries again, since it's otherwise picked up by later tests.
-      hunspell.removeDirectory(en_GB);
-      hunspell.removeDirectory(en_AU);
-      hunspell.removeDirectory(de_DE);
-
-      // Reset the preference, so the last value we set doesn't collide with the next test.
-      Services.prefs.setCharPref("spellchecker.dictionary", "");
+      script.sendSyncMessage("destroy");
 
       SimpleTest.finish();
     }
   });
 
 }
 
 content.addEventListener('load', loadListener, false);
--- a/editor/composer/test/test_bug1204147.html
+++ b/editor/composer/test/test_bug1204147.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1204147
 -->
 <head>
   <title>Test for Bug 1204147</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1204147">Mozilla Bug 1204147</a>
 <p id="display"></p>
 <iframe id="content"></iframe>
 </div>
 
 <pre id="test">
@@ -22,54 +22,58 @@ SimpleTest.waitForExplicitFinish();
 var content = document.getElementById('content');
 // Load a subframe containing an editor with using "en-GB". At first
 // load, it will set the dictionary to "en-GB". The bug was that a content preference
 // was also created. At second load, we check the dictionary for another element,
 // one that should use "en-US". With the bug corrected, we get "en-US", before
 // we got "en-GB" from the content preference.
 
 var firstLoad = true;
-var en_GB;
-var hunspell;
+var script;
 
 var loadListener = function(evt) {
-  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+  if (firstLoad) {
+    script = SpecialPowers.loadChromeScript(function() {
+      var dir = Components.classes["@mozilla.org/file/directory_service;1"]
+                          .getService(Components.interfaces.nsIProperties)
+                          .get("CurWorkD", Components.interfaces.nsIFile);
+      dir.append("tests");
+      dir.append("editor");
+      dir.append("composer");
+      dir.append("test");
 
-  if (firstLoad) {
-    var dir = Components.classes["@mozilla.org/file/directory_service;1"]
-                        .getService(Components.interfaces.nsIProperties)
-                        .get("CurWorkD", Components.interfaces.nsIFile);
-    dir.append("tests");
-    dir.append("editor");
-    dir.append("composer");
-    dir.append("test");
+      var hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
+                               .getService(Components.interfaces.mozISpellCheckingEngine);
 
-    hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
-                         .getService(Components.interfaces.mozISpellCheckingEngine);
+      // Install en-GB dictionary.
+      en_GB = dir.clone();
+      en_GB.append("en-GB");
+      hunspell.addDirectory(en_GB);
 
-    // Instal