Merge m-c to fx-team, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 31 Jul 2015 17:15:43 -0700
changeset 287383 466ad45c683129be0274da16a505e6245293ab31
parent 287382 4cb44bbf5e2567da7b15565afbe648826fd00d7b (current diff)
parent 287341 aeb85029c3b3b594d96a9c72d04b8e971e1bdf5b (diff)
child 287384 d4446e517b3aef8b3766882ceca912fa0e1b7c33
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team, a=merge
browser/config/tooltool-manifests/linux32/valgrind.manifest
browser/config/tooltool-manifests/linux64/valgrind.manifest
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1188462 rearranged the skia directory.
+Bug 1186748 needed a CLOBBER again
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -450,26 +450,26 @@ DocManager::CreateDocOrRootAccessible(ns
     // constructed because event processing and tree construction are done by
     // the same document.
     // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
     // events processing.
     docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
                              ApplicationAcc());
 
     if (IPCAccessibilityActive()) {
-      DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
-      docAcc->SetIPCDoc(ipcDoc);
       nsIDocShell* docShell = aDocument->GetDocShell();
       if (docShell) {
         nsCOMPtr<nsITabChild> tabChild = do_GetInterface(docShell);
 
         // XXX We may need to handle the case that we don't have a tab child
         // differently.  It may be that this will cause us to fail to notify
         // the parent process about important accessible documents.
         if (tabChild) {
+          DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
+          docAcc->SetIPCDoc(ipcDoc);
           static_cast<TabChild*>(tabChild.get())->
             SendPDocAccessibleConstructor(ipcDoc, nullptr, 0);
         }
       }
     }
   } else {
     parentDocAcc->BindChildDocument(docAcc);
   }
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1160,8 +1160,11 @@ pref("dom.serviceWorkers.enabled", false
 // Retain at most 10 processes' layers buffers
 pref("layers.compositor-lru-size", 10);
 
 // Enable Cardboard VR on mobile, assuming VR at all is enabled
 pref("dom.vr.cardboard.enabled", true);
 
 // In B2G by deafult any AudioChannelAgent is muted when created.
 pref("dom.audiochannel.mutedByDefault", true);
+
+// Default device name for Presentation API
+pref("dom.presentation.device.name", "Firefox OS");
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -558,22 +558,30 @@ let settingsToObserve = {
   'debug.log-animations.enabled': {
     prefName: 'layers.offmainthreadcomposition.log-animations',
     defaultValue: false
   },
   'debug.paint-flashing.enabled': {
     prefName: 'nglayout.debug.paint_flashing',
     defaultValue: false
   },
+  // FIXME: Bug 1185806 - Provide a common device name setting.
+  // Borrow device name from developer's menu to avoid multiple name settings.
+  'devtools.discovery.device': {
+    prefName: 'dom.presentation.device.name',
+    defaultValue: 'Firefox OS'
+  },
   'devtools.eventlooplag.threshold': 100,
   'devtools.remote.wifi.visible': {
     resetToPref: true
   },
   'dom.mozApps.use_reviewer_certs': false,
   'dom.mozApps.signed_apps_installable_from': 'https://marketplace.firefox.com',
+  'dom.presentation.discovery.enabled': true,
+  'dom.presentation.discoverable': false,
   'dom.serviceWorkers.interception.enabled': true,
   'dom.serviceWorkers.testing.enabled': false,
   'gfx.layerscope.enabled': false,
   'layers.draw-borders': false,
   'layers.draw-tile-borders': false,
   'layers.dump': false,
   'layers.enable-tiles': true,
   'layers.effect.invert': false,
--- a/b2g/config/tooltool-manifests/linux32/releng.manifest
+++ b/b2g/config/tooltool-manifests/linux32/releng.manifest
@@ -2,16 +2,24 @@
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
+"size": 11179576,
+"digest": "91567ce8e2bb8ab0ebc60c31e90731d88a1ea889fb71bcf55c735746a60fa7610b7e040ea3d8f727b6f692ae3ee703d6f3b30cdbd76fdf5617f77d9c38aa20ed",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "size": 31078810,
--- a/b2g/config/tooltool-manifests/linux64/releng.manifest
+++ b/b2g/config/tooltool-manifests/linux64/releng.manifest
@@ -2,16 +2,24 @@
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
+"size": 12057960,
+"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "size": 31078810,
--- a/browser/config/mozconfigs/linux64/hazards
+++ b/browser/config/mozconfigs/linux64/hazards
@@ -21,12 +21,13 @@ mk_add_options MOZ_OBJDIR=obj-analyzed
 ac_add_options --enable-debug
 ac_add_options --enable-tests
 ac_add_options --enable-optimize
 
 CFLAGS="$CFLAGS -Wno-attributes"
 CPPFLAGS="$CPPFLAGS -Wno-attributes"
 CXXFLAGS="$CXXFLAGS -Wno-attributes"
 
+TOOLTOOL_DIR="$(dirname $topsrcdir)"
 export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
 . $topsrcdir/build/unix/mozconfig.gtk
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/tooltool-manifests/linux32/releng.manifest
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -2,20 +2,21 @@
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512", 
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
-"size": 4079256,
-"digest": "bb5238558bcf6db2ca395513c8dccaa15dd61b3c375598eb6a685356b0c1a2d9840e3bf81bc00242b872fd798541f53d723777c754412abf0e772b7cc284937c",
+"size": 11179576,
+"digest": "91567ce8e2bb8ab0ebc60c31e90731d88a1ea889fb71bcf55c735746a60fa7610b7e040ea3d8f727b6f692ae3ee703d6f3b30cdbd76fdf5617f77d9c38aa20ed",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
+"setup": "setup.sh",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
deleted file mode 100644
--- a/browser/config/tooltool-manifests/linux32/valgrind.manifest
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512", 
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-}
-]
--- a/browser/config/tooltool-manifests/linux64/clang.manifest
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest
@@ -3,10 +3,18 @@
 "clang_version": "r241406"
 }, 
 {
 "size": 100307285, 
 "digest": "4d147d0072a928945fc1e938f39a5d0a9d3c676399c09e092c8750b2f973cdbbebda8d94d4d05805fae74a5c49c54263dc22b8b443c23c9a0ae830a261d3cf30", 
 "algorithm": "sha512", 
 "filename": "clang.tar.bz2",
 "unpack": true
+},
+{
+"size": 12057960,
+"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -2,20 +2,21 @@
 {
 "size": 80458572,
 "digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
 "algorithm": "sha512", 
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
-"size": 4431740,
-"digest": "68fc56b0fb0cdba629b95683d6649ff76b00dccf97af90960c3d7716f6108b2162ffd5ffcd5c3a60a21b28674df688fe4dabc67345e2da35ec5abeae3d48c8e3",
+"size": 12057960,
+"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
+"setup": "setup.sh",
 "unpack": true
 },
 {
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
--- a/browser/config/tooltool-manifests/linux64/tsan.manifest
+++ b/browser/config/tooltool-manifests/linux64/tsan.manifest
@@ -5,15 +5,16 @@
 {
 "size": 89690541, 
 "digest": "470d258d9785a120fcba65eee90daa632a42affa0f97f57d70fc8285bd76bcc27d4d0d70b6c37577ab271a04c843b6269425391a8d6df1967718dba26dd3a73d", 
 "algorithm": "sha512", 
 "filename": "clang.tar.bz2",
 "unpack": true
 },
 {
-"size": 4431740,
-"digest": "68fc56b0fb0cdba629b95683d6649ff76b00dccf97af90960c3d7716f6108b2162ffd5ffcd5c3a60a21b28674df688fe4dabc67345e2da35ec5abeae3d48c8e3",
+"size": 12057960,
+"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
+"setup": "setup.sh",
 "unpack": true
 }
 ]
deleted file mode 100644
--- a/browser/config/tooltool-manifests/linux64/valgrind.manifest
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-{
-"size": 80458572,
-"digest": "e5101f9dee1e462f6cbd3897ea57eede41d23981825c7b20d91d23ab461875d54d3dfc24999aa58a31e8b01f49fb3140e05ffe5af2957ef1d1afb89fd0dfe1ad",
-"algorithm": "sha512", 
-"filename": "gcc.tar.xz",
-"unpack": true
-},
-{
-"size": 167175,
-"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
-"algorithm": "sha512",
-"filename": "sccache.tar.bz2",
-"unpack": true
-}
-]
--- a/build/unix/build-gtk3/build-gtk3.sh
+++ b/build/unix/build-gtk3/build-gtk3.sh
@@ -52,17 +52,17 @@ build() {
 	version=$(eval echo \$${pkg}_version)
 	url=$(eval echo \$${pkg}_url)
 	wget -c -P $TMPDIR $url
 	tar -axf $TMPDIR/$name-$version.tar.*
 	mkdir -p build/$name
 	cd build/$name
 	eval ../../$name-$version/configure --disable-static $* $configure_args
 	make $make_flags
-	make install-strip DESTDIR=$root_dir/gtk3
+	make install DESTDIR=$root_dir/gtk3
 	find $root_dir/gtk3 -name \*.la -delete
 	cd ../..
 }
 
 case "$1" in
 32)
 	configure_args='--host=i686-pc-linux --build=i686-pc-linux CC="gcc -m32" CXX="g++ -m32"'
         lib=lib
@@ -98,10 +98,49 @@ build cairo --enable-tee
 build pango
 build atk
 make_flags="$make_flags GLIB_COMPILE_SCHEMAS=glib-compile-schemas"
 build gtk+
 
 rm -rf $root_dir/gtk3/usr/local/share/gtk-doc
 rm -rf $root_dir/gtk3/usr/local/share/locale
 
+# mock build environment doesn't have fonts in /usr/share/fonts, but
+# has some in /usr/share/X11/fonts. Add this directory to the
+# fontconfig configuration without changing the gtk3 tooltool package.
+cat << EOF > $root_dir/gtk3/usr/local/etc/fonts/local.conf
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<fontconfig>
+  <dir>/usr/share/X11/fonts</dir>
+</fontconfig>
+EOF
+
+cat <<EOF > $root_dir/gtk3/setup.sh
+#!/bin/sh
+
+cd \$(dirname \$0)
+
+# pango expects absolute paths in pango.modules, and TOOLTOOL_DIR may vary...
+LD_LIBRARY_PATH=./usr/local/lib \
+PANGO_SYSCONFDIR=./usr/local/etc \
+PANGO_LIBDIR=./usr/local/lib \
+./usr/local/bin/pango-querymodules > ./usr/local/etc/pango/pango.modules
+
+# same with gdb-pixbuf and loaders.cache
+LD_LIBRARY_PATH=./usr/local/lib \
+GDK_PIXBUF_MODULE_FILE=./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache \
+GDK_PIXBUF_MODULEDIR=./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders \
+./usr/local/bin/gdk-pixbuf-query-loaders > ./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
+
+# The fontconfig version in the tooltool package has known uses of
+# uninitialized memory when creating its cache, and while most users
+# will already have an existing cache, running Firefox on automation
+# will create it. Combined with valgrind, this generates irrelevant
+# errors.
+# So create the fontconfig cache beforehand.
+./usr/local/bin/fc-cache
+EOF
+
+chmod +x $root_dir/gtk3/setup.sh
+
 cd $cwd
 tar -C $root_dir -Jcf gtk3.tar.xz gtk3
--- a/build/unix/mozconfig.gtk
+++ b/build/unix/mozconfig.gtk
@@ -1,48 +1,28 @@
-# $topsrcdir/gtk3 comes from tooltool, when the tooltool manifest contains it.
-if [ -d "$topsrcdir/gtk3" ]; then
+TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
+
+# $TOOLTOOL_DIR/gtk3 comes from tooltool, when the tooltool manifest contains it.
+if [ -d "$TOOLTOOL_DIR/gtk3" ]; then
   if [ -z "$PKG_CONFIG_LIBDIR" ]; then
     echo PKG_CONFIG_LIBDIR must be set >&2
     exit 1
   fi
-  export PKG_CONFIG_SYSROOT_DIR="$topsrcdir/gtk3"
-  export PKG_CONFIG_PATH="$topsrcdir/gtk3/usr/local/lib/pkgconfig"
-  export PATH="$topsrcdir/gtk3/usr/local/bin:${PATH}"
+  export PKG_CONFIG_SYSROOT_DIR="$TOOLTOOL_DIR/gtk3"
+  export PKG_CONFIG_PATH="$TOOLTOOL_DIR/gtk3/usr/local/lib/pkgconfig"
+  export PATH="$TOOLTOOL_DIR/gtk3/usr/local/bin:${PATH}"
   # Ensure cairo, gdk-pixbuf, etc. are not taken from the system installed packages.
-  LDFLAGS="-L$topsrcdir/gtk3/usr/local/lib ${LDFLAGS}"
-  mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/gtk3/usr/local/lib"
+  LDFLAGS="-L$TOOLTOOL_DIR/gtk3/usr/local/lib ${LDFLAGS}"
   ac_add_options --enable-default-toolkit=cairo-gtk3
 
   # Set things up to use Gtk+3 from the tooltool package
-  mk_add_options "export FONTCONFIG_PATH=$topsrcdir/gtk3/usr/local/etc/fonts"
-  mk_add_options "export PANGO_SYSCONFDIR=$topsrcdir/gtk3/usr/local/etc"
-  mk_add_options "export PANGO_LIBDIR=$topsrcdir/gtk3/usr/local/lib"
-  mk_add_options "export GDK_PIXBUF_MODULE_FILE=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
-  mk_add_options "export GDK_PIXBUF_MODULEDIR=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders"
-  mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/gtk3/usr/local/lib"
-
-  # pango expects absolute paths in pango.modules, and topsrcdir may vary...
-  LD_LIBRARY_PATH=$topsrcdir/gtk3/usr/local/lib \
-  PANGO_SYSCONFDIR=$topsrcdir/gtk3/usr/local/etc \
-  PANGO_LIBDIR=$topsrcdir/gtk3/usr/local/lib \
-  $topsrcdir/gtk3/usr/local/bin/pango-querymodules > $topsrcdir/gtk3/usr/local/etc/pango/pango.modules
+  mk_add_options "export FONTCONFIG_PATH=$TOOLTOOL_DIR/gtk3/usr/local/etc/fonts"
+  mk_add_options "export PANGO_SYSCONFDIR=$TOOLTOOL_DIR/gtk3/usr/local/etc"
+  mk_add_options "export PANGO_LIBDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib"
+  mk_add_options "export GDK_PIXBUF_MODULE_FILE=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
+  mk_add_options "export GDK_PIXBUF_MODULEDIR=$TOOLTOOL_DIR/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders"
+  mk_add_options "export LD_LIBRARY_PATH=$TOOLTOOL_DIR/gtk3/usr/local/lib"
 
-  # same with gdb-pixbuf and loaders.cache
-  LD_LIBRARY_PATH=$topsrcdir/gtk3/usr/local/lib \
-  GDK_PIXBUF_MODULE_FILE=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache \
-  GDK_PIXBUF_MODULEDIR=$topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders \
-  $topsrcdir/gtk3/usr/local/bin/gdk-pixbuf-query-loaders > $topsrcdir/gtk3/usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
-
-  # mock build environment doesn't have fonts in /usr/share/fonts, but
-  # has some in /usr/share/X11/fonts. Add this directory to the
-  # fontconfig configuration without changing the gtk3 tooltool package.
-  cat << EOF > $topsrcdir/gtk3/usr/local/etc/fonts/local.conf
-<?xml version="1.0"?>
-<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
-<fontconfig>
-  <dir>/usr/share/X11/fonts</dir>
-</fontconfig>
-EOF
-
+  # Until a tooltool with bug 1188571 landed is available everywhere
+  $TOOLTOOL_DIR/gtk3/setup.sh
 else
   ac_add_options --enable-default-toolkit=cairo-gtk2
 fi
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -106,16 +106,23 @@ class MachCommands(MachCommandBase):
                 '--smc-check=all-non-file',
                 '--vex-iropt-register-updates=allregs-at-mem-access',
                 '--gen-suppressions=all',
                 '--num-callers=36',
                 '--leak-check=full',
                 '--show-possibly-lost=no',
                 '--track-origins=yes',
                 '--trace-children=yes',
+                # The gstreamer plugin scanner can run as part of executing
+                # firefox, but is an external program. In some weird cases,
+                # valgrind finds errors while executing __libc_freeres when
+                # it runs, but those are not relevant, as it's related to
+                # executing third party code. So don't trace
+                # gst-plugin-scanner.
+                '--trace-children-skip=*/gst-plugin-scanner',
                 '-v',  # Enable verbosity to get the list of used suppressions
             ]
 
             for s in suppressions:
                 valgrind_args.append('--suppressions=' + s)
 
             supps_dir = os.path.join(build_dir, 'valgrind')
             supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup')
--- a/build/valgrind/valgrind.sh
+++ b/build/valgrind/valgrind.sh
@@ -20,17 +20,17 @@ fi
 cd $objdir
 
 if [ "`uname -m`" = "x86_64" ]; then
     _arch=64
 else
     _arch=32
 fi
 
-TOOLTOOL_MANIFEST=browser/config/tooltool-manifests/linux${_arch}/valgrind.manifest
+TOOLTOOL_MANIFEST=browser/config/tooltool-manifests/linux${_arch}/releng.manifest
 TOOLTOOL_SERVER=https://api.pub.build.mozilla.org/tooltool/
 (cd $srcdir; python /builds/tooltool.py --url $TOOLTOOL_SERVER --overwrite -m $TOOLTOOL_MANIFEST fetch ${TOOLTOOL_CACHE:+ -c ${TOOLTOOL_CACHE}}) || exit 2
 
 # Note: an exit code of 2 turns the job red on TBPL.
 MOZCONFIG=$srcdir/browser/config/mozconfigs/linux${_arch}/valgrind make -f $srcdir/client.mk configure || exit 2
 make -j4 || exit 2
 make package || exit 2
 
--- a/build/valgrind/x86_64-redhat-linux-gnu.sup
+++ b/build/valgrind/x86_64-redhat-linux-gnu.sup
@@ -23,16 +23,124 @@
    Memcheck:Leak
    fun:realloc
    obj:/usr/lib64/libfontconfig.so.1.4.4
    ...
    fun:FcDefaultSubstitute
    fun:_ZN17gfxPangoFontGroup11MakeFontSetEP14_PangoLanguagedP9nsAutoRefI10_FcPatternE
    ...
 }
+# Fontconfig is going fancy with its cache structure and that confuses valgrind.
+# https://bugs.freedesktop.org/show_bug.cgi?id=8215
+# https://bugs.freedesktop.org/show_bug.cgi?id=8428
+{
+   Bug 1187649
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:realloc
+   fun:FcPatternObjectInsertElt
+   ...
+}
+# Leaks due to either Gtk+3 or cairo, but Gecko is not directly involved with
+# those cairo interactions.
+{
+   Bug 1187649
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:_cairo_freelist_alloc
+   fun:_cairo_xlib_display_queue_resource
+   fun:_cairo_xlib_surface_finish
+   ...
+   fun:gtk_widget_realize
+   fun:_ZN8nsWindow6CreateEP9nsIWidgetPvRKN7mozilla3gfx12IntRectTypedINS4_12UnknownUnitsEEEP16nsWidgetInitData
+   ...
+}
+# The three following leaks are deep in Gtk+3, and it doesn't seem we're doing
+# anything wrong on our end with the container objects. Those suppressions
+# are purposefully verbose so as to avoid them catching actual leaks due to
+# Gecko code.
+# Note: valgrind doesn't support more than 24 elements in a suppression stack,
+# which explains why the second has an ellipsis above g_slice_alloc.
+{
+   Bug 1187649
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:g_malloc
+   fun:g_slice_alloc
+   fun:g_list_prepend
+   fun:gtk_combo_box_get_path_for_child
+   fun:gtk_container_get_path_for_child
+   fun:gtk_widget_get_path
+   fun:_gtk_widget_update_path
+   fun:reset_style_recurse
+   fun:gtk_widget_reset_style
+   fun:gtk_widget_set_parent
+   fun:gtk_combo_box_add
+   fun:g_cclosure_marshal_VOID__OBJECTv
+   fun:_g_closure_invoke_va
+   fun:g_signal_emit_valist
+   fun:g_signal_emit
+   fun:gtk_combo_box_constructor
+   fun:g_object_newv
+   fun:g_object_new_valist
+   fun:g_object_new
+   fun:_ZN13nsLookAndFeel4InitEv
+   ...
+}
+{
+   Bug 1187649
+   Memcheck:Leak
+   match-leak-kinds: definite
+   ...
+   fun:g_slice_alloc
+   fun:g_slice_copy
+   fun:boxed_proxy_lcopy_value
+   fun:gtk_style_context_get_valist
+   fun:gtk_style_context_get
+   fun:set_color
+   fun:gtk_style_update_from_context
+   fun:gtk_style_constructed
+   fun:g_object_newv
+   fun:g_object_new_valist
+   fun:g_object_new
+   fun:_gtk_style_new_for_path
+   fun:gtk_style_new
+   fun:gtk_widget_get_default_style
+   fun:gtk_widget_init
+   fun:g_type_create_instance
+   fun:g_object_constructor
+   fun:g_object_newv
+   fun:g_object_new
+   fun:gtk_accel_label_new
+   fun:_ZN13nsLookAndFeel4InitEv
+   ...
+}
+{
+   Bug 1187649
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:g_malloc
+   fun:g_slice_alloc
+   fun:g_slice_copy
+   fun:boxed_proxy_lcopy_value
+   fun:gtk_style_context_get_valist
+   fun:gtk_style_context_get
+   fun:set_color
+   fun:gtk_style_update_from_context
+   fun:gtk_style_constructed
+   fun:g_object_newv
+   fun:g_object_new_valist
+   fun:g_object_new
+   fun:gtk_widget_get_style
+   fun:_ZN13nsIconChannel4InitEP6nsIURI
+   ...
+}
 {
    Bug 794366
    Memcheck:Leak
    ...
    obj:/usr/lib64/libgtk-x11-2.0.so.0.1800.9
    ...
 }
 {
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -42,16 +42,22 @@ OriginAttributes::CreateSuffix(nsACStrin
   if (mInBrowser) {
     params->Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1"));
   }
 
   if (!mAddonId.IsEmpty()) {
     params->Set(NS_LITERAL_STRING("addonId"), mAddonId);
   }
 
+  if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
+    value.Truncate();
+    value.AppendInt(mUserContextId);
+    params->Set(NS_LITERAL_STRING("userContextId"), value);
+  }
+
   aStr.Truncate();
 
   params->Serialize(value);
   if (!value.IsEmpty()) {
     aStr.AppendLiteral("^");
     aStr.Append(NS_ConvertUTF16toUTF8(value));
   }
 }
@@ -95,16 +101,26 @@ public:
     }
 
     if (aName.EqualsLiteral("addonId")) {
       MOZ_RELEASE_ASSERT(mOriginAttributes->mAddonId.IsEmpty());
       mOriginAttributes->mAddonId.Assign(aValue);
       return true;
     }
 
+    if (aName.EqualsLiteral("userContextId")) {
+      nsresult rv;
+      mOriginAttributes->mUserContextId = aValue.ToInteger(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      return true;
+    }
+
     // No other attributes are supported.
     return false;
   }
 
 private:
   OriginAttributes* mOriginAttributes;
 };
 
@@ -304,16 +320,23 @@ BasePrincipal::GetAppId(uint32_t* aAppId
     return NS_OK;
   }
 
   *aAppId = AppId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+BasePrincipal::GetUserContextId(uint32_t* aUserContextId)
+{
+  *aUserContextId = UserContextId();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 BasePrincipal::GetIsInBrowserElement(bool* aIsInBrowserElement)
 {
   *aIsInBrowserElement = IsInBrowserElement();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetUnknownAppId(bool* aUnknownAppId)
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -30,17 +30,18 @@ public:
   }
   explicit OriginAttributes(const OriginAttributesDictionary& aOther)
     : OriginAttributesDictionary(aOther) {}
 
   bool operator==(const OriginAttributes& aOther) const
   {
     return mAppId == aOther.mAppId &&
            mInBrowser == aOther.mInBrowser &&
-           mAddonId == aOther.mAddonId;
+           mAddonId == aOther.mAddonId &&
+           mUserContextId == aOther.mUserContextId;
   }
   bool operator!=(const OriginAttributes& aOther) const
   {
     return !(*this == aOther);
   }
 
   // Serializes/Deserializes non-default values into the suffix format, i.e.
   // |!key1=value1&key2=value2|. If there are no non-default attributes, this
@@ -78,16 +79,20 @@ public:
     if (mInBrowser.WasPassed() && mInBrowser.Value() != aAttrs.mInBrowser) {
       return false;
     }
 
     if (mAddonId.WasPassed() && mAddonId.Value() != aAttrs.mAddonId) {
       return false;
     }
 
+    if (mUserContextId.WasPassed() && mUserContextId.Value() != aAttrs.mUserContextId) {
+      return false;
+    }
+
     return true;
   }
 };
 
 /*
  * Base class from which all nsIPrincipal implementations inherit. Use this for
  * default implementations and other commonalities between principal
  * implementations.
@@ -114,26 +119,28 @@ public:
   NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override;
   NS_IMETHOD GetJarPrefix(nsACString& aJarPrefix) final;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
   NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
   NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
   NS_IMETHOD GetIsInBrowserElement(bool* aIsInBrowserElement) final;
   NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
+  NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
 
   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> CreateCodebasePrincipal(nsIURI* aURI, OriginAttributes& aAttrs);
 
   const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
   uint32_t AppId() const { return mOriginAttributes.mAppId; }
+  uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
   bool IsInBrowserElement() const { return mOriginAttributes.mInBrowser; }
 
 protected:
   virtual ~BasePrincipal();
 
   virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0;
   virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0;
 
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -15,17 +15,17 @@ struct JSPrincipals;
 
 interface nsIURI;
 interface nsIContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
 [ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
 
-[scriptable, builtinclass, uuid(7a9aa074-7565-4567-af2f-9e3704c7af9e)]
+[scriptable, builtinclass, uuid(a083acd0-1ebf-4585-85ab-08cfdd9c96bd)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Returns whether the other principal is equivalent to this principal.
      * Principals are considered equal if they are the same principal, or
      * they have the same origin.
      */
     boolean equals(in nsIPrincipal other);
@@ -255,16 +255,23 @@ interface nsIPrincipal : nsISerializable
      * origin as the app.
      *
      * If you're doing a security check based on appId, you must check
      * appStatus as well.
      */
     [infallible] readonly attribute unsigned long appId;
 
     /**
+     * Gets the id of the user context this principal is inside.  If this
+     * principal is inside the default userContext, this returns
+     * nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID.
+     */
+    [infallible] readonly attribute unsigned long userContextId;
+
+    /**
      * Returns true iff the principal is inside a browser element.  (<iframe
      * mozbrowser mozapp> does not count as a browser element.)
      */
     [infallible] readonly attribute boolean isInBrowserElement;
 
     /**
      * Returns true if this principal has an unknown appId. This shouldn't
      * generally be used. We only expose it due to not providing the correct
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -21,17 +21,17 @@ class DomainPolicyClone;
 }
 }
 %}
 
 [ptr] native JSContextPtr(JSContext);
 [ptr] native JSObjectPtr(JSObject);
 [ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
 
-[scriptable, uuid(50418f5c-b0d8-42c3-ba5d-efffb6927e1c)]
+[scriptable, uuid(9a8f0b70-6b9f-4e19-8885-7cfe24f4a42d)]
 interface nsIScriptSecurityManager : nsISupports
 {
     /**
      * For each of these hooks returning NS_OK means 'let the action continue'.
      * Returning an error code means 'veto the action'. XPConnect will return
      * false to the js engine if the action is vetoed. The implementor of this
      * interface is responsible for setting a JS exception into the JSContext
      * if that is appropriate.
@@ -250,16 +250,18 @@ interface nsIScriptSecurityManager : nsI
       return isSystem;
     }
 %}
 
     const unsigned long NO_APP_ID = 0;
     const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
     const unsigned long SAFEBROWSING_APP_ID = 4294967294; // UINT32_MAX - 1
 
+    const unsigned long DEFAULT_USER_CONTEXT_ID = 0;
+
     /**
      * Returns the jar prefix for the app.
      * appId can be NO_APP_ID or a valid app id. appId should not be
      * UNKNOWN_APP_ID.
      * inMozBrowser has to be true if the app is inside a mozbrowser iframe.
      */
     AUTF8String getJarPrefix(in unsigned long appId, in boolean inMozBrowser);
 
--- a/caps/nsNullPrincipal.h
+++ b/caps/nsNullPrincipal.h
@@ -18,18 +18,18 @@
 #include "nsCOMPtr.h"
 #include "nsIContentSecurityPolicy.h"
 
 #include "mozilla/BasePrincipal.h"
 
 class nsIURI;
 
 #define NS_NULLPRINCIPAL_CID \
-{ 0x34a19ab6, 0xca47, 0x4098, \
-  { 0xa7, 0xb8, 0x4a, 0xfc, 0xdd, 0xcd, 0x8f, 0x88 } }
+{ 0xbd066e5f, 0x146f, 0x4472, \
+  { 0x83, 0x31, 0x7b, 0xfd, 0x05, 0xb1, 0xed, 0x90 } }
 #define NS_NULLPRINCIPAL_CONTRACTID "@mozilla.org/nullprincipal;1"
 
 #define NS_NULLPRINCIPAL_SCHEME "moz-nullprincipal"
 
 class nsNullPrincipal final : public mozilla::BasePrincipal
 {
 public:
   // This should only be used by deserialization, and the factory constructor.
--- a/caps/nsPrincipal.h
+++ b/caps/nsPrincipal.h
@@ -107,17 +107,17 @@ protected:
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override;
 
 private:
   nsTArray< nsCOMPtr<nsIPrincipal> > mPrincipals;
 };
 
 #define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1"
 #define NS_PRINCIPAL_CID \
-{ 0xb02c3023, 0x5b37, 0x472a, \
-  { 0xa2, 0xcd, 0x35, 0xaa, 0x5e, 0xe2, 0xa8, 0x19 } }
+{ 0x653e0e4d, 0x3ee4, 0x45fa, \
+  { 0xb2, 0x72, 0x97, 0xc2, 0x0b, 0xc0, 0x1e, 0xb8 } }
 
 #define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1"
 #define NS_EXPANDEDPRINCIPAL_CID \
 { 0xe8ee88b0, 0x5571, 0x4086, \
   { 0xa4, 0x5b, 0x39, 0xa7, 0x16, 0x90, 0x6b, 0xdb } }
 
 #endif // nsPrincipal_h__
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -92,18 +92,41 @@ function run_test() {
   var exampleOrg_addon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy'});
   checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '^addonId=dummy');
   do_check_eq(exampleOrg_addon.origin, 'http://example.org^addonId=dummy');
 
   // Make sure that we refuse to create .origin for principals with UNKNOWN_APP_ID.
   var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com'));
   try { simplePrin.origin; do_check_true(false); } catch (e) { do_check_true(true); }
 
+  // Just userContext.
+  var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
+  checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
+  do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42');
+
+  // UserContext and Addon.
+  var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42});
+  var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42});
+  checkOriginAttributes(exampleOrg_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
+  checkOriginAttributes(nullPrin_userContextAddon, {addonId: 'dummy', userContextId: 42}, '^addonId=dummy&userContextId=42');
+  do_check_eq(exampleOrg_userContextAddon.origin, 'http://example.org^addonId=dummy&userContextId=42');
+
+  // UserContext and App.
+  var exampleOrg_userContextApp = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 24, userContextId: 42});
+  var nullPrin_userContextApp = ssm.createNullPrincipal({appId: 24, userContextId: 42});
+  checkOriginAttributes(exampleOrg_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
+  checkOriginAttributes(nullPrin_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
+  do_check_eq(exampleOrg_userContextApp.origin, 'http://example.org^appId=24&userContextId=42');
+
   // Check that all of the above are cross-origin.
   checkCrossOrigin(exampleOrg_app, exampleOrg);
   checkCrossOrigin(exampleOrg_app, nullPrin_app);
   checkCrossOrigin(exampleOrg_browser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_browser, nullPrin_browser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser);
   checkCrossOrigin(exampleOrg_addon, exampleOrg);
+  checkCrossOrigin(exampleOrg_userContext, exampleOrg);
+  checkCrossOrigin(exampleOrg_userContextAddon, exampleOrg);
+  checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextAddon);
+  checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp);
 }
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -2,39 +2,44 @@
 /* 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 "Animation.h"
 #include "AnimationUtils.h"
 #include "mozilla/dom/AnimationBinding.h"
+#include "mozilla/dom/AnimationPlaybackEvent.h"
 #include "mozilla/AutoRestore.h"
+#include "mozilla/AsyncEventDispatcher.h" //For AsyncEventDispatcher
 #include "AnimationCommon.h" // For AnimationCollection,
                              // CommonAnimationManager
 #include "nsIDocument.h" // For nsIDocument
 #include "nsIPresShell.h" // For nsIPresShell
 #include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336)
 #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
 #include "PendingAnimationTracker.h" // For PendingAnimationTracker
 
 namespace mozilla {
 namespace dom {
 
 // Static members
 uint64_t Animation::sNextSequenceNum = 0;
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation, mGlobal, mTimeline,
-                                      mEffect, mReady, mFinished)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(Animation)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(Animation)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper,
+                                   mTimeline,
+                                   mEffect,
+                                   mReady,
+                                   mFinished)
+
+NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Animation)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 JSObject*
 Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return dom::AnimationBinding::Wrap(aCx, this, aGivenProto);
 }
 
 // ---------------------------------------------------------------------------
@@ -185,32 +190,34 @@ Animation::PlayState() const
   }
 
   return AnimationPlayState::Running;
 }
 
 Promise*
 Animation::GetReady(ErrorResult& aRv)
 {
-  if (!mReady && mGlobal) {
-    mReady = Promise::Create(mGlobal, aRv); // Lazily create on demand
+  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  if (!mReady && global) {
+    mReady = Promise::Create(global, aRv); // Lazily create on demand
   }
   if (!mReady) {
     aRv.Throw(NS_ERROR_FAILURE);
   } else if (PlayState() != AnimationPlayState::Pending) {
     mReady->MaybeResolve(this);
   }
   return mReady;
 }
 
 Promise*
 Animation::GetFinished(ErrorResult& aRv)
 {
-  if (!mFinished && mGlobal) {
-    mFinished = Promise::Create(mGlobal, aRv); // Lazily create on demand
+  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  if (!mFinished && global) {
+    mFinished = Promise::Create(global, aRv); // Lazily create on demand
   }
   if (!mFinished) {
     aRv.Throw(NS_ERROR_FAILURE);
   } else if (mFinishedIsResolved) {
     MaybeResolveFinishedPromise();
   }
   return mFinished;
 }
@@ -466,16 +473,18 @@ Animation::DoCancel()
     }
   }
 
   if (mFinished) {
     mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   }
   ResetFinishedPromise();
 
+  DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
+
   mHoldTime.SetNull();
   mStartTime.SetNull();
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 }
 
 void
 Animation::UpdateRelevance()
@@ -1062,41 +1071,70 @@ Animation::GetCollection() const
 
   return manager->GetAnimations(targetElement, targetPseudoType, false);
 }
 
 void
 Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
 {
   if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
-    MaybeResolveFinishedPromise();
+    DoFinishNotificationImmediately();
   } else if (!mFinishNotificationTask.IsPending()) {
     nsRefPtr<nsRunnableMethod<Animation>> runnable =
-      NS_NewRunnableMethod(this, &Animation::MaybeResolveFinishedPromise);
+      NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
     Promise::DispatchToMicroTask(runnable);
     mFinishNotificationTask = runnable;
   }
 }
 
 void
 Animation::ResetFinishedPromise()
 {
   mFinishedIsResolved = false;
   mFinished = nullptr;
 }
 
 void
 Animation::MaybeResolveFinishedPromise()
 {
+  if (mFinished) {
+    mFinished->MaybeResolve(this);
+  }
+  mFinishedIsResolved = true;
+}
+
+void
+Animation::DoFinishNotificationImmediately()
+{
   mFinishNotificationTask.Revoke();
 
   if (PlayState() != AnimationPlayState::Finished) {
     return;
   }
 
-  if (mFinished) {
-    mFinished->MaybeResolve(this);
+  MaybeResolveFinishedPromise();
+
+  DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
+}
+
+void
+Animation::DispatchPlaybackEvent(const nsAString& aName)
+{
+  AnimationPlaybackEventInit init;
+
+  if (aName.EqualsLiteral("finish")) {
+    init.mCurrentTime = GetCurrentTimeAsDouble();
   }
-  mFinishedIsResolved = true;
+  if (mTimeline) {
+    init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
+  }
+
+  nsRefPtr<AnimationPlaybackEvent> event =
+    AnimationPlaybackEvent::Constructor(this, aName, init);
+  event->SetTrusted(true);
+
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  asyncDispatcher->PostDOMEvent();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_Animation_h
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
 #include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
 #include "mozilla/dom/AnimationTimeline.h" // for AnimationTimeline
+#include "mozilla/DOMEventTargetHelper.h" // for DOMEventTargetHelper
 #include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadOnly
 #include "mozilla/dom/Promise.h" // for Promise
 #include "nsCSSProperty.h" // for nsCSSProperty
 #include "nsIGlobalObject.h"
 
 // X11 has a #define for CurrentTime.
 #ifdef CurrentTime
 #undef CurrentTime
@@ -42,37 +43,37 @@ class CommonAnimationManager;
 } // namespace css
 
 namespace dom {
 
 class CSSAnimation;
 class CSSTransition;
 
 class Animation
-  : public nsISupports
-  , public nsWrapperCache
+  : public DOMEventTargetHelper
 {
 protected:
   virtual ~Animation() {}
 
 public:
   explicit Animation(nsIGlobalObject* aGlobal)
-    : mGlobal(aGlobal)
+    : DOMEventTargetHelper(aGlobal)
     , mPlaybackRate(1.0)
     , mPendingState(PendingState::NotPending)
     , mSequenceNum(kUnsequenced)
     , mIsRunningOnCompositor(false)
     , mFinishedAtLastComposeStyle(false)
     , mIsRelevant(false)
     , mFinishedIsResolved(false)
   {
   }
 
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Animation)
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
+                                           DOMEventTargetHelper)
 
   AnimationTimeline* GetParentObject() const { return mTimeline; }
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   virtual CSSAnimation* AsCSSAnimation() { return nullptr; }
   virtual const CSSAnimation* AsCSSAnimation() const { return nullptr; }
   virtual CSSTransition* AsCSSTransition() { return nullptr; }
@@ -105,16 +106,18 @@ public:
   virtual Promise* GetReady(ErrorResult& aRv);
   virtual Promise* GetFinished(ErrorResult& aRv);
   void Cancel();
   virtual void Finish(ErrorResult& aRv);
   virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   virtual void Pause(ErrorResult& aRv);
   virtual void Reverse(ErrorResult& aRv);
   bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
+  IMPL_EVENT_HANDLER(finish);
+  IMPL_EVENT_HANDLER(cancel);
 
   // Wrapper functions for Animation DOM methods when called
   // from script.
   //
   // We often use the same methods internally and from script but when called
   // from script we (or one of our subclasses) perform extra steps such as
   // flushing style or converting the return type.
   Nullable<double> GetStartTimeAsDouble() const;
@@ -333,33 +336,34 @@ protected:
   void UpdateFinishedState(SeekFlag aSeekFlag,
                            SyncNotifyFlag aSyncNotifyFlag);
   void UpdateEffect();
   void FlushStyle() const;
   void PostUpdate();
   void ResetFinishedPromise();
   void MaybeResolveFinishedPromise();
   void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
+  void DoFinishNotificationImmediately();
+  void DispatchPlaybackEvent(const nsAString& aName);
 
   /**
    * Remove this animation from the pending animation tracker and reset
    * mPendingState as necessary. The caller is responsible for resolving or
    * aborting the mReady promise as necessary.
    */
   void CancelPendingTasks();
 
   bool IsPossiblyOrphanedPendingAnimation() const;
   StickyTimeDuration EffectEnd() const;
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
   virtual css::CommonAnimationManager* GetAnimationManager() const = 0;
   AnimationCollection* GetCollection() const;
 
