merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 30 Jun 2016 12:33:41 +0200
changeset 303238 bcf4ff0c3eef498cd853a88270c75c161e524549
parent 303150 4a860475d96a0f7705106498e7380debc7fb2ab9 (current diff)
parent 303237 633d41ede644bf0fed6ca98328168d9c8f44c1de (diff)
child 303239 82e1f1b9c0559f38a8460e2f2f3044de4c7712d6
child 303241 fc64969ac221ee16036001dcc4057c4f6be9bd1f
child 303251 7b4e8a8e4f0b3a13e0862d8b54f7e9967069b6d9
child 303287 280e4ef4d764a418b10983df476cdc806d1bccff
push id30382
push usercbook@mozilla.com
push dateThu, 30 Jun 2016 10:34:10 +0000
treeherdermozilla-central@bcf4ff0c3eef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/base/nsDocument.cpp
dom/webidl/Document.webidl
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/mochitest.ini
js/src/asmjs/Wasm.cpp
js/src/asmjs/Wasm.h
layout/generic/nsGfxScrollFrame.cpp
media/libstagefright/binding/mp4parse-thread.patch
security/manager/ssl/tests/compiled/TestMD4.cpp
taskcluster/ci/legacy/tasks/branches/try/job_flags.yml
testing/tools/autotry/autotry.py
toolkit/components/telemetry/Histograms.json
toolkit/components/telemetry/histogram-whitelists.json
widget/CompositorWidgetProxy.cpp
widget/CompositorWidgetProxy.h
widget/android/bindings/OverScroller-classes.txt
widget/windows/WinCompositorWidgetProxy.cpp
widget/windows/WinCompositorWidgetProxy.h
--- 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 1276696 - New Android dependencies
+Bug 1267887 - Build skew after updating rust mp4parse
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -606,17 +606,16 @@ var WebBrowserChrome = {
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
   let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsITabChild);
   tabchild.webBrowserChrome = WebBrowserChrome;
 }
 
 
 var DOMFullscreenHandler = {
-  _fullscreenDoc: null,
 
   init: function() {
     addMessageListener("DOMFullscreen:Entered", this);
     addMessageListener("DOMFullscreen:CleanUp", this);
     addEventListener("MozDOMFullscreen:Request", this);
     addEventListener("MozDOMFullscreen:Entered", this);
     addEventListener("MozDOMFullscreen:NewOrigin", this);
     addEventListener("MozDOMFullscreen:Exit", this);
@@ -641,36 +640,38 @@ var DOMFullscreenHandler = {
           // If we don't actually have any pending fullscreen request
           // to handle, neither we have been in fullscreen, tell the
           // parent to just exit.
           sendAsyncMessage("DOMFullscreen:Exit");
         }
         break;
       }
       case "DOMFullscreen:CleanUp": {
-        if (windowUtils) {
+        // If we've exited fullscreen at this point, no need to record
+        // transaction id or call exit fullscreen. This is especially
+        // important for non-e10s, since in that case, it is possible
+        // that no more paint would be triggered after this point.
+        if (content.document.fullscreenElement && windowUtils) {
           this._lastTransactionId = windowUtils.lastTransactionId;
           windowUtils.exitFullscreen();
         }
-        this._fullscreenDoc = null;
         break;
       }
     }
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "MozDOMFullscreen:Request": {
         sendAsyncMessage("DOMFullscreen:Request");
         break;
       }
       case "MozDOMFullscreen:NewOrigin": {
-        this._fullscreenDoc = aEvent.target;
         sendAsyncMessage("DOMFullscreen:NewOrigin", {
-          originNoSuffix: this._fullscreenDoc.nodePrincipal.originNoSuffix,
+          originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix,
         });
         break;
       }
       case "MozDOMFullscreen:Exit": {
         sendAsyncMessage("DOMFullscreen:Exit");
         break;
       }
       case "MozDOMFullscreen:Entered":
@@ -682,17 +683,20 @@ var DOMFullscreenHandler = {
           // ensure that the parent always exits fullscreen when we do.
           sendAsyncMessage("DOMFullscreen:Exit");
         }
         break;
       }
       case "MozAfterPaint": {
         // Only send Painted signal after we actually finish painting
         // the transition for the fullscreen change.
-        if (aEvent.transactionId > this._lastTransactionId) {
+        // Note that this._lastTransactionId is not set when in non-e10s
+        // mode, so we need to check that explicitly.
+        if (!this._lastTransactionId ||
+            aEvent.transactionId > this._lastTransactionId) {
           removeEventListener("MozAfterPaint", this);
           sendAsyncMessage("DOMFullscreen:Painted");
         }
         break;
       }
     }
   }
 };
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -48,16 +48,18 @@ PluginContent.prototype = {
     global.addEventListener("unload",                this);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
+
+    Services.obs.addObserver(this, "Plugin::HiddenPluginTouched", false);
   },
 
   uninit: function() {
     let global = this.global;
 
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
@@ -70,16 +72,18 @@ PluginContent.prototype = {
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
     delete this.content;
+
+    Services.obs.removeObserver(this, "Plugin::HiddenPluginTouched");
   },
 
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "BrowserPlugins:ActivatePlugins":
         this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
         break;
       case "BrowserPlugins:NotificationShown":
@@ -111,16 +115,25 @@ PluginContent.prototype = {
       case "BrowserPlugins:Test:ClearCrashData":
         // This message should ONLY ever be sent by automated tests.
         if (Services.prefs.getBoolPref("plugins.testmode")) {
           this.pluginCrashData.clear();
         }
     }
   },
 
+  observe: function observe(aSubject, aTopic, aData) {
+    let pluginTag = aSubject.QueryInterface(Ci.nsIPluginTag);
+    if (aTopic == "Plugin::HiddenPluginTouched") {
+      this._showClickToPlayNotification(pluginTag, false);
+    } else {
+      Cu.reportError("unknown topic observed: " + aTopic);
+    }
+  },
+
   onPageShow: function (event) {
     // Ignore events that aren't from the main document.
     if (!this.content || event.target != this.content.document) {
       return;
     }
 
     // The PluginClickToPlay events are not fired when navigating using the
     // BF cache. |persisted| is true when the page is loaded from the
@@ -189,16 +202,55 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
+  _getPluginInfoForTag: function (pluginTag, tagMimetype) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+    let permissionString = null;
+    let blocklistState = null;
+
+    if (pluginTag) {
+      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+      blocklistState = pluginTag.blocklistState;
+
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
+      // Make state-softblocked == state-notblocked for our purposes,
+      // they have the same UI. STATE_OUTDATED should not exist for plugin
+      // items, but let's alias it anyway, just in case.
+      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+    }
+
+    return { mimetype: tagMimetype,
+             pluginName: pluginName,
+             pluginTag: pluginTag,
+             permissionString: permissionString,
+             fallbackType: null,
+             blocklistState: blocklistState,
+           };
+  },
+
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
@@ -705,17 +757,23 @@ PluginContent.prototype = {
     }
 
     let pluginData = this.pluginData;
 
     let principal = this.content.document.nodePrincipal;
     let location = this.content.document.location.href;
 
     for (let p of plugins) {
-      let pluginInfo = this._getPluginInfo(p);
+      let pluginInfo;
+      if (p instanceof Ci.nsIPluginTag) {
+        let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
+        pluginInfo = this._getPluginInfoForTag(p, mimeType);
+      } else {
+        pluginInfo = this._getPluginInfo(p);
+      }
       if (pluginInfo.permissionString === null) {
         Cu.reportError("No permission string for active plugin.");
         continue;
       }
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -22,16 +22,17 @@ import java.util.Iterator;
  * annotation. Returns an object containing both the annotation (Which may contain interesting
  * parameters) and the argument.
  */
 public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
     private final ClassWithOptions mClass;
     private final Member[] mObjects;
     private AnnotatableEntity mNextReturnValue;
     private int mElementIndex;
+    private AnnotationInfo mClassInfo;
 
     private boolean mIterateEveryEntry;
 
     public GeneratableElementIterator(ClassWithOptions annotatedClass) {
         mClass = annotatedClass;
 
         final Class<?> aClass = annotatedClass.wrappedClass;
         // Get all the elements of this class as AccessibleObjects.
@@ -50,18 +51,18 @@ public class GeneratableElementIterator 
         System.arraycopy(aCtors, 0, objs, offset, aCtors.length);
 
         // Sort the elements to ensure determinism.
         Arrays.sort(objs, new AlphabeticAnnotatableEntityComparator<Member>());
         mObjects = objs;
 
         // Check for "Wrap ALL the things" flag.
         for (Annotation annotation : aClass.getDeclaredAnnotations()) {
-            final String annotationTypeName = annotation.annotationType().getName();
-            if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
+            mClassInfo = buildAnnotationInfo(aClass, annotation);
+            if (mClassInfo != null) {
                 mIterateEveryEntry = true;
                 break;
             }
         }
 
         findNextValue();
     }
 
@@ -110,95 +111,108 @@ public class GeneratableElementIterator 
         Arrays.sort(ret, new Comparator<ClassWithOptions>() {
             @Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
                 return lhs.generatedName.compareTo(rhs.generatedName);
             }
         });
         return ret;
     }
 