-  nsCOMPtr<nsIGlobalObject> mGlobal;
   nsRefPtr<AnimationTimeline> mTimeline;
   nsRefPtr<KeyframeEffectReadOnly> mEffect;
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
   Nullable<TimeDuration> mHoldTime;  // Animation timescale
   Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
   Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale
   double mPlaybackRate;
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-animations/file_animation-oncancel.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<style>
+@keyframes abc {
+  to { transform: translate(10px) }
+}
+</style>
+<body>
+<script>
+'use strict';
+
+async_test(function(t) {
+  var div = addDiv(t, {'style': 'animation: abc 100s'});
+  var animation = div.getAnimations()[0];
+
+  var finishedTimelineTime;
+  animation.finished.then().catch(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.oncancel = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, null,
+      'event.currentTime should be null');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is rejected');
+  });
+
+  animation.cancel();
+}, 'oncancel event is fired when animation.cancel()');
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-animations/file_animation-onfinish.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<style>
+@keyframes abc {
+  to { transform: translate(10px) }
+}
+</style>
+<body>
+<script>
+'use strict';
+
+const ANIM_PROP_VAL = 'abc 100s';
+const ANIM_DURATION = 100000; // ms
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, 0,
+      'event.currentTime should be zero');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.playbackRate = -1;
+}, 'onfinish event is fired when the currentTime < 0 and ' +
+   'the playbackRate < 0');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, ANIM_DURATION,
+      'event.currentTime should be the effect end');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.currentTime = ANIM_DURATION;
+}, 'onfinish event is fired when the currentTime > 0 and ' +
+   'the playbackRate > 0');
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  var finishedTimelineTime;
+  animation.finished.then(function() {
+    finishedTimelineTime = animation.timeline.currentTime;
+  });
+
+  animation.onfinish = t.step_func_done(function(event) {
+    assert_equals(event.currentTime, ANIM_DURATION,
+      'event.currentTime should be the effect end');
+    assert_equals(event.timelineTime, finishedTimelineTime,
+      'event.timelineTime should equal to the animation timeline ' +
+      'when finished promise is resolved');
+  });
+
+  animation.finish();
+}, 'onfinish event is fired when animation.finish() is called');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.onfinish = t.step_func(function(event) {
+    assert_unreached('onfinish event should not be fired');
+  });
+
+  animation.currentTime = ANIM_DURATION / 2;
+  animation.pause();
+
+  animation.ready.then(t.step_func(function() {
+    animation.currentTime = ANIM_DURATION;
+    return waitForAnimationFrames(2);
+  })).then(t.step_func(function() {
+    t.done();
+  }));
+}, 'onfinish event is not fired when paused');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.onfinish = t.step_func(function(event) {
+    assert_unreached('onfinish event should not be fired');
+  });
+
+  animation.ready.then(function() {
+    animation.playbackRate = 0;
+    animation.currentTime = ANIM_DURATION;
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(function() {
+    t.done();
+  }));
+
+}, 'onfinish event is not fired when the playbackRate is zero');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.onfinish = t.step_func(function(event) {
+    assert_unreached('onfinish event should not be fired');
+  });
+
+  animation.ready.then(function() {
+    animation.currentTime = ANIM_DURATION;
+    animation.currentTime = ANIM_DURATION / 2;
+    return waitForAnimationFrames(2);
+  }).then(t.step_func(function() {
+    t.done();
+  }));
+
+}, 'onfinish event is not fired when the animation falls out ' +
+   'finished state immediately');
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-animations/test_animation-oncancel.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-oncancel.html");
+  });
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-animations/test_animation-onfinish.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-onfinish.html");
+  });
+</script>
+</html>
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -7,16 +7,20 @@ support-files = css-animations/file_anim
 [css-animations/test_animation-cancel.html]
 support-files = css-animations/file_animation-cancel.html
 [css-animations/test_animation-currenttime.html]
 support-files = css-animations/file_animation-currenttime.html
 [css-animations/test_animation-finish.html]
 support-files = css-animations/file_animation-finish.html
 [css-animations/test_animation-finished.html]
 support-files = css-animations/file_animation-finished.html
+[css-animations/test_animation-oncancel.html]
+support-files = css-animations/file_animation-oncancel.html
+[css-animations/test_animation-onfinish.html]
+support-files = css-animations/file_animation-onfinish.html
 [css-animations/test_animation-pausing.html]
 support-files = css-animations/file_animation-pausing.html
 [css-animations/test_animation-play.html]
 support-files = css-animations/file_animation-play.html
 [css-animations/test_animation-playbackrate.html]
 support-files = css-animations/file_animation-playbackrate.html
 [css-animations/test_animation-playstate.html]
 support-files = css-animations/file_animation-playstate.html
--- a/dom/base/StructuredCloneHelper.cpp
+++ b/dom/base/StructuredCloneHelper.cpp
@@ -1,18 +1,21 @@
 /* -*- 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 "StructuredCloneHelper.h"
 
+#include "ImageContainer.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/FileListBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 JSObject*
@@ -358,16 +361,25 @@ StructuredCloneHelper::ReadCallback(JSCo
       if (!ToJSValue(aCx, fileList, &val)) {
         return nullptr;
       }
     }
 
     return &val.toObject();
   }
 
+  if (aTag == SCTAG_DOM_IMAGEBITMAP) {
+     // Get the current global object.
+     // This can be null.
+     nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+     // aIndex is the index of the cloned image.
+     return ImageBitmap::ReadStructuredClone(aCx, aReader,
+                                             parent, GetImages(), aIndex);
+   }
+
   return NS_DOMReadStructuredClone(aCx, aReader, aTag, aIndex, nullptr);
 }
 
 bool
 StructuredCloneHelper::WriteCallback(JSContext* aCx,
                                      JSStructuredCloneWriter* aWriter,
                                      JS::Handle<JSObject*> aObj)
 {
@@ -406,16 +418,26 @@ StructuredCloneHelper::WriteCallback(JSC
       for (uint32_t i = 0; i < fileList->Length(); ++i) {
         mBlobImplArray.AppendElement(fileList->Item(i)->Impl());
       }
 
       return true;
     }
   }
 
+  // See if this is an ImageBitmap object.
+  {
+    ImageBitmap* imageBitmap = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
+      return ImageBitmap::WriteStructuredClone(aWriter,
+                                               GetImages(),
+                                               imageBitmap);
+    }
+  }
+
   return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr);
 }
 
 bool
 StructuredCloneHelper::ReadTransferCallback(JSContext* aCx,
                                             JSStructuredCloneReader* aReader,
                                             uint32_t aTag,
                                             void* aContent,
@@ -449,17 +471,16 @@ StructuredCloneHelper::ReadTransferCallb
 
     aReturnObject.set(&value.toObject());
     return true;
   }
 
   return false;
 }
 
-
 bool
 StructuredCloneHelper::WriteTransferCallback(JSContext* aCx,
                                              JS::Handle<JSObject*> aObj,
                                              uint32_t* aTag,
                                              JS::TransferableOwnership* aOwnership,
                                              void** aContent,
                                              uint64_t* aExtraData)
 {
--- a/dom/base/StructuredCloneHelper.h
+++ b/dom/base/StructuredCloneHelper.h
@@ -7,16 +7,20 @@
 #define mozilla_dom_StructuredCloneHelper_h
 
 #include "js/StructuredClone.h"
 #include "nsAutoPtr.h"
 #include "nsISupports.h"
 #include "nsTArray.h"
 
 namespace mozilla {
+namespace layers {
+class Image;
+}
+
 namespace dom {
 
 class StructuredCloneHelperInternal
 {
 public:
   StructuredCloneHelperInternal();
   virtual ~StructuredCloneHelperInternal();
 
@@ -172,16 +176,21 @@ public:
   }
 
   nsTArray<MessagePortIdentifier>& PortIdentifiers()
   {
     MOZ_ASSERT(mSupportsTransferring);
     return mPortIdentifiers;
   }
 
+  nsTArray<nsRefPtr<layers::Image>>& GetImages()
+  {
+    return mClonedImages;
+  }
+
   // Custom Callbacks
 
   virtual JSObject* ReadCallback(JSContext* aCx,
                                  JSStructuredCloneReader* aReader,
                                  uint32_t aTag,
                                  uint32_t aIndex) override;
 
   virtual bool WriteCallback(JSContext* aCx,
@@ -209,16 +218,22 @@ public:
 private:
   bool mSupportsCloning;
   bool mSupportsTransferring;
 
   // Useful for the structured clone algorithm:
 
   nsTArray<nsRefPtr<BlobImpl>> mBlobImplArray;
 
+  // This is used for sharing the backend of ImageBitmaps.
+  // The layers::Image object must be thread-safely reference-counted.
+  // The layers::Image object will not be written ever via any ImageBitmap
+  // instance, so no race condition will occur.
+  nsTArray<nsRefPtr<layers::Image>> mClonedImages;
+
   // This raw pointer is set and unset into the ::Read(). It's always null
   // outside that method. For this reason it's a raw pointer.
   nsISupports* MOZ_NON_OWNING_REF mParent;
 
   // This hashtable contains the ports while doing write (transferring and
   // mapping transferred objects to the objects in the clone). It's an empty
   // array outside the 'Write()' method.
   nsTArray<nsRefPtr<MessagePortBase>> mTransferringPort;
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -37,16 +37,17 @@ enum StructuredCloneTags {
   // This tag is for WebCrypto keys
   SCTAG_DOM_WEBCRYPTO_KEY,
 
   SCTAG_DOM_NULL_PRINCIPAL,
   SCTAG_DOM_SYSTEM_PRINCIPAL,
   SCTAG_DOM_CONTENT_PRINCIPAL,
 
   SCTAG_DOM_NFC_NDEF,
+  SCTAG_DOM_IMAGEBITMAP,
 
   SCTAG_DOM_RTC_CERTIFICATE,
 
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1284,67 +1284,32 @@ nsExternalResourceMap::PendingLoad::OnSt
 
 nsresult
 nsExternalResourceMap::PendingLoad::StartLoad(nsIURI* aURI,
                                               nsINode* aRequestingNode)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aRequestingNode, "Must have a node");
 
-  // Time to start a load.  First, the security checks.
-
-  nsIPrincipal* requestingPrincipal = aRequestingNode->NodePrincipal();
-
-  nsresult rv = nsContentUtils::GetSecurityManager()->
-    CheckLoadURIWithPrincipal(requestingPrincipal, aURI,
-                              nsIScriptSecurityManager::STANDARD);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Allow data URIs and other URI's that inherit their principal by passing
-  // true as the 3rd argument of CheckMayLoad, since we want
-  // to allow external resources from data URIs regardless of the difference
-  // in URI scheme.
-  rv = requestingPrincipal->CheckMayLoad(aURI, true, true);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OTHER,
-                                 aURI,
-                                 requestingPrincipal,
-                                 aRequestingNode,
-                                 EmptyCString(), //mime guess
-                                 nullptr,         //extra
-                                 &shouldLoad,
-                                 nsContentUtils::GetContentPolicy(),
-                                 nsContentUtils::GetSecurityManager());
-  if (NS_FAILED(rv)) return rv;
-  if (NS_CP_REJECTED(shouldLoad)) {
-    // Disallowed by content policy
-    return NS_ERROR_CONTENT_BLOCKED;
-  }
-
-  nsIDocument* doc = aRequestingNode->OwnerDoc();
-
-  nsCOMPtr<nsIInterfaceRequestor> req = nsContentUtils::SameOriginChecker();
-
-  nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+  nsCOMPtr<nsILoadGroup> loadGroup =
+    aRequestingNode->OwnerDoc()->GetDocumentLoadGroup();
+
+  nsresult rv = NS_OK;
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewChannel(getter_AddRefs(channel),
                      aURI,
                      aRequestingNode,
-                     nsILoadInfo::SEC_NORMAL,
+                     nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
                      nsIContentPolicy::TYPE_OTHER,
-                     loadGroup,
-                     req); // aCallbacks
-
+                     loadGroup);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mURI = aURI;
 
-  return channel->AsyncOpen(this, nullptr);
+  return channel->AsyncOpen2(this);
 }
 
 NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks,
                   nsIInterfaceRequestor)
 
 #define IMPL_SHIM(_i) \
   NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -691,16 +691,17 @@ GK_ATOM(onbeforeprint, "onbeforeprint")
 GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
 GK_ATOM(onbeforeunload, "onbeforeunload")
 GK_ATOM(onblocked, "onblocked")
 GK_ATOM(onblur, "onblur")
 GK_ATOM(onbroadcast, "onbroadcast")
 GK_ATOM(onbusy, "onbusy")
 GK_ATOM(oncached, "oncached")
 GK_ATOM(oncallschanged, "oncallschanged")
+GK_ATOM(oncancel, "oncancel")
 GK_ATOM(oncardstatechange, "oncardstatechange")
 GK_ATOM(oncfstatechange, "oncfstatechange")
 GK_ATOM(onchange, "onchange")
 GK_ATOM(oncharacteristicchanged, "oncharacteristicchanged")
 GK_ATOM(onchargingchange, "onchargingchange")
 GK_ATOM(onchargingtimechange, "onchargingtimechange")
 GK_ATOM(onchecking, "onchecking")
 GK_ATOM(onclick, "onclick")
@@ -765,16 +766,17 @@ GK_ATOM(oneitbroadcasted, "oneitbroadcas
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfacesdetected, "onfacesdetected")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfetch, "onfetch")
+GK_ATOM(onfinish, "onfinish")
 GK_ATOM(onfocus, "onfocus")
 GK_ATOM(onfrequencychange, "onfrequencychange")
 GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
 GK_ATOM(onget, "onget")
 GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(onheadphoneschange, "onheadphoneschange")
 GK_ATOM(onheld, "onheld")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -37,17 +37,16 @@
 #include "nsIScriptTimeoutHandler.h"
 #include "nsIController.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsISlowScriptDebug.h"
 #include "nsWindowMemoryReporter.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
-#include "nsIConsoleService.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "jswrapper.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsJSEnvironment.h"
@@ -214,16 +213,17 @@
 #include "mozilla/dom/PopStateEvent.h"
 #include "mozilla/dom/PopupBlockedEvent.h"
 #include "mozilla/dom/PrimitiveConversions.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
 #endif
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
@@ -1561,22 +1561,16 @@ nsGlobalWindow::ClearControllers()
   }
 }
 
 void
 nsGlobalWindow::FreeInnerObjects()
 {
   NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window");
 
-  // Prune messages related to this window in the console cache
-  nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
-  if (console) {
-    console->ClearMessagesForWindowID(mWindowID);
-  }
-
   // Make sure that this is called before we null out the document and
   // other members that the window destroyed observers could
   // re-create.
   NotifyDOMWindowDestroyed(this);
 
   mInnerObjectsFreed = true;
 
   // Kill all of the workers for this window.
@@ -14660,8 +14654,23 @@ nsGlobalWindow::FireOnNewGlobalObject()
   AutoEntryScript aes(this, "nsGlobalWindow report new global");
   JS::Rooted<JSObject*> global(aes.cx(), GetWrapper());
   JS_FireOnNewGlobalObject(aes.cx(), global);
 }
 
 #ifdef _WINDOWS_
 #error "Never include windows.h in this file!"
 #endif
+
+already_AddRefed<Promise>
+nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
+                                  ErrorResult& aRv)
+{
+  return ImageBitmap::Create(this, aImage, Nothing(), aRv);
+}
+
+already_AddRefed<Promise>
+nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
+                                  int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+                                  ErrorResult& aRv)
+{
+  return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv);
+}
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -47,16 +47,17 @@
 #include "nsIIdleObserver.h"
 #include "nsIDocument.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "Units.h"
 #include "nsComponentManagerUtils.h"
 #include "nsSize.h"
 #include "nsCheapSets.h"
+#include "mozilla/dom/ImageBitmapSource.h"
 
 #define DEFAULT_HOME_PAGE "www.mozilla.org"
 #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage"
 
 // Amount of time allowed between alert/prompt/confirm before enabling
 // the stop dialog checkbox.
 #define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec
 
@@ -1127,16 +1128,25 @@ public:
                    mozilla::ErrorResult& aError)
   {
     if (mDoc) {
       mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
     }
     GetContent(aCx, aRetval, aError);
   }
 
+  already_AddRefed<mozilla::dom::Promise>
+  CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage,
+                    mozilla::ErrorResult& aRv);
+
+  already_AddRefed<mozilla::dom::Promise>
+  CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage,
+                    int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+                    mozilla::ErrorResult& aRv);
+
   // ChromeWindow bits.  Do NOT call these unless your window is in
   // fact an nsGlobalChromeWindow.
   uint16_t WindowState();
   nsIBrowserDOMWindow* GetBrowserDOMWindowOuter();
   nsIBrowserDOMWindow* GetBrowserDOMWindow(mozilla::ErrorResult& aError);
   void SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow);
   void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
                            mozilla::ErrorResult& aError);
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1019,37 +1019,43 @@ ThrowingConstructor(JSContext* cx, unsig
 
 bool
 ThrowConstructorWithoutNew(JSContext* cx, const char* name)
 {
   return ThrowErrorMessage(cx, MSG_CONSTRUCTOR_WITHOUT_NEW, name);
 }
 
 inline const NativePropertyHooks*
+GetNativePropertyHooksFromConstructorFunction(JS::Handle<JSObject*> obj)
+{
+  MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
+  const JS::Value& v =
+    js::GetFunctionNativeReserved(obj,
+                                  CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
+  const JSNativeHolder* nativeHolder =
+    static_cast<const JSNativeHolder*>(v.toPrivate());
+  return nativeHolder->mPropertyHooks;
+}
+
+inline const NativePropertyHooks*
 GetNativePropertyHooks(JSContext *cx, JS::Handle<JSObject*> obj,
                        DOMObjectType& type)
 {
   const js::Class* clasp = js::GetObjectClass(obj);
 
   const DOMJSClass* domClass = GetDOMClass(clasp);
   if (domClass) {
     bool isGlobal = (clasp->flags & JSCLASS_DOM_GLOBAL) != 0;
     type = isGlobal ? eGlobalInstance : eInstance;
     return domClass->mNativeHooks;
   }
 
   if (JS_ObjectIsFunction(cx, obj)) {
-    MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
     type = eInterface;
-    const JS::Value& v =
-      js::GetFunctionNativeReserved(obj,
-                                    CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
-    const JSNativeHolder* nativeHolder =
-      static_cast<const JSNativeHolder*>(v.toPrivate());
-    return nativeHolder->mPropertyHooks;
+    return GetNativePropertyHooksFromConstructorFunction(obj);
   }
 
   MOZ_ASSERT(IsDOMIfaceAndProtoClass(js::GetObjectClass(obj)));
   const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
     DOMIfaceAndProtoJSClass::FromJSClass(js::GetObjectClass(obj));
   type = ifaceAndProtoJSClass->mType;
   return ifaceAndProtoJSClass->mNativeHooks;
 }
@@ -2929,10 +2935,120 @@ ForEachHandler(JSContext* aCx, unsigned 
   newArgs.append(args.get(0));
   newArgs.append(args.get(1));
   newArgs.append(maplikeOrSetlikeObj);
   JS::Rooted<JS::Value> rval(aCx, JS::UndefinedValue());
   // Now actually call the user specified callback
   return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval);
 }
 
+static inline prototypes::ID
+GetProtoIdForNewtarget(JS::Handle<JSObject*> aNewTarget)
+{
+  const js::Class* newTargetClass = js::GetObjectClass(aNewTarget);
+  if (IsDOMIfaceAndProtoClass(newTargetClass)) {
+    const DOMIfaceAndProtoJSClass* newTargetIfaceClass =
+      DOMIfaceAndProtoJSClass::FromJSClass(newTargetClass);
+    if (newTargetIfaceClass->mType == eInterface) {
+      return newTargetIfaceClass->mPrototypeID;
+    }
+  } else if (JS_IsNativeFunction(aNewTarget, Constructor)) {
+    return GetNativePropertyHooksFromConstructorFunction(aNewTarget)->mPrototypeID;
+  }
+
+  return prototypes::id::_ID_Count;
+}
+
+bool
+GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
+                JS::MutableHandle<JSObject*> aDesiredProto)
+{
+  if (!aCallArgs.isConstructing()) {
+    aDesiredProto.set(nullptr);
+    return true;
+  }
+
+  // The desired prototype depends on the actual constructor that was invoked,
+  // which is passed to us as the newTarget in the callargs.  We want to do
+  // something akin to the ES6 specification's GetProtototypeFromConstructor (so
+  // get .prototype on the newTarget, with a fallback to some sort of default).
+
+  // First, a fast path for the case when the the constructor is in fact one of
+  // our DOM constructors.  This is safe because on those the "constructor"
+  // property is non-configurable and non-writable, so we don't have to do the
+  // slow JS_GetProperty call.
+  JS::Rooted<JSObject*> newTarget(aCx, &aCallArgs.newTarget().toObject());
+  JS::Rooted<JSObject*> originalNewTarget(aCx, newTarget);
+  // See whether we have a known DOM constructor here, such that we can take a
+  // fast path.
+  prototypes::ID protoID = GetProtoIdForNewtarget(newTarget);
+  if (protoID == prototypes::id::_ID_Count) {
+    // We might still have a cross-compartment wrapper for a known DOM
+    // constructor.
+    newTarget = js::CheckedUnwrap(newTarget);
+    if (newTarget && newTarget != originalNewTarget) {
+      protoID = GetProtoIdForNewtarget(newTarget);
+    }
+  }
+
+  if (protoID != prototypes::id::_ID_Count) {
+    ProtoAndIfaceCache& protoAndIfaceCache =
+      *GetProtoAndIfaceCache(js::GetGlobalForObjectCrossCompartment(newTarget));
+    aDesiredProto.set(protoAndIfaceCache.EntrySlotMustExist(protoID));
+    if (newTarget != originalNewTarget) {
+      return JS_WrapObject(aCx, aDesiredProto);
+    }
+    return true;
+  }
+
+  // Slow path.  This basically duplicates the ES6 spec's
+  // GetPrototypeFromConstructor except that instead of taking a string naming
+  // the fallback prototype we just fall back to using null and assume that our
+  // caller will then pick the right default.  The actual defaulting behavior
+  // here still needs to be defined in the Web IDL specification.
+  //
+  // Note that it's very important to do this property get on originalNewTarget,
+  // not our unwrapped newTarget, since we want to get Xray behavior here as
+  // needed.
+  // XXXbz for speed purposes, using a preinterned id here sure would be nice.
+  JS::Rooted<JS::Value> protoVal(aCx);
+  if (!JS_GetProperty(aCx, originalNewTarget, "prototype", &protoVal)) {
+    return false;
+  }
+
+  if (!protoVal.isObject()) {
+    aDesiredProto.set(nullptr);
+    return true;
+  }
+
+  aDesiredProto.set(&protoVal.toObject());
+  return true;
+}
+
+#ifdef DEBUG
+namespace binding_detail {
+void
+AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
+                             JS::Handle<JSObject*> aGivenProto)
+{
+  if (!aGivenProto) {
+    // Nothing to assert here
+    return;
+  }
+
+  JS::Rooted<JSObject*> reflector(aCx, aReflector);
+  JSAutoCompartment ac(aCx, reflector);
+  JS::Rooted<JSObject*> reflectorProto(aCx);
+  bool ok = JS_GetPrototype(aCx, reflector, &reflectorProto);
+  MOZ_ASSERT(ok);
+  // aGivenProto may not be in the right compartment here, so we
+  // have to wrap it to compare.
+  JS::Rooted<JSObject*> givenProto(aCx, aGivenProto);
+  ok = JS_WrapObject(aCx, &givenProto);
+  MOZ_ASSERT(ok);
+  MOZ_ASSERT(givenProto == reflectorProto,
+             "How are we supposed to change the proto now?");
+}
+} // namespace binding_detail
+#endif // DEBUG
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -922,36 +922,45 @@ struct CheckWrapperCacheTracing<T, true>
 
     bool wasPreservingWrapper = wrapperCacheFromQI->PreservingWrapper();
     wrapperCacheFromQI->SetPreservingWrapper(true);
     wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant);
     wrapperCacheFromQI->SetPreservingWrapper(wasPreservingWrapper);
   }
 };
 
-#endif
+void
+AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
+                             JS::Handle<JSObject*> aGivenProto);
+#endif // DEBUG
 
 template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
 MOZ_ALWAYS_INLINE bool
 DoGetOrCreateDOMReflector(JSContext* cx, T* value,
+                          JS::Handle<JSObject*> givenProto,
                           JS::MutableHandle<JS::Value> rval)
 {
   MOZ_ASSERT(value);
-  JSObject* obj = value->GetWrapperPreserveColor();
   // We can get rid of this when we remove support for hasXPConnectImpls.
   bool couldBeDOMBinding = CouldBeDOMBinding(value);
+  JSObject* obj = value->GetWrapper();
   if (obj) {
-    JS::ExposeObjectToActiveJS(obj);
+#ifdef DEBUG
+    AssertReflectorHasGivenProto(cx, obj, givenProto);
+    // Have to reget obj because AssertReflectorHasGivenProto can
+    // trigger gc so the pointer may now be invalid.
+    obj = value->GetWrapper();
+#endif
   } else {
     // Inline this here while we have non-dom objects in wrapper caches.
     if (!couldBeDOMBinding) {
       return false;
     }
 
-    obj = value->WrapObject(cx, nullptr);
+    obj = value->WrapObject(cx, givenProto);
     if (!obj) {
       // At this point, obj is null, so just return false.
       // Callers seem to be testing JS_IsExceptionPending(cx) to
       // figure out whether WrapObject() threw.
       return false;
     }
 
 #ifdef DEBUG
@@ -1006,67 +1015,75 @@ DoGetOrCreateDOMReflector(JSContext* cx,
 // having "value" inherit from nsWrapperCache.
 //
 // The value stored in rval will be ready to be exposed to whatever JS
 // is running on cx right now.  In particular, it will be in the
 // compartment of cx, and outerized as needed.
 template <class T>
 MOZ_ALWAYS_INLINE bool
 GetOrCreateDOMReflector(JSContext* cx, T* value,
-                        JS::MutableHandle<JS::Value> rval)
+                        JS::MutableHandle<JS::Value> rval,
+                        JS::Handle<JSObject*> givenProto = nullptr)
 {
   using namespace binding_detail;
   return DoGetOrCreateDOMReflector<T, eWrapIntoContextCompartment>(cx, value,
+                                                                   givenProto,
                                                                    rval);
 }
 
 // Like GetOrCreateDOMReflector but doesn't wrap into the context compartment,
 // and hence does not actually require cx to be in a compartment.
 template <class T>
 MOZ_ALWAYS_INLINE bool
 GetOrCreateDOMReflectorNoWrap(JSContext* cx, T* value,
                               JS::MutableHandle<JS::Value> rval)
 {
   using namespace binding_detail;
   return DoGetOrCreateDOMReflector<T, eDontWrapIntoContextCompartment>(cx,
                                                                        value,
+                                                                       nullptr,
                                                                        rval);
 }
 
 // Create a JSObject wrapping "value", for cases when "value" is a
 // non-wrapper-cached object using WebIDL bindings.  "value" must implement a
 // WrapObject() method taking a JSContext and a scope.
 template <class T>
 inline bool
 WrapNewBindingNonWrapperCachedObject(JSContext* cx,
                                      JS::Handle<JSObject*> scopeArg,
                                      T* value,
-                                     JS::MutableHandle<JS::Value> rval)
+                                     JS::MutableHandle<JS::Value> rval,
+                                     JS::Handle<JSObject*> givenProto = nullptr)
 {
   static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
   MOZ_ASSERT(value);
   // We try to wrap in the compartment of the underlying object of "scope"
   JS::Rooted<JSObject*> obj(cx);
   {
     // scope for the JSAutoCompartment so that we restore the compartment
     // before we call JS_WrapValue.
     Maybe<JSAutoCompartment> ac;
     // Maybe<Handle> doesn't so much work, and in any case, adding
     // more Maybe (one for a Rooted and one for a Handle) adds more
     // code (and branches!) than just adding a single rooted.
     JS::Rooted<JSObject*> scope(cx, scopeArg);
+    JS::Rooted<JSObject*> proto(cx, givenProto);
     if (js::IsWrapper(scope)) {
       scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false);
       if (!scope)
         return false;
       ac.emplace(cx, scope);
+      if (!JS_WrapObject(cx, &proto)) {
+        return false;
+      }
     }
 
     MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
-    if (!value->WrapObject(cx, nullptr, &obj)) {
+    if (!value->WrapObject(cx, proto, &obj)) {
       return false;
     }
   }
 
   // We can end up here in all sorts of compartments, per above.  Make
   // sure to JS_WrapValue!
   rval.set(JS::ObjectValue(*obj));
   return MaybeWrapObjectValue(cx, rval);
@@ -1076,17 +1093,18 @@ WrapNewBindingNonWrapperCachedObject(JSC
 // non-wrapper-cached owned object using WebIDL bindings.  "value" must implement a
 // WrapObject() method taking a JSContext, a scope, and a boolean outparam that
 // is true if the JSObject took ownership
 template <class T>
 inline bool
 WrapNewBindingNonWrapperCachedObject(JSContext* cx,
                                      JS::Handle<JSObject*> scopeArg,
                                      nsAutoPtr<T>& value,
-                                     JS::MutableHandle<JS::Value> rval)
+                                     JS::MutableHandle<JS::Value> rval,
+                                     JS::Handle<JSObject*> givenProto = nullptr)
 {
   static_assert(!IsRefcounted<T>::value, "Only pass owned classes in here.");
   // We do a runtime check on value, because otherwise we might in
   // fact end up wrapping a null and invoking methods on it later.
   if (!value) {
     NS_RUNTIMEABORT("Don't try to wrap null objects");
   }
   // We try to wrap in the compartment of the underlying object of "scope"
@@ -1094,25 +1112,29 @@ WrapNewBindingNonWrapperCachedObject(JSC
   {
     // scope for the JSAutoCompartment so that we restore the compartment
     // before we call JS_WrapValue.
     Maybe<JSAutoCompartment> ac;
     // Maybe<Handle> doesn't so much work, and in any case, adding
     // more Maybe (one for a Rooted and one for a Handle) adds more
     // code (and branches!) than just adding a single rooted.
     JS::Rooted<JSObject*> scope(cx, scopeArg);
+    JS::Rooted<JSObject*> proto(cx, givenProto);
     if (js::IsWrapper(scope)) {
       scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false);
       if (!scope)
         return false;
       ac.emplace(cx, scope);
+      if (!JS_WrapObject(cx, &proto)) {
+        return false;
+      }
     }
 
     MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
-    if (!value->WrapObject(cx, nullptr, &obj)) {
+    if (!value->WrapObject(cx, proto, &obj)) {
       return false;
     }
 
     value.forget();
   }
 
   // We can end up here in all sorts of compartments, per above.  Make
   // sure to JS_WrapValue!
@@ -1121,19 +1143,21 @@ WrapNewBindingNonWrapperCachedObject(JSC
 }
 
 // Helper for smart pointers (nsRefPtr/nsCOMPtr).
 template <template <typename> class SmartPtr, typename T,
           typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type>
 inline bool
 WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope,
                                      const SmartPtr<T>& value,
-                                     JS::MutableHandle<JS::Value> rval)
+                                     JS::MutableHandle<JS::Value> rval,
+                                     JS::Handle<JSObject*> givenProto = nullptr)
 {
-  return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), rval);
+  return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), rval,
+                                              givenProto);
 }
 
 // Only set allowNativeWrapper to false if you really know you need it, if in
 // doubt use true. Setting it to false disables security wrappers.
 bool
 NativeInterface2JSObjectAndThrowIfFailed(JSContext* aCx,
                                          JS::Handle<JSObject*> aScope,
                                          JS::MutableHandle<JS::Value> aRetval,
@@ -1645,50 +1669,43 @@ struct GetParentObject<T, false>
 };
 
 // Helper for calling GetOrCreateDOMReflector with smart pointers
 // (nsAutoPtr/nsRefPtr/nsCOMPtr) or references.
 template <class T, bool isSmartPtr=IsSmartPtr<T>::value>
 struct GetOrCreateDOMReflectorHelper
 {
   static inline bool GetOrCreate(JSContext* cx, const T& value,
+                                 JS::Handle<JSObject*> givenProto,
                                  JS::MutableHandle<JS::Value> rval)
   {
-    return GetOrCreateDOMReflector(cx, value.get(), rval);
+    return GetOrCreateDOMReflector(cx, value.get(), rval, givenProto);
   }
 };
 
 template <class T>
 struct GetOrCreateDOMReflectorHelper<T, false>
 {
   static inline bool GetOrCreate(JSContext* cx, T& value,
+                                 JS::Handle<JSObject*> givenProto,
                                  JS::MutableHandle<JS::Value> rval)
   {
     static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
-    return GetOrCreateDOMReflector(cx, &value, rval);
+    return GetOrCreateDOMReflector(cx, &value, rval, givenProto);
   }
 };
 
 template<class T>
 inline bool
 GetOrCreateDOMReflector(JSContext* cx, T& value,
-                        JS::MutableHandle<JS::Value> rval)
+                        JS::MutableHandle<JS::Value> rval,
+                        JS::Handle<JSObject*> givenProto = nullptr)
 {
-  return GetOrCreateDOMReflectorHelper<T>::GetOrCreate(cx, value, rval);
-}
-
-// We need this version of GetOrCreateDOMReflector for codegen, so it'll have
-// the same signature as WrapNewBindingNonWrapperCachedObject and
-// WrapNewBindingNonWrapperCachedOwnedObject, which still need the scope.
-template<class T>
-inline bool
-GetOrCreateDOMReflector(JSContext* cx, JS::Handle<JSObject*> scope, T& value,
-                        JS::MutableHandle<JS::Value> rval)
-{
-  return GetOrCreateDOMReflector(cx, value, rval);
+  return GetOrCreateDOMReflectorHelper<T>::GetOrCreate(cx, value, givenProto,
+                                                       rval);
 }
 
 // Helper for calling GetOrCreateDOMReflectorNoWrap with smart pointers
 // (nsAutoPtr/nsRefPtr/nsCOMPtr) or references.
 template <class T, bool isSmartPtr=IsSmartPtr<T>::value>
 struct GetOrCreateDOMReflectorNoWrapHelper
 {
   static inline bool GetOrCreate(JSContext* cx, const T& value,
@@ -3289,13 +3306,18 @@ bool GetMaplikeBackingObject(JSContext* 
                              size_t aSlotIndex,
                              JS::MutableHandle<JSObject*> aBackingObj,
                              bool* aBackingObjCreated);
 bool GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
                              size_t aSlotIndex,
                              JS::MutableHandle<JSObject*> aBackingObj,
                              bool* aBackingObjCreated);
 
+// Get the desired prototype object for an object construction from the given
+// CallArgs.  Null is returned if the default prototype should be used.
+bool
+GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
+                JS::MutableHandle<JSObject*> aDesiredProto);
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1650,16 +1650,20 @@ class CGClassConstructor(CGAbstractStati
             #ifdef RELEASE_BUILD
             mayInvoke = mayInvoke || nsContentUtils::ThreadsafeIsCallerChrome();
             #endif // RELEASE_BUILD
             if (!mayInvoke) {
               // XXXbz wish I could get the name from the callee instead of
               // Adding more relocations
               return ThrowConstructorWithoutNew(cx, "${ctorName}");
             }
+            JS::Rooted<JSObject*> desiredProto(cx);
+            if (!GetDesiredProto(cx, args, &desiredProto)) {
+              return false;
+            }
             """,
             chromeOnlyCheck=chromeOnlyCheck,
             ctorName=ctorName)
 
         name = self._ctor.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
         callGenerator = CGMethodCall(nativeName, True, self.descriptor,
                                      self._ctor, isConstructor=True,
@@ -3403,16 +3407,19 @@ def DeclareProto():
         """
         JS::Handle<JSObject*> canonicalProto = GetProtoObjectHandle(aCx, global);
         if (!canonicalProto) {
           return false;
         }
         JS::Rooted<JSObject*> proto(aCx);
         if (aGivenProto) {
           proto = aGivenProto;
+          // Unfortunately, while aGivenProto was in the compartment of aCx
+          // coming in, we changed compartments to that of "parent" so may need
+          // to wrap the proto here.
           if (js::GetContextCompartment(aCx) != js::GetObjectCompartment(proto)) {
             if (!JS_WrapObject(aCx, &proto)) {
               return false;
             }
           }
         } else {
           proto = canonicalProto;
         }
@@ -3431,16 +3438,26 @@ class CGWrapWithCacheMethod(CGAbstractMe
                 Argument(descriptor.nativeType + '*', 'aObject'),
                 Argument('nsWrapperCache*', 'aCache'),
                 Argument('JS::Handle<JSObject*>', 'aGivenProto'),
                 Argument('JS::MutableHandle<JSObject*>', 'aReflector')]
         CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'bool', args)
         self.properties = properties
 
     def definition_body(self):
+        if self.descriptor.proxy:
+            preserveWrapper = dedent(
+                """
+                // For DOM proxies, the only reliable way to preserve the wrapper
+                // is to force creation of the expando object.
+                JS::Rooted<JSObject*> unused(aCx,
+                  DOMProxyHandler::EnsureExpandoObject(aCx, aReflector));
+                """)
+        else:
+            preserveWrapper = "PreserveWrapper(aObject);\n"
         return fill(
             """
             $*{assertInheritance}
             MOZ_ASSERT(!aCache->GetWrapper(),
                        "You should probably not be using Wrap() directly; use "
                        "GetOrCreateDOMReflector instead");
 
             MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
@@ -3450,38 +3467,53 @@ class CGWrapWithCacheMethod(CGAbstractMe
             if (!parent) {
               return false;
             }
 
             // That might have ended up wrapping us already, due to the wonders
             // of XBL.  Check for that, and bail out as needed.
             aReflector.set(aCache->GetWrapper());
             if (aReflector) {
-              MOZ_ASSERT(!aGivenProto,
-                         "How are we supposed to change the proto now?");
+            #ifdef DEBUG
+              binding_detail::AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto);
+            #endif // DEBUG
               return true;
             }
 
             JSAutoCompartment ac(aCx, parent);
             JS::Rooted<JSObject*> global(aCx, js::GetGlobalForObjectCrossCompartment(parent));
             $*{declareProto}
 
             $*{createObject}
 
             aCache->SetWrapper(aReflector);
             $*{unforgeable}
             $*{slots}
             creator.InitializationSucceeded();
+
+            MOZ_ASSERT(aCache->GetWrapperPreserveColor() &&
+                       aCache->GetWrapperPreserveColor() == aReflector);
+            // If proto != canonicalProto, we have to preserve our wrapper;
+            // otherwise we won't be able to properly recreate it later, since
+            // we won't know what proto to use.  Note that we don't check
+            // aGivenProto here, since it's entirely possible (and even
+            // somewhat common) to have a non-null aGivenProto which is the
+            // same as canonicalProto.
+            if (proto != canonicalProto) {
+              $*{preserveWrapper}
+            }
+
             return true;
             """,
             assertInheritance=AssertInheritanceChain(self.descriptor),
             declareProto=DeclareProto(),
             createObject=CreateBindingJSObject(self.descriptor, self.properties),
             unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor, True),
-            slots=InitMemberSlots(self.descriptor, True))
+            slots=InitMemberSlots(self.descriptor, True),
+            preserveWrapper=preserveWrapper)
 
 
 class CGWrapMethod(CGAbstractMethod):
     def __init__(self, descriptor):
         # XXX can we wrap if we don't have an interface prototype object?
         assert descriptor.interface.hasInterfacePrototypeObject()
         args = [Argument('JSContext*', 'aCx'),
                 Argument('T*', 'aObject'),
@@ -4892,36 +4924,44 @@ def getJSToNativeConversionInfo(type, de
             conversion = indent(CGCallbackTempRoot(name).define())
 
             template = wrapObjectTemplate(conversion, type,
                                           "${declName} = nullptr;\n",
                                           failureCode)
             return JSToNativeConversionInfo(template, declType=declType,
                                             dealWithOptional=isOptional)
 
+        if descriptor.interface.identifier.name == "AbortablePromise":
+            raise TypeError("Need to figure out what argument conversion "
+                            "should look like for AbortablePromise: %s" %
+                            sourceDescription)
+
         # This is an interface that we implement as a concrete class
         # or an XPCOM interface.
 
         # Allow null pointers for nullable types and old-binding classes, and
         # use an nsRefPtr or raw pointer for callback return values to make
         # them easier to return.
         argIsPointer = (type.nullable() or type.unroll().inner.isExternal() or
                         isCallbackReturnValue)
 
-        # Sequences and non-worker callbacks have to hold a strong ref to the
-        # thing being passed down.  Union return values must hold a strong ref
-        # because they may be returning an addrefed pointer.
-        # Also, callback return values always end up
-        # addrefing anyway, so there is no point trying to avoid it here and it
-        # makes other things simpler since we can assume the return value is a
-        # strong ref.
-        forceOwningType = ((descriptor.interface.isCallback() and
-                            not descriptor.workers) or
-                           isMember or
-                           isCallbackReturnValue)
+        # Sequence and dictionary members, as well as owning unions (which can
+        # appear here as return values in JS-implemented interfaces) have to
+        # hold a strong ref to the thing being passed down.  Those all set
+        # isMember.
+        #
+        # Also, callback return values always end up addrefing anyway, so there
+        # is no point trying to avoid it here and it makes other things simpler
+        # since we can assume the return value is a strong ref.
+        #
+        # Finally, promises need to hold a strong ref because that's what
+        # Promise.resolve returns.
+        assert not descriptor.interface.isCallback()
+        isPromise = descriptor.interface.identifier.name == "Promise"
+        forceOwningType = isMember or isCallbackReturnValue or isPromise
 
         typeName = descriptor.nativeType
         typePtr = typeName + "*"
 
         # Compute a few things:
         #  - declType is the type we want to return as the first element of our
         #    tuple.
         #  - holderType is the type we want to return as the third element
@@ -4938,17 +4978,39 @@ def getJSToNativeConversionInfo(type, de
             if forceOwningType:
                 declType = "OwningNonNull<" + typeName + ">"
             else:
                 declType = "NonNull<" + typeName + ">"
 
         templateBody = ""
         if forceOwningType:
             templateBody += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName
-        if not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal():
+
+        if isPromise:
+            templateBody = fill(
+                """
+                { // Scope for our GlobalObject and ErrorResult
+
+                  // Might as well use CurrentGlobalOrNull here; that will at
+                  // least give us the same behavior as if the caller just called
+                  // Promise.resolve() themselves.
+                  GlobalObject promiseGlobal(cx, JS::CurrentGlobalOrNull(cx));
+                  if (promiseGlobal.Failed()) {
+                    $*{exceptionCode}
+                  }
+                  ErrorResult promiseRv;
+                  $${declName} = Promise::Resolve(promiseGlobal, $${val}, promiseRv);
+                  if (promiseRv.Failed()) {
+                    ThrowMethodFailed(cx, promiseRv);
+                    $*{exceptionCode}
+                  }
+                }
+                """,
+                exceptionCode=exceptionCode)
+        elif not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal():
             if failureCode is not None:
                 templateBody += str(CastableObjectUnwrapper(
                     descriptor,
                     "&${val}.toObject()",
                     "${declName}",
                     failureCode))
             else:
                 templateBody += str(FailureFatalCastableObjectUnwrapper(
@@ -4979,21 +5041,34 @@ def getJSToNativeConversionInfo(type, de
             templateBody += CGIndenter(onFailureBadType(failureCode,
                                                         descriptor.interface.identifier.name)).define()
             templateBody += ("}\n"
                              "MOZ_ASSERT(${holderName});\n")
 
             # And store our value in ${declName}
             templateBody += "${declName} = ${holderName};\n"
 
-        # Just pass failureCode, not onFailureBadType, here, so we'll report the
-        # thing as not an object as opposed to not implementing whatever our
-        # interface is.
-        templateBody = wrapObjectTemplate(templateBody, type,
-                                          "${declName} = nullptr;\n", failureCode)
+        if isPromise:
+            if type.nullable():
+                codeToSetNull = "${declName} = nullptr;\n"
+                templateBody = CGIfElseWrapper(
+                    "${val}.isNullOrUndefined()",
+                    CGGeneric(codeToSetNull),
+                    CGGeneric(templateBody)).define()
+                if isinstance(defaultValue, IDLNullValue):
+                    templateBody = handleDefault(templateBody, codeToSetNull)
+            else:
+                assert defaultValue is None
+        else:
+            # Just pass failureCode, not onFailureBadType, here, so we'll report
+            # the thing as not an object as opposed to not implementing whatever
+            # our interface is.
+            templateBody = wrapObjectTemplate(templateBody, type,
+                                              "${declName} = nullptr;\n",
+                                              failureCode)
 
         declType = CGGeneric(declType)
         if holderType is not None:
             holderType = CGGeneric(holderType)
         return JSToNativeConversionInfo(templateBody,
                                         declType=declType,
                                         holderType=holderType,
                                         dealWithOptional=isOptional)
@@ -5740,17 +5815,18 @@ def getMaybeWrapValueFuncForType(type):
     return "MaybeWrapValue"
 
 
 sequenceWrapLevel = 0
 mozMapWrapLevel = 0
 
 
 def getWrapTemplateForType(type, descriptorProvider, result, successCode,
-                           returnsNewObject, exceptionCode, typedArraysAreStructs):
+                           returnsNewObject, exceptionCode, typedArraysAreStructs,
+                           isConstructorRetval=False):
     """
     Reflect a C++ value stored in "result", of IDL type "type" into JS.  The
     "successCode" is the code to run once we have successfully done the
     conversion and must guarantee that execution of the conversion template
     stops once the successCode has executed (e.g. by doing a 'return', or by
     doing a 'break' if the entire conversion template is inside a block that
     the 'break' will exit).
 
@@ -5985,21 +6061,25 @@ def getWrapTemplateForType(type, descrip
                             indent(setNull()) +
                             "}\n")
         else:
             wrappingCode = ""
 
         if not descriptor.interface.isExternal() and not descriptor.skipGen:
             if descriptor.wrapperCache:
                 wrapMethod = "GetOrCreateDOMReflector"
+                wrapArgs = "cx, %s, ${jsvalHandle}" % result
             else:
                 if not returnsNewObject:
                     raise MethodNotNewObjectError(descriptor.interface.identifier.name)
                 wrapMethod = "WrapNewBindingNonWrapperCachedObject"
-            wrap = "%s(cx, ${obj}, %s, ${jsvalHandle})" % (wrapMethod, result)
+                wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
+            if isConstructorRetval:
+                wrapArgs += ", desiredProto"
+            wrap = "%s(%s)" % (wrapMethod, wrapArgs)
             if not descriptor.hasXPConnectImpls:
                 # Can only fail to wrap as a new-binding object
                 # if they already threw an exception.
                 #XXX Assertion disabled for now, see bug 991271.
                 failed = ("MOZ_ASSERT(true || JS_IsExceptionPending(cx));\n" +
                           exceptionCode)
             else:
                 if descriptor.notflattened:
@@ -6184,25 +6264,27 @@ def wrapForType(type, descriptorProvider
                                   executed (e.g. by doing a 'return' or 'break'
                                   as appropriate).
       * 'returnsNewObject' (optional): If true, we're wrapping for the return
                                        value of a [NewObject] method.  Assumed
                                        false if not set.
       * 'exceptionCode' (optional): Code to run when a JS exception is thrown.
                                     The default is "return false;".  The code
                                     passed here must return.
-    """
-    wrap = getWrapTemplateForType(type, descriptorProvider,
-                                  templateValues.get('result', 'result'),
-                                  templateValues.get('successCode', None),
-                                  templateValues.get('returnsNewObject', False),
-                                  templateValues.get('exceptionCode',
-                                                     "return false;\n"),
-                                  templateValues.get('typedArraysAreStructs',
-                                                     False))[0]
+      * 'isConstructorRetval' (optional): If true, we're wrapping a constructor
+                                          return value.
+    """
+    wrap = getWrapTemplateForType(
+        type, descriptorProvider,
+        templateValues.get('result', 'result'),
+        templateValues.get('successCode', None),
+        templateValues.get('returnsNewObject', False),
+        templateValues.get('exceptionCode', "return false;\n"),
+        templateValues.get('typedArraysAreStructs', False),
+        isConstructorRetval=templateValues.get('isConstructorRetval', False))[0]
 
     defaultValues = {'obj': 'obj'}
     return string.Template(wrap).substitute(defaultValues, **templateValues)
 
 
 def infallibleForMember(member, type, descriptorProvider):
     """
     Determine the fallibility of changing a C++ value of IDL type "type" into
@@ -6736,16 +6818,17 @@ class CGPerSignatureCall(CGThing):
         self.returnType = returnType
         self.descriptor = descriptor
         self.idlNode = idlNode
         self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
                                                                    getter=getter,
                                                                    setter=setter)
         self.arguments = arguments
         self.argCount = len(arguments)
+        self.isConstructor = isConstructor
         cgThings = []
 
         # Here, we check if the current getter, setter, method, interface or
         # inherited interfaces have the UnsafeInPrerendering extended attribute
         # and if so, we add a check to make sure it is safe.
         if (idlNode.getExtendedAttribute("UnsafeInPrerendering") or
             descriptor.interface.getExtendedAttribute("UnsafeInPrerendering") or
             any(i.getExtendedAttribute("UnsafeInPrerendering")
@@ -6820,16 +6903,20 @@ class CGPerSignatureCall(CGThing):
             argsPre.append("cx")
 
         needsUnwrap = False
         argsPost = []
         if isConstructor:
             needsUnwrap = True
             needsUnwrappedVar = False
             unwrappedVar = "obj"
+            if descriptor.name == "Promise" or descriptor.name == "MozAbortablePromise":
+                # Hack for Promise for now: pass in our desired proto so the
+                # implementation can create the reflector with the right proto.
+                argsPost.append("desiredProto")
         elif descriptor.interface.isJSImplemented():
             if not idlNode.isStatic():
                 needsUnwrap = True
                 needsUnwrappedVar = True
                 argsPost.append("js::GetObjectCompartment(unwrappedObj ? *unwrappedObj : obj)")
         elif needScopeObject(returnType, arguments, self.extendedAttributes,
                              descriptor.wrapperCache, True,
                              idlNode.getExtendedAttribute("StoreInSlot")):
@@ -6900,16 +6987,22 @@ class CGPerSignatureCall(CGThing):
                 # already done the conversions from JS values to WebIDL (C++)
                 # values, so we only need to worry about cases where there are 'any'
                 # or 'object' types, or other things that we represent as actual
                 # JSAPI types, present.  Effectively, we're emulating a
                 # CrossCompartmentWrapper, but working with the C++ types, not the
                 # original list of JS::Values.
                 cgThings.append(CGGeneric("Maybe<JSAutoCompartment> ac;\n"))
                 xraySteps.append(CGGeneric("ac.emplace(cx, obj);\n"))
+                xraySteps.append(CGGeneric(dedent(
+                    """
+                    if (!JS_WrapObject(cx, &desiredProto)) {
+                      return false;
+                    }
+                    """)))
                 xraySteps.extend(
                     wrapArgIntoCurrentCompartment(arg, argname, isMember=False)
                     for arg, argname in self.getArguments())
 
             cgThings.append(
                 CGIfWrapper(CGList(xraySteps),
                             "objIsXray"))
 
@@ -6953,16 +7046,17 @@ class CGPerSignatureCall(CGThing):
             successCode = "break;\n"
         else:
             successCode = None
 
         resultTemplateValues = {
             'jsvalRef': 'args.rval()',
             'jsvalHandle': 'args.rval()',
             'returnsNewObject': returnsNewObject,
+            'isConstructorRetval': self.isConstructor,
             'successCode': successCode,
             'obj': "reflector" if setSlot else "obj"
         }
         try:
             wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
         except MethodNotNewObjectError, err:
             assert not returnsNewObject
             raise TypeError("%s being returned from non-NewObject method or property %s.%s" %
@@ -13130,17 +13224,18 @@ class CGNativeMember(ClassMethod):
         if type.isUnion():
             # unionTypeDecl will handle nullable types, so return False for
             # auto-wrapping in Nullable
             return CGUnionStruct.unionTypeDecl(type, isMember), True, False
 
         if type.isGeckoInterface() and not type.isCallbackInterface():
             iface = type.unroll().inner
             argIsPointer = type.nullable() or iface.isExternal()
-            forceOwningType = iface.isCallback() or isMember
+            forceOwningType = (iface.isCallback() or isMember or
+                               iface.identifier.name == "Promise")
             if argIsPointer:
                 if (optional or isMember) and forceOwningType:
                     typeDecl = "nsRefPtr<%s>"
                 else:
                     typeDecl = "%s*"
             else:
                 if optional or isMember:
                     if forceOwningType:
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -131,20 +131,20 @@ class Configuration:
         # a union type, so there can be multiple tuples with union types that
         # have the same name.
         self.unionsPerFilename = defaultdict(list)
 
         for (t, descriptor, _) in getAllTypes(self.descriptors, self.dictionaries, self.callbacks):
             while True:
                 if t.isMozMap():
                     t = t.inner
+                elif t.unroll() != t:
+                    t = t.unroll()
                 elif t.isPromise():
                     t = t.promiseInnerType()
-                elif t.unroll() != t:
-                    t = t.unroll()
                 else:
                     break
             if t.isUnion():
                 filenamesForUnion = self.filenamesPerUnion[t.name]
                 if t.filename() not in filenamesForUnion:
                     if len(filenamesForUnion) == 0:
                         # This is the first file that we found a union with this
                         # name in, record the union as part of the file.
--- a/dom/bindings/OwningNonNull.h
+++ b/dom/bindings/OwningNonNull.h
@@ -68,17 +68,17 @@ public:
   OwningNonNull<T>&
   operator=(T& aValue)
   {
     init(&aValue);
     return *this;
   }
 
   OwningNonNull<T>&
-  operator=(const already_AddRefed<T>& aValue)
+  operator=(already_AddRefed<T>&& aValue)
   {
     init(aValue);
     return *this;
   }
 
   // Don't allow assigning nullptr, it makes no sense
   void operator=(decltype(nullptr)) = delete;
 
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -2021,16 +2021,19 @@ class IDLNullableType(IDLType):
         return self.inner.isSharedTypedArray()
 
     def isDictionary(self):
         return self.inner.isDictionary()
 
     def isInterface(self):
         return self.inner.isInterface()
 
+    def isPromise(self):
+        return self.inner.isPromise()
+
     def isCallbackInterface(self):
         return self.inner.isCallbackInterface()
 
     def isNonCallbackInterface(self):
         return self.inner.isNonCallbackInterface()
 
     def isEnum(self):
         return self.inner.isEnum()
@@ -2153,21 +2156,24 @@ class IDLSequenceType(IDLType):
         self.inner = self.inner.complete(scope)
         self.name = self.inner.name + "Sequence"
         return self
 
     def unroll(self):
         return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         return (other.isPrimitive() or other.isString() or other.isEnum() or
-                other.isDate() or other.isInterface() or other.isDictionary() or
+                other.isDate() or other.isInterface() or
+                other.isDictionary() or
                 other.isCallback() or other.isMozMap())
 
     def _getDependentObjects(self):
         return self.inner._getDependentObjects()
 
 class IDLMozMapType(IDLType):
     # XXXbz This is pretty similar to IDLSequenceType in various ways.
     # And maybe to IDLNullableType.  Should we have a superclass for
@@ -2212,16 +2218,18 @@ class IDLMozMapType(IDLType):
 
     def unroll(self):
         # We do not unroll our inner.  Just stop at ourselves.  That
         # lets us add headers for both ourselves and our inner as
         # needed.
         return self
 
     def isDistinguishableFrom(self, other):
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isDate() or other.isNonCallbackInterface() or other.isSequence())
 
     def isExposedInAllOf(self, exposureSet):
         return self.inner.unroll().isExposedInAllOf(exposureSet)
@@ -2437,16 +2445,18 @@ class IDLArrayType(IDLType):
         assert not self.inner.isSequence()
 
         return self
 
     def unroll(self):
         return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isDate() or other.isNonCallbackInterface())
 
     def _getDependentObjects(self):
         return self.inner._getDependentObjects()
@@ -2671,16 +2681,20 @@ class IDLWrapperType(IDLType):
         elif self.isEnum():
             return IDLType.Tags.enum
         elif self.isDictionary():
             return IDLType.Tags.dictionary
         else:
             assert False
 
     def isDistinguishableFrom(self, other):
+        if self.isPromise():
+            return False
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         assert self.isInterface() or self.isEnum() or self.isDictionary()
         if self.isEnum():
             return (other.isPrimitive() or other.isInterface() or other.isObject() or
                     other.isCallback() or other.isDictionary() or
                     other.isSequence() or other.isMozMap() or other.isArray() or
@@ -2930,16 +2944,18 @@ class IDLBuiltinType(IDLType):
 
     def includesRestrictedFloat(self):
         return self.isFloat() and not self.isUnrestricted()
 
     def tag(self):
         return IDLBuiltinType.TagLookup[self._typeTag]
 
     def isDistinguishableFrom(self, other):
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         if self.isBoolean():
             return (other.isNumeric() or other.isString() or other.isEnum() or
                     other.isInterface() or other.isObject() or
                     other.isCallback() or other.isDictionary() or
                     other.isSequence() or other.isMozMap() or other.isArray() or
@@ -4187,16 +4203,18 @@ class IDLCallbackType(IDLType):
 
     def isCallback(self):
         return True
 
     def tag(self):
         return IDLType.Tags.callback
 
     def isDistinguishableFrom(self, other):
+        if other.isPromise():
+            return False
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isNonCallbackInterface() or other.isDate() or
                 other.isSequence())
 
     def _getDependentObjects(self):
--- a/dom/bindings/parser/tests/test_distinguishability.py
+++ b/dom/bindings/parser/tests/test_distinguishability.py
@@ -155,39 +155,41 @@ def WebIDLTest(parser, harness):
                  "Interface", "Interface?",
                  "AncestorInterface", "UnrelatedInterface",
                  "ImplementedInterface", "CallbackInterface",
                  "CallbackInterface?", "CallbackInterface2",
                  "object", "Callback", "Callback2", "optional Dict",
                  "optional Dict2", "sequence<long>", "sequence<short>",
                  "MozMap<object>", "MozMap<Dict>", "MozMap<long>",
                  "long[]", "short[]", "Date", "Date?", "any",
+                 "Promise<any>", "Promise<any>?",
                  "USVString", "ArrayBuffer", "ArrayBufferView", "SharedArrayBuffer", "SharedArrayBufferView",
                  "Uint8Array", "SharedUint8Array", "Uint16Array", "SharedUint16Array" ]
     # When we can parse Date and RegExp, we need to add them here.
 
     # Try to categorize things a bit to keep list lengths down
     def allBut(list1, list2):
-        return [a for a in list1 if a not in list2 and a != "any"]
+        return [a for a in list1 if a not in list2 and
+                (a != "any" and a != "Promise<any>" and a != "Promise<any>?")]
     numerics = [ "long", "short", "long?", "short?" ]
     booleans = [ "boolean", "boolean?" ]
     primitives = numerics + booleans
     nonNumerics = allBut(argTypes, numerics)
     nonBooleans = allBut(argTypes, booleans)
     strings = [ "DOMString", "ByteString", "Enum", "Enum2", "USVString" ]
     nonStrings = allBut(argTypes, strings)
     nonObjects = primitives + strings
     objects = allBut(argTypes, nonObjects )
     bufferSourceTypes = ["ArrayBuffer", "ArrayBufferView", "Uint8Array", "Uint16Array"]
     sharedBufferSourceTypes = ["SharedArrayBuffer", "SharedArrayBufferView", "SharedUint8Array", "SharedUint16Array"]
     interfaces = [ "Interface", "Interface?", "AncestorInterface",
                    "UnrelatedInterface", "ImplementedInterface" ] + bufferSourceTypes + sharedBufferSourceTypes
     nullables = ["long?", "short?", "boolean?", "Interface?",
                  "CallbackInterface?", "optional Dict", "optional Dict2",
-                 "Date?", "any"]
+                 "Date?", "any", "Promise<any>?"]
     dates = [ "Date", "Date?" ]
     sequences = [ "sequence<long>", "sequence<short>" ]
     arrays = [ "long[]", "short[]" ]
     nonUserObjects = nonObjects + interfaces + dates + sequences
     otherObjects = allBut(argTypes, nonUserObjects + ["object"])
     notRelatedInterfaces = (nonObjects + ["UnrelatedInterface"] +
                             otherObjects + dates + sequences + bufferSourceTypes + sharedBufferSourceTypes)
     mozMaps = [ "MozMap<object>", "MozMap<Dict>", "MozMap<long>" ]
@@ -233,16 +235,18 @@ def WebIDLTest(parser, harness):
     setDistinguishable("MozMap<object>", nonUserObjects)
     setDistinguishable("MozMap<Dict>", nonUserObjects)
     setDistinguishable("MozMap<long>", nonUserObjects)
     setDistinguishable("long[]", allBut(nonUserObjects, sequences))
     setDistinguishable("short[]", allBut(nonUserObjects, sequences))
     setDistinguishable("Date", allBut(argTypes, dates + ["object"]))
     setDistinguishable("Date?", allBut(argTypes, dates + nullables + ["object"]))
     setDistinguishable("any", [])
+    setDistinguishable("Promise<any>", [])
+    setDistinguishable("Promise<any>?", [])
     setDistinguishable("ArrayBuffer", allBut(argTypes, ["ArrayBuffer", "object"]))
     setDistinguishable("ArrayBufferView", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "Uint16Array", "object"]))
     setDistinguishable("Uint8Array", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "object"]))
     setDistinguishable("Uint16Array", allBut(argTypes, ["ArrayBufferView", "Uint16Array", "object"]))
     setDistinguishable("SharedArrayBuffer", allBut(argTypes, ["SharedArrayBuffer", "object"]))
     setDistinguishable("SharedArrayBufferView", allBut(argTypes, ["SharedArrayBufferView", "SharedUint8Array", "SharedUint16Array", "object"]))
     setDistinguishable("SharedUint8Array", allBut(argTypes, ["SharedArrayBufferView", "SharedUint8Array", "object"]))
     setDistinguishable("SharedUint16Array", allBut(argTypes, ["SharedArrayBufferView", "SharedUint16Array", "object"]))
@@ -260,16 +264,17 @@ def WebIDLTest(parser, harness):
           interface ImplementedInterface {};
           Interface implements ImplementedInterface;
           callback interface CallbackInterface {};
           callback interface CallbackInterface2 {};
           callback Callback = any();
           callback Callback2 = long(short arg);
           dictionary Dict {};
           dictionary Dict2 {};
+          interface _Promise {};
           interface TestInterface {%s
           };
         """
         methodTemplate = """
             void myMethod(%s arg);"""
         methods = (methodTemplate % type1) + (methodTemplate % type2)
         idl = idlTemplate % methods
         parser = parser.reset()
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -17,16 +17,17 @@
 #include "nsGenericHTMLElement.h"
 #include "nsWrapperCache.h"
 
 // Forward declare this before we include TestCodeGenBinding.h, because that header relies on including
 // this one for it, for ParentDict. Hopefully it won't begin to rely on it in more fundamental ways.
 namespace mozilla {
 namespace dom {
 class TestExternalInterface;
+class Promise;
 } // namespace dom
 } // namespace mozilla
 
 // We don't export TestCodeGenBinding.h, but it's right in our parent dir.
 #include "../TestCodeGenBinding.h"
 
 extern bool TestFuncControlledMember(JSContext*, JSObject*);
 
@@ -702,16 +703,28 @@ public:
   void PassOptionalNullableDate(const Optional<Nullable<Date> >&);
   void PassOptionalNullableDateWithDefaultValue(const Nullable<Date>&);
   void PassDateSequence(const Sequence<Date>&);
   void PassDateMozMap(const MozMap<Date>&);
   void PassNullableDateSequence(const Sequence<Nullable<Date> >&);
   Date ReceiveDate();
   Nullable<Date> ReceiveNullableDate();
 
+  // Promise types
+  void PassPromise(Promise&);
+  void PassNullablePromise(Promise*);
+  void PassOptionalPromise(const Optional<OwningNonNull<Promise>>&);
+  void PassOptionalNullablePromise(const Optional<nsRefPtr<Promise>>&);
+  void PassOptionalNullablePromiseWithDefaultValue(Promise*);
+  void PassPromiseSequence(const Sequence<OwningNonNull<Promise>>&);
+  void PassPromiseMozMap(const MozMap<nsRefPtr<Promise>>&);
+  void PassNullablePromiseSequence(const Sequence<nsRefPtr<Promise>> &);
+  Promise* ReceivePromise();
+  already_AddRefed<Promise> ReceiveAddrefedPromise();
+
   // binaryNames tests
   void MethodRenamedTo();
   void OtherMethodRenamedTo();
   void MethodRenamedTo(int8_t);
   int8_t AttributeGetterRenamedTo();
   int8_t AttributeRenamedTo();
   void SetAttributeRenamedTo(int8_t);
   int8_t OtherAttributeRenamedTo();
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -672,16 +672,27 @@ interface TestInterface {
   void passOptionalNullableDate(optional Date? arg);
   void passOptionalNullableDateWithDefaultValue(optional Date? arg = null);
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
+  // Promise types
+  void passPromise(Promise<any> arg);
+  void passNullablePromise(Promise<any>? arg);
+  void passOptionalPromise(optional Promise<any> arg);
+  void passOptionalNullablePromise(optional Promise<any>? arg);
+  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
+  void passPromiseSequence(sequence<Promise<any>> arg);
+  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
+  Promise<any> receivePromise();
+  Promise<any> receiveAddrefedPromise();
+
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
   readonly attribute byte attributeGetterRenamedFrom;
   attribute byte attributeRenamedFrom;
   [BinaryName="otherAttributeRenamedTo"]
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -536,16 +536,27 @@ interface TestExampleInterface {
   void passOptionalNullableDate(optional Date? arg);
   void passOptionalNullableDateWithDefaultValue(optional Date? arg = null);
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
+  // Promise types
+  void passPromise(Promise<any> arg);
+  void passNullablePromise(Promise<any>? arg);
+  void passOptionalPromise(optional Promise<any> arg);
+  void passOptionalNullablePromise(optional Promise<any>? arg);
+  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
+  void passPromiseSequence(sequence<Promise<any>> arg);
+  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
+  Promise<any> receivePromise();
+  Promise<any> receiveAddrefedPromise();
+
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
   readonly attribute byte attributeGetterRenamedFrom;
   attribute byte attributeRenamedFrom;
   [BinaryName="otherAttributeRenamedTo"]
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -548,16 +548,27 @@ interface TestJSImplInterface {
   void passOptionalNullableDate(optional Date? arg);
   void passOptionalNullableDateWithDefaultValue(optional Date? arg = null);
   void passDateSequence(sequence<Date> arg);
   void passNullableDateSequence(sequence<Date?> arg);
   void passDateMozMap(MozMap<Date> arg);
   Date receiveDate();
   Date? receiveNullableDate();
 
+  // Promise types
+  void passPromise(Promise<any> arg);
+  void passNullablePromise(Promise<any>? arg);
+  void passOptionalPromise(optional Promise<any> arg);
+  void passOptionalNullablePromise(optional Promise<any>? arg);
+  void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null);
+  void passPromiseSequence(sequence<Promise<any>> arg);
+  void passNullablePromiseSequence(sequence<Promise<any>?> arg);
+  Promise<any> receivePromise();
+  Promise<any> receiveAddrefedPromise();
+
   // binaryNames tests
   void methodRenamedFrom();
   [BinaryName="otherMethodRenamedTo"]
   void otherMethodRenamedFrom();
   void methodRenamedFrom(byte argument);
   readonly attribute byte attributeGetterRenamedFrom;
   attribute byte attributeRenamedFrom;
   [BinaryName="otherAttributeRenamedTo"]
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -217,16 +217,21 @@ BrowserElementChild.prototype = {
       "get-screenshot": this._recvGetScreenshot,
       "get-contentdimensions": this._recvGetContentDimensions,
       "set-visible": this._recvSetVisible,
       "get-visible": this._recvVisible,
       "send-mouse-event": this._recvSendMouseEvent,
       "send-touch-event": this._recvSendTouchEvent,
       "get-can-go-back": this._recvCanGoBack,
       "get-can-go-forward": this._recvCanGoForward,
+      "mute": this._recvMute.bind(this),
+      "unmute": this._recvUnmute.bind(this),
+      "get-muted": this._recvGetMuted.bind(this),
+      "set-volume": this._recvSetVolume.bind(this),
+      "get-volume": this._recvGetVolume.bind(this),
       "go-back": this._recvGoBack,
       "go-forward": this._recvGoForward,
       "reload": this._recvReload,
       "stop": this._recvStop,
       "zoom": this._recvZoom,
       "unblock-modal-prompt": this._recvStopWaiting,
       "fire-ctx-callback": this._recvFireCtxCallback,
       "owner-visibility-change": this._recvOwnerVisibilityChange,
@@ -1295,16 +1300,42 @@ BrowserElementChild.prototype = {
   _recvCanGoForward: function(data) {
     var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     sendAsyncMsg('got-can-go-forward', {
       id: data.json.id,
       successRv: webNav.canGoForward
     });
   },
 
+  _recvMute: function(data) {
+    this._windowUtils.audioMuted = true;
+  },
+
+  _recvUnmute: function(data) {
+    this._windowUtils.audioMuted = false;
+  },
+
+  _recvGetMuted: function(data) {
+    sendAsyncMsg('got-muted', {
+      id: data.json.id,
+      successRv: this._windowUtils.audioMuted
+    });
+  },
+
+  _recvSetVolume: function(data) {
+    this._windowUtils.audioVolume = data.json.volume;
+  },
+
+  _recvGetVolume: function(data) {
+    sendAsyncMsg('got-volume', {
+      id: data.json.id,
+      successRv: this._windowUtils.audioVolume
+    });
+  },
+
   _recvGoBack: function(data) {
     try {
       docShell.QueryInterface(Ci.nsIWebNavigation).goBack();
     } catch(e) {
       // Silently swallow errors; these happen when we can't go back.
     }
   },
 
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -179,16 +179,18 @@ BrowserElementParent.prototype = {
       "firstpaint": this._fireProfiledEventFromMsg,
       "documentfirstpaint": this._fireProfiledEventFromMsg,
       "nextpaint": this._recvNextPaint,
       "got-purge-history": this._gotDOMRequestResult,
       "got-screenshot": this._gotDOMRequestResult,
       "got-contentdimensions": this._gotDOMRequestResult,
       "got-can-go-back": this._gotDOMRequestResult,
       "got-can-go-forward": this._gotDOMRequestResult,
+      "got-muted": this._gotDOMRequestResult,
+      "got-volume": this._gotDOMRequestResult,
       "requested-dom-fullscreen": this._requestedDOMFullscreen,
       "fullscreen-origin-change": this._fullscreenOriginChange,
       "exit-dom-fullscreen": this._exitDomFullscreen,
       "got-visible": this._gotDOMRequestResult,
       "visibilitychange": this._childVisibilityChange,
       "got-set-input-method-active": this._gotDOMRequestResult,
       "selectionstatechanged": this._handleSelectionStateChanged,
       "scrollviewchange": this._handleScrollViewChange,
@@ -659,16 +661,32 @@ BrowserElementParent.prototype = {
       backward: direction == Ci.nsIBrowserElementAPI.FIND_BACKWARD
     });
   }),
 
   clearMatch: defineNoReturnMethod(function() {
     return this._sendAsyncMsg('clear-match');
   }),
 
+  mute: defineNoReturnMethod(function() {
+    this._sendAsyncMsg('mute');
+  }),
+
+  unmute: defineNoReturnMethod(function() {
+    this._sendAsyncMsg('unmute');
+  }),
+
+  getMuted: defineDOMRequestMethod('get-muted'),
+
+  getVolume: defineDOMRequestMethod('get-volume'),
+
+  setVolume: defineNoReturnMethod(function(volume) {
+    this._sendAsyncMsg('set-volume', {volume});
+  }),
+
   goBack: defineNoReturnMethod(function() {
     this._sendAsyncMsg('go-back');
   }),
 
   goForward: defineNoReturnMethod(function() {
     this._sendAsyncMsg('go-forward');
   }),
 
--- a/dom/browser-element/mochitest/browserElement_AudioChannel.js
+++ b/dom/browser-element/mochitest/browserElement_AudioChannel.js
@@ -13,16 +13,22 @@ SpecialPowers.setBoolPref("media.useAudi
 
 function noaudio() {
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
   iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
   iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_empty.html';
 
   function noaudio_loadend() {
+    ok("mute" in iframe, "iframe.mute exists");
+    ok("unmute" in iframe, "iframe.unmute exists");
+    ok("getMuted" in iframe, "iframe.getMuted exists");
+    ok("getVolume" in iframe, "iframe.getVolume exists");
+    ok("setVolume" in iframe, "iframe.setVolume exists");
+
     ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist");
     var channels = iframe.allowedAudioChannels;
     is(channels.length, 1, "1 audio channel by default");
 
     var ac = channels[0];
 
     ok(ac instanceof BrowserElementAudioChannel, "Correct class");
     ok("getVolume" in ac, "ac.getVolume exists");
@@ -36,16 +42,52 @@ function noaudio() {
       ok(req instanceof DOMRequest, "This is a domRequest.");
       req.onsuccess = function(e) {
         is(e.target.result, 1.0, "The default volume should be 1.0");
         r();
       }
     })
 
     .then(function() {
+      return new Promise(function(resolve) {
+        iframe.mute();
+        iframe.getMuted()
+          .then(result => is(result, true, "iframe.getMuted should be true."))
+          .then(resolve);
+      });
+    })
+
+    .then(function() {
+      return new Promise(function(resolve) {
+        iframe.unmute();
+        iframe.getMuted()
+          .then(result => is(result, false, "iframe.getMuted should be false."))
+          .then(resolve);
+      });
+    })
+
+    .then(function() {
+      return new Promise(function(resolve) {
+        iframe.setVolume(0);
+        iframe.getVolume()
+          .then(result => is(result, 0, "iframe.getVolume should be 0."))
+          .then(resolve);
+      });
+    })
+
+    .then(function() {
+      return new Promise(function(resolve) {
+        iframe.setVolume(1);
+        iframe.getVolume()
+          .then(result => is(result, 1, "iframe.getVolume should be 1."))
+          .then(resolve);
+      });
+    })
+
+    .then(function() {
       return new Promise(function(r, rr) {
         ac.getMuted().onsuccess = function(e) {
           is(e.target.result, false, "The default muted value should be false");
           r();
         }
       });
     })
 
@@ -104,16 +146,22 @@ function noaudio() {
 
 function audio() {
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
   iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
   iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/iframe_file_audio.html';
 
   function audio_loadend() {
+    ok("mute" in iframe, "iframe.mute exists");
+    ok("unmute" in iframe, "iframe.unmute exists");
+    ok("getMuted" in iframe, "iframe.getMuted exists");
+    ok("getVolume" in iframe, "iframe.getVolume exists");
+    ok("setVolume" in iframe, "iframe.setVolume exists");
+
     ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist");
     var channels = iframe.allowedAudioChannels;
     is(channels.length, 1, "1 audio channel by default");
 
     var ac = channels[0];
 
     ok(ac instanceof BrowserElementAudioChannel, "Correct class");
     ok("getVolume" in ac, "ac.getVolume exists");
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -21,17 +21,17 @@ interface nsIBrowserElementNextPaintList
     { 0x651db7e3, 0x1734, 0x4536,                               \
       { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
 %}
 
 /**
  * Interface to the BrowserElementParent implementation. All methods
  * but setFrameLoader throw when the remote process is dead.
  */