+    private AnnotationInfo buildAnnotationInfo(AnnotatedElement element, Annotation annotation) {
+        Class<? extends Annotation> annotationType = annotation.annotationType();
+        final String annotationTypeName = annotationType.getName();
+        if (!annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
+            return null;
+        }
+
+        String stubName = null;
+        boolean isMultithreadedStub = false;
+        boolean noThrow = false;
+        boolean narrowChars = false;
+        boolean catchException = false;
+        try {
+            // Determine the explicitly-given name of the stub to generate, if any.
+            final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
+            stubNameMethod.setAccessible(true);
+            stubName = (String) stubNameMethod.invoke(annotation);
+
+            if (element instanceof Class<?>) {
+                // Make @WrapForJNI always allow multithread by default, individual methods can then
+                // override with their own annotation
+                isMultithreadedStub = true;
+            } else {
+                // Determine if the generated stub is to allow calls from multiple threads.
+                final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
+                multithreadedStubMethod.setAccessible(true);
+                isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
+            }
+
+            // Determine if ignoring exceptions
+            final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
+            noThrowMethod.setAccessible(true);
+            noThrow = (Boolean) noThrowMethod.invoke(annotation);
+
+            // Determine if strings should be wide or narrow
+            final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
+            narrowCharsMethod.setAccessible(true);
+            narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
+
+            // Determine if we should catch exceptions
+            final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
+            catchExceptionMethod.setAccessible(true);
+            catchException = (Boolean) catchExceptionMethod.invoke(annotation);
+
+        } catch (NoSuchMethodException e) {
+            System.err.println("Unable to find expected field on WrapForJNI annotation. Did the signature change?");
+            e.printStackTrace(System.err);
+            System.exit(3);
+        } catch (IllegalAccessException e) {
+            System.err.println("IllegalAccessException reading fields on WrapForJNI annotation. Seems the semantics of Reflection have changed...");
+            e.printStackTrace(System.err);
+            System.exit(4);
+        } catch (InvocationTargetException e) {
+            System.err.println("InvocationTargetException reading fields on WrapForJNI annotation. This really shouldn't happen.");
+            e.printStackTrace(System.err);
+            System.exit(5);
+        }
+
+        // If the method name was not explicitly given in the annotation generate one...
+        if (stubName.isEmpty()) {
+            stubName = Utils.getNativeName(element);
+        }
+
+        return new AnnotationInfo(
+            stubName, isMultithreadedStub, noThrow, narrowChars, catchException);
+    }
+
     /**
      * Find and cache the next appropriately annotated method, plus the annotation parameter, if
      * one exists. Otherwise cache null, so hasNext returns false.
      */
     private void findNextValue() {
         while (mElementIndex < mObjects.length) {
             Member candidateElement = mObjects[mElementIndex];
             mElementIndex++;
             for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) {
-                // WrappedJNIMethod has parameters. Use Reflection to obtain them.
-                Class<? extends Annotation> annotationType = annotation.annotationType();
-                final String annotationTypeName = annotationType.getName();
-                if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) {
-                    String stubName = null;
-                    boolean isMultithreadedStub = false;
-                    boolean noThrow = false;
-                    boolean narrowChars = false;
-                    boolean catchException = false;
-                    try {
-                        // Determine the explicitly-given name of the stub to generate, if any.
-                        final Method stubNameMethod = annotationType.getDeclaredMethod("stubName");
-                        stubNameMethod.setAccessible(true);
-                        stubName = (String) stubNameMethod.invoke(annotation);
-
-                        // Determine if the generated stub is to allow calls from multiple threads.
-                        final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread");
-                        multithreadedStubMethod.setAccessible(true);
-                        isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation);
-
-                        // Determine if ignoring exceptions
-                        final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow");
-                        noThrowMethod.setAccessible(true);
-                        noThrow = (Boolean) noThrowMethod.invoke(annotation);
-
-                        // Determine if strings should be wide or narrow
-                        final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars");
-                        narrowCharsMethod.setAccessible(true);
-                        narrowChars = (Boolean) narrowCharsMethod.invoke(annotation);
-
-                        // Determine if we should catch exceptions
-                        final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException");
-                        catchExceptionMethod.setAccessible(true);
-                        catchException = (Boolean) catchExceptionMethod.invoke(annotation);
-
-                    } catch (NoSuchMethodException e) {
-                        System.err.println("Unable to find expected field on WrapForJNI annotation. Did the signature change?");
-                        e.printStackTrace(System.err);
-                        System.exit(3);
-                    } catch (IllegalAccessException e) {
-                        System.err.println("IllegalAccessException reading fields on WrapForJNI annotation. Seems the semantics of Reflection have changed...");
-                        e.printStackTrace(System.err);
-                        System.exit(4);
-                    } catch (InvocationTargetException e) {
-                        System.err.println("InvocationTargetException reading fields on WrapForJNI annotation. This really shouldn't happen.");
-                        e.printStackTrace(System.err);
-                        System.exit(5);
-                    }
-
-                    // If the method name was not explicitly given in the annotation generate one...
-                    if (stubName.isEmpty()) {
-                        stubName = Utils.getNativeName(candidateElement);
-                    }
-
-                    AnnotationInfo annotationInfo = new AnnotationInfo(
-                        stubName, isMultithreadedStub, noThrow, narrowChars, catchException);
-                    mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
+                AnnotationInfo info = buildAnnotationInfo((AnnotatedElement)candidateElement, annotation);
+                if (info != null) {
+                    mNextReturnValue = new AnnotatableEntity(candidateElement, info);
                     return;
                 }
             }
 
             // If no annotation found, we might be expected to generate anyway
             // using default arguments, thanks to the "Generate everything" annotation.
             if (mIterateEveryEntry) {
                 AnnotationInfo annotationInfo = new AnnotationInfo(
                     Utils.getNativeName(candidateElement),
-                    /* multithreaded */ true,
-                    /* noThrow */ false,
-                    /* narrowChars */ false,
-                    /* catchException */ false);
+                    mClassInfo.isMultithreaded,
+                    mClassInfo.noThrow,
+                    mClassInfo.narrowChars,
+                    mClassInfo.catchException);
                 mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo);
                 return;
             }
         }
         mNextReturnValue = null;
     }
 
     @Override
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.annotationProcessors.utils;
 
 import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
 
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.HashMap;
 
 /**
@@ -208,16 +209,43 @@ public class Utils {
      * @return JNI name as a string
      */
     public static String getNativeName(Member member) {
         final String name = getMemberName(member);
         return name.substring(0, 1).toUpperCase() + name.substring(1);
     }
 
     /**
+     * Get the C++ name for a member.
+     *
+     * @param member Member to get the name for.
+     * @return JNI name as a string
+     */
+    public static String getNativeName(Class<?> clz) {
+        final String name = clz.getName();
+        return name.substring(0, 1).toUpperCase() + name.substring(1);
+    }
+
+    /**
+     * Get the C++ name for a member.
+     *
+     * @param member Member to get the name for.
+     * @return JNI name as a string
+     */
+    public static String getNativeName(AnnotatedElement element) {
+        if (element instanceof Class<?>) {
+            return getNativeName((Class<?>)element);
+        } else if (element instanceof Member) {
+            return getNativeName((Member)element);
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Get the JNI name for a member.
      *
      * @param member Member to get the name for.
      * @return JNI name as a string
      */
     public static String getMemberName(Member member) {
         if (member instanceof Constructor) {
             return "<init>";
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -81,16 +81,17 @@
 #include "nsIScrollObserver.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIChannel.h"
 #include "IHistory.h"
 #include "nsViewSourceHandler.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsICookieService.h"
 #include "nsIConsoleReportCollector.h"
+#include "nsObjectLoadingContent.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"
 #include "nsPILoadGroupInternal.h"
 
 // Local Includes
@@ -2519,25 +2520,45 @@ nsDocShell::GetFullscreenAllowed(bool* a
   nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
   if (!win) {
     return NS_OK;
   }
   nsCOMPtr<Element> frameElement = win->GetFrameElementInternal();
   if (frameElement && !frameElement->IsXULElement()) {
     // We do not allow document inside any containing element other
     // than iframe to enter fullscreen.
-    if (!frameElement->IsHTMLElement(nsGkAtoms::iframe)) {
-      return NS_OK;
-    }
-    // If any ancestor iframe does not have allowfullscreen attribute
-    // set, then fullscreen is not allowed.
-    if (!frameElement->HasAttr(kNameSpaceID_None,
-                               nsGkAtoms::allowfullscreen) &&
-        !frameElement->HasAttr(kNameSpaceID_None,
-                               nsGkAtoms::mozallowfullscreen)) {
+    if (frameElement->IsHTMLElement(nsGkAtoms::iframe)) {
+      // If any ancestor iframe does not have allowfullscreen attribute
+      // set, then fullscreen is not allowed.
+      if (!frameElement->HasAttr(kNameSpaceID_None,
+                                 nsGkAtoms::allowfullscreen) &&
+          !frameElement->HasAttr(kNameSpaceID_None,
+                                 nsGkAtoms::mozallowfullscreen)) {
+        return NS_OK;
+      }
+    } else if (frameElement->IsHTMLElement(nsGkAtoms::embed)) {
+      // Respect allowfullscreen only if this is a rewritten YouTube embed.
+      nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent =
+        do_QueryInterface(frameElement);
+      if (!objectLoadingContent) {
+        return NS_OK;
+      }
+      nsObjectLoadingContent* olc =
+        static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
+      if (!olc->IsRewrittenYoutubeEmbed()) {
+        return NS_OK;
+      }
+      // We don't have to check prefixed attributes because Flash does not
+      // support them.
+      if (!frameElement->HasAttr(kNameSpaceID_None,
+                                 nsGkAtoms::allowfullscreen)) {
+        return NS_OK;
+      }
+    } else {
+      // neither iframe nor embed
       return NS_OK;
     }
     nsIDocument* doc = frameElement->GetUncomposedDoc();
     if (!doc || !doc->FullscreenEnabledInternal()) {
       return NS_OK;
     }
   }
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1728,28 +1728,27 @@ Element::UnbindFromTree(bool aDeep, bool
 
   // Make sure to unbind this node before doing the kids
   nsIDocument* document =
     HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
 
   if (HasPointerLock()) {
     nsIDocument::UnlockPointer();
   }
+  if (mState.HasState(NS_EVENT_STATE_FULL_SCREEN)) {
+    // The element being removed is an ancestor of the full-screen element,
+    // exit full-screen state.
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("DOM"), OwnerDoc(),
+                                    nsContentUtils::eDOM_PROPERTIES,
+                                    "RemovedFullscreenElement");
+    // Fully exit full-screen.
+    nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
+  }
   if (aNullParent) {
-    if (IsFullScreenAncestor()) {
-      // The element being removed is an ancestor of the full-screen element,
-      // exit full-screen state.
-      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                      NS_LITERAL_CSTRING("DOM"), OwnerDoc(),
-                                      nsContentUtils::eDOM_PROPERTIES,
-                                      "RemovedFullscreenElement");
-      // Fully exit full-screen.
-      nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
-    }
-
     if (GetParent() && GetParent()->IsInUncomposedDoc()) {
       // Update the editable descendant count in the ancestors before we
       // lose the reference to the parent.
       int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this);
       if (editableDescendantChange != 0) {
         nsIContent* parent = GetParent();
         while (parent) {
           parent->ChangeEditableDescendantCount(editableDescendantChange);
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -188,25 +188,16 @@ public:
   void UpdateState(bool aNotify);
 
   /**
    * Method to update mState with link state information.  This does not notify.
    */
   void UpdateLinkState(EventStates aState);
 
   /**
-   * Returns true if this element is either a full-screen element or an
-   * ancestor of the full-screen element.
-   */
-  bool IsFullScreenAncestor() const {
-    return mState.HasAtLeastOneOfStates(NS_EVENT_STATE_FULL_SCREEN_ANCESTOR |
-                                        NS_EVENT_STATE_FULL_SCREEN);
-  }
-
-  /**
    * The style state of this element. This is the real state of the element
    * with any style locks applied for pseudo-class inspecting.
    */
   EventStates StyleState() const
   {
     if (!HasLockedStyleStates()) {
       return mState;
     }
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -1841,16 +1841,22 @@ WebSocketImpl::InitializeConnection(nsIP
   // manually adding loadinfo to the channel since it
   // was not set during channel creation.
   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mOriginDocument);
 
   // mOriginDocument has to be release on main-thread because WeakReferences
   // are not thread-safe.
   mOriginDocument = nullptr;
 
+
+  // The TriggeringPrincipal for websockets must always be a script.
+  // Let's make sure that the doc's principal (if a doc exists)
+  // and aPrincipal are same origin.
+  MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
+
   wsChannel->InitLoadInfo(doc ? doc->AsDOMNode() : nullptr,
                           doc ? doc->NodePrincipal() : aPrincipal,
                           aPrincipal,
                           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                           nsIContentPolicy::TYPE_WEBSOCKET);
 
   if (!mRequestedProtocolList.IsEmpty()) {
     rv = wsChannel->SetProtocol(mRequestedProtocolList);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -1339,16 +1339,90 @@ nsContentUtils::GetParserService()
     if (NS_FAILED(rv)) {
       sParserService = nullptr;
     }
   }
 
   return sParserService;
 }
 
+/**
+* A helper function that parses a sandbox attribute (of an <iframe> or a CSP
+* directive) and converts it to the set of flags used internally.
+*
+* @param aSandboxAttr  the sandbox attribute
+* @return              the set of flags (SANDBOXED_NONE if aSandboxAttr is
+*                      null)
+*/
+uint32_t
+nsContentUtils::ParseSandboxAttributeToFlags(const nsAttrValue* aSandboxAttr)
+{
+  if (!aSandboxAttr) {
+    return SANDBOXED_NONE;
+  }
+
+  uint32_t out = SANDBOX_ALL_FLAGS;
+
+#define SANDBOX_KEYWORD(string, atom, flags)                  \
+  if (aSandboxAttr->Contains(nsGkAtoms::atom, eIgnoreCase)) { \
+    out &= ~(flags);                                          \
+  }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+
+  return out;
+}
+
+/**
+* A helper function that checks if a string matches a valid sandbox flag.
+*
+* @param aFlag   the potential sandbox flag.
+* @return        true if the flag is a sandbox flag.
+*/
+bool
+nsContentUtils::IsValidSandboxFlag(const nsAString& aFlag)
+{
+#define SANDBOX_KEYWORD(string, atom, flags)                                  \
+  if (EqualsIgnoreASCIICase(nsDependentAtomString(nsGkAtoms::atom), aFlag)) { \
+    return true;                                                              \
+  }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+  return false;
+}
+
+/**
+ * A helper function that returns a string attribute corresponding to the
+ * sandbox flags.
+ *
+ * @param aFlags    the sandbox flags
+ * @param aString   the attribute corresponding to the flags (null if aFlags
+ *                  is zero)
+ */
+void
+nsContentUtils::SandboxFlagsToString(uint32_t aFlags, nsAString& aString)
+{
+  if (!aFlags) {
+    SetDOMStringToNull(aString);
+    return;
+  }
+
+  aString.Truncate();
+
+#define SANDBOX_KEYWORD(string, atom, flags)                \
+  if (!(aFlags & (flags))) {                                \
+    if (!aString.IsEmpty()) {                               \
+      aString.Append(NS_LITERAL_STRING(" "));               \
+    }                                                       \
+    aString.Append(nsDependentAtomString(nsGkAtoms::atom)); \
+  }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+}
+
 nsIBidiKeyboard*
 nsContentUtils::GetBidiKeyboard()
 {
   if (!sBidiKeyboard) {
     nsresult rv = CallGetService("@mozilla.org/widget/bidikeyboard;1", &sBidiKeyboard);
     if (NS_FAILED(rv)) {
       sBidiKeyboard = nullptr;
     }
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -913,16 +913,44 @@ public:
   /**
    * Get the localized string named |aKey| in properties file |aFile|.
    */
   static nsresult GetLocalizedString(PropertiesFile aFile,
                                      const char* aKey,
                                      nsXPIDLString& aResult);
 
   /**
+   * A helper function that parses a sandbox attribute (of an <iframe> or a CSP
+   * directive) and converts it to the set of flags used internally.
+   *
+   * @param aSandboxAttr  the sandbox attribute
+   * @return              the set of flags (SANDBOXED_NONE if aSandboxAttr is
+   *                      null)
+   */
+  static uint32_t ParseSandboxAttributeToFlags(const nsAttrValue* aSandboxAttr);
+
+  /**
+   * A helper function that checks if a string matches a valid sandbox flag.
+   *
+   * @param aFlag   the potential sandbox flag.
+   * @return        true if the flag is a sandbox flag.
+   */
+  static bool IsValidSandboxFlag(const nsAString& aFlag);
+
+  /**
+   * A helper function that returns a string attribute corresponding to the
+   * sandbox flags.
+   *
+   * @param aFlags    the sandbox flags
+   * @param aString   the attribute corresponding to the flags (null if aFlags
+   *                  is zero)
+   */
+  static void SandboxFlagsToString(uint32_t aFlags, nsAString& aString);
+
+  /**
    * Helper function that generates a UUID.
    */
   static nsresult GenerateUUIDInPlace(nsID& aUUID);
 
   static bool PrefetchEnabled(nsIDocShell* aDocShell);
 
   /**
    * Fill (with the parameters given) the localized string named |aKey| in
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3209,16 +3209,20 @@ static void
 PrepareForFullscreenChange(nsIPresShell* aPresShell, const nsSize& aSize,
                            nsSize* aOldSize = nullptr)
 {
   if (!aPresShell) {
     return;
   }
   if (nsRefreshDriver* rd = aPresShell->GetRefreshDriver()) {
     rd->SetIsResizeSuppressed();
+    // Since we are suppressing the resize reflow which would originally
+    // be triggered by view manager, we need to ensure that the refresh
+    // driver actually schedules a flush, otherwise it may get stuck.
+    rd->ScheduleViewManagerFlush();
   }
   if (!aSize.IsEmpty()) {
     if (nsViewManager* viewManager = aPresShell->GetViewManager()) {
       if (aOldSize) {
         viewManager->GetWindowDimensions(&aOldSize->width, &aOldSize->height);
       }
       viewManager->SetWindowDimensions(aSize.width, aSize.height);
     }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2753,16 +2753,18 @@ nsDocument::ApplySettingsFromCSP(bool aS
       NS_ENSURE_SUCCESS_VOID(rv);
     }
   }
 }
 
 nsresult
 nsDocument::InitCSP(nsIChannel* aChannel)
 {
+  MOZ_ASSERT(!mScriptGlobalObject,
+             "CSP must be initialized before mScriptGlobalObject is set!");
   if (!CSPService::sCSPEnabled) {
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
            ("CSP is disabled, skipping CSP init for document %p", this));
     return NS_OK;
   }
 
   nsAutoCString tCspHeaderValue, tCspROHeaderValue;
 
@@ -2785,17 +2787,17 @@ nsDocument::InitCSP(nsIChannel* aChannel
     httpChannel->GetResponseHeader(
         NS_LITERAL_CSTRING("content-security-policy-report-only"),
         tCspROHeaderValue);
   }
   NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
   NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
 
   // Figure out if we need to apply an app default CSP or a CSP from an app manifest
-  nsIPrincipal* principal = NodePrincipal();
+  nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
 
   uint16_t appStatus = principal->GetAppStatus();
   bool applyAppDefaultCSP = false;
   bool applyAppManifestCSP = false;
 
   nsAutoString appManifestCSP;
   nsAutoString appDefaultCSP;
   if (appStatus != nsIPrincipal::APP_STATUS_NOT_INSTALLED) {
@@ -2934,16 +2936,34 @@ nsDocument::InitCSP(nsIChannel* aChannel
   }
 
   // ----- if there's a report-only CSP header, apply it.
   if (!cspROHeaderValue.IsEmpty()) {
     rv = AppendCSPFromHeader(csp, cspROHeaderValue, true);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // ----- Enforce sandbox policy if supplied in CSP header
+  // The document may already have some sandbox flags set (e.g. if the document
+  // is an iframe with the sandbox attribute set). If we have a CSP sandbox
+  // directive, intersect the CSP sandbox flags with the existing flags. This
+  // corresponds to the _least_ permissive policy.
+  uint32_t cspSandboxFlags = SANDBOXED_NONE;
+  rv = csp->GetCSPSandboxFlags(&cspSandboxFlags);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mSandboxFlags |= cspSandboxFlags;
+
+  if (cspSandboxFlags & SANDBOXED_ORIGIN) {
+    // If the new CSP sandbox flags do not have the allow-same-origin flag
+    // reset the document principal to a null principal
+    principal = do_CreateInstance("@mozilla.org/nullprincipal;1");
+    SetPrincipal(principal);
+  }
+
   // ----- Enforce frame-ancestor policy on any applied policies
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
   if (docShell) {
     bool safeAncestry = false;
 
     // PermitsAncestry sends violation reports when necessary
     rv = csp->PermitsAncestry(docShell, &safeAncestry);
 
@@ -3666,16 +3686,22 @@ nsDocument::AddCharSetObserver(nsIObserv
 
 void
 nsDocument::RemoveCharSetObserver(nsIObserver* aObserver)
 {
   mCharSetObservers.RemoveElement(aObserver);
 }
 
 void
+nsIDocument::GetSandboxFlagsAsString(nsAString& aFlags)
+{
+  nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
+}
+
+void
 nsDocument::GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const
 {
   aData.Truncate();
   const nsDocHeaderData* data = mHeaderData;
   while (data) {
     if (data->mField == aHeaderField) {
       aData = data->mData;
 
@@ -11653,17 +11679,17 @@ nsDocument::FullScreenStackPop()
   mFullScreenStack.RemoveElementAt(last);
 
   // Pop from the stack null elements (references to elements which have
   // been GC'd since they were added to the stack) and elements which are
   // no longer in this document.
   while (!mFullScreenStack.IsEmpty()) {
     Element* element = FullScreenStackTop();
     if (!element || !element->IsInUncomposedDoc() || element->OwnerDoc() != this) {
-      NS_ASSERTION(!element->IsFullScreenAncestor(),
+      NS_ASSERTION(!element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
                    "Should have already removed full-screen styles");
       uint32_t last = mFullScreenStack.Length() - 1;
       mFullScreenStack.RemoveElementAt(last);
     } else {
       // The top element of the stack is now an in-doc element. Return here.
       break;
     }
   }
@@ -12215,17 +12241,17 @@ nsDocument::GetMozFullScreenElement(nsID
   return NS_OK;
 }
 
 Element*
 nsDocument::GetFullscreenElement()
 {
   Element* element = FullScreenStackTop();
   NS_ASSERTION(!element ||
-               element->IsFullScreenAncestor(),
+               element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
     "Fullscreen element should have fullscreen styles applied");
   return element;
 }
 
 NS_IMETHODIMP
 nsDocument::GetMozFullScreen(bool *aFullScreen)
 {
   *aFullScreen = Fullscreen();
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -692,16 +692,21 @@ public:
    * @see nsSandboxFlags.h for the possible flags
    */
   uint32_t GetSandboxFlags() const
   {
     return mSandboxFlags;
   }
 
   /**
+   * Get string representation of sandbox flags (null if no flags are set)
+   */
+  void GetSandboxFlagsAsString(nsAString& aFlags);
+
+  /**
    * Set the sandbox flags for this document.
    * @see nsSandboxFlags.h for the possible flags
    */
   void SetSandboxFlags(uint32_t sandboxFlags)
   {
     mSandboxFlags = sandboxFlags;
   }
 
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -716,17 +716,18 @@ nsObjectLoadingContent::nsObjectLoadingC
   , mRunID(0)
   , mHasRunID(false)
   , mChannelLoaded(false)
   , mInstantiating(false)
   , mNetworkCreated(true)
   , mActivated(false)
   , mIsStopping(false)
   , mIsLoading(false)
-  , mScriptRequested(false) {}
+  , mScriptRequested(false)
+  , mRewrittenYoutubeEmbed(false) {}
 
 nsObjectLoadingContent::~nsObjectLoadingContent()
 {
   // Should have been unbound from the tree at this point, and
   // CheckPluginStopEvent keeps us alive
   if (mFrameLoader) {
     NS_NOTREACHED("Should not be tearing down frame loaders at this point");
     mFrameLoader->Destroy();
@@ -1838,29 +1839,31 @@ nsObjectLoadingContent::UpdateObjectPara
     thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr);
   } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::embed)) {
     thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr);
   } else {
     // Applet tags should always have a java MIME type at this point
     NS_NOTREACHED("Unrecognized plugin-loading tag");
   }
 
+  mRewrittenYoutubeEmbed = false;
   // Note that the baseURI changing could affect the newURI, even if uriStr did
   // not change.
   if (!uriStr.IsEmpty()) {
     rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(newURI),
                                                    uriStr,
                                                    thisContent->OwnerDoc(),
                                                    newBaseURI);
     nsCOMPtr<nsIURI> rewrittenURI;
     MaybeRewriteYoutubeEmbed(newURI,
                              newBaseURI,
                              getter_AddRefs(rewrittenURI));
     if (rewrittenURI) {
       newURI = rewrittenURI;
+      mRewrittenYoutubeEmbed = true;
       newMime = NS_LITERAL_CSTRING("text/html");
     }
 
     if (NS_SUCCEEDED(rv)) {
       NS_TryToSetImmutable(newURI);
     } else {
       stateInvalid = true;
     }
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -245,16 +245,21 @@ class nsObjectLoadingContent : public ns
       if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return 0;
       }
 
       return runID;
     }
 
+    bool IsRewrittenYoutubeEmbed() const
+    {
+      return mRewrittenYoutubeEmbed;
+    }
+
   protected:
     /**
      * Begins loading the object when called
      *
      * Attributes of |this| QI'd to nsIContent will be inspected, depending on
      * the node type. This function currently assumes it is a <applet>,
      * <object>, or <embed> tag.
      *
@@ -623,17 +628,17 @@ class nsObjectLoadingContent : public ns
 
 
     // Type of the currently-loaded content.
     ObjectType                  mType           : 8;
     // The type of fallback content we're showing (see ObjectState())
     FallbackType                mFallbackType : 8;
 
     uint32_t                    mRunID;
-    bool                        mHasRunID;
+    bool                        mHasRunID : 1;
 
     // If true, we have opened a channel as the listener and it has reached
     // OnStartRequest. Does not get set for channels that are passed directly to
     // the plugin listener.
     bool                        mChannelLoaded    : 1;
 
     // Whether we are about to call instantiate on our frame. If we aren't,
     // SetFrame needs to asynchronously call Instantiate.
@@ -653,16 +658,22 @@ class nsObjectLoadingContent : public ns
 
     // Protects LoadObject from re-entry
     bool                        mIsLoading : 1;
 
     // For plugin stand-in types (click-to-play) tracks
     // whether content js has tried to access the plugin script object.
     bool                        mScriptRequested : 1;
 
+    // True if object represents an object/embed tag pointing to a flash embed
+    // for a youtube video. When possible (see IsRewritableYoutubeEmbed function
+    // comments for details), we change these to try to load HTML5 versions of
+    // videos.
+    bool                        mRewrittenYoutubeEmbed : 1;
+
     nsWeakFrame                 mPrintFrame;
 
     RefPtr<nsPluginInstanceOwner> mInstanceOwner;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;
 };
 
 #endif
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -14,16 +14,18 @@
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
@@ -61,17 +63,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins)
+                                      mPlugins,
+                                      mCTPPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -141,16 +144,17 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -216,16 +220,23 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
+  if (!aFound) {
+    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+    if (hiddenPlugin) {
+      nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+      obs->NotifyObservers(hiddenPlugin->PluginTag(), "Plugin::HiddenPluginTouched", nsString(aName).get());
+    }
+  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins()) {
     return 0;
@@ -277,34 +288,58 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+    if (!pluginTag) {
+      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    } else if (pluginTag->IsActive()) {
+      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+      if (pluginTag->IsClicktoplay()) {
+        nsCString name;
+        pluginTag->GetName(name);
+        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
+          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+          nsCString permString;
+          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+          if (rv == NS_OK) {
+            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+          }
+        }
+      }
+      if (permission == nsIPermissionManager::ALLOW_ACTION) {
+        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      } else {
+        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      }
+    }
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,16 +55,20 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
+  /* A separate list of click-to-play plugins that we don't tell content
+   * about but keep track of so we can still prompt the user to click to play.
+   */
+  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/base/nsSandboxFlags.h
+++ b/dom/base/nsSandboxFlags.h
@@ -8,16 +8,21 @@
  * Constant flags that describe how a document is sandboxed according to the
  * HTML5 spec.
  */
 
 #ifndef nsSandboxFlags_h___
 #define nsSandboxFlags_h___
 
 /**
+ * This constant denotes the lack of a sandbox attribute/directive.
+ */
+const unsigned long SANDBOXED_NONE = 0x0;
+
+/**
  * This flag prevents content from navigating browsing contexts other than
  * itself, browsing contexts nested inside it, the top-level browsing context
  * and browsing contexts that it has opened.
  * As it is always on for sandboxed browsing contexts, it is used implicitly
  * within the code by checking that the overall flags are non-zero.
  * It is only uesd directly when the sandbox flags are initially set up.
  */
 const unsigned long SANDBOXED_NAVIGATION  = 0x1;
copy from dom/base/test/test_youtube_flash_embed.html
copy to dom/base/test/file_youtube_flash_embed.html
--- a/dom/base/test/test_youtube_flash_embed.html
+++ b/dom/base/test/file_youtube_flash_embed.html
@@ -1,44 +1,65 @@
 <!DOCTYPE HTML>
 <html>
   <!--
        https://bugzilla.mozilla.org/show_bug.cgi?id=1240471
      -->
   <head>
     <meta charset="utf-8">
     <title>Test for Bug 1240471</title>
-    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="application/javascript">
-     SimpleTest.waitForExplicitFinish();
+     let SimpleTest = {
+       finish: function() {
+         parent.postMessage(JSON.stringify({fn: "finish"}), "*");
+       }
+     };
+     ["ok", "is", "info"].forEach(fn => {
+       self[fn] = function (...args) {
+         parent.postMessage(JSON.stringify({fn: fn, args: args}), "*");
+       }
+     });
      "use strict";
-     function onLoad () {
+     function onLoad() {
        let youtube_changed_url_query = "https://mochitest.youtube.com/embed/Xm5i5kbIXzc?start=10&end=20";
 
-       function testEmbed(embed, expected_url) {
-         ok (embed, "Embed node exists");
+       function testEmbed(embed, expected_url, expected_fullscreen) {
+         ok (!!embed, "Embed node exists");
+         // getSVGDocument will return HTMLDocument if the content is HTML
+         let doc = embed.getSVGDocument();
+         // doc must be unprivileged because privileged doc will always be
+         // allowed to use fullscreen.
+         is (doc.fullscreenEnabled, expected_fullscreen,
+             "fullscreen should be " + (expected_fullscreen ? "enabled" : "disabled"));
          embed = SpecialPowers.wrap(embed);
          is (embed.srcURI.spec, expected_url, "Should have src uri of " + expected_url);
        }
        info("Running youtube rewrite query test");
-       testEmbed(document.getElementById("testembed-correct"), youtube_changed_url_query);
-       testEmbed(document.getElementById("testembed-wrong"), youtube_changed_url_query);
-       testEmbed(document.getElementById("testembed-whywouldyouevendothat"), youtube_changed_url_query);
+       testEmbed(document.getElementById("testembed-correct"), youtube_changed_url_query, false);
+       testEmbed(document.getElementById("testembed-correct-fs"), youtube_changed_url_query, true);
+       testEmbed(document.getElementById("testembed-wrong"), youtube_changed_url_query, false);
+       testEmbed(document.getElementById("testembed-whywouldyouevendothat"), youtube_changed_url_query, true);
        SimpleTest.finish();
      }
     </script>
   </head>
   <body onload="onLoad()">
     <embed id="testembed-correct"
            src="https://mochitest.youtube.com/v/Xm5i5kbIXzc?start=10&end=20"
            type="application/x-shockwave-flash"
            allowscriptaccess="always"></embed>
+    <embed id="testembed-correct-fs"
+           src="https://mochitest.youtube.com/v/Xm5i5kbIXzc?start=10&end=20"
+           type="application/x-shockwave-flash"
+           allowfullscreen
+           allowscriptaccess="always"></embed>
     <embed id="testembed-wrong"
            src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10&end=20"
            type="application/x-shockwave-flash"
            allowscriptaccess="always"></embed>
     <embed id="testembed-whywouldyouevendothat"
            src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10?end=20"
            type="application/x-shockwave-flash"
+           allowfullscreen
            allowscriptaccess="always"></embed>
   </body>
 </html>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -184,16 +184,17 @@ support-files =
   file_xhtmlserializer_1_sibling_body.xhtml
   file_xhtmlserializer_1_sibling_body_only_body.xhtml
   file_xhtmlserializer_1_wrap.xhtml
   file_xhtmlserializer_2.xhtml
   file_xhtmlserializer_2_basic.xhtml
   file_xhtmlserializer_2_enthtml.xhtml
   file_xhtmlserializer_2_entw3c.xhtml
   file_xhtmlserializer_2_latin1.xhtml
+  file_youtube_flash_embed.html
   fileapi_chromeScript.js
   fileutils.js
   forRemoval.resource
   forRemoval.resource^headers^
   formReset.html
   invalid_accesscontrol.resource
   invalid_accesscontrol.resource^headers^
   mutationobserver_dialog.html
--- a/dom/base/test/test_youtube_flash_embed.html
+++ b/dom/base/test/test_youtube_flash_embed.html
@@ -5,40 +5,28 @@
      -->
   <head>
     <meta charset="utf-8">
     <title>Test for Bug 1240471</title>
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     <script type="application/javascript">
      SimpleTest.waitForExplicitFinish();
-     "use strict";
-     function onLoad () {
-       let youtube_changed_url_query = "https://mochitest.youtube.com/embed/Xm5i5kbIXzc?start=10&end=20";
-
-       function testEmbed(embed, expected_url) {
-         ok (embed, "Embed node exists");
-         embed = SpecialPowers.wrap(embed);
-         is (embed.srcURI.spec, expected_url, "Should have src uri of " + expected_url);
+     let path = location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/file_youtube_flash_embed.html';
+     onmessage = function(e) {
+       let msg = JSON.parse(e.data);
+       if (msg.fn == "finish") {
+         SimpleTest.finish();
+         return;
        }
-       info("Running youtube rewrite query test");
-       testEmbed(document.getElementById("testembed-correct"), youtube_changed_url_query);
-       testEmbed(document.getElementById("testembed-wrong"), youtube_changed_url_query);
-       testEmbed(document.getElementById("testembed-whywouldyouevendothat"), youtube_changed_url_query);
-       SimpleTest.finish();
+       self[msg.fn].apply(null, msg.args);
+     }
+     function onLoad() {
+       // The test file must be loaded into youtube.com domain
+       // because it needs unprivileged access to fullscreenEnabled.
+       ifr.src = "https://mochitest.youtube.com" + path;
      }
     </script>
   </head>
   <body onload="onLoad()">
-    <embed id="testembed-correct"
-           src="https://mochitest.youtube.com/v/Xm5i5kbIXzc?start=10&end=20"
-           type="application/x-shockwave-flash"
-           allowscriptaccess="always"></embed>
-    <embed id="testembed-wrong"
-           src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10&end=20"
-           type="application/x-shockwave-flash"
-           allowscriptaccess="always"></embed>
-    <embed id="testembed-whywouldyouevendothat"
-           src="https://mochitest.youtube.com/v/Xm5i5kbIXzc&start=10?end=20"
-           type="application/x-shockwave-flash"
-           allowscriptaccess="always"></embed>
+    <iframe id="ifr" allowfullscreen></iframe>
   </body>
 </html>
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -236,22 +236,22 @@ UnwrapObject(JSObject* obj, U& value)
 }
 
 inline bool
 IsNotDateOrRegExp(JSContext* cx, JS::Handle<JSObject*> obj,
                   bool* notDateOrRegExp)
 {
   MOZ_ASSERT(obj);
 
-  js::ESClassValue cls;
+  js::ESClass cls;
   if (!js::GetBuiltinClass(cx, obj, &cls)) {
     return false;
   }
 
-  *notDateOrRegExp = cls != js::ESClass_Date && cls != js::ESClass_RegExp;
+  *notDateOrRegExp = cls != js::ESClass::Date && cls != js::ESClass::RegExp;
   return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 IsObjectValueConvertibleToDictionary(JSContext* cx,
                                      JS::Handle<JS::Value> objVal,
                                      bool* convertible)
 {
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -50,22 +50,16 @@
 #include "nsPIDOMWindow.h"
 #include "nsSandboxFlags.h"
 #include "xpcpublic.h"
 #include "nsIFrame.h"
 #include "nsDisplayList.h"
 
 namespace mozilla {
 
-namespace dom {
-namespace workers {
-extern bool IsCurrentThreadRunningChromeWorker();
-} // namespace workers
-} // namespace dom
-
 using namespace dom;
 using namespace hal;
 
 #define EVENT_TYPE_EQUALS(ls, message, userType, typeString, allEvents) \
   ((ls->mEventMessage == message &&                                     \
     (ls->mEventMessage != eUnidentifiedEvent ||                         \
     (mIsMainThreadELM && ls->mTypeAtom == userType) ||                  \
     (!mIsMainThreadELM && ls->mTypeString.Equals(typeString)))) ||      \
@@ -1339,34 +1333,16 @@ EventListenerManager::HandleEventInterna
 
 void
 EventListenerManager::Disconnect()
 {
   mTarget = nullptr;
   RemoveAllListeners();
 }
 
-static EventListenerFlags
-GetEventListenerFlagsFromOptions(const EventListenerOptions& aOptions,
-                                 bool aIsMainThread)
-{
-  EventListenerFlags flags;
-  flags.mCapture = aOptions.mCapture;
-  if (aOptions.mMozSystemGroup) {
-    if (aIsMainThread) {
-      JSContext* cx = nsContentUtils::GetCurrentJSContext();
-      MOZ_ASSERT(cx, "Not being called from JS?");
-      flags.mInSystemGroup = IsChromeOrXBL(cx, nullptr);
-    } else {
-      flags.mInSystemGroup = workers::IsCurrentThreadRunningChromeWorker();
-    }
-  }
-  return flags;
-}
-
 void
 EventListenerManager::AddEventListener(
                         const nsAString& aType,
                         const EventListenerHolder& aListenerHolder,
                         bool aUseCapture,
                         bool aWantsUntrusted)
 {
   EventListenerFlags flags;
@@ -1382,17 +1358,18 @@ EventListenerManager::AddEventListener(
                         const dom::AddEventListenerOptionsOrBoolean& aOptions,
                         bool aWantsUntrusted)
 {
   EventListenerFlags flags;
   if (aOptions.IsBoolean()) {
     flags.mCapture = aOptions.GetAsBoolean();
   } else {
     const auto& options = aOptions.GetAsAddEventListenerOptions();
-    flags = GetEventListenerFlagsFromOptions(options, mIsMainThreadELM);
+    flags.mCapture = options.mCapture;
+    flags.mInSystemGroup = options.mMozSystemGroup;
     flags.mPassive = options.mPassive;
   }
   flags.mAllowUntrustedEvents = aWantsUntrusted;
   return AddEventListenerByType(aListenerHolder, aType, flags);
 }
 
 void
 EventListenerManager::RemoveEventListener(
@@ -1411,17 +1388,18 @@ EventListenerManager::RemoveEventListene
                         const EventListenerHolder& aListenerHolder,
                         const dom::EventListenerOptionsOrBoolean& aOptions)
 {
   EventListenerFlags flags;
   if (aOptions.IsBoolean()) {
     flags.mCapture = aOptions.GetAsBoolean();
   } else {
     const auto& options = aOptions.GetAsEventListenerOptions();
-    flags = GetEventListenerFlagsFromOptions(options, mIsMainThreadELM);
+    flags.mCapture = options.mCapture;
+    flags.mInSystemGroup = options.mMozSystemGroup;
   }
   RemoveEventListenerByType(aListenerHolder, aType, flags);
 }
 
 void
 EventListenerManager::AddListenerForAllEvents(nsIDOMEventListener* aDOMListener,
                                               bool aUseCapture,
                                               bool aWantsUntrusted,
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -4812,36 +4812,21 @@ static nsIContent* FindCommonAncestor(ns
         anc2 = anc2->GetFlattenedTreeParent();
       }
       return anc1;
     }
   }
   return nullptr;
 }
 
-static Element*
-GetParentElement(Element* aElement)
-{
-  nsIContent* p = aElement->GetParent();
-  return (p && p->IsElement()) ? p->AsElement() : nullptr;
-}
-
 /* static */
 void
 EventStateManager::SetFullScreenState(Element* aElement, bool aIsFullScreen)
 {
   DoStateChange(aElement, NS_EVENT_STATE_FULL_SCREEN, aIsFullScreen);
-  Element* ancestor = aElement;
-  while ((ancestor = GetParentElement(ancestor))) {
-    DoStateChange(ancestor, NS_EVENT_STATE_FULL_SCREEN_ANCESTOR, aIsFullScreen);
-    if (ancestor->State().HasState(NS_EVENT_STATE_FULL_SCREEN)) {
-      // If we meet another fullscreen element, stop here.
-      break;
-    }
-  }
 }
 
 /* static */
 inline void
 EventStateManager::DoStateChange(Element* aElement, EventStates aState,
                                  bool aAddState)
 {
   if (aAddState) {
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -257,18 +257,18 @@ private:
 #define NS_EVENT_STATE_MOZ_SUBMITINVALID NS_DEFINE_EVENT_STATE_MACRO(30)
 // UI friendly version of :invalid pseudo-class.
 #define NS_EVENT_STATE_MOZ_UI_INVALID NS_DEFINE_EVENT_STATE_MACRO(31)
 // UI friendly version of :valid pseudo-class.
 #define NS_EVENT_STATE_MOZ_UI_VALID NS_DEFINE_EVENT_STATE_MACRO(32)
 // Content is the full screen element, or a frame containing the
 // current full-screen element.
 #define NS_EVENT_STATE_FULL_SCREEN   NS_DEFINE_EVENT_STATE_MACRO(33)
-// Content is an ancestor of the DOM full-screen element.
-#define NS_EVENT_STATE_FULL_SCREEN_ANCESTOR   NS_DEFINE_EVENT_STATE_MACRO(34)
+// This bit is currently free.
+// #define NS_EVENT_STATE_?????????? NS_DEFINE_EVENT_STATE_MACRO(34)
 // Handler for click to play plugin
 #define NS_EVENT_STATE_TYPE_CLICK_TO_PLAY NS_DEFINE_EVENT_STATE_MACRO(35)
 // Content is in the optimum region.
 #define NS_EVENT_STATE_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(36)
 // Content is in the suboptimal region.
 #define NS_EVENT_STATE_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(37)
 // Content is in the sub-suboptimal region.
 #define NS_EVENT_STATE_SUB_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(38)
@@ -294,15 +294,14 @@ private:
  * NOTE: do not go over 63 without updating EventStates::InternalType!
  */
 
 #define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
 
 #define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS |     \
                             NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER |   \
                             NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \
-                            NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_FULL_SCREEN_ANCESTOR | \
-                            NS_EVENT_STATE_UNRESOLVED)
+                            NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_UNRESOLVED)
 
 #define INTRINSIC_STATES (~ESM_MANAGED_STATES)
 
 #endif // mozilla_EventStates_h_
 
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -237,27 +237,20 @@ HTMLIFrameElement::UnsetAttr(int32_t aNa
 }
 
 uint32_t
 HTMLIFrameElement::GetSandboxFlags()
 {
   const nsAttrValue* sandboxAttr = GetParsedAttr(nsGkAtoms::sandbox);
   // No sandbox attribute, no sandbox flags.
   if (!sandboxAttr) {
-    return 0;
+    return SANDBOXED_NONE;
   }
 
-  //  Start off by setting all the restriction flags.
-  uint32_t out = SANDBOX_ALL_FLAGS;
-
-// Macro for updating the flag according to the keywords
-#define SANDBOX_KEYWORD(string, atom, flags)                             \
-  if (sandboxAttr->Contains(nsGkAtoms::atom, eIgnoreCase)) { out &= ~(flags); }
-#include "IframeSandboxKeywordList.h"
-#undef SANDBOX_KEYWORD
+  uint32_t out = nsContentUtils::ParseSandboxAttributeToFlags(sandboxAttr);
 
   if (GetParsedAttr(nsGkAtoms::allowfullscreen) ||
       GetParsedAttr(nsGkAtoms::mozallowfullscreen)) {
     out &= ~SANDBOXED_FULLSCREEN;
   }
 
   return out;
 }
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3160,17 +3160,23 @@ HTMLInputElement::GetDisplayFileName(nsA
   if (mFilesOrDirectories.Length() == 1) {
     GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
     return;
   }
 
   nsXPIDLString value;
 
   if (mFilesOrDirectories.IsEmpty()) {
-    if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+    if ((Preferences::GetBool("dom.input.dirpicker", false) &&
+         HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) ||
+        (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+         HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+      nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                         "NoDirSelected", value);
+    } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                          "NoFilesSelected", value);
     } else {
       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                          "NoFileSelected", value);
     }
   } else {
     nsString count;
@@ -4400,22 +4406,20 @@ HTMLInputElement::MaybeInitPickers(Event
     // If the user clicked on the "Choose folder..." button we open the
     // directory picker, else we open the file picker.
     FilePickerType type = FILE_PICKER_FILE;
     nsCOMPtr<nsIContent> target =
       do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (target &&
         target->GetParent() == this &&
         target->IsRootOfNativeAnonymousSubtree() &&
-        (target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory) ||
-         target->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
-      MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false) ||
-                 Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false),
-                 "No API or UI should have been exposed to allow this code to "
-                 "be reached");
+        ((Preferences::GetBool("dom.input.dirpicker", false) &&
+          HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) ||
+         (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+          HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
       type = FILE_PICKER_DIRECTORY;
     }
     return InitFilePicker(type);
   }
   if (mType == NS_FORM_INPUT_COLOR) {
     return InitColorPicker();
   }
   if (mType == NS_FORM_INPUT_DATE) {
--- a/dom/html/test/file_fullscreen-api.html
+++ b/dom/html/test/file_fullscreen-api.html
@@ -150,22 +150,16 @@ function error1(event) {
   addFullscreenChangeContinuation("enter", enter4);
   inDocElement.requestFullscreen();
 }
 
 function enter4(event) {
   is(event.target, document, "Event target should be full-screen document #5");
   is(document.fullscreenElement, inDocElement, "FSE should be inDocElement.");
 
-  var n = container;
-  do {
-    ok(n.matches(":-moz-full-screen-ancestor"), "Ancestor " + n + " should match :-moz-full-screen-ancestor")
-    n = n.parentNode;
-  } while (n && n.matches);
-    
   // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
   addFullscreenChangeContinuation("exit", exit_to_arg_test_1);
   container.parentNode.removeChild(container);
   is(document.fullscreenElement, null,
      "Should not have a full-screen element again.");
 }
 
 function exit_to_arg_test_1(event) {
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_iframe_sandbox_c_if9.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 671389</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  I am
+  <ul>
+    <li>sandboxed but with "allow-forms", "allow-pointer-lock", "allow-popups", "allow-same-origin", "allow-scripts", and "allow-top-navigation", </li>
+    <li>sandboxed but with "allow-same-origin", "allow-scripts", </li>
+    <li>sandboxed, or </li>
+    <li>not sandboxed.</li>
+  </ul>
+</body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -93,16 +93,17 @@ support-files =
   file_iframe_sandbox_c_if1.html
   file_iframe_sandbox_c_if2.html
   file_iframe_sandbox_c_if3.html
   file_iframe_sandbox_c_if4.html
   file_iframe_sandbox_c_if5.html
   file_iframe_sandbox_c_if6.html
   file_iframe_sandbox_c_if7.html
   file_iframe_sandbox_c_if8.html
+  file_iframe_sandbox_c_if9.html
   file_iframe_sandbox_close.html
   file_iframe_sandbox_d_if1.html
   file_iframe_sandbox_d_if10.html
   file_iframe_sandbox_d_if11.html
   file_iframe_sandbox_d_if12.html
   file_iframe_sandbox_d_if13.html
   file_iframe_sandbox_d_if14.html
   file_iframe_sandbox_d_if15.html
--- a/dom/html/test/test_iframe_sandbox_general.html
+++ b/dom/html/test/test_iframe_sandbox_general.html
@@ -36,17 +36,17 @@ function ok_wrapper(result, desc) {
   ok(result, desc);
 
   completedTests++;
 
   if (result) {
     passedTests++;
   }
 
-  if (completedTests == 27) {
+  if (completedTests == 33) {
     is(passedTests, completedTests, "There are " + completedTests + " general tests that should pass");
     SimpleTest.finish();
   }
 }
 
 function doTest() {
   // passes twice if good
   // 1) test that inline scripts (<script>) can run in an iframe sandboxed with "allow-scripts"
@@ -175,16 +175,24 @@ function doTest() {
   // done via file_iframe_sandbox_c_if8.html, which has sandbox='allow-scripts allow-same-origin'
 
   // fails if bad
   // 28) Test that a sandboxed iframe can't open a new window using the target.attribute for a
   // non-existing browsing context (BC341604).
   // This is done via file_iframe_sandbox_c_if4.html which is sandboxed with "allow-scripts" and "allow-same-origin"
   // the window it attempts to open calls window.opener.ok(false, ...) and file_iframe_c_if4.html has an ok()
   // function that calls window.parent.ok_wrapper.
+
+  // passes twice if good
+  // 29-32) Test that sandboxFlagsAsString returns the set flags.
+  // see if_14 and if_15
+
+  // passes once if good
+  // 33) Test that sandboxFlagsAsString returns null if iframe does not have sandbox flag set.
+  // see if_16
 }
 
 addLoadEvent(doTest);
 
 var started_if_9 = false;
 var started_if_10 = false;
 
 function start_if_9() {
@@ -207,16 +215,46 @@ function do_if_9() {
   var if_9 = document.getElementById('if_9');
   if_9.src = 'javascript:"<html><script>window.parent.ok_wrapper(false, \'an iframe sandboxed without allow-scripts should not execute script in a javascript URL in a newly set src attribute\');<\/script><\/html>"';
 }
 
 function do_if_10() {
   var if_10 = document.getElementById('if_10');
   if_10.src = 'javascript:"<html><script>window.parent.ok_wrapper(true, \'an iframe sandboxed with allow-scripts should execute script in a javascript URL in a newly set src attribute\');<\/script><\/html>"';
 }
+
+function eqFlags(a, b) {
+  // both a and b should be either null or have the array same flags
+  if (a === null && b === null) { return true; }
+  if (a === null || b === null) { return false; }
+  if (a.length !== b.length) { return false; }
+  var a_sorted = a.sort();
+  var b_sorted = b.sort();
+  for (var i in a_sorted) {
+    if (a_sorted[i] !== b_sorted[i]) { return false; }
+  }
+  return true;
+}
+
+function getSandboxFlags(doc) {
+  var flags = doc.sandboxFlagsAsString;
+  if (flags === null) { return null; }
+  return flags? flags.split(" "):[];
+}
+
+function test_sandboxFlagsAsString(name, expected) {
+  var ifr = document.getElementById(name);
+  try {
+    var flags = getSandboxFlags(SpecialPowers.wrap(ifr).contentDocument);
+    ok_wrapper(eqFlags(flags, expected), name + ' expected: "' + expected + '", got: "' + flags + '"');
+  } catch (e) {
+    ok_wrapper(false, name + ' expected "' + expected + ', but failed with ' + e);
+  }
+}
+
 </script>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=341604">Mozilla Bug 341604</a> - Implement HTML5 sandbox attribute for IFRAMEs
 <p id="display"></p>
 <div id="content">
 <iframe sandbox="allow-same-origin allow-scripts" id="if_1" src="file_iframe_sandbox_c_if1.html" height="10" width="10"></iframe>
 <iframe sandbox="aLlOw-SAME-oRiGin ALLOW-sCrIpTs" id="if_1_case_insensitive" src="file_iframe_sandbox_c_if1.html" height="10" width="10"></iframe>
 <iframe sandbox="" id="if_2" src="file_iframe_sandbox_c_if2.html" height="10" width="10"></iframe>
@@ -229,13 +267,17 @@ function do_if_10() {
 <iframe sandbox="&#x0c;allow-same-origin&#x0c;allow-scripts&#x0c;" id="if_6_d" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
 <iframe sandbox="&#x0d;allow-same-origin&#x0d;allow-scripts&#x0d;" id="if_6_e" src="file_iframe_sandbox_c_if6.html" height="10" width="10"></iframe>
 <iframe sandbox="allow-same-origin" id='if_7' src="javascript:'<html><script>window.parent.ok_wrapper(false, \'an iframe sandboxed without allow-scripts should not execute script in a javascript URL in its src attribute\');<\/script><\/html>';" height="10" width="10"></iframe>
 <iframe sandbox="allow-same-origin allow-scripts" id='if_8' src="javascript:'<html><script>window.parent.ok_wrapper(true, \'an iframe sandboxed without allow-scripts should execute script in a javascript URL in its src attribute\');<\/script><\/html>';" height="10" width="10"></iframe>
 <iframe sandbox="allow-same-origin" onload='start_if_9()' id='if_9' src="about:blank" height="10" width="10"></iframe>
 <iframe sandbox="allow-same-origin allow-scripts" onload='start_if_10()' id='if_10' src="about:blank" height="10" width="10"></iframe>
 <iframe sandbox="allow-scripts" id='if_11' src="file_iframe_sandbox_c_if7.html" height="10" width="10"></iframe>
 <iframe sandbox="allow-same-origin allow-scripts" id='if_12' src="file_iframe_sandbox_c_if8.html" height="10" width="10"></iframe>
+<iframe sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation " id='if_13' src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_13",["allow-forms", "allow-pointer-lock", "allow-popups", "allow-same-origin", "allow-scripts", "allow-top-navigation"])'></iframe>
+<iframe sandbox="&#x09;allow-same-origin&#x09;allow-scripts&#x09;" id="if_14" src="file_iframe_sandbox_c_if6.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_14",["allow-same-origin","allow-scripts"])'></iframe>
+<iframe sandbox="" id="if_15" src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_15",[])'></iframe>
+<iframe id="if_16" src="file_iframe_sandbox_c_if9.html" height="10" width="10" onload='test_sandboxFlagsAsString("if_16",null)'></iframe>
 <input type='button' id="a_button" onclick='do_if_9()'>
 <input type='button' id="a_button2" onclick='do_if_10()'>
 </div>
 </body>
 </html>
--- a/dom/indexedDB/IDBKeyRange.cpp
+++ b/dom/indexedDB/IDBKeyRange.cpp
@@ -106,21 +106,21 @@ IDBKeyRange::FromJSVal(JSContext* aCx,
     // undefined and null returns no IDBKeyRange.
     keyRange.forget(aKeyRange);
     return NS_OK;
   }
 
   JS::Rooted<JSObject*> obj(aCx, aVal.isObject() ? &aVal.toObject() : nullptr);
   bool isValidKey = aVal.isPrimitive();
   if (!isValidKey) {
-    js::ESClassValue cls;
+    js::ESClass cls;
     if (!js::GetBuiltinClass(aCx, obj, &cls)) {
       return NS_ERROR_UNEXPECTED;
     }
-    isValidKey = cls == js::ESClass_Array || cls == js::ESClass_Date;
+    isValidKey = cls == js::ESClass::Array || cls == js::ESClass::Date;
   }
   if (isValidKey) {
     // A valid key returns an 'only' IDBKeyRange.
     keyRange = new IDBKeyRange(nullptr, false, false, true);
 
     nsresult rv = GetKeyFromJSVal(aCx, aVal, keyRange->Lower());
     if (NS_FAILED(rv)) {
       return rv;
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.cpp
@@ -227,22 +227,22 @@ Key::EncodeJSValInternal(JSContext* aCx,
     }
     EncodeNumber(d, eFloat + aTypeOffset);
     return NS_OK;
   }
 
   if (aVal.isObject()) {
     JS::Rooted<JSObject*> obj(aCx, &aVal.toObject());
 
-    js::ESClassValue cls;
+    js::ESClass cls;
     if (!js::GetBuiltinClass(aCx, obj, &cls)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
-    if (cls == js::ESClass_Array) {
+    if (cls == js::ESClass::Array) {
       aTypeOffset += eMaxType;
 
       if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
         mBuffer.Append(aTypeOffset);
         aTypeOffset = 0;
       }
       NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
                    aTypeOffset < (eMaxType * kMaxArrayCollapse),
@@ -270,17 +270,17 @@ Key::EncodeJSValInternal(JSContext* aCx,
         aTypeOffset = 0;
       }
 
       mBuffer.Append(eTerminator + aTypeOffset);
 
       return NS_OK;
     }
 
-    if (cls == js::ESClass_Date) {
+    if (cls == js::ESClass::Date) {
       bool valid;
       if (!js::DateIsValid(aCx, obj, &valid)) {
         IDB_REPORT_INTERNAL_ERR();
         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
       }
       if (!valid)  {
         return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
       }
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -55,16 +55,17 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short BASE_URI_DIRECTIVE             = 13;
   const unsigned short FORM_ACTION_DIRECTIVE          = 14;
   const unsigned short REFERRER_DIRECTIVE             = 15;
   const unsigned short WEB_MANIFEST_SRC_DIRECTIVE     = 16;
   const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE  = 17;
   const unsigned short CHILD_SRC_DIRECTIVE            = 18;
   const unsigned short BLOCK_ALL_MIXED_CONTENT        = 19;
   const unsigned short REQUIRE_SRI_FOR                = 20;
+  const unsigned short SANDBOX_DIRECTIVE              = 21;
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   AString getPolicy(in unsigned long index);
 
   /**
@@ -153,16 +154,27 @@ interface nsIContentSecurityPolicy : nsI
    *     shouldReportViolations is true as well.
    * @return
    *     Whether or not the effects of the eval call should be allowed
    *     (block the call if false).
    */
   boolean getAllowsEval(out boolean shouldReportViolations);
 
   /**
+   * Delegate method called by the service when the protected document is loaded.
+   * Returns the union of all the sandbox flags contained in CSP policies. This is the most
+   * restrictive interpretation of flags set in multiple policies.
+   * See nsSandboxFlags.h for the possible flags.
+   *
+   * @return
+   *    sandbox flags or SANDBOXED_NONE if no sandbox directive exists
+   */
+  uint32_t getCSPSandboxFlags();
+
+  /**
    * For each violated policy (of type violationType), log policy violation on
    * the Error Console and send a report to report-uris present in the violated
    * policies.
    *
    * @param violationType
    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
    * @param sourceFile
    *     name of the source file containing the violation (if available)
--- a/dom/locales/en-US/chrome/layout/HtmlForm.properties
+++ b/dom/locales/en-US/chrome/layout/HtmlForm.properties
@@ -1,17 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Reset=Reset
 Submit=Submit Query
 Browse=Browse…
-ChooseFiles=Choose files…
-ChooseDirs=Choose folder…
 FileUpload=File Upload
 # LOCALIZATION NOTE (IsIndexPromptWithSpace): The last character of the string 
 # should be a space (U+0020) in most locales. The prompt is followed by an 
 # input field. The space needs be escaped in the property file to avoid 
 # trimming.
 IsIndexPromptWithSpace=This is a searchable index. Enter search keywords:\u0020
 ForgotPostWarning=Form contains enctype=%S, but does not contain method=post.  Submitting normally with method=GET and no enctype instead.
 ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.
@@ -20,16 +18,20 @@ DefaultFormSubject=Form Post from %S
 CannotEncodeAllUnicode=A form was submitted in the %S encoding which cannot encode all Unicode characters, so user input may get corrupted. To avoid this problem, the page should be changed so that the form is submitted in the UTF-8 encoding either by changing the encoding of the page itself to UTF-8 or by specifying accept-charset=utf-8 on the form element.
 AllSupportedTypes=All Supported Types
 # LOCALIZATION NOTE (NoFileSelected): this string is shown on a
 # <input type='file'> when there is no file selected yet.
 NoFileSelected=No file selected.
 # LOCALIZATION NOTE (NoFilesSelected): this string is shown on a
 # <input type='file' multiple> when there is no file selected yet.
 NoFilesSelected=No files selected.
+# LOCALIZATION NOTE (NoDirSelected): this string is shown on a
+# <input type='file' directory/webkitdirectory> when there is no directory
+# selected yet.
+NoDirSelected=No directory selected.
 # LOCALIZATION NOTE (XFilesSelected): this string is shown on a
 # <input type='file' multiple> when there are more than one selected file.
 # %S will be a number greater or equal to 2.
 XFilesSelected=%S files selected.
 ColorPicker=Choose a color
 DatePicker=Choose a date
 # LOCALIZATION NOTE (AndNMoreFiles): Semi-colon list of plural forms. 
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals 
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -69,16 +69,19 @@ hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use ‘%2$S’ (wrapped in single quotes).
 # directive is not supported (e.g. 'reflected-xss')
 notSupportingDirective = Not supporting directive ‘%1$S’. Directive and values will be ignored.
 # LOCALIZATION NOTE (blockAllMixedContent):
 # %1$S is the URL of the blocked resource load.
 blockAllMixedContent = Blocking insecure request ‘%1$S’.
 # LOCALIZATION NOTE (ignoringDirectiveWithNoValues):
 # %1$S is the name of a CSP directive that requires additional values (e.g., 'require-sri-for')
 ignoringDirectiveWithNoValues = Ignoring ‘%1$S’ since it does not contain any parameters.
+# LOCALIZATION NOTE (ignoringReportOnlyDirective):
+# %1$S is the directive that is ignored in report-only mode.
+ignoringReportOnlyDirective = Ignoring sandbox directive when delivered in a report-only policy ‘%1$S’
 
 # CSP Errors:
 # LOCALIZATION NOTE (couldntParseInvalidSource):
 # %1$S is the source that could not be parsed
 couldntParseInvalidSource = Couldn’t parse invalid source %1$S
 # LOCALIZATION NOTE (couldntParseInvalidHost):
 # %1$S is the host that's invalid
 couldntParseInvalidHost = Couldn’t parse invalid host %1$S
@@ -89,8 +92,11 @@ couldntParseScheme = Couldn’t parse scheme in %1$S
 # %1$S is the string source
 couldntParsePort = Couldn’t parse port in %1$S
 # LOCALIZATION NOTE (duplicateDirective):
 # %1$S is the name of the duplicate directive
 duplicateDirective = Duplicate %1$S directives detected.  All but the first instance will be ignored.
 # LOCALIZATION NOTE (deprecatedDirective):
 # %1$S is the name of the deprecated directive, %2$S is the name of the replacement.
 deprecatedDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘%2$S’ instead.
+# LOCALIZATION NOTE (couldntParseInvalidSandboxFlag):
+# %1$S is the option that could not be understood
+couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’
--- a/dom/manifest/test/browser_fire_install_event.js
+++ b/dom/manifest/test/browser_fire_install_event.js
@@ -1,32 +1,46 @@
 //Used by JSHint:
-/*global Cu, BrowserTestUtils, ok, add_task, PromiseMessage, gBrowser */
+/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
 "use strict";
 const { PromiseMessage } = Cu.import("resource://gre/modules/PromiseMessage.jsm", {});
 const testPath = "/browser/dom/manifest/test/file_reg_install_event.html";
 const defaultURL = new URL("http://example.org/browser/dom/manifest/test/file_testserver.sjs");
 const testURL = new URL(defaultURL);
 testURL.searchParams.append("file", testPath);
 
+// Enable window.oninstall, so we can fire events at it.
+function enableOnInstallPref() {
+  const ops = {
+    "set": [
+      ["dom.manifest.oninstall", true],
+    ],
+  };
+  return SpecialPowers.pushPrefEnv(ops);
+}
+
 // Send a message for the even to be fired.
 // This cause file_reg_install_event.html to be dynamically change.
 function* theTest(aBrowser) {
+  aBrowser.allowEvents = true;
+  // Resolves when we get a custom event with the correct name
+  const responsePromise = new Promise((resolve) => {
+    aBrowser.contentDocument.addEventListener("dom.manifest.oninstall", resolve);
+  });
   const mm = aBrowser.messageManager;
   const msgKey = "DOM:Manifest:FireInstallEvent";
-  const initialText = aBrowser.contentWindowAsCPOW.document.body.innerHTML.trim()
-  is(initialText, '<h1 id="output">waiting for event</h1>', "should be waiting for event");
   const { data: { success } } = yield PromiseMessage.send(mm, msgKey);
   ok(success, "message sent and received successfully.");
-  const eventText = aBrowser.contentWindowAsCPOW.document.body.innerHTML.trim();
-  is(eventText, '<h1 id="output">event received!</h1>', "text of the page should have changed.");
-};
+  const { detail: { result } } = yield responsePromise;
+  ok(result, "the page sent us an acknowledgment as a custom event");
+}
 
 // Open a tab and run the test
 add_task(function*() {
+  yield enableOnInstallPref();
   let tabOptions = {
     gBrowser: gBrowser,
     url: testURL.href,
   };
   yield BrowserTestUtils.withNewTab(
     tabOptions,
     theTest
   );
--- a/dom/manifest/test/file_reg_install_event.html
+++ b/dom/manifest/test/file_reg_install_event.html
@@ -1,9 +1,15 @@
+<meta charset=utf-8>
 <script>
-window.addEventListener("install", (ev) => {
+"use strict";
+window.addEventListener("install", () => {
   document
     .querySelector("#output")
-    .innerHTML = "event received!"
+    .innerHTML = "event received!";
+  // Send a custom event back to the browser
+  // to acknowledge that we got this
+  const detail = { result: true }
+  const ev = new CustomEvent("dom.manifest.oninstall", { detail });
+  document.dispatchEvent(ev);
 });
 </script>
-<link rel="manifest" href="file_manifest.json">
 <h1 id=output>waiting for event</h1>
--- a/dom/manifest/test/test_window_oninstall_event.html
+++ b/dom/manifest/test/test_window_oninstall_event.html
@@ -1,44 +1,98 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1265279
 -->
+
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1265279 - Web Manifest: Implement window.oninstall</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
   <script>
-"use strict";
-SimpleTest.waitForExplicitFinish();
-is(window.hasOwnProperty("oninstall"), true, "window has own oninstall property");
-is(window.oninstall, null, "window install is initially set to null");
+  "use strict";
+  SimpleTest.waitForExplicitFinish();
+  const finish = SimpleTest.finish.bind(SimpleTest);
+  enableOnInstallPref()
+    .then(createIframe)
+    .then(checkImplementation)
+    .then(checkOnInstallEventFires)
+    .then(checkAddEventListenerFires)
+    .then(finish)
+    .catch(err => {
+      dump(`${err}: ${err.stack}`);
+      finish();
+    });
 
-// Check that enumerable, configurable, and has a getter and setter
-const objDescriptor = Object.getOwnPropertyDescriptor(window, "oninstall");
-is(objDescriptor.enumerable, true, "is enumerable");
-is(objDescriptor.configurable, true, "is configurable");
-is(objDescriptor.hasOwnProperty("get"), true, "has getter");
-is(objDescriptor.hasOwnProperty("set"), true, "has setter");
+  function enableOnInstallPref() {
+    const ops = {
+      "set": [
+        ["dom.manifest.oninstall", true],
+      ],
+    };
+    return SpecialPowers.pushPrefEnv(ops);
+  }
+
+  // WebIDL conditional annotations for an interface are evaluate once per
+  // global, so we need to create an iframe to see the effects of calling
+  // enableOnInstallPref().
+  function createIframe() {
+    return new Promise((resolve) => {
+      const iframe = document.createElement("iframe");
+      iframe.src = "about:blank";
+      iframe.onload = () => resolve(iframe.contentWindow);
+      document.body.appendChild(iframe);
+    });
+  }
+
+  // Check that the WebIDL is as expected.
+  function checkImplementation(ifrWindow) {
+    return new Promise((resolve, reject) => {
+      const hasOnInstallProp = ifrWindow.hasOwnProperty("oninstall");
+      ok(hasOnInstallProp, "window has own oninstall property");
 
-// Test is we receive the event on window.install
-const customEv = new CustomEvent("install");
-window.oninstall = function handler(ev){
-  window.oninstall = null;
-  is(ev, customEv, "The events should be the same");
-  testAddEventListener();
-};
-window.dispatchEvent(customEv);
+      // no point in continuing
+      if (!hasOnInstallProp) {
+        const err = new Error("No 'oninstall' IDL attribute. Aborting early.");
+        return reject(err);
+      }
+      is(ifrWindow.oninstall, null, "window install is initially set to null");
+
+      // Check that enumerable, configurable, and has a getter and setter.
+      const objDescriptor = Object.getOwnPropertyDescriptor(window, "oninstall");
+      ok(objDescriptor.enumerable, "is enumerable");
+      ok(objDescriptor.configurable, "is configurable");
+      ok(objDescriptor.hasOwnProperty("get"), "has getter");
+      ok(objDescriptor.hasOwnProperty("set"), "has setter");
+      resolve(ifrWindow);
+    });
+  }
 
-// Test that it works with .addEventListener("install", f);
-function testAddEventListener(){
-  const customEv2 = new CustomEvent("install");
-  window.addEventListener("install", function handler2(ev2) {
-    window.removeEventListener("install", handler2);
-    is(ev2, customEv2, "The events should be the same");
-    SimpleTest.finish();
-  });
-  window.dispatchEvent(customEv2);
-}
+  // Checks that .oninstall receives an event.
+  function checkOnInstallEventFires(ifrWindow) {
+    const customEv = new CustomEvent("install");
+    return new Promise((resolve) => {
+      // Test is we receive the event on `oninstall`
+      ifrWindow.oninstall = ev => {
+        ifrWindow.oninstall = null;
+        is(ev, customEv, "The events should be the same event object");
+        resolve(ifrWindow);
+      };
+      ifrWindow.dispatchEvent(customEv);
+    });
+  }
+
+  // Checks that .addEventListener("install") receives an event.
+  function checkAddEventListenerFires(ifrWindow) {
+    const customEv = new CustomEvent("install");
+    return new Promise((resolve) => {
+      ifrWindow.addEventListener("install", function handler(ev) {
+        ifrWindow.removeEventListener("install", handler);
+        is(ev, customEv, "The events should be the same");
+        resolve(ifrWindow);
+      });
+      ifrWindow.dispatchEvent(customEv);
+    });
+  }
   </script>
 </head>
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -88,17 +88,18 @@ MP4Decoder::CanHandleMediaType(const nsA
   if (!IsEnabled()) {
     return false;
   }
 
   // Whitelist MP4 types, so they explicitly match what we encounter on
   // the web, as opposed to what we use internally (i.e. what our demuxers
   // etc output).
   const bool isMP4Audio = aMIMETypeExcludingCodecs.EqualsASCII("audio/mp4") ||
-                          aMIMETypeExcludingCodecs.EqualsASCII("audio/x-m4a");
+                          aMIMETypeExcludingCodecs.EqualsASCII("audio/x-m4a") ||
+                          aMIMETypeExcludingCodecs.EqualsASCII("audio/opus");
   const bool isMP4Video =
   // On B2G, treat 3GPP as MP4 when Gonk PDM is available.
 #ifdef MOZ_GONK_MEDIACODEC
     aMIMETypeExcludingCodecs.EqualsASCII(VIDEO_3GPP) ||
 #endif
     aMIMETypeExcludingCodecs.EqualsASCII("video/mp4") ||
     aMIMETypeExcludingCodecs.EqualsASCII("video/quicktime") ||
     aMIMETypeExcludingCodecs.EqualsASCII("video/x-m4v");
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -1,30 +1,35 @@
 /* -*- 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 "AgnosticDecoderModule.h"
+#include "mozilla/Logging.h"
 #include "OpusDecoder.h"
 #include "VorbisDecoder.h"
 #include "VPXDecoder.h"
 #include "WAVDecoder.h"
 
 namespace mozilla {
 
 bool
 AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                         DecoderDoctorDiagnostics* aDiagnostics) const
 {
-  return VPXDecoder::IsVPX(aMimeType) ||
+  bool supports =
+    VPXDecoder::IsVPX(aMimeType) ||
     OpusDataDecoder::IsOpus(aMimeType) ||
     VorbisDataDecoder::IsVorbis(aMimeType) ||
     WaveDataDecoder::IsWave(aMimeType);
+  MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
+        supports ? "supports" : "rejects"));
+  return supports;
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> m;
 
   if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -42,28 +42,38 @@ OpusDataDecoder::~OpusDataDecoder()
 }
 
 nsresult
 OpusDataDecoder::Shutdown()
 {
   return NS_OK;
 }
 
+void
+OpusDataDecoder::AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS)
+{
+  uint8_t buffer[sizeof(uint64_t)];
+  BigEndian::writeUint64(buffer, codecDelayUS);
+  config->AppendElements(buffer, sizeof(uint64_t));
+}
+
 RefPtr<MediaDataDecoder::InitPromise>
 OpusDataDecoder::Init()
 {
   size_t length = mInfo.mCodecSpecificConfig->Length();
   uint8_t *p = mInfo.mCodecSpecificConfig->Elements();
   if (length < sizeof(uint64_t)) {
+    OPUS_DEBUG("CodecSpecificConfig too short to read codecDelay!");
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
   int64_t codecDelay = BigEndian::readUint64(p);
   length -= sizeof(uint64_t);
   p += sizeof(uint64_t);
   if (NS_FAILED(DecodeHeader(p, length))) {
+    OPUS_DEBUG("Error decoding header!");
     return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
   }
 
   int r;
   mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
                                                  mOpusParser->mChannels,
                                                  mOpusParser->mStreams,
                                                  mOpusParser->mCoupledStreams,
@@ -343,13 +353,15 @@ OpusDataDecoder::Flush()
   return NS_OK;
 }
 
 /* static */
 bool
 OpusDataDecoder::IsOpus(const nsACString& aMimeType)
 {
   return aMimeType.EqualsLiteral("audio/webm; codecs=opus") ||
-         aMimeType.EqualsLiteral("audio/ogg; codecs=opus");
+         aMimeType.EqualsLiteral("audio/ogg; codecs=opus") ||
+         aMimeType.EqualsLiteral("audio/mp4; codecs=opus") ||
+         aMimeType.EqualsLiteral("audio/opus");
 }
 
 } // namespace mozilla
 #undef OPUS_DEBUG
--- a/dom/media/platforms/agnostic/OpusDecoder.h
+++ b/dom/media/platforms/agnostic/OpusDecoder.h
@@ -28,16 +28,23 @@ public:
   const char* GetDescriptionName() const override
   {
     return "opus audio decoder";
   }
 
   // Return true if mimetype is Opus
   static bool IsOpus(const nsACString& aMimeType);
 
+  // Pack pre-skip/CodecDelay, given in microseconds, into a
+  // MediaByteBuffer. The decoder expects this value to come
+  // from the container (if any) and to precede the OpusHead
+  // block in the CodecSpecificConfig buffer to verify the
+  // values match.
+  static void AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS);
+
 private:
   enum DecodeError {
     DECODE_SUCCESS,
     DECODE_ERROR,
     FATAL_ERROR
   };
 
   nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
+#include "OpusDecoder.h"
 #include "WebMDemuxer.h"
 #include "WebMBufferedParser.h"
 #include "gfx2DGlue.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/SharedThreadPool.h"
 #include "MediaDataDemuxer.h"
 #include "nsAutoPtr.h"
@@ -391,25 +392,23 @@ WebMDemuxer::ReadMetadata()
       nestegg_audio_params params;
       r = nestegg_track_audio_params(context, track, &params);
       if (r == -1) {
         return NS_ERROR_FAILURE;
       }
 
       mAudioTrack = track;
       mHasAudio = true;
-      mCodecDelay = media::TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds();
       mAudioCodec = nestegg_track_codec_id(context, track);
       if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
         mInfo.mAudio.mMimeType = "audio/webm; codecs=vorbis";
       } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
         mInfo.mAudio.mMimeType = "audio/webm; codecs=opus";
-        uint8_t c[sizeof(uint64_t)];
-        BigEndian::writeUint64(&c[0], mCodecDelay);
-        mInfo.mAudio.mCodecSpecificConfig->AppendElements(&c[0], sizeof(uint64_t));
+        OpusDataDecoder::AppendCodecDelay(mInfo.mAudio.mCodecSpecificConfig,
+            media::TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds());
       }
       mSeekPreroll = params.seek_preroll;
       mInfo.mAudio.mRate = params.rate;
       mInfo.mAudio.mChannels = params.channels;
 
       unsigned int nheaders = 0;
       r = nestegg_track_codec_data_count(context, track, &nheaders);
       if (r == -1) {
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -208,19 +208,16 @@ private:
   // Queue of video and audio packets that have been read but not decoded.
   WebMPacketQueue mVideoPackets;
   WebMPacketQueue mAudioPackets;
 
   // Index of video and audio track to play
   uint32_t mVideoTrack;
   uint32_t mAudioTrack;
 
-  // Number of microseconds that must be discarded from the start of the Stream.
-  uint64_t mCodecDelay;
-
   // Nanoseconds to discard after seeking.
   uint64_t mSeekPreroll;
 
   // Calculate the frame duration from the last decodeable frame using the
   // previous frame's timestamp.  In NS.
   Maybe<int64_t> mLastAudioFrameTime;
   Maybe<int64_t> mLastVideoFrameTime;
 
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -95,16 +95,28 @@ interface nsIPluginHost : nsISupports
    *
    * @mimeType The MIME type we're interested in.
    * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
+   * Get the "permission string" for the plugin.  This is a string that can be
+   * passed to the permission manager to see whether the plugin is allowed to
+   * run, for example.  This will typically be based on the plugin's "nice name"
+   * and its blocklist state.
+   *
+   * @tag The tage we're interested in
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
+   */
+  ACString getPermissionStringForTag(in nsIPluginTag tag,
+                                     [optional] in uint32_t excludeFlags);
+
+  /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
    * A fake plugin tag, if one exists and is available, will be returned in
    * preference to NPAPI plugin tags unless excluded by the excludeFlags.
    *
    * @mimeType The MIME type we're interested in.
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1105,33 +1105,41 @@ NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(tag, NS_ERROR_FAILURE);
+  return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag,
+                                        uint32_t aExcludeFlags,
+                                        nsACString &aPermissionString)
+{
+  NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE);
 
   aPermissionString.Truncate();
   uint32_t blocklistState;
-  rv = tag->GetBlocklistState(&blocklistState);
+  nsresult rv = aTag->GetBlocklistState(&blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aPermissionString.AssignLiteral("plugin-vulnerable:");
   }
   else {
     aPermissionString.AssignLiteral("plugin:");
   }
 
   nsCString niceName;
-  rv = tag->GetNiceName(niceName);
+  rv = aTag->GetNiceName(niceName);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
 
   aPermissionString.Append(niceName);
 
   return NS_OK;
 }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -318,17 +318,17 @@ nsPluginTag::nsPluginTag(uint32_t aId,
 {
 }
 
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
-NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag, nsIInternalPluginTag)
+NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag,  nsIInternalPluginTag, nsIPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
                            const char* const* aMimeDescriptions,
                            const char* const* aExtensions,
                            uint32_t aVariantCount)
 {
   if (!aMimeTypes) {
     return;
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -25,16 +25,19 @@ struct FakePluginTagInit;
 } // namespace dom
 } // namespace mozilla
 
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
+#define NS_PLUGINTAG_IID \
+  { 0xcce2e8b9, 0x9702, 0x4d4b, \
+   { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } }
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
 
   nsIInternalPluginTag();
   nsIInternalPluginTag(const char* aName, const char* aDescription,
                        const char* aFileName, const char* aVersion);
@@ -85,16 +88,18 @@ protected:
 };
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
 
 // A linked-list of plugin information that is used for instantiating plugins
 // and reflecting plugin information into JavaScript.
 class nsPluginTag final : public nsIInternalPluginTag
 {
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID)
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
 
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
@@ -187,16 +192,17 @@ private:
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   void InitSandboxLevel();
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 
   static uint32_t sNextId;
 };
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID)
 
 // A class representing "fake" plugin tags; that is plugin tags not
 // corresponding to actual NPAPI plugins.  In practice these are all
 // JS-implemented plugins; maybe we want a better name for this class?
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -37,16 +37,17 @@
 #include "nsString.h"
 #include "nsScriptSecurityManager.h"
 #include "nsStringStream.h"
 #include "mozilla/Logging.h"
 #include "mozilla/dom/CSPReportBinding.h"
 #include "mozilla/dom/CSPDictionariesBinding.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "nsINetworkInterceptController.h"
+#include "nsSandboxFlags.h"
 
 using namespace mozilla;
 
 static LogModule*
 GetCspContextLog()
 {
   static LazyLogModule gCspContextPRLog("CSPContext");
   return gCspContextPRLog;
@@ -1318,16 +1319,55 @@ nsCSPContext::ToJSON(nsAString& outCSPin
 
   // convert the gathered information to JSON
   if (!jsonPolicies.ToJSON(outCSPinJSON)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsCSPContext::GetCSPSandboxFlags(uint32_t* aOutSandboxFlags)
+{
+  if (!aOutSandboxFlags) {
+    return NS_ERROR_FAILURE;
+  }
+  *aOutSandboxFlags = SANDBOXED_NONE;
+
+  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+    uint32_t flags = mPolicies[i]->getSandboxFlags();
+
+    // current policy doesn't have sandbox flag, check next policy
+    if (!flags) {
+      continue;
+    }
+
+    // current policy has sandbox flags, if the policy is in enforcement-mode
+    // (i.e. not report-only) set these flags and check for policies with more
+    // restrictions
+    if (!mPolicies[i]->getReportOnlyFlag()) {
+      *aOutSandboxFlags |= flags;
+    } else {
+      // sandbox directive is ignored in report-only mode, warn about it and
+      // continue the loop checking for an enforcement policy.
+      nsAutoString policy;
+      mPolicies[i]->toString(policy);
+
+      CSPCONTEXTLOG(("nsCSPContext::GetCSPSandboxFlags, report only policy, ignoring sandbox in: %s",
+                    policy.get()));
+
+      const char16_t* params[] = { policy.get() };
+      logToConsole(MOZ_UTF16("ignoringReportOnlyDirective"), params, ArrayLength(params),
+                   EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag);
+    }
+  }
+
+  return NS_OK;
+}
+
 /* ========== CSPViolationReportListener implementation ========== */
 
 NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports);
 
 CSPViolationReportListener::CSPViolationReportListener()
 {
 }
 
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -2,16 +2,17 @@
 /* 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/ArrayUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
 #include "nsCSPParser.h"
 #include "nsCSPUtils.h"
 #include "nsIConsoleService.h"
 #include "nsIContentPolicy.h"
 #include "nsIScriptError.h"
 #include "nsIStringBundle.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
@@ -988,16 +989,51 @@ nsCSPParser::reportURIList(nsTArray<nsCS
     }
 
     // Create new nsCSPReportURI and append to the list.
     nsCSPReportURI* reportURI = new nsCSPReportURI(uri);
     outSrcs.AppendElement(reportURI);
   }
 }
 
+/* Helper function for parsing sandbox flags. This function solely concatenates
+ * all the source list tokens (the sandbox flags) so the attribute parser
+ * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them.
+ */
+void
+nsCSPParser::sandboxFlagList(nsTArray<nsCSPBaseSrc*>& outSrcs)
+{
+  nsAutoString flags;
+
+  // remember, srcs start at index 1
+  for (uint32_t i = 1; i < mCurDir.Length(); i++) {
+    mCurToken = mCurDir[i];
+
+    CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s",
+                 NS_ConvertUTF16toUTF8(mCurToken).get(),
+                 NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+    if (!nsContentUtils::IsValidSandboxFlag(mCurToken)) {
+      const char16_t* params[] = { mCurToken.get() };
+      logWarningErrorToConsole(nsIScriptError::warningFlag,
+                               "couldntParseInvalidSandboxFlag",
+                               params, ArrayLength(params));
+      continue;
+    }
+
+    flags.Append(mCurToken);
+    if (i != mCurDir.Length() - 1) {
+      flags.AppendASCII(" ");
+    }
+  }
+
+  nsCSPSandboxFlags* sandboxFlags = new nsCSPSandboxFlags(flags);
+  outSrcs.AppendElement(sandboxFlags);
+}
+
 // directive-value = *( WSP / <VCHAR except ";" and ","> )
 void
 nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs)
 {
   CSPPARSERLOG(("nsCSPParser::directiveValue"));
 
   // The tokenzier already generated an array in the form of
   // [ name, src, src, ... ], no need to parse again, but
@@ -1009,16 +1045,23 @@ nsCSPParser::directiveValue(nsTArray<nsC
 
   // special case handling of the referrer directive (since it doesn't contain
   // source lists)
   if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
     referrerDirectiveValue();
     return;
   }
 
+  // For the sandbox flag the source list is a list of flags, so we're special
+  // casing this directive
+  if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
+    sandboxFlagList(outSrcs);
+    return;
+  }
+
   // Otherwise just forward to sourceList
   sourceList(outSrcs);
 }
 
 // directive-name = 1*( ALPHA / DIGIT / "-" )
 nsCSPDirective*
 nsCSPParser::directiveName()
 {
@@ -1056,17 +1099,18 @@ nsCSPParser::directiveName()
     return nullptr;
   }
 
   // CSP delivered via meta tag should ignore the following directives:
   // report-uri, frame-ancestors, and sandbox, see:
   // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
   if (mDeliveredViaMetaTag &&
        ((CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) ||
-        (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)))) {
+        (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)) ||
+        (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)))) {
     // log to the console to indicate that meta CSP is ignoring the directive
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag,
                              "ignoringSrcFromMetaCSP",
                              params, ArrayLength(params));
     return nullptr;
   }
 
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -132,24 +132,25 @@ class nsCSPParser {
     nsCSPHashSrc*       hashSource();
     nsCSPHostSrc*       appHost(); // helper function to support app specific hosts
     nsCSPHostSrc*       host();
     bool                hostChar();
     bool                schemeChar();
     bool                port();
     bool                path(nsCSPHostSrc* aCspHost);
 
-    bool subHost();                                       // helper function to parse subDomains
-    bool atValidUnreservedChar();                         // helper function to parse unreserved
-    bool atValidSubDelimChar();                           // helper function to parse sub-delims
-    bool atValidPctEncodedChar();                         // helper function to parse pct-encoded
-    bool subPath(nsCSPHostSrc* aCspHost);                 // helper function to parse paths
-    void reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs); // helper function to parse report-uris
-    void percentDecodeStr(const nsAString& aEncStr,       // helper function to percent-decode
+    bool subHost();                                         // helper function to parse subDomains
+    bool atValidUnreservedChar();                           // helper function to parse unreserved
+    bool atValidSubDelimChar();                             // helper function to parse sub-delims
+    bool atValidPctEncodedChar();                           // helper function to parse pct-encoded
+    bool subPath(nsCSPHostSrc* aCspHost);                   // helper function to parse paths
+    void reportURIList(nsTArray<nsCSPBaseSrc*>& outSrcs);   // helper function to parse report-uris
+    void percentDecodeStr(const nsAString& aEncStr,         // helper function to percent-decode
                           nsAString& outDecStr);
+    void sandboxFlagList(nsTArray<nsCSPBaseSrc*>& outSrcs); // helper function to parse sandbox flags
 
     inline bool atEnd()
     {
       return mCurChar >= mEndChar;
     }
 
     inline bool accept(char16_t aSymbol)
     {
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -1,23 +1,26 @@
 /* -*- 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 "nsAttrValue.h"
+#include "nsContentUtils.h"
 #include "nsCSPUtils.h"
 #include "nsDebug.h"
 #include "nsIConsoleService.h"
 #include "nsICryptoHash.h"
 #include "nsIScriptError.h"
 #include "nsIServiceManager.h"
 #include "nsIStringBundle.h"
 #include "nsIURL.h"
 #include "nsReadableUtils.h"
+#include "nsSandboxFlags.h"
 
 static mozilla::LogModule*
 GetCspUtilsLog()
 {
   static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
   return gCspUtilsPRLog;
 }
 
@@ -787,16 +790,39 @@ nsCSPReportURI::toString(nsAString& outS
   nsAutoCString spec;
   nsresult rv = mReportURI->GetSpec(spec);
   if (NS_FAILED(rv)) {
     return;
   }
   outStr.AppendASCII(spec.get());
 }
 
+/* ===== nsCSPSandboxFlags ===================== */
+
+nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags)
+  : mFlags(aFlags)
+{
+}
+
+nsCSPSandboxFlags::~nsCSPSandboxFlags()
+{
+}
+
+bool
+nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const
+{
+  return false;
+}
+
+void
+nsCSPSandboxFlags::toString(nsAString& outStr) const
+{
+  outStr.Append(mFlags);
+}
+
 /* ===== nsCSPDirective ====================== */
 
 nsCSPDirective::nsCSPDirective(CSPDirective aDirective)
 {
   mDirective = aDirective;
 }
 
 nsCSPDirective::~nsCSPDirective()
@@ -948,16 +974,21 @@ nsCSPDirective::toDomCSPStruct(mozilla::
       // does not have any srcs
       return;
 
     case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
       outCSP.mChild_src.Construct();
       outCSP.mChild_src.Value() = mozilla::Move(srcs);
       return;
 
+    case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
+      outCSP.mSandbox.Construct();
+      outCSP.mSandbox.Value() = mozilla::Move(srcs);
+      return;
+
     // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct()
 
     default:
       NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
   }
 }
 
 
@@ -1339,16 +1370,43 @@ nsCSPPolicy::getDirectiveAsString(CSPDir
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(aDir)) {
       mDirectives[i]->toString(outDirective);
       return;
     }
   }
 }
 
+/*
+ * Helper function that returns the underlying bit representation of sandbox
+ * flags. The function returns SANDBOXED_NONE if there are no sandbox
+ * directives.
+ */
+uint32_t
+nsCSPPolicy::getSandboxFlags() const
+{
+  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+    if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
+      nsAutoString flags;
+      mDirectives[i]->toString(flags);
+
+      if (flags.IsEmpty()) {
+        return SANDBOX_ALL_FLAGS;
+      }
+
+      nsAttrValue attr;
+      attr.ParseAtomArray(flags);
+
+      return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
+    }
+  }
+
+  return SANDBOXED_NONE;
+}
+
 void
 nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
       mDirectives[i]->getReportURIs(outReportURIs);
       return;
     }
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -87,18 +87,18 @@ static const char* CSPStrDirectives[] = 
   "reflected-xss",             // REFLECTED_XSS_DIRECTIVE
   "base-uri",                  // BASE_URI_DIRECTIVE
   "form-action",               // FORM_ACTION_DIRECTIVE
   "referrer",                  // REFERRER_DIRECTIVE
   "manifest-src",              // MANIFEST_SRC_DIRECTIVE
   "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE
   "child-src",                 // CHILD_SRC_DIRECTIVE
   "block-all-mixed-content",   // BLOCK_ALL_MIXED_CONTENT
-  "require-sri-for"            // REQUIRE_SRI_FOR
-
+  "require-sri-for",           // REQUIRE_SRI_FOR
+  "sandbox"                    // SANDBOX_DIRECTIVE
 };
 
 inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
 {
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
 inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
@@ -329,16 +329,30 @@ class nsCSPReportURI : public nsCSPBaseS
 
     bool visit(nsCSPSrcVisitor* aVisitor) const;
     void toString(nsAString& outStr) const;
 
   private:
     nsCOMPtr<nsIURI> mReportURI;
 };
 
+/* =============== nsCSPSandboxFlags ================== */
+
+class nsCSPSandboxFlags : public nsCSPBaseSrc {
+  public:
+    explicit nsCSPSandboxFlags(const nsAString& aFlags);
+    virtual ~nsCSPSandboxFlags();
+
+    bool visit(nsCSPSrcVisitor* aVisitor) const;
+    void toString(nsAString& outStr) const;
+
+  private:
+    nsString mFlags;
+};
+
 /* =============== nsCSPSrcVisitor ================== */
 
 class nsCSPSrcVisitor {
   public:
     virtual bool visitSchemeSrc(const nsCSPSchemeSrc& src) = 0;
 
     virtual bool visitHostSrc(const nsCSPHostSrc& src) = 0;
 
@@ -553,16 +567,18 @@ class nsCSPPolicy {
 
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
     void getDirectiveStringForContentType(nsContentPolicyType aContentType,
                                           nsAString& outDirective) const;
 
     void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
 
+    uint32_t getSandboxFlags() const;
+
     bool requireSRIForType(nsContentPolicyType aContentType);
 
     inline uint32_t getNumDirectives() const
       { return mDirectives.Length(); }
 
     bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const;
 
   private:
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -170,16 +170,23 @@ DoContentSecurityChecks(nsIChannel* aCha
   
   nsContentPolicyType contentPolicyType =
     aLoadInfo->GetExternalContentPolicyType();
   nsContentPolicyType internalContentPolicyType =
     aLoadInfo->InternalContentPolicyType();
   nsCString mimeTypeGuess;
   nsCOMPtr<nsINode> requestingContext = nullptr;
 
+#ifdef DEBUG
+  // Don't enforce TYPE_DOCUMENT assertions for loads
+  // initiated by javascript tests.
+  bool skipContentTypeCheck = false;
+  skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
   switch(contentPolicyType) {
     case nsIContentPolicy::TYPE_OTHER: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_SCRIPT: {
@@ -202,17 +209,17 @@ DoContentSecurityChecks(nsIChannel* aCha
 
     case nsIContentPolicy::TYPE_OBJECT: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_DOCUMENT: {
-      MOZ_ASSERT(false, "contentPolicyType not supported yet");
+      MOZ_ASSERT(skipContentTypeCheck || false, "contentPolicyType not supported yet");
       break;
     }
 
     case nsIContentPolicy::TYPE_SUBDOCUMENT: {
       mimeTypeGuess = NS_LITERAL_CSTRING("text/html");
       requestingContext = aLoadInfo->LoadingNode();
       MOZ_ASSERT(!requestingContext ||
                  requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_document_write.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+  function doStuff() {
+    var beforePrincipal = SpecialPowers.wrap(document).nodePrincipal;
+    document.open();
+    document.write("rewritten sandboxed document");
+    document.close();
+    var afterPrincipal = SpecialPowers.wrap(document).nodePrincipal;
+    ok(beforePrincipal.equals(afterPrincipal),
+       "document.write() does not change underlying principal");
+  }
+</script>
+<body onLoad='doStuff();'>
+  sandboxed with allow-scripts
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_1.html
@@ -0,0 +1,16 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox="allow-same-origin" -->
+    <!-- Content-Security-Policy: default-src 'self' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img1_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img1a_good&type=img/png" />
+    <!-- should not execute script -->
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_10.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- Content-Security-Policy: default-src 'none'; sandbox -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img10_bad&type=img/png"> </img>
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img10a_bad&type=img/png" />
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_11.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+  }
+</script>
+<script src='file_sandbox_fail.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with only inline "allow-scripts"
+
+ <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img11_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img11a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script11_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script11a_bad&type=text/javascript'></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_12.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+    document.getElementById('a_form').submit();
+
+    // trigger the javascript: url test
+    sendMouseEvent({type:'click'}, 'a_link');
+  }
+</script>
+<script src='file_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with "allow-same-origin" and allow-scripts"
+
+
+  <!-- Content-Security-Policy: sandbox allow-same-origin allow-scripts; default-src 'self' 'unsafe-inline'; -->
+
+  <!-- these should be stopped by CSP -->
+  <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img12_bad&type=img/png"> </img>
+  <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script12_bad&type=text/javascript'></script>
+
+  <form method="get" action="/tests/content/html/content/test/file_iframe_sandbox_form_fail.html" id="a_form">
+    First name: <input type="text" name="firstname">
+    Last name: <input type="text" name="lastname">
+    <input type="submit" onclick="doSubmit()" id="a_button">
+  </form>
+
+  <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_2.html
@@ -0,0 +1,16 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox -->
+    <!-- Content-Security-Policy: default-src 'self' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img2a_good&type=img/png" />
+    <!-- should not execute script -->
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_3.html
@@ -0,0 +1,13 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox="allow-same-origin" -->
+    <!-- Content-Security-Policy: default-src 'none' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img3_bad&type=img/png"> </img>
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img3a_bad&type=img/png" />
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_4.html
@@ -0,0 +1,13 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- sandbox -->
+    <!-- Content-Security-Policy: default-src 'none' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/base/test/csp/file_CSP.sjs?testid=img4_bad&type=img/png"> </img>
+    <img src="/tests/dom/base/test/csp/file_CSP.sjs?testid=img4a_bad&type=img/png" />
+    <script src='/tests/dom/base/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_5.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+  }
+</script>
+<script src='file_sandbox_fail.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with only inline "allow-scripts"
+
+ <!-- sandbox="allow-scripts" -->
+ <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img5_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img5a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script5_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script5a_bad&type=text/javascript'></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_6.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+  function ok(result, desc) {
+    window.parent.postMessage({ok: result, desc: desc}, "*");
+  }
+
+  function doStuff() {
+    ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+    document.getElementById('a_form').submit();
+
+    // trigger the javascript: url test
+    sendMouseEvent({type:'click'}, 'a_link');
+  }
+</script>
+<script src='file_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+  I am sandboxed but with "allow-same-origin" and allow-scripts"
+  <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img6_bad&type=img/png"> </img>
+  <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script6_bad&type=text/javascript'></script>
+
+  <form method="get" action="/tests/content/html/content/test/file_iframe_sandbox_form_fail.html" id="a_form">
+    First name: <input type="text" name="firstname">
+    Last name: <input type="text" name="lastname">
+    <input type="submit" onclick="doSubmit()" id="a_button">
+  </form>
+
+  <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_7.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- Content-Security-Policy: default-src 'self'; sandbox allow-same-origin -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img7_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img7a_good&type=img/png" />
+    <!-- should not execute script -->
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_8.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- Content-Security-Policy: sandbox; default-src 'self' -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img8_bad&type=img/png"> </img>
+
+    <!-- these should load ok -->
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img8a_good&type=img/png" />
+    <!-- should not execute script -->
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_9.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+    <!-- Content-Security-Policy: default-src 'none'; sandbox allow-same-origin -->
+
+    <!-- these should be stopped by CSP -->
+    <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img9_bad&type=img/png"> </img>
+    <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img9a_bad&type=img/png" />
+
+    <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_fail.js
@@ -0,0 +1,4 @@
+function ok(result, desc) {
+  window.parent.postMessage({ok: result, desc: desc}, "*");
+}
+ok(false, "documents sandboxed with allow-scripts should NOT be able to run <script src=...>");
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_pass.js
@@ -0,0 +1,4 @@
+function ok(result, desc) {
+  window.parent.postMessage({ok: result, desc: desc}, "*");
+}
+ok(true, "documents sandboxed with allow-scripts should be able to run <script src=...>");
--- a/dom/security/test/csp/file_testserver.sjs
+++ b/dom/security/test/csp/file_testserver.sjs
@@ -34,17 +34,24 @@ function handleRequest(request, response
   // avoid confusing cache behaviors
   response.setHeader("Cache-Control", "no-cache", false);
 
   // Deliver the CSP policy encoded in the URL
   if(query.has("csp")){
     response.setHeader("Content-Security-Policy", query.get("csp"), false);
   }
 
+  // Deliver the CSP report-only policy encoded in the URI
+  if(query.has("cspRO")){
+    response.setHeader("Content-Security-Policy-Report-Only", query.get("cspRO"), false);
+  }
+
   // Deliver the CORS header in the URL
   if(query.has("cors")){
     response.setHeader("Access-Control-Allow-Origin", query.get("cors"), false);
   }
 
   // Send HTML to test allowed/blocked behaviors
   response.setHeader("Content-Type", "text/html", false);
-  response.write(loadHTMLFromFile(query.get("file")));
+  if(query.has("file")){
+    response.write(loadHTMLFromFile(query.get("file")));
+  }
 }
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -158,16 +158,32 @@ support-files =
   file_fontloader.woff
   file_block_all_mcb.sjs
   file_block_all_mixed_content_frame_navigation1.html
   file_block_all_mixed_content_frame_navigation2.html
   file_form_action_server.sjs
   !/image/test/mochitest/blue.png
   file_meta_whitespace_skipping.html
   file_ping.html
+  test_iframe_sandbox_top_1.html^headers^
+  file_iframe_sandbox_document_write.html
+  file_sandbox_pass.js
+  file_sandbox_fail.js
+  file_sandbox_1.html
+  file_sandbox_2.html
+  file_sandbox_3.html
+  file_sandbox_4.html
+  file_sandbox_5.html
+  file_sandbox_6.html
+  file_sandbox_7.html
+  file_sandbox_8.html
+  file_sandbox_9.html
+  file_sandbox_10.html
+  file_sandbox_11.html
+  file_sandbox_12.html
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
 [test_allow_https_schemes.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_bug663567.html]
@@ -244,9 +260,12 @@ skip-if = toolkit == 'android' #investig
 [test_multipartchannel.html]
 [test_fontloader.html]
 [test_block_all_mixed_content.html]
 tags = mcb
 [test_block_all_mixed_content_frame_navigation.html]
 tags = mcb
 [test_form_action_blocks_url.html]
 [test_meta_whitespace_skipping.html]
+[test_iframe_sandbox.html]
+[test_iframe_sandbox_top_1.html]
+[test_sandbox.html]
 [test_ping.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox.html
@@ -0,0 +1,239 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=671389
+Bug 671389 - Implement CSP sandbox directive
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tests for Bug 671389</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+
+  SimpleTest.waitForExplicitFinish();
+
+  // Check if two sandbox flags are the same, ignoring case-sensitivity.
+  // getSandboxFlags returns a list of sandbox flags (if any) or
+  // null if the flag is not set.
+  // This function checks if two flags are the same, i.e., they're
+  // either not set or have the same flags.
+  function eqFlags(a, b) {
+    if (a === null && b === null) { return true; }
+    if (a === null || b === null) { return false; }
+    if (a.length !== b.length) {  return false; }
+    var a_sorted = a.map(function(e) { return e.toLowerCase(); }).sort();
+    var b_sorted = b.map(function(e) { return e.toLowerCase(); }).sort();
+    for (var i in a_sorted) {
+      if (a_sorted[i] !== b_sorted[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Get the sandbox flags of document doc.
+  // If the flag is not set sandboxFlagsAsString returns null,
+  // this function also returns null.
+  // If the flag is set it may have some flags; in this case
+  // this function returns the (potentially empty) list of flags.
+  function getSandboxFlags(doc) {
+    var flags = doc.sandboxFlagsAsString;
+    if (flags === null) { return null; }
+    return flags? flags.split(" "):[];
+  }
+
+  // Constructor for a CSP sandbox flags test. The constructor
+  // expectes a description 'desc' and set of options 'opts':
+  //  - sandboxAttribute: [null] or string corresponding to the iframe sandbox attributes
+  //  - csp: [null] or string corresponding to the CSP sandbox flags
+  //  - cspReportOnly: [null] or string corresponding to the CSP report-only sandbox flags
+  //  - file: [null] or string corresponding to file the server should serve
+  // Above, we use [brackets] to denote default values.
+  function CSPFlagsTest(desc, opts) {
+    function ifundef(x, v) {
+      return (x !== undefined) ? x : v;
+    }
+
+    function intersect(as, bs) { // Intersect two csp attributes:
+      as = as === null ? null
+                       : as.split(' ').filter(function(x) { return !!x; });
+      bs = bs === null ? null
+                       : bs.split(' ').filter(function(x) { return !!x; });
+
+      if (as === null) { return bs; }
+      if (bs === null) { return as; }
+
+      var cs = [];
+      as.forEach(function(a) {
+        if (a && bs.indexOf(a) != -1)
+          cs.push(a);
+      });
+      return cs;
+    }
+
+    this.desc     = desc || "Untitled test";
+    this.attr     = ifundef(opts.sandboxAttribute, null);
+    this.csp      = ifundef(opts.csp, null);
+    this.cspRO    = ifundef(opts.cspReportOnly, null);
+    this.file     = ifundef(opts.file, null);
+    this.expected = intersect(this.attr, this.csp);
+  }
+
+  // Return function that checks that the actual flags are the same as the
+  // expected flags
+  CSPFlagsTest.prototype.checkFlags = function(iframe) {
+    var this_ = this;
+    return function() {
+      try {
+        var actual = getSandboxFlags(SpecialPowers.wrap(iframe).contentDocument);
+        ok(eqFlags(actual, this_.expected),
+           this_.desc, 'expected: "' + this_.expected + '", got: "' + actual + '"');
+      } catch (e) {
+        ok(false, this_.desc, 'expected: "' + this_.expected + '", failed with: "' + e + '"');
+      }
+      runNextTest();
+     };
+  };
+
+  // Set the iframe src and sandbox attribute
+  CSPFlagsTest.prototype.runTest = function () {
+    var iframe = document.createElement('iframe');
+    document.getElementById("content").appendChild(iframe);
+    iframe.onload = this.checkFlags(iframe);
+
+    // set sandbox attribute
+    if (this.attr === null) {
+      iframe.removeAttribute('sandbox');
+    } else {
+      iframe.sandbox = this.attr;
+    }
+
+    // set query string
+    var src = 'http://mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs';
+
+    var delim = '?';
+
+    if (this.csp !== null) {
+      src += delim + 'csp=' + escape('sandbox ' + this.csp);
+      delim = '&';
+    }
+
+    if (this.cspRO !== null) {
+      src += delim + 'cspRO=' + escape('sandbox ' + this.cspRO);
+      delim = '&';
+    }
+
+    if (this.file !== null) {
+      src += delim + 'file=' + escape(this.file);
+      delim = '&';
+    }
+
+    iframe.src = src;
+    iframe.width = iframe.height = 10;
+
+  }
+
+  testCases = [
+    {
+      desc: "Test 1: Header should not override attribute",
+      sandboxAttribute: "",
+      csp: "allow-forms aLLOw-POinter-lock alLOW-popups aLLOW-SAME-ORIGin ALLOW-SCRIPTS allow-top-navigation"
+    },
+    {
+      desc: "Test 2: Attribute should not override header",
+      sandboxAttribute: "sandbox allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation",
+      csp: ""
+    },
+    {
+      desc: "Test 3: Header and attribute intersect",
+      sandboxAttribute: "allow-same-origin allow-scripts",
+      csp: "allow-forms allow-same-origin allow-scripts"
+    },
+    {
+      desc: "Test 4: CSP sandbox sets the right flags (pt 1)",
+      csp: "alLOW-FORms ALLOW-pointer-lock allow-popups allow-same-origin allow-scripts ALLOW-TOP-NAVIGation"
+    },
+    {
+      desc: "Test 5: CSP sandbox sets the right flags (pt 2)",
+      csp: "allow-same-origin allow-TOP-navigation"
+    },
+    {
+      desc: "Test 6: CSP sandbox sets the right flags (pt 3)",
+      csp: "allow-FORMS ALLOW-scripts"
+    },
+    {
+      desc: "Test 7: CSP sandbox sets the right flags (pt 4)",
+      csp: ""
+    },
+    {
+      desc: "Test 8: CSP sandbox sets the right flags (pt 5)",
+      csp: null
+    },
+    {
+      desc: "Test 9: Read-only header should not override attribute",
+      sandboxAttribute: "",
+      cspReportOnly: "allow-forms ALLOW-pointer-lock allow-POPUPS allow-same-origin ALLOW-scripts allow-top-NAVIGATION"
+    },
+    {
+      desc: "Test 10: Read-only header should not override CSP header",
+      csp: "allow-forms allow-scripts",
+      cspReportOnly: "allow-forms aLlOw-PoInTeR-lOcK aLLow-pOPupS aLLoW-SaME-oRIgIN alLow-scripts allow-tOp-navigation"
+    },
+    {
+      desc: "Test 11: Read-only header should not override attribute or CSP header",
+      sandboxAttribute: "allow-same-origin allow-scripts",
+      csp: "allow-forms allow-same-origin allow-scripts",
+      cspReportOnly: "allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation"
+    },
+    {
+      desc: "Test 12: CSP sandbox not affected by document.write()",
+      csp: "allow-scripts",
+      file: 'tests/dom/security/test/csp/file_iframe_sandbox_document_write.html'
+    },
+  ].map(function(t) { return (new CSPFlagsTest(t.desc,t)); });
+
+
+  var testCaseIndex = 0;
+
+  // Track ok messages from iframes
+  var childMessages = 0;
+  var totalChildMessages = 1;
+
+
+  // Check to see if we ran all the tests and received all messges
+  // from child iframes. If so, finish.
+  function tryFinish() {
+    if (testCaseIndex === testCases.length && childMessages === totalChildMessages){
+      SimpleTest.finish();
+    }
+  }
+
+  function runNextTest() {
+
+    tryFinish();
+
+    if (testCaseIndex < testCases.length) {
+      testCases[testCaseIndex].runTest();
+      testCaseIndex++;
+    }
+  }
+
+  function receiveMessage(event) {
+    ok(event.data.ok, event.data.desc);
+    childMessages++;
+    tryFinish();
+  }
+
+  window.addEventListener("message", receiveMessage, false);
+
+  addLoadEvent(runNextTest);
+</script>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=671389">Mozilla Bug 671389</a> - Implement CSP sandbox directive
+  <p id="display"></p>
+  <div id="content">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_top_1.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=671389
+Bug 671389 - Implement CSP sandbox directive
+
+Tests CSP sandbox attribute on top-level page.
+
+Minimal flags: allow-same-origin allow-scripts:
+Since we need to load the SimpleTest files, we have to set the
+allow-same-origin flag. Additionally, we set the allow-scripts flag
+since we need JS to check the flags.
+
+Though not necessary, for this test we also set the allow-forms flag.
+We may later wish to extend the testing suite with sandbox_csp_top_*
+tests that set different permutations of the flags.
+
+CSP header: Content-Security-Policy: sandbox allow-forms allow-scripts allow-same-origin
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tests for Bug 671389</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Check if two sandbox flags are the same.
+// getSandboxFlags returns a list of sandbox flags (if any) or
+// null if the flag is not set.
+// This function checks if two flags are the same, i.e., they're
+// either not set or have the same flags.
+function eqFlags(a, b) {
+  if (a === null && b === null) { return true; }
+  if (a === null || b === null) { return false; }
+  if (a.length !== b.length) { return false; }
+  var a_sorted = a.sort();
+  var b_sorted = b.sort();
+  for (var i in a_sorted) {
+    if (a_sorted[i] !== b_sorted[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Get the sandbox flags of document doc.
+// If the flag is not set sandboxFlagsAsString returns null,
+// this function also returns null.
+// If the flag is set it may have some flags; in this case
+// this function returns the (potentially empty) list of flags.
+function getSandboxFlags(doc) {
+  var flags = doc.sandboxFlagsAsString;
+  if (flags === null) { return null; }
+  return flags? flags.split(" "):[];
+}
+
+function checkFlags(expected) {
+  try {
+    var flags = getSandboxFlags(SpecialPowers.wrap(document));
+    ok(eqFlags(flags, expected), name + ' expected: "' + expected + '", got: "' + flags + '"');
+  } catch (e) {
+    ok(false, name + ' expected "' + expected + ', but failed with ' + e);
+  }
+  SimpleTest.finish();
+}
+
+</script>
+
+<body onLoad='checkFlags(["allow-forms", "allow-scripts", "allow-same-origin"]);'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=671389">Mozilla Bug 671389</a> - Implement CSP sandbox directive
+<p id="display"></p>
+<div id="content">
+  I am a top-level page sandboxed with "allow-scripts allow-forms
+  allow-same-origin".
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_top_1.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: sAnDbOx aLLow-FOrms aLlOw-ScRiPtS ALLOW-same-origin
--- a/dom/security/test/csp/test_ignore_unsafe_inline.html
+++ b/dom/security/test/csp/test_ignore_unsafe_inline.html
@@ -62,17 +62,17 @@ var tests = [
     policy1: POLICY_PREFIX + "'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI=' 'nonce-FooNonce' 'unsafe-inline'",
     policy2: "referrer origin",
     description: "defining hash, nonce and 'unsafe-inline' twice should still only allow two scripts to execute",
     file: "file_ignore_unsafe_inline.html",
     result: "acd",
   },
   {
     policy1: "default-src 'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI=' 'nonce-FooNonce' ",
-    policy2: "sandbox allow-forms",
+    policy2: "sandbox allow-scripts allow-same-origin",
     description: "unsafe-inline should *not* be ignored within default-src even if hash or nonce is specified",
     file: "file_ignore_unsafe_inline.html",
     result: "abcd",
   },
 ];
 
 var counter = 0;
 var curTest;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_sandbox.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Tests for bugs 886164 and 671389</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+</div>
+
+<script class="testbody" type="text/javascript">
+
+var testCases = [
+  {
+    // Test 1: don't load image from non-same-origin; allow loading
+    // images from same-same origin
+    sandboxAttribute: "allow-same-origin",
+    csp: "default-src 'self'",
+    file: "file_sandbox_1.html",
+    results: { img1a_good: -1, img1_bad: -1 }
+    // fails if scripts execute
+  },
+  {
+    // Test 2: don't load image from non-same-origin; allow loading
+    // images from same-same origin, even without allow-same-origin
+    // flag
+    sandboxAttribute: "",
+    csp: "default-src 'self'",
+    file: "file_sandbox_2.html",
+    results: { img2_bad: -1, img2a_good: -1 }
+    // fails if scripts execute
+  },
+  {
+    // Test 3: disallow loading images from any host, even with
+    // allow-same-origin flag set
+    sandboxAttribute: "allow-same-origin",
+    csp: "default-src 'none'",
+    file: "file_sandbox_3.html",
+    results: { img3_bad: -1, img3a_bad: -1 },
+    // fails if scripts execute
+  },
+  {
+    // Test 4: disallow loading images from any host
+    sandboxAttribute: "",
+    csp: "default-src 'none'",
+    file: "file_sandbox_4.html",
+    results: { img4_bad: -1, img4a_bad: -1 }
+    // fails if scripts execute
+  },
+  {
+    // Test 5: disallow loading images or scripts, allow inline scripts
+    sandboxAttribute: "allow-scripts",
+    csp: "default-src 'none'; script-src 'unsafe-inline';",
+    file: "file_sandbox_5.html",
+    results: { img5_bad: -1, img5a_bad: -1, script5_bad: -1, script5a_bad: -1 },
+    nrOKmessages: 2 // sends 2 ok message
+    // fails if scripts execute
+  },
+  {
+    // Test 6: disallow non-same-origin images, allow inline and same origin scripts
+    sandboxAttribute: "allow-same-origin allow-scripts",
+    csp: "default-src 'self' 'unsafe-inline';",
+    file: "file_sandbox_6.html",
+    results: { img6_bad: -1, script6_bad: -1 },
+    nrOKmessages: 4 // sends 4 ok message
+    // fails if forms are not disallowed
+  },
+  {
+    // Test 7: same as Test 1
+    csp: "default-src 'self'; sandbox allow-same-origin",
+    file: "file_sandbox_7.html",
+    results: { img7a_good: -1, img7_bad: -1 }
+  },
+  {
+    // Test 8: same as Test 2
+    csp: "sandbox allow-same-origin; default-src 'self'",
+    file: "file_sandbox_8.html",
+    results: { img8_bad: -1, img8a_good: -1 }
+  },
+  {
+    // Test 9: same as Test 3
+    csp: "default-src 'none'; sandbox allow-same-origin",
+    file: "file_sandbox_9.html",
+    results: { img9_bad: -1, img9a_bad: -1 }
+  },
+  {
+    // Test 10: same as Test 4
+    csp: "default-src 'none'; sandbox allow-same-origin",
+    file: "file_sandbox_10.html",
+    results: { img10_bad: -1, img10a_bad: -1 }
+  },
+  {
+    // Test 11: same as Test 5
+    csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts allow-same-origin",
+    file: "file_sandbox_11.html",
+    results: { img11_bad: -1, img11a_bad: -1, script11_bad: -1, script11a_bad: -1 },
+    nrOKmessages: 2 // sends 2 ok message
+  },
+  {
+    // Test 12: same as Test 6
+    csp: "sandbox allow-same-origin allow-scripts; default-src 'self' 'unsafe-inline';",
+    file: "file_sandbox_12.html",
+    results: { img12_bad: -1, script12_bad: -1 },
+    nrOKmessages: 4 // sends 4 ok message
+  },
+];
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like:
+//  { ok: true/false,
+//    desc: <description of the test> which it then forwards to ok() }
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+  ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var completedTests = 0;
+var passedTests = 0;
+
+var totalTests = (function() {
+    var nrCSPloadTests = 0;
+    for(var i = 0; i < testCases.length; i++) {
+      nrCSPloadTests += Object.keys(testCases[i].results).length;
+      if (testCases[i].nrOKmessages) {
+        // + number of expected postMessages from iframe
+        nrCSPloadTests += testCases[i].nrOKmessages;
+      }
+    }
+    return nrCSPloadTests;
+})();
+
+function ok_wrapper(result, desc) {
+  ok(result, desc);
+
+  completedTests++;
+
+  if (result) {
+    passedTests++;
+  }
+
+  if (completedTests === totalTests) {
+    window.examiner.remove();
+    SimpleTest.finish();
+  }
+}
+
+// Set the iframe src and sandbox attribute
+function runTest(test) {
+  var iframe = document.createElement('iframe');
+
+  document.getElementById('content').appendChild(iframe);
+
+  // set sandbox attribute
+  if (test.sandboxAttribute !== undefined) {
+    iframe.sandbox = test.sandboxAttribute;
+  }
+
+  // set query string
+  var src = 'file_testserver.sjs';
+  // path where the files are
+  var path = '/tests/dom/security/test/csp/';
+
+  src += '?file=' + escape(path+test.file);
+
+  if (test.csp !== undefined) {
+    src += '&csp=' + escape(test.csp);
+  }
+
+  iframe.src = src;
+  iframe.width = iframe.height = 10;
+}
+
+// Examiner related
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+  SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+  SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+    //_good things better be allowed!
+    //_bad things better be stopped!
+
+    if (topic === "specialpowers-http-notify-request") {
+      //these things were allowed by CSP
+      var uri = data;
+      if (!testpat.test(uri)) return;
+      var testid = testpat.exec(uri)[1];
+
+      if(/_good/.test(testid)) {
+        ok_wrapper(true, uri + " is allowed by csp");
+      } else {
+        ok_wrapper(false, uri + " should not be allowed by csp");
+      }
+    }
+
+    if(topic === "csp-on-violate-policy") {
+      //these were blocked... record that they were blocked
+      var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+      if (!testpat.test(asciiSpec)) return;
+      var testid = testpat.exec(asciiSpec)[1];
+      if(/_bad/.test(testid)) {
+        ok_wrapper(true, asciiSpec + " was blocked by \"" + data + "\"");
+      } else {
+        ok_wrapper(false, asciiSpec + " should have been blocked by \"" + data + "\"");
+      }
+    }
+  },
+
+  // must eventually call this to remove the listener,
+  // or mochitests might get borked.
+  remove: function() {
+    SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+    SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+  }
+}
+
+window.examiner = new examiner();
+
+SimpleTest.waitForExplicitFinish();
+
+(function() { // Run tests:
+  for(var i = 0; i < testCases.length; i++) {
+    runTest(testCases[i]);
+  }
+})();
+
+</script>
+</body>
+</html>
--- a/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js
+++ b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js
@@ -88,17 +88,17 @@ function serverHandler(metadata, respons
 
 function run_next_test() {
   curTest = tests.shift();
   if (!curTest) {
     httpserver.stop(do_test_finished);
     return;
   }
   channel = setupChannel(curTest.contentType);
-  channel.asyncOpen(new ChannelListener(), null);
+  channel.asyncOpen2(new ChannelListener());
 }
 
 function run_test() {
   // set up the test environment
   httpserver = new HttpServer();
   httpserver.registerPathHandler(testpath, serverHandler);
   httpserver.start(-1);
 
--- a/dom/webidl/CSPDictionaries.webidl
+++ b/dom/webidl/CSPDictionaries.webidl
@@ -15,22 +15,23 @@ dictionary CSP {
   sequence<DOMString> style-src;
   sequence<DOMString> img-src;
   sequence<DOMString> media-src;
   sequence<DOMString> frame-src;
   sequence<DOMString> font-src;
   sequence<DOMString> connect-src;
   sequence<DOMString> report-uri;
   sequence<DOMString> frame-ancestors;
-  // sequence<DOMString> reflected-xss; // not suppored in Firefox
+  // sequence<DOMString> reflected-xss; // not supported in Firefox
   sequence<DOMString> base-uri;
   sequence<DOMString> form-action;
   sequence<DOMString> referrer;
   sequence<DOMString> manifest-src;
   sequence<DOMString> upgrade-insecure-requests;
   sequence<DOMString> child-src;
   sequence<DOMString> block-all-mixed-content;
   sequence<DOMString> require-sri-for;
+  sequence<DOMString> sandbox;
 };
 
 dictionary CSPPolicies {
   sequence<CSP> csp-policies;
 };
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -387,16 +387,24 @@ partial interface Document {
 };
 
 // Extension to give chrome JS the ability to determine when a document was
 // created to satisfy an iframe with srcdoc attribute.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean isSrcdocDocument;
 };
 
+
+// Extension to give chrome JS the ability to get the underlying
+// sandbox flag attribute
+partial interface Document {
+  [ChromeOnly] readonly attribute DOMString? sandboxFlagsAsString;
+};
+
+
 /**
  * Chrome document anonymous content management.
  * This is a Chrome-only API that allows inserting fixed positioned anonymous
  * content on top of the current page displayed in the document.
  * The supplied content is cloned and inserted into the document's CanvasFrame.
  * Note that this only works for HTML documents.
  */
 partial interface Document {
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -8,18 +8,18 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 
 dictionary EventListenerOptions {
   boolean capture = false;
-  /* This is a Mozilla extension only available in Chrome and XBL.
-     Setting to true make the listener be added to the system group. */
+  /* Setting to true make the listener be added to the system group. */
+  [Func="ThreadSafeIsChromeOrXBL"]
   boolean mozSystemGroup = false;
 };
 
 dictionary AddEventListenerOptions : EventListenerOptions {
   boolean passive = false;
   boolean once = false;
 };
 
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -84,21 +84,21 @@ typedef any Transferable;
 
   [Throws, CrossOriginCallable] void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer);
 
   // also has obsolete members
 };
 Window implements GlobalEventHandlers;
 Window implements WindowEventHandlers;
 
-[NoInterfaceObject, Exposed=(Window)]
-interface AppInstallEventHandlersMixin {
+// https://w3c.github.io/manifest/#oninstall-attribute
+partial interface Window {
+  [Pref="dom.manifest.oninstall"]
   attribute EventHandler oninstall;
 };
-Window implements AppInstallEventHandlersMixin;
 
 // http://www.whatwg.org/specs/web-apps/current-work/
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface WindowTimers {
   [Throws] long setTimeout(Function handler, optional long timeout = 0, any... arguments);
   [Throws] long setTimeout(DOMString handler, optional long timeout = 0, any... unused);
   void clearTimeout(optional long handle = 0);
   [Throws] long setInterval(Function handler, optional long timeout, any... arguments);
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -223,57 +223,60 @@ SetPaintPattern(SkPaint& aPaint, const P
       aPaint.setColor(ColorToSkColor(color, aAlpha));
       break;
     }
     case PatternType::LINEAR_GRADIENT: {
       const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
       GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get());
       SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
 
-      if (stops->mCount >= 2) {
+      if (stops->mCount < 2 ||
+          !pat.mBegin.IsFinite() || !pat.mEnd.IsFinite()) {
+        aPaint.setColor(SK_ColorTRANSPARENT);
+      } else {
         SkPoint points[2];
         points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x), SkFloatToScalar(pat.mBegin.y));
         points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x), SkFloatToScalar(pat.mEnd.y));
 
         SkMatrix mat;
         GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
         sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points,
                                                               &stops->mColors.front(),
                                                               &stops->mPositions.front(),
                                                               stops->mCount,
                                                               mode, 0, &mat);
         aPaint.setShader(shader);
-      } else {
-        aPaint.setColor(SK_ColorTRANSPARENT);
       }
       break;
     }
     case PatternType::RADIAL_GRADIENT: {
       const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
       GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get());
       SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
 
-      if (stops->mCount >= 2) {
+      if (stops->mCount < 2 ||
+          !pat.mCenter1.IsFinite() || !IsFinite(pat.mRadius1) ||
+          !pat.mCenter2.IsFinite() || !IsFinite(pat.mRadius2)) {
+        aPaint.setColor(SK_ColorTRANSPARENT);
+      } else {
         SkPoint points[2];
         points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x), SkFloatToScalar(pat.mCenter1.y));
         points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x), SkFloatToScalar(pat.mCenter2.y));
 
         SkMatrix mat;
         GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
         sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(points[0],
                                                                        SkFloatToScalar(pat.mRadius1),
                                                                        points[1],
                                                                        SkFloatToScalar(pat.mRadius2),
                                                                        &stops->mColors.front(),
                                                                        &stops->mPositions.front(),
                                                                        stops->mCount,
                                                                        mode, 0, &mat);
         aPaint.setShader(shader);
-      } else {
-        aPaint.setColor(SK_ColorTRANSPARENT);
       }
       break;
     }
     case PatternType::SURFACE: {
       const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
       SkBitmap bitmap = GetBitmapForSurface(pat.mSurface);
 
       SkMatrix mat;
--- a/gfx/layers/BufferTexture.cpp
+++ b/gfx/layers/BufferTexture.cpp
@@ -221,16 +221,28 @@ BufferTextureData::FillInfo(TextureData:
 }
 
 gfx::IntSize
 BufferTextureData::GetSize() const
 {
   return ImageDataSerializer::SizeFromBufferDescriptor(mDescriptor);
 }
 
+Maybe<gfx::IntSize>
+BufferTextureData::GetCbCrSize() const
+{
+  return ImageDataSerializer::CbCrSizeFromBufferDescriptor(mDescriptor);
+}
+
+Maybe<StereoMode>
+BufferTextureData::GetStereoMode() const
+{
+  return ImageDataSerializer::StereoModeFromBufferDescriptor(mDescriptor);
+}
+
 gfx::SurfaceFormat
 BufferTextureData::GetFormat() const
 {
   return ImageDataSerializer::FormatFromBufferDescriptor(mDescriptor);
 }
 
 already_AddRefed<gfx::DrawTarget>
 BufferTextureData::BorrowDrawTarget()
--- a/gfx/layers/BufferTexture.h
+++ b/gfx/layers/BufferTexture.h
@@ -47,19 +47,25 @@ public:
 
   virtual bool BorrowMappedData(MappedTextureData& aMap) override;
 
   virtual bool BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap) override;
 
   // use TextureClient's default implementation
   virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override;
 
+  virtual BufferTextureData* AsBufferTextureData() override { return this; }
+
   // Don't use this.
   void SetDesciptor(const BufferDescriptor& aDesc);
 
+  Maybe<gfx::IntSize> GetCbCrSize() const;
+
+  Maybe<StereoMode> GetStereoMode() const;
+
 protected:
   gfx::IntSize GetSize() const;
 
   gfx::SurfaceFormat GetFormat() const;
 
   static BufferTextureData* CreateInternal(ClientIPCAllocator* aAllocator,
                                            const BufferDescriptor& aDesc,
                                            gfx::BackendType aMoz2DBackend,
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/ipc/CrossProcessMutex.h"  // for CrossProcessMutex, etc
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/ImageBridgeChild.h"  // for ImageBridgeChild
 #include "mozilla/layers/PImageContainerChild.h"
 #include "mozilla/layers/ImageClient.h"  // for ImageClient
 #include "mozilla/layers/LayersMessages.h"
 #include "mozilla/layers/SharedPlanarYCbCrImage.h"
 #include "mozilla/layers/SharedRGBImage.h"
+#include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "nsISupportsUtils.h"           // for NS_IF_ADDREF
 #include "YCbCrUtils.h"                 // for YCbCr conversions
 #ifdef MOZ_WIDGET_GONK
 #include "GrallocImages.h"
 #endif
 #if defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_CAMERA) && defined(MOZ_WEBRTC)
 #include "GonkCameraImage.h"
 #endif
@@ -351,16 +352,20 @@ ImageContainer::ClearAllImages()
   SetCurrentImageInternal(nsTArray<NonOwningImage>());
 }
 
 void
 ImageContainer::ClearCachedResources()
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   if (mImageClient && mImageClient->AsImageClientSingle()) {
+    if (!mImageClient->HasTextureClientRecycler()) {
+      return;
+    }
+    mImageClient->GetTextureClientRecycler()->ShrinkToMinimumSize();
     return;
   }
   return mRecycleBin->ClearRecycledBuffers();
 }
 
 void
 ImageContainer::SetCurrentImageInTransaction(Image *aImage)
 {
--- a/gfx/layers/ImageDataSerializer.cpp
+++ b/gfx/layers/ImageDataSerializer.cpp
@@ -123,16 +123,40 @@ gfx::IntSize SizeFromBufferDescriptor(co
       return aDescriptor.get_RGBDescriptor().size();
     case BufferDescriptor::TYCbCrDescriptor:
       return aDescriptor.get_YCbCrDescriptor().ySize();
     default:
       MOZ_CRASH("GFX: SizeFromBufferDescriptor");
   }
 }
 
+Maybe<gfx::IntSize> CbCrSizeFromBufferDescriptor(const BufferDescriptor& aDescriptor)
+{
+  switch (aDescriptor.type()) {
+    case BufferDescriptor::TRGBDescriptor:
+      return Nothing();
+    case BufferDescriptor::TYCbCrDescriptor:
+      return Some(aDescriptor.get_YCbCrDescriptor().cbCrSize());
+    default:
+      MOZ_CRASH("GFX:  CbCrSizeFromBufferDescriptor");
+  }
+}
+
+Maybe<StereoMode> StereoModeFromBufferDescriptor(const BufferDescriptor& aDescriptor)
+{
+  switch (aDescriptor.type()) {
+    case BufferDescriptor::TRGBDescriptor:
+      return Nothing();
+    case BufferDescriptor::TYCbCrDescriptor:
+      return Some(aDescriptor.get_YCbCrDescriptor().stereoMode());
+    default:
+      MOZ_CRASH("GFX:  CbCrSizeFromBufferDescriptor");
+  }
+}
+
 uint8_t* GetYChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor)
 {
   return aBuffer + aDescriptor.yOffset();
 }
 
 uint8_t* GetCbChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor)
 {
   return aBuffer + aDescriptor.cbOffset();
--- a/gfx/layers/ImageDataSerializer.h
+++ b/gfx/layers/ImageDataSerializer.h
@@ -53,16 +53,20 @@ uint32_t ComputeYCbCrBufferSize(uint32_t
 void ComputeYCbCrOffsets(int32_t yStride, int32_t yHeight,
                          int32_t cbCrStride, int32_t cbCrHeight,
                          uint32_t& outYOffset, uint32_t& outCbOffset, uint32_t& outCrOffset);
 
 gfx::SurfaceFormat FormatFromBufferDescriptor(const BufferDescriptor& aDescriptor);
 
 gfx::IntSize SizeFromBufferDescriptor(const BufferDescriptor& aDescriptor);
 
+Maybe<gfx::IntSize> CbCrSizeFromBufferDescriptor(const BufferDescriptor& aDescriptor);
+
+Maybe<StereoMode> StereoModeFromBufferDescriptor(const BufferDescriptor& aDescriptor);
+
 uint8_t* GetYChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor);
 
 uint8_t* GetCbChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor);
 
 uint8_t* GetCrChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor);
 
 already_AddRefed<gfx::DataSourceSurface>
 DataSourceSurfaceFromYCbCrDescriptor(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor);
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -687,18 +687,21 @@ APZCTreeManager::ReceiveInputEvent(Input
       }
 
       bool hitScrollbar = false;
       RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(mouseInput.mOrigin,
             &hitResult, &hitScrollbar);
 
       // When the mouse is outside the window we still want to handle dragging
       // but we won't find an APZC. Fallback to root APZC then.
-      if (!apzc && mRootNode) {
-        apzc = mRootNode->GetApzc();
+      { // scope lock
+        MutexAutoLock lock(mTreeLock);
+        if (!apzc && mRootNode) {
+          apzc = mRootNode->GetApzc();
+        }
       }
 
       if (apzc) {
         bool targetConfirmed = (hitResult != HitNothing && hitResult != HitDispatchToContentRegion);
         if (gfxPrefs::APZDragEnabled() && hitScrollbar) {
           // If scrollbar dragging is enabled and we hit a scrollbar, wait
           // for the main-thread confirmation because it contains drag metrics
           // that we need.
--- a/gfx/layers/apz/src/AndroidAPZ.cpp
+++ b/gfx/layers/apz/src/AndroidAPZ.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AndroidAPZ.h"
 
 #include "AsyncPanZoomController.h"
 #include "GeneratedJNIWrappers.h"
 #include "gfxPrefs.h"
 #include "OverscrollHandoffState.h"
-#include "OverScroller.h"
 #include "ViewConfiguration.h"
 
 #define ANDROID_APZ_LOG(...)
 // #define ANDROID_APZ_LOG(...) printf_stderr("ANDROID_APZ: " __VA_ARGS__)
 
 static float sMaxFlingSpeed = 0.0f;
 
 namespace mozilla {
@@ -28,19 +27,19 @@ AndroidSpecificState::AndroidSpecificSta
     if (config->GetScaledMaximumFlingVelocity(&speed) == NS_OK) {
       sMaxFlingSpeed = (float)speed * 0.001f;
     } else {
       ANDROID_APZ_LOG("%p Failed to query ViewConfiguration for scaled maximum fling velocity\n", this);
     }
   } else {
     ANDROID_APZ_LOG("%p Failed to get ViewConfiguration\n", this);
   }
-  widget::sdk::OverScroller::LocalRef scroller;
-  if (widget::sdk::OverScroller::New(widget::GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
-    ANDROID_APZ_LOG("%p Failed to create Android OverScroller\n", this);
+  widget::StackScroller::LocalRef scroller;
+  if (widget::StackScroller::New(widget::GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
+    ANDROID_APZ_LOG("%p Failed to create Android StackScroller\n", this);
     return;
   }
   mOverScroller = scroller;
 }
 
 const float BOUNDS_EPSILON = 1.0f;
 
 // This function is used to convert the scroll offset from a float to an integer
@@ -70,16 +69,17 @@ AndroidFlingAnimation::AndroidFlingAnima
                                              const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                              bool aFlingIsHandoff,
                                              const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
   : mApzc(aApzc)
   , mOverscrollHandoffChain(aOverscrollHandoffChain)
   , mScrolledApzc(aScrolledApzc)
   , mSentBounceX(false)
   , mSentBounceY(false)
+  , mFlingDuration(0)
 {
   MOZ_ASSERT(mOverscrollHandoffChain);
   MOZ_ASSERT(aPlatformSpecificState->AsAndroidSpecificState());
   mOverScroller = aPlatformSpecificState->AsAndroidSpecificState()->mOverScroller;
   MOZ_ASSERT(mOverScroller);
 
   // Drop any velocity on axes where we don't have room to scroll anyways
   // (in this APZC, or an APZC further in the handoff chain).
@@ -115,35 +115,34 @@ AndroidFlingAnimation::AndroidFlingAnima
 
   int32_t originX = ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX);
   int32_t originY = ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY);
   mOverScroller->Fling(originX, originY,
                        // Android needs the velocity in pixels per second and it is in pixels per ms.
                        (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f),
                        (int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX),
                        (int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY),
-                       0, 0);
+                       0, 0, 0);
 }
 
 /**
  * Advances a fling by an interpolated amount based on the Android OverScroller.
  * This should be called whenever sampling the content transform for this
  * frame. Returns true if the fling animation should be advanced by one frame,
  * or false if there is no fling or the fling has ended.
  */
 bool
 AndroidFlingAnimation::DoSample(FrameMetrics& aFrameMetrics,
                                 const TimeDuration& aDelta)
 {
   bool shouldContinueFling = true;
 
-  mOverScroller->ComputeScrollOffset(&shouldContinueFling);
-  // OverScroller::GetCurrVelocity will sometimes return NaN. So need to externally
-  // calculate current velocity and not rely on what the OverScroller calculates.
-  // mOverScroller->GetCurrVelocity(&speed);
+  mFlingDuration += aDelta.ToMilliseconds();
+  mOverScroller->ComputeScrollOffset(mFlingDuration, &shouldContinueFling);
+
   int32_t currentX = 0;
   int32_t currentY = 0;
   mOverScroller->GetCurrX(&currentX);
   mOverScroller->GetCurrY(&currentY);
   ParentLayerPoint offset((float)currentX, (float)currentY);
 
   bool hitBoundX = CheckBounds(mApzc.mX, offset.x, mFlingDirection.x, &(offset.x));
   bool hitBoundY = CheckBounds(mApzc.mY, offset.y, mFlingDirection.y, &(offset.y));
@@ -151,17 +150,22 @@ AndroidFlingAnimation::DoSample(FrameMet
   ParentLayerPoint velocity = mPreviousVelocity;
 
   // Sometimes the OverScroller fails to update the offset for a frame.
   // If the frame can still scroll we just use the velocity from the previous
   // frame. However, if the frame can no longer scroll in the direction
   // of the fling, then end the animation.
   if (offset != mPreviousOffset) {
     if (aDelta.ToMilliseconds() > 0) {
-      velocity = (offset - mPreviousOffset) / (float)aDelta.ToMilliseconds();
+      mOverScroller->GetCurrSpeedX(&velocity.x);
+      mOverScroller->GetCurrSpeedY(&velocity.y);
+
+      velocity.x /= 1000;
+      velocity.y /= 1000;
+
       mPreviousVelocity = velocity;
     }
   } else if (hitBoundX || hitBoundY) {
     // We have reached the end of the scroll in one of the directions being scrolled and the offset has not
     // changed so end animation.
     shouldContinueFling = false;
   }
 
--- a/gfx/layers/apz/src/AndroidAPZ.h
+++ b/gfx/layers/apz/src/AndroidAPZ.h
@@ -5,30 +5,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_AndroidAPZ_h_
 #define mozilla_layers_AndroidAPZ_h_
 
 #include "AsyncPanZoomAnimation.h"
 #include "AsyncPanZoomController.h"
 #include "GeneratedJNIWrappers.h"
-#include "OverScroller.h"
 
 namespace mozilla {
 namespace layers {
 
 class AndroidSpecificState : public PlatformSpecificStateBase {
 public:
   AndroidSpecificState();
 
   virtual AndroidSpecificState* AsAndroidSpecificState() override {
     return this;
   }
 
-  widget::sdk::OverScroller::GlobalRef mOverScroller;
+  widget::StackScroller::GlobalRef mOverScroller;
 };
 
 class AndroidFlingAnimation: public AsyncPanZoomAnimation {
 public:
   AndroidFlingAnimation(AsyncPanZoomController& aApzc,
                         PlatformSpecificStateBase* aPlatformSpecificState,
                         const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                         bool aFlingIsHandoff /* ignored */,
@@ -36,21 +35,22 @@ public:
   virtual bool DoSample(FrameMetrics& aFrameMetrics,
                         const TimeDuration& aDelta) override;
 private:
   void DeferHandleFlingOverscroll(ParentLayerPoint& aVelocity);
   // Returns true if value is on or outside of axis bounds.
   bool CheckBounds(Axis& aAxis, float aValue, float aDirection, float* aClamped);
 
   AsyncPanZoomController& mApzc;
-  widget::sdk::OverScroller::GlobalRef mOverScroller;
+  widget::StackScroller::GlobalRef mOverScroller;
   RefPtr<const OverscrollHandoffChain> mOverscrollHandoffChain;
   RefPtr<const AsyncPanZoomController> mScrolledApzc;
   bool mSentBounceX;
   bool mSentBounceY;
+  long mFlingDuration;
   ParentLayerPoint mStartOffset;
   ParentLayerPoint mPreviousOffset;
   // Unit vector in the direction of the fling.
   ParentLayerPoint mFlingDirection;
   ParentLayerPoint mPreviousVelocity;
 };
 
 
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -11,16 +11,31 @@
 function convertEntries(entries) {
   var result = {};
   for (var i = 0; i < entries.length; ++i) {
     result[entries[i].key] = entries[i].value;
   }
   return result;
 }
 
+function getPropertyAsRect(scrollFrames, scrollId, prop) {
+  SimpleTest.ok(scrollId in scrollFrames,
+                'expected scroll frame data for scroll id ' + scrollId);
+  var scrollFrameData = scrollFrames[scrollId];
+  SimpleTest.ok('displayport' in scrollFrameData,
+                'expected a ' + prop + ' for scroll id ' + scrollId);
+  var value = scrollFrameData[prop];
+  var pieces = value.replace(/[()\s]+/g, '').split(',');
+  SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)");
+  return { x: parseInt(pieces[0]),
+           y: parseInt(pieces[1]),
+           w: parseInt(pieces[2]),
+           h: parseInt(pieces[3]) };
+}
+
 function convertScrollFrameData(scrollFrames) {
   var result = {};
   for (var i = 0; i < scrollFrames.length; ++i) {
     result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries);
   }
   return result;
 }
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1280013.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html style="overflow:hidden">
+<head>
+  <meta charset="utf-8">
+  <!-- The viewport tag will result in APZ being in a "zoomed-in" state, assuming
+       the device width is less than 980px. -->
+  <meta name="viewport" content="width=980; initial-scale=1.0">
+  <title>Test for bug 1280013</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript">
+function* test(testDriver) {
+  SimpleTest.ok(screen.height > 500, "Screen height must be at least 500 pixels for this test to work");
+
+  // This listener will trigger the test to continue once APZ is done with
+  // processing the scroll.
+  SpecialPowers.Services.obs.addObserver(testDriver, "APZ:TransformEnd", false);
+
+  // Scroll down to the iframe. Do it in two drags instead of one in case the
+  // device screen is short
+  yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
+  yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
+  // Now the top of the visible area should be at y=700 of the top-level page,
+  // so if the screen is >= 500px tall, the entire iframe should be visible, at
+  // least vertically.
+
+  // However, because of the overflow:hidden on the root elements, all this
+  // scrolling is happening in APZ and is not reflected in the main-thread
+  // scroll position (it is stored in the callback transform instead). We check
+  // this by checking the scroll offset.
+  yield flushApzRepaints(testDriver);
+  SimpleTest.is(window.scrollY, 0, "Main-thread scroll position is still at 0");
+
+  // Scroll the iframe by 300px. Note that since the main-thread scroll position
+  // is still 0, the subframe's getBoundingClientRect is going to be off by
+  // 700 pixels, so we compensate for that here.
+  var subframe = document.getElementById('subframe');
+  yield synthesizeNativeTouchDrag(subframe, 10, 200 - 700, 0, -(300 + TOUCH_SLOP));
+
+  // Remove the observer, we don't need it any more.
+  SpecialPowers.Services.obs.removeObserver(testDriver, "APZ:TransformEnd", false);
+
+  // Flush any pending paints
+  yield flushApzRepaints(testDriver);
+
+  // get the displayport for the subframe
+  var utils = SpecialPowers.getDOMWindowUtils(window);
+  var contentPaints = utils.getContentAPZTestData().paints;
+  var lastPaint = convertScrollFrameData(contentPaints[contentPaints.length - 1].scrollFrames);
+  var foundIt = 0;
+  for (var scrollId in lastPaint) {
+    if (('contentDescription' in lastPaint[scrollId]) &&
+        (lastPaint[scrollId]['contentDescription'].includes('tall_html'))) {
+      var dp = getPropertyAsRect(lastPaint, scrollId, 'criticalDisplayport');
+      SimpleTest.ok(dp.y <= 0, 'The critical displayport top should be less than or equal to zero to cover the visible part of the subframe; it is ' + dp.y);
+      SimpleTest.ok(dp.y + dp.h >= subframe.clientHeight, 'The critical displayport bottom should be greater than the clientHeight; it is ' + (dp.y + dp.h));
+      foundIt++;
+    }
+  }
+  SimpleTest.is(foundIt, 1, "Found exactly one critical displayport for the subframe we were interested in.");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+  </script>
+</head>
+<body style="overflow:hidden">
+  The iframe below is at (0, 800). Scroll it into view, and then scroll the contents. The content should be fully rendered in high-resolution.
+  <iframe id="subframe" style="position:absolute; left: 0px; top: 800px; width: 600px; height: 350px" src="helper_tall.html"></iframe>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/helper_bug982141.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html
@@ -67,33 +67,24 @@ https://bugzilla.mozilla.org/show_bug.cg
       SimpleTest.is(rcd.children.length, 1, "expected a single child APZC");
       var childScrollId = rcd.children[0].scrollId;
 
       // We should have content-side data for the same paint.
       SimpleTest.ok(lastCompositorPaintSeqNo in contentTestData.paints,
                     "expected a content paint with sequence number" + lastCompositorPaintSeqNo);
       var correspondingContentPaint = contentTestData.paints[lastCompositorPaintSeqNo];
 
-      // This content-side data should have a displayport for our scrollable <div>.
-      SimpleTest.ok(childScrollId in correspondingContentPaint,
-                    "expected scroll frame data for scroll id " + childScrollId);
-      SimpleTest.ok("displayport" in correspondingContentPaint[childScrollId],
-                    "expected a displayport for scroll id " + childScrollId);
-      var childDisplayport = correspondingContentPaint[childScrollId]["displayport"];
-      var dpFields = childDisplayport.replace(/[()\s]+/g, '').split(',');
-      SimpleTest.is(dpFields.length, 4, "expected displayport string of form (x,y,w,h)");
-      var dpWidth = dpFields[2];
-      var dpHeight = dpFields[3];
+      var dp = getPropertyAsRect(correspondingContentPaint, childScrollId, 'displayport');
       var subframe = document.getElementById('subframe');
       // The clientWidth and clientHeight may be less than 50 if there are scrollbars showing.
       // In general they will be (50 - <scrollbarwidth>, 50 - <scrollbarheight>).
       SimpleTest.ok(subframe.clientWidth > 0, "Expected a non-zero clientWidth, got: " + subframe.clientWidth);
       SimpleTest.ok(subframe.clientHeight > 0, "Expected a non-zero clientHeight, got: " + subframe.clientHeight);
-      SimpleTest.ok(dpWidth >= subframe.clientWidth && dpHeight >= subframe.clientHeight,
-                    "expected a displayport at least as large as the scrollable element, got " + childDisplayport);
+      SimpleTest.ok(dp.w >= subframe.clientWidth && dp.h >= subframe.clientHeight,
+                    "expected a displayport at least as large as the scrollable element, got " + JSON.stringify(dp));
 
       window.opener.finishTest();
     }
   </script>
 </head>
 <body style="overflow: hidden;"><!-- This combined with the user-scalable=no ensures the root frame is not scrollable -->
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982141">Mozilla Bug 982141</a>
   <!-- A scrollable subframe, with enough content to make it have a nonzero scroll range -->
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_tall.html
@@ -0,0 +1,504 @@
+<html id="tall_html">
+<body>
+This is a tall page<br/>
+1<br/>
+2<br/>
+3<br/>
+4<br/>
+5<br/>
+6<br/>
+7<br/>
+8<br/>
+9<br/>
+10<br/>
+11<br/>
+12<br/>
+13<br/>
+14<br/>
+15<br/>
+16<br/>
+17<br/>
+18<br/>
+19<br/>
+20<br/>
+21<br/>
+22<br/>
+23<br/>
+24<br/>
+25<br/>
+26<br/>
+27<br/>
+28<br/>
+29<br/>
+30<br/>
+31<br/>
+32<br/>
+33<br/>
+34<br/>
+35<br/>
+36<br/>
+37<br/>
+38<br/>
+39<br/>
+40<br/>
+41<br/>
+42<br/>
+43<br/>
+44<br/>
+45<br/>
+46<br/>
+47<br/>
+48<br/>
+49<br/>
+50<br/>
+51<br/>
+52<br/>
+53<br/>
+54<br/>
+55<br/>
+56<br/>
+57<br/>
+58<br/>
+59<br/>
+60<br/>
+61<br/>
+62<br/>
+63<br/>
+64<br/>
+65<br/>
+66<br/>
+67<br/>
+68<br/>
+69<br/>
+70<br/>
+71<br/>
+72<br/>
+73<br/>
+74<br/>
+75<br/>
+76<br/>
+77<br/>
+78<br/>
+79<br/>
+80<br/>
+81<br/>
+82<br/>
+83<br/>
+84<br/>
+85<br/>
+86<br/>
+87<br/>
+88<br/>
+89<br/>
+90<br/>
+91<br/>
+92<br/>
+93<br/>
+94<br/>
+95<br/>
+96<br/>
+97<br/>
+98<br/>
+99<br/>
+100<br/>
+101<br/>
+102<br/>
+103<br/>
+104<br/>
+105<br/>
+106<br/>
+107<br/>
+108<br/>
+109<br/>
+110<br/>
+111<br/>
+112<br/>
+113<br/>
+114<br/>
+115<br/>
+116<br/>
+117<br/>
+118<br/>
+119<br/>
+120<br/>
+121<br/>
+122<br/>
+123<br/>
+124<br/>
+125<br/>
+126<br/>
+127<br/>
+128<br/>
+129<br/>
+130<br/>
+131<br/>
+132<br/>
+133<br/>
+134<br/>
+135<br/>
+136<br/>
+137<br/>
+138<br/>
+139<br/>
+140<br/>
+141<br/>
+142<br/>
+143<br/>
+144<br/>
+145<br/>
+146<br/>
+147<br/>
+148<br/>
+149<br/>
+150<br/>
+151<br/>
+152<br/>
+153<br/>
+154<br/>
+155<br/>
+156<br/>
+157<br/>
+158<br/>
+159<br/>
+160<br/>
+161<br/>
+162<br/>
+163<br/>
+164<br/>
+165<br/>
+166<br/>
+167<br/>
+168<br/>
+169<br/>
+170<br/>
+171<br/>
+172<br/>
+173<br/>
+174<br/>
+175<br/>
+176<br/>
+177<br/>
+178<br/>
+179<br/>
+180<br/>
+181<br/>
+182<br/>
+183<br/>
+184<br/>
+185<br/>
+186<br/>
+187<br/>
+188<br/>
+189<br/>
+190<br/>
+191<br/>
+192<br/>
+193<br/>
+194<br/>
+195<br/>
+196<br/>
+197<br/>
+198<br/>
+199<br/>
+200<br/>
+201<br/>
+202<br/>
+203<br/>
+204<br/>
+205<br/>
+206<br/>
+207<br/>
+208<br/>
+209<br/>
+210<br/>
+211<br/>
+212<br/>
+213<br/>
+214<br/>
+215<br/>
+216<br/>
+217<br/>
+218<br/>
+219<br/>
+220<br/>
+221<br/>
+222<br/>
+223<br/>
+224<br/>
+225<br/>
+226<br/>
+227<br/>
+228<br/>
+229<br/>
+230<br/>
+231<br/>
+232<br/>
+233<br/>
+234<br/>
+235<br/>
+236<br/>
+237<br/>
+238<br/>
+239<br/>
+240<br/>
+241<br/>
+242<br/>
+243<br/>
+244<br/>
+245<br/>
+246<br/>
+247<br/>
+248<br/>
+249<br/>
+250<br/>
+251<br/>
+252<br/>
+253<br/>
+254<br/>
+255<br/>
+256<br/>
+257<br/>
+258<br/>
+259<br/>
+260<br/>
+261<br/>
+262<br/>
+263<br/>
+264<br/>
+265<br/>
+266<br/>
+267<br/>
+268<br/>
+269<br/>
+270<br/>
+271<br/>
+272<br/>
+273<br/>
+274<br/>
+275<br/>
+276<br/>
+277<br/>
+278<br/>
+279<br/>
+280<br/>
+281<br/>
+282<br/>
+283<br/>
+284<br/>
+285<br/>
+286<br/>
+287<br/>
+288<br/>
+289<br/>
+290<br/>
+291<br/>
+292<br/>
+293<br/>
+294<br/>
+295<br/>
+296<br/>
+297<br/>
+298<br/>
+299<br/>
+300<br/>
+301<br/>
+302<br/>
+303<br/>
+304<br/>
+305<br/>
+306<br/>
+307<br/>
+308<br/>
+309<br/>
+310<br/>
+311<br/>
+312<br/>
+313<br/>
+314<br/>
+315<br/>
+316<br/>
+317<br/>
+318<br/>
+319<br/>
+320<br/>
+321<br/>
+322<br/>
+323<br/>
+324<br/>
+325<br/>
+326<br/>
+327<br/>
+328<br/>
+329<br/>
+330<br/>
+331<br/>
+332<br/>
+333<br/>
+334<br/>
+335<br/>
+336<br/>
+337<br/>
+338<br/>
+339<br/>
+340<br/>
+341<br/>
+342<br/>
+343<br/>
+344<br/>
+345<br/>
+346<br/>
+347<br/>
+348<br/>
+349<br/>
+350<br/>
+351<br/>
+352<br/>
+353<br/>
+354<br/>
+355<br/>
+356<br/>
+357<br/>
+358<br/>
+359<br/>
+360<br/>
+361<br/>
+362<br/>
+363<br/>
+364<br/>
+365<br/>
+366<br/>
+367<br/>
+368<br/>
+369<br/>
+370<br/>
+371<br/>
+372<br/>
+373<br/>
+374<br/>
+375<br/>
+376<br/>
+377<br/>
+378<br/>
+379<br/>
+380<br/>
+381<br/>
+382<br/>
+383<br/>
+384<br/>
+385<br/>
+386<br/>
+387<br/>
+388<br/>
+389<br/>
+390<br/>
+391<br/>
+392<br/>
+393<br/>
+394<br/>
+395<br/>
+396<br/>
+397<br/>
+398<br/>
+399<br/>
+400<br/>
+401<br/>
+402<br/>
+403<br/>
+404<br/>
+405<br/>
+406<br/>
+407<br/>
+408<br/>
+409<br/>
+410<br/>
+411<br/>
+412<br/>
+413<br/>
+414<br/>
+415<br/>
+416<br/>
+417<br/>
+418<br/>
+419<br/>
+420<br/>
+421<br/>
+422<br/>
+423<br/>
+424<br/>
+425<br/>
+426<br/>
+427<br/>
+428<br/>
+429<br/>
+430<br/>
+431<br/>
+432<br/>
+433<br/>
+434<br/>
+435<br/>
+436<br/>
+437<br/>
+438<br/>
+439<br/>
+440<br/>
+441<br/>
+442<br/>
+443<br/>
+444<br/>
+445<br/>
+446<br/>
+447<br/>
+448<br/>
+449<br/>
+450<br/>
+451<br/>
+452<br/>
+453<br/>
+454<br/>
+455<br/>
+456<br/>
+457<br/>
+458<br/>
+459<br/>
+460<br/>
+461<br/>
+462<br/>
+463<br/>
+464<br/>
+465<br/>
+466<br/>
+467<br/>
+468<br/>
+469<br/>
+470<br/>
+471<br/>
+472<br/>
+473<br/>
+474<br/>
+475<br/>
+476<br/>
+477<br/>
+478<br/>
+479<br/>
+480<br/>
+481<br/>
+482<br/>
+483<br/>
+484<br/>
+485<br/>
+486<br/>
+487<br/>
+488<br/>
+489<br/>
+490<br/>
+491<br/>
+492<br/>
+493<br/>
+494<br/>
+495<br/>
+496<br/>
+497<br/>
+498<br/>
+499<br/>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -17,16 +17,18 @@ support-files =
   helper_tap_passive.html
   helper_click.html
   helper_drag_click.html
   helper_bug1271432.html
   helper_touch_action.html
   helper_touch_action_regions.html
   helper_scroll_inactive_perspective.html
   helper_scroll_inactive_zindex.html
+  helper_bug1280013.html
+  helper_tall.html
   helper_drag_scroll.html
 tags = apz
 [test_bug982141.html]
 [test_bug1151663.html]
 [test_bug1277814.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_wheel_scroll.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
@@ -49,8 +51,10 @@ skip-if = (os == 'android') || (os == 'b
 skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
 [test_group_wheelevents.html]
 skip-if = (toolkit == 'android') # wheel events not supported on mobile
 [test_group_mouseevents.html]
 [test_touch_listeners_impacting_wheel.html]
 skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
 [test_bug1253683.html]
 skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile
+[test_group_zoom.html]
+skip-if = (toolkit != 'android') # only android supports zoom
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Various zoom-related tests that spawn in new windows</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+var prefs = [
+  // We need the APZ paint logging information
+  ["apz.test.logging_enabled", true],
+  // Dropping the touch slop to 0 makes the tests easier to write because
+  // we can just do a one-pixel drag to get over the pan threshold rather
+  // than having to hard-code some larger value.
+  ["apz.touch_start_tolerance", "0.0"],
+  // The subtests in this test do touch-drags to pan the page, but we don't
+  // want those pans to turn into fling animations, so we increase the
+  // fling-stop threshold velocity to absurdly high.
+  ["apz.fling_stopped_threshold", "10000"],
+  // The helper_bug1280013's div gets a displayport on scroll, but if the
+  // test takes too long the displayport can expire before we read the value
+  // out of the test. So we disable displayport expiry for these tests.
+  ["apz.displayport_expiry_ms", 0],
+];
+
+var subtests = [
+  {'file': 'helper_bug1280013.html', 'prefs': prefs},
+];
+
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  window.onload = function() {
+    runSubtestsSeriallyInFreshWindows(subtests)
+    .then(SimpleTest.finish);
+  };
+}
+
+  </script>
+</head>
+<body>
+</body>
+</html>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -403,49 +403,29 @@ APZCCallbackHelper::ApplyCallbackTransfo
     if (nsIPresShell* shell = GetRootDocumentPresShell(content)) {
         input = input / shell->GetResolution();
     }
 
     // This represents any resolution on the Root Content Document (RCD)
     // that's not on the Root Document (RD). That is, on platforms where
     // RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
     // resolution. 'input' has this resolution applied, but the scroll
-    // deltas retrieved below do not, so we need to apply them to the
-    // deltas before adding the deltas to 'input'. (Technically, deltas
+    // delta retrieved below do not, so we need to apply them to the
+    // delta before adding the delta to 'input'. (Technically, deltas
     // from scroll frames outside the RCD would already have this
     // resolution applied, but we don't have such scroll frames in
     // practice.)
     float nonRootResolution = 1.0f;
     if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
       nonRootResolution = shell->GetCumulativeNonRootScaleResolution();
     }
-    // Now apply the callback-transform.
-    // XXX: Walk up the frame tree from the frame of this content element
-    // to the root of the frame tree, and apply any apzCallbackTransform
-    // found on the way. This is only approximately correct, as it does
-    // not take into account CSS transforms, nor differences in structure between
-    // the frame tree (which determines the transforms we're applying)
-    // and the layer tree (which determines the transforms we *want* to
-    // apply).
-    nsIFrame* frame = content->GetPrimaryFrame();
-    nsCOMPtr<nsIContent> lastContent;
-    while (frame) {
-        if (content && (content != lastContent)) {
-            void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
-            if (property) {
-                CSSPoint delta = (*static_cast<CSSPoint*>(property));
-                delta = delta * nonRootResolution;
-                input += delta;
-            }
-        }
-        frame = frame->GetParent();
-        lastContent = content;
-        content = frame ? frame->GetContent() : nullptr;
-    }
-    return input;
+    // Now apply the callback-transform. This is only approximately correct,
+    // see the comment on GetCumulativeApzCallbackTransform for details.
+    CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(content->GetPrimaryFrame());
+    return input + transform * nonRootResolution;
 }
 
 LayoutDeviceIntPoint
 APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
                                            const ScrollableLayerGuid& aGuid,
                                            const CSSToLayoutDeviceScale& aScale)
 {
     LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -420,19 +420,26 @@ ClientTiledPaintedLayer::RenderLayer()
 {
   LayerManager::DrawPaintedLayerCallback callback =
     ClientManager()->GetPaintedLayerCallback();
   void *data = ClientManager()->GetPaintedLayerCallbackData();
 
   IntSize layerSize = mVisibleRegion.ToUnknownRegion().GetBounds().Size();
   IntSize tileSize(gfxPlatform::GetPlatform()->GetTileWidth(),
                    gfxPlatform::GetPlatform()->GetTileHeight());
+  bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 ||
+    layerSize.height <= tileSize.height / 2;
 
+  // Use single tile when layer is not scrollable, is smaller than one
+  // tile, or when more than half of the tiles' pixels in either
+  // dimension would be wasted.
   bool wantSingleTiledContentClient =
-      (mCreationHint == LayerManager::NONE || layerSize <= tileSize) &&
+      (mCreationHint == LayerManager::NONE ||
+       layerSize <= tileSize ||
+       isHalfTileWidthOrHeight) &&
       SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) &&
       gfxPrefs::LayersSingleTileEnabled();
 
   if (mContentClient && mHaveSingleTiledContentClient && !wantSingleTiledContentClient) {
     mContentClient = nullptr;
     mValidRegion.SetEmpty();
   }
 
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CompositableClient.h"
 #include <stdint.h>                     // for uint64_t, uint32_t
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "mozilla/layers/CompositableForwarder.h"
+#include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layers/TextureClient.h"  // for TextureClient, etc
 #include "mozilla/layers/TextureClientOGL.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "mozilla/layers/PCompositableChild.h"
 #include "mozilla/layers/TextureClientRecycleAllocator.h"
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"         // for gfxWindowsPlatform
 #include "mozilla/layers/TextureD3D11.h"
@@ -241,18 +242,50 @@ CompositableClient::GetTextureClientRecy
   if (mTextureClientRecycler) {
     return mTextureClientRecycler;
   }
 
   if (!mForwarder) {
     return nullptr;
   }
 
-  mTextureClientRecycler =
-    new layers::TextureClientRecycleAllocator(mForwarder);
+  if(!mForwarder->UsesImageBridge()) {
+    MOZ_ASSERT(NS_IsMainThread());
+    mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder);
+    return mTextureClientRecycler;
+  }
+
+  // Handle a case that mForwarder is ImageBridge
+
+  if (InImageBridgeChildThread()) {
+    mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder);
+    return mTextureClientRecycler;
+  }
+
+  ReentrantMonitor barrier("CompositableClient::GetTextureClientRecycler");
+  ReentrantMonitorAutoEnter mainThreadAutoMon(barrier);
+  bool done = false;
+
+  RefPtr<Runnable> runnable =
+    NS_NewRunnableFunction([&]() {
+      if (!mTextureClientRecycler) {
+        mTextureClientRecycler = new layers::TextureClientRecycleAllocator(mForwarder);
+      }
+      ReentrantMonitorAutoEnter childThreadAutoMon(barrier);
+      done = true;
+      barrier.NotifyAll();
+    });
+
+  ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(runnable.forget());
+
+  // should stop the thread until done.
+  while (!done) {
+    barrier.Wait();
+  }
+
   return mTextureClientRecycler;
 }
 
 void
 CompositableClient::DumpTextureClient(std::stringstream& aStream,
                                       TextureClient* aTexture,
                                       TextureDumpMode aCompress)
 {
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -225,16 +225,18 @@ public:
   static bool DestroyIPDLActor(PCompositableChild* actor);
 
   void InitIPDLActor(PCompositableChild* aActor, uint64_t aAsyncID = 0);
 
   TextureFlags GetTextureFlags() const { return mTextureFlags; }
 
   TextureClientRecycleAllocator* GetTextureClientRecycler();
 
+  bool HasTextureClientRecycler() { return !!mTextureClientRecycler; }
+
   static void DumpTextureClient(std::stringstream& aStream,
                                 TextureClient* aTexture,
                                 TextureDumpMode aCompress);
 protected:
   CompositableChild* mCompositableChild;
   RefPtr<CompositableForwarder> mForwarder;
   // Some layers may want to enforce some flags to all their textures
   // (like disallowing tiling)
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -724,59 +724,52 @@ TextureClient::IsSharedWithCompositor() 
 }
 
 void
 TextureClient::AddFlags(TextureFlags aFlags)
 {
   MOZ_ASSERT(!IsSharedWithCompositor() ||
              ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient()));
   mFlags |= aFlags;
-  if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) {
-    mActor->SendRecycleTexture(mFlags);
-  }
 }
 
 void
 TextureClient::RemoveFlags(TextureFlags aFlags)
 {
   MOZ_ASSERT(!IsSharedWithCompositor() ||
              ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient()));
   mFlags &= ~aFlags;