-[scriptable, uuid(26a832d1-9d71-43ef-9d46-9d7c8ec33f00)]
+[scriptable, uuid(56bd3e12-4a8b-422a-89fc-6dc25aa30aa2)]
 interface nsIBrowserElementAPI : nsISupports
 {
   const long FIND_CASE_SENSITIVE = 0;
   const long FIND_CASE_INSENSITIVE = 1;
 
   const long FIND_FORWARD = 0;
   const long FIND_BACKWARD = 1;
 
@@ -72,16 +72,23 @@ interface nsIBrowserElementAPI : nsISupp
   nsIDOMDOMRequest getCanGoBack();
   nsIDOMDOMRequest getCanGoForward();
   nsIDOMDOMRequest getContentDimensions();
 
   void findAll(in DOMString searchString, in long caseSensitivity);
   void findNext(in long direction);
   void clearMatch();
 
+  void mute();
+  void unmute();
+  nsIDOMDOMRequest getMuted();
+
+  void setVolume(in float volume);
+  nsIDOMDOMRequest getVolume();
+
   void addNextPaintListener(in nsIBrowserElementNextPaintListener listener);
   void removeNextPaintListener(in nsIBrowserElementNextPaintListener listener);
 
   nsIDOMDOMRequest setInputMethodActive(in boolean isActive);
 
   nsIDOMDOMRequest getAudioChannelVolume(in uint32_t audioChannel);
   nsIDOMDOMRequest setAudioChannelVolume(in uint32_t audioChannel, in float volume);
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -71,16 +71,17 @@
 #include "jsfriendapi.h"
 #include "js/Conversions.h"
 
 #include "mozilla/Alignment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/Endian.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
@@ -1983,17 +1984,17 @@ CanvasRenderingContext2D::CreateRadialGr
 
   nsRefPtr<CanvasGradient> grad =
     new CanvasRadialGradient(this, Point(x0, y0), r0, Point(x1, y1), r1);
 
   return grad.forget();
 }
 
 already_AddRefed<CanvasPattern>
-CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element,
+CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& source,
                                         const nsAString& repeat,
                                         ErrorResult& error)
 {
   CanvasPattern::RepeatMode repeatMode =
     CanvasPattern::RepeatMode::NOREPEAT;
 
   if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) {
     repeatMode = CanvasPattern::RepeatMode::REPEAT;
@@ -2004,18 +2005,18 @@ CanvasRenderingContext2D::CreatePattern(
   } else if (repeat.EqualsLiteral("no-repeat")) {
     repeatMode = CanvasPattern::RepeatMode::NOREPEAT;
   } else {
     error.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
   Element* htmlElement;
-  if (element.IsHTMLCanvasElement()) {
-    HTMLCanvasElement* canvas = &element.GetAsHTMLCanvasElement();
+  if (source.IsHTMLCanvasElement()) {
+    HTMLCanvasElement* canvas = &source.GetAsHTMLCanvasElement();
     htmlElement = canvas;
 
     nsIntSize size = canvas->GetSize();
     if (size.width == 0 || size.height == 0) {
       error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return nullptr;
     }
 
@@ -2025,26 +2026,39 @@ CanvasRenderingContext2D::CreatePattern(
       // This might not be an Azure canvas!
       RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot();
 
       nsRefPtr<CanvasPattern> pat =
         new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false);
 
       return pat.forget();
     }
-  } else if (element.IsHTMLImageElement()) {
-    HTMLImageElement* img = &element.GetAsHTMLImageElement();
+  } else if (source.IsHTMLImageElement()) {
+    HTMLImageElement* img = &source.GetAsHTMLImageElement();
     if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) {
       error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return nullptr;
     }
 
     htmlElement = img;
+  } else if (source.IsHTMLVideoElement()) {
+    htmlElement = &source.GetAsHTMLVideoElement();
   } else {
-    htmlElement = &element.GetAsHTMLVideoElement();
+    // Special case for ImageBitmap
+    ImageBitmap& imgBitmap = source.GetAsImageBitmap();
+    EnsureTarget();
+    RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget);
+
+    // An ImageBitmap never taints others so we set principalForSecurityCheck to
+    // nullptr and set CORSUsed to true for passing the security check in
+    // CanvasUtils::DoDrawImageSecurityCheck().
+    nsRefPtr<CanvasPattern> pat =
+      new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true);
+
+    return pat.forget();
   }
 
   EnsureTarget();
 
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(htmlElement,
@@ -4233,17 +4247,17 @@ CanvasRenderingContext2D::CachedSurfaceF
 //   -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
 
 // If only dx and dy are passed in then optional_argc should be 0. If only
 // dx, dy, dw and dh are passed in then optional_argc should be 2. The only
 // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh
 // are all passed in.
 
 void
-CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
+CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image,
                                     double sx, double sy, double sw,
                                     double sh, double dx, double dy,
                                     double dw, double dh,
                                     uint8_t optional_argc,
                                     ErrorResult& error)
 {
   if (mDrawObserver) {
     mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
@@ -4260,17 +4274,27 @@ CanvasRenderingContext2D::DrawImage(cons
   if (image.IsHTMLCanvasElement()) {
     HTMLCanvasElement* canvas = &image.GetAsHTMLCanvasElement();
     element = canvas;
     nsIntSize size = canvas->GetSize();
     if (size.width == 0 || size.height == 0) {
       error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
-  } else {
+  } else if (image.IsImageBitmap()) {
+    ImageBitmap& imageBitmap = image.GetAsImageBitmap();
+    srcSurf = imageBitmap.PrepareForDrawTarget(mTarget);
+
+    if (!srcSurf) {
+      return;
+    }
+
+    imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height());
+  }
+  else {
     if (image.IsHTMLImageElement()) {
       HTMLImageElement* img = &image.GetAsHTMLImageElement();
       element = img;
     } else {
       HTMLVideoElement* video = &image.GetAsHTMLVideoElement();
       element = video;
     }
 
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -32,17 +32,18 @@ class nsGlobalWindow;
 class nsXULElement;
 
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 } // namespace gl
 
 namespace dom {
-class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement;
+class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap;
+typedef HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource;
 class ImageData;
 class StringOrCanvasGradientOrCanvasPattern;
 class OwningStringOrCanvasGradientOrCanvasPattern;
 class TextMetrics;
 class CanvasFilterChainObserver;
 class CanvasPath;
 
 extern const mozilla::gfx::Float SIGMA_MAX;
@@ -55,19 +56,16 @@ class CanvasDrawObserver;
 
 /**
  ** CanvasRenderingContext2D
  **/
 class CanvasRenderingContext2D final :
   public nsICanvasRenderingContextInternal,
   public nsWrapperCache
 {
-typedef HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement
-  HTMLImageOrCanvasOrVideoElement;
-
   virtual ~CanvasRenderingContext2D();
 
 public:
   CanvasRenderingContext2D();
 
   virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
 
   HTMLCanvasElement* GetCanvas() const
@@ -127,17 +125,17 @@ public:
   }
 
   already_AddRefed<CanvasGradient>
     CreateLinearGradient(double x0, double y0, double x1, double y1);
   already_AddRefed<CanvasGradient>
     CreateRadialGradient(double x0, double y0, double r0, double x1, double y1,
                          double r1, ErrorResult& aError);
   already_AddRefed<CanvasPattern>
-    CreatePattern(const HTMLImageOrCanvasOrVideoElement& element,
+    CreatePattern(const CanvasImageSource& element,
                   const nsAString& repeat, ErrorResult& error);
 
   double ShadowOffsetX()
   {
     return CurrentState().shadowOffset.x;
   }
 
   void SetShadowOffsetX(double shadowOffsetX)
@@ -203,30 +201,30 @@ public:
                   mozilla::ErrorResult& error);
   TextMetrics*
     MeasureText(const nsAString& rawText, mozilla::ErrorResult& error);
 
   void AddHitRegion(const HitRegionOptions& options, mozilla::ErrorResult& error);
   void RemoveHitRegion(const nsAString& id);
   void ClearHitRegions();
 
-  void DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
+  void DrawImage(const CanvasImageSource& image,
                  double dx, double dy, mozilla::ErrorResult& error)
   {
     DrawImage(image, 0.0, 0.0, 0.0, 0.0, dx, dy, 0.0, 0.0, 0, error);
   }
 
-  void DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
+  void DrawImage(const CanvasImageSource& image,
                  double dx, double dy, double dw, double dh,
                  mozilla::ErrorResult& error)
   {
     DrawImage(image, 0.0, 0.0, 0.0, 0.0, dx, dy, dw, dh, 2, error);
   }
 
-  void DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
+  void DrawImage(const CanvasImageSource& image,
                  double sx, double sy, double sw, double sh, double dx,
                  double dy, double dw, double dh, mozilla::ErrorResult& error)
   {
     DrawImage(image, sx, sy, sw, sh, dx, dy, dw, dh, 6, error);
   }
 
   already_AddRefed<ImageData>
     CreateImageData(JSContext* cx, double sw, double sh,
@@ -661,17 +659,17 @@ protected:
    * Update CurrentState().filter with the filter description for
    * CurrentState().filterChain.
    */
   void UpdateFilter();
 
   nsLayoutUtils::SurfaceFromElementResult
     CachedSurfaceFromElement(Element* aElement);
 
-  void DrawImage(const HTMLImageOrCanvasOrVideoElement &imgElt,
+  void DrawImage(const CanvasImageSource &imgElt,
                  double sx, double sy, double sw, double sh,
                  double dx, double dy, double dw, double dh,
                  uint8_t optional_argc, mozilla::ErrorResult& error);
 
   void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& image,
                             mozilla::gfx::Rect* bounds,
                             mozilla::gfx::Rect dest,
                             mozilla::gfx::Rect src,
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -24,24 +24,29 @@
 #include "CanvasUtils.h"
 #include "mozilla/gfx/Matrix.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace CanvasUtils {
 
+/**
+ * This security check utility might be called from an source that never taints
+ * others. For example, while painting a CanvasPattern, which is created from an
+ * ImageBitmap, onto a canvas. In this case, the caller could set the CORSUsed
+ * true in order to pass this check and leave the aPrincipal to be a nullptr
+ * since the aPrincipal is not going to be used.
+ */
 void
 DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
                          nsIPrincipal *aPrincipal,
                          bool forceWriteOnly,
                          bool CORSUsed)
 {
-    NS_PRECONDITION(aPrincipal, "Must have a principal here");
-
     // Callers should ensure that mCanvasElement is non-null before calling this
     if (!aCanvasElement) {
         NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
         return;
     }
 
     if (aCanvasElement->IsWriteOnly())
         return;
@@ -51,16 +56,18 @@ DoDrawImageSecurityCheck(dom::HTMLCanvas
         aCanvasElement->SetWriteOnly();
         return;
     }
 
     // No need to do a security check if the image used CORS for the load
     if (CORSUsed)
         return;
 
+    NS_PRECONDITION(aPrincipal, "Must have a principal here");
+
     if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) {
         // This canvas has access to that image anyway
         return;
     }
 
     aCanvasElement->SetWriteOnly();
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/canvas/ImageBitmap.cpp
@@ -0,0 +1,1174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/gfx/2D.h"
+#include "imgTools.h"
+#include "js/StructuredClone.h"
+#include "libyuv.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/*
+ * This helper function copies the data of the given DataSourceSurface,
+ *  _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface.
+ * This might return null if it can not create a new SourceSurface or it cannot
+ * read data from the given _aSurface_.
+ */
+static already_AddRefed<DataSourceSurface>
+CropDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect)
+{
+  MOZ_ASSERT(aSurface);
+
+  // Calculate the size of the new SourceSurface.
+  const SurfaceFormat format = aSurface->GetFormat();
+  const IntSize  dstSize = gfx::IntSize(aCropRect.width, aCropRect.height);
+  const uint32_t dstStride = dstSize.width * BytesPerPixel(format);
+
+  // Create a new SourceSurface.
+  RefPtr<DataSourceSurface> dstDataSurface =
+    Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride);
+
+  if (NS_WARN_IF(!dstDataSurface)) {
+    return nullptr;
+  }
+
+  // Copy the raw data into the newly created DataSourceSurface.
+  RefPtr<DataSourceSurface> srcDataSurface = aSurface;
+  DataSourceSurface::MappedSurface srcMap;
+  DataSourceSurface::MappedSurface dstMap;
+  if (NS_WARN_IF(!srcDataSurface->Map(DataSourceSurface::MapType::READ, &srcMap)) ||
+      NS_WARN_IF(!dstDataSurface->Map(DataSourceSurface::MapType::WRITE, &dstMap))) {
+    return nullptr;
+  }
+
+  uint8_t* srcBufferPtr = srcMap.mData + aCropRect.y * srcMap.mStride
+                                       + aCropRect.x * BytesPerPixel(format);
+  uint8_t* dstBufferPtr = dstMap.mData;
+  for (int i = 0; i < dstSize.height; ++i) {
+    memcpy(dstBufferPtr, srcBufferPtr, dstMap.mStride);
+    srcBufferPtr += srcMap.mStride;
+    dstBufferPtr += dstMap.mStride;
+  }
+
+  srcDataSurface->Unmap();
+  dstDataSurface->Unmap();
+
+  return dstDataSurface.forget();
+}
+
+/*
+ * Encapsulate the given _aSurface_ into a layers::CairoImage.
+ */
+static already_AddRefed<layers::Image>
+CreateImageFromSurface(SourceSurface* aSurface, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aSurface);
+
+  layers::CairoImage::Data cairoData;
+  cairoData.mSize = aSurface->GetSize();
+  cairoData.mSourceSurface = aSurface;
+
+  nsRefPtr<layers::CairoImage> image = new layers::CairoImage();
+
+  image->SetData(cairoData);
+
+  return image.forget();
+}
+
+/*
+ * CreateImageFromRawData(), CreateSurfaceFromRawData() and
+ * CreateImageFromRawDataInMainThreadSyncTask are helpers for
+ * create-from-ImageData case
+ */
+static already_AddRefed<SourceSurface>
+CreateSurfaceFromRawData(const gfx::IntSize& aSize,
+                         uint32_t aStride,
+                         gfx::SurfaceFormat aFormat,
+                         uint8_t* aBuffer,
+                         uint32_t aBufferLength,
+                         const Maybe<IntRect>& aCropRect,
+                         ErrorResult& aRv)
+{
+  MOZ_ASSERT(!aSize.IsEmpty());
+  MOZ_ASSERT(aBuffer);
+
+  // Wrap the source buffer into a SourceSurface.
+  RefPtr<DataSourceSurface> dataSurface =
+    Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat);
+
+  if (NS_WARN_IF(!dataSurface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  // The temporary cropRect variable is equal to the size of source buffer if we
+  // do not need to crop, or it equals to the given cropping size.
+  const IntRect cropRect = aCropRect.valueOr(IntRect(0, 0, aSize.width, aSize.height));
+
+  // Copy the source buffer in the _cropRect_ area into a new SourceSurface.
+  RefPtr<DataSourceSurface> result = CropDataSourceSurface(dataSurface, cropRect);
+
+  if (NS_WARN_IF(!result)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  return result.forget();
+}
+
+static already_AddRefed<layers::Image>
+CreateImageFromRawData(const gfx::IntSize& aSize,
+                       uint32_t aStride,
+                       gfx::SurfaceFormat aFormat,
+                       uint8_t* aBuffer,
+                       uint32_t aBufferLength,
+                       const Maybe<IntRect>& aCropRect,
+                       ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Copy and crop the source buffer into a SourceSurface.
+  RefPtr<SourceSurface> rgbaSurface =
+    CreateSurfaceFromRawData(aSize, aStride, aFormat,
+                             aBuffer, aBufferLength,
+                             aCropRect, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Convert RGBA to BGRA
+  RefPtr<DataSourceSurface> rgbaDataSurface = rgbaSurface->GetDataSurface();
+  RefPtr<DataSourceSurface> bgraDataSurface =
+    Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(),
+                                               SurfaceFormat::B8G8R8A8,
+                                               rgbaDataSurface->Stride());
+
+  DataSourceSurface::MappedSurface rgbaMap;
+  DataSourceSurface::MappedSurface bgraMap;
+
+  if (NS_WARN_IF(!rgbaDataSurface->Map(DataSourceSurface::MapType::READ, &rgbaMap)) ||
+      NS_WARN_IF(!bgraDataSurface->Map(DataSourceSurface::MapType::WRITE, &bgraMap))) {
+    return nullptr;
+  }
+
+  libyuv::ABGRToARGB(rgbaMap.mData, rgbaMap.mStride,
+                     bgraMap.mData, bgraMap.mStride,
+                     bgraDataSurface->GetSize().width,
+                     bgraDataSurface->GetSize().height);
+
+  rgbaDataSurface->Unmap();
+  bgraDataSurface->Unmap();
+
+  // Create an Image from the BGRA SourceSurface.
+  nsRefPtr<layers::Image> image = CreateImageFromSurface(bgraDataSurface, aRv);
+
+  return image.forget();
+}
+
+/*
+ * This is a synchronous task.
+ * This class is used to create a layers::CairoImage from raw data in the main
+ * thread. While creating an ImageBitmap from an ImageData, we need to create
+ * a SouceSurface from the ImageData's raw data and then set the SourceSurface
+ * into a layers::CairoImage. However, the layers::CairoImage asserts the
+ * setting operation in the main thread, so if we are going to create an
+ * ImageBitmap from an ImageData off the main thread, we post an event to the
+ * main thread to create a layers::CairoImage from an ImageData's raw data.
+ */
+class CreateImageFromRawDataInMainThreadSyncTask final :
+  public WorkerMainThreadRunnable
+{
+public:
+  CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer,
+                                             uint32_t aBufferLength,
+                                             uint32_t aStride,
+                                             gfx::SurfaceFormat aFormat,
+                                             const gfx::IntSize& aSize,
+                                             const Maybe<IntRect>& aCropRect,
+                                             ErrorResult& aError,
+                                             layers::Image** aImage)
+  : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate())
+  , mImage(aImage)
+  , mBuffer(aBuffer)
+  , mBufferLength(aBufferLength)
+  , mStride(aStride)
+  , mFormat(aFormat)
+  , mSize(aSize)
+  , mCropRect(aCropRect)
+  , mError(aError)
+  {
+  }
+
+  bool MainThreadRun() override
+  {
+    nsRefPtr<layers::Image> image =
+      CreateImageFromRawData(mSize, mStride, mFormat,
+                             mBuffer, mBufferLength,
+                             mCropRect,
+                             mError);
+
+    if (NS_WARN_IF(mError.Failed())) {
+      return false;
+    }
+
+    image.forget(mImage);
+
+    return true;
+  }
+
+private:
+  layers::Image** mImage;
+  uint8_t* mBuffer;
+  uint32_t mBufferLength;
+  uint32_t mStride;
+  gfx::SurfaceFormat mFormat;
+  gfx::IntSize mSize;
+  const Maybe<IntRect>& mCropRect;
+  ErrorResult& mError;
+};
+
+static bool
+CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed, nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(aPrincipal);
+
+  if (aIsWriteOnly) {
+    return false;
+  }
+
+  if (!aCORSUsed) {
+    nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal();
+    if (NS_WARN_IF(!incumbentSettingsObject)) {
+      return false;
+    }
+
+    nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
+    if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool
+CheckSecurityForHTMLElements(const nsLayoutUtils::SurfaceFromElementResult& aRes)
+{
+  return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed, aRes.mPrincipal);
+}
+
+/*
+ * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
+ * security checking.
+ */
+template<class HTMLElementType>
+static already_AddRefed<SourceSurface>
+GetSurfaceFromElement(nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv)
+{
+  nsLayoutUtils::SurfaceFromElementResult res =
+    nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME);
+
+  // check origin-clean
+  if (!CheckSecurityForHTMLElements(res)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(!res.mSourceSurface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> surface(res.mSourceSurface);
+  return surface.forget();
+}
+
+/*
+ * The specification doesn't allow to create an ImegeBitmap from a vector image.
+ * This function is used to check if the given HTMLImageElement contains a
+ * raster image.
+ */
+static bool
+HasRasterImage(HTMLImageElement& aImageEl)
+{
+  nsresult rv;
+
+  nsCOMPtr<imgIRequest> imgRequest;
+  rv = aImageEl.GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+                           getter_AddRefs(imgRequest));
+  if (NS_SUCCEEDED(rv) && imgRequest) {
+    nsCOMPtr<imgIContainer> imgContainer;
+    rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
+    if (NS_SUCCEEDED(rv) && imgContainer &&
+        imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData)
+  : mParent(aGlobal)
+  , mData(aData)
+  , mSurface(nullptr)
+  , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
+{
+  MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
+}
+
+ImageBitmap::~ImageBitmap()
+{
+}
+
+JSObject*
+ImageBitmap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ImageBitmapBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv)
+{
+  gfx::IntRect rect = aRect;
+
+  // fix up negative dimensions
+  if (rect.width < 0) {
+    CheckedInt32 checkedX = CheckedInt32(rect.x) + rect.width;
+
+    if (!checkedX.isValid()) {
+      aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+      return;
+    }
+
+    rect.x = checkedX.value();
+    rect.width = -(rect.width);
+  }
+
+  if (rect.height < 0) {
+    CheckedInt32 checkedY = CheckedInt32(rect.y) + rect.height;
+
+    if (!checkedY.isValid()) {
+      aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+      return;
+    }
+
+    rect.y = checkedY.value();
+    rect.height = -(rect.height);
+  }
+
+  mPictureRect = rect;
+}
+
+already_AddRefed<SourceSurface>
+ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget)
+{
+  MOZ_ASSERT(aTarget);
+
+  if (!mSurface) {
+    mSurface = mData->GetAsSourceSurface();
+  }
+
+  if (!mSurface) {
+    return nullptr;
+  }
+
+  RefPtr<DrawTarget> target = aTarget;
+  IntRect surfRect(0, 0, mSurface->GetSize().width, mSurface->GetSize().height);
+
+  // Check if we still need to crop our surface
+  if (!mPictureRect.IsEqualEdges(surfRect)) {
+
+    IntRect surfPortion = surfRect.Intersect(mPictureRect);
+
+    // the crop lies entirely outside the surface area, nothing to draw
+    if (surfPortion.IsEmpty()) {
+      mSurface = nullptr;
+      RefPtr<gfx::SourceSurface> surface(mSurface);
+      return surface.forget();
+    }
+
+    IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()),
+                  std::max(0, surfPortion.Y() - mPictureRect.Y()));
+
+    // Do not initialize this target with mPictureRect.Size().
+    // In the Windows8 D2D1 backend, it might trigger "partial upload" from a
+    // non-SourceSurfaceD2D1 surface to a D2D1Image in the following
+    // CopySurface() step. However, the "partial upload" only supports uploading
+    // a rectangle starts from the upper-left point, which means it cannot
+    // upload an arbitrary part of the source surface and this causes problems
+    // if the mPictureRect is not starts from the upper-left point.
+    target = target->CreateSimilarDrawTarget(mSurface->GetSize(),
+                                             target->GetFormat());
+
+    if (!target) {
+      mSurface = nullptr;
+      RefPtr<gfx::SourceSurface> surface(mSurface);
+      return surface.forget();
+    }
+
+    // Make mCropRect match new surface we've cropped to
+    mPictureRect.MoveTo(0, 0);
+    target->CopySurface(mSurface, surfPortion, dest);
+    mSurface = target->Snapshot();
+  }
+
+  // Replace our surface with one optimized for the target we're about to draw
+  // to, under the assumption it'll likely be drawn again to that target.
+  // This call should be a no-op for already-optimized surfaces
+  mSurface = target->OptimizeSourceSurface(mSurface);
+
+  RefPtr<gfx::SourceSurface> surface(mSurface);
+  return surface.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  // Check if the image element is completely available or not.
+  if (!aImageEl.Complete()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  // Check if the image element is a bitmap (e.g. it's a vector graphic) or not.
+  if (!HasRasterImage(aImageEl)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  // Get the SourceSurface out from the image element and then do security
+  // checking.
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Create ImageBitmap.
+  nsRefPtr<layers::Image> data = CreateImageFromSurface(surface, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Set the picture rectangle.
+  if (ret && aCropRect.isSome()) {
+    ret->SetPictureRect(aCropRect.ref(), aRv);
+  }
+
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  // Check network state.
+  if (aVideoEl.NetworkState() == HTMLMediaElement::NETWORK_EMPTY) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  // Check ready state.
+  // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA.
+  if (aVideoEl.ReadyState() <= HTMLMediaElement::HAVE_METADATA) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  // Check security.
+  nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentPrincipal();
+  bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
+  if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  // Create ImageBitmap.
+  ImageContainer *container = aVideoEl.GetImageContainer();
+
+  if (!container) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  AutoLockImage lockImage(container);
+  layers::Image* data = lockImage.GetImage();
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Set the picture rectangle.
+  if (ret && aCropRect.isSome()) {
+    ret->SetPictureRect(aCropRect.ref(), aRv);
+  }
+
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Crop the source surface if needed.
+  RefPtr<SourceSurface> croppedSurface;
+  IntRect cropRect = aCropRect.valueOr(IntRect());
+
+  // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot
+  // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy
+  // of the rendering context. We handle cropping in this case.
+  if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 ||
+       aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) &&
+      aCropRect.isSome()) {
+    // The _surface_ must be a DataSourceSurface.
+    MOZ_ASSERT(surface->GetType() == SurfaceType::DATA,
+               "The snapshot SourceSurface from WebGL rendering contest is not \
+               DataSourceSurface.");
+    RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+    croppedSurface = CropDataSourceSurface(dataSurface, cropRect);
+    cropRect.MoveTo(0, 0);
+  }
+  else {
+    croppedSurface = surface;
+  }
+
+  if (NS_WARN_IF(!croppedSurface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  // Create an Image from the SourceSurface.
+  nsRefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Set the picture rectangle.
+  if (ret && aCropRect.isSome()) {
+    ret->SetPictureRect(cropRect, aRv);
+  }
+
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  // Copy data into SourceSurface.
+  dom::Uint8ClampedArray array;
+  DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
+  MOZ_ASSERT(inited);
+
+  array.ComputeLengthAndData();
+  const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
+  const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT);
+  const uint32_t imageWidth = aImageData.Width();
+  const uint32_t imageHeight = aImageData.Height();
+  const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
+  const uint32_t dataLength = array.Length();
+  const gfx::IntSize imageSize(imageWidth, imageHeight);
+
+  // Check the ImageData is neutered or not.
+  if (imageWidth == 0 || imageHeight == 0 ||
+      (imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  // Create and Crop the raw data into a layers::Image
+  nsRefPtr<layers::Image> data;
+  if (NS_IsMainThread()) {
+    data = CreateImageFromRawData(imageSize, imageStride, FORMAT,
+                                  array.Data(), dataLength,
+                                  aCropRect, aRv);
+  } else {
+    nsRefPtr<CreateImageFromRawDataInMainThreadSyncTask> task
+      = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(),
+                                                       dataLength,
+                                                       imageStride,
+                                                       FORMAT,
+                                                       imageSize,
+                                                       aCropRect,
+                                                       aRv,
+                                                       getter_AddRefs(data));
+    task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext());
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Create an ImageBimtap.
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // The cropping information has been handled in the CreateImageFromRawData()
+  // function.
+
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  // Check origin-clean.
+  if (aCanvasCtx.GetCanvas()->IsWriteOnly()) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
+  RefPtr<SourceSurface> surface = aCanvasCtx.GetSurfaceSnapshot();
+
+  if (NS_WARN_IF(!surface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  const IntSize surfaceSize = surface->GetSize();
+  if (surfaceSize.width == 0 || surfaceSize.height == 0) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  nsRefPtr<layers::Image> data = CreateImageFromSurface(surface, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Set the picture rectangle.
+  if (ret && aCropRect.isSome()) {
+    ret->SetPictureRect(aCropRect.ref(), aRv);
+  }
+
+  return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap,
+                            const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  if (!aImageBitmap.mData) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  nsRefPtr<layers::Image> data = aImageBitmap.mData;
+  nsRefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+  // Set the picture rectangle.
+  if (ret && aCropRect.isSome()) {
+    ret->SetPictureRect(aCropRect.ref(), aRv);
+  }
+
+  return ret.forget();
+}
+
+class FulfillImageBitmapPromise
+{
+protected:
+  FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
+  : mPromise(aPromise)
+  , mImageBitmap(aImageBitmap)
+  {
+    MOZ_ASSERT(aPromise);
+  }
+
+  void DoFulfillImageBitmapPromise()
+  {
+    mPromise->MaybeResolve(mImageBitmap);
+  }
+
+private:
+  nsRefPtr<Promise> mPromise;
+  nsRefPtr<ImageBitmap> mImageBitmap;
+};
+
+class FulfillImageBitmapPromiseTask final : public nsRunnable,
+                                            public FulfillImageBitmapPromise
+{
+public:
+  FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap)
+  : FulfillImageBitmapPromise(aPromise, aImageBitmap)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    DoFulfillImageBitmapPromise();
+    return NS_OK;
+  }
+};
+
+class FulfillImageBitmapPromiseWorkerTask final : public WorkerSameThreadRunnable,
+                                                  public FulfillImageBitmapPromise
+{
+public:
+  FulfillImageBitmapPromiseWorkerTask(Promise* aPromise, ImageBitmap* aImageBitmap)
+  : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+    FulfillImageBitmapPromise(aPromise, aImageBitmap)
+  {
+  }
+
+  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    DoFulfillImageBitmapPromise();
+    return true;
+  }
+};
+
+static void
+AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task =
+      new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap);
+    NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+  } else {
+    nsRefPtr<FulfillImageBitmapPromiseWorkerTask> task =
+      new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap);
+    task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread.
+  }
+}
+
+static already_AddRefed<SourceSurface>
+DecodeBlob(Blob& aBlob, ErrorResult& aRv)
+{
+  // Get the internal stream of the blob.
+  nsCOMPtr<nsIInputStream> stream;
+  aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Get the MIME type string of the blob.
+  // The type will be checked in the DecodeImage() method.
+  nsAutoString mimeTypeUTF16;
+  aBlob.GetType(mimeTypeUTF16);
+
+  // Get the Component object.
+  nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
+  if (NS_WARN_IF(!imgtool)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  // Decode image.
+  NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16); // NS_ConvertUTF16toUTF8 ---|> nsAutoCString
+  nsCOMPtr<imgIContainer> imgContainer;
+  nsresult rv = imgtool->DecodeImage(stream, mimeTypeUTF8, getter_AddRefs(imgContainer));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  // Get the surface out.
+  uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE;
+  uint32_t whichFrame = imgIContainer::FRAME_FIRST;
+  RefPtr<SourceSurface> surface = imgContainer->GetFrame(whichFrame, frameFlags);
+
+  if (NS_WARN_IF(!surface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  return surface.forget();
+}
+
+static already_AddRefed<layers::Image>
+DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+  // Decode the blob into a SourceSurface.
+  RefPtr<SourceSurface> surface = DecodeBlob(aBlob, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Crop the source surface if needed.
+  RefPtr<SourceSurface> croppedSurface = surface;
+
+  if (aCropRect.isSome()) {
+    // The blob is just decoded into a RasterImage and not optimized yet, so the
+    // _surface_ we get is a DataSourceSurface which wraps the RasterImage's
+    // raw buffer.
+    MOZ_ASSERT(surface->GetType() == SurfaceType::DATA,
+          "The SourceSurface from just decoded Blob is not DataSourceSurface.");
+    RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+    croppedSurface = CropDataSourceSurface(dataSurface, aCropRect.ref());
+    aCropRect->MoveTo(0, 0);
+  }
+
+  if (NS_WARN_IF(!croppedSurface)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  // Create an Image from the source surface.
+  nsRefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface, aRv);
+
+  return image.forget();
+}
+
+class CreateImageBitmapFromBlob
+{
+protected:
+  CreateImageBitmapFromBlob(Promise* aPromise,
+                            nsIGlobalObject* aGlobal,
+                            Blob& aBlob,
+                            const Maybe<IntRect>& aCropRect)
+  : mPromise(aPromise),
+    mGlobalObject(aGlobal),
+    mBlob(&aBlob),
+    mCropRect(aCropRect)
+  {
+  }
+
+  virtual ~CreateImageBitmapFromBlob()
+  {
+  }
+
+  void DoCreateImageBitmapFromBlob(ErrorResult& aRv)
+  {
+    nsRefPtr<ImageBitmap> imageBitmap = CreateImageBitmap(aRv);
+
+    // handle errors while creating ImageBitmap
+    // (1) error occurs during reading of the object
+    // (2) the image data is not in a supported file format
+    // (3) the image data is corrupted
+    // All these three cases should reject promise with null value
+    if (aRv.Failed()) {
+      mPromise->MaybeReject(aRv);
+      return;
+    }
+
+    if (imageBitmap && mCropRect.isSome()) {
+      imageBitmap->SetPictureRect(mCropRect.ref(), aRv);
+
+      if (aRv.Failed()) {
+        mPromise->MaybeReject(aRv);
+        return;
+      }
+    }
+
+    mPromise->MaybeResolve(imageBitmap);
+    return;
+  }
+
+  virtual already_AddRefed<ImageBitmap> CreateImageBitmap(ErrorResult& aRv) = 0;
+
+  nsRefPtr<Promise> mPromise;
+  nsCOMPtr<nsIGlobalObject> mGlobalObject;
+  RefPtr<mozilla::dom::Blob> mBlob;
+  Maybe<IntRect> mCropRect;
+};
+
+class CreateImageBitmapFromBlobTask final : public nsRunnable,
+                                            public CreateImageBitmapFromBlob
+{
+public:
+  CreateImageBitmapFromBlobTask(Promise* aPromise,
+                                nsIGlobalObject* aGlobal,
+                                Blob& aBlob,
+                                const Maybe<IntRect>& aCropRect)
+  :CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    ErrorResult error;
+    DoCreateImageBitmapFromBlob(error);
+    return NS_OK;
+  }
+
+private:
+  already_AddRefed<ImageBitmap> CreateImageBitmap(ErrorResult& aRv) override
+  {
+    nsRefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect, aRv);
+
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    // Create ImageBitmap object.
+    nsRefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+    return imageBitmap.forget();
+  }
+};
+
+class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnable,
+                                                  public CreateImageBitmapFromBlob
+{
+  // This is a synchronous task.
+  class DecodeBlobInMainThreadSyncTask final : public WorkerMainThreadRunnable
+  {
+  public:
+    DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate,
+                                   Blob& aBlob,
+                                   Maybe<IntRect>& aCropRect,
+                                   ErrorResult& aError,
+                                   layers::Image** aImage)
+    : WorkerMainThreadRunnable(aWorkerPrivate)
+    , mBlob(aBlob)
+    , mCropRect(aCropRect)
+    , mError(aError)
+    , mImage(aImage)
+    {
+    }
+
+    bool MainThreadRun() override
+    {
+      nsRefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect, mError);
+
+      if (NS_WARN_IF(mError.Failed())) {
+        return false;
+      }
+
+      image.forget(mImage);
+
+      return true;
+    }
+
+  private:
+    Blob& mBlob;
+    Maybe<IntRect>& mCropRect;
+    ErrorResult& mError;
+    layers::Image** mImage;
+  };
+
+public:
+  CreateImageBitmapFromBlobWorkerTask(Promise* aPromise,
+                                  nsIGlobalObject* aGlobal,
+                                  mozilla::dom::Blob& aBlob,
+                                  const Maybe<IntRect>& aCropRect)
+  : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+    CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect)
+  {
+  }
+
+  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    ErrorResult error;
+    DoCreateImageBitmapFromBlob(error);
+    return !(error.Failed());
+  }
+
+private:
+  already_AddRefed<ImageBitmap> CreateImageBitmap(ErrorResult& aRv) override
+  {
+    nsRefPtr<layers::Image> data;
+
+    nsRefPtr<DecodeBlobInMainThreadSyncTask> task =
+      new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect,
+                                         aRv, getter_AddRefs(data));
+    task->Dispatch(mWorkerPrivate->GetJSContext()); // This is a synchronous call.
+
+    if (NS_WARN_IF(aRv.Failed())) {
+      mPromise->MaybeReject(aRv);
+      return nullptr;
+    }
+
+    // Create ImageBitmap object.
+    nsRefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+    return imageBitmap.forget();
+  }
+
+};
+
+static void
+AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal,
+                               Blob& aBlob, const Maybe<IntRect>& aCropRect)
+{
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task =
+      new CreateImageBitmapFromBlobTask(aPromise, aGlobal, aBlob, aCropRect);
+    NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+  } else {
+    nsRefPtr<CreateImageBitmapFromBlobWorkerTask> task =
+      new CreateImageBitmapFromBlobWorkerTask(aPromise, aGlobal, aBlob, aCropRect);
+    task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread.
+  }
+}
+
+/* static */ already_AddRefed<Promise>
+ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
+                    const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+
+  nsRefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) {
+    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return promise.forget();
+  }
+
+  nsRefPtr<ImageBitmap> imageBitmap;
+
+  if (aSrc.IsHTMLImageElement()) {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "Creating ImageBitmap from HTMLImageElement off the main thread.");
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(), aCropRect, aRv);
+  } else if (aSrc.IsHTMLVideoElement()) {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "Creating ImageBitmap from HTMLVideoElement off the main thread.");
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), aCropRect, aRv);
+  } else if (aSrc.IsHTMLCanvasElement()) {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "Creating ImageBitmap from HTMLCanvasElement off the main thread.");
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), aCropRect, aRv);
+  } else if (aSrc.IsImageData()) {
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, aRv);
+  } else if (aSrc.IsCanvasRenderingContext2D()) {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "Creating ImageBitmap from CanvasRenderingContext2D off the main thread.");
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), aCropRect, aRv);
+  } else if (aSrc.IsImageBitmap()) {
+    imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, aRv);
+  } else if (aSrc.IsBlob()) {
+    AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), aCropRect);
+    return promise.forget();
+  } else {
+    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  }
+
+  if (!aRv.Failed()) {
+    AsyncFulfillImageBitmapPromise(promise, imageBitmap);
+  }
+
+  return promise.forget();
+}
+
+/*static*/ JSObject*
+ImageBitmap::ReadStructuredClone(JSContext* aCx,
+                                 JSStructuredCloneReader* aReader,
+                                 nsIGlobalObject* aParent,
+                                 const nsTArray<nsRefPtr<layers::Image>>& aClonedImages,
+                                 uint32_t aIndex)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+  // aParent might be null.
+
+  uint32_t picRectX_;
+  uint32_t picRectY_;
+  uint32_t picRectWidth_;
+  uint32_t picRectHeight_;
+
+  if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
+      !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_)) {
+    return nullptr;
+  }
+
+  int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
+  int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
+  int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
+  int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
+
+  // Create a new ImageBitmap.
+  MOZ_ASSERT(!aClonedImages.IsEmpty());
+  MOZ_ASSERT(aIndex < aClonedImages.Length());
+
+  // nsRefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
+  // called because the static analysis thinks dereferencing XPCOM objects
+  // can GC (because in some cases it can!), and a return statement with a
+  // JSObject* type means that JSObject* is on the stack as a raw pointer
+  // while destructors are running.
+  JS::Rooted<JS::Value> value(aCx);
+  {
+    nsRefPtr<ImageBitmap> imageBitmap =
+      new ImageBitmap(aParent, aClonedImages[aIndex]);
+
+    ErrorResult error;
+    imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
+                                        picRectWidth, picRectHeight), error);
+    if (NS_WARN_IF(error.Failed())) {
+      error.SuppressException();
+      return nullptr;
+    }
+
+    if (!GetOrCreateDOMReflector(aCx, imageBitmap, &value)) {
+      return nullptr;
+    }
+  }
+
+  return &(value.toObject());
+}
+
+/*static*/ bool
+ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
+                                  nsTArray<nsRefPtr<layers::Image>>& aClonedImages,
+                                  ImageBitmap* aImageBitmap)
+{
+  MOZ_ASSERT(aWriter);
+  MOZ_ASSERT(aImageBitmap);
+
+  const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
+  const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
+  const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
+  const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
+
+  // Indexing the cloned images and send the index to the receiver.
+  uint32_t index = aClonedImages.Length();
+
+  if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
+      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
+      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight))) {
+    return false;
+  }
+
+  aClonedImages.AppendElement(aImageBitmap->mData);
+
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/canvas/ImageBitmap.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ImageBitmap_h
+#define mozilla_dom_ImageBitmap_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/Maybe.h"
+#include "nsCycleCollectionParticipant.h"
+
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace gfx {
+class SourceSurface;
+}
+
+namespace layers {
+class Image;
+}
+
+namespace dom {
+
+namespace workers {
+class WorkerStructuredCloneClosure;
+}
+
+class CanvasRenderingContext2D;
+class File;
+class HTMLCanvasElement;
+class HTMLImageElement;
+class HTMLVideoElement;
+class ImageData;
+class Promise;
+class PostMessageEvent; // For StructuredClone between windows.
+class CreateImageBitmapFromBlob;
+class CreateImageBitmapFromBlobTask;
+class CreateImageBitmapFromBlobWorkerTask;
+
+/*
+ * ImageBitmap is an opaque handler to several kinds of image-like objects from
+ * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
+ * CanvasRenderingContext2D and Image Blob.
+ *
+ * An ImageBitmap could be painted to a canvas element.
+ *
+ * Generally, an ImageBitmap only keeps a reference to its source object's
+ * buffer, but if the source object is an ImageData, an Blob or a
+ * HTMLCanvasElement with WebGL rendering context, the ImageBitmap copy the
+ * source object's buffer.
+ */
+class ImageBitmap final : public nsISupports,
+                          public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmap)
+
+  nsCOMPtr<nsIGlobalObject> GetParentObject() const { return mParent; }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  uint32_t Width() const
+  {
+    return mPictureRect.Width();
+  }
+
+  uint32_t Height() const
+  {
+    return mPictureRect.Height();
+  }
+
+  /*
+   * The PrepareForDrawTarget() might return null if the mPictureRect does not
+   * intersect with the size of mData.
+   */
+  already_AddRefed<gfx::SourceSurface>
+  PrepareForDrawTarget(gfx::DrawTarget* aTarget);
+
+  static already_AddRefed<Promise>
+  Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
+         const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static JSObject*
+  ReadStructuredClone(JSContext* aCx,
+                      JSStructuredCloneReader* aReader,
+                      nsIGlobalObject* aParent,
+                      const nsTArray<nsRefPtr<layers::Image>>& aClonedImages,
+                      uint32_t aIndex);
+
+  static bool
+  WriteStructuredClone(JSStructuredCloneWriter* aWriter,
+                       nsTArray<nsRefPtr<layers::Image>>& aClonedImages,
+                       ImageBitmap* aImageBitmap);
+
+  friend CreateImageBitmapFromBlob;
+  friend CreateImageBitmapFromBlobTask;
+  friend CreateImageBitmapFromBlobWorkerTask;
+
+protected:
+
+  ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData);
+
+  virtual ~ImageBitmap();
+
+  void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  static already_AddRefed<ImageBitmap>
+  CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap,
+                 const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv);
+
+  nsCOMPtr<nsIGlobalObject> mParent;
+
+  /*
+   * The mData is the data buffer of an ImageBitmap, so the mData must not be
+   * null.
+   *
+   * The mSurface is a cache for drawing the ImageBitmap onto a
+   * HTMLCanvasElement. The mSurface is null while the ImageBitmap is created
+   * and then will be initialized while the PrepareForDrawTarget() method is
+   * called first time.
+   *
+   * The mSurface might just be a reference to the same data buffer of the mData
+   * if the are of mPictureRect is just the same as the mData's size. Or, it is
+   * a independent data buffer which is copied and cropped form the mData's data
+   * buffer.
+   */
+  nsRefPtr<layers::Image> mData;
+  RefPtr<gfx::SourceSurface> mSurface;
+
+  /*
+   * The mPictureRect is the size of the source image in default, however, if
+   * users specify the cropping area while creating an ImageBitmap, then this
+   * mPictureRect is the cropping area.
+   *
+   * Note that if the CreateInternal() copies and crops data from the source
+   * image, then this mPictureRect is just the size of the final mData.
+   *
+   * The mPictureRect will be used at PrepareForDrawTarget() while user is going
+   * to draw this ImageBitmap into a HTMLCanvasElement.
+   */
+  gfx::IntRect mPictureRect;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ImageBitmap_h
+
+
new file mode 100644
--- /dev/null
+++ b/dom/canvas/ImageBitmapSource.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ImageBitmapSource_h
+#define mozilla_dom_ImageBitmapSource_h
+
+namespace mozilla {
+namespace dom {
+
+// So we don't have to forward declare this elsewhere.
+class HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap;
+typedef HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap
+        ImageBitmapSource;
+
+}
+}
+
+#endif
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -24,31 +24,34 @@ EXPORTS.mozilla.ipc += [
 ]
 
 EXPORTS.mozilla.dom += [
     'CanvasGradient.h',
     'CanvasPath.h',
     'CanvasPattern.h',
     'CanvasRenderingContext2D.h',
     'CanvasUtils.h',
+    'ImageBitmap.h',
+    'ImageBitmapSource.h',
     'ImageData.h',
     'TextMetrics.h',
     'WebGLVertexArrayObject.h',
 ]
 
 # http://support.microsoft.com/kb/143208
 DEFINES['NOMINMAX'] = True
 
 # Canvas 2D and common sources
 UNIFIED_SOURCES += [
     'CanvasImageCache.cpp',
     'CanvasRenderingContext2D.cpp',
     'CanvasUtils.cpp',
     'DocumentRendererChild.cpp',
     'DocumentRendererParent.cpp',
+    'ImageBitmap.cpp',
     'ImageData.cpp',
 ]
 
 # WebGL Sources
 UNIFIED_SOURCES += [
     'MurmurHash3.cpp',
     'WebGL1Context.cpp',
     'WebGL1ContextBuffers.cpp',
@@ -141,25 +144,27 @@ LOCAL_INCLUDES += [
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
+    '../workers',
     '/dom/base',
     '/dom/html',
     '/dom/svg',
     '/dom/xul',
     '/gfx/gl',
     '/image',
     '/js/xpconnect/src',
     '/layout/generic',
     '/layout/style',
     '/layout/xul',
+    '/media/libyuv/include',
 ]
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
 
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/image_error-early.png
@@ -0,0 +1,1 @@
+ERROR
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/imagebitmap_on_worker.js
@@ -0,0 +1,81 @@
+function ok(expect, msg) {
+  postMessage({type: "status", status: !!expect, msg: msg});
+}
+
+function doneTask() {
+  postMessage({type: "doneTask"});
+}
+
+function promiseThrows(p, name) {
+  var didThrow;
+  return p.then(function() { didThrow = false; },
+                function() { didThrow = true; })
+          .then(function() { ok(didThrow, "[TestException] " + name); });
+}
+
+onmessage = function(event) {
+  if (event.data.type == "testImageData") {
+    var width = event.data.width;
+    var height = event.data.height;
+    var imageData = event.data.source;
+    ok(imageData, "[CreateFromImageData] An ImageData is passed into worker.")
+    ok(imageData.width == width, "[CreateFromImageData] Passed ImageData has right width = " + width);
+    ok(imageData.height == height, "[CreateFromImageData] Passed ImageData has right height = " + height);
+
+    var promise = createImageBitmap(imageData);
+    promise.then(function(bitmap) {
+      ok(bitmap, "[CreateFromImageData] ImageBitmap is created successfully.");
+      ok(bitmap.width == width, "[CreateFromImageData] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width);
+      ok(bitmap.height == height, "[CreateFromImageData] ImageBitmap.height = " + bitmap.height + ", expected height = " + height);
+
+      doneTask();
+    });
+  } else if (event.data.type == "testBlob") {
+    var width = event.data.width;
+    var height = event.data.height;
+    var blob = event.data.source;
+    ok(blob, "[CreateFromBlob] A Blob object is passed into worker.");
+
+    var promise = createImageBitmap(blob);
+    promise.then(function(bitmap) {
+      ok(bitmap, "[CreateFromBlob] ImageBitmap is created successfully.");
+      ok(bitmap.width == width, "[CreateFromBlob] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width);
+      ok(bitmap.height == height, "[CreateFromBlob] ImageBitmap.height = " + bitmap.height + ", expected height = " + height);
+
+      doneTask();
+    });
+  } else if (event.data.type == "testImageBitmap") {
+    var width  = event.data.width;
+    var height = event.data.height;
+    var source = event.data.source;
+    ok(source, "[CreateFromImageBitmap] A soruce object is passed into worker.");
+
+    var promise = createImageBitmap(source);
+    promise.then(function(bitmap) {
+      ok(bitmap, "[CreateFromImageBitmap] ImageBitmap is created successfully.");
+      ok(bitmap.width == width, "[CreateFromImageBitmap] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width);
+      ok(bitmap.height == height, "[CreateFromImageBitmap] ImageBitmap.height = " + bitmap.height + ", expected height = " + height);
+
+      var promise2 = createImageBitmap(bitmap);
+      promise2.then(function(bitmap2) {
+        ok(bitmap2, "[CreateFromImageBitmap] 2nd ImageBitmap is created successfully.");
+        ok(bitmap.width == width, "[CreateFromImageBitmap] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width);
+        ok(bitmap.height == height, "[CreateFromImageBitmap] ImageBitmap.height = " + bitmap.height + ", expected height = " + height);
+
+        doneTask();
+      });
+    });
+  } else if (event.data.type == "testException") {
+    var source = event.data.source;
+    if (event.data.sx) {
+      var sx = event.data.sx;
+      var sy = event.data.sy;
+      var sw = event.data.sw;
+      var sh = event.data.sh;
+      promiseThrows(createImageBitmap(source, sx, sy, sw, sh), event.data.msg);
+    } else {
+      promiseThrows(createImageBitmap(source), event.data.msg);
+    }
+    doneTask();
+  }
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/imagebitmap_structuredclone.js
@@ -0,0 +1,30 @@
+function ok(expect, msg) {
+  postMessage({"type": "status", status: !!expect, msg: msg});
+}
+
+onmessage = function(event) {
+  ok(!!event.data.bitmap1, "Get the 1st ImageBitmap from the main script.");
+  ok(!!event.data.bitmap2, "Get the 2st ImageBitmap from the main script.");
+
+  // send the first original ImageBitmap back to the main-thread
+  postMessage({"type":"bitmap1",
+               "bitmap":event.data.bitmap1});
+
+  // create a new ImageBitmap from the 2nd original ImageBitmap
+  // and then send the newly created ImageBitmap back to the main-thread
+  var promise = createImageBitmap(event.data.bitmap2);
+  promise.then(
+    function(bitmap) {
+      ok(true, "Successfully create a new ImageBitmap from the 2nd original bitmap in worker.");
+
+      // send the newly created ImageBitmap back to the main-thread
+      postMessage({"type":"bitmap2", "bitmap":bitmap});
+
+      // finish the test
+      postMessage({"type": "finish"});
+    },
+    function() {
+      ok(false, "Cannot create a new bitmap from the original bitmap in worker.");
+    }
+  );
+}
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/imagebitmap_structuredclone_iframe.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<body>
+  <script type="application/javascript">
+
+  function ok(expect, msg) {
+    window.parent.postMessage({"type": "status", status: !!expect, msg: msg}, "*");
+  }
+
+  window.onmessage = function(event) {
+    ok(!!event.data.bitmap1, "Get the 1st ImageBitmap from the main script.");
+    ok(!!event.data.bitmap2, "Get the 2st ImageBitmap from the main script.");
+
+    // send the first original ImageBitmap back to the main window
+    window.parent.postMessage({"type":"bitmap1", "bitmap":event.data.bitmap1}, "*");
+
+    // create a new ImageBitmap from the 2nd original ImageBitmap
+    // and then send the newly created ImageBitmap back to the main window
+    var promise = createImageBitmap(event.data.bitmap2);
+    promise.then(
+      function(bitmap) {
+        ok(true, "Successfully create a new ImageBitmap from the 2nd original bitmap in worker.");
+
+        // send the newly created ImageBitmap back to the main window
+        window.parent.postMessage({"type":"bitmap2", "bitmap":bitmap}, "*");
+
+        // finish the test
+        window.parent.postMessage({"type": "finish"}, "*");
+      },
+      function() {
+        ok(false, "Cannot create a new bitmap from the original bitmap in worker.");
+      }
+    );
+  }
+
+
+  </script>
+</body>
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   android.json
   file_drawImage_document_domain.html
   image_anim-gr.gif
   image_anim-gr.png
   image_anim-poster-gr.png
   image_broken.png
+  image_error-early.png
   image_ggrr-256x256.png
   image_green-16x16.png
   image_green-1x1.png
   image_green-redirect
   image_green-redirect^headers^
   image_green.png
   image_red-16x16.png
   image_red.png
@@ -18,16 +19,19 @@ support-files =
   image_red_crossorigin_credentials.png^headers^
   image_redtransparent.png
   image_rgrg-256x256.png
   image_rrgg-256x256.png
   image_transparent.png
   image_transparent50.png
   image_yellow.png
   image_yellow75.png
+  imagebitmap_on_worker.js
+  imagebitmap_structuredclone.js
+  imagebitmap_structuredclone_iframe.html
 
 [test_2d.clearRect.image.offscreen.html]
 [test_2d.clip.winding.html]
 [test_2d.composite.canvas.color-burn.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.color-dodge.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.color.html]
@@ -229,16 +233,21 @@ skip-if = os == "android" || appname == 
 [test_capture.html]
 support-files = captureStream_common.js
 [test_drawImageIncomplete.html]
 [test_drawImage_document_domain.html]
 [test_drawImage_edge_cases.html]
 [test_drawWindow.html]
 support-files = file_drawWindow_source.html file_drawWindow_common.js
 skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && toolkit != 'gonk')
+[test_imagebitmap.html]
+[test_imagebitmap_on_worker.html]
+[test_imagebitmap_structuredclone.html]
+[test_imagebitmap_structuredclone_iframe.html]
+[test_imagebitmap_structuredclone_window.html]
 [test_ImageData_ctor.html]
 [test_isPointInStroke.html]
 [test_mozDashOffset.html]
 [test_mozGetAsFile.html]
 [test_strokeText_throw.html]
 [test_toBlob.html]
 [test_toDataURL_alpha.html]
 [test_toDataURL_lowercase_ascii.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap.html
@@ -0,0 +1,342 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+
+<img src="image_anim-gr.gif" id="image" class="resource">
+<video width="320" height="240" src="http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg&cors=anonymous" id="video" crossOrigin="anonymous" autoplay></video>
+
+<canvas id="c1" class="output" width="128" height="128"></canvas>
+<canvas id="c2" width="128" height="128"></canvas>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/**
+ * [isPixel description]
+ * @param  {[type]}  ctx : canvas context
+ * @param  {[type]}  x   : pixel x coordinate
+ * @param  {[type]}  y   : pixel y coordinate
+ * @param  {[type]}  c   : a rgba color code
+ * @param  {[type]}  d   : error duration
+ * @return {Promise}
+ */
+function isPixel(ctx, x, y, c, d) {
+  var pos = x + "," + y;
+  var color = c[0] + "," + c[1] + "," + c[2] + "," + c[3];
+  var pixel = ctx.getImageData(x, y, 1, 1);
+  var pr = pixel.data[0],
+      pg = pixel.data[1],
+      pb = pixel.data[2],
+      pa = pixel.data[3];
+  ok(c[0]-d <= pr && pr <= c[0]+d &&
+     c[1]-d <= pg && pg <= c[1]+d &&
+     c[2]-d <= pb && pb <= c[2]+d &&
+     c[3]-d <= pa && pa <= c[3]+d,
+     "pixel "+pos+" of "+ctx.canvas.id+" is "+pr+","+pg+","+pb+","+pa+"; expected "+color+" +/- "+d);
+}
+
+var TEST_BITMAPS = [
+    {'rect': [0, 0, 128, 128],        'draw': [0, 0, 64, 64, 0, 0, 64, 64],       'test': [[0,    0,    [255, 0, 0, 255], 5]]},
+    {'rect': [128, 0, 128, 128],      'draw': [0, 0, 64, 64, 0, 0, 64, 64],       'test': [[0,    0,    [0, 255, 0, 255], 5]]},
+    {'rect': [230, 230, 128, 128],    'draw': [0, 0, 128, 128, 0, 0, 128, 128],   'test': [[0,    0,    [255, 0, 0, 255], 5],
+                                                                                           [100,  100,  [0, 0, 0, 0],     5]]},
+    {'rect': [-64, -64, 512, 512],    'draw': [0, 0, 128, 128, 0, 0, 128, 128],   'test': [[0,    0,    [0, 0, 0, 0],     5],
+                                                                                           [100,  100,  [255, 0, 0, 255], 5]]},
+    {'rect': [128, 128, -128, -128],  'draw': [0, 0, 128, 128, 0, 0, 128, 128],   'test': [[0,    0,    [255, 0, 0, 255], 5]]},
+    {'rect': [0, 0, 256, 256],        'draw': [0, 128, 128, 128, 0, 0, 128, 128], 'test': [[0,    0,    [0, 255, 0, 255], 5]]},
+];
+
+var canvas, ctx, ctx2, completedImage;
+
+function failed(ex) {
+  ok(false, "Promise failure: " + ex);
+}
+
+function testDraw() {
+  var resolver, bitmaps = [], image = new Image();
+
+  image.src = 'image_rgrg-256x256.png';
+  var promise = new Promise(function (arg) { resolver = arg; });
+
+  function createBitmap(def) {
+    return createImageBitmap(image, def.rect[0], def.rect[1], def.rect[2], def.rect[3])
+      .then(function (bitmap) { def.bitmap = bitmap; }, failed);
+  };
+
+  image.onload = function() {
+    completedImage = image;
+    resolver(Promise.all(TEST_BITMAPS.map(createBitmap)));
+  };
+
+  function testPixel(test) {
+    isPixel(ctx, test[0], test[1], test[2], test[3]);
+  };
+
+  return promise.then(function() {
+    TEST_BITMAPS.forEach(function (test) {
+      if (!test.bitmap) { return; }
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+      ctx.drawImage(test.bitmap, test.draw[0], test.draw[1], test.draw[2], test.draw[3], test.draw[4], test.draw[5], test.draw[6], test.draw[7]);
+      test.test.forEach(testPixel);
+      is(test.bitmap.width, Math.abs(test.rect[2]), "Bitmap has correct width " + test.bitmap.width);
+      is(test.bitmap.height, Math.abs(test.rect[3]), "Bitmap has correct height " + test.bitmap.height);
+    });
+  });
+}
+
+function testSources() {
+  ctx.fillStyle="#00FF00";
+  ctx.fillRect(0, 0, 128, 128);
+
+  function check(bitmap) {
+    ctx2.clearRect(0, 0, 128, 128);
+    ctx2.drawImage(bitmap, 0, 0);
+    isPixel(ctx2, 0, 0, [0, 255, 0, 255], 5);
+  };
+
+  function getPNGBlobBitmapPromise() {
+    return new Promise(function(resolve, reject) {
+      canvas.toBlob(function(blob) {
+        resolve(createImageBitmap(blob));
+      })
+    });
+  }
+
+  function getJPGBlobBitmapPromise() {
+    return new Promise(function(resolve, reject) {
+      canvas.toBlob(function(blob) {
+        resolve(createImageBitmap(blob));
+      }, "image/jpeg", 0.95)
+    });
+  }
+
+  return Promise.all([
+    createImageBitmap(document.getElementById('image')).then(check, failed),                                         // HTMLImageElement
+    createImageBitmap(ctx).then(check, failed),                                                                      // CanvasRenderingContext2D
+    createImageBitmap(canvas).then(check, failed),                                                                   // HTMLCanvasElement
+    createImageBitmap(ctx).then(function (bitmap) { return createImageBitmap(bitmap).then(check, failed); }, failed), // ImageBitmap
+    createImageBitmap(document.getElementById('video'), 140, 0, 20, 20).then(check, failed),                         // HTMLVideoElement
+    createImageBitmap(ctx.getImageData(0, 0, 128, 128)).then(check, failed),                                          // ImageData
+    getPNGBlobBitmapPromise().then(check, failed),                                                                   // PNG blob
+    getJPGBlobBitmapPromise().then(check, failed),                                                                   // JPEG blob
+  ]);
+}
+
+function promiseThrows(p, name) {
+  var didThrow;
+  return p.then(function() { didThrow = false; },
+                function() { didThrow = true; })
+          .then(function() { ok(didThrow, name); });
+}
+
+function testExceptions() {
+
+  function createImageBitmapWithNeuturedImageData() {
+    return new Promise(function(resolve, reject) {
+      var tempImage = document.createElement('img');
+      tempImage.src = 'image_rgrg-256x256.png';
+      tempImage.onload = function() {
+        var tempCanvas = document.createElement('canvas');
+        var tempCtx = tempCanvas.getContext('2d');
+        tempCanvas.with = tempImage.naturalWidth;
+        tempCanvas.height = tempImage.naturalHeight;
+        tempCtx.drawImage(tempImage, 0, 0);
+        var tempWorker = new Worker("test_imagebitmap_on_worker.js");
+        var imageData = tempCtx.getImageData(0, 0, tempImage.naturalWidth, tempImage.naturalHeight);
+        tempWorker.postMessage(imageData.data.buffer, [imageData.data.buffer]);
+        tempWorker.terminate();
+
+        ok(imageData.data.length == 0, "Get a neutured ImageData.");
+        resolve(createImageBitmap(imageData));
+      }
+    });
+  }
+
+  function createImageBitmapWithCorruptedBlob() {
+    return new Promise(function(resolve, reject) {
+      var xhr = new XMLHttpRequest();
+      xhr.open("GET", "image_error-early.png");
+      xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
+      xhr.onload = function()
+      {
+        ok(xhr.response, "Get a corrupted blob");
+        resolve(createImageBitmap(xhr.response));
+      }
+      xhr.send();
+    });
+  }
+
+  function createImageBitmapWithNonImageFile() {
+    return new Promise(function(resolve, reject) {
+      var xhr = new XMLHttpRequest();
+      xhr.open("GET", "test_imagebitmap_on_worker.js");
+      xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
+      xhr.onload = function()
+      {
+        ok(xhr.response, "Get a non-image blob");
+        resolve(createImageBitmap(xhr.response));
+      }
+      xhr.send();
+    });
+  }
+
+  return Promise.all([
+    promiseThrows(createImageBitmap(new Image()), 'createImageBitmap should throw with unloaded image'),
+    promiseThrows(createImageBitmap(completedImage, 0, 0, 0, 0), 'createImageBitmap should throw with 0 width/height'),
+    promiseThrows(createImageBitmap(null), 'createImageBitmap should throw with null source'),
+    promiseThrows(createImageBitmapWithNeuturedImageData(), "createImageBitmap should throw with neutured ImageData"),
+    promiseThrows(createImageBitmapWithCorruptedBlob(), "createImageBitmap should throw with corrupted blob"),
+    promiseThrows(createImageBitmapWithNonImageFile(), "createImageBitmap should throw with non-image blob"),
+  ]);
+}
+
+function testSecurityErrors() {
+
+  function getUncleanImagePromise() {
+    return new Promise(function(resolve, reject) {
+      var uncleanImage = document.createElement('img');
+
+      uncleanImage.onload = function() {
+        resolve(createImageBitmap(uncleanImage));
+      }
+
+      uncleanImage.onerror = function() {
+        reject();
+      }
+
+      uncleanImage.src = "http://example.com/tests/dom/canvas/test/crossorigin/image.png";
+    });
+  }
+
+  function getUncleanVideoPromise() {
+    return new Promise(function(resolve, reject) {
+      var uncleanVideo = document.createElement('video');
+
+      uncleanVideo.onloadeddata = function() {
+        resolve(createImageBitmap(uncleanVideo));
+      }
+
+      uncleanVideo.onerror = function() {
+        reject();
+      }
+
+      uncleanVideo.src = "http://example.com/tests/dom/canvas/test/crossorigin/video.sjs?name=tests/dom/media/test/320x240.ogv&type=video/ogg";
+      uncleanVideo.play();
+    });
+  }
+
+  function getTaintedCanvasPromise() {
+    return new Promise(function(resolve, reject) {
+      var uncleanImage = document.createElement('img');
+
+      uncleanImage.onload = function() {
+        var taintedCanvas = document.createElement('canvas');
+        var taintedCtx = taintedCanvas.getContext('2d');
+        taintedCtx.drawImage(uncleanImage, 0, 0);
+        resolve(createImageBitmap(taintedCanvas));
+      }
+
+      uncleanImage.onerror = function() {
+        reject();
+      }
+
+      uncleanImage.src = "http://example.com/tests/dom/canvas/test/crossorigin/image.png";
+    });
+  }
+
+  function getTaintedCanvasRenderingContex2dPromise() {
+    return new Promise(function(resolve, reject) {
+      var uncleanImage = document.createElement('img');
+
+      uncleanImage.onload = function() {
+        var taintedCanvas = document.createElement('canvas');
+        var taintedCtx = taintedCanvas.getContext('2d');
+        taintedCtx.drawImage(uncleanImage, 0, 0);
+        resolve(createImageBitmap(taintedCtx));
+      }
+
+      uncleanImage.onerror = function() {
+        reject();
+      }
+
+      uncleanImage.src = "http://example.com/tests/dom/canvas/test/crossorigin/image.png";
+    });
+  }
+
+  function checkPromiseFailedWithSecurityError(p) {
+    return p.then( function(reason) { ok(false, "Did not get SecurityError with unclean source. ImageBitmap was created successfully."); },
+                   function(reason) { if (reason == "SecurityError: The operation is insecure.") {
+                                        ok(true, reason);
+                                      }
+                                      else {
+                                        ok(false, "Did not get SecurityError with unclean source. Error Message: " + reason);
+                                      }});
+  }
+
+  return Promise.all([
+    checkPromiseFailedWithSecurityError(getUncleanImagePromise()),
+    checkPromiseFailedWithSecurityError(getUncleanVideoPromise()),
+    checkPromiseFailedWithSecurityError(getTaintedCanvasPromise()),
+    checkPromiseFailedWithSecurityError(getTaintedCanvasRenderingContex2dPromise()),
+  ]);
+}
+
+function testCreatePattern() {
+  var resolve;
+  var promise = new Promise(function (arg) { resolve = arg; });
+
+  var TEST_PATTERN = [
+    [0,   0,   [255, 0, 0, 255], 1],
+    [128, 128, [255, 0, 0, 255], 1],
+    [256, 256, [255, 0, 0, 255], 1],
+    [384, 0,   [0, 255, 0, 255], 1],
+    [0,   384, [0, 255, 0, 255], 1],
+  ];
+
+  var canvas = document.createElement('canvas');
+  canvas.width = "512";
+  canvas.height = "512";
+  var ctx = canvas.getContext('2d');
+
+  var image = new Image();
+  image.src = 'image_rgrg-256x256.png';
+  image.onload = function() {
+    var p = createImageBitmap(image);
+    p.then(function(bitmap) {
+      ctx.rect(0, 0, 512, 512);
+      ctx.fillStyle = ctx.createPattern(bitmap, "repeat");
+      ctx.fill();
+      document.body.appendChild(canvas);
+    });
+    resolve(p);
+  }
+
+  return promise.then(function() {
+    TEST_PATTERN.forEach(function(test) {
+      isPixel(ctx, test[0], test[1], test[2], test[3]);
+    });
+  });
+}
+
+
+function runTests() {
+  canvas = document.getElementById('c1');
+  ctx = canvas.getContext('2d');
+  ctx2 = document.getElementById('c2').getContext('2d');
+
+  testDraw()
+    .then(testCreatePattern)
+    .then(testSources)
+    .then(testExceptions)
+    .then(testSecurityErrors)
+    .then(SimpleTest.finish, function(ev) { failed(ev); SimpleTest.finish(); });
+}
+
+addLoadEvent(runTests);
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_on_worker.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap on Worker</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<script type="text/javascript">
+
+// The following tests is not enabled in Worker now:
+// create from a HTMLImageElement
+// create from a HTMLVideoElement
+// create from a HTMLCanvasElement
+// create from a CanvasRenderingContext2D
+// call CanvasRenderingContext2D.drawImage()
+// call CanvasRenderingContext2D.createPaattern()
+// test security error from an unclean HTHMLImageElemnt
+// test security error from an unclean HTHMLVideoElemnt
+// test security error from an tainted HTHMLCanvasElemnt
+// test security error from an tainted CanvasRenderingContext2D
+
+// Task constructor function
+function Task(aType, aWidth, aHeight, aMsg, aSource) {
+  this.type = aType;
+  this.width = aWidth;
+  this.height = aHeight;
+  this.msg = aMsg;
+  this.source = aSource;
+}
+
+function TaskWithCrop(aType, aWidth, aHeight, aMsg, aSource, aSx, aSy, aSw, aSh) {
+  Task.call(this, aType, aWidth, aHeight, aMsg, aSource);
+  this.sx = aSx;
+  this.sy = aSy;
+  this.sw = aSw;
+  this.sh = aSh;
+}
+TaskWithCrop.prototype = Object.create(Task.prototype);
+TaskWithCrop.prototype.constructor = TaskWithCrop;
+
+var WORKER_TASKS = {
+  tasks: [], // an arrayf of Task objects
+  dispatch: function() {
+    if (this.tasks.length) {
+      worker.postMessage(this.tasks.pop());
+    } else {
+      worker.terminate();
+      SimpleTest.finish();
+    }
+  },
+};
+
+var worker = new Worker("imagebitmap_on_worker.js");
+worker.onmessage = function(event) {
+  if (event.data.type == "status") {
+    ok(event.data.status, event.data.msg);
+  } else if (event.data.type == "doneTask") {
+    WORKER_TASKS.dispatch();
+  }
+}
+
+function runTests() {
+  ok(worker, "Worker created successfully.");
+
+  // prepare an ImageData object
+  var image = document.createElement('img');
+  var canvas = document.createElement('canvas');
+  var ctx = canvas.getContext('2d');
+  var imageData;
+  image.src = "image_rgrg-256x256.png";
+  image.onload = function() {
+    var width = image.naturalWidth;
+    var height = image.naturalHeight;
+
+    canvas.width = image.naturalWidth;
+    canvas.height = image.naturalHeight;
+    ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
+
+    imageData = ctx.getImageData(0, 0, image.naturalWidth, image.naturalHeight);
+
+    // task: test soruce: an ImageData
+    WORKER_TASKS.tasks.push(new Task("testImageData", width, height, "", imageData));
+
+    // task: test soruce: an ImageBitmap
+    WORKER_TASKS.tasks.push(new Task("testImageBitmap", width, height, "", imageData));
+
+    // task: test soruce: a Blob
+    canvas.toBlob(function(aBlob) {
+    	WORKER_TASKS.tasks.push(new Task("testBlob", width, height, "", aBlob));
+    });
+  };
+
+  // task: throw exception: general: sw == 0 || sh == 0
+  WORKER_TASKS.tasks.push(new TaskWithCrop("testException", 0, 0, "createImageBitmap should throw with 0 width/height", imageData, 0, 0, 0, 0));
+
+  // task: throw exception: general: source is a null
+  WORKER_TASKS.tasks.push(new TaskWithCrop("testException", 0, 0, "createImageBitmap should throw with null source", null, 0, 0, 0, 0));
+
+  // task: throw exception: ImageData: an ImageData object whose data is data attribute has been neutered
+  var neuturedImageData = function getNeuturedImageData(imageData) {
+  	worker.postMessage(imageData.data.buffer, [imageData.data.buffer]);
+  	return imageData;
+  }(ctx.getImageData(0, 0, 50, 50));
+  WORKER_TASKS.tasks.push(new TaskWithCrop("testException", neuturedImageData.width, neuturedImageData.height,
+                                           "createImageBitmap should throw with neutured ImageData",
+                                           neuturedImageData, 0, 0, neuturedImageData.width, neuturedImageData.height));
+
+  // task: throw exception: Blob: a corrupted blob
+  function getCorruptedBlob(fileName) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", fileName);
+    xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
+    xhr.onload = function() {
+        WORKER_TASKS.tasks.push(new Task("testException", 0, 0, "createImageBitmap should reject promise with corrupted blob", xhr.response));
+    }
+    xhr.send();
+  }
+  getCorruptedBlob("image_error-early.png");
+
+  // task: throw exception: Blob: non-image file
+  function getNonImageFile(fileName) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", fileName);
+    xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
+    xhr.onload = function() {
+      WORKER_TASKS.tasks.push(new Task("testException", 0, 0, "createImageBitmap should reject promise with non-image blob", xhr.response));
+
+      // start to dispatch tasks to worker
+      WORKER_TASKS.dispatch();
+    }
+    xhr.send();
+  }
+  getNonImageFile("imagebitmap_on_worker.js");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_structuredclone.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap : Structured Clone</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<script type="text/javascript">
+
+var gImage1;
+var gImage2;
+var gImageBitmap1;
+var gImageBitmap2;
+
+function isPixel(ctx1, ctx2, x, y) {
+  var pixel1 = ctx1.getImageData(x, y, 1, 1);
+  var pixel2 = ctx2.getImageData(x, y, 1, 1);
+  ok(pixel1.data[0] == pixel2.data[0] &&
+     pixel1.data[1] == pixel2.data[1] &&
+     pixel1.data[2] == pixel2.data[2] &&
+     pixel1.data[3] == pixel2.data[3],
+    "Color(" + pixel1.data[0] + ", " + pixel1.data[1] + ", " + pixel1.data[2] + ", " + pixel1.data[3] + ") should qual to Color(" + pixel2.data[0] + ", " + pixel2.data[1] + ", " + pixel2.data[2] + ", " + pixel2.data[3] +  ")");
+}
+
+function compareImageBitmapWithImageElement(imageBitmap, imageElement) {
+  var canvas1 = document.createElement('canvas');
+  var canvas2 = document.createElement('canvas');
+
+  canvas1.width  = imageElement.naturalWidth;
+  canvas1.height = imageElement.naturalHeight;
+  canvas2.width  = imageElement.naturalWidth;
+  canvas2.height = imageElement.naturalHeight;
+
+  var ctx1 = canvas1.getContext('2d');
+  var ctx2 = canvas2.getContext('2d');
+
+  ctx1.drawImage(imageElement, 0, 0);
+  ctx2.drawImage(imageBitmap, 0, 0);
+
+  document.body.appendChild(canvas1);
+  document.body.appendChild(canvas2);
+
+  for (var t = 0; t < 20; ++t) {
+    // check one random pixel
+    var randomX = Math.floor(Math.random() * imageElement.naturalWidth);
+    var randomY = Math.floor(Math.random() * imageElement.naturalHeight);
+    isPixel(ctx1, ctx2, randomX, randomY);
+  }
+}
+
+var worker = new Worker("imagebitmap_structuredclone.js");
+worker.onmessage = function(event) {
+
+  if (event.data.type == "status") {
+    ok(event.data.status, event.data.msg);
+  } else if (event.data.type == "finish") {
+    SimpleTest.finish();
+  } else if (event.data.type == "bitmap1") {
+    compareImageBitmapWithImageElement(event.data.bitmap, gImage1);
+  } else if (event.data.type == "bitmap2") {
+    compareImageBitmapWithImageElement(event.data.bitmap, gImage2);
+  }
+}
+
+function prepareTwoImageBitmap() {
+  gImage1 = document.createElement('img');
+  gImage2 = document.createElement('img');
+  gImage1.src = "image_rgrg-256x256.png";
+  gImage2.src = "image_yellow.png";
+
+  var p1 = new Promise(function(resolve, reject) {
+    gImage1.onload = function() {
+      var promise = createImageBitmap(gImage1);
+      promise.then(function(bitmap) {
+        gImageBitmap1 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  var p2 = new Promise(function(resolve, reject) {
+    gImage2.onload = function() {
+      var promise = createImageBitmap(gImage2);
+      promise.then(function(bitmap) {
+        gImageBitmap2 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  return Promise.all([p1, p2]);
+}
+
+function runTests() {
+  ok(worker, "Worker created successfully.");
+
+  prepareTwoImageBitmap().then(function(){
+    worker.postMessage({"bitmap1":gImageBitmap1, "bitmap2":gImageBitmap2});
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_structuredclone_iframe.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap : StructuredClone between main window and iframe</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<div id="content"></div>
+<script type="text/javascript">
+
+var gImage1;
+var gImage2;
+var gImageBitmap1;
+var gImageBitmap2;
+
+function isPixel(ctx1, ctx2, x, y) {
+  var pixel1 = ctx1.getImageData(x, y, 1, 1);
+  var pixel2 = ctx2.getImageData(x, y, 1, 1);
+  ok(pixel1.data[0] == pixel2.data[0] &&
+     pixel1.data[1] == pixel2.data[1] &&
+     pixel1.data[2] == pixel2.data[2] &&
+     pixel1.data[3] == pixel2.data[3],
+    "Color(" + pixel1.data[0] + ", " + pixel1.data[1] + ", " + pixel1.data[2] + ", " + pixel1.data[3] + ") should qual to Color(" + pixel2.data[0] + ", " + pixel2.data[1] + ", " + pixel2.data[2] + ", " + pixel2.data[3] +  ")");
+}
+
+function compareImageBitmapWithImageElement(imageBitmap, imageElement) {
+  var canvas1 = document.createElement('canvas');
+  var canvas2 = document.createElement('canvas');
+
+  canvas1.width  = imageElement.naturalWidth;
+  canvas1.height = imageElement.naturalHeight;
+  canvas2.width  = imageElement.naturalWidth;
+  canvas2.height = imageElement.naturalHeight;
+
+  var ctx1 = canvas1.getContext('2d');
+  var ctx2 = canvas2.getContext('2d');
+
+  ctx1.drawImage(imageElement, 0, 0);
+  ctx2.drawImage(imageBitmap, 0, 0);
+
+  document.body.appendChild(canvas1);
+  document.body.appendChild(canvas2);
+
+  for (var t = 0; t < 20; ++t) {
+    // check one random pixel
+    var randomX = Math.floor(Math.random() * imageElement.naturalWidth);
+    var randomY = Math.floor(Math.random() * imageElement.naturalHeight);
+    isPixel(ctx1, ctx2, randomX, randomY);
+  }
+}
+
+function prepareTwoImageBitmap() {
+  gImage1 = document.createElement('img');
+  gImage2 = document.createElement('img');
+  gImage1.src = "image_rgrg-256x256.png";
+  gImage2.src = "image_yellow.png";
+
+  var p1 = new Promise(function(resolve, reject) {
+    gImage1.onload = function() {
+      var promise = createImageBitmap(gImage1);
+      promise.then(function(bitmap) {
+        gImageBitmap1 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  var p2 = new Promise(function(resolve, reject) {
+    gImage2.onload = function() {
+      var promise = createImageBitmap(gImage2);
+      promise.then(function(bitmap) {
+        gImageBitmap2 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  return Promise.all([p1, p2]);
+}
+
+function runTests() {
+  window.onmessage = function(event) {
+    if (event.data.type == "status") {
+      ok(event.data.status, event.data.msg);
+    } else if (event.data.type == "finish") {
+      SimpleTest.finish();
+    } else if (event.data.type == "bitmap1") {
+      compareImageBitmapWithImageElement(event.data.bitmap, gImage1);
+    } else if (event.data.type == "bitmap2") {
+      compareImageBitmapWithImageElement(event.data.bitmap, gImage2);
+    }
+  }
+
+  var div = document.getElementById("content");
+  ok(div, "Parent exists");
+
+  var ifr = document.createElement("iframe");
+  ifr.addEventListener("load", iframeLoaded, false);
+  ifr.setAttribute('src', "imagebitmap_structuredclone_iframe.html");
+  div.appendChild(ifr);
+
+  function iframeLoaded() {
+    prepareTwoImageBitmap().then(function(){
+      ifr.contentWindow.postMessage({"bitmap1":gImageBitmap1, "bitmap2":gImageBitmap2}, "*");
+    });
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_imagebitmap_structuredclone_window.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<title>Test ImageBitmap : StructuredClone main window to main window</title>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<script type="text/javascript">
+
+var gImage1;
+var gImage2;
+var gImageBitmap1;
+var gImageBitmap2;
+
+function isPixel(ctx1, ctx2, x, y) {
+  var pixel1 = ctx1.getImageData(x, y, 1, 1);
+  var pixel2 = ctx2.getImageData(x, y, 1, 1);
+  ok(pixel1.data[0] == pixel2.data[0] &&
+     pixel1.data[1] == pixel2.data[1] &&
+     pixel1.data[2] == pixel2.data[2] &&
+     pixel1.data[3] == pixel2.data[3],
+    "Color(" + pixel1.data[0] + ", " + pixel1.data[1] + ", " + pixel1.data[2] + ", " + pixel1.data[3] + ") should qual to Color(" + pixel2.data[0] + ", " + pixel2.data[1] + ", " + pixel2.data[2] + ", " + pixel2.data[3] +  ")");
+}
+
+function compareImageBitmapWithImageElement(imageBitmap, imageElement) {
+  var canvas1 = document.createElement('canvas');
+  var canvas2 = document.createElement('canvas');
+
+  canvas1.width  = imageElement.naturalWidth;
+  canvas1.height = imageElement.naturalHeight;
+  canvas2.width  = imageElement.naturalWidth;
+  canvas2.height = imageElement.naturalHeight;
+
+  var ctx1 = canvas1.getContext('2d');
+  var ctx2 = canvas2.getContext('2d');
+
+  ctx1.drawImage(imageElement, 0, 0);
+  ctx2.drawImage(imageBitmap, 0, 0);
+
+  document.body.appendChild(canvas1);
+  document.body.appendChild(canvas2);
+
+  for (var t = 0; t < 20; ++t) {
+    // check one random pixel
+    var randomX = Math.floor(Math.random() * imageElement.naturalWidth);
+    var randomY = Math.floor(Math.random() * imageElement.naturalHeight);
+    isPixel(ctx1, ctx2, randomX, randomY);
+  }
+}
+
+window.onmessage = function(event) {
+  compareImageBitmapWithImageElement(event.data.bitmap1, gImage1);
+  compareImageBitmapWithImageElement(event.data.bitmap2, gImage2);
+  SimpleTest.finish();
+}
+
+function prepareTwoImageBitmap() {
+  gImage1 = document.createElement('img');
+  gImage2 = document.createElement('img');
+  gImage1.src = "image_rgrg-256x256.png";
+  gImage2.src = "image_yellow.png";
+
+  var p1 = new Promise(function(resolve, reject) {
+    gImage1.onload = function() {
+      var promise = createImageBitmap(gImage1);
+      promise.then(function(bitmap) {
+        gImageBitmap1 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  var p2 = new Promise(function(resolve, reject) {
+    gImage2.onload = function() {
+      var promise = createImageBitmap(gImage2);
+      promise.then(function(bitmap) {
+        gImageBitmap2 = bitmap;
+        resolve(true);
+      });
+    }
+  });
+
+  return Promise.all([p1, p2]);
+}
+
+function runTests() {
+  prepareTwoImageBitmap().then(function(){
+    window.postMessage({"bitmap1":gImageBitmap1, "bitmap2":gImageBitmap2}, "*");
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTests);
+
+</script>
+</body>
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -929,17 +929,17 @@ EventListenerManager::CompileEventHandle
   // the JS string stuff, so don't worry about playing games with
   // refcounting XPCOM stringbuffers.
   JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx,
                                                       str.BeginReading(),
                                                       str.Length()));
   NS_ENSURE_TRUE(jsStr, NS_ERROR_OUT_OF_MEMORY);
 
   // Get the reflector for |aElement|, so that we can pass to setElement.
-  if (NS_WARN_IF(!GetOrCreateDOMReflector(cx, target, aElement, &v))) {
+  if (NS_WARN_IF(!GetOrCreateDOMReflector(cx, aElement, &v))) {
     return NS_ERROR_FAILURE;
   }
   JS::CompileOptions options(cx);
   options.setIntroductionType("eventHandler")
          .setFileAndLine(url.get(), lineNo)
          .setVersion(JSVERSION_DEFAULT)
          .setElement(&v.toObject())
          .setElementAttributeName(jsStr);
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -27,16 +27,20 @@ const kEventConstructors = {
   Event:                                     { create: function (aName, aProps) {
                                                          return new Event(aName, aProps);
                                                        },
                                              },
   AnimationEvent:                            { create: function (aName, aProps) {
                                                          return new AnimationEvent(aName, aProps);
                                                        },
                                              },
+  AnimationPlaybackEvent:                    { create: function (aName, aProps) {
+                                                         return new AnimationPlaybackEvent(aName, aProps);
+                                                       },
+                                             },
   AudioProcessingEvent:                      { create: null, // Cannot create untrusted event from JS.
                                              },
   BeforeAfterKeyboardEvent:                  { create: function (aName, aProps) {
                                                          return new BeforeAfterKeyboardEvent(aName, aProps);
                                                        },
                                              },
   BeforeUnloadEvent:                         { create: function (aName, aProps) {
                                                          var e = document.createEvent("beforeunloadevent");
--- a/dom/gamepad/Gamepad.cpp
+++ b/dom/gamepad/Gamepad.cpp
@@ -26,17 +26,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Ga
 
 void
 Gamepad::UpdateTimestamp()
 {
   nsCOMPtr<nsPIDOMWindow> newWindow(do_QueryInterface(mParent));
   if(newWindow) {
     nsPerformance* perf = newWindow->GetPerformance();
     if (perf) {
-      mTimestamp =  perf->GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now());
+      mTimestamp =  perf->Now();
     }
   }
 }
 
 Gamepad::Gamepad(nsISupports* aParent,
                  const nsAString& aID, uint32_t aIndex,
                  GamepadMappingType aMapping,
                  uint32_t aNumButtons, uint32_t aNumAxes)
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -40,23 +40,16 @@
 #include "WebGL1Context.h"
 #include "WebGL2Context.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
 
-namespace {
-
-typedef mozilla::dom::HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement
-HTMLImageOrCanvasOrVideoElement;
-
-} // namespace
-
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
                                       mContext, mCallback)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLCanvasPrintState, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLCanvasPrintState, Release)
@@ -278,20 +271,20 @@ HTMLCanvasElement::CopyInnerTo(Element* 
     HTMLCanvasElement* dest = static_cast<HTMLCanvasElement*>(aDest);
     dest->mOriginalCanvas = this;
 
     nsCOMPtr<nsISupports> cxt;
     dest->GetContext(NS_LITERAL_STRING("2d"), getter_AddRefs(cxt));
     nsRefPtr<CanvasRenderingContext2D> context2d =
       static_cast<CanvasRenderingContext2D*>(cxt.get());
     if (context2d && !mPrintCallback) {
-      HTMLImageOrCanvasOrVideoElement element;
-      element.SetAsHTMLCanvasElement() = this;
+      CanvasImageSource source;
+      source.SetAsHTMLCanvasElement() = this;
       ErrorResult err;
-      context2d->DrawImage(element,
+      context2d->DrawImage(source,
                            0.0, 0.0, err);
       rv = err.StealNSResult();
     }
   }
   return rv;
 }
 
 nsresult HTMLCanvasElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
@@ -744,19 +737,22 @@ GetCanvasContextType(const nsAString& st
   }
 
   return false;
 }
 
 static already_AddRefed<nsICanvasRenderingContextInternal>
 CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas)
 {
+  MOZ_ASSERT(contextType != CanvasContextType::NoContext);
   nsRefPtr<nsICanvasRenderingContextInternal> ret;
 
   switch (contextType) {
+  case CanvasContextType::NoContext:
+    break;
   case CanvasContextType::Canvas2D:
     Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
     ret = new CanvasRenderingContext2D();
     break;
 
   case CanvasContextType::WebGL1:
     Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
 
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -31,16 +31,17 @@ class SourceSurface;
 namespace dom {
 class CanvasCaptureMediaStream;
 class File;
 class FileCallback;
 class HTMLCanvasPrintState;
 class PrintCallback;
 
 enum class CanvasContextType : uint8_t {
+  NoContext,
   Canvas2D,
   WebGL1,
   WebGL2
 };
 
 class HTMLCanvasElement final : public nsGenericHTMLElement,
                                 public nsIDOMHTMLCanvasElement
 {
@@ -257,16 +258,20 @@ public:
 
   void HandlePrintCallback(nsPresContext::nsPresContextType aType);
 
   nsresult DispatchPrintCallback(nsITimerCallback* aCallback);
 
   void ResetPrintCallback();
 
   HTMLCanvasElement* GetOriginalCanvas();
+
+  CanvasContextType GetCurrentContextType() {
+    return mCurrentContextType;
+  }
 };
 
 class HTMLCanvasPrintState final : public nsWrapperCache
 {
 public:
   HTMLCanvasPrintState(HTMLCanvasElement* aCanvas,
                        nsICanvasRenderingContextInternal* aContext,
                        nsITimerCallback* aCallback);
--- a/dom/html/HTMLMetaElement.cpp
+++ b/dom/html/HTMLMetaElement.cpp
@@ -110,17 +110,17 @@ void
 HTMLMetaElement::CreateAndDispatchEvent(nsIDocument* aDoc,
                                         const nsAString& aEventName)
 {
   if (!aDoc)
     return;
 
   nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, aEventName, true, true);
-  asyncDispatcher->PostDOMEvent();
+  asyncDispatcher->RunDOMEventWhenSafe();
 }
 
 JSObject*
 HTMLMetaElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLMetaElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
--- a/dom/html/HTMLOptionsCollection.h
+++ b/dom/html/HTMLOptionsCollection.h
@@ -38,16 +38,17 @@ class HTMLOptionsCollection final : publ
   typedef HTMLOptionElementOrHTMLOptGroupElement HTMLOptionOrOptGroupElement;
 public:
   explicit HTMLOptionsCollection(HTMLSelectElement* aSelect);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
+  using nsWrapperCache::GetWrapper;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 protected:
   virtual ~HTMLOptionsCollection();
 
   virtual JSObject* GetWrapperPreserveColorInternal() override
   {
     return nsWrapperCache::GetWrapperPreserveColor();
   }
--- a/dom/html/HTMLPropertiesCollection.h
+++ b/dom/html/HTMLPropertiesCollection.h
@@ -54,16 +54,17 @@ class HTMLPropertiesCollection final : p
 {
   friend class PropertyNodeList;
   friend class PropertyStringList;
 public:
   explicit HTMLPropertiesCollection(nsGenericHTMLElement* aRoot);
 
   // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
+  using nsWrapperCache::GetWrapper;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 protected:
   virtual ~HTMLPropertiesCollection();
 
   virtual JSObject* GetWrapperPreserveColorInternal() override
   {
     return nsWrapperCache::GetWrapperPreserveColor();
   }
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -208,17 +208,17 @@ HTMLVideoElement::GetVideoPlaybackQualit
   uint64_t droppedFrames = 0;
   uint64_t corruptedFrames = 0;
 
   if (sVideoStatsEnabled) {
     nsPIDOMWindow* window = OwnerDoc()->GetInnerWindow();
     if (window) {
       nsPerformance* perf = window->GetPerformance();
       if (perf) {
-        creationTime = perf->GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now());
+        creationTime = perf->Now();
       }
     }
 
     if (mDecoder) {
       MediaDecoder::FrameStatistics& stats = mDecoder->GetFrameStatistics();
       totalFrames = stats.GetParsedFrames();
       droppedFrames = stats.GetDroppedFrames();
       corruptedFrames = 0;
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -620,16 +620,89 @@ nsBrowserElement::GetAllowedAudioChannel
     }
 
     mBrowserElementAudioChannels.AppendElements(channels);
   }
 
   aAudioChannels.AppendElements(mBrowserElementAudioChannels);
 }
 
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetMuted(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetMuted(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::Mute(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->Mute();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+nsBrowserElement::Unmute(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->Unmute();
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetVolume(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetVolume(getter_AddRefs(req));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  return req.forget().downcast<DOMRequest>();
+}
+
+void
+nsBrowserElement::SetVolume(float aVolume, ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
+  nsresult rv = mBrowserElementAPI->SetVolume(aVolume);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
 void
 nsBrowserElement::SetNFCFocus(bool aIsFocus,
                               ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->SetNFCFocus(aIsFocus);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -71,16 +71,23 @@ public:
            ErrorResult& aRv);
 
   already_AddRefed<dom::DOMRequest> PurgeHistory(ErrorResult& aRv);
 
   void GetAllowedAudioChannels(
             nsTArray<nsRefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
             ErrorResult& aRv);
 
+  void Mute(ErrorResult& aRv);
+  void Unmute(ErrorResult& aRv);
+  already_AddRefed<dom::DOMRequest> GetMuted(ErrorResult& aRv);
+
+  void SetVolume(float aVolume , ErrorResult& aRv);
+  already_AddRefed<dom::DOMRequest> GetVolume(ErrorResult& aRv);
+
   already_AddRefed<dom::DOMRequest>
   GetScreenshot(uint32_t aWidth,
                 uint32_t aHeight,
                 const nsAString& aMimeType,
                 ErrorResult& aRv);
 
   void Zoom(float aZoom, ErrorResult& aRv);
 
--- a/dom/html/nsIHTMLCollection.h
+++ b/dom/html/nsIHTMLCollection.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIHTMLCollection_h___
 #define nsIHTMLCollection_h___
 
 #include "nsIDOMHTMLCollection.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsWrapperCache.h"
+#include "js/GCAPI.h"
 #include "js/TypeDecls.h"
 
 class nsINode;
 class nsString;
 
 namespace mozilla {
 namespace dom {
 class Element;
@@ -77,16 +78,24 @@ public:
 
   virtual void GetSupportedNames(unsigned aFlags,
                                  nsTArray<nsString>& aNames) = 0;
 
   JSObject* GetWrapperPreserveColor()
   {
     return GetWrapperPreserveColorInternal();
   }
+  JSObject* GetWrapper()
+  {
+    JSObject* obj = GetWrapperPreserveColor();
+    if (obj) {
+      JS::ExposeObjectToActiveJS(obj);
+    }
+    return obj;
+  }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) = 0;
 protected:
   virtual JSObject* GetWrapperPreserveColorInternal() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLCollection, NS_IHTMLCOLLECTION_IID)
 
 #endif /* nsIHTMLCollection_h___ */
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -148,19 +148,19 @@
 #include "mozilla/X11Util.h"
 #endif
 
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
-#include <setjmp.h>
 #include "ipc/Nuwa.h"
 #endif
+#include "NuwaChild.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadService.h"
 #endif
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h"
 #include "mozilla/dom/icc/IccChild.h"
@@ -215,29 +215,16 @@ using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
 #endif
 using namespace mozilla::widget;
 
-#ifdef MOZ_NUWA_PROCESS
-static bool sNuwaForking = false;
-
-// The size of the reserved stack (in unsigned ints). It's used to reserve space
-// to push sigsetjmp() in NuwaCheckpointCurrentThread() to higher in the stack
-// so that after it returns and do other work we don't garble the stack we want
-// to preserve in NuwaCheckpointCurrentThread().
-#define RESERVED_INT_STACK 128
-
-// A sentinel value for checking whether RESERVED_INT_STACK is large enough.
-#define STACK_SENTINEL_VALUE 0xdeadbeef
-#endif
-
 namespace mozilla {
 namespace dom {
 
 class MemoryReportRequestChild : public PMemoryReportRequestChild,
                                  public nsIRunnable
 {
 public:
     NS_DECL_ISUPPORTS
@@ -534,17 +521,17 @@ private:
 };
 
 NS_IMPL_ISUPPORTS(BackgroundChildPrimer, nsIIPCBackgroundChildCreateCallback)
 
 ContentChild* ContentChild::sSingleton;
 
 // Performs initialization that is not fork-safe, i.e. that must be done after
 // forking from the Nuwa process.
-static void
+void
 InitOnContentProcessCreated()
 {
 #ifdef MOZ_NUWA_PROCESS
     // Wait until we are forked from Nuwa
     if (IsNuwaProcess()) {
         return;
     }
 
@@ -2201,18 +2188,35 @@ ContentChild::RecvCycleCollect()
         obs->NotifyObservers(nullptr, "child-cc-request", nullptr);
     }
     nsJSContext::CycleCollectNow();
     return true;
 }
 
 #ifdef MOZ_NUWA_PROCESS
 static void
-OnFinishNuwaPreparation ()
+OnFinishNuwaPreparation()
 {
+    // We want to ensure that the PBackground actor gets cloned in the Nuwa
+    // process before we freeze. Also, we have to do this to avoid deadlock.
+    // Protocols that are "opened" (e.g. PBackground, PCompositor) block the
+    // main thread to wait for the IPC thread during the open operation.
+    // NuwaSpawnWait() blocks the IPC thread to wait for the main thread when
+    // the Nuwa process is forked. Unless we ensure that the two cannot happen
+    // at the same time then we risk deadlock. Spinning the event loop here
+    // guarantees the ordering is safe for PBackground.
+    while (!BackgroundChild::GetForCurrentThread()) {
+        if (NS_WARN_IF(!NS_ProcessNextEvent())) {
+            return;
+        }
+    }
+
+    // This will create the actor.
+    unused << mozilla::dom::NuwaChild::GetSingleton();
+
     MakeNuwaProcess();
 }
 #endif
 
 static void
 PreloadSlowThings()
 {
     // This fetches and creates all the built-in stylesheets.
@@ -2488,97 +2492,16 @@ bool
 ContentChild::DeallocPOfflineCacheUpdateChild(POfflineCacheUpdateChild* actor)
 {
     OfflineCacheUpdateChild* offlineCacheUpdate =
         static_cast<OfflineCacheUpdateChild*>(actor);
     NS_RELEASE(offlineCacheUpdate);
     return true;
 }
 
-#ifdef MOZ_NUWA_PROCESS
-class CallNuwaSpawn : public nsRunnable
-{
-public:
-    NS_IMETHOD Run()
-    {
-        NuwaSpawn();
-        if (IsNuwaProcess()) {
-            return NS_OK;
-        }
-
-        // In the new process.
-        ContentChild* child = ContentChild::GetSingleton();
-        child->SetProcessName(NS_LITERAL_STRING("(Preallocated app)"), false);
-
-        // Perform other after-fork initializations.
-        InitOnContentProcessCreated();
-
-        return NS_OK;
-    }
-};
-
-static void
-DoNuwaFork()
-{
-    NuwaSpawnPrepare();       // NuwaSpawn will be blocked.
-
-    {
-        nsCOMPtr<nsIRunnable> callSpawn(new CallNuwaSpawn());
-        NS_DispatchToMainThread(callSpawn);
-    }
-
-    // IOThread should be blocked here for waiting NuwaSpawn().
-    NuwaSpawnWait();        // Now! NuwaSpawn can go.
-    // Here, we can make sure the spawning was finished.
-}
-
-/**
- * This function should keep IO thread in a stable state and freeze it
- * until the spawning is finished.
- */
-static void
-RunNuwaFork()
-{
-    if (NuwaCheckpointCurrentThread()) {
-      DoNuwaFork();
-    }
-}
-#endif
-
-bool
-ContentChild::RecvNuwaFork()
-{
-#ifdef MOZ_NUWA_PROCESS
-    if (sNuwaForking) {           // No reentry.
-        return true;
-    }
-    sNuwaForking = true;
-
-    // We want to ensure that the PBackground actor gets cloned in the Nuwa
-    // process before we freeze. Also, we have to do this to avoid deadlock.
-    // Protocols that are "opened" (e.g. PBackground, PCompositor) block the
-    // main thread to wait for the IPC thread during the open operation.
-    // NuwaSpawnWait() blocks the IPC thread to wait for the main thread when
-    // the Nuwa process is forked. Unless we ensure that the two cannot happen
-    // at the same time then we risk deadlock. Spinning the event loop here
-    // guarantees the ordering is safe for PBackground.
-    while (!BackgroundChild::GetForCurrentThread()) {
-        if (NS_WARN_IF(!NS_ProcessNextEvent())) {
-            return false;
-        }
-    }
-
-    MessageLoop* ioloop = XRE_GetIOMessageLoop();
-    ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork));
-    return true;
-#else
-    return false; // Makes the underlying IPC channel abort.
-#endif
-}
-
 bool
 ContentChild::RecvOnAppThemeChanged()
 {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
         os->NotifyObservers(nullptr, "app-theme-changed", nullptr);
     }
     return true;
@@ -2913,114 +2836,8 @@ ContentChild::RecvTestGraphicsDeviceRese
   gfxPlatform::GetPlatform()->TestDeviceReset(DeviceResetReason(aResetReason));
 #endif
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
 
-extern "C" {
-
-#if defined(MOZ_NUWA_PROCESS)
-NS_EXPORT void
-GetProtoFdInfos(NuwaProtoFdInfo* aInfoList,
-                size_t aInfoListSize,
-                size_t* aInfoSize)
-{
-    size_t i = 0;
-
-    mozilla::dom::ContentChild* content =
-        mozilla::dom::ContentChild::GetSingleton();
-    aInfoList[i].protoId = content->GetProtocolId();
-    aInfoList[i].originFd =
-        content->GetTransport()->GetFileDescriptor();
-    i++;
-
-    IToplevelProtocol* actors[NUWA_TOPLEVEL_MAX];
-    size_t count = content->GetOpenedActorsUnsafe(actors, ArrayLength(actors));
-    for (size_t j = 0; j < count; j++) {
-        IToplevelProtocol* actor = actors[j];
-        if (i >= aInfoListSize) {
-            NS_RUNTIMEABORT("Too many top level protocols!");
-        }
-
-        aInfoList[i].protoId = actor->GetProtocolId();
-        aInfoList[i].originFd =
-            actor->GetTransport()->GetFileDescriptor();
-        i++;
-    }
-
-    if (i > NUWA_TOPLEVEL_MAX) {
-        NS_RUNTIMEABORT("Too many top level protocols!");
-    }
-    *aInfoSize = i;
-}
-
-class RunAddNewIPCProcess : public nsRunnable
-{
-public:
-    RunAddNewIPCProcess(pid_t aPid,
-                        nsTArray<mozilla::ipc::ProtocolFdMapping>& aMaps)
-        : mPid(aPid)
-    {
-        mMaps.SwapElements(aMaps);
-    }
-
-    NS_IMETHOD Run()
-    {
-        mozilla::dom::ContentChild::GetSingleton()->
-            SendAddNewProcess(mPid, mMaps);
-
-        MOZ_ASSERT(sNuwaForking);
-        sNuwaForking = false;
-
-        return NS_OK;
-    }
-
-private:
-    pid_t mPid;
-    nsTArray<mozilla::ipc::ProtocolFdMapping> mMaps;
-};
-
-/**
- * AddNewIPCProcess() is called by Nuwa process to tell the parent
- * process that a new process is created.
- *
- * In the newly created process, ResetContentChildTransport() is called to
- * reset fd for the IPC Channel and the session.
- */
-NS_EXPORT void
-AddNewIPCProcess(pid_t aPid, NuwaProtoFdInfo* aInfoList, size_t aInfoListSize)
-{
-    nsTArray<mozilla::ipc::ProtocolFdMapping> maps;
-
-    for (size_t i = 0; i < aInfoListSize; i++) {
-        int _fd = aInfoList[i].newFds[NUWA_NEWFD_PARENT];
-        mozilla::ipc::FileDescriptor fd(_fd);
-        mozilla::ipc::ProtocolFdMapping map(aInfoList[i].protoId, fd);
-        maps.AppendElement(map);
-    }
-
-    nsRefPtr<RunAddNewIPCProcess> runner = new RunAddNewIPCProcess(aPid, maps);
-    NS_DispatchToMainThread(runner);
-}
-
-NS_EXPORT void
-OnNuwaProcessReady()
-{
-    mozilla::dom::ContentChild* content =
-        mozilla::dom::ContentChild::GetSingleton();
-    content->SendNuwaReady();
-}
-
-NS_EXPORT void
-AfterNuwaFork()
-{
-    SetCurrentProcessPrivileges(base::PRIVILEGES_DEFAULT);
-#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
-    mozilla::SandboxEarlyInit(XRE_GetProcessType(), /* isNuwa: */ false);
-#endif
-}
-
-#endif // MOZ_NUWA_PROCESS
-
-}
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -358,18 +358,16 @@ public:
                                       const bool& aIsSharing,
                                       const bool& aIsFormatting,
                                       const bool& aIsFake,
                                       const bool& aIsUnmounting,
                                       const bool& aIsRemovable,
                                       const bool& aIsHotSwappable) override;
     virtual bool RecvVolumeRemoved(const nsString& aFsName) override;
 
-    virtual bool RecvNuwaFork() override;
-
     virtual bool
     RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority) override;
     virtual bool RecvMinimizeMemoryUsage() override;
 
     virtual bool RecvLoadAndRegisterSheet(const URIParams& aURI,
                                           const uint32_t& aType) override;
     virtual bool RecvUnregisterSheet(const URIParams& aURI, const uint32_t& aType) override;
 
@@ -512,15 +510,18 @@ private:
 
     static ContentChild* sSingleton;
 
     nsCOMPtr<nsIDomainPolicy> mPolicy;
 
     DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
+void
+InitOnContentProcessCreated();
+
 uint64_t
 NextWindowID();
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -37,16 +37,17 @@
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GeolocationBinding.h"
+#include "mozilla/dom/NuwaParent.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/PFMRadioParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
@@ -70,16 +71,17 @@
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/SharedBufferManagerParent.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
+#include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "mozilla/ProfileGatherer.h"
 #endif
@@ -2836,57 +2838,64 @@ ContentParent::RecvDataStoreGetStores(
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   mSendDataStoreInfos = true;
   return true;
 }
 
-bool
-ContentParent::RecvNuwaReady()
+void
+ContentParent::ForkNewProcess(bool aBlocking)
 {
 #ifdef MOZ_NUWA_PROCESS
-    if (!IsNuwaProcess()) {
-        NS_ERROR(
-            nsPrintfCString(
-                "Terminating child process %d for unauthorized IPC message: NuwaReady",
-                Pid()).get());
-
-        KillHard("NuwaReady");
-        return false;
-    }
+  uint32_t pid;
+  auto fds = MakeUnique<nsTArray<ProtocolFdMapping>>();
+
+  MOZ_ASSERT(IsNuwaProcess() && mNuwaParent);
+
+  if (mNuwaParent->ForkNewProcess(pid, mozilla::Move(fds), aBlocking)) {
+    OnNewProcessCreated(pid, mozilla::Move(fds));
+  }
+#else
+  NS_ERROR("ContentParent::ForkNewProcess() not implemented!");
+#endif
+}
+
+void
+ContentParent::OnNuwaReady()
+{
+#ifdef MOZ_NUWA_PROCESS
+    // Protection from unauthorized IPC message is done in PNuwa protocol.
+    // Just assert that this actor is really for the Nuwa process.
+    MOZ_ASSERT(IsNuwaProcess());
+
     sNuwaReady = true;
     PreallocatedProcessManager::OnNuwaReady();
-    return true;
+    return;
 #else
-    NS_ERROR("ContentParent::RecvNuwaReady() not implemented!");
-    return false;
+    NS_ERROR("ContentParent::OnNuwaReady() not implemented!");
+    return;
 #endif
 }
 
-bool
-ContentParent::RecvAddNewProcess(const uint32_t& aPid,
-                                 InfallibleTArray<ProtocolFdMapping>&& aFds)
+void
+ContentParent::OnNewProcessCreated(uint32_t aPid,
+                                   UniquePtr<nsTArray<ProtocolFdMapping>>&& aFds)
 {
 #ifdef MOZ_NUWA_PROCESS
-    if (!IsNuwaProcess()) {
-        NS_ERROR(
-            nsPrintfCString(
-                "Terminating child process %d for unauthorized IPC message: "
-                "AddNewProcess(%d)", Pid(), aPid).get());
-
-        KillHard("AddNewProcess");
-        return false;
-    }
+    // Protection from unauthorized IPC message is done in PNuwa protocol.
+    // Just assert that this actor is really for the Nuwa process.
+    MOZ_ASSERT(IsNuwaProcess());
+
     nsRefPtr<ContentParent> content;
     content = new ContentParent(this,
                                 MAGIC_PREALLOCATED_APP_MANIFEST_URL,
                                 aPid,
-                                Move(aFds));
+                                Move(*aFds.get()));
     content->Init();
 
     size_t numNuwaPrefUpdates = sNuwaPrefUpdates ?
                                 sNuwaPrefUpdates->Length() : 0;
     // Resend pref updates to the forked child.
     for (size_t i = 0; i < numNuwaPrefUpdates; i++) {
         mozilla::unused << content->SendPreferenceUpdate(sNuwaPrefUpdates->ElementAt(i));
     }
@@ -2904,20 +2913,20 @@ ContentParent::RecvAddNewProcess(const u
                                   &clipboardCaps, &domainPolicy, &initialData);
     mozilla::unused << content->SendSetOffline(isOffline);
     mozilla::unused << content->SendSetConnectivity(isConnected);
     MOZ_ASSERT(!clipboardCaps.supportsSelectionClipboard() &&
                !clipboardCaps.supportsFindClipboard(),
                "Unexpected values");
 
     PreallocatedProcessManager::PublishSpareProcess(content);
-    return true;
+    return;
 #else
-    NS_ERROR("ContentParent::RecvAddNewProcess() not implemented!");
-    return false;
+    NS_ERROR("ContentParent::OnNewProcessCreated() not implemented!");
+    return;
 #endif
 }
 
 // We want ContentParent to show up in CC logs for debugging purposes, but we
 // don't actually cycle collect it.
 NS_IMPL_CYCLE_COLLECTION_0(ContentParent)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentParent)
@@ -4290,23 +4299,24 @@ ContentParent::HandleEvent(nsIDOMGeoPosi
 
 nsConsoleService *
 ContentParent::GetConsoleService()
 {
     if (mConsoleService) {
         return mConsoleService.get();
     }
 
+    // XXXkhuey everything about this is terrible.
     // Get the ConsoleService by CID rather than ContractID, so that we
     // can cast the returned pointer to an nsConsoleService (rather than
     // just an nsIConsoleService). This allows us to call the non-idl function
     // nsConsoleService::LogMessageWithMode.
     NS_DEFINE_CID(consoleServiceCID, NS_CONSOLESERVICE_CID);
-    nsCOMPtr<nsConsoleService>  consoleService(do_GetService(consoleServiceCID));
-    mConsoleService = consoleService;
+    nsCOMPtr<nsIConsoleService> consoleService(do_GetService(consoleServiceCID));
+    mConsoleService = static_cast<nsConsoleService*>(consoleService.get());
     return mConsoleService.get();
 }
 
 bool
 ContentParent::RecvConsoleMessage(const nsString& aMessage)
 {
     nsRefPtr<nsConsoleService> consoleService = GetConsoleService();
     if (!consoleService) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -2,16 +2,17 @@
 /* vim: set sw=4 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ContentParent_h
 #define mozilla_dom_ContentParent_h
 
+#include "mozilla/dom/NuwaParent.h"
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/nsIContentParent.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/StaticPtr.h"
@@ -378,16 +379,19 @@ public:
     AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
                                          const IPC::Principal& aPrincipal,
                                          const TabId& aTabId) override;
     virtual bool
     DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override;
 
     bool HasGamepadListener() const { return mHasGamepadListener; }
 
+    void SetNuwaParent(NuwaParent* aNuwaParent) { mNuwaParent = aNuwaParent; }
+    void ForkNewProcess(bool aBlocking);
+
 protected:
     void OnChannelConnected(int32_t pid) override;
     virtual void ActorDestroy(ActorDestroyReason why) override;
     void OnNuwaForkTimeout();
 
     bool ShouldContinueFromReplyTimeout() override;
 
 private:
@@ -770,20 +774,20 @@ private:
                        InfallibleTArray<DataStoreSetting>* aValue) override;
 
     virtual bool RecvSpeakerManagerGetSpeakerStatus(bool* aValue) override;
 
     virtual bool RecvSpeakerManagerForceSpeaker(const bool& aEnable) override;
 
     virtual bool RecvSystemMessageHandled() override;
 
-    virtual bool RecvNuwaReady() override;
-
-    virtual bool RecvAddNewProcess(const uint32_t& aPid,
-                                   InfallibleTArray<ProtocolFdMapping>&& aFds) override;
+    // Callbacks from NuwaParent.
+    void OnNuwaReady();
+    void OnNewProcessCreated(uint32_t aPid,
+                             UniquePtr<nsTArray<ProtocolFdMapping>>&& aFds);
 
     virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) override;
 
     virtual bool RecvSetFakeVolumeState(const nsString& fsName, const int32_t& fsState) override;
 
     virtual bool RecvRemoveFakeVolume(const nsString& fsName) override;
 
     virtual bool RecvKeywordToURI(const nsCString& aKeyword,
@@ -911,16 +915,19 @@ private:
     bool mCalledCloseWithError;
     bool mCalledKillHard;
     bool mCreatedPairedMinidumps;
     bool mShutdownPending;
     bool mIPCOpen;
 
     friend class CrashReporterParent;
 
+    // Allows NuwaParent to access OnNuwaReady() and OnNewProcessCreated().
+    friend class NuwaParent;
+
     nsRefPtr<nsConsoleService>  mConsoleService;
     nsConsoleService* GetConsoleService();
 
     nsTArray<nsCOMPtr<nsIObserver>> mIdleListeners;
 
 #ifdef MOZ_X11
     // Dup of child's X socket, used to scope its resources to this
     // object instead of the child process's lifetime.
@@ -928,16 +935,21 @@ private:
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
     static int32_t sNuwaPid;
     static bool sNuwaReady;
 #endif
 
     PProcessHangMonitorParent* mHangMonitorActor;
+
+    // NuwaParent and ContentParent hold strong references to each other. The
+    // cycle will be broken when either actor is destroyed.
+    nsRefPtr<NuwaParent> mNuwaParent;
+
 #ifdef MOZ_ENABLE_PROFILER_SPS
     nsRefPtr<mozilla::ProfileGatherer> mGatherer;
 #endif
     nsCString mProfile;
 };
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/NuwaChild.cpp
@@ -0,0 +1,256 @@
+/* -*- 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 "ContentChild.h"
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#if defined(MOZ_CONTENT_SANDBOX)
+#if defined(XP_LINUX)
+#include "mozilla/Sandbox.h"
+#include "mozilla/SandboxInfo.h"
+#elif defined(XP_MACOSX)
+#include "mozilla/Sandbox.h"
+#endif
+#endif
+#include "mozilla/unused.h"
+#include "nsXULAppAPI.h"
+#include "NuwaChild.h"
+
+
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace dom {
+
+#ifdef MOZ_NUWA_PROCESS
+
+namespace {
+
+class CallNuwaSpawn: public nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+    NuwaSpawn();
+    if (IsNuwaProcess()) {
+      return NS_OK;
+    }
+
+    // In the new process.
+    ContentChild* child = ContentChild::GetSingleton();
+    child->InitProcessAttributes();
+
+    // Perform other after-fork initializations.
+    InitOnContentProcessCreated();
+
+    return NS_OK;
+  }
+};
+
+static void
+DoNuwaFork()
+{
+  NuwaSpawnPrepare(); // NuwaSpawn will be blocked.
+
+  {
+    nsCOMPtr<nsIRunnable> callSpawn(new CallNuwaSpawn());
+    NS_DispatchToMainThread(callSpawn);
+  }
+
+  // IOThread should be blocked here for waiting NuwaSpawn().
+  NuwaSpawnWait(); // Now! NuwaSpawn can go.
+  // Here, we can make sure the spawning was finished.
+}
+
+/**
+ * This function should keep IO thread in a stable state and freeze it
+ * until the spawning is finished.
+ */
+static void
+RunNuwaFork()
+{
+  if (NuwaCheckpointCurrentThread()) {
+    DoNuwaFork();
+  }
+}
+
+static bool sNuwaForking = false;
+
+void
+NuwaFork()
+{
+  if (sNuwaForking) {           // No reentry.
+      return;
+  }
+  sNuwaForking = true;
+
+  MessageLoop* ioloop = XRE_GetIOMessageLoop();
+  ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork));
+}
+
+} // Anonymous namespace.
+
+#endif
+
+NuwaChild* NuwaChild::sSingleton;
+
+NuwaChild*
+NuwaChild::GetSingleton()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sSingleton) {
+    PNuwaChild* nuwaChild =
+      BackgroundChild::GetForCurrentThread()->SendPNuwaConstructor();
+    MOZ_ASSERT(nuwaChild);
+
+    sSingleton = static_cast<NuwaChild*>(nuwaChild);
+  }
+
+  return sSingleton;
+}
+
+
+bool
+NuwaChild::RecvFork()
+{
+#ifdef MOZ_NUWA_PROCESS
+  if (!IsNuwaProcess()) {
+    NS_ERROR(
+      nsPrintfCString(
+        "Terminating child process %d for unauthorized IPC message: "
+          "RecvFork(%d)", getpid()).get());
+    return false;
+  }
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableFunction(&NuwaFork);
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+
+  return true;
+#else
+  NS_ERROR("NuwaChild::RecvFork() not implemented!");
+  return false;
+#endif
+}
+
+} // namespace dom
+} // namespace mozilla
+
+
+extern "C" {
+
+#if defined(MOZ_NUWA_PROCESS)
+NS_EXPORT void
+GetProtoFdInfos(NuwaProtoFdInfo* aInfoList,
+                size_t aInfoListSize,
+                size_t* aInfoSize)
+{
+  size_t i = 0;
+
+  mozilla::dom::ContentChild* content =
+    mozilla::dom::ContentChild::GetSingleton();
+  aInfoList[i].protoId = content->GetProtocolId();
+  aInfoList[i].originFd =
+    content->GetTransport()->GetFileDescriptor();
+  i++;
+
+  IToplevelProtocol* actors[NUWA_TOPLEVEL_MAX];
+  size_t count = content->GetOpenedActorsUnsafe(actors, ArrayLength(actors));
+  for (size_t j = 0; j < count; j++) {
+    IToplevelProtocol* actor = actors[j];
+    if (i >= aInfoListSize) {
+      NS_RUNTIMEABORT("Too many top level protocols!");
+    }
+
+    aInfoList[i].protoId = actor->GetProtocolId();
+    aInfoList[i].originFd =
+      actor->GetTransport()->GetFileDescriptor();
+    i++;
+  }
+
+  if (i > NUWA_TOPLEVEL_MAX) {
+    NS_RUNTIMEABORT("Too many top level protocols!");
+  }
+  *aInfoSize = i;
+}
+
+class RunAddNewIPCProcess : public nsRunnable
+{
+public:
+  RunAddNewIPCProcess(pid_t aPid,
+                      nsTArray<mozilla::ipc::ProtocolFdMapping>& aMaps)
+      : mPid(aPid)
+  {
+    mMaps.SwapElements(aMaps);
+  }
+
+  NS_IMETHOD Run()
+  {
+    NuwaChild::GetSingleton()->SendAddNewProcess(mPid, mMaps);
+
+    MOZ_ASSERT(sNuwaForking);
+    sNuwaForking = false;
+
+    return NS_OK;
+  }
+
+private:
+  pid_t mPid;
+  nsTArray<mozilla::ipc::ProtocolFdMapping> mMaps;
+};
+
+/**
+ * AddNewIPCProcess() is called by Nuwa process to tell the parent
+ * process that a new process is created.
+ *
+ * In the newly created process, ResetContentChildTransport() is called to
+ * reset fd for the IPC Channel and the session.
+ */
+NS_EXPORT void
+AddNewIPCProcess(pid_t aPid, NuwaProtoFdInfo* aInfoList, size_t aInfoListSize)
+{
+  nsTArray<mozilla::ipc::ProtocolFdMapping> maps;
+
+  for (size_t i = 0; i < aInfoListSize; i++) {
+    int _fd = aInfoList[i].newFds[NUWA_NEWFD_PARENT];
+    mozilla::ipc::FileDescriptor fd(_fd);
+    mozilla::ipc::ProtocolFdMapping map(aInfoList[i].protoId, fd);
+    maps.AppendElement(map);
+  }
+
+  nsRefPtr<RunAddNewIPCProcess> runner = new RunAddNewIPCProcess(aPid, maps);
+  NS_DispatchToMainThread(runner);
+}
+
+NS_EXPORT void
+OnNuwaProcessReady()
+{
+  NuwaChild* nuwaChild = NuwaChild::GetSingleton();
+  MOZ_ASSERT(nuwaChild);
+
+  mozilla::unused << nuwaChild->SendNotifyReady();
+}
+
+NS_EXPORT void
+AfterNuwaFork()
+{
+  SetCurrentProcessPrivileges(base::PRIVILEGES_DEFAULT);
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+  mozilla::SandboxEarlyInit(XRE_GetProcessType(), /* isNuwa: */ false);
+#endif
+}
+
+#endif // MOZ_NUWA_PROCESS
+
+}
new file mode 100644
--- /dev/null
+++ b/dom/ipc/NuwaChild.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_NuwaChild_h
+#define mozilla_dom_NuwaChild_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/PNuwaChild.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+class NuwaChild: public mozilla::dom::PNuwaChild
+{
+public:
+  virtual bool RecvFork() override;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override
+  { }
+
+  static NuwaChild* GetSingleton();
+
+private:
+  static NuwaChild* sSingleton;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
+
new file mode 100644
--- /dev/null
+++ b/dom/ipc/NuwaParent.cpp
@@ -0,0 +1,263 @@
+/* -*- 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 "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/unused.h"
+#include "nsThreadUtils.h"
+#include "NuwaParent.h"
+
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+using namespace IPC;
+
+namespace mozilla {
+namespace dom {
+
+/*static*/ NuwaParent*
+NuwaParent::Alloc() {
+  nsRefPtr<NuwaParent> actor = new NuwaParent();
+  return actor.forget().take();
+}
+
+/*static*/ bool
+NuwaParent::ActorConstructed(mozilla::dom::PNuwaParent *aActor)
+{
+  NuwaParent* actor = static_cast<NuwaParent*>(aActor);
+  actor->ActorConstructed();
+
+  return true;
+}
+
+/*static*/ bool
+NuwaParent::Dealloc(mozilla::dom::PNuwaParent *aActor)
+{
+  nsRefPtr<NuwaParent> actor = dont_AddRef(static_cast<NuwaParent*>(aActor));
+  return true;
+}
+
+NuwaParent::NuwaParent()
+  : mBlocked(false)
+  , mMonitor("NuwaParent")
+  , mClonedActor(nullptr)
+  , mWorkerThread(do_GetCurrentThread())
+  , mNewProcessPid(0)
+{
+  AssertIsOnBackgroundThread();
+}
+
+NuwaParent::~NuwaParent()
+{
+  // Both the worker thread and the main thread (ContentParent) hold a ref to
+  // this. The instance may be destroyed on either thread.
+  MOZ_ASSERT(!mContentParent);
+}
+
+inline void
+NuwaParent::AssertIsOnWorkerThread()
+{
+  nsCOMPtr<nsIThread> currentThread = do_GetCurrentThread();
+  MOZ_ASSERT(currentThread ==  mWorkerThread);
+}
+
+bool
+NuwaParent::ActorConstructed()
+{
+  AssertIsOnWorkerThread();
+  MOZ_ASSERT(Manager());
+  MOZ_ASSERT(!mContentParent);
+
+  mContentParent = BackgroundParent::GetContentParent(Manager());
+  if (!mContentParent) {
+    return false;
+  }
+
+  // mContentParent is guaranteed to be alive. It's safe to set its backward ref
+  // to this.
+  mContentParent->SetNuwaParent(this);
+  return true;
+}
+
+mozilla::ipc::IProtocol*
+NuwaParent::CloneProtocol(Channel* aChannel,
+                          ProtocolCloneContext* aCtx)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsRefPtr<NuwaParent> self = this;
+
+  MonitorAutoLock lock(mMonitor);
+
+  // Alloc NuwaParent on the worker thread.
+  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self] () -> void
+  {
+    MonitorAutoLock lock(self->mMonitor);
+    // XXX Calling NuwaParent::Alloc() leads to a compilation error. Use
+    // self->Alloc() as a workaround.
+    self->mClonedActor = self->Alloc();
+    lock.Notify();
+  });
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable,
+                                                       NS_DISPATCH_NORMAL)));
+
+  while (!mClonedActor) {
+    lock.Wait();
+  }
+  nsRefPtr<NuwaParent> actor = mClonedActor;
+  mClonedActor = nullptr;
+
+  // mManager of the cloned actor is assigned after returning from this method.
+  // We can't call ActorConstructed() right after Alloc() in the above runnable.
+  // To be safe we dispatch a runnable to the current thread to do it.
+  runnable = NS_NewRunnableFunction([actor] () -> void
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIRunnable> nested = NS_NewRunnableFunction([actor] () -> void
+    {
+      AssertIsOnBackgroundThread();
+
+      // Call NuwaParent::ActorConstructed() on the worker thread.
+      actor->ActorConstructed();
+
+      // The actor can finally be deleted after fully constructed.
+      mozilla::unused << actor->Send__delete__(actor);
+    });
+    MOZ_ASSERT(nested);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      actor->mWorkerThread->Dispatch(nested, NS_DISPATCH_NORMAL)));
+  });
+
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+
+  return actor;
+}
+
+void
+NuwaParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnWorkerThread();
+
+  nsRefPtr<NuwaParent> self = this;
+  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self] () -> void
+  {
+    // These extra nsRefPtr serve as kungFuDeathGrip to keep both objects from
+    // deletion in breaking the ref cycle.
+    nsRefPtr<ContentParent> contentParent = self->mContentParent;
+
+    contentParent->SetNuwaParent(nullptr);
+    // Need to clear the ref to ContentParent on the main thread.
+    self->mContentParent = nullptr;
+  });
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+}
+
+bool
+NuwaParent::RecvNotifyReady()
+{
+#ifdef MOZ_NUWA_PROCESS
+  if (!mContentParent || !mContentParent->IsNuwaProcess()) {
+    NS_ERROR("Received NotifyReady() message from a non-Nuwa process.");
+    return false;
+  }
+
+  // Creating a NonOwningRunnableMethod here is safe because refcount changes of
+  // mContentParent have to go the the main thread. The mContentParent will
+  // be alive when the runnable runs.
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewNonOwningRunnableMethod(mContentParent.get(),
+                                  &ContentParent::OnNuwaReady);
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+
+  return true;
+#else
+  NS_ERROR("NuwaParent::RecvNotifyReady() not implemented!");
+  return false;
+#endif
+}
+
+bool
+NuwaParent::RecvAddNewProcess(const uint32_t& aPid,
+                              nsTArray<ProtocolFdMapping>&& aFds)
+{
+#ifdef MOZ_NUWA_PROCESS
+  if (!mContentParent || !mContentParent->IsNuwaProcess()) {
+    NS_ERROR("Received AddNewProcess() message from a non-Nuwa process.");
+    return false;
+  }
+
+  mNewProcessPid = aPid;
+  mNewProcessFds->SwapElements(aFds);
+  MonitorAutoLock lock(mMonitor);
+  if (mBlocked) {
+    // Unblock ForkNewProcess().
+    mMonitor.Notify();
+    mBlocked = false;
+  } else {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewNonOwningRunnableMethodWithArgs<
+        uint32_t,
+        UniquePtr<nsTArray<ProtocolFdMapping>>&& >(
+          mContentParent.get(),
+          &ContentParent::OnNewProcessCreated,
+          mNewProcessPid,
+          Move(mNewProcessFds));
+    MOZ_ASSERT(runnable);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+  }
+  return true;
+#else
+  NS_ERROR("NuwaParent::RecvAddNewProcess() not implemented!");
+  return false;
+#endif
+}
+
+bool
+NuwaParent::ForkNewProcess(uint32_t& aPid,
+                           UniquePtr<nsTArray<ProtocolFdMapping>>&& aFds,
+                           bool aBlocking)
+{
+  MOZ_ASSERT(mWorkerThread);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mNewProcessFds = Move(aFds);
+
+  nsRefPtr<NuwaParent> self = this;
+  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self] () -> void
+  {
+    mozilla::unused << self->SendFork();
+  });
+  MOZ_ASSERT(runnable);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable,
+                                                       NS_DISPATCH_NORMAL)));
+  if (!aBlocking) {
+    return false;
+  }
+
+  MonitorAutoLock lock(mMonitor);
+  mBlocked = true;
+  while (mBlocked) {
+    // This will be notified in NuwaParent::RecvAddNewProcess().
+    lock.Wait();
+  }
+
+  if (!mNewProcessPid) {
+    return false;
+  }
+
+  aPid = mNewProcessPid;
+  aFds = Move(mNewProcessFds);
+
+  mNewProcessPid = 0;
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/NuwaParent.h
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_NuwaParent_h
+#define mozilla_dom_NuwaParent_h
+
+#include "base/message_loop.h"
+#include "mozilla/dom/PNuwaParent.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/nsRefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class ContentParent;
+
+class NuwaParent : public mozilla::dom::PNuwaParent
+{
+public:
+  explicit NuwaParent();
+
+  // Called on the main thread.
+  bool ForkNewProcess(uint32_t& aPid,
+                      UniquePtr<nsTArray<ProtocolFdMapping>>&& aFds,
+                      bool aBlocking);
+
+  // Called on the background thread.
+  bool ActorConstructed();
+
+  // Both the worker thread and the main thread hold a ref to this.
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NuwaParent)
+
+  // Functions to be invoked by the manager of this actor to alloc/dealloc the
+  // actor.
+  static NuwaParent* Alloc();
+  static bool ActorConstructed(mozilla::dom::PNuwaParent *aActor);
+  static bool Dealloc(mozilla::dom::PNuwaParent *aActor);
+
+protected:
+  virtual ~NuwaParent();
+
+  virtual bool RecvNotifyReady() override;
+  virtual bool RecvAddNewProcess(const uint32_t& aPid,
+                                 nsTArray<ProtocolFdMapping>&& aFds) override;
+  virtual mozilla::ipc::IProtocol*
+  CloneProtocol(Channel* aChannel,
+                ProtocolCloneContext* aCtx) override;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  void AssertIsOnWorkerThread();
+
+  bool mBlocked;
+  mozilla::Monitor mMonitor;
+  NuwaParent* mClonedActor;
+
+  nsCOMPtr<nsIThread> mWorkerThread;
+
+  uint32_t mNewProcessPid;
+  UniquePtr<nsTArray<ProtocolFdMapping>> mNewProcessFds;
+
+  // The mutual reference will be broken on the main thread.
+  nsRefPtr<ContentParent> mContentParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -574,19 +574,16 @@ child:
     FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
                      int32_t mountGeneration, bool isMediaPresent,
                      bool isSharing, bool isFormatting, bool isFake,
                      bool isUnmounting, bool isRemovable, bool isHotSwappable);
 
     // Notify volume is removed.
     VolumeRemoved(nsString fsName);
 
-    // Ask the Nuwa process to create a new child process.
-    NuwaFork();
-
     NotifyProcessPriorityChanged(ProcessPriority priority);
     MinimizeMemoryUsage();
 
     /**
      * Used to manage nsIStyleSheetService across processes.
      */
     async LoadAndRegisterSheet(URIParams uri, uint32_t type);
     async UnregisterSheet(URIParams uri, uint32_t type);
@@ -883,20 +880,16 @@ parent:
     async FilePathUpdateNotify(nsString aType,
                                nsString aStorageName,
                                nsString aFilepath,
                                nsCString aReason);
 
     // Notify the parent that the child has finished handling a system message.
     async SystemMessageHandled();
 
-    NuwaReady();
-
-    sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds);
-
     // called by the child (test code only) to propagate volume changes to the parent
     async CreateFakeVolume(nsString fsName, nsString mountPoint);
     async SetFakeVolumeState(nsString fsName, int32_t fsState);
     async RemoveFakeVolume(nsString fsName);
 
     sync KeywordToURI(nsCString keyword)
         returns (nsString providerName, OptionalInputStreamParams postData, OptionalURIParams uri);
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PNuwa.ipdl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+include ProtocolTypes;
+
+namespace mozilla {
+namespace dom {
+
+sync protocol PNuwa
+{
+  manager PBackground;
+
+child:
+  // Ask the Nuwa process to create a new child process.
+  async Fork();
+
+  // This message will be sent to non-Nuwa process, or to Nuwa process during
+  // test.
+  async __delete__();
+
+parent:
+  async NotifyReady();
+  sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds);
+};
+
+} // namespace layout
+} // namespace mozilla
+
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -273,18 +273,23 @@ PreallocatedProcessManagerImpl::DelayedN
 /**
  * Get a spare ContentParent from mSpareProcesses list.
  */
 already_AddRefed<ContentParent>
 PreallocatedProcessManagerImpl::GetSpareProcess()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (!mIsNuwaReady) {
+    return nullptr;
+  }
+
   if (mSpareProcesses.IsEmpty()) {
-    return nullptr;
+    // After this call, there should be a spare process.
+    mPreallocatedAppProcess->ForkNewProcess(true);
   }
 
   nsRefPtr<ContentParent> process = mSpareProcesses.LastElement();
   mSpareProcesses.RemoveElementAt(mSpareProcesses.Length() - 1);
 
   if (mSpareProcesses.IsEmpty() && mIsNuwaReady) {
     NS_ASSERTION(mPreallocatedAppProcess != nullptr,
                  "Nuwa process is not present!");
@@ -364,17 +369,17 @@ PreallocatedProcessManagerImpl::Prealloc
 {
   return !mSpareProcesses.IsEmpty();
 }
 
 
 void
 PreallocatedProcessManagerImpl::NuwaFork()
 {
-  mozilla::unused << mPreallocatedAppProcess->SendNuwaFork();
+  mPreallocatedAppProcess->ForkNewProcess(false);
 }
 #endif
 
 void
 PreallocatedProcessManagerImpl::Disable()
 {
   if (!mEnabled) {
     return;
--- a/dom/ipc/ScreenManagerParent.cpp
+++ b/dom/ipc/ScreenManagerParent.cpp
@@ -1,14 +1,15 @@
 /* -*- 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 "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "nsIWidget.h"
 #include "nsServiceManagerUtils.h"
 #include "ScreenManagerParent.h"
 #include "ContentProcessManager.h"
 
 namespace mozilla {
--- a/dom/ipc/StructuredCloneUtils.cpp
+++ b/dom/ipc/StructuredCloneUtils.cpp
@@ -8,16 +8,17 @@
 
 #include "nsIDOMDOMException.h"
 #include "nsIMutable.h"
 #include "nsIXPConnect.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/ToJSValue.h"
 #include "nsContentUtils.h"
 #include "nsJSEnvironment.h"
 #include "MainThreadUtils.h"
 #include "StructuredCloneTags.h"
 #include "jsapi.h"
 
 using namespace mozilla::dom;
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 
 #include "TabChild.h"
 
+#include "AudioChannelService.h"
+#include "gfxPrefs.h"
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleChild.h"
 #endif
 #include "Layers.h"
 #include "ContentChild.h"
 #include "TabParent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
@@ -26,16 +28,17 @@
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/DoubleTapToZoom.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layout/RenderFrameChild.h"
+#include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/unused.h"
 #include "mozIApplication.h"
 #include "nsContentUtils.h"
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -29,16 +29,18 @@ EXPORTS.mozilla.dom += [
     'ContentProcess.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'CrashReporterChild.h',
     'CrashReporterParent.h',
     'FilePickerParent.h',
     'nsIContentChild.h',
     'nsIContentParent.h',
+    'NuwaChild.h',
+    'NuwaParent.h',
     'PermissionMessageUtils.h',
     'StructuredCloneUtils.h',
     'TabChild.h',
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
 ]
 
@@ -57,16 +59,18 @@ UNIFIED_SOURCES += [
     'ContentBridgeParent.cpp',
     'ContentParent.cpp',
     'ContentProcess.cpp',
     'ContentProcessManager.cpp',
     'CrashReporterParent.cpp',
     'FilePickerParent.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
+    'NuwaChild.cpp',
+    'NuwaParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
     'ScreenManagerParent.cpp',
     'StructuredCloneUtils.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
@@ -96,16 +100,17 @@ IPDL_SOURCES += [
     'PContentBridge.ipdl',
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCrashReporter.ipdl',
     'PCycleCollectWithLogs.ipdl',
     'PDocumentRenderer.ipdl',
     'PFilePicker.ipdl',
     'PMemoryReportRequest.ipdl',
+    'PNuwa.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PScreenManager.ipdl',
     'PTabContext.ipdlh',
 ]
 
 FAIL_ON_WARNINGS = True
 
--- a/dom/ipc/nsIContentChild.h
+++ b/dom/ipc/nsIContentChild.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_nsIContentChild_h
 #define mozilla_dom_nsIContentChild_h
 
 #include "mozilla/dom/ipc/IdType.h"
 
 #include "nsISupports.h"
 #include "nsTArrayForwardDeclare.h"
 #include "mozilla/dom/CPOWManagerGetter.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 
 #define NS_ICONTENTCHILD_IID                                    \
   { 0x4eed2e73, 0x94ba, 0x48a8,                                 \
     { 0xa2, 0xd1, 0xa5, 0xed, 0x86, 0xd7, 0xbb, 0xe4 } }
 
 class nsString;
 
 namespace IPC {
--- a/dom/media/eme/DetailedPromise.cpp
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -47,14 +47,14 @@ DetailedPromise::MaybeReject(ErrorResult
 }
 
 /* static */ already_AddRefed<DetailedPromise>
 DetailedPromise::Create(nsIGlobalObject* aGlobal,
                         ErrorResult& aRv,
                         const nsACString& aName)
 {
   nsRefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName);
-  promise->CreateWrapper(aRv);
+  promise->CreateWrapper(nullptr, aRv);
   return aRv.Failed() ? nullptr : promise.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -136,16 +136,18 @@ public:
     // ...
     // DocType == "webm"
     // ...
     // 0x18538067 // Segment (must be "unknown" size or contain a value large
                   // enough to include the Segment Information and Tracks
                   // elements that follow)
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
+
+    // 0x1a45dfa3 // EBML
     if (aData->Length() >= 4 &&
         (*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
         (*aData)[3] == 0xa3) {
       return true;
     }
     return false;
   }
 
@@ -157,34 +159,44 @@ public:
     // single aData segment.
     // 0x1a45dfa3 // EBML
     // ...
     // DocType == "webm"
     // ...
     // 0x18538067 // Segment (must be "unknown" size)
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
+
+    // 0x1f43b675 // Cluster
     if (aData->Length() >= 4 &&
         (*aData)[0] == 0x1f && (*aData)[1] == 0x43 && (*aData)[2] == 0xb6 &&
         (*aData)[3] == 0x75) {
       return true;
     }
+    // 0x1c53bb6b // Cues
+    if (aData->Length() >= 4 &&
+        (*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
+        (*aData)[3] == 0x6b) {
+      return true;
+    }
     return false;
   }
 
   bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
                                   int64_t& aStart, int64_t& aEnd) override
   {
     bool initSegment = IsInitSegmentPresent(aData);
     if (initSegment) {
       mOffset = 0;
       mParser = WebMBufferedParser(0);
       mOverlappedMapping.Clear();
       mInitData = new MediaByteBuffer();
       mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
+      mCompleteMediaHeaderRange = MediaByteRange();
+      mCompleteMediaSegmentRange = MediaByteRange();
     }
 
     // XXX if it only adds new mappings, overlapped but not available
     // (e.g. overlap < 0) frames are "lost" from the reported mappings here.
     nsTArray<WebMTimeDataOffset> mapping;
     mapping.AppendElements(mOverlappedMapping);
     mOverlappedMapping.Clear();
     ReentrantMonitor dummy("dummy");
@@ -215,18 +227,31 @@ public:
       mHasInitData = true;
     }
     mOffset += aData->Length();
 
     if (mapping.IsEmpty()) {
       return false;
     }
 
-    // Exclude frames that we don't enough data to cover the end of.
     uint32_t endIdx = mapping.Length() - 1;
+
+    // Calculate media range for first media segment
+    uint32_t segmentEndIdx = endIdx;
+    while (mapping[0].mSyncOffset != mapping[segmentEndIdx].mSyncOffset) {
+      segmentEndIdx -= 1;
+    }
+    if (segmentEndIdx > 0 && mOffset >= mapping[segmentEndIdx].mEndOffset) {
+      mCompleteMediaHeaderRange = MediaByteRange(mParser.mInitEndOffset,
+                                                 mapping[0].mEndOffset);
+      mCompleteMediaSegmentRange = MediaByteRange(mParser.mInitEndOffset,
+                                                  mapping[segmentEndIdx].mEndOffset);
+    }
+
+    // Exclude frames that we don't have enough data to cover the end of.
     while (mOffset < mapping[endIdx].mEndOffset && endIdx > 0) {
       endIdx -= 1;
     }
 
     if (endIdx == 0) {
       return false;
     }
 
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -13,24 +13,20 @@
 #include "SourceBufferList.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/mozalloc.h"
 #include "nsContentTypeParser.h"
-#include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
-#include "nsIEffectiveTLDService.h"
 #include "nsIRunnable.h"
 #include "nsIScriptObjectPrincipal.h"
-#include "nsIURI.h"
-#include "nsNetCID.h"
 #include "nsPIDOMWindow.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 #include "nsServiceManagerUtils.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
@@ -337,56 +333,17 @@ MediaSource::IsTypeSupported(const Globa
           NS_ConvertUTF16toUTF8(aType).get(), rv == NS_OK ? "OK" : "[not supported]");
 #undef this // don't ever remove this line !
   return NS_SUCCEEDED(rv);
 }
 
 /* static */ bool
 MediaSource::Enabled(JSContext* cx, JSObject* aGlobal)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Don't use aGlobal across Preferences stuff, which the static
-  // analysis thinks can GC.
-  JS::Rooted<JSObject*> global(cx, aGlobal);
-
-  bool enabled = Preferences::GetBool("media.mediasource.enabled");
-  if (!enabled) {
-    return false;
-  }
-
-  // Check whether it's enabled everywhere or just whitelisted sites.
-  bool restrict = Preferences::GetBool("media.mediasource.whitelist", false);
-  if (!restrict) {
-    return true;
-  }
-
-  // We want to restrict to YouTube only.
-  // We define that as the origin being *.youtube.com.
-  // We also support *.youtube-nocookie.com
-  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(global);
-  nsCOMPtr<nsIURI> uri;
-  if (NS_FAILED(principal->GetURI(getter_AddRefs(uri))) || !uri) {
-    return false;
-  }
-
-  nsCOMPtr<nsIEffectiveTLDService> tldServ =
-    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(tldServ, false);
-
-  nsAutoCString eTLDplusOne;
-   if (NS_FAILED(tldServ->GetBaseDomain(uri, 0, eTLDplusOne))) {
-     return false;
-   }
-
-   return eTLDplusOne.EqualsLiteral("youtube.com") ||
-          eTLDplusOne.EqualsLiteral("youtube-nocookie.com") ||
-          eTLDplusOne.EqualsLiteral("netflix.com") ||
-          eTLDplusOne.EqualsLiteral("dailymotion.com") ||
-          eTLDplusOne.EqualsLiteral("dmcdn.net");
+  return Preferences::GetBool("media.mediasource.enabled");
 }
 
 bool
 MediaSource::Attach(MediaSourceDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("Attach(aDecoder=%p) owner=%p", aDecoder, aDecoder->GetOwner());
   MOZ_ASSERT(aDecoder);
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -8,16 +8,20 @@
 #include "ContainerParser.h"
 #include "MediaSourceDemuxer.h"
 #include "MediaSourceUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StateMirroring.h"
 #include "SourceBufferResource.h"
 #include "SourceBuffer.h"
 
+#ifdef MOZ_WEBM
+#include "WebMDemuxer.h"
+#endif
+
 #ifdef MOZ_FMP4
 #include "MP4Demuxer.h"
 #endif
 
 #include <limits>
 
 extern PRLogModuleInfo* GetMediaSourceLog();
 
@@ -365,16 +369,18 @@ TrackBuffersManager::CompleteResetParser
     // to discard now.
     track->mQueuedSamples.Clear();
   }
   // 6. Remove all bytes from the input buffer.
   mIncomingBuffers.Clear();
   mInputBuffer = nullptr;
   if (mCurrentInputBuffer) {
     mCurrentInputBuffer->EvictAll();
+    // The demuxer will be recreated during the next run of SegmentParserLoop.
+    // As such we don't need to notify it that data has been removed.
     mCurrentInputBuffer = new SourceBufferResource(mType);
   }
 
   // We could be left with a demuxer in an unusable state. It needs to be
   // recreated. We store in the InputBuffer an init segment which will be parsed
   // during the next Segment Parser Loop and a new demuxer will be created and
   // initialized.
   if (mFirstInitializationSegmentReceived) {
@@ -680,17 +686,17 @@ TrackBuffersManager::SegmentParserLoop()
     if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
       // 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm.
       if (!mFirstInitializationSegmentReceived) {
         RejectAppend(NS_ERROR_FAILURE, __func__);
         return;
       }
       // 2. If the input buffer does not contain a complete media segment header yet, then jump to the need more data step below.
       if (mParser->MediaHeaderRange().IsNull()) {
-        mCurrentInputBuffer->AppendData(mInputBuffer);
+        AppendDataToCurrentInputBuffer(mInputBuffer);
         mInputBuffer = nullptr;
         NeedMoreData();
         return;
       }
       // 3. If the input buffer contains one or more complete coded frames, then run the coded frame processing algorithm.
       nsRefPtr<TrackBuffersManager> self = this;
       mProcessingRequest.Begin(CodedFrameProcessing()
           ->Then(GetTaskQueue(), __func__,
@@ -752,37 +758,51 @@ TrackBuffersManager::ShutdownDemuxers()
   mInputDemuxer = nullptr;
 }
 
 void
 TrackBuffersManager::CreateDemuxerforMIMEType()
 {
   ShutdownDemuxers();
 
+#ifdef MOZ_WEBM
   if (mType.LowerCaseEqualsLiteral("video/webm") || mType.LowerCaseEqualsLiteral("audio/webm")) {
-    NS_WARNING("Waiting on WebMDemuxer");
-  // mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer);
+    mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer);
     return;
   }
+#endif
 
 #ifdef MOZ_FMP4
   if (mType.LowerCaseEqualsLiteral("video/mp4") || mType.LowerCaseEqualsLiteral("audio/mp4")) {
     mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
     return;
   }
 #endif
   NS_WARNING("Not supported (yet)");
   return;
 }
 
 void
+TrackBuffersManager::AppendDataToCurrentInputBuffer(MediaByteBuffer* aData)
+{
+  MOZ_ASSERT(mCurrentInputBuffer);
+  int64_t offset = mCurrentInputBuffer->GetLength();
+  mCurrentInputBuffer->AppendData(aData);
+  // A MediaByteBuffer has a maximum size of 2GiB.
+  mInputDemuxer->NotifyDataArrived(uint32_t(aData->Length()), offset);
+}
+
+void
 TrackBuffersManager::InitializationSegmentReceived()
 {
   MOZ_ASSERT(mParser->HasCompleteInitData());
   mCurrentInputBuffer = new SourceBufferResource(mType);
+  // The demuxer isn't initialized yet ; we don't want to notify it
+  // that data has been appended yet ; so we simply append the init segment
+  // to the resource.
   mCurrentInputBuffer->AppendData(mParser->InitData());
   uint32_t length =
     mParser->InitSegmentRange().mEnd - (mProcessedInput - mInputBuffer->Length());
   if (mInputBuffer->Length() == length) {
     mInputBuffer = nullptr;
   } else {
     mInputBuffer->RemoveElementsAt(0, length);
   }
@@ -985,16 +1005,17 @@ TrackBuffersManager::OnDemuxerInitDone(n
 
   // We now have a valid init data ; we can store it for later use.
   mInitData = mParser->InitData();
 
   // 3. Remove the initialization segment bytes from the beginning of the input buffer.
   // This step has already been done in InitializationSegmentReceived when we
   // transferred the content into mCurrentInputBuffer.
   mCurrentInputBuffer->EvictAll();
+  mInputDemuxer->NotifyDataRemoved();
   RecreateParser(true);
 
   // 4. Set append state to WAITING_FOR_SEGMENT.
   SetAppendState(AppendState::WAITING_FOR_SEGMENT);
   // 5. Jump to the loop top step above.
   ScheduleSegmentParserLoop();
 }
 
@@ -1009,35 +1030,32 @@ TrackBuffersManager::OnDemuxerInitFailed
 
 nsRefPtr<TrackBuffersManager::CodedFrameProcessingPromise>
 TrackBuffersManager::CodedFrameProcessing()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mProcessingPromise.IsEmpty());
   nsRefPtr<CodedFrameProcessingPromise> p = mProcessingPromise.Ensure(__func__);
 
-  int64_t offset = mCurrentInputBuffer->GetLength();
   MediaByteRange mediaRange = mParser->MediaSegmentRange();
-  uint32_t length;
   if (mediaRange.IsNull()) {
-    length = mInputBuffer->Length();
-    mCurrentInputBuffer->AppendData(mInputBuffer);
+    AppendDataToCurrentInputBuffer(mInputBuffer);
     mInputBuffer = nullptr;
   } else {
     // The mediaRange is offset by the init segment position previously added.
-    length = mediaRange.mEnd - (mProcessedInput - mInputBuffer->Length());
+    uint32_t length =
+      mediaRange.mEnd - (mProcessedInput - mInputBuffer->Length());
     nsRefPtr<MediaByteBuffer> segment = new MediaByteBuffer;
     MOZ_ASSERT(mInputBuffer->Length() >= length);
     if (!segment->AppendElements(mInputBuffer->Elements(), length, fallible)) {
       return CodedFrameProcessingPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
     }
-    mCurrentInputBuffer->AppendData(segment);
+    AppendDataToCurrentInputBuffer(segment);
     mInputBuffer->RemoveElementsAt(0, length);
   }
-  mInputDemuxer->NotifyDataArrived(length, offset);
 
   DoDemuxVideo();
 
   return p;
 }
 
 void
 TrackBuffersManager::OnDemuxFailed(TrackType aTrack,
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -155,16 +155,17 @@ private:
   // Those are used to parse the incoming input buffer.
 
   // Recreate the ContainerParser and if aReuseInitData is true then
   // feed it with the previous init segment found.
   void RecreateParser(bool aReuseInitData);
   nsAutoPtr<ContainerParser> mParser;
 
   // Demuxer objects and methods.
+  void AppendDataToCurrentInputBuffer(MediaByteBuffer* aData);
   nsRefPtr<MediaByteBuffer> mInitData;
   nsRefPtr<SourceBufferResource> mCurrentInputBuffer;
   nsRefPtr<MediaDataDemuxer> mInputDemuxer;
   // Length already processed in current media segment.
   uint32_t mProcessedInput;
 
   void OnDemuxerInitDone(nsresult);
   void OnDemuxerInitFailed(DemuxerFailureReason aFailure);
--- a/dom/media/mediasource/test/crashtests/1005366.html
+++ b/dom/media/mediasource/test/crashtests/1005366.html
@@ -1,17 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
 <script>
 
 /*
 user_pref("media.mediasource.enabled", true);
-user_pref("media.mediasource.whitelist", false);
 */
 
 function boom()
 {
     var source = new window.MediaSource();
     var videoElement = document.createElementNS('http://www.w3.org/1999/xhtml', 'video');
     videoElement.src = URL.createObjectURL(source);
 
--- a/dom/media/mediasource/test/crashtests/1059035.html
+++ b/dom/media/mediasource/test/crashtests/1059035.html
@@ -1,16 +1,15 @@
 <!DOCTYPE html>
 <html>
 <head>
 <script>
 
 /*
 user_pref("media.mediasource.enabled", true);
-user_pref("media.mediasource.whitelist", false);
 */
 
 function boom()
 {
     var mediaSource = new MediaSource();
     var htmlAudio = document.createElement("audio");
     htmlAudio.src = URL.createObjectURL(mediaSource);
 
--- a/dom/media/mediasource/test/crashtests/crashtests.list
+++ b/dom/media/mediasource/test/crashtests/crashtests.list
@@ -1,4 +1,4 @@
-test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.whitelist,false) load 926665.html
-test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.whitelist,false) load 931388.html
-test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.whitelist,false) load 1005366.html
-test-pref(media.mediasource.enabled,true) test-pref(media.mediasource.whitelist,false) load 1059035.html
+test-pref(media.mediasource.enabled,true) load 926665.html
+test-pref(media.mediasource.enabled,true) load 931388.html
+test-pref(media.mediasource.enabled,true) load 1005366.html
+test-pref(media.mediasource.enabled,true) load 1059035.html
--- a/dom/media/mediasource/test/mediasource.js
+++ b/dom/media/mediasource/test/mediasource.js
@@ -14,17 +14,16 @@ function runWithMSE(testFunction) {
     });
 
     testFunction(ms, el);
   }
 
   addLoadEvent(function () {
     SpecialPowers.pushPrefEnv({"set": [
       [ "media.mediasource.enabled", true ],
-      [ "media.mediasource.whitelist", false ],
     ]},
                               bootstrapTest);
   });
 }
 
 function fetchWithXHR(uri, onLoadFunction) {
   var p = new Promise(function(resolve, reject) {
     var xhr = new XMLHttpRequest();
--- a/dom/media/mediasource/test/test_MediaSource_disabled.html
+++ b/dom/media/mediasource/test/test_MediaSource_disabled.html
@@ -17,17 +17,16 @@ function test() {
   SimpleTest.doesThrow(() => new MediaSource,
                        "MediaSource should be hidden behind a pref");
   SimpleTest.finish();
 }
 
 SpecialPowers.pushPrefEnv({"set":
     [
       ["media.mediasource.enabled", false],
-      ["media.mediasource.whitelist", false],
     ]
   },
                           test);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -130,17 +130,17 @@ VPXDecoder::DoDecodeFrame(MediaRawData* 
     info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight);
     nsRefPtr<VideoData> v = VideoData::Create(info,
                                               mImageContainer,
                                               aSample->mOffset,
                                               aSample->mTime,
                                               aSample->mDuration,
                                               b,
                                               aSample->mKeyframe,
-                                              -1,
+                                              aSample->mTimecode,
                                               picture);
 
     if (!v) {
       LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
           img->d_w, img->d_h, mDisplayWidth, mDisplayHeight,
           picture.width, picture.height);
       return -1;
     }
--- a/dom/media/test/eme.js
+++ b/dom/media/test/eme.js
@@ -394,17 +394,16 @@ function SetupEME(test, token, params)
     }
   });
   return v;
 }
 
 function SetupEMEPref(callback) {
   var prefs = [
     [ "media.mediasource.enabled", true ],
-    [ "media.mediasource.whitelist", false ],
     [ "media.fragmented-mp4.exposed", true ],
     [ "media.eme.apiVisible", true ],
   ];
 
   if (/Linux/.test(manifestNavigator().userAgent)) {
     prefs.push([ "media.fragmented-mp4.ffmpeg.enabled", true ]);
   } else if (SpecialPowers.Services.appinfo.name == "B2G" ||
              !manifestVideo().canPlayType("video/mp4")) {
--- a/dom/media/test/test_VideoPlaybackQuality.html
+++ b/dom/media/test/test_VideoPlaybackQuality.html
@@ -49,16 +49,15 @@ function test() {
     });
   });
 }
 
 addLoadEvent(function() {
   SpecialPowers.pushPrefEnv({"set":
     [
       ["media.mediasource.enabled", true],
-      ["media.mediasource.whitelist", false],
     ]
   }, test);
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/test/test_VideoPlaybackQuality_disabled.html
+++ b/dom/media/test/test_VideoPlaybackQuality_disabled.html
@@ -23,16 +23,15 @@ function test() {
   ok(accessThrows, "getVideoPlaybackQuality should be hidden behind a pref");
   SimpleTest.finish();
 }
 
 addLoadEvent(function() {
   SpecialPowers.pushPrefEnv({"set":
     [
      ["media.mediasource.enabled", false],
-     ["media.mediasource.whitelist", false],
     ]
   }, test);
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -1,16 +1,16 @@
 default-preferences  pref(media.peerconnection.enabled,true) pref(media.navigator.permission.disabled,true)
 
 load 780790.html
 load 791270.html
 load 791278.html
-skip-if(Android||B2G) load 791330.html # bug 909925
+load 791330.html
 load 799419.html
 load 802982.html
 load 812785.html
 load 834100.html
 load 836349.html
 load 837324.html
-skip-if(Android||B2G) load 855796.html # bug 909925
+load 855796.html
 load 860143.html
 load 861958.html
-skip-if(Android||B2G) load 863929.html # bug 909925
+load 863929.html
--- a/dom/presentation/PresentationDeviceManager.cpp
+++ b/dom/presentation/PresentationDeviceManager.cpp
@@ -31,16 +31,38 @@ PresentationDeviceManager::PresentationD
 
 PresentationDeviceManager::~PresentationDeviceManager()
 {
   UnloadDeviceProviders();
   mDevices.Clear();
 }
 
 void
+PresentationDeviceManager::Init()
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  }
+
+  LoadDeviceProviders();
+}
+
+void
+PresentationDeviceManager::Shutdown()
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+  }
+
+  UnloadDeviceProviders();
+}
+
+void
 PresentationDeviceManager::LoadDeviceProviders()
 {
   MOZ_ASSERT(mProviders.IsEmpty());
 
   nsCategoryCache<nsIPresentationDeviceProvider> providerCache(PRESENTATION_DEVICE_PROVIDER_CATEGORY);
   providerCache.GetEntries(mProviders);
 
   for (uint32_t i = 0; i < mProviders.Length(); ++i) {
@@ -218,16 +240,18 @@ PresentationDeviceManager::OnSessionRequ
 
 // nsIObserver
 NS_IMETHODIMP
 PresentationDeviceManager::Observe(nsISupports *aSubject,
                                    const char *aTopic,
                                    const char16_t *aData)
 {
   if (!strcmp(aTopic, "profile-after-change")) {
-    LoadDeviceProviders();
+    Init();
+  } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    Shutdown();
   }
 
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/PresentationDeviceManager.h
+++ b/dom/presentation/PresentationDeviceManager.h
@@ -30,16 +30,20 @@ public:
   NS_DECL_NSIPRESENTATIONDEVICEEVENTLISTENER
   NS_DECL_NSIOBSERVER
 
   PresentationDeviceManager();
 
 private:
   virtual ~PresentationDeviceManager();
 
+  void Init();
+
+  void Shutdown();
+
   void LoadDeviceProviders();
 
   void UnloadDeviceProviders();
 
   void NotifyDeviceChange(nsIPresentationDevice* aDevice,
                           const char16_t* aType);
 
   nsCOMArray<nsIPresentationDeviceProvider> mProviders;
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -1,37 +1,54 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MulticastDNSDeviceProvider.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
 #include "nsAutoPtr.h"
 #include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
 #include "nsIPresentationDevice.h"
 #include "nsServiceManagerUtils.h"
 
+#define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
+#define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
+#define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
+
+#define TCP_PRESENTATION_SERVER_CONTACT_ID \
+  "@mozilla.org/presentation-device/tcp-presentation-server;1"
+
+#define SERVICE_TYPE "_mozilla_papi._tcp."
+
 inline static PRLogModuleInfo*
 GetProviderLog()
 {
   static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider");
   return log;
 }
 #undef LOG_I
 #define LOG_I(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 #undef LOG_E
 #define LOG_E(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Error, (__VA_ARGS__))
 
-#define SERVICE_TYPE "_mozilla_papi._tcp."
-
 namespace mozilla {
 namespace dom {
 namespace presentation {
 
+static const char* kObservedPrefs[] = {
+  PREF_PRESENTATION_DISCOVERY,
+  PREF_PRESENTATION_DISCOVERABLE,
+  PREF_PRESENTATION_DEVICE_NAME,
+  nullptr
+};
+
 /**
  * This wrapper is used to break circular-reference problem.
  */
 class DNSServiceWrappedListener final
   : public nsIDNSServiceDiscoveryListener
   , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
   , public nsITCPPresentationServerListener
@@ -63,17 +80,18 @@ NS_IMPL_ISUPPORTS(DNSServiceWrappedListe
                   nsIDNSServiceResolveListener,
                   nsITCPPresentationServerListener)
 
 NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider,
                   nsIPresentationDeviceProvider,
                   nsIDNSServiceDiscoveryListener,
                   nsIDNSRegistrationListener,
                   nsIDNSServiceResolveListener,
-                  nsITCPPresentationServerListener)
+                  nsITCPPresentationServerListener,
+                  nsIObserver)
 
 MulticastDNSDeviceProvider::~MulticastDNSDeviceProvider()
 {
   Uninit();
 }
 
 nsresult
 MulticastDNSDeviceProvider::Init()
@@ -92,94 +110,134 @@ MulticastDNSDeviceProvider::Init()
   mWrappedListener = new DNSServiceWrappedListener();
   if (NS_WARN_IF(!mWrappedListener)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (NS_WARN_IF(NS_FAILED(rv = mWrappedListener->SetListener(this)))) {
     return rv;
   }
 
-  mPresentationServer = do_CreateInstance("@mozilla.org/presentation-device/tcp-presentation-server;1", &rv);
+  mPresentationServer = do_CreateInstance(TCP_PRESENTATION_SERVER_CONTACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(mPresentationServer->SetListener(mWrappedListener)))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
+
+  Preferences::AddStrongObservers(this, kObservedPrefs);
+
+  mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY);
+  mDiscoverable = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE);
+  mServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
+
+  if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
     return rv;
   }
 
-  uint16_t port = 0;
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-    return rv;
-  }
-
-  if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
+  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
     return rv;
   }
 
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
 MulticastDNSDeviceProvider::Uninit()
 {
   if (!mInitialized) {
     return NS_OK;
   }
 
-  if (mPresentationServer) {
-    mPresentationServer->Close();
-    mPresentationServer = nullptr;
-  }
+  Preferences::RemoveObservers(this, kObservedPrefs);
 
-  if (mDiscoveryRequest) {
-    mDiscoveryRequest->Cancel(NS_OK);
-    mDiscoveryRequest = nullptr;
-  }
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
-    mRegisterRequest = nullptr;
-  }
+  StopDiscovery(NS_OK);
+  UnregisterService(NS_OK);
+
   mMulticastDNS = nullptr;
 
   if (mWrappedListener) {
     mWrappedListener->SetListener(nullptr);
     mWrappedListener = nullptr;
   }
 
   mInitialized = false;
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RegisterService(uint32_t aPort)
+MulticastDNSDeviceProvider::RegisterService()
 {
-  LOG_I("RegisterService: %d", aPort);
+  LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable);
+
+  if (!mDiscoverable) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!mRegisterRequest);
 
   nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) {
+    return rv;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
+    return rv;
+  }
+  uint16_t servicePort;
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) {
+    return rv;
+  }
 
-  nsCOMPtr<nsIDNSServiceInfo> serviceInfo = do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
+  /**
+   * Register the presentation control channel server as an mDNS service.
+   */
+  nsCOMPtr<nsIDNSServiceInfo> serviceInfo =
+    do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(NS_LITERAL_CSTRING(SERVICE_TYPE))))) {
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(
+      NS_LITERAL_CSTRING(SERVICE_TYPE))))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(aPort)))) {
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceName(mServiceName)))) {
+    return rv;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(servicePort)))) {
     return rv;
   }
 
+  return mMulticastDNS->RegisterService(serviceInfo,
+                                        mWrappedListener,
+                                        getter_AddRefs(mRegisterRequest));
+}
+
+nsresult
+MulticastDNSDeviceProvider::UnregisterService(nsresult aReason)
+{
   if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
+    mRegisterRequest->Cancel(aReason);
     mRegisterRequest = nullptr;
   }
-  return mMulticastDNS->RegisterService(serviceInfo, mWrappedListener, getter_AddRefs(mRegisterRequest));
+
+  if (mPresentationServer) {
+    mPresentationServer->SetListener(nullptr);
+    mPresentationServer->Close();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::StopDiscovery(nsresult aReason)
+{
+  if (mDiscoveryRequest) {
+    mDiscoveryRequest->Cancel(aReason);
+    mDiscoveryRequest = nullptr;
+  }
+
+  return NS_OK;
 }
 
 // nsIPresentationDeviceProvider
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
 {
   if (NS_WARN_IF(!aListener)) {
     return NS_ERROR_INVALID_POINTER;
@@ -208,26 +266,27 @@ MulticastDNSDeviceProvider::SetListener(
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::ForceDiscovery()
 {
-  LOG_I("ForceDiscovery");
-  MOZ_ASSERT(mInitialized);
+  LOG_I("ForceDiscovery (%d)", mDiscoveryEnabled);
+
+  if (!mDiscoveryEnabled) {
+    return NS_OK;
+  }
+
   MOZ_ASSERT(mMulticastDNS);
 
+  StopDiscovery(NS_OK);
+
   nsresult rv;
-
-  if (mDiscoveryRequest) {
-    mDiscoveryRequest->Cancel(NS_OK);
-    mDiscoveryRequest = nullptr;
-  }
   if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->StartDiscovery(
       NS_LITERAL_CSTRING(SERVICE_TYPE),
       mWrappedListener,
       getter_AddRefs(mDiscoveryRequest))))) {
     return rv;
   }
 
   return NS_OK;
@@ -272,17 +331,18 @@ MulticastDNSDeviceProvider::OnServiceFou
   nsCOMPtr<nsIPresentationDevice> device;
   if (NS_SUCCEEDED(mPresentationServer->GetTCPDevice(serviceName,
                                                      getter_AddRefs(device)))) {
     LOG_I("device exists");
     return NS_OK;
   }
 
   if (mMulticastDNS) {
-    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(aServiceInfo, mWrappedListener)))) {
+    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(
+        aServiceInfo, mWrappedListener)))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -297,40 +357,43 @@ MulticastDNSDeviceProvider::OnServiceLos
   nsAutoCString serviceName;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(serviceName)))) {
     return rv;
   }
 
   LOG_I("OnServiceLost: %s", serviceName.get());
 
   nsCOMPtr<nsIPresentationDevice> device;