-  if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) {
-    mActor->SendRecycleTexture(mFlags);
-  }
 }
 
 void
 TextureClient::RecycleTexture(TextureFlags aFlags)
 {
   MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE);
   MOZ_ASSERT(!mIsLocked);
-  if (mIsLocked) {
-    return;
-  }
-
-  LockActor();
 
   mAddedToCompositableClient = false;
   if (mFlags != aFlags) {
     mFlags = aFlags;
-    if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) {
-      mActor->SendRecycleTexture(mFlags);
-    }
   }
-
-  UnlockActor();
 }
 
 void
 TextureClient::SetAddedToCompositableClient()
 {
   if (!mAddedToCompositableClient) {
     mAddedToCompositableClient = true;
+    if(!(GetFlags() & TextureFlags::RECYCLE)) {
+      return;
+    }
+    MOZ_ASSERT(!mIsLocked);
+    LockActor();
+    if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) {
+      mActor->SendRecycleTexture(mFlags);
+    }
+    UnlockActor();
   }
 }
 
 void
 TextureClient::WaitFenceHandleOnImageBridge(Mutex& aMutex)
 {
   MOZ_ASSERT(NS_IsMainThread());
   aMutex.AssertCurrentThreadOwns();
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -43,16 +43,17 @@ namespace mozilla {
 
 namespace gl {
 class SharedSurface_Gralloc;
 }
 
 namespace layers {
 
 class AsyncTransactionWaiter;
+class BufferTextureData;
 class CompositableForwarder;
 class GrallocTextureData;
 class ClientIPCAllocator;
 class CompositableClient;
 struct PlanarYCbCrData;
 class Image;
 class PTextureChild;
 class TextureChild;
@@ -294,16 +295,18 @@ public:
 
 #ifdef XP_WIN
   virtual D3D11TextureData* AsD3D11TextureData() {
     return nullptr;
   }
 #endif
 
   virtual GrallocTextureData* AsGrallocTextureData() { return nullptr; }
+
+  virtual BufferTextureData* AsBufferTextureData() { return nullptr; }
 };
 
 /**
  * TextureClient is a thin abstraction over texture data that need to be shared
  * between the content process and the compositor process. It is the
  * content-side half of a TextureClient/TextureHost pair. A corresponding
  * TextureHost lives on the compositor-side.
  *
@@ -510,17 +513,17 @@ public:
   {
     return (mFlags & aFlags) == aFlags;
   }
 
   void AddFlags(TextureFlags aFlags);
 
   void RemoveFlags(TextureFlags aFlags);
 
-  // The TextureClient must not be locked when calling this method.
+  // Must not be called when TextureClient is in use by CompositableClient.
   void RecycleTexture(TextureFlags aFlags);
 
   /**
    * After being shared with the compositor side, an immutable texture is never
    * modified, it can only be read. It is safe to not Lock/Unlock immutable
    * textures.
    */
   bool IsImmutable() const { return !!(mFlags & TextureFlags::IMMUTABLE); }
@@ -727,17 +730,19 @@ protected:
   uint32_t mExpectedDtRefs;
 #endif
   bool mIsLocked;
   // This member tracks that the texture was written into until the update
   // is sent to the compositor. We need this remember to lock mReadLock on
   // behalf of the compositor just before sending the notification.
   bool mUpdated;
 
+  // Used when TextureClient is recycled with TextureFlags::RECYCLE flag.
   bool mAddedToCompositableClient;
+
   bool mWorkaroundAnnoyingSharedSurfaceLifetimeIssues;
   bool mWorkaroundAnnoyingSharedSurfaceOwnershipIssues;
 
   RefPtr<TextureReadbackSink> mReadbackSink;
 
   uint64_t mFwdTransactionId;
 
   // Serial id of TextureClient. It is unique in current process.
--- a/gfx/layers/client/TextureClientRecycleAllocator.cpp
+++ b/gfx/layers/client/TextureClientRecycleAllocator.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxPlatform.h"
+#include "ImageContainer.h"
+#include "mozilla/layers/BufferTexture.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/CompositableForwarder.h"
 #include "TextureClientRecycleAllocator.h"
 
 namespace mozilla {
 namespace layers {
 
 // Used to keep TextureClient's reference count stable as not to disrupt recycling.
@@ -66,16 +68,53 @@ public:
                                 mTextureFlags,
                                 mAllocationFlags);
   }
 
 protected:
   TextureClientRecycleAllocator* mAllocator;
 };
 
+YCbCrTextureClientAllocationHelper::YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData,
+                                                                       TextureFlags aTextureFlags)
+  : ITextureClientAllocationHelper(gfx::SurfaceFormat::YUV,
+                                   aData.mYSize,
+                                   BackendSelector::Content,
+                                   aTextureFlags,
+                                   ALLOC_DEFAULT)
+  , mData(aData)
+{
+}
+
+bool
+YCbCrTextureClientAllocationHelper::IsCompatible(TextureClient* aTextureClient)
+{
+  MOZ_ASSERT(aTextureClient->GetFormat() == gfx::SurfaceFormat::YUV);
+
+  BufferTextureData* bufferData = aTextureClient->GetInternalData()->AsBufferTextureData();
+  if (!bufferData ||
+      aTextureClient->GetSize() != mData.mYSize ||
+      bufferData->GetCbCrSize().isNothing() ||
+      bufferData->GetCbCrSize().ref() != mData.mCbCrSize ||
+      bufferData->GetStereoMode().isNothing() ||
+      bufferData->GetStereoMode().ref() != mData.mStereoMode) {
+    return false;
+  }
+  return true;
+}
+
+already_AddRefed<TextureClient>
+YCbCrTextureClientAllocationHelper::Allocate(CompositableForwarder* aAllocator)
+{
+  return TextureClient::CreateForYCbCr(aAllocator,
+                                       mData.mYSize, mData.mCbCrSize,
+                                       mData.mStereoMode,
+                                       mTextureFlags);
+}
+
 TextureClientRecycleAllocator::TextureClientRecycleAllocator(CompositableForwarder* aAllocator)
   : mSurfaceAllocator(aAllocator)
   , mMaxPooledSize(kMaxPooledSized)
   , mLock("TextureClientRecycleAllocatorImp.mLock")
 {
 }
 
 TextureClientRecycleAllocator::~TextureClientRecycleAllocator()