-  if (NS_FAILED(mPresentationServer->GetTCPDevice(serviceName, getter_AddRefs(device)))) {
+  if (NS_FAILED(mPresentationServer->GetTCPDevice(serviceName,
+                                                  getter_AddRefs(device)))) {
     return NS_OK; // ignore non-existing device;
   }
 
   NS_WARN_IF(NS_FAILED(mPresentationServer->RemoveTCPDevice(serviceName)));
 
   nsCOMPtr<nsIPresentationDeviceListener> listener;
   GetListener(getter_AddRefs(listener));
   if (listener) {
     listener->RemoveDevice(device);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType,
+                                                   int32_t aErrorCode)
 {
   LOG_E("OnStartDiscoveryFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType,
+                                                  int32_t aErrorCode)
 {
   LOG_E("OnStopDiscoveryFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsIDNSRegistrationListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
@@ -358,38 +421,37 @@ MulticastDNSDeviceProvider::OnServiceReg
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
 {
   LOG_I("OnServiceUnregistered");
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo,
+                                                 int32_t aErrorCode)
 {
   LOG_E("OnRegistrationFailed: %d", aErrorCode);
 
+  mRegisterRequest = nullptr;
+
   nsresult rv;
 
   if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) {
-    uint16_t port = 0;
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-      return rv;
-    }
-
-    if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
+    if (NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo,
+                                                   int32_t aErrorCode)
 {
   LOG_E("OnUnregistrationFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsIDNSServiceResolveListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
@@ -446,48 +508,108 @@ MulticastDNSDeviceProvider::OnServiceRes
   if (listener) {
     listener->AddDevice(device);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo,
+                                            int32_t aErrorCode)
 {
   LOG_E("OnResolveFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsITCPPresentationServerListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnClose(nsresult aReason)
 {
   LOG_I("OnClose: %x", aReason);
 
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(aReason);
-    mRegisterRequest = nullptr;
-  }
+  UnregisterService(aReason);
 
   nsresult rv;
 
-  if (NS_FAILED(aReason)) {
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
-      return rv;
+  if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
+    return rv;
+  }
+
+  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::Observe(nsISupports* aSubject,
+                                    const char* aTopic,
+                                    const char16_t* aData)
+{
+  NS_ConvertUTF16toUTF8 data(aData);
+  LOG_I("Observe: topic = %s, data = %s", aTopic, data.get());
+
+  if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+    if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERY)) {
+      OnDiscoveryChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERY));
+    } else if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERABLE)) {
+      OnDiscoverableChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE));
+    } else if (data.EqualsLiteral(PREF_PRESENTATION_DEVICE_NAME)) {
+      OnServiceNameChanged(Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME));
     }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnDiscoveryChanged(bool aEnabled)