@@ -88,35 +127,16 @@ TextureClientRecycleAllocator::~TextureC
 }
 
 void
 TextureClientRecycleAllocator::SetMaxPoolSize(uint32_t aMax)
 {
   mMaxPooledSize = aMax;
 }
 
-class TextureClientRecycleTask : public Runnable
-{
-public:
-  explicit TextureClientRecycleTask(TextureClient* aClient, TextureFlags aFlags)
-    : mTextureClient(aClient)
-    , mFlags(aFlags)
-  {}
-
-  NS_IMETHOD Run() override
-  {
-    mTextureClient->RecycleTexture(mFlags);
-    return NS_OK;
-  }
-
-private:
-  RefPtr<TextureClient> mTextureClient;
-  TextureFlags mFlags;
-};
-
 already_AddRefed<TextureClient>
 TextureClientRecycleAllocator::CreateOrRecycle(gfx::SurfaceFormat aFormat,
                                                gfx::IntSize aSize,
                                                BackendSelector aSelector,
                                                TextureFlags aTextureFlags,
                                                TextureAllocationFlags aAllocFlags)
 {
   MOZ_ASSERT(!(aTextureFlags & TextureFlags::RECYCLE));
@@ -142,27 +162,26 @@ TextureClientRecycleAllocator::CreateOrR
 
   RefPtr<TextureClientHolder> textureHolder;
 
   {
     MutexAutoLock lock(mLock);
     if (!mPooledClients.empty()) {
       textureHolder = mPooledClients.top();
       mPooledClients.pop();
-      RefPtr<Runnable> task;
       // If a pooled TextureClient is not compatible, release it.
       if (!aHelper.IsCompatible(textureHolder->GetTextureClient())) {
         // Release TextureClient.
-        task = new TextureClientReleaseTask(textureHolder->GetTextureClient());
+        RefPtr<Runnable> task = new TextureClientReleaseTask(textureHolder->GetTextureClient());
         textureHolder->ClearTextureClient();
         textureHolder = nullptr;
+        mSurfaceAllocator->GetMessageLoop()->PostTask(task.forget());
       } else {
-        task = new TextureClientRecycleTask(textureHolder->GetTextureClient(), aHelper.mTextureFlags);
+        textureHolder->GetTextureClient()->RecycleTexture(aHelper.mTextureFlags);
       }
-      mSurfaceAllocator->GetMessageLoop()->PostTask(task.forget());
     }
   }
 
   if (!textureHolder) {
     // Allocate new TextureClient
     RefPtr<TextureClient> texture = aHelper.Allocate(mSurfaceAllocator);
     if (!texture) {
       return nullptr;
--- a/gfx/layers/client/TextureClientRecycleAllocator.h
+++ b/gfx/layers/client/TextureClientRecycleAllocator.h
@@ -13,16 +13,17 @@
 #include "mozilla/RefPtr.h"
 #include "TextureClient.h"
 #include "mozilla/Mutex.h"
 
 namespace mozilla {
 namespace layers {
 
 class TextureClientHolder;
+struct PlanarYCbCrData;
 
 class ITextureClientRecycleAllocator
 {
 protected:
   virtual ~ITextureClientRecycleAllocator() {}
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ITextureClientRecycleAllocator)
@@ -52,16 +53,31 @@ public:
 
   const gfx::SurfaceFormat mFormat;
   const gfx::IntSize mSize;
   const BackendSelector mSelector;
   const TextureFlags mTextureFlags;
   const TextureAllocationFlags mAllocationFlags;
 };
 
+class YCbCrTextureClientAllocationHelper : public ITextureClientAllocationHelper
+{
+public:
+  YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData,
+                                     TextureFlags aTextureFlags);
+
+  bool IsCompatible(TextureClient* aTextureClient) override;
+
+  already_AddRefed<TextureClient> Allocate(CompositableForwarder* aAllocator) override;
+
+protected:
+  const PlanarYCbCrData& mData;
+};
+
+
 /**
  * TextureClientRecycleAllocator provides TextureClients allocation and
  * recycling capabilities. It expects allocations of same sizes and
  * attributres. If a recycled TextureClient is different from
  * requested one, the recycled one is dropped and new TextureClient is allocated.
  *
  * By default this uses TextureClient::CreateForDrawing to allocate new texture
  * clients.
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
@@ -9,16 +9,17 @@
 #include "gfx2DGlue.h"                  // for Moz2D transition helpers
 #include "ISurfaceAllocator.h"          // for ISurfaceAllocator, etc
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/Types.h"          // for SurfaceFormat::SurfaceFormat::YUV
 #include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
 #include "mozilla/layers/ImageClient.h"  // for ImageClient
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor, etc
 #include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TextureClientRecycleAllocator.h"
 #include "mozilla/layers/BufferTexture.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/ImageBridgeChild.h"  // for ImageBridgeChild
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "nsISupportsImpl.h"            // for Image::AddRef
 #include "mozilla/ipc/Shmem.h"
 
 namespace mozilla {
@@ -171,21 +172,28 @@ SharedPlanarYCbCrImage::IsValid() {
   return !!mTextureClient;
 }
 
 bool
 SharedPlanarYCbCrImage::Allocate(PlanarYCbCrData& aData)
 {
   MOZ_ASSERT(!mTextureClient,
              "This image already has allocated data");
+  static const uint32_t MAX_POOLED_VIDEO_COUNT = 5;
 
-  mTextureClient = TextureClient::CreateForYCbCr(mCompositable->GetForwarder(),
-                                                 aData.mYSize, aData.mCbCrSize,
-                                                 aData.mStereoMode,
-                                                 mCompositable->GetTextureFlags());
+  if (!mCompositable->HasTextureClientRecycler()) {
+    // Initialize TextureClientRecycler
+    mCompositable->GetTextureClientRecycler()->SetMaxPoolSize(MAX_POOLED_VIDEO_COUNT);
+  }
+
+  {
+    YCbCrTextureClientAllocationHelper helper(aData, mCompositable->GetTextureFlags());
+    mTextureClient = mCompositable->GetTextureClientRecycler()->CreateOrRecycle(helper);
+  }
+
   if (!mTextureClient) {
     NS_WARNING("SharedPlanarYCbCrImage::Allocate failed.");
     return false;
   }
 
   MappedYCbCrTextureData mapped;
   // The locking here is sort of a lie. The SharedPlanarYCbCrImage just pulls
   // pointers out of the TextureClient and keeps them around, which works only
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -19,133 +19,42 @@
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "gfxPrefs.h"
 
 #include "Decoder.h"
+#include "IDecodingTask.h"
 #include "RasterImage.h"
 
 using std::max;
 using std::min;
 
 namespace mozilla {
 namespace image {
 
 ///////////////////////////////////////////////////////////////////////////////
-// Helper runnables.
-///////////////////////////////////////////////////////////////////////////////
-
-class NotifyProgressWorker : public Runnable
-{
-public:
-  /**
-   * Called by the DecodePool when it's done some significant portion of
-   * decoding, so that progress can be recorded and notifications can be sent.
-   */
-  static void Dispatch(RasterImage* aImage,
-                       Progress aProgress,
-                       const nsIntRect& aInvalidRect,
-                       SurfaceFlags aSurfaceFlags)
-  {
-    MOZ_ASSERT(aImage);
-
-    nsCOMPtr<nsIRunnable> worker =
-      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aSurfaceFlags);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mImage->NotifyProgress(mProgress, mInvalidRect, mSurfaceFlags);
-    return NS_OK;
-  }
-
-private:
-  NotifyProgressWorker(RasterImage* aImage,
-                       Progress aProgress,
-                       const nsIntRect& aInvalidRect,
-                       SurfaceFlags aSurfaceFlags)
-    : mImage(aImage)
-    , mProgress(aProgress)
-    , mInvalidRect(aInvalidRect)
-    , mSurfaceFlags(aSurfaceFlags)
-  { }
-
-  RefPtr<RasterImage> mImage;
-  const Progress mProgress;
-  const nsIntRect mInvalidRect;
-  const SurfaceFlags mSurfaceFlags;
-};
-
-class NotifyDecodeCompleteWorker : public Runnable
-{
-public:
-  /**
-   * Called by the DecodePool when decoding is complete, so that final cleanup
-   * can be performed.
-   */
-  static void Dispatch(Decoder* aDecoder)
-  {
-    MOZ_ASSERT(aDecoder);
-
-    nsCOMPtr<nsIRunnable> worker = new NotifyDecodeCompleteWorker(aDecoder);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mDecoder->GetImage()->FinalizeDecoder(mDecoder);
-    return NS_OK;
-  }
-
-private:
-  explicit NotifyDecodeCompleteWorker(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  { }
-
-  RefPtr<Decoder> mDecoder;
-};
-
-#ifdef MOZ_NUWA_PROCESS
-
-class RegisterDecodeIOThreadWithNuwaRunnable : public Runnable
-{
-public:
-  NS_IMETHOD Run()
-  {
-    NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
-    return NS_OK;
-  }
-};
-
-#endif // MOZ_NUWA_PROCESS
-
-
-///////////////////////////////////////////////////////////////////////////////
 // DecodePool implementation.
 ///////////////////////////////////////////////////////////////////////////////
 
 /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
 /* static */ uint32_t DecodePool::sNumCores = 0;
 
 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
 
 struct Work
 {
   enum class Type {
-    DECODE,
+    TASK,
     SHUTDOWN
   } mType;
 
-  RefPtr<Decoder> mDecoder;
+  RefPtr<IDecodingTask> mTask;
 };
 
 class DecodePoolImpl
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
 
@@ -184,82 +93,81 @@ public:
   void RequestShutdown()
   {
     MonitorAutoLock lock(mMonitor);
     mShuttingDown = true;
     mMonitor.NotifyAll();
   }
 
   /// Pushes a new decode work item.
-  void PushWork(Decoder* aDecoder)
+  void PushWork(IDecodingTask* aTask)
   {
-    MOZ_ASSERT(aDecoder);
-    RefPtr<Decoder> decoder(aDecoder);
+    MOZ_ASSERT(aTask);
+    RefPtr<IDecodingTask> task(aTask);
 
     MonitorAutoLock lock(mMonitor);
 
     if (mShuttingDown) {
       // Drop any new work on the floor if we're shutting down.
       return;
     }
 
-    if (aDecoder->IsMetadataDecode()) {
-      mMetadataDecodeQueue.AppendElement(Move(decoder));
+    if (task->Priority() == TaskPriority::eHigh) {
+      mHighPriorityQueue.AppendElement(Move(task));
     } else {
-      mFullDecodeQueue.AppendElement(Move(decoder));
+      mLowPriorityQueue.AppendElement(Move(task));
     }
 
     mMonitor.Notify();
   }
 
   /// Pops a new work item, blocking if necessary.
   Work PopWork()
   {
     MonitorAutoLock lock(mMonitor);
 
     do {
-      // Prioritize metadata decodes over full decodes.
-      if (!mMetadataDecodeQueue.IsEmpty()) {
-        return PopWorkFromQueue(mMetadataDecodeQueue);
+      if (!mHighPriorityQueue.IsEmpty()) {
+        return PopWorkFromQueue(mHighPriorityQueue);
       }
 
-      if (!mFullDecodeQueue.IsEmpty()) {
-        return PopWorkFromQueue(mFullDecodeQueue);
+      if (!mLowPriorityQueue.IsEmpty()) {
+        return PopWorkFromQueue(mLowPriorityQueue);
       }
 
       if (mShuttingDown) {
         Work work;
         work.mType = Work::Type::SHUTDOWN;
         return work;
       }
 
       // Nothing to do; block until some work is available.
       mMonitor.Wait();
     } while (true);
   }
 
 private:
   ~DecodePoolImpl() { }
 
-  Work PopWorkFromQueue(nsTArray<RefPtr<Decoder>>& aQueue)
+  Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
   {
     Work work;
-    work.mType = Work::Type::DECODE;
-    work.mDecoder = aQueue.LastElement().forget();
+    work.mType = Work::Type::TASK;
+    work.mTask = aQueue.LastElement().forget();
     aQueue.RemoveElementAt(aQueue.Length() - 1);
 
     return work;
   }
 
   nsThreadPoolNaming mThreadNaming;
 
   // mMonitor guards the queues and mShuttingDown.
   Monitor mMonitor;
-  nsTArray<RefPtr<Decoder>> mMetadataDecodeQueue;
-  nsTArray<RefPtr<Decoder>> mFullDecodeQueue;
+  nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
+  nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
   bool mShuttingDown;
 };
 
 class DecodePoolWorker : public Runnable
 {
 public:
   explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
 
@@ -270,18 +178,18 @@ public:
     mImpl->InitCurrentThread();
 
     nsCOMPtr<nsIThread> thisThread;
     nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thisThread));
 
     do {
       Work work = mImpl->PopWork();
       switch (work.mType) {
-        case Work::Type::DECODE:
-          DecodePool::Singleton()->Decode(work.mDecoder);
+        case Work::Type::TASK:
+          work.mTask->Run();
           break;
 
         case Work::Type::SHUTDOWN:
           DecodePoolImpl::ShutdownThread(thisThread);
           return NS_OK;
 
         default:
           MOZ_ASSERT_UNREACHABLE("Unknown work type");
@@ -359,18 +267,20 @@ DecodePool::DecodePool()
   }
 
   // Initialize the I/O thread.
   nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
                      "Should successfully create image I/O thread");
 
 #ifdef MOZ_NUWA_PROCESS
-  nsCOMPtr<nsIRunnable> worker = new RegisterDecodeIOThreadWithNuwaRunnable();
-  rv = mIOThread->Dispatch(worker, NS_DISPATCH_NORMAL);
+  rv = mIOThread->Dispatch(NS_NewRunnableFunction([]() -> void {
+    NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
+  }), NS_DISPATCH_NORMAL);
+
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv),
                      "Should register decode IO thread with Nuwa process");
 #endif
 
   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
   if (obsSvc) {
     obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
   }
@@ -404,100 +314,46 @@ DecodePool::Observe(nsISupports*, const 
   if (ioThread) {
     ioThread->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
-DecodePool::AsyncDecode(Decoder* aDecoder)
+DecodePool::AsyncRun(IDecodingTask* aTask)
 {
-  MOZ_ASSERT(aDecoder);
-  mImpl->PushWork(aDecoder);
+  MOZ_ASSERT(aTask);
+  mImpl->PushWork(aTask);
 }
 
 void
-DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
+DecodePool::SyncRunIfPreferred(IDecodingTask* aTask)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aDecoder);
+  MOZ_ASSERT(aTask);
 
-  if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) {
-    Decode(aDecoder);
+  if (aTask->ShouldPreferSyncRun()) {
+    aTask->Run();
     return;
   }
 
-  AsyncDecode(aDecoder);
+  AsyncRun(aTask);
 }
 
 void
-DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
+DecodePool::SyncRunIfPossible(IDecodingTask* aTask)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  Decode(aDecoder);
+  MOZ_ASSERT(aTask);
+  aTask->Run();
 }
 
 already_AddRefed<nsIEventTarget>
 DecodePool::GetIOEventTarget()
 {
   MutexAutoLock threadPoolLock(mMutex);
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
   return target.forget();
 }
 
-void
-DecodePool::Decode(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-
-  nsresult rv = aDecoder->Decode();
-
-  if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
-    // If this isn't a metadata decode, notify for the progress we've made so
-    // far. It's important that metadata decode results are delivered
-    // atomically, so for those decodes we wait until NotifyDecodeComplete.
-    if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) {
-      NotifyProgress(aDecoder);
-    }
-    // The decoder will ensure that a new worker gets enqueued to continue
-    // decoding when more data is available.
-  } else {
-    NotifyDecodeComplete(aDecoder);
-  }
-}
-
-void
-DecodePool::NotifyProgress(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-  MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
-
-  if (!NS_IsMainThread() ||
-      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
-    NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
-                                   aDecoder->TakeProgress(),
-                                   aDecoder->TakeInvalidRect(),
-                                   aDecoder->GetSurfaceFlags());
-    return;
-  }
-
-  aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
-                                       aDecoder->TakeInvalidRect(),
-                                       aDecoder->GetSurfaceFlags());
-}
-
-void
-DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-
-  if (!NS_IsMainThread() ||
-      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
-    NotifyDecodeCompleteWorker::Dispatch(aDecoder);
-    return;
-  }
-
-  aDecoder->GetImage()->FinalizeDecoder(aDecoder);
-}
-
 } // namespace image
 } // namespace mozilla