+{
+  LOG_I("DiscoveryEnabled = %d\n", aEnabled);
 
-    uint16_t port = 0;
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-      return rv;
-    }
+  mDiscoveryEnabled = aEnabled;
+
+  if (mDiscoveryEnabled) {
+    return ForceDiscovery();
+  }
+
+  return StopDiscovery(NS_OK);
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnDiscoverableChanged(bool aEnabled)
+{
+  LOG_I("Discoverable = %d\n", aEnabled);
+
+  mDiscoverable = aEnabled;
 
-    if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
-      return rv;
-    }
+  if (mDiscoverable) {
+    return RegisterService();
+  }
+
+  return UnregisterService(NS_OK);
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnServiceNameChanged(const nsCString& aServiceName)
+{
+  LOG_I("serviceName = %s\n", aServiceName.get());
+
+  mServiceName = aServiceName;
+
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(NS_OK)))) {
+    return rv;
+  }
+
+  if (mDiscoverable) {
+    return RegisterService();
   }
 
   return NS_OK;
 }
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
 #define mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
 
 #include "nsCOMPtr.h"
 #include "nsICancelable.h"
 #include "nsIDNSServiceDiscovery.h"
+#include "nsIObserver.h"
 #include "nsIPresentationDeviceProvider.h"
 #include "nsITCPPresentationServer.h"
 #include "mozilla/nsRefPtr.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 
 namespace mozilla {
 namespace dom {
@@ -23,42 +24,53 @@ class DNSServiceWrappedListener;
 class MulticastDNSService;
 
 class MulticastDNSDeviceProvider final
   : public nsIPresentationDeviceProvider
   , public nsIDNSServiceDiscoveryListener
   , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
   , public nsITCPPresentationServerListener
+  , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEPROVIDER
   NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
   NS_DECL_NSIDNSREGISTRATIONLISTENER
   NS_DECL_NSIDNSSERVICERESOLVELISTENER
   NS_DECL_NSITCPPRESENTATIONSERVERLISTENER
+  NS_DECL_NSIOBSERVER
 
   explicit MulticastDNSDeviceProvider() = default;
   nsresult Init();
   nsresult Uninit();
 
 private:
   virtual ~MulticastDNSDeviceProvider();
-  nsresult RegisterService(uint32_t aPort);
+  nsresult RegisterService();
+  nsresult UnregisterService(nsresult aReason);
+  nsresult StopDiscovery(nsresult aReason);
+
+  nsresult OnDiscoveryChanged(bool aEnabled);
+  nsresult OnDiscoverableChanged(bool aEnabled);
+  nsresult OnServiceNameChanged(const nsCString& aServiceName);
 
   bool mInitialized = false;
   nsWeakPtr mDeviceListener;
   nsCOMPtr<nsITCPPresentationServer> mPresentationServer;
   nsCOMPtr<nsIDNSServiceDiscovery> mMulticastDNS;
   nsRefPtr<DNSServiceWrappedListener> mWrappedListener;
 
   nsCOMPtr<nsICancelable> mDiscoveryRequest;
   nsCOMPtr<nsICancelable> mRegisterRequest;
 
+  bool mDiscoveryEnabled = false;
+  bool mDiscoverable = false;
+  nsCString mServiceName;
   nsCString mRegisteredName;
 };
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
--- a/dom/presentation/provider/PresentationDeviceProviderModule.cpp
+++ b/dom/presentation/provider/PresentationDeviceProviderModule.cpp
@@ -23,17 +23,17 @@ static const mozilla::Module::CIDEntry k
 };
 
 static const mozilla::Module::ContractIDEntry kPresentationDeviceProviderContracts[] = {
   { MULTICAST_DNS_PROVIDER_CONTRACT_ID, &kMULTICAST_DNS_PROVIDER_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kPresentationDeviceProviderCategories[] = {
-#if defined(MOZ_WIDGET_ANDROID) // || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
+#if defined(MOZ_WIDGET_ANDROID) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
   { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "MulticastDNSDeviceProvider", MULTICAST_DNS_PROVIDER_CONTRACT_ID },
 #endif
   { nullptr }
 };
 
 static const mozilla::Module kPresentationDeviceProviderModule = {
   mozilla::Module::kVersion,
   kPresentationDeviceProviderCIDs,
--- a/dom/presentation/provider/TCPPresentationServer.js
+++ b/dom/presentation/provider/TCPPresentationServer.js
@@ -21,17 +21,17 @@ function TCPDeviceInfo(aHost, aPort, aId
   this.name = aName;
   this.type = aType;
 }
 
 function TCPPresentationServer() {
   this._id = null;
   this._port = 0;
   this._serverSocket = null;
-  this._devices = null;
+  this._devices = new Map(); // id -> device
 }
 
 TCPPresentationServer.prototype = {
   /**
    * If a user agent connects to this server, we create a control channel but
    * hand it to |TCPDevice.listener| when the initial information exchange
    * finishes. Therefore, we hold the control channels in this period.
    */
@@ -73,17 +73,16 @@ TCPPresentationServer.prototype = {
     }
 
     /**
      * The setter may trigger |_serverSocket.asyncListen| if the |id| setting
      * successes.
      */
     this.id = aId;
     this._port = this._serverSocket.port;
-    this._devices = new Map(); // id -> device
   },
 
   get id() {
     return this._id;
   },
 
   set id(aId) {
     if (!aId || aId.length == 0 || aId === this._id) {
@@ -246,17 +245,16 @@ TCPPresentationServer.prototype = {
     if (this._serverSocket) {
       DEBUG && log("TCPPresentationServer - close server socket");
       this._serverSocket.close();
       this._serverSocket = null;
     }
     this._id = null;
     this._port = 0;
     this._devices && this._devices.clear();
-    this._devices = null;
   },
 
   classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"),
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener,
                                           Ci.nsITCPPresentationServer]),
 };
 
 function ChannelDescription(aInit) {
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -1,23 +1,27 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const INFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
 const PROVIDER_CONTRACT_ID = "@mozilla.org/presentation-device/multicastdns-provider;1";
 const SD_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
 const UUID_CONTRACT_ID = "@mozilla.org/uuid-generator;1";
 
+const PREF_DISCOVERY = "dom.presentation.discovery.enabled";
+const PREF_DISCOVERABLE = "dom.presentation.discoverable";
+
 let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
 
 function MockFactory(aClass) {
   this._cls = aClass;
 }
 MockFactory.prototype = {
   createInstance: function(aOuter, aIID) {
     if (aOuter) {
@@ -53,31 +57,37 @@ function ContractHook(aContractID, aClas
 ContractHook.prototype = {
   hookedMap: new Map(), // remember only the most original factory.
 
   init: function() {
     this.reset();
 
     let oldContract = this.unregister();
     this.hookedMap.get(this._contractID).push(oldContract);
-    registrar.registerFactory(this.classID, "", this._contractID, this._newFactory);
+    registrar.registerFactory(this.classID,
+                              "",
+                              this._contractID,
+                              this._newFactory);
 
     do_register_cleanup(() => { this.cleanup.apply(this); });
   },
 
   reset: function() {},
 
   cleanup: function() {
     this.reset();
 
     this.unregister();
     let prevContract = this.hookedMap.get(this._contractID).pop();
 
     if (prevContract.factory) {
-      registrar.registerFactory(prevContract.classID, "", this._contractID, prevContract.factory);
+      registrar.registerFactory(prevContract.classID,
+                                "",
+                                this._contractID,
+                                prevContract.factory);
     }
   },
 
   unregister: function() {
     var classID, factory;
 
     try {
       classID = registrar.contractIDToCID(this._contractID);
@@ -164,92 +174,375 @@ function createDevice(host, port, servic
   device.serviceName = serviceName || "";
   device.serviceType = serviceType || "";
   device.domainName = domainName || "";
   device.attributes = attributes || null;
   return device;
 }
 
 function registerService() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {},
+    registerService: function(serviceInfo, listener) {
+      this.serviceRegistered++;
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {
+          this.serviceUnregistered++;
+        }.bind(this)
+      };
+    },
+    resolveService: function(serviceInfo, listener) {},
+    serviceRegistered: 0,
+    serviceUnregistered: 0
+  };
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Register
+  provider.listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Unregister
+  provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+
+  run_next_test();
+}
+
+function noRegisterService() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {},
+    registerService: function(serviceInfo, listener) {
+      Assert.ok(false, "should not register service if not discoverable");
+    },
+    resolveService: function(serviceInfo, listener) {},
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  // Try register
+  provider.listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  provider.listener = null;
+
+  run_next_test();
+}
+
+function registerServiceDynamically() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {},
     registerService: function(serviceInfo, listener) {
       this.serviceRegistered++;
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           this.serviceUnregistered++;
         }.bind(this)
-      }
+      };
     },
     resolveService: function(serviceInfo, listener) {},
     serviceRegistered: 0,
     serviceUnregistered: 0
   };
   let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
 
   Assert.equal(mockObj.serviceRegistered, 0);
-  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   Assert.equal(mockObj.serviceRegistered, 0);
+
+  // Try Register
   provider.listener = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, Ci.nsISupportsWeakReference]),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
     addDevice: function(device) {},
     removeDevice: function(device) {},
     updateDevice: function(device) {},
   };
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Enable registration
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
   Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
 
-  Assert.equal(mockObj.serviceUnregistered, 0);
+  // Disable registration
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+
+  // Try unregister
   provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 1);
   Assert.equal(mockObj.serviceUnregistered, 1);
 
   run_next_test();
 }
 
 function addDevice() {
-  let mockDevice = createDevice("device.local", 12345, "service.name", "_mozilla_papi._tcp");
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
-      listener.onServiceFound(createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType));
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
-      }
+      };
     },
     registerService: function(serviceInfo, listener) {},
     resolveService: function(serviceInfo, listener) {
       Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
       Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
-      listener.onServiceResolved(createDevice(mockDevice.host, mockDevice.port, mockDevice.serviceName, mockDevice.serviceType));
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) { this.devices.push(device); },
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+    devices: []
+  };
+  Assert.equal(listener.devices.length, 0);
+
+  // Start discovery
+  provider.listener = listener;
+  Assert.equal(listener.devices.length, 1);
+
+  // Force discovery again
+  provider.forceDiscovery();
+  Assert.equal(listener.devices.length, 1);
+
+  provider.listener = null;
+  Assert.equal(listener.devices.length, 1);
+
+  run_next_test();
+}
+
+function noAddDevice() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+
+  let mockDevice = createDevice("device.local", 12345, "service.name", "_mozilla_papi._tcp");
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      Assert.ok(false, "shouldn't perform any device discovery");
+    },
+    registerService: function(serviceInfo, listener) {},
+    resolveService: function(serviceInfo, listener) {
     }
   };
   let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
 
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, Ci.nsISupportsWeakReference]),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  provider.listener = listener;
+  provider.forceDiscovery();
+  provider.listener = null;
+
+  run_next_test();
+}
+
+function addDeviceDynamically() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {},
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
     addDevice: function(device) { this.devices.push(device); },
     removeDevice: function(device) {},
     updateDevice: function(device) {},
     devices: []
   };
   provider.listener = listener;
+  Assert.equal(listener.devices.length, 0);
 
-  Assert.equal(listener.devices.length, 0);
+  // Enable discovery
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+  Assert.equal(listener.devices.length, 1);
+
+  // Try discovery again
   provider.forceDiscovery();
   Assert.equal(listener.devices.length, 1);
 
   provider.listener = null;
 
   run_next_test();
 }
 
+function serverClosed() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {
+      this.serviceRegistered++;
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {
+          this.serviceUnregistered++;
+        }.bind(this)
+      };
+    },
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    },
+    serviceRegistered: 0,
+    serviceUnregistered: 0
+  };
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Register
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) { this.devices.push(device); },
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+    devices: []
+  };
+  Assert.equal(listener.devices.length, 0);
+
+  provider.listener = listener;
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+  Assert.equal(listener.devices.length, 1);
+
+  let serverListener = provider.QueryInterface(Ci.nsITCPPresentationServerListener);
+  serverListener.onClose(Cr.NS_ERROR_UNEXPECTED);
+
+  Assert.equal(mockObj.serviceRegistered, 2);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+  Assert.equal(listener.devices.length, 2);
+
+  // Unregister
+  provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 2);
+  Assert.equal(mockObj.serviceUnregistered, 2);
+  Assert.equal(listener.devices.length, 2);
+
+  run_next_test();
+}
+
 function run_test() {
   let infoHook = new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo);
 
+  do_register_cleanup(() => {
+    Services.prefs.clearUserPref(PREF_DISCOVERY);
+    Services.prefs.clearUserPref(PREF_DISCOVERABLE);
+  });
+
   add_test(registerService);
+  add_test(noRegisterService);
+  add_test(registerServiceDynamically);
   add_test(addDevice);
+  add_test(noAddDevice);
+  add_test(addDeviceDynamically);
+  add_test(serverClosed);
 
   run_next_test();
 }
--- a/dom/promise/AbortablePromise.cpp
+++ b/dom/promise/AbortablePromise.cpp
@@ -45,42 +45,43 @@ AbortablePromise::~AbortablePromise()
 }
 
 /* static */ already_AddRefed<AbortablePromise>
 AbortablePromise::Create(nsIGlobalObject* aGlobal,
                          PromiseNativeAbortCallback& aAbortCallback,
                          ErrorResult& aRv)
 {
   nsRefPtr<AbortablePromise> p = new AbortablePromise(aGlobal, aAbortCallback);
-  p->CreateWrapper(aRv);
+  p->CreateWrapper(nullptr, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   return p.forget();
 }
 
 JSObject*
 AbortablePromise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return MozAbortablePromiseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 /* static */ already_AddRefed<AbortablePromise>
 AbortablePromise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
-                              AbortCallback& aAbortCallback, ErrorResult& aRv)
+                              AbortCallback& aAbortCallback, ErrorResult& aRv,
+                              JS::Handle<JSObject*> aDesiredProto)
 {
   nsCOMPtr<nsIGlobalObject> global;
   global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<AbortablePromise> promise = new AbortablePromise(global);
-  promise->CreateWrapper(aRv);
+  promise->CreateWrapper(aDesiredProto, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   promise->CallInitFunction(aGlobal, aInit, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
--- a/dom/promise/AbortablePromise.h
+++ b/dom/promise/AbortablePromise.h
@@ -43,17 +43,18 @@ protected:
   virtual ~AbortablePromise();
 
 public:
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<AbortablePromise>
   Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
-              AbortCallback& aAbortCallback, ErrorResult& aRv);
+              AbortCallback& aAbortCallback, ErrorResult& aRv,
+              JS::Handle<JSObject*> aDesiredProto);
 
   void Abort();
 
 private:
   void DoAbort();
 
   // The callback functions to abort the promise.
   CallbackObjectHolder<AbortCallback,
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -413,38 +413,39 @@ Promise::~Promise()
 
 JSObject*
 Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PromiseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
-Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
+Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
+                JS::Handle<JSObject*> aDesiredProto)
 {
   nsRefPtr<Promise> p = new Promise(aGlobal);
-  p->CreateWrapper(aRv);
+  p->CreateWrapper(aDesiredProto, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
   return p.forget();
 }
 
 void
-Promise::CreateWrapper(ErrorResult& aRv)
+Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
 {
   AutoJSAPI jsapi;
   if (!jsapi.Init(mGlobal)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
   JSContext* cx = jsapi.cx();
 
   JS::Rooted<JS::Value> wrapper(cx);
-  if (!GetOrCreateDOMReflector(cx, this, &wrapper)) {
+  if (!GetOrCreateDOMReflector(cx, this, &wrapper, aDesiredProto)) {
     JS_ClearPendingException(cx);
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   dom::PreserveWrapper(this);
 
   // Now grab our allocation stack
@@ -634,27 +635,27 @@ Promise::CreateThenableFunction(JSContex
   }
 
   js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
 
   return obj;
 }
 
 /* static */ already_AddRefed<Promise>
-Promise::Constructor(const GlobalObject& aGlobal,
-                     PromiseInit& aInit, ErrorResult& aRv)
+Promise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+                     ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto)
 {
   nsCOMPtr<nsIGlobalObject> global;
   global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  nsRefPtr<Promise> promise = Create(global, aRv);
+  nsRefPtr<Promise> promise = Create(global, aRv, aDesiredProto);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   promise->CallInitFunction(aGlobal, aInit, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -95,17 +95,19 @@ public:
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(Promise)
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise)
 
   // Promise creation tries to create a JS reflector for the Promise, so is
   // fallible.  Furthermore, we don't want to do JS-wrapping on a 0-refcount
   // object, so we addref before doing that and return the addrefed pointer
   // here.
   static already_AddRefed<Promise>
-  Create(nsIGlobalObject* aGlobal, ErrorResult& aRv);
+  Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
+         // Passing null for aDesiredProto will use Promise.prototype.
+         JS::Handle<JSObject*> aDesiredProto = nullptr);
 
   typedef void (Promise::*MaybeFunc)(JSContext* aCx,
                                      JS::Handle<JS::Value> aValue);
 
   void MaybeResolve(JSContext* aCx,
                     JS::Handle<JS::Value> aValue);
   void MaybeReject(JSContext* aCx,
                    JS::Handle<JS::Value> aValue);
@@ -154,17 +156,17 @@ public:
     return mGlobal;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<Promise>
   Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
-              ErrorResult& aRv);
+              ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto);
 
   static already_AddRefed<Promise>
   Resolve(const GlobalObject& aGlobal,
           JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
           JS::Handle<JS::Value> aValue, ErrorResult& aRv);
@@ -211,18 +213,19 @@ public:
 
 protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
   virtual ~Promise();
 
-  // Do JS-wrapping after Promise creation.
-  void CreateWrapper(ErrorResult& aRv);
+  // Do JS-wrapping after Promise creation.  Passing null for aDesiredProto will
+  // use the default prototype for the sort of Promise we have.
+  void CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv);
 
   // Create the JS resolving functions of resolve() and reject(). And provide
   // references to the two functions by calling PromiseInit passed from Promise
   // constructor.
   void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
                         ErrorResult& aRv);
 
   bool IsPending()
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -459,19 +459,23 @@ public:
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     nsRefPtr<PromiseWorkerProxy> proxy = mProxy.forget();
     nsRefPtr<Promise> promise = proxy->GetWorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
-      nsRefPtr<WorkerPushSubscription> sub =
-        new WorkerPushSubscription(mEndpoint, mScope);
-      promise->MaybeResolve(sub);
+      if (mEndpoint.IsEmpty()) {
+        promise->MaybeResolve(JS::NullHandleValue);
+      } else {
+        nsRefPtr<WorkerPushSubscription> sub =
+          new WorkerPushSubscription(mEndpoint, mScope);
+        promise->MaybeResolve(sub);
+      }
     } else {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     }
 
     proxy->CleanUp(aCx);
     return true;
   }
 private:
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -16,8 +16,10 @@ skip-if = os == "android" || toolkit == 
 [test_multiple_register_during_service_activation.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_unregister.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_multiple_register_different_scope.html]
 skip-if = os == "android" || toolkit == "gonk"
 [test_try_registering_offline_disabled.html]
 skip-if = os == "android" || toolkit == "gonk"
+[test_push_manager_worker.html]
+skip-if = os == "android" || toolkit == "gonk"
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_push_manager_worker.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1184574: Expose PushManager to workers.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1184574</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1184574">Mozilla Bug 1184574</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var registration;
+
+  function start() {
+    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
+    .then(swr => { registration = swr; return swr; });
+  }
+
+  function unregisterSW() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  }
+
+  function setupPushNotification(swr) {
+    return swr.pushManager.subscribe().then(
+      pushSubscription => {
+        ok(true, "successful registered for push notification");
+        return pushSubscription;
+      }, error => {
+        ok(false, "could not register for push notification");
+      });
+  }
+
+  function getNewEndpointFromWorker(pushSubscription) {
+    return new Promise((resolve, reject) => {
+      var channel = new MessageChannel();
+      channel.port1.onmessage = e => {
+        (e.data.error ? reject : resolve)(e.data);
+      };
+      registration.active.postMessage({
+        endpoint: pushSubscription.endpoint,
+      }, [channel.port2]);
+    }).then(data => {
+      return registration.pushManager.getSubscription().then(
+        pushSubscription => {
+          is(data.endpoint, pushSubscription.endpoint,
+             "Wrong push endpoint in parent");
+          return pushSubscription;
+      });
+    });
+  }
+
+  function unregisterPushNotification(pushSubscription) {
+    return pushSubscription.unsubscribe().then(
+      result => {
+      ok(result, "unsubscribe() on existing subscription should return true.");
+      return pushSubscription;
+    }, error => {
+      ok(false, "unsubscribe() should never fail.");
+    });
+  }
+
+  function runTest() {
+    start()
+    .then(setupPushNotification)
+    .then(getNewEndpointFromWorker)
+    .then(unregisterPushNotification)
+    .then(unregisterSW)
+    .catch(function(e) {
+      ok(false, "Some test failed with error " + e);
+    }).then(SimpleTest.finish);
+  }
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.push.enabled", true],
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+    ]}, runTest);
+  SpecialPowers.addPermission('push', true, document);
+  SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -1,12 +1,19 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/licenses/publicdomain/
 
+// This worker is used for two types of tests. `handlePush` sends messages to
+// `frame.html`, which verifies that the worker can receive push messages.
+
+// `handleMessage` receives a message from `test_push_manager_worker.html`, and
+// verifies that `PushManager` can be used from the worker.
+
 this.onpush = handlePush;
+this.onmessage = handleMessage;
 
 function handlePush(event) {
 
   self.clients.matchAll().then(function(result) {
     if (event instanceof PushEvent &&
       event.data instanceof PushMessageData &&
       event.data.text === undefined &&
       event.data.json === undefined &&
@@ -14,8 +21,31 @@ function handlePush(event) {
       event.data.blob === undefined) {
 
       result[0].postMessage({type: "finished", okay: "yes"});
       return;
     }
     result[0].postMessage({type: "finished", okay: "no"});
   });
 }
+
+function handleMessage(event) {
+  self.registration.pushManager.getSubscription().then(subscription => {
+    if (subscription.endpoint != event.data.endpoint) {
+      throw new Error("Wrong push endpoint in worker");
+    }
+    return subscription.unsubscribe();
+  }).then(result => {
+    if (!result) {
+      throw new Error("Error dropping subscription in worker");
+    }
+    return self.registration.pushManager.getSubscription();
+  }).then(subscription => {
+    if (subscription) {
+      throw new Error("Subscription not dropped in worker");
+    }
+    return self.registration.pushManager.subscribe();
+  }).then(subscription => {
+    event.ports[0].postMessage({endpoint: subscription.endpoint});
+  }).catch(error => {
+    event.ports[0].postMessage({error: error});
+  });
+}
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -113,17 +113,22 @@ DoCORSChecks(nsIChannel* aChannel, nsILo
 nsresult
 DoContentSecurityChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
 {
   nsContentPolicyType contentPolicyType = aLoadInfo->GetContentPolicyType();
   nsCString mimeTypeGuess;
   nsCOMPtr<nsINode> requestingContext = nullptr;
 
   switch(contentPolicyType) {
-    case nsIContentPolicy::TYPE_OTHER:
+    case nsIContentPolicy::TYPE_OTHER: {
+      mimeTypeGuess = EmptyCString();
+      requestingContext = aLoadInfo->LoadingNode();
+      break;
+    }
+
     case nsIContentPolicy::TYPE_SCRIPT:
     case nsIContentPolicy::TYPE_IMAGE:
     case nsIContentPolicy::TYPE_STYLESHEET:
     case nsIContentPolicy::TYPE_OBJECT:
     case nsIContentPolicy::TYPE_DOCUMENT:
     case nsIContentPolicy::TYPE_SUBDOCUMENT:
     case nsIContentPolicy::TYPE_REFRESH:
     case nsIContentPolicy::TYPE_XBL:
--- a/dom/tests/mochitest/gamepad/test_gamepad.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad.html
@@ -14,17 +14,18 @@ SimpleTest.waitForExplicitFinish();
 window.addEventListener("gamepadconnected", connecthandler);
 // Add a gamepad
 var index = GamepadService.addGamepad("test gamepad", // id
                                       SpecialPowers.Ci.nsIGamepadServiceTest.STANDARD_MAPPING,
                                       4, // buttons
                                       2);// axes
 GamepadService.newButtonEvent(index, 0, true);
 function connecthandler(e) {
-  ok(e.gamepad.timestamp <= performance.now());
+  ok(e.gamepad.timestamp <= performance.now(),
+     "gamepad.timestamp should less than or equal to performance.now()");
   is(e.gamepad.index, 0, "correct gamepad index");
   is(e.gamepad.id, "test gamepad", "correct gamepad name");
   is(e.gamepad.mapping, "standard", "standard mapping");
   is(e.gamepad.buttons.length, 4, "correct number of buttons");
   is(e.gamepad.axes.length, 2, "correct number of axes");
   // Press a button
   GamepadService.newButtonEvent(index, 0, true);
   gamepads = navigator.getGamepads();
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -135,16 +135,18 @@ var interfaceNamesInGlobalScope =
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Animation", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectReadOnly", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnimationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "AnimationPlaybackEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationTimeline", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Attr",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Audio",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AudioBuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -673,16 +675,18 @@ var interfaceNamesInGlobalScope =
     "IDBRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBTransaction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "IDBVersionChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Image",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageBitmap",
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ImageCapture", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ImageCaptureErrorEvent", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ImageData",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "InputEvent",