--- a/image/DecodePool.h
+++ b/image/DecodePool.h
@@ -21,16 +21,17 @@
 class nsIThread;
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class DecodePoolImpl;
+class IDecodingTask;
 
 /**
  * DecodePool is a singleton class that manages decoding of raster images. It
  * owns a pool of image decoding threads that are used for asynchronous
  * decoding.
  *
  * DecodePool allows callers to run a decoder, handling management of the
  * decoder's lifecycle and whether it executes on the main thread,
@@ -48,60 +49,54 @@ public:
 
   /// Returns the singleton instance.
   static DecodePool* Singleton();
 
   /// @return the number of processor cores we have available. This is not the
   /// same as the number of decoding threads we're actually using.
   static uint32_t NumberOfCores();
 
-  /// Ask the DecodePool to run @aDecoder asynchronously and return immediately.
-  void AsyncDecode(Decoder* aDecoder);
+  /// Ask the DecodePool to run @aTask asynchronously and return immediately.
+  void AsyncRun(IDecodingTask* aTask);
 
   /**
-   * Run @aDecoder synchronously if the image it's decoding is small. If the
-   * image is too large, or if the source data isn't complete yet, run @aDecoder
-   * asynchronously instead.
+   * Run @aTask synchronously if the task would prefer it. It's up to the task
+   * itself to make this decision; @see IDecodingTask::ShouldPreferSyncRun(). If
+   * @aTask doesn't prefer it, just run @aTask asynchronously and return
+   * immediately.
    */
-  void SyncDecodeIfSmall(Decoder* aDecoder);
+  void SyncRunIfPreferred(IDecodingTask* aTask);
 
   /**
-   * Run aDecoder synchronously if at all possible. If it can't complete
-   * synchronously because the source data isn't complete, asynchronously decode
-   * the rest.
+   * Run @aTask synchronously. This does not guarantee that @aTask will complete
+   * synchronously. If, for example, @aTask doesn't yet have the data it needs to
+   * run synchronously, it may recover by scheduling an async task to finish up
+   * the work when the remaining data is available.
    */
-  void SyncDecodeIfPossible(Decoder* aDecoder);
+  void SyncRunIfPossible(IDecodingTask* aTask);
 
   /**
    * Returns an event target interface to the DecodePool's I/O thread. Callers
    * who want to deliver data to workers on the DecodePool can use this event
    * target.
    *
    * @return An nsIEventTarget interface to the thread pool's I/O thread.
    */
   already_AddRefed<nsIEventTarget> GetIOEventTarget();
 
-  /**
-   * Notify about progress on aDecoder.
-   */
-  void NotifyProgress(Decoder* aDecoder);
-
 private:
   friend class DecodePoolWorker;
 
   DecodePool();
   virtual ~DecodePool();
 
-  void Decode(Decoder* aDecoder);
-  void NotifyDecodeComplete(Decoder* aDecoder);
-
   static StaticRefPtr<DecodePool> sSingleton;
   static uint32_t sNumCores;
 
-  RefPtr<DecodePoolImpl>    mImpl;
+  RefPtr<DecodePoolImpl> mImpl;
 
   // mMutex protects mThreads and mIOThread.
   Mutex                         mMutex;
   nsTArray<nsCOMPtr<nsIThread>> mThreads;
   nsCOMPtr<nsIThread>           mIOThread;
 };
 
 } // namespace image
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Decoder.h"
 
 #include "mozilla/gfx/2D.h"
 #include "DecodePool.h"
 #include "GeckoProfiler.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/Telemetry.h"
 
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
@@ -76,28 +77,25 @@ Decoder::Init()
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
 nsresult
-Decoder::Decode(IResumable* aOnResume)
+Decoder::Decode(NotNull<IResumable*> aOnResume)
 {
   MOZ_ASSERT(mInitialized, "Should be initialized here");
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
-  // If no IResumable was provided, default to |this|.
-  IResumable* onResume = aOnResume ? aOnResume : this;
-
   // We keep decoding chunks until the decode completes or there are no more
   // chunks available.
   while (!GetDecodeDone() && !HasError()) {
-    auto newState = mIterator->AdvanceOrScheduleResume(onResume);
+    auto newState = mIterator->AdvanceOrScheduleResume(aOnResume.get());
 
     if (newState == SourceBufferIterator::WAITING) {
       // We can't continue because the rest of the data hasn't arrived from the
       // network yet. We don't have to do anything special; the
       // SourceBufferIterator will ensure that Decode() gets called again on a
       // DecodePool thread when more data is available.
       return NS_OK;
     }
@@ -118,24 +116,16 @@ Decoder::Decode(IResumable* aOnResume)
 
     Write(mIterator->Data(), mIterator->Length());
   }
 
   CompleteDecode();
   return HasError() ? NS_ERROR_FAILURE : NS_OK;
 }
 
-void
-Decoder::Resume()
-{
-  DecodePool* decodePool = DecodePool::Singleton();
-  MOZ_ASSERT(decodePool);
-  decodePool->AsyncDecode(this);
-}
-
 bool
 Decoder::ShouldSyncDecode(size_t aByteLimit)
 {
   MOZ_ASSERT(aByteLimit > 0);
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
   return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
 }
@@ -450,17 +440,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 
   // If we are going to keep decoding we should notify now about the first frame being done.
   if (mImage && mFrameCount == 1 && HasAnimation()) {
     MOZ_ASSERT(HasProgress());
-    DecodePool::Singleton()->NotifyProgress(this);
+    IDecodingTask::NotifyProgress(WrapNotNull(this));
   }
 }
 
 void
 Decoder::PostInvalidation(const nsIntRect& aRect,
                           const Maybe<nsIntRect>& aRectAtTargetSize
                             /* = Nothing() */)
 {
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_Decoder_h
 #define mozilla_image_Decoder_h
 
 #include "FrameAnimator.h"
 #include "RasterImage.h"
+#include "mozilla/NotNull.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
 #include "DecoderFlags.h"
 #include "Downscaler.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "SourceBuffer.h"
 #include "SurfaceFlags.h"
@@ -20,38 +21,37 @@
 namespace mozilla {
 
 namespace Telemetry {
   enum ID : uint32_t;
 } // namespace Telemetry
 
 namespace image {
 
-class Decoder : public IResumable
+class Decoder
 {
 public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
 
   explicit Decoder(RasterImage* aImage);
 
   /**
    * Initialize an image decoder. Decoders may not be re-initialized.
    */
   void Init();
 
   /**
    * Decodes, reading all data currently available in the SourceBuffer.
    *
    * If more data is needed, Decode() will schedule @aOnResume to be called when
-   * more data is available. If @aOnResume is null or unspecified, the default
-   * implementation resumes decoding on a DecodePool thread. Most callers should
-   * use the default implementation.
+   * more data is available.
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    */
-  nsresult Decode(IResumable* aOnResume = nullptr);
+  nsresult Decode(NotNull<IResumable*> aOnResume);
 
   /**
    * Given a maximum number of bytes we're willing to decode, @aByteLimit,
    * returns true if we should attempt to run this decoder synchronously.
    */
   bool ShouldSyncDecode(size_t aByteLimit);
 
   /**
@@ -82,22 +82,16 @@ public:
   /**
    * Returns true if there's any progress to report.
    */
   bool HasProgress() const
   {
     return mProgress != NoProgress || !mInvalidRect.IsEmpty();
   }
 
-  // We're not COM-y, so we don't get refcounts by default
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder, override)
-
-  // Implement IResumable.
-  virtual void Resume() override;
-
   /*
    * State.
    */
 
   /**
    * If we're doing a metadata decode, we only decode the image's headers, which
    * is enough to determine the image's intrinsic size. A metadata decode is
    * enabled by calling SetMetadataDecode() *before* calling Init().
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -5,16 +5,17 @@
 
 #include "DecoderFactory.h"
 
 #include "nsMimeTypes.h"
 #include "mozilla/RefPtr.h"
 #include "nsString.h"
 
 #include "Decoder.h"
+#include "IDecodingTask.h"
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 
 namespace mozilla {
@@ -99,20 +100,20 @@ DecoderFactory::GetDecoder(DecoderType a
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
   }
 
   return decoder.forget();
 }
 
-/* static */ already_AddRefed<Decoder>
+/* static */ already_AddRefed<IDecodingTask>
 DecoderFactory::CreateDecoder(DecoderType aType,
-                              RasterImage* aImage,
-                              SourceBuffer* aSourceBuffer,
+                              NotNull<RasterImage*> aImage,
+                              NotNull<SourceBuffer*> aSourceBuffer,
                               const Maybe<IntSize>& aTargetSize,
                               DecoderFlags aDecoderFlags,
                               SurfaceFlags aSurfaceFlags,
                               int aSampleSize)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
@@ -134,23 +135,24 @@ DecoderFactory::CreateDecoder(DecoderTyp
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
   }
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
-  return decoder.forget();
+  RefPtr<IDecodingTask> task = new DecodingTask(WrapNotNull(decoder));
+  return task.forget();
 }
 
-/* static */ already_AddRefed<Decoder>
+/* static */ already_AddRefed<IDecodingTask>
 DecoderFactory::CreateAnimationDecoder(DecoderType aType,
-                                       RasterImage* aImage,
-                                       SourceBuffer* aSourceBuffer,
+                                       NotNull<RasterImage*> aImage,
+                                       NotNull<SourceBuffer*> aSourceBuffer,
                                        DecoderFlags aDecoderFlags,
                                        SurfaceFlags aSurfaceFlags)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
@@ -166,23 +168,24 @@ DecoderFactory::CreateAnimationDecoder(D
   decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
   decoder->SetSurfaceFlags(aSurfaceFlags);
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
-  return decoder.forget();
+  RefPtr<IDecodingTask> task = new DecodingTask(WrapNotNull(decoder));
+  return task.forget();
 }
 
-/* static */ already_AddRefed<Decoder>
+/* static */ already_AddRefed<IDecodingTask>
 DecoderFactory::CreateMetadataDecoder(DecoderType aType,
-                                      RasterImage* aImage,
-                                      SourceBuffer* aSourceBuffer,
+                                      NotNull<RasterImage*> aImage,
+                                      NotNull<SourceBuffer*> aSourceBuffer,
                                       int aSampleSize)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   RefPtr<Decoder> decoder =
     GetDecoder(aType, aImage, /* aIsRedecode = */ false);
@@ -193,22 +196,23 @@ DecoderFactory::CreateMetadataDecoder(De
   decoder->SetIterator(aSourceBuffer->Iterator());
   decoder->SetSampleSize(aSampleSize);
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
-  return decoder.forget();
+  RefPtr<IDecodingTask> task = new MetadataDecodingTask(WrapNotNull(decoder));
+  return task.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
-                                       SourceBuffer* aSourceBuffer,
+                                       NotNull<SourceBuffer*> aSourceBuffer,
                                        const Maybe<IntSize>& aTargetSize,
                                        SurfaceFlags aSurfaceFlags)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   RefPtr<Decoder> decoder =
@@ -244,17 +248,17 @@ DecoderFactory::CreateAnonymousDecoder(D
     return nullptr;
   }
 
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
-                                               SourceBuffer* aSourceBuffer)
+                                               NotNull<SourceBuffer*> aSourceBuffer)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   RefPtr<Decoder> decoder =
     GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
   MOZ_ASSERT(decoder, "Should have a decoder now");
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -5,26 +5,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_DecoderFactory_h
 #define mozilla_image_DecoderFactory_h
 
 #include "DecoderFlags.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
 #include "mozilla/gfx/2D.h"
 #include "nsCOMPtr.h"
 #include "SurfaceFlags.h"
 
 class nsACString;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
+class IDecodingTask;
 class RasterImage;
 class SourceBuffer;
 
 /**
  * The type of decoder; this is usually determined from a MIME type using
  * DecoderFactory::GetDecoderType().
  */
 enum class DecoderType
@@ -59,20 +61,20 @@ public:
    *                    a target size for a decoder type which doesn't support
    *                    downscale-during-decode.
    * @param aDecoderFlags Flags specifying the behavior of this decoder.
    * @param aSurfaceFlags Flags specifying the type of output this decoder
    *                      should produce.
    * @param aSampleSize The sample size requested using #-moz-samplesize (or 0
    *                    if none).
    */
-  static already_AddRefed<Decoder>
+  static already_AddRefed<IDecodingTask>
   CreateDecoder(DecoderType aType,
-                RasterImage* aImage,
-                SourceBuffer* aSourceBuffer,
+                NotNull<RasterImage*> aImage,
+                NotNull<SourceBuffer*> aSourceBuffer,
                 const Maybe<gfx::IntSize>& aTargetSize,
                 DecoderFlags aDecoderFlags,
                 SurfaceFlags aSurfaceFlags,
                 int aSampleSize);
 
   /**
    * Creates and initializes a decoder for animated images of type @aType.
    * The decoder will send notifications to @aImage.
@@ -81,20 +83,20 @@ public:
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    * @param aDecoderFlags Flags specifying the behavior of this decoder.
    * @param aSurfaceFlags Flags specifying the type of output this decoder
    *                      should produce.
    */
-  static already_AddRefed<Decoder>
+  static already_AddRefed<IDecodingTask>
   CreateAnimationDecoder(DecoderType aType,
-                         RasterImage* aImage,
-                         SourceBuffer* aSourceBuffer,
+                         NotNull<RasterImage*> aImage,
+                         NotNull<SourceBuffer*> aSourceBuffer,
                          DecoderFlags aDecoderFlags,
                          SurfaceFlags aSurfaceFlags);
 
   /**
    * Creates and initializes a metadata decoder of type @aType. This decoder
    * will only decode the image's header, extracting metadata like the size of
    * the image. No actual image data will be decoded and no surfaces will be
    * allocated. The decoder will send notifications to @aImage.
@@ -102,20 +104,20 @@ public:
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    * @param aSampleSize The sample size requested using #-moz-samplesize (or 0
    *                    if none).
    */
-  static already_AddRefed<Decoder>
+  static already_AddRefed<IDecodingTask>
   CreateMetadataDecoder(DecoderType aType,
-                        RasterImage* aImage,
-                        SourceBuffer* aSourceBuffer,
+                        NotNull<RasterImage*> aImage,
+                        NotNull<SourceBuffer*> aSourceBuffer,
                         int aSampleSize);
 
   /**
    * Creates and initializes an anonymous decoder (one which isn't associated
    * with an Image object). Only the first frame of the image will be decoded.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
@@ -124,33 +126,33 @@ public:
    *                    be scaled to during decoding. It's an error to specify
    *                    a target size for a decoder type which doesn't support
    *                    downscale-during-decode.
    * @param aSurfaceFlags Flags specifying the type of output this decoder
    *                      should produce.
    */
   static already_AddRefed<Decoder>
   CreateAnonymousDecoder(DecoderType aType,
-                         SourceBuffer* aSourceBuffer,
+                         NotNull<SourceBuffer*> aSourceBuffer,
                          const Maybe<gfx::IntSize>& aTargetSize,
                          SurfaceFlags aSurfaceFlags);
 
   /**
    * Creates and initializes an anonymous metadata decoder (one which isn't
    * associated with an Image object). This decoder will only decode the image's
    * header, extracting metadata like the size of the image. No actual image
    * data will be decoded and no surfaces will be allocated.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    */
   static already_AddRefed<Decoder>
   CreateAnonymousMetadataDecoder(DecoderType aType,
-                                 SourceBuffer* aSourceBuffer);
+                                 NotNull<SourceBuffer*> aSourceBuffer);
 
 private:
   virtual ~DecoderFactory() = 0;
 
   /**
    * An internal method which allocates a new decoder of the requested @aType.
    */
   static already_AddRefed<Decoder> GetDecoder(DecoderType aType,
new file mode 100644
--- /dev/null
+++ b/image/IDecodingTask.cpp
@@ -0,0 +1,154 @@
+/* -*- 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 "IDecodingTask.h"
+
+#include "gfxPrefs.h"
+#include "nsThreadUtils.h"
+
+#include "Decoder.h"
+#include "DecodePool.h"
+#include "RasterImage.h"
+
+namespace mozilla {
+namespace image {
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for sending notifications to the image associated with a decoder.
+///////////////////////////////////////////////////////////////////////////////
+
+/* static */ void
+IDecodingTask::NotifyProgress(NotNull<Decoder*> aDecoder)
+{
+  MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
+
+  // Synchronously notify if we can.
+  if (NS_IsMainThread() &&
+      !(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
+    aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
+                                         aDecoder->TakeInvalidRect(),
+                                         aDecoder->GetSurfaceFlags());
+    return;
+  }
+
+  // We're forced to notify asynchronously.
+  NotNull<RefPtr<Decoder>> decoder = aDecoder;
+  NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
+    decoder->GetImage()->NotifyProgress(decoder->TakeProgress(),
+                                        decoder->TakeInvalidRect(),
+                                        decoder->GetSurfaceFlags());
+  }));
+}
+
+static void
+NotifyDecodeComplete(NotNull<Decoder*> aDecoder)
+{
+  // Synchronously notify if we can.
+  if (NS_IsMainThread() &&
+      !(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
+    aDecoder->GetImage()->FinalizeDecoder(aDecoder);
+    return;
+  }
+
+  // We're forced to notify asynchronously.
+  NotNull<RefPtr<Decoder>> decoder = aDecoder;
+  NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
+    decoder->GetImage()->FinalizeDecoder(decoder.get());
+  }));
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// IDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+void
+IDecodingTask::Resume()
+{
+  DecodePool::Singleton()->AsyncRun(this);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// DecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+DecodingTask::DecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{
+  MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
+             "Use MetadataDecodingTask for metadata decodes");
+}
+
+void
+DecodingTask::Run()
+{
+  nsresult rv = mDecoder->Decode(WrapNotNull(this));
+
+  if (NS_SUCCEEDED(rv) && !mDecoder->GetDecodeDone()) {
+    // Notify for the progress we've made so far.
+    if (mDecoder->HasProgress()) {
+      NotifyProgress(mDecoder);
+    }
+
+    // We don't need to do anything else for this case. The decoder itself will
+    // ensure that we get reenqueued when more data is available.
+    return;
+  }
+
+  NotifyDecodeComplete(mDecoder);
+}
+
+bool
+DecodingTask::ShouldPreferSyncRun() const
+{
+  return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// MetadataDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+MetadataDecodingTask::MetadataDecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{
+  MOZ_ASSERT(mDecoder->IsMetadataDecode(),
+             "Use DecodingTask for non-metadata decodes");
+}
+
+void
+MetadataDecodingTask::Run()
+{
+  nsresult rv = mDecoder->Decode(WrapNotNull(this));
+
+  if (NS_SUCCEEDED(rv) && !mDecoder->GetDecodeDone()) {
+    // It's important that metadata decode results are delivered atomically, so
+    // we'll wait until NotifyDecodeComplete() to report any progress.  We don't
+    // need to do anything else for this case. The decoder itself will ensure
+    // that we get reenqueued when more data is available.
+    return;
+  }
+
+  NotifyDecodeComplete(mDecoder);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// AnonymousDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{ }
+
+void
+AnonymousDecodingTask::Run()
+{
+  mDecoder->Decode(WrapNotNull(this));
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/IDecodingTask.h
@@ -0,0 +1,151 @@
+/* -*- 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/. */
+
+/**
+ * An interface for tasks which can execute on the ImageLib DecodePool, and
+ * various implementations.
+ */
+
+#ifndef mozilla_image_IDecodingTask_h
+#define mozilla_image_IDecodingTask_h
+
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+
+#include "SourceBuffer.h"
+
+namespace mozilla {
+namespace image {
+
+class Decoder;
+
+/// A priority hint that DecodePool can use when scheduling an IDecodingTask.
+enum class TaskPriority : uint8_t
+{
+  eLow,
+  eHigh
+};
+
+/**
+ * An interface for tasks which can execute on the ImageLib DecodePool.
+ */
+class IDecodingTask : public IResumable
+{
+public:
+  /// Run the task.
+  virtual void Run() = 0;
+
+  /// @return true if, given the option, this task prefers to run synchronously.
+  virtual bool ShouldPreferSyncRun() const = 0;
+
+  /// @return a priority hint that DecodePool can use when scheduling this task.
+  virtual TaskPriority Priority() const = 0;
+
+  /// A default implementation of IResumable which resubmits the task to the
+  /// DecodePool. Subclasses can override this if they need different behavior.
+  void Resume() override;
+
+  /// @return a non-null weak pointer to the Decoder associated with this task.
+  virtual NotNull<Decoder*> GetDecoder() const = 0;
+
+  // Notify the Image associated with a Decoder of its progress, sending a
+  // runnable to the main thread if necessary.
+  // XXX(seth): This is a hack that will be removed soon.
+  static void NotifyProgress(NotNull<Decoder*> aDecoder);
+
+protected:
+  virtual ~IDecodingTask() { }
+};
+
+
+/**
+ * An IDecodingTask implementation for full decodes of images.
+ */
+class DecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodingTask, override)
+
+  explicit DecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+  bool ShouldPreferSyncRun() const override;
+
+  // Full decodes are low priority compared to metadata decodes because they
+  // don't block layout or page load.
+  TaskPriority Priority() const override { return TaskPriority::eLow; }
+
+  NotNull<Decoder*> GetDecoder() const override { return mDecoder; }
+
+private:
+  virtual ~DecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+
+/**
+ * An IDecodingTask implementation for metadata decodes of images.
+ */
+class MetadataDecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override)
+
+  explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+
+  // Metadata decodes are very fast (since they only need to examine an image's
+  // header) so there's no reason to refuse to run them synchronously if the
+  // caller will allow us to.
+  bool ShouldPreferSyncRun() const override { return true; }
+
+  // Metadata decodes run at the highest priority because they block layout and
+  // page load.
+  TaskPriority Priority() const override { return TaskPriority::eHigh; }
+
+  NotNull<Decoder*> GetDecoder() const override { return mDecoder; }
+
+private:
+  virtual ~MetadataDecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+
+/**
+ * An IDecodingTask implementation for anonymous decoders - that is, decoders
+ * with no associated Image object.
+ */
+class AnonymousDecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
+
+  explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+
+  bool ShouldPreferSyncRun() const override { return true; }
+  TaskPriority Priority() const override { return TaskPriority::eLow; }
+
+  // Anonymous decoders normally get all their data at once. We have tests where
+  // they don't; in these situations, the test re-runs them manually. So no
+  // matter what, we don't want to resume by posting a task to the DecodePool.
+  void Resume() override { }
+
+  NotNull<Decoder*> GetDecoder() const override { return mDecoder; }
+
+private:
+  virtual ~AnonymousDecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_IDecodingTask_h
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -7,16 +7,17 @@
 #include "ImageOps.h"
 
 #include "ClippedImage.h"
 #include "DecodePool.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "DynamicImage.h"
 #include "FrozenImage.h"
+#include "IDecodingTask.h"
 #include "Image.h"
 #include "imgIContainer.h"
 #include "mozilla/gfx/2D.h"
 #include "nsStreamUtils.h"
 #include "OrientedImage.h"
 #include "SourceBuffer.h"
 
 using namespace mozilla::gfx;
@@ -101,38 +102,37 @@ ImageOps::DecodeToSurface(nsIInputStream
   // Figure out how much data we've been passed.
   uint64_t length;
   rv = inputStream->Available(&length);
   if (NS_FAILED(rv) || length > UINT32_MAX) {
     return nullptr;
   }
 
   // Write the data into a SourceBuffer.
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   sourceBuffer->ExpectLength(length);
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
   sourceBuffer->Complete(NS_OK);
 
   // Create a decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
   RefPtr<Decoder> decoder =
-    DecoderFactory::CreateAnonymousDecoder(decoderType,
-                                           sourceBuffer,
-                                           Nothing(),
-                                           ToSurfaceFlags(aFlags));
+    DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
+                                           Nothing(), ToSurfaceFlags(aFlags));
   if (!decoder) {
     return nullptr;
   }
 
   // Run the decoder synchronously.
-  decoder->Decode();
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
+  task->Run();
   if (!decoder->GetDecodeDone() || decoder->HasError()) {
     return nullptr;
   }
 
   // Pull out the surface.
   RawAccessFrameRef frame = decoder->GetCurrentFrameRef();
   if (!frame) {
     return nullptr;
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -10,16 +10,17 @@
 #include "RasterImage.h"
 
 #include "gfxPlatform.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "Decoder.h"
 #include "prenv.h"
 #include "prsystem.h"
+#include "IDecodingTask.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
 #include "LookupResult.h"
 #include "nsIConsoleService.h"
 #include "nsIInputStream.h"
 #include "nsIScriptError.h"
 #include "nsISupportsPrimitives.h"
@@ -78,17 +79,17 @@ RasterImage::RasterImage(ImageURL* aURI 
   mDecodeCount(0),
   mRequestedSampleSize(0),
   mImageProducerID(ImageContainer::AllocateProducerID()),
   mLastFrameID(0),
   mLastImageContainerDrawResult(DrawResult::NOT_READY),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
-  mSourceBuffer(new SourceBuffer()),
+  mSourceBuffer(WrapNotNull(new SourceBuffer())),
   mFrameCount(0),
   mHasSize(false),
   mTransient(false),
   mSyncLoad(false),
   mDiscardable(false),
   mHasSourceData(false),
   mHasBeenDecoded(false),
   mPendingAnimation(false),
@@ -1225,43 +1226,43 @@ RasterImage::RequestDecodeForSize(const 
   // Look up the first frame of the image, which will implicitly start decoding
   // if it's not available right now.
   LookupFrame(0, aSize, flags);
 
   return NS_OK;
 }
 
 static void
-LaunchDecoder(Decoder* aDecoder,
-              RasterImage* aImage,
-              uint32_t aFlags,
-              bool aHaveSourceData)
+LaunchDecodingTask(IDecodingTask* aTask,
+                   RasterImage* aImage,
+                   uint32_t aFlags,
+                   bool aHaveSourceData)
 {
   if (aHaveSourceData) {
     // If we have all the data, we can sync decode if requested.
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible",
+      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPossible",
         js::ProfileEntry::Category::GRAPHICS,
         "%s", aImage->GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfPossible(aDecoder);
+      DecodePool::Singleton()->SyncRunIfPossible(aTask);
       return;
     }
 
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE_IF_FAST) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfSmall",
+      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPreferred",
         js::ProfileEntry::Category::GRAPHICS,
         "%s", aImage->GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfSmall(aDecoder);
+      DecodePool::Singleton()->SyncRunIfPreferred(aTask);
       return;
     }
   }
 
   // Perform an async decode. We also take this path if we don't have all the
   // source data yet, since sync decoding is impossible in that situation.
-  DecodePool::Singleton()->AsyncDecode(aDecoder);
+  DecodePool::Singleton()->AsyncRun(aTask);
 }
 
 NS_IMETHODIMP
 RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
@@ -1300,72 +1301,72 @@ RasterImage::Decode(const IntSize& aSize
   SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags);
   if (IsOpaque()) {
     // If there's no transparency, it doesn't matter whether we premultiply
     // alpha or not.
     surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
 
   // Create a decoder.
-  RefPtr<Decoder> decoder;
+  RefPtr<IDecodingTask> task;
   if (mAnim) {
-    decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
-                                                     mSourceBuffer, decoderFlags,
-                                                     surfaceFlags);
+    task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
+                                                  mSourceBuffer, decoderFlags,
+                                                  surfaceFlags);
   } else {
-    decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
-                                            targetSize, decoderFlags,
-                                            surfaceFlags,
-                                            mRequestedSampleSize);
+    task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
+                                         mSourceBuffer, targetSize, decoderFlags,
+                                         surfaceFlags, mRequestedSampleSize);
   }
 
   // Make sure DecoderFactory was able to create a decoder successfully.
-  if (!decoder) {
+  if (!task) {
     return NS_ERROR_FAILURE;
   }
 
   // Add a placeholder for the first frame to the SurfaceCache so we won't
   // trigger any more decoders with the same parameters.
+  SurfaceKey surfaceKey =
+    RasterSurfaceKey(aSize,
+                     task->GetDecoder()->GetSurfaceFlags(),
+                     /* aFrameNum = */ 0);
   InsertOutcome outcome =
-    SurfaceCache::InsertPlaceholder(ImageKey(this),
-                                    RasterSurfaceKey(aSize,
-                                                     decoder->GetSurfaceFlags(),
-                                                     /* aFrameNum = */ 0));
+    SurfaceCache::InsertPlaceholder(ImageKey(this), surfaceKey);
   if (outcome != InsertOutcome::SUCCESS) {
     return NS_ERROR_FAILURE;
   }
 
   mDecodeCount++;
 
   // We're ready to decode; start the decoder.
-  LaunchDecoder(decoder, this, aFlags, mHasSourceData);
+  LaunchDecodingTask(task, this, aFlags, mHasSourceData);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RasterImage::DecodeMetadata(uint32_t aFlags)
 {
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   MOZ_ASSERT(!mHasSize, "Should not do unnecessary metadata decodes");
 
   // Create a decoder.
-  RefPtr<Decoder> decoder =
-    DecoderFactory::CreateMetadataDecoder(mDecoderType, this, mSourceBuffer,
-                                          mRequestedSampleSize);
+  RefPtr<IDecodingTask> task =
+    DecoderFactory::CreateMetadataDecoder(mDecoderType, WrapNotNull(this),
+                                          mSourceBuffer, mRequestedSampleSize);
 
   // Make sure DecoderFactory was able to create a decoder successfully.
-  if (!decoder) {
+  if (!task) {
     return NS_ERROR_FAILURE;
   }
 
   // We're ready to decode; start the decoder.
-  LaunchDecoder(decoder, this, aFlags, mHasSourceData);
+  LaunchDecodingTask(task, this, aFlags, mHasSourceData);
   return NS_OK;
 }
 
 void
 RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags)
 {
   if (!mHasSize) {
     return;
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -27,16 +27,17 @@
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
 #include "DecoderFactory.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/NotNull.h"
 #include "mozilla/Pair.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "ImageContainer.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
@@ -375,17 +376,17 @@ private: // data
   // the last time we updated it.
   DrawResult mLastImageContainerDrawResult;
 
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
   // The source data for this image.
-  RefPtr<SourceBuffer>     mSourceBuffer;
+  NotNull<RefPtr<SourceBuffer>>  mSourceBuffer;
 
   // The number of frames this image has.
   uint32_t                   mFrameCount;
 
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mTransient:1;     // Is the image short-lived?
   bool                       mSyncLoad:1;      // Are we loading synchronously?
@@ -453,9 +454,18 @@ protected:
 inline NS_IMETHODIMP
 RasterImage::GetAnimationMode(uint16_t* aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
 }
 
 } // namespace image
 } // namespace mozilla
 
+/**
+ * Casting RasterImage to nsISupports is ambiguous. This method handles that.
+ */
+inline nsISupports*
+ToSupports(mozilla::image::RasterImage* p)
+{
+  return NS_ISUPPORTS_CAST(mozilla::image::ImageResource*, p);
+}
+
 #endif /* mozilla_image_RasterImage_h */
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -1198,16 +1198,20 @@ VectorImage::OnSVGDocumentLoaded()
   // Tell *our* observers that we're done loading.
   if (mProgressTracker) {
     Progress progress = FLAG_SIZE_AVAILABLE |
                         FLAG_HAS_TRANSPARENCY |
                         FLAG_FRAME_COMPLETE |
                         FLAG_DECODE_COMPLETE |
                         FLAG_ONLOAD_UNBLOCKED;
 
+    if (mHaveAnimations) {
+      progress |= FLAG_IS_ANIMATED;
+    }
+
     // Merge in any saved progress from OnImageDataComplete.
     if (mLoadProgress) {
       progress |= *mLoadProgress;
       mLoadProgress = Nothing();
     }
 
     mProgressTracker->SyncNotifyProgress(progress, GetMaxSizedIntRect());
   }
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -1,32 +1,34 @@
 /* -*- 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 "ImageLogging.h" // Must appear first
+
+#include <algorithm>
+#include <cstdint>
+
 #include "gfxColor.h"
 #include "gfxPlatform.h"
 #include "imgFrame.h"
 #include "nsColor.h"
 #include "nsIInputStream.h"
 #include "nsMemory.h"
 #include "nsPNGDecoder.h"
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
 #include "RasterImage.h"
 #include "SurfacePipeFactory.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Telemetry.h"
 
-#include <algorithm>
-
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
 
 static LazyLogModule sPNGLog("PNGDecoder");
 static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting");
 
@@ -89,30 +91,31 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
 nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
  : Decoder(aImage)
+ , mLexer(Transition::ToUnbuffered(State::FINISHED_PNG_DATA,
+                                   State::PNG_DATA,
+                                   SIZE_MAX))
  , mPNG(nullptr)
  , mInfo(nullptr)
  , mCMSLine(nullptr)
  , interlacebuf(nullptr)
  , mInProfile(nullptr)
  , mTransform(nullptr)
  , format(gfx::SurfaceFormat::UNKNOWN)
- , mHeaderBytesRead(0)
  , mCMSMode(0)
  , mChannels(0)
  , mPass(0)
  , mFrameIsHidden(false)
  , mDisablePremultipliedAlpha(false)
- , mSuccessfulEarlyFinish(false)
  , mNumFrames(0)
 { }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mPNG) {
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
   }
@@ -344,35 +347,68 @@ nsPNGDecoder::InitInternal()
                               nsPNGDecoder::end_callback);
 
 }
 
 void
 nsPNGDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
-
-  // libpng uses setjmp/longjmp for error handling. Set it up.
-  if (setjmp(png_jmpbuf(mPNG))) {
+  MOZ_ASSERT(aBuffer);
+  MOZ_ASSERT(aCount > 0);
 
-    // We exited early. If mSuccessfulEarlyFinish isn't true, then we
-    // encountered an error. We might not really know what caused it, but it
-    // makes more sense to blame the data.
-    if (!mSuccessfulEarlyFinish && !HasError()) {
-      PostDataError();
-    }
+  Maybe<TerminalState> terminalState =
+    mLexer.Lex(aBuffer, aCount, [=](State aState,
+                                    const char* aData, size_t aLength) {
+      switch (aState) {
+        case State::PNG_DATA:
+          return ReadPNGData(aData, aLength);
+        case State::FINISHED_PNG_DATA:
+          return FinishedPNGData();
+      }
+      MOZ_CRASH("Unknown State");
+    });
 
-    png_destroy_read_struct(&mPNG, &mInfo, nullptr);
-    return;
+  if (terminalState == Some(TerminalState::FAILURE)) {
+    PostDataError();
+  }
+}
+
+LexerTransition<nsPNGDecoder::State>
+nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength)
+{
+  // libpng uses setjmp/longjmp for error handling.
+  if (setjmp(png_jmpbuf(mPNG))) {
+    return Transition::TerminateFailure();
   }
 
   // Pass the data off to libpng.
   png_process_data(mPNG, mInfo,
-                   reinterpret_cast<unsigned char*>(const_cast<char*>((aBuffer))),
-                   aCount);
+                   reinterpret_cast<unsigned char*>(const_cast<char*>((aData))),
+                   aLength);
+
+  if (HasError()) {
+    return Transition::TerminateFailure();
+  }
+
+  if (GetDecodeDone()) {
+    return Transition::TerminateSuccess();
+  }
+
+  // Keep reading data.
+  return Transition::ContinueUnbuffered(State::PNG_DATA);
+}
+
+LexerTransition<nsPNGDecoder::State>
+nsPNGDecoder::FinishedPNGData()
+{
+  // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read
+  // all that data something is really wrong.
+  MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
+  return Transition::TerminateFailure();
 }
 
 // Sets up gamma pre-correction in libpng before our callback gets called.
 // We need to do this if we don't end up with a CMS profile.
 static void
 PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
 {
   double aGamma;
@@ -647,20 +683,20 @@ nsPNGDecoder::info_callback(png_structp 
     // if the IDAT chunk is part of the animation 2) the frame rect of the first
     // fDAT chunk otherwise. If we are not animated then we want to make sure to
     // call PostHasTransparency in the metadata decode if we need to. So it's okay
     // to pass IntRect(0, 0, width, height) here for animated images; they will
     // call with the proper first frame rect in the full decode.
     auto transparency = decoder->GetTransparencyType(decoder->format, frameRect);
     decoder->PostHasTransparencyIfNeeded(transparency);
 
-    // We have the metadata we're looking for, so we don't need to decode any
-    // further.
-    decoder->mSuccessfulEarlyFinish = true;
-    png_longjmp(decoder->mPNG, 1);
+    // We have the metadata we're looking for, so stop here, before we allocate
+    // buffers below.
+    png_process_data_pause(png_ptr, /* save = */ false);
+    return;
   }
 
 #ifdef PNG_APNG_SUPPORTED
   if (isAnimated) {
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
@@ -771,16 +807,18 @@ nsPNGDecoder::row_callback(png_structp p
    */
   nsPNGDecoder* decoder =
     static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
   if (decoder->mFrameIsHidden) {
     return;  // Skip this frame.
   }
 
+  MOZ_ASSERT_IF(decoder->IsFirstFrameDecode(), decoder->mNumFrames == 0);
+
   while (pass > decoder->mPass) {
     // Advance to the next pass. We may have to do this multiple times because
     // libpng will skip passes if the image is so small that no pixels have
     // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset
     // once for every pass to perform interpolation properly.
     decoder->mPipe.ResetToFirstRow();
     decoder->mPass++;
   }
@@ -876,20 +914,20 @@ nsPNGDecoder::frame_info_callback(png_st
   nsPNGDecoder* decoder =
                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
   // old frame is done
   decoder->EndImageFrame();
 
   if (!decoder->mFrameIsHidden && decoder->IsFirstFrameDecode()) {
     // We're about to get a second non-hidden frame, but we only want the first.
-    // Stop decoding now.
+    // Stop decoding now. (And avoid allocating the unnecessary buffers below.)
     decoder->PostDecodeDone();
-    decoder->mSuccessfulEarlyFinish = true;
-    png_longjmp(decoder->mPNG, 1);
+    png_process_data_pause(png_ptr, /* save = */ false);
+    return;
   }
 
   // Only the first frame can be hidden, so unhide unconditionally here.
   decoder->mFrameIsHidden = false;
 
   const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo),
                           png_get_next_frame_y_offset(png_ptr, decoder->mInfo),
                           png_get_next_frame_width(png_ptr, decoder->mInfo),
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsPNGDecoder_h
 #define mozilla_image_decoders_nsPNGDecoder_h
 
 #include "Decoder.h"
 #include "png.h"
 #include "qcms.h"
+#include "StreamingLexer.h"
 #include "SurfacePipe.h"
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
@@ -79,39 +80,45 @@ private:
       return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
                png_color_type == PNG_COLOR_TYPE_RGB) &&
               png_bit_depth == 8);
     } else {
       return false;
     }
   }
 
+  enum class State
+  {
+    PNG_DATA,
+    FINISHED_PNG_DATA
+  };
+
+  LexerTransition<State> ReadPNGData(const char* aData, size_t aLength);
+  LexerTransition<State> FinishedPNGData();
+
+  StreamingLexer<State> mLexer;
+
 public:
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   uint8_t* mCMSLine;
   uint8_t* interlacebuf;
   qcms_profile* mInProfile;
   qcms_transform* mTransform;
 
   gfx::SurfaceFormat format;
 
-  // For metadata decodes.
-  uint8_t mSizeBytes[8]; // Space for width and height, both 4 bytes
-  uint32_t mHeaderBytesRead;
-
   // whether CMS or premultiplied alpha are forced off
   uint32_t mCMSMode;
 
   uint8_t mChannels;
   uint8_t mPass;
   bool mFrameIsHidden;
   bool mDisablePremultipliedAlpha;
-  bool mSuccessfulEarlyFinish;
 
   struct AnimFrameInfo
   {
     AnimFrameInfo();
 #ifdef PNG_APNG_SUPPORTED
     AnimFrameInfo(png_structp aPNG, png_infop aInfo);
 #endif
 
--- a/image/moz.build
+++ b/image/moz.build
@@ -53,16 +53,17 @@ EXPORTS += [
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',
+    'IDecodingTask.cpp',
     'Image.cpp',
     'ImageCacheKey.cpp',
     'ImageFactory.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',
     'MultipartImage.cpp',
--- a/image/test/gtest/Common.cpp
+++ b/image/test/gtest/Common.cpp
@@ -258,17 +258,17 @@ RowHasPixels(SourceSurface* aSurface,
 // SurfacePipe Helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 already_AddRefed<Decoder>
 CreateTrivialDecoder()
 {
   gfxPrefs::GetSingleton();
   DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
                                            DefaultSurfaceFlags());
   return decoder.forget();
 }
 
 void
 AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "Common.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "decoders/nsBMPDecoder.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "imgITools.h"
 #include "ImageFactory.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIRunnable.h"
@@ -98,94 +99,82 @@ void WithSingleChunkDecode(const ImageTe
   ASSERT_TRUE(inputStream != nullptr);
 
   // Figure out how much data we have.
   uint64_t length;
   nsresult rv = inputStream->Available(&length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // Write the data into a SourceBuffer.
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   sourceBuffer->ExpectLength(length);
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   sourceBuffer->Complete(NS_OK);
 
   // Create a decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   // Run the full decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   // Call the lambda to verify the expected results.
   aResultChecker(decoder);
 }
 
 static void
 CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
 {
   WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
     CheckDecoderResults(aTestCase, aDecoder);
   });
 }
 
-class NoResume : public IResumable
-{
-public:
-  NS_INLINE_DECL_REFCOUNTING(NoResume, override)
-  virtual void Resume() override { }
-
-private:
-  ~NoResume() { }
-};
-
 static void
 CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
 {
   nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
   ASSERT_TRUE(inputStream != nullptr);
 
   // Figure out how much data we have.
   uint64_t length;
   nsresult rv = inputStream->Available(&length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // Create a SourceBuffer and a decoder.
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   sourceBuffer->ExpectLength(length);
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
-  // Decode synchronously, using a |NoResume| IResumable so the Decoder doesn't
-  // attempt to schedule itself on a nonexistent DecodePool when we write more
-  // data into the SourceBuffer.
-  RefPtr<NoResume> noResume = new NoResume();
   for (uint64_t read = 0; read < length ; ++read) {
     uint64_t available = 0;
     rv = inputStream->Available(&available);
     ASSERT_TRUE(available > 0);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
 
     rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
 
-    decoder->Decode(noResume);
+    task->Run();
   }
 
   sourceBuffer->Complete(NS_OK);
-  decoder->Decode(noResume);
+  task->Run();
   
   CheckDecoderResults(aTestCase, decoder);
 }
 
 static void
 CheckDownscaleDuringDecode(const ImageTestCase& aTestCase)
 {
   // This function expects that |aTestCase| consists of 25 lines of green,
--- a/image/test/gtest/TestMetadata.cpp
+++ b/image/test/gtest/TestMetadata.cpp
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "Common.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "decoders/nsBMPDecoder.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "imgITools.h"
 #include "ImageFactory.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIRunnable.h"
@@ -42,35 +43,36 @@ CheckMetadata(const ImageTestCase& aTest
   ASSERT_TRUE(inputStream != nullptr);
 
   // Figure out how much data we have.
   uint64_t length;
   nsresult rv = inputStream->Available(&length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // Write the data into a SourceBuffer.
-  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
   sourceBuffer->ExpectLength(length);
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   sourceBuffer->Complete(NS_OK);
 
   // Create a metadata decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   if (aBMPWithinICO == BMPWithinICO::YES) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
   }
 
   // Run the metadata decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   // Ensure that the metadata decoder didn't make progress it shouldn't have
   // (which would indicate that it decoded past the header of the image).
   Progress metadataProgress = decoder->TakeProgress();
   EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
                                          FLAG_HAS_TRANSPARENCY |
                                          FLAG_IS_ANIMATED)));
 
@@ -98,23 +100,24 @@ CheckMetadata(const ImageTestCase& aTest
   EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
             bool(metadataProgress & FLAG_IS_ANIMATED));
 
   // Create a full decoder, so we can compare the result.
   decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   if (aBMPWithinICO == BMPWithinICO::YES) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
   }
 
   // Run the full decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
   Progress fullProgress = decoder->TakeProgress();
 
   // If the metadata decoder set a progress bit, the full decoder should also
   // have set the same bit.
   EXPECT_EQ(fullProgress, metadataProgress | fullProgress);
 
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -485,34 +485,34 @@ WrapperAnswer::RecvHasInstance(const Obj
 
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
                                    uint32_t* classValue)
 {
-    *classValue = js::ESClass_Other;
+    *classValue = uint32_t(js::ESClass::Other);
 
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
         return false;
     JSContext* cx = jsapi.cx();
 
     RootedObject obj(cx, findObjectById(cx, objId));
     if (!obj)
         return fail(jsapi, rs);
 
     LOG("%s.getBuiltinClass()", ReceiverObj(objId));
 
-    js::ESClassValue cls;
+    js::ESClass cls;
     if (!js::GetBuiltinClass(cx, obj, &cls))
         return fail(jsapi, rs);
 
-    *classValue = cls;
+    *classValue = uint32_t(cls);
     return ok(rs);
 }
 
 bool
 WrapperAnswer::RecvIsArray(const ObjectId& objId, ReturnStatus* rs,
                            uint32_t* ans)
 {
     *ans = uint32_t(IsArrayAnswer::NotArray);
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -121,18 +121,17 @@ class CPOWProxyHandler : public BaseProx
 
     virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                        MutableHandle<PropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy,
                              MutableHandleValue v, bool* bp) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject obj,
-                                 js::ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
     virtual void objectMoved(JSObject* proxy, const JSObject* old) const override;
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
@@ -724,33 +723,31 @@ WrapperOwner::hasInstance(JSContext* cx,
         return ipcfail(cx);
 
     LOG_STACK();
 
     return ok(cx, status);
 }
 
 bool
-CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                  js::ESClassValue* classValue) const
+CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
-    FORWARD(getBuiltinClass, (cx, proxy, classValue));
+    FORWARD(getBuiltinClass, (cx, proxy, cls));
 }
 
 bool
-WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy,
-                              js::ESClassValue* classValue)
+WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
 {
     ObjectId objId = idOf(proxy);
 
-    uint32_t cls = ESClass_Other;
+    uint32_t classValue = uint32_t(ESClass::Other);
     ReturnStatus status;
-    if (!SendGetBuiltinClass(objId, &status, &cls))
+    if (!SendGetBuiltinClass(objId, &status, &classValue))
         return ipcfail(cx);
-    *classValue = ESClassValue(cls);
+    *cls = ESClass(classValue);
 
     LOG_STACK();
 
     return ok(cx, status);
 }
 
 bool
 CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -49,17 +49,17 @@ class WrapperOwner : public virtual Java
 
     // SpiderMonkey extensions.
     bool getPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
                                JS::MutableHandle<JS::PropertyDescriptor> desc);
     bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp);
     bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::HandleObject proxy,
                                       JS::AutoIdVector& props);
     bool hasInstance(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleValue v, bool* bp);
-    bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, js::ESClassValue* classValue);
+    bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, js::ESClass* cls);
     bool isArray(JSContext* cx, JS::HandleObject proxy, JS::IsArrayAnswer* answer);
     const char* className(JSContext* cx, JS::HandleObject proxy);
     bool getPrototype(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleObject protop);
     bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, bool* isOrdinary,
                                 JS::MutableHandleObject protop);
 
     bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g);
 
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -953,24 +953,36 @@ Valueify(const JSClass* c)
 {
     return (const Class*)c;
 }
 
 /**
  * Enumeration describing possible values of the [[Class]] internal property
  * value of objects.
  */
-enum ESClassValue {
-    ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
-    ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
-    ESClass_Date, ESClass_Set, ESClass_Map, ESClass_Promise, ESClass_MapIterator,
-    ESClass_SetIterator,
+enum class ESClass {
+    Object,
+    Array,
+    Number,
+    String,
+    Boolean,
+    RegExp,
+    ArrayBuffer,
+    SharedArrayBuffer,
+    Date,
+    Set,
+    Map,
+    Promise,
+    MapIterator,
+    SetIterator,
+    Arguments,
+    Error,
 
     /** None of the above. */
-    ESClass_Other
+    Other
 };
 
 /* Fills |vp| with the unboxed value for boxed types, or undefined otherwise. */
 bool
 Unbox(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp);
 
 #ifdef DEBUG
 JS_FRIEND_API(bool)
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -311,17 +311,17 @@ class JS_FRIEND_API(BaseProxyHandler)
                                        MutableHandle<PropertyDescriptor> desc) const;
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const;
     virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const;
+                                 ESClass* cls) const;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const;
     virtual const char* className(JSContext* cx, HandleObject proxy) const;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const;
     virtual void trace(JSTracer* trc, JSObject* proxy) const;
     virtual void finalize(JSFreeOp* fop, JSObject* proxy) const;
     virtual void objectMoved(JSObject* proxy, const JSObject* old) const;
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -24,17 +24,16 @@
 
 #include "jsmath.h"
 #include "jsprf.h"
 #include "jsstr.h"
 #include "jsutil.h"
 
 #include "jswrapper.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmGenerator.h"
 #include "asmjs/WasmInstance.h"
 #include "asmjs/WasmJS.h"
 #include "asmjs/WasmSerialize.h"
 #include "builtin/SIMD.h"
 #include "frontend/Parser.h"
 #include "gc/Policy.h"
 #include "js/MemoryMetrics.h"
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -16,17 +16,16 @@
  * limitations under the License.
  */
 
 #include "asmjs/WasmBinaryToAST.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/MathAlgorithms.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmBinaryIterator.h"
 
 using namespace js;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::FloorLog2;
 
--- a/js/src/asmjs/WasmBinaryToExperimentalText.cpp
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -18,17 +18,16 @@
 
 #include "asmjs/WasmBinaryToExperimentalText.h"
 
 #include "mozilla/CheckedInt.h"
 
 #include "jsnum.h"
 #include "jsprf.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmAST.h"
 #include "asmjs/WasmBinaryToAST.h"
 #include "asmjs/WasmTypes.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/StringBuffer.h"
 
 using namespace js;
 using namespace js::wasm;
--- a/js/src/asmjs/WasmBinaryToText.cpp
+++ b/js/src/asmjs/WasmBinaryToText.cpp
@@ -16,17 +16,16 @@
  * limitations under the License.
  */
 
 #include "asmjs/WasmBinaryToText.h"
 
 #include "jsnum.h"
 #include "jsprf.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmAST.h"
 #include "asmjs/WasmBinaryToAST.h"
 #include "asmjs/WasmTypes.h"
 #include "vm/ArrayBufferObject.h"
 #include "vm/StringBuffer.h"
 
 using namespace js;
 using namespace js::wasm;
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -117,16 +117,19 @@ struct ShareableBase : RefCounted<T>
     }
 };
 
 // ShareableBytes is a ref-counted vector of bytes which are incrementally built
 // during compilation and then immutably shared.
 
 struct ShareableBytes : ShareableBase<ShareableBytes>
 {
+    ShareableBytes() = default;
+    explicit ShareableBytes(Bytes&& bytes) : bytes(Move(bytes)) {}
+
     // Vector is 'final', so instead make Vector a member and add boilerplate.
     Bytes bytes;
     size_t sizeOfExcludingThis(MallocSizeOf m) const { return bytes.sizeOfExcludingThis(m); }
     const uint8_t* begin() const { return bytes.begin(); }
     const uint8_t* end() const { return bytes.end(); }
     bool append(const uint8_t *p, uint32_t ct) { return bytes.append(p, ct); }
 };
 
@@ -405,16 +408,17 @@ typedef Vector<char16_t, 64> TwoByteName
 //
 // Metadata is built incrementally by ModuleGenerator and then shared immutably
 // between modules.
 
 struct MetadataCacheablePod
 {
     ModuleKind            kind;
     HeapUsage             heapUsage;
+    uint32_t              initialHeapLength;
     CompileArgs           compileArgs;
 
     MetadataCacheablePod() { mozilla::PodZero(this); }
 };
 
 struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
 {
     virtual ~Metadata() {}
rename from js/src/asmjs/Wasm.cpp
rename to js/src/asmjs/WasmCompile.cpp
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -11,50 +11,32 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#include "asmjs/Wasm.h"
+#include "asmjs/WasmCompile.h"
 
 #include "mozilla/CheckedInt.h"
-#include "mozilla/unused.h"
 
 #include "jsprf.h"
 
 #include "asmjs/WasmBinaryIterator.h"
 #include "asmjs/WasmGenerator.h"
-#include "asmjs/WasmInstance.h"
-#include "vm/ArrayBufferObject.h"
-#include "vm/Debugger.h"
-
-#include "jsatominlines.h"
-
-#include "vm/Debugger-inl.h"
+#include "vm/TypedArrayObject.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::IsNaN;
-using mozilla::Unused;
-
-/*****************************************************************************/
-// reporting
-
-static bool
-Fail(JSContext* cx, const char* str)
-{
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, str);
-    return false;
-}
 
 static bool
 Fail(JSContext* cx, const Decoder& d, const char* str)
 {
     uint32_t offset = d.currentOffset();
     char offsetStr[sizeof "4294967295"];
     JS_snprintf(offsetStr, sizeof offsetStr, "%" PRIu32, offset);
     JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_DECODE_FAIL, offsetStr, str);
@@ -491,19 +473,16 @@ DecodeExpr(FunctionDecoder& f)
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         break;
     }
 
     return f.iter().unrecognizedOpcode(expr);
 }
 
-/*****************************************************************************/
-// wasm decoding and generation
-
 static bool
 DecodePreamble(JSContext* cx, Decoder& d)
 {
     uint32_t u32;
     if (!d.readFixedU32(&u32) || u32 != MagicNumber)
         return Fail(cx, d, "failed to match magic number");
 
     if (!d.readFixedU32(&u32) || u32 != EncodingVersion)
@@ -755,17 +734,17 @@ DecodeImportSection(JSContext* cx, Decod
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "import section byte size mismatch");
 
     return true;
 }
 
 static bool
-DecodeMemorySection(JSContext* cx, Decoder& d, ModuleGenerator& mg, MutableHandleArrayBufferObject heap)
+DecodeMemorySection(JSContext* cx, Decoder& d, ModuleGenerator& mg)
 {
     uint32_t sectionStart, sectionSize;
     if (!d.startSection(MemorySectionId, &sectionStart, &sectionSize))
         return Fail(cx, d, "failed to start section");
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t initialSizePages;
@@ -798,21 +777,16 @@ DecodeMemorySection(JSContext* cx, Decod
         UniqueChars fieldName = DuplicateString("memory");
         if (!fieldName || !mg.addMemoryExport(Move(fieldName)))
             return false;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "memory section byte size mismatch");
 
-    bool signalsForOOB = CompileArgs(cx).useSignalHandlersForOOB;
-    heap.set(ArrayBufferObject::createForWasm(cx, initialSize.value(), signalsForOOB));
-    if (!heap)
-        return false;
-
     mg.initHeapUsage(HeapUsage::Unshared, initialSize.value());
     return true;
 }
 
 typedef HashSet<const char*, CStringHasher> CStringSet;
 
 static UniqueChars
 DecodeExportName(JSContext* cx, Decoder& d, CStringSet* dupSet)
@@ -971,56 +945,59 @@ DecodeCodeSection(JSContext* cx, Decoder
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "function section byte size mismatch");
 
     return mg.finishFuncDefs();
 }
 
 static bool
-DecodeDataSection(JSContext* cx, Decoder& d, Handle<ArrayBufferObject*> heap)
+DecodeDataSection(JSContext* cx, Decoder& d, ModuleGenerator& mg)
 {
     uint32_t sectionStart, sectionSize;
     if (!d.startSection(DataSectionId, &sectionStart, &sectionSize))
         return Fail(cx, d, "failed to start section");
     if (sectionStart == Decoder::NotStarted)
         return true;
 
-    if (!heap)
+    if (!mg.usesHeap())
         return Fail(cx, d, "data section requires a memory section");
 
     uint32_t numSegments;
     if (!d.readVarU32(&numSegments))
         return Fail(cx, d, "failed to read number of data segments");
 
-    uint8_t* const heapBase = heap->dataPointer();
-    uint32_t const heapLength = heap->byteLength();
-    uint32_t prevEnd = 0;
+    if (numSegments > MaxDataSegments)
+        return Fail(cx, d, "too many data segments");
 
-    for (uint32_t i = 0; i < numSegments; i++) {
-        uint32_t dstOffset;
-        if (!d.readVarU32(&dstOffset))
+    uint32_t max = mg.initialHeapLength();
+    for (uint32_t i = 0, prevEnd = 0; i < numSegments; i++) {
+        DataSegment seg;
+
+        if (!d.readVarU32(&seg.memoryOffset))
             return Fail(cx, d, "expected segment destination offset");
 
-        if (dstOffset < prevEnd)
+        if (seg.memoryOffset < prevEnd)
             return Fail(cx, d, "data segments must be disjoint and ordered");
 
-        uint32_t numBytes;
-        if (!d.readVarU32(&numBytes))
+        if (!d.readVarU32(&seg.length))
             return Fail(cx, d, "expected segment size");
 
-        if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
-            return Fail(cx, d, "data segment does not fit in memory");
+        if (seg.memoryOffset > max || max - seg.memoryOffset < seg.length)
+            return Fail(cx, d, "data segment data segment does not fit");
 
-        const uint8_t* src;
-        if (!d.readBytes(numBytes, &src))
+        seg.bytecodeOffset = d.currentOffset();
+
+        if (!d.readBytes(seg.length))
             return Fail(cx, d, "data segment shorter than declared");
 
-        memcpy(heapBase + dstOffset, src, numBytes);
-        prevEnd = dstOffset + numBytes;
+        if (!mg.addDataSegment(seg))
+            return false;
+
+        prevEnd = seg.memoryOffset + seg.length;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(cx, d, "data section byte size mismatch");
 
     return true;
 }
 
@@ -1028,17 +1005,17 @@ static bool
 MaybeDecodeNameSectionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t sectionStart,
                            uint32_t sectionSize)
 {
     uint32_t numFuncNames;
     if (!d.readVarU32(&numFuncNames))
         return false;
 
     if (numFuncNames > MaxFuncs)
-        return false;
+        return Fail(cx, d, "too many function names");
 
     NameInBytecodeVector funcNames;
     if (!funcNames.resize(numFuncNames))
         return false;
 
     for (uint32_t i = 0; i < numFuncNames; i++) {
         uint32_t numBytes;
         if (!d.readVarU32(&numBytes))
@@ -1098,18 +1075,17 @@ DecodeUnknownSections(JSContext* cx, Dec
         if (!d.skipSection())
             return Fail(cx, d, "failed to skip unknown section at end");
     }
 
     return true;
 }
 
 static UniqueModule
-DecodeModule(JSContext* cx, UniqueChars file, const ShareableBytes& bytecode,
-             MutableHandleArrayBufferObject heap)
+DecodeModule(JSContext* cx, UniqueChars file, const ShareableBytes& bytecode)
 {
     UniqueModuleGeneratorData init = js::MakeUnique<ModuleGeneratorData>(cx);
     if (!init)
         return nullptr;
 
     Decoder d(bytecode.begin(), bytecode.end());
 
     if (!DecodePreamble(cx, d))
@@ -1127,135 +1103,47 @@ DecodeModule(JSContext* cx, UniqueChars 
 
     if (!DecodeTableSection(cx, d, init.get()))
         return nullptr;
 
     ModuleGenerator mg(cx);
     if (!mg.init(Move(init), Move(file)))
         return nullptr;
 
-    if (!DecodeMemorySection(cx, d, mg, heap))
+    if (!DecodeMemorySection(cx, d, mg))
         return nullptr;
 
     if (!DecodeExportSection(cx, d, mg))
         return nullptr;
 
     if (!DecodeCodeSection(cx, d, mg))
         return nullptr;
 
-    if (!DecodeDataSection(cx, d, heap))
+    if (!DecodeDataSection(cx, d, mg))
         return nullptr;
 
     if (!DecodeNameSection(cx, d, mg))
         return nullptr;
 
     if (!DecodeUnknownSections(cx, d))
         return nullptr;
 
     return mg.finish(Move(importNames), bytecode);
 }
 
-/*****************************************************************************/
-// Top-level functions
-
-bool
-wasm::HasCompilerSupport(ExclusiveContext* cx)
-{
-    if (!cx->jitSupportsFloatingPoint())
-        return false;
-
-#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
-    return false;
-#else
-    return true;
-#endif
-}
-
-static bool
-CheckCompilerSupport(JSContext* cx)
+UniqueModule
+wasm::Compile(JSContext* cx, UniqueChars file, Bytes&& bytecode)
 {
-    if (!HasCompilerSupport(cx)) {
-#ifdef JS_MORE_DETERMINISTIC
-        fprintf(stderr, "WebAssembly is not supported on the current device.\n");
-#endif
-        JS_ReportError(cx, "WebAssembly is not supported on the current device.");
-        return false;
-    }
-    return true;
-}
-
-static bool
-GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v)
-{
-    JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
-    if (!atom)
-        return false;
-
-    RootedId id(cx, AtomToId(atom));
-    return GetProperty(cx, obj, obj, id, v);
-}
-
-static bool
-ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& importNames,
-                MutableHandle<FunctionVector> imports)
-{
-    if (!importNames.empty() && !importObj)
-        return Fail(cx, "no import object given");
+    MOZ_ASSERT(HasCompilerSupport(cx));
 
-    for (const ImportName& name : importNames) {
-        RootedValue v(cx);
-        if (!GetProperty(cx, importObj, name.module.get(), &v))
-            return false;
-
-        if (strlen(name.func.get()) > 0) {
-            if (!v.isObject())
-                return Fail(cx, "import object field is not an Object");
-
-            RootedObject obj(cx, &v.toObject());
-            if (!GetProperty(cx, obj, name.func.get(), &v))
-                return false;
-        }
-
-        if (!IsFunctionObject(v))
-            return Fail(cx, "import object field is not a Function");
-
-        if (!imports.append(&v.toObject().as<JSFunction>()))
-            return false;
-    }
-
-    return true;
-}
+    SharedBytes sharedBytes = cx->new_<ShareableBytes>(Move(bytecode));
+    if (!sharedBytes)
+        return nullptr;
 
-bool
-wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> view, HandleObject importObj,
-           MutableHandleWasmInstanceObject instanceObj)
-{
-    if (!CheckCompilerSupport(cx))
-        return false;
-
-    uint8_t* viewBegin = (uint8_t*)view->viewDataEither().unwrap(/* for copy */);
-
-    MutableBytes bytecode = cx->new_<ShareableBytes>();
-    if (!bytecode || !bytecode->append(viewBegin, view->byteLength()))
-        return false;
-
-    JS::AutoFilename filename;
-    if (!DescribeScriptedCaller(cx, &filename))
-        return false;
-
-    UniqueChars file = DuplicateString(filename.get());
-    if (!file)
-        return false;
-
-    Rooted<ArrayBufferObject*> heap(cx);
-    UniqueModule module = DecodeModule(cx, Move(file), *bytecode, &heap);
+    UniqueModule module = DecodeModule(cx, Move(file), *sharedBytes);
     if (!module) {
         if (!cx->isExceptionPending())
             ReportOutOfMemory(cx);
-        return false;
+        return nullptr;
     }
 
-    Rooted<FunctionVector> funcImports(cx, FunctionVector(cx));
-    if (!ImportFunctions(cx, importObj, module->importNames(), &funcImports))
-        return false;
-
-    return module->instantiate(cx, funcImports, heap, instanceObj);
+    return module;
 }
rename from js/src/asmjs/Wasm.h
rename to js/src/asmjs/WasmCompile.h
--- a/js/src/asmjs/Wasm.h
+++ b/js/src/asmjs/WasmCompile.h
@@ -11,48 +11,27 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#ifndef wasm_h
-#define wasm_h
+#ifndef wasm_compile_h
+#define wasm_compile_h
 
-#include "NamespaceImports.h"
-
-#include "gc/Rooting.h"
+#include "asmjs/WasmBinary.h"
+#include "asmjs/WasmJS.h"
 
 namespace js {
-
-class TypedArrayObject;
-class WasmInstanceObject;
-
 namespace wasm {
 
-// Return whether WebAssembly can be compiled on this platform.
-bool
-HasCompilerSupport(ExclusiveContext* cx);
-
-// The WebAssembly spec hard-codes the virtual page size to be 64KiB and
-// requires linear memory to always be a multiple of 64KiB.
-static const unsigned PageSize = 64 * 1024;
+// Compile the given WebAssembly bytecode with the given filename into a
+// wasm::Module.
 
-// When signal handling is used for bounds checking, MappedSize bytes are
-// reserved and the subrange [0, memory_size) is given readwrite permission.
-// See also static asserts in MIRGenerator::foldableOffsetRange.
-#ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
-static const uint64_t Uint32Range = uint64_t(UINT32_MAX) + 1;
-static const uint64_t MappedSize = 2 * Uint32Range + PageSize;
-#endif
-
-// Compiles the given binary wasm module given the ArrayBufferObject
-// and links the module's imports with the given import object.
-MOZ_MUST_USE bool
-Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject imports,
-     MutableHandle<WasmInstanceObject*> instance);
+UniqueModule
+Compile(JSContext* cx, UniqueChars filename, Bytes&& code);
 
 }  // namespace wasm
 }  // namespace js
 
-#endif // namespace wasm_h
+#endif // namespace wasm_compile_h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -593,29 +593,40 @@ ModuleGenerator::allocateGlobal(ValType 
     if (!allocateGlobalBytes(width, width, &offset))
         return false;
 
     *index = shared_->globals.length();
     return shared_->globals.append(GlobalDesc(type, offset, isConst));
 }
 
 void
-ModuleGenerator::initHeapUsage(HeapUsage heapUsage, uint32_t minHeapLength)
+ModuleGenerator::initHeapUsage(HeapUsage heapUsage, uint32_t initialHeapLength)
 {
     MOZ_ASSERT(metadata_->heapUsage == HeapUsage::None);
     metadata_->heapUsage = heapUsage;
-    shared_->minHeapLength = minHeapLength;
+    metadata_->initialHeapLength = initialHeapLength;
+    if (isAsmJS())
+        MOZ_ASSERT(initialHeapLength == 0);
+    else
+        shared_->minHeapLength = initialHeapLength;
 }
 
 bool
 ModuleGenerator::usesHeap() const
 {
     return UsesHeap(metadata_->heapUsage);
 }
 
+uint32_t
+ModuleGenerator::initialHeapLength() const
+{
+    MOZ_ASSERT(!isAsmJS());
+    return metadata_->initialHeapLength;
+}
+
 void
 ModuleGenerator::initSig(uint32_t sigIndex, Sig&& sig)
 {
     MOZ_ASSERT(isAsmJS());
     MOZ_ASSERT(sigIndex == numSigs_);
     numSigs_++;
 
     MOZ_ASSERT(shared_->sigs[sigIndex] == Sig());
@@ -945,11 +956,16 @@ ModuleGenerator::finish(ImportNameVector
         MOZ_ASSERT(codeRange.begin() >= lastEnd);
         lastEnd = codeRange.end();
     }
 #endif
 
     if (!finishLinkData(code))
         return nullptr;
 
-    return cx_->make_unique<Module>(Move(code), Move(linkData_), Move(importNames),
-                                    Move(exportMap_), *metadata_, bytecode);
+    return cx_->make_unique<Module>(Move(code),
+                                    Move(linkData_),
+                                    Move(importNames),
+                                    Move(exportMap_),
+                                    Move(dataSegments_),
+                                    *metadata_,
+                                    bytecode);
 }
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -100,16 +100,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     ExclusiveContext*               cx_;
     jit::JitContext                 jcx_;
 
     // Data that is moved into the result of finish()
     LinkData                        linkData_;
     MutableMetadata                 metadata_;
     ExportMap                       exportMap_;
+    DataSegmentVector               dataSegments_;
 
     // Data scoped to the ModuleGenerator's lifetime
     UniqueModuleGeneratorData       shared_;
     uint32_t                        numSigs_;
     LifoAlloc                       lifo_;
     jit::TempAllocator              alloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcIndexToCodeRange_;
@@ -147,18 +148,20 @@ class MOZ_STACK_CLASS ModuleGenerator
                            UniqueChars filename,
                            Metadata* maybeMetadata = nullptr);
 
     bool isAsmJS() const { return metadata_->kind == ModuleKind::AsmJS; }
     CompileArgs args() const { return metadata_->compileArgs; }
     jit::MacroAssembler& masm() { return masm_; }
 
     // Heap usage:
-    void initHeapUsage(HeapUsage heapUsage, uint32_t minHeapLength = 0);
+    void initHeapUsage(HeapUsage heapUsage, uint32_t initialHeapLength = 0);
     bool usesHeap() const;
+    uint32_t initialHeapLength() const;
+    MOZ_MUST_USE bool addDataSegment(DataSegment s) { return dataSegments_.append(s); }
 
     // Signatures:
     uint32_t numSigs() const { return numSigs_; }
     const DeclaredSig& sig(uint32_t sigIndex) const;
 
     // Function declarations:
     uint32_t numFuncSigs() const { return shared_->funcSigs.length(); }
     const DeclaredSig& funcSig(uint32_t funcIndex) const;
@@ -178,17 +181,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool addMemoryExport(UniqueChars fieldName);
 
     // Function definitions:
     MOZ_MUST_USE bool startFuncDefs();
     MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDefs();
 
-    // Pretty function names:
+    // Function names:
     void setFuncNames(NameInBytecodeVector&& funcNames);
 
     // asm.js lazy initialization:
     void initSig(uint32_t sigIndex, Sig&& sig);
     void initFuncSig(uint32_t funcIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initImport(uint32_t importIndex, uint32_t sigIndex);
     MOZ_MUST_USE bool initSigTableLength(uint32_t sigIndex, uint32_t numElems);
     void initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -13,25 +13,122 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/WasmJS.h"
 
-#include "asmjs/Wasm.h"
+#include "asmjs/WasmCompile.h"
 #include "asmjs/WasmInstance.h"
 #include "asmjs/WasmModule.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::wasm;
 
+bool
+wasm::HasCompilerSupport(ExclusiveContext* cx)
+{
+    if (!cx->jitSupportsFloatingPoint())
+        return false;
+
+#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
+    return false;
+#else
+    return true;
+#endif
+}
+
+static bool
+Throw(JSContext* cx, const char* str)
+{
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL, str);
+    return false;
+}
+
+static bool
+GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v)
+{
+    JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
+    if (!atom)
+        return false;
+
+    RootedId id(cx, AtomToId(atom));
+    return GetProperty(cx, obj, obj, id, v);
+}
+
+static bool
+ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& importNames,
+                MutableHandle<FunctionVector> imports)
+{
+    if (!importNames.empty() && !importObj)
+        return Throw(cx, "no import object given");
+
+    for (const ImportName& name : importNames) {
+        RootedValue v(cx);
+        if (!GetProperty(cx, importObj, name.module.get(), &v))
+            return false;
+
+        if (strlen(name.func.get()) > 0) {
+            if (!v.isObject())
+                return Throw(cx, "import object field is not an Object");
+
+            RootedObject obj(cx, &v.toObject());
+            if (!GetProperty(cx, obj, name.func.get(), &v))
+                return false;
+        }
+
+        if (!IsFunctionObject(v))
+            return Throw(cx, "import object field is not a Function");
+
+        if (!imports.append(&v.toObject().as<JSFunction>()))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj,
+           MutableHandleWasmInstanceObject instanceObj)
+{
+    if (!HasCompilerSupport(cx)) {
+#ifdef JS_MORE_DETERMINISTIC
+        fprintf(stderr, "WebAssembly is not supported on the current device.\n");
+#endif
+        JS_ReportError(cx, "WebAssembly is not supported on the current device.");
+        return false;
+    }
+
+    Bytes bytecode;
+    if (!bytecode.append((uint8_t*)code->viewDataEither().unwrap(), code->byteLength()))
+        return false;
+
+    JS::AutoFilename filename;
+    if (!DescribeScriptedCaller(cx, &filename))
+        return false;
+
+    UniqueChars file = DuplicateString(filename.get());
+    if (!file)
+        return false;
+
+    UniqueModule module = Compile(cx, Move(file), Move(bytecode));
+    if (!module)
+        return false;
+
+    Rooted<FunctionVector> funcImports(cx, FunctionVector(cx));
+    if (!ImportFunctions(cx, importObj, module->importNames(), &funcImports))
+        return false;
+
+    return module->instantiate(cx, funcImports, nullptr, instanceObj);
+}
+
 static bool
 InstantiateModule(JSContext* cx, unsigned argc, Value* vp)
 {
     MOZ_ASSERT(cx->runtime()->options().wasm());
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!args.get(0).isObject() || !args.get(0).toObject().is<TypedArrayObject>()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -18,24 +18,42 @@
 
 #ifndef wasm_js_h
 #define wasm_js_h
 
 #include "js/UniquePtr.h"
 #include "vm/NativeObject.h"
 
 namespace js {
+
+class TypedArrayObject;
+class WasmInstanceObject;
+
 namespace wasm {
 
+// This is a widespread header, so keep out core wasm impl definitions.
+
 class Module;
 class Instance;
 
 typedef UniquePtr<Module> UniqueModule;
 typedef UniquePtr<Instance> UniqueInstance;
 
+// Return whether WebAssembly can be compiled on this platform.
+
+bool
+HasCompilerSupport(ExclusiveContext* cx);
+
+// Compiles the given binary wasm module given the ArrayBufferObject
+// and links the module's imports with the given import object.
+
+MOZ_MUST_USE bool
+Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj,
+     MutableHandle<WasmInstanceObject*> instanceObj);
+
 } // namespace wasm
 
 // The class of the Wasm global namespace object.
 
 extern const Class WasmClass;
 
 JSObject*
 InitWasmClass(JSContext* cx, HandleObject global);
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -222,27 +222,29 @@ ExportMap::sizeOfExcludingThis(MallocSiz
 
 size_t
 Module::serializedSize() const
 {
     return SerializedPodVectorSize(code_) +
            linkData_.serializedSize() +
            SerializedVectorSize(importNames_) +
            exportMap_.serializedSize() +
+           SerializedPodVectorSize(dataSegments_) +
            metadata_->serializedSize() +
            SerializedPodVectorSize(bytecode_->bytes);
 }
 
 uint8_t*
 Module::serialize(uint8_t* cursor) const
 {
     cursor = SerializePodVector(cursor, code_);
     cursor = linkData_.serialize(cursor);
     cursor = SerializeVector(cursor, importNames_);
     cursor = exportMap_.serialize(cursor);
+    cursor = SerializePodVector(cursor, dataSegments_);
     cursor = metadata_->serialize(cursor);
     cursor = SerializePodVector(cursor, bytecode_->bytes);
     return cursor;
 }
 
 /* static */ const uint8_t*
 Module::deserialize(ExclusiveContext* cx, const uint8_t* cursor, UniquePtr<Module>* module,
                     Metadata* maybeMetadata)
@@ -262,16 +264,21 @@ Module::deserialize(ExclusiveContext* cx
     if (!cursor)
         return nullptr;
 
     ExportMap exportMap;
     cursor = exportMap.deserialize(cx, cursor);
     if (!cursor)
         return nullptr;
 
+    DataSegmentVector dataSegments;
+    cursor = DeserializePodVector(cx, cursor, &dataSegments);
+    if (!cursor)
+        return nullptr;
+
     MutableMetadata metadata;
     if (maybeMetadata) {
         metadata = maybeMetadata;
     } else {
         metadata = cx->new_<Metadata>();
         if (!metadata)
             return nullptr;
     }
@@ -282,18 +289,23 @@ Module::deserialize(ExclusiveContext* cx
 
     MutableBytes bytecode = cx->new_<ShareableBytes>();
     if (!bytecode)
         return nullptr;
     cursor = DeserializePodVector(cx, cursor, &bytecode->bytes);
     if (!cursor)
         return nullptr;
 
-    *module = cx->make_unique<Module>(Move(code), Move(linkData), Move(importNames),
-                                      Move(exportMap), *metadata, *bytecode);
+    *module = cx->make_unique<Module>(Move(code),
+                                      Move(linkData),
+                                      Move(importNames),
+                                      Move(exportMap),
+                                      Move(dataSegments),
+                                      *metadata,
+                                      *bytecode);
     if (!*module)
         return nullptr;
 
     return cursor;
 }
 
 /* virtual */ void
 Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
@@ -302,36 +314,52 @@ Module::addSizeOfMisc(MallocSizeOf mallo
                       size_t* code,
                       size_t* data) const
 {
     *data += mallocSizeOf(this) +
              code_.sizeOfExcludingThis(mallocSizeOf) +
              linkData_.sizeOfExcludingThis(mallocSizeOf) +
              importNames_.sizeOfExcludingThis(mallocSizeOf) +
              exportMap_.sizeOfExcludingThis(mallocSizeOf) +
+             dataSegments_.sizeOfExcludingThis(mallocSizeOf) +
              metadata_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) +
              bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
 }
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
-                    HandleArrayBufferObjectMaybeShared heap,
+                    Handle<ArrayBufferObjectMaybeShared*> asmJSHeap,
                     MutableHandleWasmInstanceObject instanceObj) const
 {
     MOZ_ASSERT(funcImports.length() == metadata_->imports.length());
+    MOZ_ASSERT_IF(asmJSHeap, metadata_->isAsmJS());
 
-    uint8_t* heapBase = nullptr;
-    uint32_t heapLength = 0;
-    if (metadata_->usesHeap()) {
-        heapBase = heap->dataPointerEither().unwrap(/* for patching thead-safe code */);
-        heapLength = heap->byteLength();
+    // asm.js module instantiation supplies its own heap, but for wasm, create
+    // and initialize the heap if one is requested.
+
+    Rooted<ArrayBufferObjectMaybeShared*> heap(cx, asmJSHeap);
+    if (metadata_->usesHeap() && !heap) {
+        MOZ_ASSERT(!metadata_->isAsmJS());
+        bool signalsForOOB = metadata_->compileArgs.useSignalHandlersForOOB;
+        heap = ArrayBufferObject::createForWasm(cx, metadata_->initialHeapLength, signalsForOOB);
+        if (!heap)
+            return false;
     }
 
-    auto cs = CodeSegment::create(cx, code_, linkData_, *metadata_, heapBase, heapLength);
+    uint8_t* memoryBase = heap ? heap->dataPointerEither().unwrap(/* code patching */) : nullptr;
+    uint32_t memoryLength = heap ? heap->byteLength() : 0;
+
+    const uint8_t* bytecode = bytecode_->begin();
+    for (const DataSegment& seg : dataSegments_)
+        memcpy(memoryBase + seg.memoryOffset, bytecode + seg.bytecodeOffset, seg.length);
+
+    // Create a new, specialized CodeSegment for the new Instance (for now).
+
+    auto cs = CodeSegment::create(cx, code_, linkData_, *metadata_, memoryBase, memoryLength);
     if (!cs)
         return false;
 
     // To support viewing the source of an instance (Instance::createText), the
     // instance must hold onto a ref of the bytecode (keeping it alive). This
     // wastes memory for most users, so we try to only save the source when a
     // developer actually cares: when the compartment is debuggable (which is
     // true when the web console is open) or a names section is implied (since
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -130,60 +130,75 @@ static const uint32_t MemoryExport = UIN
 struct ExportMap
 {
     CacheableCharsVector fieldNames;
     Uint32Vector fieldsToExports;
 
     WASM_DECLARE_SERIALIZABLE(ExportMap)
 };
 
+// DataSegment describes the offset of a data segment in the bytecode that is
+// to be copied at a given offset into linear memory upon instantiation.
+
+struct DataSegment
+{
+    uint32_t memoryOffset;
+    uint32_t bytecodeOffset;
+    uint32_t length;
+};
+
+typedef Vector<DataSegment, 0, SystemAllocPolicy> DataSegmentVector;
+
 // Module represents a compiled wasm module and primarily provides two
 // operations: instantiation and serialization. A Module can be instantiated any
 // number of times to produce new Instance objects. A Module can be serialized
 // any number of times such that the serialized bytes can be deserialized later
 // to produce a new, equivalent Module.
 //
 // Since fully linked-and-instantiated code (represented by CodeSegment) cannot
 // be shared between instances, Module stores an unlinked, uninstantiated copy
 // of the code (represented by the Bytes) and creates a new CodeSegment each
 // time it is instantiated. In the future, Module will store a shareable,
 // immutable CodeSegment that can be shared by all its instances.
 
 class Module
 {
-    const Bytes            code_;
-    const LinkData         linkData_;
-    const ImportNameVector importNames_;
-    const ExportMap        exportMap_;
-    const SharedMetadata   metadata_;
-    const SharedBytes      bytecode_;
+    const Bytes             code_;
+    const LinkData          linkData_;
+    const ImportNameVector  importNames_;
+    const ExportMap         exportMap_;
+    const DataSegmentVector dataSegments_;
+    const SharedMetadata    metadata_;
+    const SharedBytes       bytecode_;
 
   public:
     Module(Bytes&& code,
            LinkData&& linkData,
            ImportNameVector&& importNames,
            ExportMap&& exportMap,
+           DataSegmentVector&& dataSegments,
            const Metadata& metadata,
            const ShareableBytes& bytecode)
       : code_(Move(code)),
         linkData_(Move(linkData)),
         importNames_(Move(importNames)),
         exportMap_(Move(exportMap)),
+        dataSegments_(Move(dataSegments)),
         metadata_(&metadata),
         bytecode_(&bytecode)
     {}
 
     const Metadata& metadata() const { return *metadata_; }
     const ImportNameVector& importNames() const { return importNames_; }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
-                     Handle<ArrayBufferObjectMaybeShared*> heap,
+                     Handle<ArrayBufferObjectMaybeShared*> asmJSHeap,
                      MutableHandle<WasmInstanceObject*> instanceObj) const;
 
     // Structured clone support:
 
     size_t serializedSize() const;
     uint8_t* serialize(uint8_t* cursor) const;
     static const uint8_t* deserialize(ExclusiveContext* cx, const uint8_t* cursor,
                                       UniquePtr<Module>* module,
--- a/js/src/asmjs/WasmSignalHandlers.cpp
+++ b/js/src/asmjs/WasmSignalHandlers.cpp
@@ -16,17 +16,16 @@
  * limitations under the License.
  */
 
 #include "asmjs/WasmSignalHandlers.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/PodOperations.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmInstance.h"
 #include "jit/AtomicOperations.h"
 #include "jit/Disassembler.h"
 #include "vm/Runtime.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -18,17 +18,16 @@
 
 #include "asmjs/WasmTypes.h"
 
 #include "fdlibm.h"
 
 #include "jslibmath.h"
 #include "jsmath.h"
 
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmInstance.h"
 #include "jit/shared/Assembler-shared.h"
 #include "js/Conversions.h"
 #include "vm/Interpreter.h"
 
 #include "vm/Stack-inl.h"
 
 using namespace js;
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -818,28 +818,38 @@ struct ExportArg
     uint64_t lo;
     uint64_t hi;
 };
 
 typedef int32_t (*ExportFuncPtr)(ExportArg* args, uint8_t* global);
 
 // Constants:
 
+// The WebAssembly spec hard-codes the virtual page size to be 64KiB and
+// requires linear memory to always be a multiple of 64KiB.
+static const unsigned PageSize = 64 * 1024;
+
+#ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
+static const uint64_t Uint32Range = uint64_t(UINT32_MAX) + 1;
+static const uint64_t MappedSize = 2 * Uint32Range + PageSize;
+#endif
+
 static const unsigned ActivationGlobalDataOffset = 0;
 static const unsigned HeapGlobalDataOffset       = ActivationGlobalDataOffset + sizeof(void*);
 static const unsigned NaN64GlobalDataOffset      = HeapGlobalDataOffset + sizeof(void*);
 static const unsigned NaN32GlobalDataOffset      = NaN64GlobalDataOffset + sizeof(double);
 static const unsigned InitialGlobalDataBytes     = NaN32GlobalDataOffset + sizeof(float);
 
 static const unsigned MaxSigs                    =        4 * 1024;
 static const unsigned MaxFuncs                   =      512 * 1024;
 static const unsigned MaxLocals                  =       64 * 1024;
 static const unsigned MaxImports                 =       64 * 1024;
 static const unsigned MaxExports                 =       64 * 1024;
 static const unsigned MaxTables                  =        4 * 1024;
 static const unsigned MaxTableElems              =      128 * 1024;
+static const unsigned MaxDataSegments            =       64 * 1024;
 static const unsigned MaxArgsPerFunc             =        4 * 1024;
 static const unsigned MaxBrTableElems            = 4 * 1024 * 1024;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_types_h
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -285,38 +285,38 @@ js::IsRegExp(JSContext* cx, HandleValue 
 
     /* Step 4. */
     if (!isRegExp.isUndefined()) {
         *result = ToBoolean(isRegExp);
         return true;
     }
 
     /* Steps 5-6. */
-    ESClassValue cls;
+    ESClass cls;
     if (!GetClassOfValue(cx, value, &cls))
         return false;
 
-    *result = cls == ESClass_RegExp;
+    *result = cls == ESClass::RegExp;
     return true;
 }
 
 /* ES6 B.2.5.1. */
 MOZ_ALWAYS_INLINE bool
 regexp_compile_impl(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
 
     // Step 3.
     RootedValue patternValue(cx, args.get(0));
-    ESClassValue cls;
+    ESClass cls;
     if (!GetClassOfValue(cx, patternValue, &cls))
         return false;
-    if (cls == ESClass_RegExp) {
+    if (cls == ESClass::RegExp) {
         // Step 3a.
         if (args.hasDefined(1)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NEWREGEXP_FLAGGED);
             return false;
         }
 
         // Beware!  |patternObj| might be a proxy into another compartment, so
         // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
@@ -400,20 +400,20 @@ js::regexp_construct(JSContext* cx, unsi
                 return true;
             }
         }
     }
 
     RootedValue patternValue(cx, args.get(0));
 
     // Step 4.
-    ESClassValue cls;
+    ESClass cls;
     if (!GetClassOfValue(cx, patternValue, &cls))
         return false;
-    if (cls == ESClass_RegExp) {
+    if (cls == ESClass::RegExp) {
         // Beware!  |patternObj| might be a proxy into another compartment, so
         // don't assume |patternObj.is<RegExpObject>()|.  For the same reason,
         // don't reuse the RegExpShared below.
         RootedObject patternObj(cx, &patternValue.toObject());
 
         RootedAtom sourceAtom(cx);
         RegExpFlag flags;
         {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -15,19 +15,19 @@
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jswrapper.h"
 
 #include "asmjs/AsmJS.h"
-#include "asmjs/Wasm.h"
 #include "asmjs/WasmBinaryToExperimentalText.h"
 #include "asmjs/WasmBinaryToText.h"
+#include "asmjs/WasmJS.h"
 #include "asmjs/WasmTextToBinary.h"
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitFrameIterator.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "js/StructuredClone.h"
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -3644,21 +3644,21 @@ ImplicitConvert(JSContext* cx,
 
         break;
       }
       default:
         return ConvError(cx, targetType, val, convType, funObj, argIndex,
                          arrObj, arrIndex);
       }
     } else {
-      ESClassValue cls;
+      ESClass cls;
       if (!GetClassOfValue(cx, val, &cls))
         return false;
 
-      if (cls == ESClass_Array) {
+      if (cls == ESClass::Array) {
         // Convert each element of the array by calling ImplicitConvert.
         uint32_t sourceLength;
         if (!JS_GetArrayLength(cx, valObj, &sourceLength) ||
             targetLength != size_t(sourceLength)) {
           MOZ_ASSERT(!funObj);
           return ArrayLengthMismatch(cx, targetLength, targetType,
                                      size_t(sourceLength), val, convType);
         }
@@ -3679,20 +3679,20 @@ ImplicitConvert(JSContext* cx,
 
           char* data = intermediate.get() + elementSize * i;
           if (!ImplicitConvert(cx, item, baseType, data, convType, nullptr,
                                funObj, argIndex, targetType, i))
             return false;
         }
 
         memcpy(buffer, intermediate.get(), arraySize);
-      } else if (cls == ESClass_ArrayBuffer || cls == ESClass_SharedArrayBuffer) {
+      } else if (cls == ESClass::ArrayBuffer || cls == ESClass::SharedArrayBuffer) {
         // Check that array is consistent with type, then
         // copy the array.
-        const bool bufferShared = cls == ESClass_SharedArrayBuffer;
+        const bool bufferShared = cls == ESClass::SharedArrayBuffer;
         uint32_t sourceLength = bufferShared ? JS_GetSharedArrayBufferByteLength(valObj)
             : JS_GetArrayBufferByteLength(valObj);
         size_t elementSize = CType::GetSize(baseType);
         size_t arraySize = elementSize * targetLength;
         if (arraySize != size_t(sourceLength)) {
           MOZ_ASSERT(!funObj);
           return ArrayLengthMismatch(cx, arraySize, targetType,
                                      size_t(sourceLength), val, convType);
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/MIRGraph.h"
 
-#include "asmjs/Wasm.h"
+#include "asmjs/WasmTypes.h"
 #include "jit/BytecodeAnalysis.h"
 #include "jit/Ion.h"
 #include "jit/JitSpewer.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 
 using namespace js;
 using namespace js::jit;
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -881,16 +881,23 @@ class Assembler : public AssemblerX86Sha
     }
     void j(Condition cond, JitCode* target) {
         j(cond, ImmPtr(target->raw()), Relocation::JITCODE);
     }
     void call(JitCode* target) {
         JmpSrc src = masm.call();
         addPendingJump(src, ImmPtr(target->raw()), Relocation::JITCODE);
     }
+    void call(ImmWord target) {
+        call(ImmPtr((void*)target.value));
+    }
+    void call(ImmPtr target) {
+        JmpSrc src = masm.call();
+        addPendingJump(src, target, Relocation::HARDCODED);
+    }
 
     // Emit a CALL or CMP (nop) instruction. ToggleCall can be used to patch
     // this instruction.
     CodeOffset toggledCall(JitCode* target, bool enabled) {
         CodeOffset offset(size());
         JmpSrc src = enabled ? masm.call() : masm.cmp_eax();
         addPendingJump(src, ImmPtr(target->raw()), Relocation::JITCODE);
         MOZ_ASSERT_IF(!oom(), size() - offset.offset() == ToggledCallSize(nullptr));
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -672,24 +672,23 @@ MacroAssembler::call(wasm::SymbolicAddre
 {
     mov(target, eax);
     Assembler::call(eax);
 }
 
 void
 MacroAssembler::call(ImmWord target)
 {
-    mov(target, eax);
-    Assembler::call(eax);
+    Assembler::call(target);
 }
 
 void
 MacroAssembler::call(ImmPtr target)
 {
-    call(ImmWord(uintptr_t(target.value)));
+    Assembler::call(target);
 }
 
 void
 MacroAssembler::call(JitCode* target)
 {
     Assembler::call(target);
 }
 
--- a/js/src/jsapi-tests/testIteratorObject.cpp
+++ b/js/src/jsapi-tests/testIteratorObject.cpp
@@ -8,24 +8,24 @@ BEGIN_TEST(testIteratorObject)
 {
     using namespace js;
     JS::RootedValue result(cx);
 
     EVAL("new Map([['key1', 'value1'], ['key2', 'value2']]).entries()", &result);
 
     CHECK(result.isObject());
     JS::RootedObject obj1(cx, &result.toObject());
-    ESClassValue class1 = ESClass_Other;
+    ESClass class1 = ESClass::Other;
     CHECK(GetBuiltinClass(cx, obj1, &class1));
-    CHECK(class1 == ESClass_MapIterator);
+    CHECK(class1 == ESClass::MapIterator);
 
     EVAL("new Set(['value1', 'value2']).entries()", &result);
 
     CHECK(result.isObject());
     JS::RootedObject obj2(cx, &result.toObject());
-    ESClassValue class2 = ESClass_Other;
+    ESClass class2 = ESClass::Other;
     CHECK(GetBuiltinClass(cx, obj2, &class2));
-    CHECK(class2 == ESClass_SetIterator);
+    CHECK(class2 == ESClass::SetIterator);
 
     return true;
 }
 END_TEST(testIteratorObject)
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3217,21 +3217,21 @@ JS_NewArrayObject(JSContext* cx, size_t 
     return NewDenseFullyAllocatedArray(cx, length);
 }
 
 JS_PUBLIC_API(bool)
 JS_IsArrayObject(JSContext* cx, JS::HandleObject obj, bool* isArray)
 {
     assertSameCompartment(cx, obj);
 
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
-    *isArray = cls == ESClass_Array;
+    *isArray = cls == ESClass::Array;
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS_IsArrayObject(JSContext* cx, JS::HandleValue value, bool* isArray)
 {
     if (!value.isObject()) {
         *isArray = false;
@@ -5665,21 +5665,21 @@ JS::NewDateObject(JSContext* cx, JS::Cli
     return NewDateObjectMsec(cx, time);
 }
 
 JS_PUBLIC_API(bool)
 JS_ObjectIsDate(JSContext* cx, HandleObject obj, bool* isDate)
 {
     assertSameCompartment(cx, obj);
 
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
-    *isDate = cls == ESClass_Date;
+    *isDate = cls == ESClass::Date;
     return true;
 }
 
 /************************************************************************/
 
 /*
  * Regular Expressions.
  */
@@ -5769,21 +5769,21 @@ JS_ExecuteRegExpNoStatics(JSContext* cx,
                                rval);
 }
 
 JS_PUBLIC_API(bool)
 JS_ObjectIsRegExp(JSContext* cx, HandleObject obj, bool* isRegExp)
 {
     assertSameCompartment(cx, obj);
 
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
-    *isRegExp = cls == ESClass_RegExp;
+    *isRegExp = cls == ESClass::RegExp;
     return true;
 }
 
 JS_PUBLIC_API(unsigned)
 JS_GetRegExpFlags(JSContext* cx, HandleObject obj)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -2922,22 +2922,22 @@ IsObject(HandleValue v)
 // ES6 20.3.4.41.
 MOZ_ALWAYS_INLINE bool
 date_toString_impl(JSContext* cx, const CallArgs& args)
 {
     // Step 1.
     RootedObject obj(cx, &args.thisv().toObject());
 
     // Step 2.
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
     double tv;
-    if (cls != ESClass_Date) {
+    if (cls != ESClass::Date) {
         // Step 2.
         tv = GenericNaN();
     } else {
         // Step 3.
         RootedValue unboxed(cx);
         if (!Unbox(cx, obj, &unboxed))
             return false;
 
@@ -3103,21 +3103,21 @@ static bool
 DateOneArgument(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 1);
 
     if (args.isConstructing()) {
         if (args[0].isObject()) {
             RootedObject obj(cx, &args[0].toObject());
 
-            ESClassValue cls;
+            ESClass cls;
             if (!GetBuiltinClass(cx, obj, &cls))
                 return false;
 
-            if (cls == ESClass_Date) {
+            if (cls == ESClass::Date) {
                 RootedValue unboxed(cx);
                 if (!Unbox(cx, obj, &unboxed))
                     return false;
 
                 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
             }
         }
 
@@ -3314,41 +3314,41 @@ js::NewDateObject(JSContext* cx, int yea
     MOZ_ASSERT(mon < 12);
     double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
     return NewDateObjectMsec(cx, TimeClip(UTC(msec_time)));
 }
 
 JS_FRIEND_API(bool)
 js::DateIsValid(JSContext* cx, HandleObject obj, bool* isValid)
 {
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
-    if (cls != ESClass_Date) {
+    if (cls != ESClass::Date) {
         *isValid = false;
         return true;
     }
 
     RootedValue unboxed(cx);
     if (!Unbox(cx, obj, &unboxed))
         return false;
 
     *isValid = !IsNaN(unboxed.toNumber());
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj, double* msecsSinceEpoch)
 {
-    ESClassValue cls;
+    ESClass cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
-    if (cls != ESClass_Date) {
+    if (cls != ESClass::Date) {
         *msecsSinceEpoch = 0;
         return true;
     }
 
     RootedValue unboxed(cx);
     if (!Unbox(cx, obj, &unboxed))
         return false;
 
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -1017,23 +1017,23 @@ js::ValueToSourceForError(JSContext* cx,
 
     RootedString str(cx, JS_ValueToSource(cx, val));
     if (!str)
         return "<<error converting value to string>>";
 
     StringBuffer sb(cx);
     if (val.isObject()) {
         RootedObject valObj(cx, val.toObjectOrNull());
-        ESClassValue cls;
+        ESClass cls;
         if (!GetBuiltinClass(cx, valObj, &cls))
             return "<<error determining class of value>>";
         const char* s;
-        if (cls == ESClass_Array)
+        if (cls == ESClass::Array)
             s = "the array ";
-        else if (cls == ESClass_ArrayBuffer)
+        else if (cls == ESClass::ArrayBuffer)
             s = "the array buffer ";
         else if (JS_IsArrayBufferViewObject(valObj))
             s = "the typed array ";
         else
             s = "the object ";
         if (!sb.append(s, strlen(s)))
             return "<<error converting value to string>>";
     } else if (val.isNumber()) {
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -261,51 +261,55 @@ JS_DefineFunctionsWithHelp(JSContext* cx
                 return false;
         }
     }
 
     return true;
 }
 
 JS_FRIEND_API(bool)
-js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClassValue* classValue)
+js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
-        return Proxy::getBuiltinClass(cx, obj, classValue);
+        return Proxy::getBuiltinClass(cx, obj, cls);
 
     if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>())
-        *classValue = ESClass_Object;
+        *cls = ESClass::Object;
     else if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
-        *classValue = ESClass_Array;
+        *cls = ESClass::Array;
     else if (obj->is<NumberObject>())
-        *classValue = ESClass_Number;
+        *cls = ESClass::Number;
     else if (obj->is<StringObject>())
-        *classValue = ESClass_String;
+        *cls = ESClass::String;
     else if (obj->is<BooleanObject>())
-        *classValue = ESClass_Boolean;
+        *cls = ESClass::Boolean;
     else if (obj->is<RegExpObject>())
-        *classValue = ESClass_RegExp;
+        *cls = ESClass::RegExp;
     else if (obj->is<ArrayBufferObject>())
-        *classValue = ESClass_ArrayBuffer;
+        *cls = ESClass::ArrayBuffer;
     else if (obj->is<SharedArrayBufferObject>())
-        *classValue = ESClass_SharedArrayBuffer;
+        *cls = ESClass::SharedArrayBuffer;
     else if (obj->is<DateObject>())
-        *classValue = ESClass_Date;
+        *cls = ESClass::Date;
     else if (obj->is<SetObject>())
-        *classValue = ESClass_Set;
+        *cls = ESClass::Set;
     else if (obj->is<MapObject>())
-        *classValue = ESClass_Map;
+        *cls = ESClass::Map;
     else if (obj->is<PromiseObject>())
-        *classValue = ESClass_Promise;
+        *cls = ESClass::Promise;
     else if (obj->is<MapIteratorObject>())
-        *classValue = ESClass_MapIterator;
+        *cls = ESClass::MapIterator;
     else if (obj->is<SetIteratorObject>())
-        *classValue = ESClass_SetIterator;
+        *cls = ESClass::SetIterator;
+    else if (obj->is<ArgumentsObject>())
+        *cls = ESClass::Arguments;
+    else if (obj->is<ErrorObject>())
+        *cls = ESClass::Error;
     else
-        *classValue = ESClass_Other;
+        *cls = ESClass::Other;
 
     return true;
 }
 
 JS_FRIEND_API(const char*)
 js::ObjectClassName(JSContext* cx, HandleObject obj)
 {
     return GetObjectClassName(cx, obj);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -184,17 +184,17 @@ JS_InitializePropertiesFromCompatibleNat
                                                   JS::HandleObject src);
 
 extern JS_FRIEND_API(JSString*)
 JS_BasicObjectToString(JSContext* cx, JS::HandleObject obj);
 
 namespace js {
 
 JS_FRIEND_API(bool)
-GetBuiltinClass(JSContext* cx, JS::HandleObject obj, ESClassValue* classValue);
+GetBuiltinClass(JSContext* cx, JS::HandleObject obj, ESClass* cls);
 
 JS_FRIEND_API(const char*)
 ObjectClassName(JSContext* cx, JS::HandleObject obj);
 
 JS_FRIEND_API(void)
 ReportOverRecursed(JSContext* maybecx);
 
 JS_FRIEND_API(bool)
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -805,29 +805,29 @@ GuessObjectGCKind(size_t numElements)
 static inline gc::AllocKind
 GuessArrayGCKind(size_t numElements)
 {
     if (numElements)
         return gc::GetGCArrayKind(numElements);
     return gc::AllocKind::OBJECT8;
 }
 
-// Returns ESClass_Other if the value isn't an object, or if the object
+// Returns ESClass::Other if the value isn't an object, or if the object
 // isn't of one of the enumerated classes.  Otherwise returns the appropriate
 // class.
 inline bool
-GetClassOfValue(JSContext* cx, HandleValue v, ESClassValue* classValue)
+GetClassOfValue(JSContext* cx, HandleValue v, ESClass* cls)
 {
     if (!v.isObject()) {
-        *classValue = ESClass_Other;
+        *cls = ESClass::Other;
         return true;
     }
 
     RootedObject obj(cx, &v.toObject());
-    return GetBuiltinClass(cx, obj, classValue);
+    return GetBuiltinClass(cx, obj, cls);
 }
 
 extern NativeObject*
 InitClass(JSContext* cx, HandleObject obj, HandleObject parent_proto,
           const Class* clasp, JSNative constructor, unsigned nargs,
           const JSPropertySpec* ps, const JSFunctionSpec* fs,
           const JSPropertySpec* static_ps, const JSFunctionSpec* static_fs,
           NativeObject** ctorp = nullptr,
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -256,31 +256,31 @@ PreprocessValue(JSContext* cx, HandleObj
         if (!js::Call(cx, replacerVal, holder, arg0, vp, vp))
             return false;
     }
 
     /* Step 4. */
     if (vp.get().isObject()) {
         RootedObject obj(cx, &vp.get().toObject());
 
-        ESClassValue cls;
+        ESClass cls;
         if (!GetBuiltinClass(cx, obj, &cls))
             return false;
 
-        if (cls == ESClass_Number) {
+        if (cls == ESClass::Number) {
             double d;
             if (!ToNumber(cx, vp, &d))
                 return false;
             vp.setNumber(d);
-        } else if (cls == ESClass_String) {
+        } else if (cls == ESClass::String) {
             JSString* str = ToStringSlow<CanGC>(cx, vp);
             if (!str)
                 return false;
             vp.setString(str);
-        } else if (cls == ESClass_Boolean) {
+        } else if (cls == ESClass::Boolean) {
             if (!Unbox(cx, obj, vp))
                 return false;
         }
     }
 
     return true;
 }
 
@@ -652,21 +652,21 @@ js::Stringify(JSContext* cx, MutableHand
                         id = INT_TO_JSID(n);
                     } else {
                         if (!ValueToId<CanGC>(cx, item, &id))
                             return false;
                     }
                 } else {
                     bool shouldAdd = item.isString();
                     if (!shouldAdd) {
-                        ESClassValue cls;
+                        ESClass cls;
                         if (!GetClassOfValue(cx, item, &cls))
                             return false;
 
-                        shouldAdd = cls == ESClass_String || cls == ESClass_Number;
+                        shouldAdd = cls == ESClass::String || cls == ESClass::Number;
                     }
 
                     if (shouldAdd) {
                         /* Step 4b(iii)(5)(f). */
                         if (!ValueToId<CanGC>(cx, item, &id))
                             return false;
                     } else {
                         /* Step 4b(iii)(5)(g). */
@@ -686,26 +686,26 @@ js::Stringify(JSContext* cx, MutableHand
             replacer = nullptr;
         }
     }
 
     /* Step 5. */
     if (space.isObject()) {
         RootedObject spaceObj(cx, &space.toObject());
 
-        ESClassValue cls;
+        ESClass cls;
         if (!GetBuiltinClass(cx, spaceObj, &cls))
             return false;
 
-        if (cls == ESClass_Number) {
+        if (cls == ESClass::Number) {
             double d;
             if (!ToNumber(cx, space, &d))
                 return false;
             space = NumberValue(d);
-        } else if (cls == ESClass_String) {
+        } else if (cls == ESClass::String) {
             JSString* str = ToStringSlow<CanGC>(cx, space);
             if (!str)
                 return false;
             space = StringValue(str);
         }
     }
 
     StringBuffer gap(cx);
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -106,18 +106,17 @@ class JS_FRIEND_API(Wrapper) : public Ba
     virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
                         bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                               AutoIdVector& props) const override;
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
                                  RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
@@ -260,18 +259,17 @@ class JS_FRIEND_API(OpaqueCrossCompartme
 
     /* SpiderMonkey extensions. */
     virtual bool getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
                                        MutableHandle<PropertyDescriptor> desc) const override;
     virtual bool hasOwn(JSContext* cx, HandleObject wrapper, HandleId id,
                         bool* bp) const override;
     virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                               AutoIdVector& props) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper,
-                                 ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject obj,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject wrapper) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
 
     static const OpaqueCrossCompartmentWrapper singleton;
 };
 
@@ -302,18 +300,17 @@ class JS_FRIEND_API(SecurityWrapper) : p
     virtual bool preventExtensions(JSContext* cx, HandleObject wrapper,
                                    ObjectOpResult& result) const override;
     virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
                               ObjectOpResult& result) const override;
     virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const override;
 
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper,
-                                 ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject wrapper, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject wrapper, JS::IsArrayAnswer* answer) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
 
     // Allow isCallable and isConstructor. They used to be class-level, and so could not be guarded
     // against.
 
     virtual bool watch(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -149,24 +149,24 @@ EXPORTS.js += [
     '../public/Utility.h',
     '../public/Value.h',
     '../public/Vector.h',
     '../public/WeakMapPtr.h',
 ]
 
 UNIFIED_SOURCES += [
     'asmjs/AsmJS.cpp',
-    'asmjs/Wasm.cpp',
     'asmjs/WasmBaselineCompile.cpp',
     'asmjs/WasmBinary.cpp',
     'asmjs/WasmBinaryIterator.cpp',
     'asmjs/WasmBinaryToAST.cpp',
     'asmjs/WasmBinaryToExperimentalText.cpp',
     'asmjs/WasmBinaryToText.cpp',
     'asmjs/WasmCode.cpp',
+    'asmjs/WasmCompile.cpp',
     'asmjs/WasmFrameIterator.cpp',
     'asmjs/WasmGenerator.cpp',
     'asmjs/WasmInstance.cpp',
     'asmjs/WasmIonCompile.cpp',
     'asmjs/WasmJS.cpp',
     'asmjs/WasmModule.cpp',
     'asmjs/WasmSignalHandlers.cpp',
     'asmjs/WasmStubs.cpp',
--- a/js/src/proxy/BaseProxyHandler.cpp
+++ b/js/src/proxy/BaseProxyHandler.cpp
@@ -354,20 +354,19 @@ BaseProxyHandler::hasInstance(JSContext*
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedValue val(cx, ObjectValue(*proxy.get()));
     ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
                      JSDVG_SEARCH_STACK, val, nullptr);
     return false;
 }
 
 bool
-BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                  ESClassValue* classValue) const
+BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
-    *classValue = ESClass_Other;
+    *cls = ESClass::Other;
     return true;
 }
 
 bool
 BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
 {
     *answer = IsArrayAnswer::NotArray;
     return true;
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -110,18 +110,17 @@ bool
 DeadObjectProxy::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const
 {
     ReportDead(cx);
     return false;
 }
 
 bool
-DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const
+DeadObjectProxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
     ReportDead(cx);
     return false;
 }
 
 bool
 DeadObjectProxy::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
 {
--- a/js/src/proxy/DeadObjectProxy.h
+++ b/js/src/proxy/DeadObjectProxy.h
@@ -40,18 +40,17 @@ class DeadObjectProxy : public BaseProxy
 
     /* SpiderMonkey extensions. */
     // BaseProxyHandler::getPropertyDescriptor will throw by calling getOwnPropertyDescriptor.
     // BaseProxyHandler::enumerate will throw by calling ownKeys.
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
 
     static const char family;
     static const DeadObjectProxy singleton;
 };
--- a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
+++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp
@@ -156,19 +156,19 @@ bool
 OpaqueCrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
                                                             AutoIdVector& props) const
 {
     return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props);
 }
 
 bool
 OpaqueCrossCompartmentWrapper::getBuiltinClass(JSContext* cx, HandleObject wrapper,
-                                               ESClassValue* classValue) const
+                                    ESClass* cls) const
 {
-    *classValue = ESClass_Other;
+    *cls = ESClass::Other;
     return true;
 }
 
 bool
 OpaqueCrossCompartmentWrapper::isArray(JSContext* cx, HandleObject obj,
                                        JS::IsArrayAnswer* answer) const
 {
     *answer = JS::IsArrayAnswer::NotArray;
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -439,20 +439,20 @@ Proxy::hasInstance(JSContext* cx, Handle
     *bp = false; // default result if we refuse to perform this action
     AutoEnterPolicy policy(cx, handler, proxy, JSID_VOIDHANDLE, BaseProxyHandler::GET, true);
     if (!policy.allowed())
         return policy.returnValue();
     return proxy->as<ProxyObject>().handler()->hasInstance(cx, proxy, v, bp);
 }
 
 bool
-Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue)
+Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
 {
     JS_CHECK_RECURSION(cx, return false);
-    return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, classValue);
+    return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls);
 }
 
 bool
 Proxy::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer)
 {
     return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer);
 }
 
--- a/js/src/proxy/Proxy.h
+++ b/js/src/proxy/Proxy.h
@@ -53,17 +53,17 @@ class Proxy
     static bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                       MutableHandle<PropertyDescriptor> desc);
     static bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
     static bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
                                              AutoIdVector& props);
     static bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                            const CallArgs& args);
     static bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp);
-    static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue);
+    static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls);
     static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer);
     static const char* className(JSContext* cx, HandleObject proxy);
     static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent);
     static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g);
     static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp);
 
     static bool watch(JSContext* cx, HandleObject proxy, HandleId id, HandleObject callable);
     static bool unwatch(JSContext* cx, HandleObject proxy, HandleId id);
--- a/js/src/proxy/ScriptedProxyHandler.cpp
+++ b/js/src/proxy/ScriptedProxyHandler.cpp
@@ -1235,19 +1235,19 @@ bool
 ScriptedProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                                   bool* bp) const
 {
     return InstanceOfOperator(cx, proxy, v, bp);
 }
 
 bool
 ScriptedProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                      ESClassValue* classValue) const
+                                      ESClass* cls) const
 {
-    *classValue = ESClass_Other;
+    *cls = ESClass::Other;
     return true;
 }
 
 bool
 ScriptedProxyHandler::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     if (target)
--- a/js/src/proxy/ScriptedProxyHandler.h
+++ b/js/src/proxy/ScriptedProxyHandler.h
@@ -58,18 +58,17 @@ class ScriptedProxyHandler : public Base
         return BaseProxyHandler::hasOwn(cx, proxy, id, bp);
     }
 
     // A scripted proxy should not be treated as generic in most contexts.
     virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
                             const CallArgs& args) const override;
     virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
                              bool* bp) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const override;
+    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override;
     virtual bool isArray(JSContext* cx, HandleObject proxy,
                          JS::IsArrayAnswer* answer) const override;
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    unsigned indent) const override;
     virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
                                  RegExpGuard* g) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
--- a/js/src/proxy/SecurityWrapper.cpp
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -72,19 +72,19 @@ SecurityWrapper<Base>::isExtensible(JSCo
     // See above.
     *extensible = true;
     return true;
 }
 
 template <class Base>
 bool
 SecurityWrapper<Base>::getBuiltinClass(JSContext* cx, HandleObject wrapper,
-                                       ESClassValue* classValue) const
+                                       ESClass* cls) const
 {
-    *classValue = ESClass_Other;
+    *cls = ESClass::Other;
     return true;
 }
 
 template <class Base>
 bool
 SecurityWrapper<Base>::isArray(JSContext* cx, HandleObject obj, JS::IsArrayAnswer* answer) const
 {
     // This should ReportUnwrapDenied(cx), but bug 849730 disagrees.  :-(
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -233,20 +233,20 @@ Wrapper::hasInstance(JSContext* cx, Hand
                                 bool* bp) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return HasInstance(cx, target, v, bp);
 }
 
 bool
-Wrapper::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClassValue* classValue) const
+Wrapper::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
-    return GetBuiltinClass(cx, target, classValue);
+    return GetBuiltinClass(cx, target, cls);
 }
 
 bool
 Wrapper::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return IsArray(cx, target, answer);
 }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -55,17 +55,17 @@
 #include "jstypes.h"
 #include "jsutil.h"
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"
 #include "shellmoduleloader.out.h"
 
-#include "asmjs/Wasm.h"
+#include "asmjs/WasmJS.h"
 #include "builtin/ModuleObject.h"
 #include "builtin/TestingFunctions.h"
 #include "frontend/Parser.h"
 #include "gc/GCInternals.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -29,17 +29,17 @@
 #include "jsobj.h"
 #include "jstypes.h"
 #include "jsutil.h"
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 #include "jswrapper.h"