Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 13 Aug 2015 10:43:42 -0400
changeset 257558 0cddd6a6565aab018321b7b68b7485a26b0ca71f
parent 257513 1ab3abe50ac7fec2964af1ff524c6eb0f23586e1 (current diff)
parent 257557 34ab0b8678b25a78620c957c9bdd5de0b4c77728 (diff)
child 257559 9158d446a8a5c0f29d2a67161c54737929cdb8d0
push id29221
push userryanvm@gmail.com
push dateThu, 13 Aug 2015 14:43:44 +0000
treeherdermozilla-central@0cddd6a6565a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.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 inbound to m-c. a=merge
--- a/AUTHORS
+++ b/AUTHORS
@@ -354,16 +354,17 @@ Fernando Jimenez <ferjmoreno@gmail.com>
 Flock Inc.
 Florian Boesch <pyalot@gmail.com>
 Florian Hänel <heeen@gmx.de>
 Florian Queze <florian@queze.net>
 Florian Scholz <elchi3@elchi3.de>
 <flying@dom.natm.ru>
 France Telecom Research and Development
 Franck
+Francois Marier <francois@fmarier.org>
 Frank Tang <ftang@netscape.com>
 Frank Yan <fyan@mozilla.com>
 Franky Braem
 <franky@pacificconnections.com>
 Franz Sirl <Franz.Sirl-kernel@lauterbach.com>
 Frederic Plourde <frederic.plourde@polymtl.ca>
 Frederic Wang <fred.wang@free.fr>
 Fredrik Holmqvist <thesuckiestemail@yahoo.se>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -4802,16 +4802,17 @@ var Utils = {
       case "Invalid HSTS Headers":
       case "Invalid HPKP Headers":
       case "SHA-1 Signature":
       case "Insecure Password Field":
       case "SSL":
       case "CORS":
       case "Iframe Sandbox":
       case "Tracking Protection":
+      case "Sub-resource Integrity":
         return CATEGORY_SECURITY;
 
       default:
         return CATEGORY_JS;
     }
   },
 
   /**
--- a/build/annotationProcessors/AnnotationProcessor.java
+++ b/build/annotationProcessors/AnnotationProcessor.java
@@ -22,16 +22,20 @@ public class AnnotationProcessor {
     public static final String GENERATED_COMMENT =
             "// GENERATED CODE\n" +
             "// Generated by the Java program at /build/annotationProcessors at compile time\n" +
             "// from annotations on Java methods. To update, change the annotations on the\n" +
             "// corresponding Java methods and rerun the build. Manually updating this file\n" +
             "// will cause your build to fail.\n" +
             "\n";
 
+    private static final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
+    private static final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
+    private static final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
+
     public static void main(String[] args) {
         // We expect a list of jars on the commandline. If missing, whinge about it.
         if (args.length <= 1) {
             System.err.println("Usage: java AnnotationProcessor jarfiles ...");
             System.exit(1);
         }
 
         System.out.println("Processing annotations...");
@@ -41,83 +45,47 @@ public class AnnotationProcessor {
         Arrays.sort(args);
 
         // Start the clock!
         long s = System.currentTimeMillis();
 
         // Get an iterator over the classes in the jar files given...
         Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
 
-        StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
         headerFile.append(
                 "#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
                 "#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
                 "\n" +
                 "#include \"mozilla/jni/Refs.h\"\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace widget {\n" +
                 "\n");
 
-        StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
         implementationFile.append(
                 "#include \"GeneratedJNIWrappers.h\"\n" +
                 "#include \"mozilla/jni/Accessors.h\"\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace widget {\n" +
                 "\n");
 
-        StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
         nativesFile.append(
                 "#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
                 "#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
                 "\n" +
                 "#include \"GeneratedJNIWrappers.h\"\n" +
                 "#include \"mozilla/jni/Natives.h\"\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace widget {\n" +
                 "\n");
 
         while (jarClassIterator.hasNext()) {
-            ClassWithOptions aClassTuple = jarClassIterator.next();
-
-            CodeGenerator generatorInstance;
-
-            // Get an iterator over the appropriately generated methods of this class
-            Iterator<AnnotatableEntity> methodIterator = new GeneratableElementIterator(aClassTuple.wrappedClass);
-
-            if (!methodIterator.hasNext()) {
-                continue;
-            }
-            generatorInstance = new CodeGenerator(aClassTuple);
-
-            // Iterate all annotated members in this class..
-            while (methodIterator.hasNext()) {
-                AnnotatableEntity aElementTuple = methodIterator.next();
-                switch (aElementTuple.mEntityType) {
-                    case METHOD:
-                        generatorInstance.generateMethod(aElementTuple);
-                        break;
-                    case NATIVE:
-                        generatorInstance.generateNative(aElementTuple);
-                        break;
-                    case FIELD:
-                        generatorInstance.generateField(aElementTuple);
-                        break;
-                    case CONSTRUCTOR:
-                        generatorInstance.generateConstructor(aElementTuple);
-                        break;
-                }
-            }
-
-            headerFile.append(generatorInstance.getHeaderFileContents());
-            implementationFile.append(generatorInstance.getWrapperFileContents());
-            nativesFile.append(generatorInstance.getNativesFileContents());
+            generateClass(jarClassIterator.next());
         }
 
         implementationFile.append(
                 "} /* widget */\n" +
                 "} /* mozilla */\n");
 
         headerFile.append(
                 "} /* widget */\n" +
@@ -127,20 +95,62 @@ public class AnnotationProcessor {
         nativesFile.append(
                 "} /* widget */\n" +
                 "} /* mozilla */\n" +
                 "#endif // " + getHeaderGuardName(NATIVES_FILE) + "\n");
 
         writeOutputFile(SOURCE_FILE, implementationFile);
         writeOutputFile(HEADER_FILE, headerFile);
         writeOutputFile(NATIVES_FILE, nativesFile);
+
         long e = System.currentTimeMillis();
         System.out.println("Annotation processing complete in " + (e - s) + "ms");
     }
 
+    private static void generateClass(final ClassWithOptions annotatedClass) {
+        // Get an iterator over the appropriately generated methods of this class
+        final GeneratableElementIterator methodIterator
+                = new GeneratableElementIterator(annotatedClass);
+        final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
+
+        if (!methodIterator.hasNext() && innerClasses.length == 0) {
+            return;
+        }
+
+        final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass);
+        generatorInstance.generateClasses(innerClasses);
+
+        // Iterate all annotated members in this class..
+        while (methodIterator.hasNext()) {
+            AnnotatableEntity aElementTuple = methodIterator.next();
+            switch (aElementTuple.mEntityType) {
+                case METHOD:
+                    generatorInstance.generateMethod(aElementTuple);
+                    break;
+                case NATIVE:
+                    generatorInstance.generateNative(aElementTuple);
+                    break;
+                case FIELD:
+                    generatorInstance.generateField(aElementTuple);
+                    break;
+                case CONSTRUCTOR:
+                    generatorInstance.generateConstructor(aElementTuple);
+                    break;
+            }
+        }
+
+        headerFile.append(generatorInstance.getHeaderFileContents());
+        implementationFile.append(generatorInstance.getWrapperFileContents());
+        nativesFile.append(generatorInstance.getNativesFileContents());
+
+        for (ClassWithOptions innerClass : innerClasses) {
+            generateClass(innerClass);
+        }
+    }
+
     private static String getHeaderGuardName(final String name) {
         return name.replaceAll("\\W", "_");
     }
 
     private static void writeOutputFile(final String name,
                                         final StringBuilder content) {
         FileOutputStream outStream = null;
         try {
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -29,57 +29,58 @@ public class CodeGenerator {
     private final String clsName;
 
     private final HashSet<String> takenMethodNames = new HashSet<String>();
 
     public CodeGenerator(ClassWithOptions annotatedClass) {
         this.cls = annotatedClass.wrappedClass;
         this.clsName = annotatedClass.generatedName;
 
+        final String unqualifiedName = Utils.getUnqualifiedName(clsName);
         header.append(
-                "class " + clsName + " : public mozilla::jni::Class<" + clsName + ">\n" +
+                "class " + clsName + " : public mozilla::jni::Class<" + unqualifiedName + ">\n" +
                 "{\n" +
                 "public:\n" +
-                "    typedef mozilla::jni::Ref<" + clsName + "> Ref;\n" +
-                "    typedef mozilla::jni::LocalRef<" + clsName + "> LocalRef;\n" +
-                "    typedef mozilla::jni::GlobalRef<" + clsName + "> GlobalRef;\n" +
-                "    typedef const mozilla::jni::Param<" + clsName + ">& Param;\n" +
+                "    typedef mozilla::jni::Ref<" + unqualifiedName + "> Ref;\n" +
+                "    typedef mozilla::jni::LocalRef<" + unqualifiedName + "> LocalRef;\n" +
+                "    typedef mozilla::jni::GlobalRef<" + unqualifiedName + "> GlobalRef;\n" +
+                "    typedef const mozilla::jni::Param<" + unqualifiedName + ">& Param;\n" +
                 "\n" +
                 "    static constexpr char name[] =\n" +
                 "            \"" + cls.getName().replace('.', '/') + "\";\n" +
                 "\n" +
                 "protected:\n" +
-                "    " + clsName + "(jobject instance) : Class(instance) {}\n" +
+                "    using Class::Class;\n" +
                 "\n");
 
         cpp.append(
                 "constexpr char " + clsName + "::name[];\n" +
                 "\n");
 
         natives.append(
                 "template<class Impl>\n" +
                 "class " + clsName + "::Natives : " +
-                        "public mozilla::jni::NativeImpl<" + clsName + ", Impl>\n" +
+                        "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" +
                 "{\n");
     }
 
     private String getTraitsName(String uniqueName, boolean includeScope) {
         return (includeScope ? clsName + "::" : "") + uniqueName + "_t";
     }
 
     private String getNativeParameterType(Class<?> type, AnnotationInfo info) {
         if (type == cls) {
-            return clsName + "::Param";
+            return Utils.getUnqualifiedName(clsName) + "::Param";
         }
         return Utils.getNativeParameterType(type, info);
     }
 
     private String getNativeReturnType(Class<?> type, AnnotationInfo info) {
         if (type == cls) {
-            return clsName + "::LocalRef";
+            return Utils.getUnqualifiedName(clsName) + "::LocalRef";
         }
         return Utils.getNativeReturnType(type, info);
     }
 
     private void generateMember(AnnotationInfo info, Member member,
                                 String uniqueName, Class<?> type, Class<?>[] argTypes) {
         final StringBuilder args = new StringBuilder();
         for (Class<?> argType : argTypes) {
@@ -87,17 +88,17 @@ public class CodeGenerator {
         }
         if (args.length() > 0) {
             args.setLength(args.length() - 1);
         }
 
         header.append(
                 "public:\n" +
                 "    struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
-                "        typedef " + clsName + " Owner;\n" +
+                "        typedef " + Utils.getUnqualifiedName(clsName) + " Owner;\n" +
                 "        typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
                 "        typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
                 "        typedef mozilla::jni::Args<" + args + "> Args;\n" +
                 "        static constexpr char name[] = \"" +
                         Utils.getMemberName(member) + "\";\n" +
                 "        static constexpr char signature[] =\n" +
                 "                \"" + Utils.getSignature(member) + "\";\n" +
                 "        static const bool isStatic = " + Utils.isStatic(member) + ";\n" +
@@ -131,26 +132,23 @@ public class CodeGenerator {
     }
 
     /**
      * Generate a method prototype that includes return and argument types,
      * without specifiers (static, const, etc.).
      */
     private String generatePrototype(String name, Class<?>[] argTypes,
                                      Class<?> returnType, AnnotationInfo info,
-                                     boolean includeScope, boolean includeArgName) {
+                                     boolean includeScope, boolean includeArgName,
+                                     boolean isConst) {
 
         final StringBuilder proto = new StringBuilder();
         int argIndex = 0;
 
-        if (info.catchException) {
-            proto.append("nsresult ");
-        } else {
-            proto.append(getNativeReturnType(returnType, info)).append(' ');
-        }
+        proto.append("auto ");
 
         if (includeScope) {
             proto.append(clsName).append("::");
         }
 
         proto.append(name).append('(');
 
         for (Class<?> argType : argTypes) {
@@ -168,47 +166,55 @@ public class CodeGenerator {
             }
             proto.append(", ");
         }
 
         if (proto.substring(proto.length() - 2).equals(", ")) {
             proto.setLength(proto.length() - 2);
         }
 
-        return proto.append(')').toString();
+        proto.append(')');
+
+        if (isConst) {
+            proto.append(" const");
+        }
+
+        if (info.catchException) {
+            proto.append(" -> nsresult");
+        } else {
+            proto.append(" -> ").append(getNativeReturnType(returnType, info));
+        }
+        return proto.toString();
     }
 
     /**
      * Generate a method declaration that includes the prototype with specifiers,
      * but without the method body.
      */
     private String generateDeclaration(String name, Class<?>[] argTypes,
                                        Class<?> returnType, AnnotationInfo info,
                                        boolean isStatic) {
 
         return (isStatic ? "static " : "") +
             generatePrototype(name, argTypes, returnType, info,
-                              /* includeScope */ false, /* includeArgName */ false) +
-            (isStatic ? ";" : " const;");
+                              /* includeScope */ false, /* includeArgName */ false,
+                              /* isConst */ !isStatic) + ';';
     }
 
     /**
      * Generate a method definition that includes the prototype with specifiers,
      * and with the method body.
      */
     private String generateDefinition(String accessorName, String name, Class<?>[] argTypes,
                                       Class<?> returnType, AnnotationInfo info, boolean isStatic) {
 
         final StringBuilder def = new StringBuilder(
                 generatePrototype(name, argTypes, returnType, info,
-                                  /* includeScope */ true, /* includeArgName */ true));
-
-        if (!isStatic) {
-            def.append(" const");
-        }
+                                  /* includeScope */ true, /* includeArgName */ true,
+                                  /* isConst */ !isStatic));
         def.append("\n{\n");
 
 
         // Generate code to handle the return value, if needed.
         // We initialize rv to NS_OK instead of NS_ERROR_* because loading NS_OK (0) uses
         // fewer instructions. We are guaranteed to set rv to the correct value later.
 
         if (info.catchException && returnType == void.class) {
@@ -480,16 +486,31 @@ public class CodeGenerator {
                 generateField(entity);
             } else {
                 throw new IllegalArgumentException(
                         "expected member to be Constructor, Method, or Field");
             }
         }
     }
 
+    public void generateClasses(final ClassWithOptions[] classes) {
+        if (classes.length == 0) {
+            return;
+        }
+
+        header.append(
+                "public:\n");
+        for (final ClassWithOptions cls : classes) {
+            // Extract "Inner" from "Outer::Inner".
+            header.append(
+                    "    class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
+        }
+        header.append('\n');
+    }
+
     /**
      * Get the finalised bytes to go into the generated wrappers file.
      *
      * @return The bytes to be written to the wrappers file.
      */
     public String getWrapperFileContents() {
         return cpp.toString();
     }
--- a/build/annotationProcessors/classloader/JarClassIterator.java
+++ b/build/annotationProcessors/classloader/JarClassIterator.java
@@ -23,30 +23,31 @@ public class JarClassIterator implements
         return mTargetClassListIterator.hasNext();
     }
 
     @Override
     public ClassWithOptions next() {
         String className = mTargetClassListIterator.next();
         try {
             Class<?> ret = mTarget.loadClass(className);
-            final String canonicalName;
 
             // Incremental builds can leave stale classfiles in the jar. Such classfiles will cause
             // an exception at this point. We can safely ignore these classes - they cannot possibly
-            // ever be loaded as they conflict with their parent class and will be killed by Proguard
-            // later on anyway.
+            // ever be loaded as they conflict with their parent class and will be killed by
+            // Proguard later on anyway.
+            final Class<?> enclosingClass;
             try {
-                canonicalName = ret.getCanonicalName();
+                enclosingClass = ret.getEnclosingClass();
             } catch (IncompatibleClassChangeError e) {
                 return next();
             }
 
-            if (canonicalName == null || "null".equals(canonicalName)) {
+            if (enclosingClass != null) {
                 // Anonymous inner class - unsupported.
+                // Or named inner class, which will be processed when we process the outer class.
                 return next();
             }
 
             return new ClassWithOptions(ret, ret.getSimpleName());
         } catch (ClassNotFoundException e) {
             System.err.println("Unable to enumerate class: " + className + ". Corrupted jar file?");
             e.printStackTrace();
             System.exit(2);
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -1,38 +1,44 @@
 /* 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 org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
+import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Iterator;
 
 /**
  * Iterator over the methods in a given method list which have the WrappedJNIMethod
  * 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 boolean mIterateEveryEntry;
 
-    public GeneratableElementIterator(Class<?> aClass) {
+    public GeneratableElementIterator(ClassWithOptions annotatedClass) {
+        mClass = annotatedClass;
+
+        final Class<?> aClass = annotatedClass.wrappedClass;
         // Get all the elements of this class as AccessibleObjects.
         Member[] aMethods = aClass.getDeclaredMethods();
         Member[] aFields = aClass.getDeclaredFields();
         Member[] aCtors = aClass.getDeclaredConstructors();
 
         // Shove them all into one buffer.
         Member[] objs = new Member[aMethods.length + aFields.length + aCtors.length];
 
@@ -54,16 +60,66 @@ public class GeneratableElementIterator 
                 mIterateEveryEntry = true;
                 break;
             }
         }
 
         findNextValue();
     }
 
+    private Class<?>[] getFilteredInnerClasses() {
+        // Go through all inner classes and see which ones we want to generate.
+        final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
+        int count = 0;
+
+        for (int i = 0; i < candidates.length; ++i) {
+            final GeneratableElementIterator testIterator
+                    = new GeneratableElementIterator(new ClassWithOptions(candidates[i], null));
+            if (testIterator.hasNext()
+                    || testIterator.getFilteredInnerClasses() != null) {
+                count++;
+                continue;
+            }
+            // Clear out ones that don't match.
+            candidates[i] = null;
+        }
+        return count > 0 ? candidates : null;
+    }
+
+    public ClassWithOptions[] getInnerClasses() {
+        final Class<?>[] candidates = getFilteredInnerClasses();
+        if (candidates == null) {
+            return new ClassWithOptions[0];
+        }
+
+        int count = 0;
+        for (Class<?> candidate : candidates) {
+            if (candidate != null) {
+                count++;
+            }
+        }
+
+        final ClassWithOptions[] ret = new ClassWithOptions[count];
+        count = 0;
+        for (Class<?> candidate : candidates) {
+            if (candidate != null) {
+                ret[count++] = new ClassWithOptions(
+                        candidate, mClass.generatedName + "::" + candidate.getSimpleName());
+            }
+        }
+        assert ret.length == count;
+
+        Arrays.sort(ret, new Comparator<ClassWithOptions>() {
+            @Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
+                return lhs.generatedName.compareTo(rhs.generatedName);
+            }
+        });
+        return ret;
+    }
+
     /**
      * 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++;
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -220,16 +220,20 @@ public class Utils {
      */
     public static String getMemberName(Member member) {
         if (member instanceof Constructor) {
             return "<init>";
         }
         return member.getName();
     }
 
+    public static String getUnqualifiedName(String name) {
+        return name.substring(name.lastIndexOf(':') + 1);
+    }
+
     /**
      * Determine if a member is declared static.
      *
      * @param member The Member to check.
      * @return true if the member is declared static, false otherwise.
      */
     public static boolean isStatic(final Member member) {
         return Modifier.isStatic(member.getModifiers());
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -427,16 +427,17 @@ LOCAL_INCLUDES += [
     '/js/xpconnect/src',
     '/js/xpconnect/wrappers',
     '/layout/base',
     '/layout/generic',
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
     '/netwerk/base',
+    '/security/manager/ssl',
     '/widget',
     '/xpcom/ds',
 ]
 
 if CONFIG['MOZ_B2G_BT_API_V1']:
     LOCAL_INCLUDES += [
         '../bluetooth/bluetooth1',
     ]
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -46,16 +46,26 @@
 #include "nsIWebNavigation.h"
 #include "nsGenericHTMLElement.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIObserverService.h"
 #include "mozilla/Preferences.h"
 #include "nsParserConstants.h"
 #include "nsSandboxFlags.h"
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 using namespace mozilla;
 
 PRLogModuleInfo* gContentSinkLogModuleInfo;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
@@ -745,22 +755,33 @@ nsContentSink::ProcessStyleLink(nsIConte
     // The URI is bad, move along, don't propagate the error (for now)
     return NS_OK;
   }
 
   NS_ASSERTION(!aElement ||
                aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
                "We only expect processing instructions here");
 
+  nsAutoString integrity;
+  if (aElement) {
+    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+  }
+  if (!integrity.IsEmpty()) {
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+            ("nsContentSink::ProcessStyleLink, integrity=%s",
+             NS_ConvertUTF16toUTF8(integrity).get()));
+  }
+
   // If this is a fragment parser, we don't want to observe.
   // We don't support CORS for processing instructions
   bool isAlternate;
   rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
                                  CORS_NONE, mDocument->GetReferrerPolicy(),
-                                 mRunsToCompletion ? nullptr : this, &isAlternate);
+                                 integrity, mRunsToCompletion ? nullptr : this,
+                                 &isAlternate);
   NS_ENSURE_SUCCESS(rv, rv);
   
   if (!isAlternate && !mRunsToCompletion) {
     ++mPendingSheetCount;
     mScriptLoader->AddExecuteBlocker();
   }
 
   return NS_OK;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9871,27 +9871,28 @@ public:
 };
 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
 
 } // namespace
 
 void
 nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
                          const nsAString& aCrossOriginAttr,
-                         const ReferrerPolicy aReferrerPolicy)
+                         const ReferrerPolicy aReferrerPolicy,
+                         const nsAString& aIntegrity)
 {
   // The CSSLoader will retain this object after we return.
   nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
 
   // Charset names are always ASCII.
   CSSLoader()->LoadSheet(uri, NodePrincipal(),
                          NS_LossyConvertUTF16toASCII(charset),
                          obs,
                          Element::StringToCORSMode(aCrossOriginAttr),
-                         aReferrerPolicy);
+                         aReferrerPolicy, aIntegrity);
 }
 
 nsresult
 nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                 CSSStyleSheet** sheet)
 {
   return CSSLoader()->LoadSheetSync(uri, isAgentSheet, isAgentSheet, sheet);
 }
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1142,17 +1142,18 @@ public:
                                  ReferrerPolicy aReferrerPolicy) override;
   virtual void ForgetImagePreload(nsIURI* aURI) override;
 
   virtual void MaybePreconnect(nsIURI* uri,
                                mozilla::CORSMode aCORSMode) override;
 
   virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
                             const nsAString& aCrossOriginAttr,
-                            ReferrerPolicy aReferrerPolicy) override;
+                            ReferrerPolicy aReferrerPolicy,
+                            const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        mozilla::CSSStyleSheet** sheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
   virtual mozilla::EventStates GetDocumentState() override;
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -484,16 +484,17 @@ GK_ATOM(input, "input")
 GK_ATOM(inputmode, "inputmode")
 GK_ATOM(ins, "ins")
 GK_ATOM(insertafter, "insertafter")
 GK_ATOM(insertbefore, "insertbefore")
 GK_ATOM(instanceOf, "instanceOf")
 GK_ATOM(int32, "int32")
 GK_ATOM(int64, "int64")
 GK_ATOM(integer, "integer")
+GK_ATOM(integrity, "integrity")
 GK_ATOM(intersection, "intersection")
 GK_ATOM(is, "is")
 GK_ATOM(iscontainer, "iscontainer")
 GK_ATOM(isempty, "isempty")
 GK_ATOM(ismap, "ismap")
 GK_ATOM(itemid, "itemid")
 GK_ATOM(itemprop, "itemprop")
 GK_ATOM(itemref, "itemref")
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -147,18 +147,18 @@ template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0xbbce44c8, 0x22fe, 0x404f, \
-  { 0x9e, 0x71, 0x23, 0x1d, 0xf4, 0xcc, 0x8e, 0x34 } }
+{ 0x6d18ec0b, 0x1f68, 0x4ae6, \
+  { 0x8b, 0x3d, 0x8d, 0x7d, 0x8b, 0x8e, 0x28, 0xd4 } }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
   DocumentFlavorSVG, // SVGDocument
   DocumentFlavorPlain, // Just a Document
 };
@@ -2018,17 +2018,18 @@ public:
 
   /**
    * Called by nsParser to preload style sheets.  Can also be merged into the
    * parser if and when the parser is merged with libgklayout.  aCrossOriginAttr
    * should be a void string if the attr is not present.
    */
   virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
                             const nsAString& aCrossOriginAttr,
-                            ReferrerPolicyEnum aReferrerPolicy) = 0;
+                            ReferrerPolicyEnum aReferrerPolicy,
+                            const nsAString& aIntegrity) = 0;
 
   /**
    * Called by the chrome registry to load style sheets.  Can be put
    * back there if and when when that module is merged with libgklayout.
    *
    * This always does a synchronous load.  If aIsAgentSheet is true,
    * it also uses the system principal and enables unsafe rules.
    * DO NOT USE FOR UNTRUSTED CONTENT.
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -48,19 +48,31 @@
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkPredictor.h"
 #include "ImportManager.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/unused.h"
+#include "mozilla/dom/SRICheck.h"
+#include "nsIScriptError.h"
 
 static PRLogModuleInfo* gCspPRLog;
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // The nsScriptLoadRequest is passed as the context to necko, and thus
 // it needs to be threadsafe. Necko won't do anything with this
 // context, but it will AddRef and Release it on other threads.
 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
 
@@ -601,17 +613,32 @@ nsScriptLoader::ProcessScriptElement(nsI
       } else {
         // Drop the preload
         request = nullptr;
       }
     }
 
     if (!request) {
       // no usable preload
-      request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
+
+      SRIMetadata sriMetadata;
+      {
+        nsAutoString integrity;
+        scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
+                               integrity);
+        if (!integrity.IsEmpty()) {
+          MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+                 ("nsScriptLoader::ProcessScriptElement, integrity=%s",
+                  NS_ConvertUTF16toUTF8(integrity).get()));
+          SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
+        }
+      }
+
+      request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
+                                        sriMetadata);
       request->mURI = scriptURI;
       request->mIsInline = false;
       request->mLoading = true;
       request->mReferrerPolicy = ourRefPolicy;
 
       // set aScriptFromHead to false so we don't treat non preloaded scripts as
       // blockers for full page load. See bug 792438.
       rv = StartLoad(request, type, false);
@@ -715,17 +742,18 @@ nsScriptLoader::ProcessScriptElement(nsI
   }
 
   // Does CSP allow this inline script to run?
   if (!CSPAllowsInlineScript(aElement, mDocument)) {
     return false;
   }
 
   // Inline scripts ignore ther CORS mode and are always CORS_NONE
-  request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
+  request = new nsScriptLoadRequest(aElement, version, CORS_NONE,
+                                    SRIMetadata()); // SRI doesn't apply
   request->mJSVersion = version;
   request->mLoading = false;
   request->mIsInline = true;
   request->mURI = mDocument->GetDocumentURI();
   request->mLineNo = aElement->GetScriptLineNumber();
 
   if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
       (!ReadyToExecuteScripts() || !mXSLTRequests.isEmpty())) {
@@ -1403,18 +1431,25 @@ nsScriptLoader::OnStreamComplete(nsIStre
                                  nsresult aStatus,
                                  uint32_t aStringLen,
                                  const uint8_t* aString)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
-  nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
-                                     aString);
+  nsresult rv = NS_ERROR_SRI_CORRUPT;
+  if (request->mIntegrity.IsEmpty() ||
+      NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity,
+                                             request->mURI,
+                                             request->mCORSMode, aStringLen,
+                                             aString, mDocument))) {
+    rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
+  }
+
   if (NS_FAILED(rv)) {
     /*
      * Handle script not loading error because source was a tracking URL.
      * We make a note of this script node by including it in a dedicated
      * array of blocked tracking nodes under its parent document.
      */
     if (rv == NS_ERROR_TRACKING_URI) {
       nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement);
@@ -1598,27 +1633,37 @@ nsScriptLoader::ParsingComplete(bool aTe
   // onload and all.
   ProcessPendingRequests();
 }
 
 void
 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                            const nsAString &aType,
                            const nsAString &aCrossOrigin,
+                           const nsAString& aIntegrity,
                            bool aScriptFromHead,
                            const mozilla::net::ReferrerPolicy aReferrerPolicy)
 {
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return;
   }
 
+  SRIMetadata sriMetadata;
+  if (!aIntegrity.IsEmpty()) {
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+           ("nsScriptLoader::PreloadURI, integrity=%s",
+            NS_ConvertUTF16toUTF8(aIntegrity).get()));
+    SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
+  }
+
   nsRefPtr<nsScriptLoadRequest> request =
     new nsScriptLoadRequest(nullptr, 0,
-                            Element::StringToCORSMode(aCrossOrigin));
+                            Element::StringToCORSMode(aCrossOrigin),
+                            sriMetadata);
   request->mURI = aURI;
   request->mIsInline = false;
   request->mLoading = true;
   request->mReferrerPolicy = aReferrerPolicy;
 
   nsresult rv = StartLoad(request, aType, aScriptFromHead);
   if (NS_FAILED(rv)) {
     return;
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -14,16 +14,17 @@
 #include "nsCOMPtr.h"
 #include "nsIScriptElement.h"
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsIDocument.h"
 #include "nsIStreamLoader.h"
 #include "mozilla/CORSMode.h"
+#include "mozilla/dom/SRIMetadata.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class nsScriptLoadRequestList;
 class nsIURI;
 
 namespace JS {
   class SourceBufferHolder;
@@ -51,31 +52,33 @@ class nsScriptLoadRequest final : public
 
   // Allow LinkedListElement<nsScriptLoadRequest> to cast us to itself as needed.
   friend class mozilla::LinkedListElement<nsScriptLoadRequest>;
   friend class nsScriptLoadRequestList;
 
 public:
   nsScriptLoadRequest(nsIScriptElement* aElement,
                       uint32_t aVersion,
-                      mozilla::CORSMode aCORSMode)
+                      mozilla::CORSMode aCORSMode,
+                      const mozilla::dom::SRIMetadata &aIntegrity)
     : mElement(aElement),
       mLoading(true),
       mIsInline(true),
       mHasSourceMapURL(false),
       mIsDefer(false),
       mIsAsync(false),
       mIsNonAsyncScriptInserted(false),
       mIsXSLT(false),
       mIsCanceled(false),
       mScriptTextBuf(nullptr),
       mScriptTextLength(0),
       mJSVersion(aVersion),
       mLineNo(1),
       mCORSMode(aCORSMode),
+      mIntegrity(aIntegrity),
       mReferrerPolicy(mozilla::net::RP_Default)
   {
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   void FireScriptAvailable(nsresult aResult)
   {
@@ -117,16 +120,17 @@ public:
   char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
   size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
   uint32_t mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIPrincipal> mOriginPrincipal;
   nsAutoCString mURL;   // Keep the URI's filename alive during off thread parsing.
   int32_t mLineNo;
   const mozilla::CORSMode mCORSMode;
+  const mozilla::dom::SRIMetadata mIntegrity;
   mozilla::net::ReferrerPolicy mReferrerPolicy;
 };
 
 class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest>
 {
   typedef mozilla::LinkedList<nsScriptLoadRequest> super;
 
 public:
@@ -362,21 +366,23 @@ public:
   /**
    * Adds aURI to the preload list and starts loading it.
    *
    * @param aURI The URI of the external script.
    * @param aCharset The charset parameter for the script.
    * @param aType The type parameter for the script.
    * @param aCrossOrigin The crossorigin attribute for the script.
    *                     Void if not present.
+   * @param aIntegrity The expect hash url, if avail, of the request
    * @param aScriptFromHead Whether or not the script was a child of head
    */
   virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                           const nsAString &aType,
                           const nsAString &aCrossOrigin,
+                          const nsAString& aIntegrity,
                           bool aScriptFromHead,
                           const mozilla::net::ReferrerPolicy aReferrerPolicy);
 
   /**
    * Process a request that was deferred so that the script could be compiled
    * off thread.
    */
   nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest,
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -416,23 +416,31 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
       return rv;
 
     // Parse the style sheet.
     rv = doc->CSSLoader()->
       LoadInlineStyle(thisContent, text, mLineNumber, title, media,
                       scopeElement, aObserver, &doneLoading, &isAlternate);
   }
   else {
+    nsAutoString integrity;
+    thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+    if (!integrity.IsEmpty()) {
+      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+              ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
+               NS_ConvertUTF16toUTF8(integrity).get()));
+    }
+
     // XXXbz clone the URI here to work around content policies modifying URIs.
     nsCOMPtr<nsIURI> clonedURI;
     uri->Clone(getter_AddRefs(clonedURI));
     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
     rv = doc->CSSLoader()->
       LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
-                    GetCORSMode(), doc->GetReferrerPolicy(),
+                    GetCORSMode(), doc->GetReferrerPolicy(), integrity,
                     aObserver, &isAlternate);
     if (NS_FAILED(rv)) {
       // Don't propagate LoadStyleLink() errors further than this, since some
       // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
       // things like a stylesheet load being blocked by the security system.
       doneLoading = true;
       isAlternate = false;
       rv = NS_OK;
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -209,16 +209,21 @@ HTMLLinkElement::ParseAttribute(int32_t 
       ParseCORSValue(aValue, aResult);
       return true;
     }
 
     if (aAttribute == nsGkAtoms::sizes) {
       aResult.ParseAtomArray(aValue);
       return true;
     }
+
+    if (aAttribute == nsGkAtoms::integrity) {
+      aResult.ParseStringOrAtom(aValue);
+      return true;
+    }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -138,16 +138,24 @@ public:
   {
     SetHTMLAttr(nsGkAtoms::rev, aRev, aRv);
   }
   // XPCOM GetTarget is fine.
   void SetTarget(const nsAString& aTarget, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
   }
+  void GetIntegrity(nsAString& aIntegrity) const
+  {
+    GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+  }
+  void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
+  }
 
   already_AddRefed<nsIDocument> GetImport();
   already_AddRefed<ImportLoader> GetImportLoader()
   {
     return nsRefPtr<ImportLoader>(mImportLoader).forget();
   }
 
 protected:
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -70,20 +70,26 @@ HTMLScriptElement::BindToTree(nsIDocumen
 }
 
 bool
 HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult)
 {
-  if (aNamespaceID == kNameSpaceID_None &&
-      aAttribute == nsGkAtoms::crossorigin) {
-    ParseCORSValue(aValue, aResult);
-    return true;
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aAttribute == nsGkAtoms::crossorigin) {
+      ParseCORSValue(aValue, aResult);
+      return true;
+    }
+
+    if (aAttribute == nsGkAtoms::integrity) {
+      aResult.ParseStringOrAtom(aValue);
+      return true;
+    }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 nsresult
 HTMLScriptElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -74,16 +74,24 @@ public:
     // always parse to an enum value, so we don't need an invalid
     // default, and we _want_ the missing default to be null.
     GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
   }
   void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
   {
     SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
   }
+  void GetIntegrity(nsAString& aIntegrity)
+  {
+    GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+  }
+  void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
+  {
+    SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
+  }
   bool Async();
   void SetAsync(bool aValue, ErrorResult& rv);
 
 protected:
   virtual ~HTMLScriptElement();
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
   // nsScriptElement
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -48,12 +48,27 @@ InsecurePasswordsPresentOnPage=Password 
 InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
 InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
 # LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource
 LoadingMixedActiveContent2=Loading mixed (insecure) active content "%1$S" on a secure page
 LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a secure page
 # LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
 BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
 
+# Sub-Resource Integrity
+# LOCALIZATION NOTE: Do not translate "script" or "integrity"
+MalformedIntegrityURI=The script element has a malformed URI in its integrity attribute: "%1$S". The correct format is "<hash algorithm>-<hash value>".
+# LOCALIZATION NOTE: Do not translate "integrity"
+InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
+# LOCALIZATION NOTE: Do not translate "integrity"
+InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
+# LOCALIZATION NOTE: Do not translate "integrity"
+IntegrityMismatch=None of the "%1$S" hashes in the integrity attribute match the content of the subresource.
+IneligibleResource="%1$S" is not eligible for integrity checks since it's neither CORS-enabled nor same-origin.
+# LOCALIZATION NOTE: Do not translate "integrity"
+UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: "%1$S"
+# LOCALIZATION NOTE: Do not translate "integrity"
+NoValidMetadata=The integrity attribute does not contain any valid metadata.
+
 # LOCALIZATION NOTE: Do not translate "SSL 3.0".
 WeakProtocolVersionWarning=This site uses the protocol SSL 3.0 for encryption, which is deprecated and insecure.
 # LOCALIZATION NOTE: Do not translate "RC4".
 WeakCipherSuiteWarning=This site uses the cipher RC4 for encryption, which is deprecated and insecure.
--- a/dom/media/BufferMediaResource.h
+++ b/dom/media/BufferMediaResource.h
@@ -21,17 +21,16 @@ class BufferMediaResource : public Media
 {
 public:
   BufferMediaResource(const uint8_t* aBuffer,
                       uint32_t aLength,
                       nsIPrincipal* aPrincipal,
                       const nsACString& aContentType) :
     mBuffer(aBuffer),
     mLength(aLength),
-    mOffset(0),
     mPrincipal(aPrincipal),
     mContentType(aContentType)
   {
     MOZ_COUNT_CTOR(BufferMediaResource);
   }
 
 protected:
   virtual ~BufferMediaResource()
@@ -54,55 +53,25 @@ private:
   {
     return nullptr;
   }
 
   // These methods are called off the main thread.
   // The mode is initially MODE_PLAYBACK.
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override
-  {
-    *aBytes = std::min(mLength - mOffset, aCount);
-    memcpy(aBuffer, mBuffer + mOffset, *aBytes);
-    mOffset += *aBytes;
-    MOZ_ASSERT(mOffset <= mLength);
-    return NS_OK;
-  }
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes) override
   {
-    nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
-    if (NS_FAILED(rv)) return rv;
-    return Read(aBuffer, aCount, aBytes);
-  }
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override
-  {
-    MOZ_ASSERT(aOffset <= UINT32_MAX);
-    switch (aWhence) {
-    case nsISeekableStream::NS_SEEK_SET:
-      if (aOffset < 0 || aOffset > mLength) {
-        return NS_ERROR_FAILURE;
-      }
-      mOffset = static_cast<uint32_t> (aOffset);
-      break;
-    case nsISeekableStream::NS_SEEK_CUR:
-      if (aOffset >= mLength - mOffset) {
-        return NS_ERROR_FAILURE;
-      }
-      mOffset += static_cast<uint32_t> (aOffset);
-      break;
-    case nsISeekableStream::NS_SEEK_END:
-      if (aOffset < 0 || aOffset > mLength) {
-        return NS_ERROR_FAILURE;
-      }
-      mOffset = mLength - aOffset;
-      break;
+    if (aOffset < 0 || aOffset > mLength) {
+      return NS_ERROR_FAILURE;
     }
-
+    *aBytes = std::min(mLength - static_cast<uint32_t>(aOffset), aCount);
+    memcpy(aBuffer, mBuffer + aOffset, *aBytes);
+    mOffset = aOffset + *aBytes;
     return NS_OK;
   }
   virtual int64_t Tell() override { return mOffset; }
 
   virtual void Pin() override {}
   virtual void Unpin() override {}
   virtual double GetDownloadRate(bool* aIsReliable) override { *aIsReliable = false; return 0.; }
   virtual int64_t GetLength() override { return mLength; }
--- a/dom/media/DecodedStream.cpp
+++ b/dom/media/DecodedStream.cpp
@@ -422,16 +422,23 @@ DecodedStream::SetPlaying(bool aPlaying)
 void
 DecodedStream::SetVolume(double aVolume)
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mVolume = aVolume;
 }
 
 void
+DecodedStream::SetSameOrigin(bool aSameOrigin)
+{
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  mSameOrigin = aSameOrigin;
+}
+
+void
 DecodedStream::InitTracks()
 {
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (mData->mStreamInitialized) {
     return;
   }
 
@@ -669,24 +676,24 @@ DecodedStream::AdvanceTracks()
   }
 
   if (!mData->mHaveSentFinish) {
     mData->mStream->AdvanceKnownTracksTime(endPosition);
   }
 }
 
 bool
-DecodedStream::SendData(bool aIsSameOrigin)
+DecodedStream::SendData()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
 
   InitTracks();
-  SendAudio(mVolume, aIsSameOrigin);
-  SendVideo(aIsSameOrigin);
+  SendAudio(mVolume, mSameOrigin);
+  SendVideo(mSameOrigin);
   AdvanceTracks();
 
   bool finished = (!mInfo.HasAudio() || mAudioQueue.IsFinished()) &&
                   (!mInfo.HasVideo() || mVideoQueue.IsFinished());
 
   if (finished && !mData->mHaveSentFinish) {
     mData->mHaveSentFinish = true;
     mData->mStream->Finish();
--- a/dom/media/DecodedStream.h
+++ b/dom/media/DecodedStream.h
@@ -59,24 +59,25 @@ public:
 
   void DestroyData();
   void RecreateData();
   void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   void Remove(MediaStream* aStream);
 
   void SetPlaying(bool aPlaying);
   void SetVolume(double aVolume);
+  void SetSameOrigin(bool aSameOrigin);
 
   int64_t AudioEndTime() const;
   int64_t GetPosition() const;
   bool IsFinished() const;
   bool HasConsumers() const;
 
   // Return true if stream is finished.
-  bool SendData(bool aIsSameOrigin);
+  bool SendData();
 
 protected:
   virtual ~DecodedStream();
 
 private:
   ReentrantMonitor& GetReentrantMonitor() const;
   void RecreateData(MediaStreamGraph* aGraph);
   void Connect(OutputStreamData* aStream);
@@ -96,16 +97,17 @@ private:
   // calling back into MDSM functions in order to prevent deadlocks.
   //
   // Please move all capture-stream related code from MDSM into DecodedStream
   // and apply "dispatch + mirroring" to get rid of this monitor in the future.
   mutable ReentrantMonitor mMonitor;
 
   bool mPlaying;
   double mVolume;
+  bool mSameOrigin;
 
   Maybe<int64_t> mStartTime;
   MediaInfo mInfo;
 
   MediaQueue<MediaData>& mAudioQueue;
   MediaQueue<MediaData>& mVideoQueue;
 };
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -327,16 +327,17 @@ MediaDecoderStateMachine::Initialization
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
   mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
   mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged);
+  mWatchManager.Watch(mSameOriginMedia, &MediaDecoderStateMachine::SameOriginMediaChanged);
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
   // We've got audio ready to play if:
@@ -370,17 +371,17 @@ int64_t MediaDecoderStateMachine::GetDec
 }
 
 void MediaDecoderStateMachine::SendStreamData()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
 
-  bool finished = mDecodedStream->SendData(mSameOriginMedia);
+  bool finished = mDecodedStream->SendData();
 
   const auto clockTime = GetClock();
   while (true) {
     const MediaData* a = AudioQueue().PeekFront();
 
     // If we discard audio samples fed to the stream immediately, we will
     // keep decoding audio samples till the end and consume a lot of memory.
     // Therefore we only discard those behind the stream clock to throttle
@@ -1403,16 +1404,23 @@ void MediaDecoderStateMachine::PlayState
 
 void MediaDecoderStateMachine::LogicallySeekingChanged()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   ScheduleStateMachine();
 }
 
+void MediaDecoderStateMachine::SameOriginMediaChanged()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  mDecodedStream->SetSameOrigin(mSameOriginMedia);
+}
+
 void MediaDecoderStateMachine::BufferedRangeUpdated()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // While playing an unseekable stream of unknown duration, mObservedDuration
   // is updated (in AdvanceFrame()) as we play. But if data is being downloaded
   // faster than played, mObserved won't reflect the end of playable data
   // since we haven't played the frame at the end of buffered data. So update
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -519,16 +519,19 @@ protected:
   void StartAudioThread();
 
   // Notification method invoked when mPlayState changes.
   void PlayStateChanged();
 
   // Notification method invoked when mLogicallySeeking changes.
   void LogicallySeekingChanged();
 
+  // Notification method invoked when mSameOriginMedia changes.
+  void SameOriginMediaChanged();
+
   // Sets internal state which causes playback of media to pause.
   // The decoder monitor must be held.
   void StopPlayback();
 
   // If the conditions are right, sets internal state which causes playback
   // of media to begin or resume.
   // Must be called with the decode monitor held.
   void MaybeStartPlayback();
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -719,30 +719,16 @@ void ChannelMediaResource::CloseChannel(
 
 nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
                                              int64_t aOffset,
                                              uint32_t aCount)
 {
   return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
 }
 
-nsresult ChannelMediaResource::Read(char* aBuffer,
-                                    uint32_t aCount,
-                                    uint32_t* aBytes)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
-  int64_t offset = mCacheStream.Tell();
-  nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes);
-  if (NS_SUCCEEDED(rv)) {
-    DispatchBytesConsumed(*aBytes, offset);
-  }
-  return rv;
-}
-
 nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
                                       char* aBuffer,
                                       uint32_t aCount,
                                       uint32_t* aBytes)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   nsresult rv = mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
@@ -772,25 +758,16 @@ ChannelMediaResource::MediaReadAt(int64_
     aOffset += bytesRead;
     aCount -= bytesRead;
     curr += bytesRead;
   }
   bytes->SetLength(curr - start);
   return bytes.forget();
 }
 
-nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
-  CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
-        aOffset, mDecoder);
-  return mCacheStream.Seek(aWhence, aOffset);
-}
-
 int64_t ChannelMediaResource::Tell()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   return mCacheStream.Tell();
 }
 
 nsresult ChannelMediaResource::GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
@@ -1204,22 +1181,19 @@ public:
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) override;
   virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
 
   // These methods are called off the main thread.
 
   // Other thread
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) override {}
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) override {}
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes) override;
   virtual already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
-  virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount) override;
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
   virtual int64_t  Tell() override;
 
   // Any thread
   virtual void    Pin() override {}
   virtual void    Unpin() override {}
   virtual double  GetDownloadRate(bool* aIsReliable) override
   {
     // The data's all already here
@@ -1495,31 +1469,16 @@ nsresult FileMediaResource::ReadFromCach
 
   // If a read failed in the loop above, we want to return its failure code.
   NS_ENSURE_SUCCESS(res,res);
 
   // Else we succeed if the reset-seek succeeds.
   return seekres;
 }
 
-nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
-{
-  nsresult rv;
-  int64_t offset = 0;
-  {
-    MutexAutoLock lock(mLock);
-    mSeekable->Tell(&offset);
-    rv = UnsafeRead(aBuffer, aCount, aBytes);
-  }
-  if (NS_SUCCEEDED(rv)) {
-    DispatchBytesConsumed(*aBytes, offset);
-  }
-  return rv;
-}
-
 nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
 {
   EnsureSizeInitialized();
   return mInput->Read(aBuffer, aCount, aBytes);
 }
 
 nsresult FileMediaResource::ReadAt(int64_t aOffset, char* aBuffer,
                                    uint32_t aCount, uint32_t* aBytes)
@@ -1567,40 +1526,16 @@ FileMediaResource::UnsafeMediaReadAt(int
     }
     aCount -= bytesRead;
     curr += bytesRead;
   }
   bytes->SetLength(curr - start);
   return bytes.forget();
 }
 
-already_AddRefed<MediaByteBuffer>
-FileMediaResource::SilentReadAt(int64_t aOffset, uint32_t aCount)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
-  MutexAutoLock lock(mLock);
-  int64_t pos = 0;
-  NS_ENSURE_TRUE(mSeekable, nullptr);
-  nsresult rv = mSeekable->Tell(&pos);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-  nsRefPtr<MediaByteBuffer> bytes = UnsafeMediaReadAt(aOffset, aCount);
-  UnsafeSeek(nsISeekableStream::NS_SEEK_SET, pos);
-  NS_ENSURE_TRUE(bytes && bytes->Length() == aCount, nullptr);
-  return bytes.forget();
-}
-
-nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-
-  MutexAutoLock lock(mLock);
-  return UnsafeSeek(aWhence, aOffset);
-}
-
 nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
   if (!mSeekable)
     return NS_ERROR_FAILURE;
   EnsureSizeInitialized();
   return mSeekable->Seek(aWhence, aOffset);
@@ -1747,10 +1682,73 @@ void BaseMediaResource::DispatchBytesCon
 {
   if (aNumBytes <= 0) {
     return;
   }
   RefPtr<nsIRunnable> event(new DispatchBytesConsumedEvent(mDecoder, aNumBytes, aOffset));
   NS_DispatchToMainThread(event);
 }
 
+nsresult
+MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
+
+  // We purposefuly don't check that we may attempt to read past
+  // mResource->GetLength() as the resource's length may change over time.
+
+  nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  mOffset += *aBytes;
+  return NS_OK;
+}
+
+nsresult
+MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
+                           uint32_t aCount, uint32_t* aBytes) const
+{
+  *aBytes = 0;
+  while (aCount > 0) {
+    uint32_t bytesRead = 0;
+    nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!bytesRead) {
+      break;
+    }
+    *aBytes += bytesRead;
+    aOffset += bytesRead;
+    aBuffer += bytesRead;
+    aCount -= bytesRead;
+  }
+  return NS_OK;
+}
+
+nsresult
+MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
+{
+  switch (aWhence) {
+    case SEEK_SET:
+      break;
+    case SEEK_CUR:
+      aOffset += mOffset;
+      break;
+    case SEEK_END:
+    {
+      int64_t length = mResource->GetLength();
+      if (length == -1 || length - aOffset < 0) {
+        return NS_ERROR_FAILURE;
+      }
+      aOffset = mResource->GetLength() - aOffset;
+    }
+      break;
+    default:
+      return NS_ERROR_FAILURE;
+  }
+
+  mOffset = aOffset;
+
+  return NS_OK;
+}
+
 } // namespace mozilla
 
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -272,23 +272,16 @@ public:
 
   // These methods are called off the main thread.
   // The mode is initially MODE_PLAYBACK.
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) = 0;
   // This is the client's estimate of the playback rate assuming
   // the media plays continuously. The cache can't guess this itself
   // because it doesn't know when the decoder was paused, buffering, etc.
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) = 0;
-  // Read up to aCount bytes from the stream. The buffer must have
-  // enough room for at least aCount bytes. Stores the number of
-  // actual bytes read in aBytes (0 on end of file).
-  // May read less than aCount bytes if the number of
-  // available bytes is less than aCount. Always check *aBytes after
-  // read, and call again if necessary.
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) = 0;
   // Read up to aCount bytes from the stream. The read starts at
   // aOffset in the stream, seeking to that location initially if
   // it is not the current stream offset. The remaining arguments,
   // results and requirements are the same as per the Read method.
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes) = 0;
   // This method returns nullptr if anything fails.
   // Otherwise, it returns an owned buffer.
@@ -311,69 +304,20 @@ public:
       aOffset += bytesRead;
       aCount -= bytesRead;
       curr += bytesRead;
     }
     bytes->SetLength(curr - start);
     return bytes.forget();
   }
 
-  // ReadAt without side-effects. Given that our MediaResource infrastructure
-  // is very side-effecty, this accomplishes its job by checking the initial
-  // position and seeking back to it. If the seek were to fail, a side-effect
-  // might be observable.
-  //
-  // This method returns null if anything fails, including the failure to read
-  // aCount bytes. Otherwise, it returns an owned buffer.
-  virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount)
-  {
-    nsRefPtr<MediaByteBuffer> bytes = new MediaByteBuffer(aCount);
-    bytes->SetLength(aCount);
-    nsresult rv =
-      ReadFromCache(reinterpret_cast<char*>(bytes->Elements()), aOffset, aCount);
-    if (NS_SUCCEEDED(rv)) {
-      return bytes.forget();
-    }
-    int64_t pos = Tell();
-    // Free our buffer first to minimize memory usage.
-    bytes = nullptr;
-    bytes = MediaReadAt(aOffset, aCount);
-    Seek(nsISeekableStream::NS_SEEK_SET, pos);
-    NS_ENSURE_TRUE(bytes && bytes->Length() == aCount, nullptr);
-    return bytes.forget();
-  }
-
-  // Seek to the given bytes offset in the stream. aWhence can be
-  // one of:
-  //   NS_SEEK_SET
-  //   NS_SEEK_CUR
-  //   NS_SEEK_END
-  //
-  // In the Http strategy case the cancel will cause the http
-  // channel's listener to close the pipe, forcing an i/o error on any
-  // blocked read. This will allow the decode thread to complete the
-  // event.
-  //
-  // In the case of a seek in progress, the byte range request creates
-  // a new listener. This is done on the main thread via seek
-  // synchronously dispatching an event. This avoids the issue of us
-  // closing the listener but an outstanding byte range request
-  // creating a new one. They run on the same thread so no explicit
-  // synchronisation is required. The byte range request checks for
-  // the cancel flag and does not create a new channel or listener if
-  // we are cancelling.
-  //
-  // The default strategy does not do any seeking - the only issue is
-  // a blocked read which it handles by causing the listener to close
-  // the pipe, as per the http case.
-  //
-  // The file strategy doesn't block for any great length of time so
-  // is fine for a no-op cancel.
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) = 0;
   // Report the current offset in bytes from the start of the stream.
+  // This is used to approximate where we currently are in the playback of a
+  // media.
+  // A call to ReadAt will update this position.
   virtual int64_t Tell() = 0;
   // Moves any existing channel loads into or out of background. Background
   // loads don't block the load event. This also determines whether or not any
   // new loads initiated (for example to seek) will be in the background.
   virtual void SetLoadInBackground(bool aLoadInBackground) {}
   // Ensures that the value returned by IsSuspendedByCache below is up to date
   // (i.e. the cache has examined this stream at least once).
   virtual void EnsureCacheUpToDate() {}
@@ -665,22 +609,20 @@ public:
     }
   }
   virtual nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount) override;
   virtual void     EnsureCacheUpToDate() override;
 
   // Other thread
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) override;
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) override;
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
   virtual nsresult ReadAt(int64_t offset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes) override;
   virtual already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
-  virtual int64_t  Tell() override;
+  virtual int64_t Tell() override;
 
   // Any thread
   virtual void    Pin() override;
   virtual void    Unpin() override;
   virtual double  GetDownloadRate(bool* aIsReliable) override;
   virtual int64_t GetLength() override;
   virtual int64_t GetNextCachedData(int64_t aOffset) override;
   virtual int64_t GetCachedDataEnd(int64_t aOffset) override;
@@ -829,11 +771,104 @@ class MOZ_STACK_CLASS AutoPinned {
   operator T*() const { return mResource; }
   T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mResource; }
 
 private:
   T* mResource;
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+/*
+ * MediaResourceIndex provides a way to access MediaResource objects.
+ * Read, Seek and Tell must only be called on non-main threads.
+ * In the case of the Ogg Decoder they are called on the Decode thread for
+ * example. You must ensure that no threads are calling these methods once
+ * the MediaResource has been Closed.
+ */
+
+class MediaResourceIndex
+{
+public:
+  explicit MediaResourceIndex(MediaResource* aResource)
+    : mResource(aResource)
+    , mOffset(0)
+  {}
+
+  // Read up to aCount bytes from the stream. The buffer must have
+  // enough room for at least aCount bytes. Stores the number of
+  // actual bytes read in aBytes (0 on end of file).
+  // May read less than aCount bytes if the number of
+  // available bytes is less than aCount. Always check *aBytes after
+  // read, and call again if necessary.
+  nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
+  // Seek to the given bytes offset in the stream. aWhence can be
+  // one of:
+  //   NS_SEEK_SET
+  //   NS_SEEK_CUR
+  //   NS_SEEK_END
+  //
+  // In the Http strategy case the cancel will cause the http
+  // channel's listener to close the pipe, forcing an i/o error on any
+  // blocked read. This will allow the decode thread to complete the
+  // event.
+  //
+  // In the case of a seek in progress, the byte range request creates
+  // a new listener. This is done on the main thread via seek
+  // synchronously dispatching an event. This avoids the issue of us
+  // closing the listener but an outstanding byte range request
+  // creating a new one. They run on the same thread so no explicit
+  // synchronisation is required. The byte range request checks for
+  // the cancel flag and does not create a new channel or listener if
+  // we are cancelling.
+  //
+  // The default strategy does not do any seeking - the only issue is
+  // a blocked read which it handles by causing the listener to close
+  // the pipe, as per the http case.
+  //
+  // The file strategy doesn't block for any great length of time so
+  // is fine for a no-op cancel.
+  nsresult Seek(int32_t aWhence, int64_t aOffset);
+  // Report the current offset in bytes from the start of the stream.
+  int64_t Tell() const { return mOffset; }
+
+  // Return the underlying MediaResource.
+  MediaResource* GetResource() const { return mResource; }
+
+  // Read up to aCount bytes from the stream. The read starts at
+  // aOffset in the stream, seeking to that location initially if
+  // it is not the current stream offset.
+  // Unlike MediaResource::ReadAt, ReadAt only returns fewer bytes than
+  // requested if end of stream or an error is encountered. There is no need to
+  // call it again to get more data.
+  // *aBytes will contain the number of bytes copied, even if an error occurred.
+  // ReadAt doesn't have an impact on the offset returned by Tell().
+  nsresult ReadAt(int64_t aOffset, char* aBuffer,
+                  uint32_t aCount, uint32_t* aBytes) const;
+
+  // Convenience methods, directly calling the MediaResource method of the same
+  // name.
+  // Those functions do not update the MediaResource offset as returned
+  // by Tell().
+
+  // This method returns nullptr if anything fails.
+  // Otherwise, it returns an owned buffer.
+  // MediaReadAt may return fewer bytes than requested if end of stream is
+  // encountered. There is no need to call it again to get more data.
+  already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) const
+  {
+    return mResource->MediaReadAt(aOffset, aCount);
+  }
+  // Get the length of the stream in bytes. Returns -1 if not known.
+  // This can change over time; after a seek operation, a misbehaving
+  // server may give us a resource of a different length to what it had
+  // reported previously --- or it may just lie in its Content-Length
+  // header and give us more or less data than it reported. We will adjust
+  // the result of GetLength to reflect the data that's actually arriving.
+  int64_t GetLength() const { return mResource->GetLength(); }
+
+private:
+  nsRefPtr<MediaResource> mResource;
+  int64_t mOffset;
+};
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/RtspMediaResource.h
+++ b/dom/media/RtspMediaResource.h
@@ -128,25 +128,16 @@ public:
                           uint32_t aCount, uint32_t* aBytes)  override{
     return NS_ERROR_FAILURE;
   }
   // dummy
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) override {}
   // dummy
   virtual void     SetPlaybackRate(uint32_t aBytesPerSecond) override {}
   // dummy
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
-  override {
-    return NS_OK;
-  }
-  // dummy
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override {
-    return NS_OK;
-  }
-  // dummy
   virtual int64_t  Tell() override { return 0; }
 
   // Any thread
   virtual void    Pin() override {}
   virtual void    Unpin() override {}
 
   virtual bool    IsSuspendedByCache() override { return mIsSuspend; }
 
--- a/dom/media/apple/AppleMP3Reader.cpp
+++ b/dom/media/apple/AppleMP3Reader.cpp
@@ -39,16 +39,17 @@ AppleMP3Reader::AppleMP3Reader(AbstractM
   , mStreamReady(false)
   , mAudioFramesPerCompressedPacket(0)
   , mCurrentAudioFrame(0)
   , mAudioChannels(0)
   , mAudioSampleRate(0)
   , mAudioFileStream(nullptr)
   , mAudioConverter(nullptr)
   , mMP3FrameParser(mDecoder->GetResource()->GetLength())
+  , mResource(mDecoder->GetResource())
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread");
 }
 
 AppleMP3Reader::~AppleMP3Reader()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread");
 }
@@ -85,37 +86,25 @@ static void _AudioSampleCallback(void *a
  * If we're not at end of stream, read |aNumBytes| from the media resource,
  * put it in |aData|, and return true.
  * Otherwise, put as much data as is left into |aData|, set |aNumBytes| to the
  * amount of data we have left, and return false.
  */
 nsresult
 AppleMP3Reader::Read(uint32_t *aNumBytes, char *aData)
 {
-  MediaResource *resource = mDecoder->GetResource();
-
-  // Loop until we have all the data asked for, or we've reached EOS
-  uint32_t totalBytes = 0;
-  uint32_t numBytes;
-  do {
-    uint32_t bytesWanted = *aNumBytes - totalBytes;
-    nsresult rv = resource->Read(aData + totalBytes, bytesWanted, &numBytes);
-    totalBytes += numBytes;
+  nsresult rv = mResource.Read(aData, *aNumBytes, aNumBytes);
 
-    if (NS_FAILED(rv)) {
-      *aNumBytes = 0;
-      return NS_ERROR_FAILURE;
-    }
-  } while(totalBytes < *aNumBytes && numBytes);
+  if (NS_FAILED(rv)) {
+    *aNumBytes = 0;
+    return NS_ERROR_FAILURE;
+  }
 
-  *aNumBytes = totalBytes;
-
-  // We will have read some data in the last iteration iff we filled the buffer.
   // XXX Maybe return a better value than NS_ERROR_FAILURE?
-  return numBytes ? NS_OK : NS_ERROR_FAILURE;
+  return *aNumBytes ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 AppleMP3Reader::Init(MediaDecoderReader* aCloneDonor)
 {
   AudioFileTypeID fileType = kAudioFileMP3Type;
 
   OSStatus rv = AudioFileStreamOpen(this,
@@ -252,17 +241,17 @@ AppleMP3Reader::AudioSampleCallback(UInt
     }
 
     int64_t time = FramesToUsecs(mCurrentAudioFrame, mAudioSampleRate).value();
     int64_t duration = FramesToUsecs(numFrames, mAudioSampleRate).value();
 
     LOGD("pushed audio at time %lfs; duration %lfs\n",
          (double)time / USECS_PER_S, (double)duration / USECS_PER_S);
 
-    AudioData *audio = new AudioData(mDecoder->GetResource()->Tell(),
+    AudioData *audio = new AudioData(mResource.Tell(),
                                      time, duration, numFrames,
                                      reinterpret_cast<AudioDataValue *>(decoded.forget()),
                                      mAudioChannels, mAudioSampleRate);
     mAudioQueue.Push(audio);
 
     mCurrentAudioFrame += numFrames;
 
     if (rv == kNeedMoreData) {
@@ -512,32 +501,33 @@ AppleMP3Reader::Seek(int64_t aTime, int6
     LOGE("Couldn't seek demuxer. Error code %x\n", rv);
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   LOGD("computed byte offset = %lld; estimated = %s\n",
        byteOffset,
        (flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO");
 
-  mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
+  mResource.Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
 
   ResetDecode();
 
   return SeekPromise::CreateAndResolve(aTime, __func__);
 }
 
 void
 AppleMP3Reader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (!mMP3FrameParser.NeedsData()) {
     return;
   }
 
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes =
+    mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
   mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
   if (!mMP3FrameParser.IsMP3()) {
     return;
   }
 
   uint64_t duration = mMP3FrameParser.GetDuration();
   if (duration != mDuration) {
--- a/dom/media/apple/AppleMP3Reader.h
+++ b/dom/media/apple/AppleMP3Reader.h
@@ -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/. */
 
 #ifndef __AppleMP3Reader_h__
 #define __AppleMP3Reader_h__
 
 #include "MediaDecoderReader.h"
+#include "MediaResource.h"
 #include "MP3FrameParser.h"
 #include "VideoUtils.h"
 
 #include <AudioToolbox/AudioToolbox.h>
 
 namespace mozilla {
 
 class AppleMP3Reader : public MediaDecoderReader
@@ -74,13 +75,15 @@ private:
   UInt32 mAudioSampleRate;
 
   uint64_t mDuration;
 
   AudioFileStreamID mAudioFileStream;
   AudioConverterRef mAudioConverter;
 
   MP3FrameParser mMP3FrameParser;
+
+  MediaResourceIndex mResource;
 };
 
 } // namespace mozilla
 
 #endif // __AppleMP3Reader_h__
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -403,17 +403,17 @@ DirectShowReader::SeekInternal(int64_t a
 void
 DirectShowReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
 {
   MOZ_ASSERT(OnTaskQueue());
   if (!mMP3FrameParser.NeedsData()) {
     return;
   }
 
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
   mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
   if (!mMP3FrameParser.IsMP3()) {
     return;
   }
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (duration != mDuration) {
--- a/dom/media/directshow/SourceFilter.cpp
+++ b/dom/media/directshow/SourceFilter.cpp
@@ -71,38 +71,39 @@ class MediaResourcePartition {
 public:
   MediaResourcePartition(MediaResource* aResource,
                          int64_t aDataStart)
     : mResource(aResource),
       mDataOffset(aDataStart)
   {}
 
   int64_t GetLength() {
-    int64_t len = mResource->GetLength();
+    int64_t len = mResource.GetLength();
     if (len == -1) {
       return len;
     }
     return std::max<int64_t>(0, len - mDataOffset);
   }
   nsresult ReadAt(int64_t aOffset, char* aBuffer,
                   uint32_t aCount, uint32_t* aBytes)
   {
-    return mResource->ReadAt(aOffset + mDataOffset,
-                             aBuffer,
-                             aCount,
-                             aBytes);
+    return mResource.ReadAt(aOffset + mDataOffset,
+                            aBuffer,
+                            aCount,
+                            aBytes);
   }
   int64_t GetCachedDataEnd() {
-    int64_t tell = mResource->Tell();
-    int64_t dataEnd = mResource->GetCachedDataEnd(tell) - mDataOffset;
+    int64_t tell = mResource.GetResource()->Tell();
+    int64_t dataEnd =
+      mResource.GetResource()->GetCachedDataEnd(tell) - mDataOffset;
     return dataEnd;
   }
 private:
   // MediaResource from which we read data.
-  RefPtr<MediaResource> mResource;
+  MediaResourceIndex mResource;
   int64_t mDataOffset;
 };
 
 
 // Output pin for SourceFilter, which implements IAsyncReader, to
 // allow downstream filters to pull/read data from it. Downstream pins
 // register to read data using Request(), and asynchronously wait for the
 // reads to complete using WaitForNext(). They may also synchronously read
@@ -554,33 +555,23 @@ OutputPin::SyncRead(LONGLONG aPosition,
   {
     // Ignore reads while flushing.
     CriticalSectionAutoEnter lock(*mLock);
     if (mFlushCount) {
       return S_FALSE;
     }
   }
 
-  // Read in a loop to ensure we fill the buffer, when possible.
-  LONG totalBytesRead = 0;
-  while (totalBytesRead < aLength) {
-    BYTE* readBuffer = aBuffer + totalBytesRead;
-    uint32_t bytesRead = 0;
-    LONG length = aLength - totalBytesRead;
-    nsresult rv = mResource.ReadAt(aPosition + totalBytesRead,
-                                   reinterpret_cast<char*>(readBuffer),
-                                   length,
-                                   &bytesRead);
-    if (NS_FAILED(rv)) {
-      return E_FAIL;
-    }
-    totalBytesRead += bytesRead;
-    if (bytesRead == 0) {
-      break;
-    }
+  uint32_t totalBytesRead = 0;
+  nsresult rv = mResource.ReadAt(aPosition,
+                                 reinterpret_cast<char*>(aBuffer),
+                                 aLength,
+                                 &totalBytesRead);
+  if (NS_FAILED(rv)) {
+    return E_FAIL;
   }
   if (totalBytesRead > 0) {
     CriticalSectionAutoEnter lock(*mLock);
     mBytesConsumed += totalBytesRead;
   }
   return (totalBytesRead == aLength) ? S_OK : S_FALSE;
 }
 
--- a/dom/media/fmp4/MP4Stream.cpp
+++ b/dom/media/fmp4/MP4Stream.cpp
@@ -27,32 +27,27 @@ bool
 MP4Stream::BlockingReadIntoCache(int64_t aOffset, size_t aCount, Monitor* aToUnlock)
 {
   MOZ_ASSERT(mPinCount > 0);
   CacheBlock block(aOffset, aCount);
   if (!block.Init()) {
     return false;
   }
 
-  uint32_t sum = 0;
   uint32_t bytesRead = 0;
-  do {
-    uint64_t offset = aOffset + sum;
-    char* buffer = block.Buffer() + sum;
-    uint32_t toRead = aCount - sum;
+  {
     MonitorAutoUnlock unlock(*aToUnlock);
-    nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead);
+    nsresult rv = mResource.ReadAt(aOffset, block.Buffer(), aCount, &bytesRead);
     if (NS_FAILED(rv)) {
       return false;
     }
-    sum += bytesRead;
-  } while (sum < aCount && bytesRead > 0);
+  }
 
-  MOZ_ASSERT(block.mCount >= sum);
-  block.mCount = sum;
+  MOZ_ASSERT(block.mCount >= bytesRead);
+  block.mCount = bytesRead;
 
   mCache.AppendElement(block);
   return true;
 }
 
 // We surreptitiously reimplement the supposedly-blocking ReadAt as a non-
 // blocking CachedReadAt, and record when it fails. This allows MP4Reader
 // to retry the read as an actual blocking read without holding the lock.
@@ -80,28 +75,29 @@ MP4Stream::CachedReadAt(int64_t aOffset,
   for (size_t i = 0; i < mCache.Length(); ++i) {
     if (mCache[i].mOffset == aOffset && mCache[i].mCount >= aCount) {
       memcpy(aBuffer, mCache[i].Buffer(), aCount);
       *aBytesRead = aCount;
       return true;
     }
   }
 
-  nsresult rv = mResource->ReadFromCache(reinterpret_cast<char*>(aBuffer),
-                                         aOffset, aCount);
+  nsresult rv =
+    mResource.GetResource()->ReadFromCache(reinterpret_cast<char*>(aBuffer),
+                                           aOffset, aCount);
   if (NS_FAILED(rv)) {
     *aBytesRead = 0;
     return false;
   }
   *aBytesRead = aCount;
   return true;
 }
 
 bool
 MP4Stream::Length(int64_t* aSize)
 {
-  if (mResource->GetLength() < 0)
+  if (mResource.GetLength() < 0)
     return false;
-  *aSize = mResource->GetLength();
+  *aSize = mResource.GetLength();
   return true;
 }
 
 } // namespace mozilla
--- a/dom/media/fmp4/MP4Stream.h
+++ b/dom/media/fmp4/MP4Stream.h
@@ -44,32 +44,32 @@ public:
 
     return false;
   }
 
   void ClearFailedRead() { mFailedRead.reset(); }
 
   void Pin()
   {
-    mResource->Pin();
+    mResource.GetResource()->Pin();
     ++mPinCount;
   }
 
   void Unpin()
   {
-    mResource->Unpin();
+    mResource.GetResource()->Unpin();
     MOZ_ASSERT(mPinCount);
     --mPinCount;
     if (mPinCount == 0) {
       mCache.Clear();
     }
   }
 
 private:
-  nsRefPtr<MediaResource> mResource;
+  MediaResourceIndex mResource;
   Maybe<ReadRecord> mFailedRead;
   uint32_t mPinCount;
 
   struct CacheBlock {
     CacheBlock(int64_t aOffset, size_t aCount)
       : mOffset(aOffset), mCount(aCount), mBuffer(nullptr) {}
     int64_t mOffset;
     size_t mCount;
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -3,17 +3,16 @@
 /* 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 "nsMimeTypes.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
-#include "MediaResource.h"
 #include "GStreamerReader.h"
 #if GST_VERSION_MAJOR >= 1
 #include "GStreamerAllocator.h"
 #endif
 #include "GStreamerFormatHelper.h"
 #include "VideoUtils.h"
 #include "mozilla/Endian.h"
 #include "mozilla/Preferences.h"
@@ -83,17 +82,18 @@ GStreamerReader::GStreamerReader(Abstrac
   mAudioSinkBufferCount(0),
   mGstThreadsMonitor("media.gst.threads"),
   mReachedAudioEos(false),
   mReachedVideoEos(false),
 #if GST_VERSION_MAJOR >= 1
   mConfigureAlignment(true),
 #endif
   fpsNum(0),
-  fpsDen(0)
+  fpsDen(0),
+  mResource(aDecoder->GetResource())
 {
   MOZ_COUNT_CTOR(GStreamerReader);
 
   mSrcCallbacks.need_data = GStreamerReader::NeedDataCb;
   mSrcCallbacks.enough_data = GStreamerReader::EnoughDataCb;
   mSrcCallbacks.seek_data = GStreamerReader::SeekDataCb;
 
   mSinkCallbacks.eos = GStreamerReader::EosCb;
@@ -276,30 +276,29 @@ void GStreamerReader::PlayBinSourceSetup
   g_object_get(aPlayBin, "source", &source, nullptr);
   reader->PlayBinSourceSetup(GST_APP_SRC(source));
 }
 
 void GStreamerReader::PlayBinSourceSetup(GstAppSrc* aSource)
 {
   mSource = GST_APP_SRC(aSource);
   gst_app_src_set_callbacks(mSource, &mSrcCallbacks, (gpointer) this, nullptr);
-  MediaResource* resource = mDecoder->GetResource();
 
   /* do a short read to trigger a network request so that GetLength() below
    * returns something meaningful and not -1
    */
   char buf[512];
   unsigned int size = 0;
-  resource->Read(buf, sizeof(buf), &size);
-  resource->Seek(SEEK_SET, 0);
+  mResource.Read(buf, sizeof(buf), &size);
+  mResource.Seek(SEEK_SET, 0);
 
   /* now we should have a length */
   int64_t resourceLength = GetDataLength();
   gst_app_src_set_size(mSource, resourceLength);
-  if (resource->IsDataCachedToEndOfResource(0) ||
+  if (mResource.GetResource()->IsDataCachedToEndOfResource(0) ||
       (resourceLength != -1 && resourceLength <= SHORT_FILE_SIZE)) {
     /* let the demuxer work in pull mode for local files (or very short files)
      * so that we get optimal seeking accuracy/performance
      */
     LOG(LogLevel::Debug, "configuring random access, len %lld", resourceLength);
     gst_app_src_set_stream_type(mSource, GST_APP_STREAM_TYPE_RANDOM_ACCESS);
   } else {
     /* make the demuxer work in push mode so that seeking is kept to a minimum
@@ -318,25 +317,23 @@ void GStreamerReader::PlayBinSourceSetup
 }
 
 /**
  * If this stream is an MP3, we want to parse the headers to estimate the
  * stream duration.
  */
 nsresult GStreamerReader::ParseMP3Headers()
 {
-  MediaResource *resource = mDecoder->GetResource();
-
   const uint32_t MAX_READ_BYTES = 4096;
 
   uint64_t offset = 0;
   char bytes[MAX_READ_BYTES];
   uint32_t bytesRead;
   do {
-    nsresult rv = resource->ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead);
+    nsresult rv = mResource.ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead);
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE);
 
     mMP3FrameParser.Parse(reinterpret_cast<uint8_t*>(bytes), bytesRead, offset);
     offset += bytesRead;
   } while (!mMP3FrameParser.ParsedHeaders());
 
   if (mMP3FrameParser.IsMP3()) {
@@ -349,17 +346,17 @@ nsresult GStreamerReader::ParseMP3Header
   }
 
   return NS_OK;
 }
 
 int64_t
 GStreamerReader::GetDataLength()
 {
-  int64_t streamLen = mDecoder->GetResource()->GetLength();
+  int64_t streamLen = mResource.GetLength();
 
   if (streamLen < 0) {
     return streamLen;
   }
 
   return streamLen - mDataOffset;
 }
 
@@ -829,17 +826,17 @@ bool GStreamerReader::DecodeVideoFrame(b
      * allocating a PlanarYCbCrImage backed GstBuffer here and memcpy.
      */
     GstBuffer* tmp = nullptr;
     CopyIntoImageBuffer(buffer, &tmp, image);
     gst_buffer_unref(buffer);
     buffer = tmp;
   }
 
-  int64_t offset = mDecoder->GetResource()->Tell(); // Estimate location in media.
+  int64_t offset = mResource.Tell(); // Estimate location in media.
   nsRefPtr<VideoData> video = VideoData::CreateFromImage(mInfo.mVideo,
                                                          mDecoder->GetImageContainer(),
                                                          offset, timestamp, duration,
                                                          static_cast<Image*>(image.get()),
                                                          isKeyframe, -1, mPicture);
   mVideoQueue.Push(video);
 
   gst_buffer_unref(buffer);
@@ -929,41 +926,39 @@ media::TimeIntervals GStreamerReader::Ge
                           media::TimeUnit::FromMicroseconds(GST_TIME_AS_USECONDS(endTime)));
   }
 
   return buffered;
 }
 
 void GStreamerReader::ReadAndPushData(guint aLength)
 {
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
-  int64_t offset1 = resource->Tell();
+  int64_t offset1 = mResource.Tell();
   unused << offset1;
   nsresult rv = NS_OK;
 
   GstBuffer* buffer = gst_buffer_new_and_alloc(aLength);
 #if GST_VERSION_MAJOR >= 1
   GstMapInfo info;
   gst_buffer_map(buffer, &info, GST_MAP_WRITE);
   guint8 *data = info.data;
 #else
   guint8* data = GST_BUFFER_DATA(buffer);
 #endif
   uint32_t size = 0, bytesRead = 0;
   while(bytesRead < aLength) {
-    rv = resource->Read(reinterpret_cast<char*>(data + bytesRead),
-        aLength - bytesRead, &size);
+    rv = mResource.Read(reinterpret_cast<char*>(data + bytesRead),
+                        aLength - bytesRead, &size);
     if (NS_FAILED(rv) || size == 0)
       break;
 
     bytesRead += size;
   }
 
-  int64_t offset2 = resource->Tell();
+  int64_t offset2 = mResource.Tell();
   unused << offset2;
 
 #if GST_VERSION_MAJOR >= 1
   gst_buffer_unmap(buffer, &info);
   gst_buffer_set_size(buffer, bytesRead);
 #else
   GST_BUFFER_SIZE(buffer) = bytesRead;
 #endif
@@ -1027,35 +1022,28 @@ gboolean GStreamerReader::SeekDataCb(Gst
   return reader->SeekData(aSrc, aOffset);
 }
 
 gboolean GStreamerReader::SeekData(GstAppSrc* aSrc, guint64 aOffset)
 {
   aOffset += mDataOffset;
 
   ReentrantMonitorAutoEnter mon(mGstThreadsMonitor);
-  MediaResource* resource = mDecoder->GetResource();
-  int64_t resourceLength = resource->GetLength();
+  int64_t resourceLength = mResource.GetLength();
 
   if (gst_app_src_get_size(mSource) == -1) {
     /* It's possible that we didn't know the length when we initialized mSource
      * but maybe we do now
      */
     gst_app_src_set_size(mSource, GetDataLength());
   }
 
   nsresult rv = NS_ERROR_FAILURE;
   if (aOffset < static_cast<guint64>(resourceLength)) {
-    rv = resource->Seek(SEEK_SET, aOffset);
-  }
-
-  if (NS_FAILED(rv)) {
-    LOG(LogLevel::Error, "seek at %lu failed", aOffset);
-  } else {
-    MOZ_ASSERT(aOffset == static_cast<guint64>(resource->Tell()));
+    rv = mResource.Seek(SEEK_SET, aOffset);
   }
 
   return NS_SUCCEEDED(rv);
 }
 
 GstFlowReturn GStreamerReader::NewPrerollCb(GstAppSink* aSink,
                                             gpointer aUserData)
 {
@@ -1279,17 +1267,18 @@ void GStreamerReader::NotifyDataArrivedI
   MOZ_ASSERT(OnTaskQueue());
   if (HasVideo()) {
     return;
   }
   if (!mMP3FrameParser.NeedsData()) {
     return;
   }
 
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes =
+    mResource.MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
   mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
   if (!mMP3FrameParser.IsMP3()) {
     return;
   }
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (duration != mLastParserDuration && mUseParserDuration) {
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -16,16 +16,17 @@
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wunknown-pragmas"
 #pragma GCC diagnostic ignored "-Wpragmas"
 #pragma GCC diagnostic ignored "-Wreserved-user-defined-literal"
 #include <gst/video/video.h>
 #pragma GCC diagnostic pop
 
 #include "MediaDecoderReader.h"
+#include "MediaResource.h"
 #include "MP3FrameParser.h"
 #include "ImageContainer.h"
 #include "nsRect.h"
 
 struct GstURIDecodeBin;
 
 namespace mozilla {
 
@@ -257,13 +258,15 @@ private:
    */
   bool mReachedAudioEos;
   bool mReachedVideoEos;
 #if GST_VERSION_MAJOR >= 1
   bool mConfigureAlignment;
 #endif
   int fpsNum;
   int fpsDen;
+
+  MediaResourceIndex mResource;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/gtest/MockMediaResource.h
+++ b/dom/media/gtest/MockMediaResource.h
@@ -27,27 +27,18 @@ public:
   virtual bool CanClone() override { return false; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder)
     override
   {
     return nullptr;
   }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
-    override
-  {
-    return NS_OK;
-  }
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
                           uint32_t* aBytes) override;
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override
-  {
-    return NS_OK;
-  }
   virtual int64_t Tell() override { return 0; }
   virtual void Pin() override {}
   virtual void Unpin() override {}
   virtual double GetDownloadRate(bool* aIsReliable) override { return 0; }
   virtual int64_t GetLength() override;
   virtual int64_t GetNextCachedData(int64_t aOffset) override;
   virtual int64_t GetCachedDataEnd(int64_t aOffset) override;
   virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override
--- a/dom/media/mediasource/MediaSourceResource.h
+++ b/dom/media/mediasource/MediaSourceResource.h
@@ -30,19 +30,17 @@ public:
 
   virtual nsresult Close() override { return NS_OK; }
   virtual void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
   virtual void Resume() override { UNIMPLEMENTED(); }
   virtual bool CanClone() override { UNIMPLEMENTED(); return false; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) override { UNIMPLEMENTED(); return nullptr; }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override  { UNIMPLEMENTED(); }
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
   virtual int64_t Tell() override { UNIMPLEMENTED(); return -1; }
   virtual void Pin() override { UNIMPLEMENTED(); }
   virtual void Unpin() override { UNIMPLEMENTED(); }
   virtual double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
   virtual int64_t GetLength() override { UNIMPLEMENTED(); return -1; }
   virtual int64_t GetNextCachedData(int64_t aOffset) override { UNIMPLEMENTED(); return -1; }
   virtual int64_t GetCachedDataEnd(int64_t aOffset) override { UNIMPLEMENTED(); return -1; }
   virtual bool IsDataCachedToEndOfResource(int64_t aOffset) override { UNIMPLEMENTED(); return false; }
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -34,26 +34,16 @@ SourceBufferResource::Close()
   SBR_DEBUG("Close");
   //MOZ_ASSERT(!mClosed);
   mClosed = true;
   mon.NotifyAll();
   return NS_OK;
 }
 
 nsresult
-SourceBufferResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
-{
-  SBR_DEBUGV("Read(aBuffer=%p, aCount=%u, aBytes=%p)",
-             aBuffer, aCount, aBytes);
-  ReentrantMonitorAutoEnter mon(mMonitor);
-
-  return ReadInternal(aBuffer, aCount, aBytes, /* aMayBlock = */ true);
-}
-
-nsresult
 SourceBufferResource::ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock)
 {
   mMonitor.AssertCurrentThreadIn();
   MOZ_ASSERT_IF(!aMayBlock, aBytes);
 
   // Cache the offset for the read in case mOffset changes while waiting on the
   // monitor below. It's basically impossible to implement these API semantics
   // sanely. :-(
@@ -110,43 +100,16 @@ SourceBufferResource::ReadAtInternal(int
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return ReadInternal(aBuffer, aCount, aBytes, aMayBlock);
 }
 
 nsresult
-SourceBufferResource::Seek(int32_t aWhence, int64_t aOffset)
-{
-  SBR_DEBUG("Seek(aWhence=%d, aOffset=%lld)",
-            aWhence, aOffset);
-  ReentrantMonitorAutoEnter mon(mMonitor);
-
-  int64_t newOffset = mOffset;
-  switch (aWhence) {
-  case nsISeekableStream::NS_SEEK_END:
-    newOffset = GetLength() - aOffset;
-    break;
-  case nsISeekableStream::NS_SEEK_CUR:
-    newOffset += aOffset;
-    break;
-  case nsISeekableStream::NS_SEEK_SET:
-    newOffset = aOffset;
-    break;
-  }
-
-  SBR_DEBUGV("newOffset=%lld GetOffset()=%llu GetLength()=%llu)",
-             newOffset, mInputBuffer.GetOffset(), GetLength());
-  nsresult rv = SeekInternal(newOffset);
-  mon.NotifyAll();
-  return rv;
-}
-
-nsresult
 SourceBufferResource::SeekInternal(int64_t aOffset)
 {
   mMonitor.AssertCurrentThreadIn();
 
   if (mClosed ||
       aOffset < 0 ||
       uint64_t(aOffset) < mInputBuffer.GetOffset() ||
       aOffset > GetLength()) {
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -41,19 +41,17 @@ public:
   explicit SourceBufferResource(const nsACString& aType);
   virtual nsresult Close() override;
   virtual void Suspend(bool aCloseImmediately) override { UNIMPLEMENTED(); }
   virtual void Resume() override { UNIMPLEMENTED(); }
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() override { UNIMPLEMENTED(); return nullptr; }
   virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) override { UNIMPLEMENTED(); return nullptr; }
   virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
   virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
-  virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
-  virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
   virtual int64_t Tell() override { return mOffset; }
   virtual void Pin() override { UNIMPLEMENTED(); }
   virtual void Unpin() override { UNIMPLEMENTED(); }
   virtual double GetDownloadRate(bool* aIsReliable) override { UNIMPLEMENTED(); *aIsReliable = false; return 0; }
   virtual int64_t GetLength() override { return mInputBuffer.GetLength(); }
   virtual int64_t GetNextCachedData(int64_t aOffset) override {
     ReentrantMonitorAutoEnter mon(mMonitor);
     MOZ_ASSERT(aOffset >= 0);
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -60,17 +60,17 @@ static const int64_t SEEK_OPUS_PREROLL =
 enum PageSyncResult {
   PAGE_SYNC_ERROR = 1,
   PAGE_SYNC_END_OF_RANGE= 2,
   PAGE_SYNC_OK = 3
 };
 
 // Reads a page from the media resource.
 static PageSyncResult
-PageSync(MediaResource* aResource,
+PageSync(MediaResourceIndex* aResource,
          ogg_sync_state* aState,
          bool aCachedDataOnly,
          int64_t aOffset,
          int64_t aEndOffset,
          ogg_page* aPage,
          int& aSkippedBytes);
 
 // Chunk size to read when reading Ogg files. Average Ogg page length
@@ -134,17 +134,18 @@ OggReader::OggReader(AbstractMediaDecode
     mOpusState(nullptr),
     mOpusEnabled(MediaDecoder::IsOpusEnabled()),
     mSkeletonState(nullptr),
     mVorbisSerial(0),
     mOpusSerial(0),
     mTheoraSerial(0),
     mOpusPreSkip(0),
     mIsChained(false),
-    mDecodedAudioFrames(0)
+    mDecodedAudioFrames(0),
+    mResource(aDecoder->GetResource())
 {
   MOZ_COUNT_CTOR(OggReader);
   memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
 }
 
 OggReader::~OggReader()
 {
   ogg_sync_clear(&mOggState);
@@ -466,23 +467,22 @@ nsresult OggReader::ReadMetadata(MediaIn
   }
 
   SetupTargetSkeleton(mSkeletonState);
   SetupMediaTracksInfo(serials);
 
   if (HasAudio() || HasVideo()) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-    MediaResource* resource = mDecoder->GetResource();
     if (mInfo.mMetadataDuration.isNothing() && !mDecoder->IsOggDecoderShutdown() &&
-        resource->GetLength() >= 0 && mDecoder->IsMediaSeekable())
+        mResource.GetLength() >= 0 && mDecoder->IsMediaSeekable())
     {
       // We didn't get a duration from the index or a Content-Duration header.
       // Seek to the end of file to find the end time.
-      int64_t length = resource->GetLength();
+      int64_t length = mResource.GetLength();
 
       NS_ASSERTION(length > 0, "Must have a content length to get end time");
 
       int64_t endTime = 0;
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         endTime = RangeEndTime(length);
       }
@@ -536,17 +536,17 @@ nsresult OggReader::DecodeVorbis(ogg_pac
 
     // No channel mapping for more than 8 channels.
     if (channels > 8) {
       return NS_ERROR_FAILURE;
     }
 
     int64_t duration = mVorbisState->Time((int64_t)frames);
     int64_t startTime = mVorbisState->Time(endFrame - frames);
-    mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
+    mAudioQueue.Push(new AudioData(mResource.Tell(),
                                    startTime,
                                    duration,
                                    frames,
                                    buffer.forget(),
                                    channels,
                                    mVorbisState->mInfo.rate));
 
     mDecodedAudioFrames += frames;
@@ -653,17 +653,17 @@ nsresult OggReader::DecodeOpus(ogg_packe
   // No channel mapping for more than 8 channels.
   if (channels > 8) {
     return NS_ERROR_FAILURE;
   }
 
   LOG(LogLevel::Debug, ("Opus decoder pushing %d frames", frames));
   int64_t startTime = mOpusState->Time(startFrame);
   int64_t endTime = mOpusState->Time(endFrame);
-  mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
+  mAudioQueue.Push(new AudioData(mResource.Tell(),
                                  startTime,
                                  endTime - startTime,
                                  frames,
                                  buffer.forget(),
                                  channels,
                                  mOpusState->mRate));
 
   mDecodedAudioFrames += frames;
@@ -861,17 +861,17 @@ nsresult OggReader::DecodeTheora(ogg_pac
     b.mPlanes[i].mHeight = buffer[i].height;
     b.mPlanes[i].mWidth = buffer[i].width;
     b.mPlanes[i].mStride = buffer[i].stride;
     b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0;
   }
 
   nsRefPtr<VideoData> v = VideoData::Create(mInfo.mVideo,
                                             mDecoder->GetImageContainer(),
-                                            mDecoder->GetResource()->Tell(),
+                                            mResource.Tell(),
                                             time,
                                             endTime - time,
                                             b,
                                             isKeyframe,
                                             aPacket->granulepos,
                                             mPicture);
   if (!v) {
     // There may be other reasons for this error, but for
@@ -944,19 +944,19 @@ bool OggReader::ReadOggPage(ogg_page* aP
     // with the given size. This buffer is stored
     // in the ogg synchronisation structure.
     char* buffer = ogg_sync_buffer(&mOggState, 4096);
     NS_ASSERTION(buffer, "ogg_sync_buffer failed");
 
     // Read from the resource into the buffer
     uint32_t bytesRead = 0;
 
-    nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead);
-    if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
-      // End of file.
+    nsresult rv = mResource.Read(buffer, 4096, &bytesRead);
+    if (NS_FAILED(rv) || !bytesRead) {
+      // End of file or error.
       return false;
     }
 
     // Update the synchronisation layer with the number
     // of bytes written to the buffer
     ret = ogg_sync_wrote(&mOggState, bytesRead);
     NS_ENSURE_TRUE(ret == 0, false);
   }
@@ -1005,19 +1005,17 @@ GetChecksum(ogg_page* page)
                (p[2] << 16) +
                (p[3] << 24);
   return c;
 }
 
 int64_t OggReader::RangeStartTime(int64_t aOffset)
 {
   MOZ_ASSERT(OnTaskQueue());
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ENSURE_TRUE(resource != nullptr, 0);
-  nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
+  nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   NS_ENSURE_SUCCESS(res, 0);
   int64_t startTime = 0;
   FindStartTime(startTime);
   return startTime;
 }
 
 struct nsAutoOggSyncState {
   nsAutoOggSyncState() {
@@ -1028,30 +1026,27 @@ struct nsAutoOggSyncState {
   }
   ogg_sync_state mState;
 };
 
 int64_t OggReader::RangeEndTime(int64_t aEndOffset)
 {
   MOZ_ASSERT(OnTaskQueue() || mDecoder->OnStateMachineTaskQueue());
 
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ENSURE_TRUE(resource != nullptr, -1);
-  int64_t position = resource->Tell();
+  int64_t position = mResource.Tell();
   int64_t endTime = RangeEndTime(0, aEndOffset, false);
-  nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position);
+  nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, position);
   NS_ENSURE_SUCCESS(res, -1);
   return endTime;
 }
 
 int64_t OggReader::RangeEndTime(int64_t aStartOffset,
                                   int64_t aEndOffset,
                                   bool aCachedDataOnly)
 {
-  MediaResource* resource = mDecoder->GetResource();
   nsAutoOggSyncState sync;
 
   // We need to find the last page which ends before aEndOffset that
   // has a granulepos that we can convert to a timestamp. We do this by
   // backing off from aEndOffset until we encounter a page on which we can
   // interpret the granulepos. If while backing off we encounter a page which
   // we've previously encountered before, we'll either backoff again if we
   // haven't found an end time yet, or return the last end time found.
@@ -1094,25 +1089,25 @@ int64_t OggReader::RangeEndTime(int64_t 
       limit = std::max(static_cast<int64_t>(0), limit);
       limit = std::min(limit, static_cast<int64_t>(step));
       uint32_t bytesToRead = static_cast<uint32_t>(limit);
       uint32_t bytesRead = 0;
       char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
       NS_ASSERTION(buffer, "Must have buffer");
       nsresult res;
       if (aCachedDataOnly) {
-        res = resource->ReadFromCache(buffer, readHead, bytesToRead);
+        res = mResource.GetResource()->ReadFromCache(buffer, readHead, bytesToRead);
         NS_ENSURE_SUCCESS(res, -1);
         bytesRead = bytesToRead;
       } else {
         NS_ASSERTION(readHead < aEndOffset,
                      "resource pos must be before range end");
-        res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
+        res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, readHead);
         NS_ENSURE_SUCCESS(res, -1);
-        res = resource->Read(buffer, bytesToRead, &bytesRead);
+        res = mResource.Read(buffer, bytesToRead, &bytesRead);
         NS_ENSURE_SUCCESS(res, -1);
       }
       readHead += bytesRead;
       if (readHead > readLimitOffset) {
         mustBackOff = true;
       }
 
       // Update the synchronisation layer with the number
@@ -1208,17 +1203,17 @@ OggReader::SeekRange
 OggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
                              int64_t aTarget,
                              int64_t aStartTime,
                              int64_t aEndTime,
                              bool aExact)
 {
   MOZ_ASSERT(OnTaskQueue());
   int64_t so = 0;
-  int64_t eo = mDecoder->GetResource()->GetLength();
+  int64_t eo = mResource.GetLength();
   int64_t st = aStartTime;
   int64_t et = aEndTime;
   for (uint32_t i = 0; i < ranges.Length(); i++) {
     const SeekRange &r = ranges[i];
     if (r.mTimeStart < aTarget) {
       so = r.mOffsetStart;
       st = r.mTimeStart;
     }
@@ -1238,71 +1233,67 @@ OggReader::SelectSeekRange(const nsTArra
   return SeekRange(so, eo, st, et);
 }
 
 OggReader::IndexedSeekResult OggReader::RollbackIndexedSeek(int64_t aOffset)
 {
   if (mSkeletonState) {
     mSkeletonState->Deactivate();
   }
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
-  nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
+  nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
   return SEEK_INDEX_FAIL;
 }
 
 OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget)
 {
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
   if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
     return SEEK_INDEX_FAIL;
   }
   // We have an index from the Skeleton track, try to use it to seek.
   nsAutoTArray<uint32_t, 2> tracks;
   BuildSerialList(tracks);
   SkeletonState::nsSeekTarget keyframe;
   if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget,
                                                   tracks,
                                                   keyframe)))
   {
     // Could not locate a keypoint for the target in the index.
     return SEEK_INDEX_FAIL;
   }
 
   // Remember original resource read cursor position so we can rollback on failure.
-  int64_t tell = resource->Tell();
+  int64_t tell = mResource.Tell();
 
   // Seek to the keypoint returned by the index.
-  if (keyframe.mKeyPoint.mOffset > resource->GetLength() ||
+  if (keyframe.mKeyPoint.mOffset > mResource.GetLength() ||
       keyframe.mKeyPoint.mOffset < 0)
   {
     // Index must be invalid.
     return RollbackIndexedSeek(tell);
   }
   LOG(LogLevel::Debug, ("Seeking using index to keyframe at offset %lld\n",
                      keyframe.mKeyPoint.mOffset));
-  nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET,
-                              keyframe.mKeyPoint.mOffset);
+  nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET,
+                                keyframe.mKeyPoint.mOffset);
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
 
   // We've moved the read set, so reset decode.
   res = ResetDecode();
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
 
   // Check that the page the index thinks is exactly here is actually exactly
   // here. If not, the index is invalid.
   ogg_page page;
   int skippedBytes = 0;
-  PageSyncResult syncres = PageSync(resource,
+  PageSyncResult syncres = PageSync(&mResource,
                                     &mOggState,
                                     false,
                                     keyframe.mKeyPoint.mOffset,
-                                    resource->GetLength(),
+                                    mResource.GetLength(),
                                     &page,
                                     skippedBytes);
   NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
   if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
     LOG(LogLevel::Debug, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
                        "or sync error after seek"));
     return RollbackIndexedSeek(tell);
   }
@@ -1425,27 +1416,25 @@ OggReader::Seek(int64_t aTarget, int64_t
 nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   NS_ENSURE_TRUE(HaveStartTime(), NS_ERROR_FAILURE);
   if (mIsChained)
     return NS_ERROR_FAILURE;
   LOG(LogLevel::Debug, ("%p About to seek to %lld", mDecoder, aTarget));
   nsresult res;
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE);
   int64_t adjustedTarget = aTarget;
   if (HasAudio() && mOpusState){
     adjustedTarget = std::max(StartTime(), aTarget - SEEK_OPUS_PREROLL);
   }
 
   if (adjustedTarget == StartTime()) {
     // We've seeked to the media start. Just seek to the offset of the first
     // content page.
-    res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+    res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
 
     res = ResetDecode(true);
     NS_ENSURE_SUCCESS(res,res);
   } else {
     // TODO: This may seek back unnecessarily far in the video, but we don't
     // have a way of asking Skeleton to seek to a different target for each
     // stream yet. Using adjustedTarget here is at least correct, if slow.
@@ -1508,17 +1497,17 @@ nsresult OggReader::SeekInternal(int64_t
     }
 #endif
   }
   return NS_OK;
 }
 
 // Reads a page from the media resource.
 static PageSyncResult
-PageSync(MediaResource* aResource,
+PageSync(MediaResourceIndex* aResource,
          ogg_sync_state* aState,
          bool aCachedDataOnly,
          int64_t aOffset,
          int64_t aEndOffset,
          ogg_page* aPage,
          int& aSkippedBytes)
 {
   aSkippedBytes = 0;
@@ -1536,18 +1525,18 @@ PageSync(MediaResource* aResource,
       int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP),
                                    aEndOffset - readHead);
       NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check");
       if (bytesToRead <= 0) {
         return PAGE_SYNC_END_OF_RANGE;
       }
       nsresult rv = NS_OK;
       if (aCachedDataOnly) {
-        rv = aResource->ReadFromCache(buffer, readHead,
-                                      static_cast<uint32_t>(bytesToRead));
+        rv = aResource->GetResource()->ReadFromCache(buffer, readHead,
+                                                     static_cast<uint32_t>(bytesToRead));
         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
         bytesRead = static_cast<uint32_t>(bytesToRead);
       } else {
         rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
         rv = aResource->Read(buffer,
                              static_cast<uint32_t>(bytesToRead),
                              &bytesRead);
@@ -1578,23 +1567,22 @@ PageSync(MediaResource* aResource,
 }
 
 nsresult OggReader::SeekBisection(int64_t aTarget,
                                     const SeekRange& aRange,
                                     uint32_t aFuzz)
 {
   MOZ_ASSERT(OnTaskQueue());
   nsresult res;
-  MediaResource* resource = mDecoder->GetResource();
 
   if (aTarget == aRange.mTimeStart) {
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
-    res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+    res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
     return NS_OK;
   }
 
   // Bisection search, find start offset of last page with end time less than
   // the seek target.
   ogg_int64_t startOffset = aRange.mOffsetStart;
   ogg_int64_t startTime = aRange.mTimeStart;
@@ -1690,17 +1678,17 @@ nsresult OggReader::SeekBisection(int64_
       NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
       previousGuess = guess;
 
       hops++;
 
       // Locate the next page after our seek guess, and then figure out the
       // granule time of the audio and video bitstreams there. We can then
       // make a bisection decision based on our location in the media.
-      PageSyncResult res = PageSync(resource,
+      PageSyncResult res = PageSync(&mResource,
                                     &mOggState,
                                     false,
                                     guess,
                                     endOffset,
                                     &page,
                                     skippedBytes);
       NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
 
@@ -1789,28 +1777,28 @@ nsresult OggReader::SeekBisection(int64_
       break;
     } // End of "until we determine time at guess offset" loop.
 
     if (interval == 0) {
       // Seek termination condition; we've found the page boundary of the
       // last page before the target, and the first page after the target.
       SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", startOffset));
       NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
-      res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
+      res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
       NS_ENSURE_SUCCESS(res,res);
       if (NS_FAILED(ResetDecode())) {
         return NS_ERROR_FAILURE;
       }
       break;
     }
 
     SEEK_LOG(LogLevel::Debug, ("Time at offset %lld is %lld", guess, granuleTime));
     if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
       // We're within the fuzzy region in which we want to terminate the search.
-      res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
+      res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
       NS_ENSURE_SUCCESS(res,res);
       if (NS_FAILED(ResetDecode())) {
         return NS_ERROR_FAILURE;
       }
       SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", pageOffset));
       break;
     }
 
@@ -1884,17 +1872,17 @@ media::TimeIntervals OggReader::GetBuffe
 
     // Find the start time of the range. Read pages until we find one with a
     // granulepos which we can convert into a timestamp to use as the time of
     // the start of the buffered range.
     ogg_sync_reset(&sync.mState);
     while (startTime == -1) {
       ogg_page page;
       int32_t discard;
-      PageSyncResult res = PageSync(resource,
+      PageSyncResult res = PageSync(&mResource,
                                     &sync.mState,
                                     true,
                                     startOffset,
                                     endOffset,
                                     &page,
                                     discard);
       if (res == PAGE_SYNC_ERROR) {
         return media::TimeIntervals::Invalid();
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -9,16 +9,17 @@
 #include <ogg/ogg.h>
 #include <theora/theoradec.h>
 #ifdef MOZ_TREMOR
 #include <tremor/ivorbiscodec.h>
 #else
 #include <vorbis/codec.h>
 #endif
 #include "MediaDecoderReader.h"
+#include "MediaResource.h"
 #include "OggCodecState.h"
 #include "VideoUtils.h"
 #include "mozilla/Monitor.h"
 #include "OggDecoder.h"
 
 namespace mozilla {
 
 // Thread safe container to store the codec information and the serial for each
@@ -311,13 +312,15 @@ private:
   nsIntRect mPicture;
 
   // True if we are decoding a chained ogg. Reading or writing to this member
   // should be done with |mMonitor| acquired.
   bool mIsChained;
 
   // Number of audio frames decoded so far.
   int64_t mDecodedAudioFrames;
+
+  MediaResourceIndex mResource;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -411,18 +411,19 @@ MediaCodecReader::DecodeAudioDataSync()
     }
   }
 
   if ((bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_EOS) ||
       (status == ERROR_END_OF_STREAM)) {
     AudioQueue().Finish();
   } else if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 &&
              bufferInfo.mBuffer->data() != nullptr) {
+    MOZ_ASSERT(mStreamSource);
     // This is the approximate byte position in the stream.
-    int64_t pos = mDecoder->GetResource()->Tell();
+    int64_t pos = mStreamSource->Tell();
 
     uint32_t frames = bufferInfo.mSize /
                       (mInfo.mAudio.mChannels * sizeof(AudioDataValue));
 
     mAudioCompactor.Push(
       pos,
       bufferInfo.mTimeUs,
       mInfo.mAudio.mRate,
@@ -521,17 +522,18 @@ MediaCodecReader::HasVideo()
 {
   return mInfo.HasVideo();
 }
 
 void
 MediaCodecReader::NotifyDataArrivedInternal(uint32_t aLength,
                                             int64_t aOffset)
 {
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes =
+    mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
 
   MonitorAutoLock monLock(mParserMonitor);
   if (mNextParserPosition == mParsedDataLength &&
       mNextParserPosition >= aOffset &&
       mNextParserPosition <= aOffset + aLength) {
     // No pending parsing runnable currently. And available data are adjacent to
     // parsed data.
@@ -932,18 +934,19 @@ MediaCodecReader::DecodeVideoFrameSync(i
     mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex);
     return;
   }
 
   nsRefPtr<VideoData> v;
   RefPtr<TextureClient> textureClient;
   sp<GraphicBuffer> graphicBuffer;
   if (bufferInfo.mBuffer != nullptr) {
+    MOZ_ASSERT(mStreamSource);
     // This is the approximate byte position in the stream.
-    int64_t pos = mDecoder->GetResource()->Tell();
+    int64_t pos = mStreamSource->Tell();
 
     if (mVideoTrack.mNativeWindow != nullptr &&
         mVideoTrack.mCodec->getOutputGraphicBufferFromIndex(bufferInfo.mIndex, &graphicBuffer) == OK &&
         graphicBuffer != nullptr) {
       textureClient = mVideoTrack.mNativeWindow->getTextureClientFromBuffer(graphicBuffer.get());
       v = VideoData::Create(mInfo.mVideo,
                             mDecoder->GetImageContainer(),
                             pos,
@@ -1191,17 +1194,17 @@ MediaCodecReader::CreateExtractor()
   DataSource::RegisterDefaultSniffers();
 
   if (mExtractor == nullptr) {
     sp<DataSource> dataSource = new MediaStreamSource(mDecoder->GetResource());
 
     if (dataSource->initCheck() != OK) {
       return false;
     }
-
+    mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
     mExtractor = MediaExtractor::Create(dataSource);
   }
 
   return mExtractor != nullptr;
 }
 
 void
 MediaCodecReader::DestroyExtractor()
--- a/dom/media/omx/MediaOmxCommonReader.cpp
+++ b/dom/media/omx/MediaOmxCommonReader.cpp
@@ -22,16 +22,17 @@ using namespace android;
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
 
 MediaOmxCommonReader::MediaOmxCommonReader(AbstractMediaDecoder *aDecoder)
   : MediaDecoderReader(aDecoder)
+  , mStreamSource(nullptr)
 {
   if (!gMediaDecoderLog) {
     gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
   }
 
   mAudioChannel = dom::AudioChannelService::GetDefaultAudioChannel();
 }
 
--- a/dom/media/omx/MediaOmxCommonReader.h
+++ b/dom/media/omx/MediaOmxCommonReader.h
@@ -10,16 +10,17 @@
 #include "MediaDecoderReader.h"
 
 #include <utils/RefBase.h>
 
 #include "mozilla/dom/AudioChannelBinding.h"
 
 namespace android {
 struct MOZ_EXPORT MediaSource;
+class MediaStreamSource;
 } // namespace android
 
 namespace mozilla {
 
 class AbstractMediaDecoder;
 
 class MediaOmxCommonReader : public MediaDecoderReader
 {
@@ -38,13 +39,16 @@ public:
   // Check whether it is possible to offload current audio track. This access
   // canOffloadStream() from libStageFright Utils.cpp, which is not there in
   // ANDROID_VERSION < 19
   void CheckAudioOffload();
 #endif
 
 protected:
   dom::AudioChannel mAudioChannel;
+  // Weak reference to the MediaStreamSource that will be created by either
+  // MediaOmxReader or MediaCodecReader.
+  android::MediaStreamSource* mStreamSource;
 };
 
 } // namespace mozilla
 
 #endif // MEDIA_OMX_COMMON_READER_H
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -208,16 +208,17 @@ nsresult MediaOmxReader::InitOmxDecoder(
     mExtractor = MediaExtractor::Create(dataSource);
     if (!mExtractor.get()) {
       return NS_ERROR_FAILURE;
     }
     mOmxDecoder = new OmxDecoder(mDecoder);
     if (!mOmxDecoder->Init(mExtractor)) {
       return NS_ERROR_FAILURE;
     }
+    mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
   }
   return NS_OK;
 }
 
 nsRefPtr<MediaDecoderReader::MetadataPromise>
 MediaOmxReader::AsyncReadMetadata()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -387,18 +388,19 @@ bool MediaOmxReader::DecodeVideoFrame(bo
       // and we will preserve the ratio of the crop rectangle as it
       // was reported relative to the picture size reported by the container.
       picture.x = (mPicture.x * frame.Y.mWidth) / mInitialFrame.width;
       picture.y = (mPicture.y * frame.Y.mHeight) / mInitialFrame.height;
       picture.width = (frame.Y.mWidth * mPicture.width) / mInitialFrame.width;
       picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
     }
 
+    MOZ_ASSERT(mStreamSource);
     // This is the approximate byte position in the stream.
-    int64_t pos = mDecoder->GetResource()->Tell();
+    int64_t pos = mStreamSource->Tell();
 
     nsRefPtr<VideoData> v;
     if (!frame.mGraphicBuffer) {
 
       VideoData::YCbCrBuffer b;
       b.mPlanes[0].mData = static_cast<uint8_t *>(frame.Y.mData);
       b.mPlanes[0].mStride = frame.Y.mStride;
       b.mPlanes[0].mHeight = frame.Y.mHeight;
@@ -466,17 +468,18 @@ void MediaOmxReader::NotifyDataArrivedIn
   }
   if (HasVideo()) {
     return;
   }
   if (!mMP3FrameParser.NeedsData()) {
     return;
   }
 
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes =
+    mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
   mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
   if (!mMP3FrameParser.IsMP3()) {
     return;
   }
 
   int64_t duration = mMP3FrameParser.GetDuration();
   if (duration != mLastParserDuration) {
@@ -485,18 +488,19 @@ void MediaOmxReader::NotifyDataArrivedIn
   }
 }
 
 bool MediaOmxReader::DecodeAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   EnsureActive();
 
+  MOZ_ASSERT(mStreamSource);
   // This is the approximate byte position in the stream.
-  int64_t pos = mDecoder->GetResource()->Tell();
+  int64_t pos = mStreamSource->Tell();
 
   // Read next frame
   MPAPI::AudioFrame source;
   if (!mOmxDecoder->ReadAudio(&source, mAudioSeekTimeUs)) {
     return false;
   }
   mAudioSeekTimeUs = -1;
 
--- a/dom/media/omx/MediaStreamSource.cpp
+++ b/dom/media/omx/MediaStreamSource.cpp
@@ -28,37 +28,44 @@ status_t MediaStreamSource::initCheck() 
 
 ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size)
 {
   char *ptr = static_cast<char *>(data);
   size_t todo = size;
   while (todo > 0) {
     Mutex::Autolock autoLock(mLock);
     uint32_t bytesRead;
-    if ((offset != mResource->Tell() &&
-         NS_FAILED(mResource->Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
-        NS_FAILED(mResource->Read(ptr, todo, &bytesRead))) {
+    if ((offset != mResource.Tell() &&
+         NS_FAILED(mResource.Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
+        NS_FAILED(mResource.Read(ptr, todo, &bytesRead))) {
       return ERROR_IO;
     }
 
     if (bytesRead == 0) {
       return size - todo;
     }
 
     offset += bytesRead;
     todo -= bytesRead;
     ptr += bytesRead;
   }
   return size;
 }
 
 status_t MediaStreamSource::getSize(off64_t *size)
 {
-  uint64_t length = mResource->GetLength();
+  uint64_t length = mResource.GetLength();
   if (length == static_cast<uint64_t>(-1))
     return ERROR_UNSUPPORTED;
 
   *size = length;
 
   return OK;
 }
 
+int64_t
+MediaStreamSource::Tell()
+{
+  Mutex::Autolock autoLock(mLock);
+  return mResource.Tell();
+}
+
 } // namespace android
--- a/dom/media/omx/MediaStreamSource.h
+++ b/dom/media/omx/MediaStreamSource.h
@@ -15,19 +15,20 @@
 #include "MediaResource.h"
 #include "nsAutoPtr.h"
 
 namespace android {
 
 // MediaStreamSource is a DataSource that reads from a MPAPI media stream.
 class MediaStreamSource : public DataSource {
   typedef mozilla::MediaResource MediaResource;
+  typedef mozilla::MediaResourceIndex MediaResourceIndex;
 
   Mutex mLock;
-  nsRefPtr<MediaResource> mResource;
+  MediaResourceIndex mResource;
 public:
   MediaStreamSource(MediaResource* aResource);
 
   virtual status_t initCheck() const;
   virtual ssize_t readAt(off64_t offset, void *data, size_t size);
   virtual ssize_t readAt(off_t offset, void *data, size_t size) {
     return readAt(static_cast<off64_t>(offset), data, size);
   }
@@ -37,16 +38,18 @@ public:
     *size = size64;
     return status;
   }
   virtual status_t getSize(off64_t *size);
   virtual uint32_t flags() {
     return kWantsPrefetching;
   }
 
+  int64_t Tell();
+
   virtual ~MediaStreamSource();
 
 private:
   MediaStreamSource(const MediaStreamSource &);
   MediaStreamSource &operator=(const MediaStreamSource &);
 };
 
 } // namespace android
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -201,17 +201,17 @@ protected:
 };
 
 class AudioDataDecoder : public MediaCodecDataDecoder {
 
 public:
   AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback)
     : MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, aFormat, aCallback)
   {
-    JNIEnv* env = GetJNIForThread();
+    JNIEnv* const env = jni::GetEnvForThread();
 
     jni::Object::LocalRef buffer(env);
     NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"), &buffer));
 
     if (!buffer && aConfig.mCodecSpecificConfig->Length() >= 2) {
       buffer = jni::Object::LocalRef::Adopt(env, env->NewDirectByteBuffer(aConfig.mCodecSpecificConfig->Elements(),
                                                                           aConfig.mCodecSpecificConfig->Length()));
       NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"), buffer));
@@ -422,17 +422,17 @@ nsresult MediaCodecDataDecoder::GetInput
 
 void MediaCodecDataDecoder::DecoderLoop()
 {
   bool outputDone = false;
 
   bool draining = false;
   bool waitingEOF = false;
 
-  AutoLocalJNIFrame frame(GetJNIForThread(), 1);
+  AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
   nsRefPtr<MediaRawData> sample;
 
   MediaFormat::LocalRef outputFormat(frame.GetEnv());
   nsresult res;
 
   for (;;) {
     {
       MonitorAutoLock lock(mMonitor);
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -11,17 +11,17 @@
 #include "nsISeekableStream.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla;
 using namespace mozilla::media;
 
 RawReader::RawReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder),
-    mCurrentFrame(0), mFrameSize(0)
+    mCurrentFrame(0), mFrameSize(0), mResource(aDecoder->GetResource())
 {
   MOZ_COUNT_CTOR(RawReader);
 }
 
 RawReader::~RawReader()
 {
   MOZ_COUNT_DTOR(RawReader);
 }
@@ -37,20 +37,17 @@ nsresult RawReader::ResetDecode()
   return MediaDecoderReader::ResetDecode();
 }
 
 nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
                                  MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
-
-  if (!ReadFromResource(resource, reinterpret_cast<uint8_t*>(&mMetadata),
+  if (!ReadFromResource(reinterpret_cast<uint8_t*>(&mMetadata),
                         sizeof(mMetadata)))
     return NS_ERROR_FAILURE;
 
   // Validate the header
   if (!(mMetadata.headerPacketID == 0 /* Packet ID of 0 for the header*/ &&
         mMetadata.codecID == RAW_ID /* "YUV" */ &&
         mMetadata.majorVersion == 0 &&
         mMetadata.minorVersion == 1))
@@ -91,17 +88,17 @@ nsresult RawReader::ReadMetadata(MediaIn
       mMetadata.lumaChannelBpp != 8 ||
       mMetadata.colorspace != 1 /* 4:2:0 */)
     return NS_ERROR_FAILURE;
 
   mFrameSize = mMetadata.frameWidth * mMetadata.frameHeight *
     (mMetadata.lumaChannelBpp + mMetadata.chromaChannelBpp) / 8.0 +
     sizeof(RawPacketHeader);
 
-  int64_t length = resource->GetLength();
+  int64_t length = mResource.GetLength();
   if (length != -1) {
     mInfo.mMetadataDuration.emplace(TimeUnit::FromSeconds((length - sizeof(RawVideoHeader)) /
                                                           (mFrameSize * mFrameRate)));
   }
 
   *aInfo = mInfo;
 
   *aTags = nullptr;
@@ -119,32 +116,25 @@ RawReader::IsMediaSeekable()
  bool RawReader::DecodeAudioData()
 {
   MOZ_ASSERT(OnTaskQueue() || mDecoder->OnStateMachineTaskQueue());
   return false;
 }
 
 // Helper method that either reads until it gets aLength bytes
 // or returns false
-bool RawReader::ReadFromResource(MediaResource *aResource, uint8_t* aBuf,
-                                   uint32_t aLength)
+bool RawReader::ReadFromResource(uint8_t* aBuf, uint32_t aLength)
 {
-  while (aLength > 0) {
-    uint32_t bytesRead = 0;
-    nsresult rv;
+  uint32_t bytesRead = 0;
+  nsresult rv;
 
-    rv = aResource->Read(reinterpret_cast<char*>(aBuf), aLength, &bytesRead);
-    NS_ENSURE_SUCCESS(rv, false);
-
-    if (bytesRead == 0) {
-      return false;
-    }
-
-    aLength -= bytesRead;
-    aBuf += bytesRead;
+  rv = mResource.Read(reinterpret_cast<char*>(aBuf), aLength, &bytesRead);
+  NS_ENSURE_SUCCESS(rv, false);
+  if (bytesRead == 0) {
+    return false;
   }
 
   return true;
 }
 
 bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                      int64_t aTimeThreshold)
 {
@@ -156,31 +146,29 @@ bool RawReader::DecodeVideoFrame(bool &a
 
   if (!mFrameSize)
     return false; // Metadata read failed.  We should refuse to play.
 
   int64_t currentFrameTime = USECS_PER_S * mCurrentFrame / mFrameRate;
   uint32_t length = mFrameSize - sizeof(RawPacketHeader);
 
   nsAutoArrayPtr<uint8_t> buffer(new uint8_t[length]);
-  MediaResource* resource = mDecoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
 
   // We're always decoding one frame when called
   while(true) {
     RawPacketHeader header;
 
     // Read in a packet header and validate
-    if (!(ReadFromResource(resource, reinterpret_cast<uint8_t*>(&header),
+    if (!(ReadFromResource(reinterpret_cast<uint8_t*>(&header),
                            sizeof(header))) ||
         !(header.packetID == 0xFF && header.codecID == RAW_ID /* "YUV" */)) {
       return false;
     }
 
-    if (!ReadFromResource(resource, buffer, length)) {
+    if (!ReadFromResource(buffer, length)) {
       return false;
     }
 
     a.mParsed++;
 
     if (currentFrameTime >= aTimeThreshold)
       break;
 
@@ -228,29 +216,26 @@ bool RawReader::DecodeVideoFrame(bool &a
   return true;
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 RawReader::Seek(int64_t aTime, int64_t aEndTime)
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  MediaResource *resource = mDecoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
-
   uint32_t frame = mCurrentFrame;
   if (aTime >= UINT_MAX)
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   mCurrentFrame = aTime * mFrameRate / USECS_PER_S;
 
   CheckedUint32 offset = CheckedUint32(mCurrentFrame) * mFrameSize;
   offset += sizeof(RawVideoHeader);
   NS_ENSURE_TRUE(offset.isValid(), SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__));
 
-  nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value());
+  nsresult rv = mResource.Seek(nsISeekableStream::NS_SEEK_SET, offset.value());
   NS_ENSURE_SUCCESS(rv, SeekPromise::CreateAndReject(rv, __func__));
 
   mVideoQueue.Reset();
   nsRefPtr<SeekPromise::Private> p = new SeekPromise::Private(__func__);
   nsRefPtr<RawReader> self = this;
   InvokeUntil([self] () {
     MOZ_ASSERT(self->OnTaskQueue());
     NS_ENSURE_TRUE(!self->mShutdown, false);
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -42,20 +42,21 @@ public:
   virtual nsRefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
 private:
-  bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
+  bool ReadFromResource(uint8_t *aBuf, uint32_t aLength);
 
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
+  MediaResourceIndex mResource;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -1,16 +1,15 @@
 /* -*- 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 "nsError.h"
 #include "AbstractMediaDecoder.h"
-#include "MediaResource.h"
 #include "WaveReader.h"
 #include "MediaDecoderStateMachine.h"
 #include "VideoUtils.h"
 #include "nsISeekableStream.h"
 
 #include <stdint.h>
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CheckedInt.h"
@@ -102,16 +101,17 @@ namespace {
     uint8_t result = uint8_t((*aBuffer)[0]);
     *aBuffer += sizeof(uint8_t);
     return result;
   }
 } // namespace
 
 WaveReader::WaveReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
+  , mResource(aDecoder->GetResource())
 {
   MOZ_COUNT_CTOR(WaveReader);
 }
 
 WaveReader::~WaveReader()
 {
   MOZ_COUNT_DTOR(WaveReader);
 }
@@ -260,17 +260,17 @@ WaveReader::Seek(int64_t aTarget, int64_
   }
   double d = BytesToTime(GetDataLength());
   NS_ASSERTION(d < INT64_MAX / USECS_PER_S, "Duration overflow");
   int64_t duration = static_cast<int64_t>(d * USECS_PER_S);
   double seekTime = std::min(aTarget, duration) / static_cast<double>(USECS_PER_S);
   int64_t position = RoundDownToFrame(static_cast<int64_t>(TimeToBytes(seekTime)));
   NS_ASSERTION(INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek");
   position += mWavePCMOffset;
-  nsresult res = mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
+  nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, position);
   if (NS_FAILED(res)) {
     return SeekPromise::CreateAndReject(res, __func__);
   } else {
     return SeekPromise::CreateAndResolve(aTarget, __func__);
   }
 }
 
 media::TimeIntervals WaveReader::GetBuffered()
@@ -297,44 +297,40 @@ media::TimeIntervals WaveReader::GetBuff
     startOffset = resource->GetNextCachedData(endOffset);
   }
   return buffered;
 }
 
 bool
 WaveReader::ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead)
 {
-  uint32_t got = 0;
   if (aBytesRead) {
     *aBytesRead = 0;
   }
-  do {
-    uint32_t read = 0;
-    if (NS_FAILED(mDecoder->GetResource()->Read(aBuf + got, uint32_t(aSize - got), &read))) {
-      NS_WARNING("Resource read failed");
-      return false;
-    }
-    if (read == 0) {
-      return false;
-    }
-    got += read;
-    if (aBytesRead) {
-      *aBytesRead = got;
-    }
-  } while (got != aSize);
+  uint32_t read = 0;
+  if (NS_FAILED(mResource.Read(aBuf, uint32_t(aSize), &read))) {
+    NS_WARNING("Resource read failed");
+    return false;
+  }
+  if (!read) {
+    return false;
+  }
+  if (aBytesRead) {
+    *aBytesRead = read;
+  }
   return true;
 }
 
 bool
 WaveReader::LoadRIFFChunk()
 {
   char riffHeader[RIFF_INITIAL_SIZE];
   const char* p = riffHeader;
 
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() == 0,
+  MOZ_ASSERT(mResource.Tell() == 0,
              "LoadRIFFChunk called when resource in invalid state");
 
   if (!ReadAll(riffHeader, sizeof(riffHeader))) {
     return false;
   }
 
   static_assert(sizeof(uint32_t) * 3 <= RIFF_INITIAL_SIZE,
                 "Reads would overflow riffHeader buffer.");
@@ -357,17 +353,17 @@ WaveReader::LoadRIFFChunk()
 bool
 WaveReader::LoadFormatChunk(uint32_t aChunkSize)
 {
   uint32_t rate, channels, frameSize, sampleFormat;
   char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
   const char* p = waveFormat;
 
   // RIFF chunks are always word (two byte) aligned.
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "LoadFormatChunk called with unaligned resource");
 
   if (!ReadAll(waveFormat, sizeof(waveFormat))) {
     return false;
   }
 
   static_assert(sizeof(uint16_t) +
                 sizeof(uint16_t) +
@@ -419,17 +415,17 @@ WaveReader::LoadFormatChunk(uint32_t aCh
       nsAutoArrayPtr<char> chunkExtension(new char[extra]);
       if (!ReadAll(chunkExtension.get(), extra)) {
         return false;
       }
     }
   }
 
   // RIFF chunks are always word (two byte) aligned.
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "LoadFormatChunk left resource unaligned");
 
   // Make sure metadata is fairly sane.  The rate check is fairly arbitrary,
   // but the channels check is intentionally limited to mono or stereo
   // when the media is intended for direct playback because that's what the
   // audio backend currently supports.
   unsigned int actualFrameSize = (sampleFormat == 8 ? 1 : 2) * channels;
   if (rate < 100 || rate > 96000 ||
@@ -453,20 +449,20 @@ WaveReader::LoadFormatChunk(uint32_t aCh
   }
   return true;
 }
 
 bool
 WaveReader::FindDataOffset(uint32_t aChunkSize)
 {
   // RIFF chunks are always word (two byte) aligned.
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "FindDataOffset called with unaligned resource");
 
-  int64_t offset = mDecoder->GetResource()->Tell();
+  int64_t offset = mResource.Tell();
   if (offset <= 0 || offset > UINT32_MAX) {
     NS_WARNING("PCM data offset out of range");
     return false;
   }
 
   ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor());
   mWaveLength = aChunkSize;
   mWavePCMOffset = uint32_t(offset);
@@ -507,25 +503,25 @@ WaveReader::GetDataLength()
     length = std::min(dataLength, length);
   }
   return length;
 }
 
 int64_t
 WaveReader::GetPosition()
 {
-  return mDecoder->GetResource()->Tell();
+  return mResource.Tell();
 }
 
 bool
 WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize)
 {
   MOZ_ASSERT(aChunk, "Must have aChunk");
   MOZ_ASSERT(aChunkSize, "Must have aChunkSize");
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "GetNextChunk called with unaligned resource");
 
   char chunkHeader[CHUNK_HEADER_SIZE];
   const char* p = chunkHeader;
 
   if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
     return false;
   }
@@ -538,17 +534,17 @@ WaveReader::GetNextChunk(uint32_t* aChun
   return true;
 }
 
 bool
 WaveReader::LoadListChunk(uint32_t aChunkSize,
                           nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
 {
   // List chunks are always word (two byte) aligned.
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "LoadListChunk called with unaligned resource");
 
   static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
   static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char),
                 "MAX_CHUNK_SIZE too large for enumerator.");
 
   if (aChunkSize > MAX_CHUNK_SIZE || aChunkSize < 4) {
     return false;
@@ -614,17 +610,17 @@ WaveReader::LoadListChunk(uint32_t aChun
 
   return true;
 }
 
 bool
 WaveReader::LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
 {
   // Chunks are always word (two byte) aligned.
-  MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
+  MOZ_ASSERT(mResource.Tell() % 2 == 0,
              "LoadAllChunks called with unaligned resource");
 
   bool loadFormatChunk = false;
   bool findDataOffset = false;
 
   for (;;) {
     static const unsigned int CHUNK_HEADER_SIZE = 8;
     char chunkHeader[CHUNK_HEADER_SIZE];
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -2,16 +2,18 @@
 /* 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/. */
 #if !defined(WaveReader_h_)
 #define WaveReader_h_
 
 #include "MediaDecoderReader.h"
+#include "MediaResource.h"
+
 #include "mozilla/dom/HTMLMediaElement.h"
 
 namespace mozilla {
 
 class WaveReader : public MediaDecoderReader
 {
 public:
   explicit WaveReader(AbstractMediaDecoder* aDecoder);
@@ -93,13 +95,15 @@ private:
 
   // Size of PCM data stored in the WAVE as reported by the data chunk in
   // the media.
   int64_t mWaveLength;
 
   // Start offset of the PCM data in the media stream.  Extends mWaveLength
   // bytes.
   int64_t mWavePCMOffset;
+
+  MediaResourceIndex mResource;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -1,17 +1,16 @@
 /* -*- 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 "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
-#include "MediaResource.h"
 #include "SoftwareWebMVideoDecoder.h"
 #include "WebMReader.h"
 #include "WebMBufferedParser.h"
 #include "gfx2DGlue.h"
 #include "Layers.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 
@@ -49,57 +48,43 @@ extern PRLogModuleInfo* gMediaDecoderLog
 PRLogModuleInfo* gNesteggLog;
 
 // Functions for reading and seeking using MediaResource required for
 // nestegg_io. The 'user data' passed to these functions is the
 // decoder from which the media resource is obtained.
 static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
 {
   MOZ_ASSERT(aUserData);
-  AbstractMediaDecoder* decoder =
-    reinterpret_cast<AbstractMediaDecoder*>(aUserData);
-  MediaResource* resource = decoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
+  MediaResourceIndex* resource =
+    reinterpret_cast<MediaResourceIndex*>(aUserData);
 
   nsresult rv = NS_OK;
-  bool eof = false;
+  uint32_t bytes = 0;
 
-  char *p = static_cast<char *>(aBuffer);
-  while (NS_SUCCEEDED(rv) && aLength > 0) {
-    uint32_t bytes = 0;
-    rv = resource->Read(p, aLength, &bytes);
-    if (bytes == 0) {
-      eof = true;
-      break;
-    }
-    aLength -= bytes;
-    p += bytes;
-  }
+  rv = resource->Read(static_cast<char *>(aBuffer), aLength, &bytes);
+
+  bool eof = !bytes;
 
   return NS_FAILED(rv) ? -1 : eof ? 0 : 1;
 }
 
 static int webm_seek(int64_t aOffset, int aWhence, void *aUserData)
 {
   MOZ_ASSERT(aUserData);
-  AbstractMediaDecoder* decoder =
-    reinterpret_cast<AbstractMediaDecoder*>(aUserData);
-  MediaResource* resource = decoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
+  MediaResourceIndex* resource =
+    reinterpret_cast<MediaResourceIndex*>(aUserData);
   nsresult rv = resource->Seek(aWhence, aOffset);
   return NS_SUCCEEDED(rv) ? 0 : -1;
 }
 
 static int64_t webm_tell(void *aUserData)
 {
   MOZ_ASSERT(aUserData);
-  AbstractMediaDecoder* decoder =
-    reinterpret_cast<AbstractMediaDecoder*>(aUserData);
-  MediaResource* resource = decoder->GetResource();
-  NS_ASSERTION(resource, "Decoder has no media resource");
+  MediaResourceIndex* resource =
+    reinterpret_cast<MediaResourceIndex*>(aUserData);
   return resource->Tell();
 }
 
 static void webm_log(nestegg * context,
                      unsigned int severity,
                      char const * format, ...)
 {
   if (!MOZ_LOG_TEST(gNesteggLog, LogLevel::Debug)) {
@@ -153,16 +138,17 @@ WebMReader::WebMReader(AbstractMediaDeco
   , mAudioFrames(0)
   , mSeekPreroll(0)
   , mLastVideoFrameTime(0)
   , mAudioCodec(-1)
   , mVideoCodec(-1)
   , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
   , mHasVideo(false)
   , mHasAudio(false)
+  , mResource(aDecoder->GetResource())
 {
   MOZ_COUNT_CTOR(WebMReader);
   if (!gNesteggLog) {
     gNesteggLog = PR_NewLogModule("Nestegg");
   }
 
 #if defined(MOZ_PDM_VPX)
   sIsIntelDecoderEnabled = Preferences::GetBool("media.webm.intel_decoder.enabled", false);
@@ -297,17 +283,17 @@ WebMReader::RetrieveWebMMetadata(MediaIn
   // queue that TrackBuffer uses. We should be able to fix this when we do
   // bug 1148234.
   MOZ_ASSERT(mDecoder->OnDecodeTaskQueue());
 
   nestegg_io io;
   io.read = webm_read;
   io.seek = webm_seek;
   io.tell = webm_tell;
-  io.userdata = mDecoder;
+  io.userdata = &mResource;
   int64_t maxOffset = mDecoder->HasInitializationData() ?
     mBufferedState->GetInitEndOffset() : -1;
   int r = nestegg_init(&mContext, io, &webm_log, maxOffset);
   if (r == -1) {
     return NS_ERROR_FAILURE;
   }
 
   uint64_t duration = 0;
@@ -630,17 +616,17 @@ WebMReader::DemuxPacket()
     if (mVideoCodec == NESTEGG_CODEC_VP8) {
       vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), data, length, &si);
     } else if (mVideoCodec == NESTEGG_CODEC_VP9) {
       vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), data, length, &si);
     }
     isKeyframe = si.is_kf;
   }
 
-  int64_t offset = mDecoder->GetResource()->Tell();
+  int64_t offset = mResource.Tell();
   nsRefPtr<NesteggPacketHolder> holder = new NesteggPacketHolder();
   if (!holder->Init(packet, offset, track, isKeyframe)) {
     return nullptr;
   }
 
   return holder;
 }
 
@@ -850,17 +836,18 @@ media::TimeIntervals WebMReader::GetBuff
   }
 
   return buffered;
 }
 
 void WebMReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
 {
   MOZ_ASSERT(OnTaskQueue());
-  nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
+  nsRefPtr<MediaByteBuffer> bytes =
+    mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
   NS_ENSURE_TRUE_VOID(bytes);
   mBufferedState->NotifyDataArrived(bytes->Elements(), aLength, aOffset);
 }
 
 int64_t WebMReader::GetEvictionOffset(double aTime)
 {
   int64_t offset;
   if (!mBufferedState->GetOffsetForTime(aTime * NS_PER_S, &offset)) {
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(WebMReader_h_)
 #define WebMReader_h_
 
 #include <stdint.h>
 
 #include "FlushableTaskQueue.h"
 #include "MediaDecoderReader.h"
+#include "MediaResource.h"
 #include "PlatformDecoderModule.h"
 #include "nsAutoRef.h"
 #include "nestegg/nestegg.h"
 
 #define VPX_DONT_DEFINE_STDINT_TYPES
 #include "vpx/vpx_codec.h"
 
 #include "mozilla/layers/LayersTypes.h"
@@ -214,13 +215,15 @@ private:
 
   // For hardware video decoding.
   nsRefPtr<FlushableTaskQueue> mVideoTaskQueue;
 
   // Booleans to indicate if we have audio and/or video data
   bool mHasVideo;
   bool mHasAudio;
 
+  MediaResourceIndex mResource;
+
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -125,17 +125,19 @@ MediaEngineWebRTC::EnumerateVideoDevices
   ScopedCustomReleasePtr<webrtc::ViEBase> ptrViEBase;
   ScopedCustomReleasePtr<webrtc::ViECapture> ptrViECapture;
   webrtc::Config configSet;
   webrtc::VideoEngine *videoEngine = nullptr;
   bool *videoEngineInit = nullptr;
 
 #ifdef MOZ_WIDGET_ANDROID
   // get the JVM
-  JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
+  JavaVM* jvm;
+  JNIEnv* const env = jni::GetEnvForThread();
+  MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
 
   if (webrtc::VideoEngine::SetAndroidObjects(jvm) != 0) {
     LOG(("VieCapture:SetAndroidObjects Failed"));
     return;
   }
 #endif
 
   switch (aMediaSource) {
@@ -297,18 +299,19 @@ MediaEngineWebRTC::EnumerateAudioDevices
     aASources->AppendElement(audioCaptureSource);
     return;
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   jobject context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
 
   // get the JVM
-  JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
-  JNIEnv *env = GetJNIForThread();
+  JavaVM* jvm;
+  JNIEnv* const env = jni::GetEnvForThread();
+  MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
 
   if (webrtc::VoiceEngine::SetAndroidObjects(jvm, env, (void*)context) != 0) {
     LOG(("VoiceEngine:SetAndroidObjects Failed"));
     return;
   }
 #endif
 
   if (!mVoiceEngine) {
--- a/dom/plugins/base/PluginPRLibrary.cpp
+++ b/dom/plugins/base/PluginPRLibrary.cpp
@@ -32,17 +32,17 @@ static int gNotOptimized;
 using namespace mozilla::layers;
 
 namespace mozilla {
 #ifdef MOZ_WIDGET_ANDROID
 nsresult
 PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs,
 			       NPPluginFuncs* pFuncs, NPError* error)
 {
-  JNIEnv* env = GetJNIForThread();
+  JNIEnv* env = jni::GetEnvForThread();
 
   mozilla::AutoLocalJNIFrame jniFrame(env);
 
   if (mNP_Initialize) {
     *error = mNP_Initialize(bFuncs, pFuncs, env);
   } else {
     NP_InitializeFunc pfNP_Initialize = (NP_InitializeFunc)
       PR_FindFunctionSymbol(mLibrary, "NP_Initialize");
--- a/dom/plugins/base/android/ANPAudio.cpp
+++ b/dom/plugins/base/android/ANPAudio.cpp
@@ -119,17 +119,17 @@ public:
   ANPAudioTrack* mTrack;
 };
 
 NS_IMETHODIMP
 AudioRunnable::Run()
 {
   PR_SetCurrentThreadName("Android Audio");
 
-  JNIEnv* jenv = GetJNIForThread();
+  JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
 
   mozilla::AutoLocalJNIFrame autoFrame(jenv, 2);
 
   jbyteArray bytearray = jenv->NewByteArray(mTrack->bufferSize);
   if (!bytearray) {
     LOG("AudioRunnable:: Run.  Could not create bytearray");
     return NS_ERROR_FAILURE;
   }
@@ -202,17 +202,17 @@ anp_audio_newTrack(uint32_t sampleRate, 
                    ANPAudioCallbackProc proc,
                    void* user)
 {
   ANPAudioTrack *s = new ANPAudioTrack();
   if (s == nullptr) {
     return nullptr;
   }
 
-  JNIEnv *jenv = GetJNIForThread();
+  JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
 
   s->at_class = init_jni_bindings(jenv);
   s->rate = sampleRate;
   s->channels = channelCount;
   s->bufferSize = s->rate * s->channels;
   s->isStopped = true;
   s->keepGoing = false;
   s->user = user;
@@ -298,17 +298,17 @@ anp_audio_start(ANPAudioTrack* s)
     return;
   }
 
   if (s->keepGoing) {
     // we are already playing.  Ignore.
     return;
   }
 
-  JNIEnv *jenv = GetJNIForThread();
+  JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
 
   mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
   jenv->CallVoidMethod(s->output_unit, at.play);
 
   if (autoFrame.CheckForException()) {
     jenv->DeleteGlobalRef(s->at_class);
     delete s;
     return;
@@ -326,31 +326,31 @@ anp_audio_start(ANPAudioTrack* s)
 
 void
 anp_audio_pause(ANPAudioTrack* s)
 {
   if (s == nullptr || s->output_unit == nullptr) {
     return;
   }
 
-  JNIEnv *jenv = GetJNIForThread();
+  JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
 
   mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
   jenv->CallVoidMethod(s->output_unit, at.pause);
 }
 
 void
 anp_audio_stop(ANPAudioTrack* s)
 {
   if (s == nullptr || s->output_unit == nullptr) {
     return;
   }
 
   s->isStopped = true;
-  JNIEnv *jenv = GetJNIForThread();
+  JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
 
   mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
   jenv->CallVoidMethod(s->output_unit, at.stop);
 }
 
 bool
 anp_audio_isStopped(ANPAudioTrack* s)
 {
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -1388,17 +1388,17 @@ LayoutDeviceRect nsPluginInstanceOwner::
 bool nsPluginInstanceOwner::AddPluginView(const LayoutDeviceRect& aRect /* = LayoutDeviceRect(0, 0, 0, 0) */)
 {
   if (!mJavaView) {
     mJavaView = mInstance->GetJavaSurface();
 
     if (!mJavaView)
       return false;
 
-    mJavaView = (void*)AndroidBridge::GetJNIEnv()->NewGlobalRef((jobject)mJavaView);
+    mJavaView = (void*)jni::GetGeckoThreadEnv()->NewGlobalRef((jobject)mJavaView);
   }
 
   if (AndroidBridge::Bridge())
     AndroidBridge::Bridge()->AddPluginView((jobject)mJavaView, aRect, mFullScreen);
 
   if (mFullScreen)
     sFullScreenInstance = this;
 
@@ -1407,17 +1407,17 @@ bool nsPluginInstanceOwner::AddPluginVie
 
 void nsPluginInstanceOwner::RemovePluginView()
 {
   if (!mInstance || !mJavaView)
     return;
 
   widget::GeckoAppShell::RemovePluginView(
       jni::Object::Ref::From(jobject(mJavaView)), mFullScreen);
-  AndroidBridge::GetJNIEnv()->DeleteGlobalRef((jobject)mJavaView);
+  jni::GetGeckoThreadEnv()->DeleteGlobalRef((jobject)mJavaView);
   mJavaView = nullptr;
 
   if (mFullScreen)
     sFullScreenInstance = nullptr;
 }
 
 void
 nsPluginInstanceOwner::GetVideos(nsTArray<nsNPAPIPluginInstance::VideoInfo*>& aVideos)
@@ -1488,17 +1488,17 @@ void nsPluginInstanceOwner::ExitFullScre
   mInstance->NotifyFullScreen(mFullScreen);
 
   // This will cause Paint() to be called, which is where
   // we normally add/update views and layers
   Invalidate();
 }
 
 void nsPluginInstanceOwner::ExitFullScreen(jobject view) {
-  JNIEnv* env = AndroidBridge::GetJNIEnv();
+  JNIEnv* env = jni::GetGeckoThreadEnv();
 
   if (sFullScreenInstance && sFullScreenInstance->mInstance &&
       env->IsSameObject(view, (jobject)sFullScreenInstance->mInstance->GetJavaSurface())) {
     sFullScreenInstance->ExitFullScreen();
   }
 }
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/security/SRICheck.cpp
@@ -0,0 +1,295 @@
+/* -*- 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 "SRICheck.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIDocument.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsWhitespaceTokenizer.h"
+
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
+#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
+#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Returns whether or not the sub-resource about to be loaded is eligible
+ * for integrity checks. If it's not, the checks will be skipped and the
+ * sub-resource will be loaded.
+ */
+static nsresult
+IsEligible(nsIURI* aRequestURI, const CORSMode aCORSMode,
+           const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aRequestURI);
+  NS_ENSURE_ARG_POINTER(aDocument);
+
+  nsAutoCString requestSpec;
+  nsresult rv = aRequestURI->GetSpec(requestSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
+
+  // Was the sub-resource loaded via CORS?
+  if (aCORSMode != CORS_NONE) {
+    SRILOG(("SRICheck::IsEligible, CORS mode"));
+    return NS_OK;
+  }
+
+  // Is the sub-resource same-origin?
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(),
+                                           aRequestURI, false))) {
+    SRILOG(("SRICheck::IsEligible, same-origin"));
+    return NS_OK;
+  }
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString documentURI;
+    aDocument->GetDocumentURI()->GetAsciiSpec(documentURI);
+    // documentURI will be empty if GetAsciiSpec failed
+    SRILOG(("SRICheck::IsEligible, NOT same origin: documentURI=%s; requestURI=%s",
+            documentURI.get(), requestSpec.get()));
+  }
+
+  const char16_t* params[] = { requestSpecUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "IneligibleResource",
+                                  params, ArrayLength(params));
+  return NS_ERROR_SRI_NOT_ELIGIBLE;
+}
+
+/**
+ * Compute the hash of a sub-resource and compare it with the expected
+ * value.
+ */
+static nsresult
+VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
+           uint32_t aStringLen, const uint8_t* aString,
+           const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aString);
+  NS_ENSURE_ARG_POINTER(aDocument);
+
+  nsAutoCString base64Hash;
+  aMetadata.GetHash(aHashIndex, &base64Hash);
+  SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
+
+  nsAutoCString binaryHash;
+  if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityBase64");
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  uint32_t hashLength;
+  int8_t hashType;
+  aMetadata.GetHashType(&hashType, &hashLength);
+  if (binaryHash.Length() != hashLength) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityLength");
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsICryptoHash> cryptoHash =
+    do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cryptoHash->Init(hashType);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cryptoHash->Update(aString, aStringLen);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString computedHash;
+  rv = cryptoHash->Finish(false, computedHash);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!binaryHash.Equals(computedHash)) {
+    SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
+  return NS_OK;
+}
+
+/* static */ nsresult
+SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
+                            const nsIDocument* aDocument,
+                            SRIMetadata* outMetadata)
+{
+  NS_ENSURE_ARG_POINTER(outMetadata);
+  NS_ENSURE_ARG_POINTER(aDocument);
+  MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
+
+  if (!Preferences::GetBool("security.sri.enable", false)) {
+    SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
+    return NS_ERROR_SRI_DISABLED;
+  }
+
+  // put a reasonable bound on the length of the metadata
+  NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
+  if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
+    metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
+  }
+  MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
+
+  // the integrity attribute is a list of whitespace-separated hashes
+  // and options so we need to look at them one by one and pick the
+  // strongest (valid) one
+  nsCWhitespaceTokenizer tokenizer(metadataList);
+  nsAutoCString token;
+  for (uint32_t i=0; tokenizer.hasMoreTokens() &&
+         i < SRICheck::MAX_METADATA_TOKENS; ++i) {
+    token = tokenizer.nextToken();
+
+    SRIMetadata metadata(token);
+    if (metadata.IsMalformed()) {
+      NS_ConvertUTF8toUTF16 tokenUTF16(token);
+      const char16_t* params[] = { tokenUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                      aDocument,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "MalformedIntegrityURI",
+                                      params, ArrayLength(params));
+    } else if (!metadata.IsAlgorithmSupported()) {
+      nsAutoCString alg;
+      metadata.GetAlgorithm(&alg);
+      NS_ConvertUTF8toUTF16 algUTF16(alg);
+      const char16_t* params[] = { algUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                      aDocument,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "UnsupportedHashAlg",
+                                      params, ArrayLength(params));
+    }
+
+    nsAutoCString alg1, alg2;
+    if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+      outMetadata->GetAlgorithm(&alg1);
+      metadata.GetAlgorithm(&alg2);
+    }
+    if (*outMetadata == metadata) {
+      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
+              alg1.get(), alg2.get()));
+      *outMetadata += metadata; // add new hash to strongest metadata
+    } else if (*outMetadata < metadata) {
+      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
+              alg1.get(), alg2.get()));
+      *outMetadata = metadata; // replace strongest metadata with current
+    }
+  }
+
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    if (outMetadata->IsValid()) {
+      nsAutoCString alg;
+      outMetadata->GetAlgorithm(&alg);
+      SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
+    } else if (outMetadata->IsEmpty()) {
+      SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
+    } else {
+      SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
+    }
+  }
+  return NS_OK;
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIURI* aRequestURI,
+                          const CORSMode aCORSMode,
+                          const nsAString& aString,
+                          const nsIDocument* aDocument)
+{
+  NS_ConvertUTF16toUTF8 utf8Hash(aString);
+  return VerifyIntegrity(aMetadata, aRequestURI, aCORSMode, utf8Hash.Length(),
+                         (uint8_t*)utf8Hash.get(), aDocument);
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIURI* aRequestURI,
+                          const CORSMode aCORSMode,
+                          uint32_t aStringLen,
+                          const uint8_t* aString,
+                          const nsIDocument* aDocument)
+{
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString requestURL;
+    aRequestURI->GetAsciiSpec(requestURL);
+    // requestURL will be empty if GetAsciiSpec fails
+    SRILOG(("SRICheck::VerifyIntegrity, url=%s (length=%u)",
+            requestURL.get(), aStringLen));
+  }
+
+  MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
+
+  // IntegrityMetadata() checks this and returns "no metadata" if
+  // it's disabled so we should never make it this far
+  MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
+
+  if (NS_FAILED(IsEligible(aRequestURI, aCORSMode, aDocument))) {
+    return NS_OK; // ignore non-CORS resources for forward-compatibility
+  }
+  if (!aMetadata.IsValid()) {
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "NoValidMetadata");
+    return NS_OK; // ignore invalid metadata for forward-compatibility
+  }
+
+  for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
+    if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
+                                aString, aDocument))) {
+      return NS_OK; // stop at the first valid hash
+    }
+  }
+
+  nsAutoCString alg;
+  aMetadata.GetAlgorithm(&alg);
+  NS_ConvertUTF8toUTF16 algUTF16(alg);
+  const char16_t* params[] = { algUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "IntegrityMismatch",
+                                  params, ArrayLength(params));
+  return NS_ERROR_SRI_CORRUPT;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/security/SRICheck.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SRICheck_h
+#define mozilla_dom_SRICheck_h
+
+#include "mozilla/CORSMode.h"
+#include "nsCOMPtr.h"
+#include "SRIMetadata.h"
+
+class nsIDocument;
+class nsIHttpChannel;
+class nsIScriptSecurityManager;
+class nsIStreamLoader;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class SRICheck final
+{
+public:
+  static const uint32_t MAX_METADATA_LENGTH = 24*1024;
+  static const uint32_t MAX_METADATA_TOKENS = 512;
+
+  /**
+   * Parse the multiple hashes specified in the integrity attribute and
+   * return the strongest supported hash.
+   */
+  static nsresult IntegrityMetadata(const nsAString& aMetadataList,
+                                    const nsIDocument* aDocument,
+                                    SRIMetadata* outMetadata);
+
+  /**
+   * Process the integrity attribute of the element.  A result of false
+   * must prevent the resource from loading.
+   */
+  static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
+                                  nsIURI* aRequestURI,
+                                  const CORSMode aCORSMode,
+                                  const nsAString& aString,
+                                  const nsIDocument* aDocument);
+
+  /**
+   * Process the integrity attribute of the element.  A result of false
+   * must prevent the resource from loading.
+   */
+  static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
+                                  nsIURI* aRequestURI,
+                                  const CORSMode aCORSMode,
+                                  uint32_t aStringLen,
+                                  const uint8_t* aString,
+                                  const nsIDocument* aDocument);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRICheck_h
new file mode 100644
--- /dev/null
+++ b/dom/security/SRIMetadata.cpp
@@ -0,0 +1,172 @@
+/* -*- 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 "SRIMetadata.h"
+
+#include "hasht.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Logging.h"
+#include "nsICryptoHash.h"
+
+static PRLogModuleInfo*
+GetSriMetadataLog()
+{
+  static PRLogModuleInfo *gSriMetadataPRLog;
+  if (!gSriMetadataPRLog) {
+    gSriMetadataPRLog = PR_NewLogModule("SRIMetadata");
+  }
+  return gSriMetadataPRLog;
+}
+
+#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
+#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+SRIMetadata::SRIMetadata(const nsACString& aToken)
+  : mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
+{
+  MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
+
+  SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
+                  PromiseFlatCString(aToken).get()));
+
+  int32_t hyphen = aToken.FindChar('-');
+  if (hyphen == -1) {
+    SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
+    return; // invalid metadata
+  }
+
+  // split the token into its components
+  mAlgorithm = Substring(aToken, 0, hyphen);
+  uint32_t hashStart = hyphen + 1;
+  if (hashStart >= aToken.Length()) {
+    SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
+    return; // invalid metadata
+  }
+  int32_t question = aToken.FindChar('?');
+  if (question == -1) {
+    mHashes.AppendElement(Substring(aToken, hashStart,
+                                    aToken.Length() - hashStart));
+  } else {
+    MOZ_ASSERT(question > 0);
+    if (static_cast<uint32_t>(question) <= hashStart) {
+      SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
+      return; // invalid metadata
+    }
+    mHashes.AppendElement(Substring(aToken, hashStart,
+                                    question - hashStart));
+  }
+
+  if (mAlgorithm.EqualsLiteral("sha256")) {
+    mAlgorithmType = nsICryptoHash::SHA256;
+  } else if (mAlgorithm.EqualsLiteral("sha384")) {
+    mAlgorithmType = nsICryptoHash::SHA384;
+  } else if (mAlgorithm.EqualsLiteral("sha512")) {
+    mAlgorithmType = nsICryptoHash::SHA512;
+  }
+
+  SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
+                  mHashes[0].get(), mAlgorithm.get()));
+}
+
+bool
+SRIMetadata::operator<(const SRIMetadata& aOther) const
+{
+  static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
+                "We rely on the order indicating relative alg strength");
+  static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
+                "We rely on the order indicating relative alg strength");
+  MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+             mAlgorithmType == nsICryptoHash::SHA256 ||
+             mAlgorithmType == nsICryptoHash::SHA384 ||
+             mAlgorithmType == nsICryptoHash::SHA512);
+  MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA512);
+
+  if (mEmpty) {
+    SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
+    return true; // anything beats the empty metadata (incl. invalid ones)
+  }
+
+  SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
+                  mAlgorithmType, aOther.mAlgorithmType));
+  return (mAlgorithmType < aOther.mAlgorithmType);
+}
+
+bool
+SRIMetadata::operator>(const SRIMetadata& aOther) const
+{
+  MOZ_ASSERT(false);
+  return false;
+}
+
+SRIMetadata&
+SRIMetadata::operator+=(const SRIMetadata& aOther)
+{
+  MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
+  MOZ_ASSERT(aOther.IsValid() && IsValid());
+  MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
+
+  // We only pull in the first element of the other metadata
+  MOZ_ASSERT(aOther.mHashes.Length() == 1);
+  if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
+    SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
+                    mAlgorithm.get(), mHashes.Length()));
+    mHashes.AppendElement(aOther.mHashes[0]);
+  }
+
+  MOZ_ASSERT(mHashes.Length() > 1);
+  MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
+  return *this;
+}
+
+bool
+SRIMetadata::operator==(const SRIMetadata& aOther) const
+{
+  if (IsEmpty() || !IsValid()) {
+    return false;
+  }
+  return mAlgorithmType == aOther.mAlgorithmType;
+}
+
+void
+SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
+{
+  MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
+  if (NS_WARN_IF(aIndex >= mHashes.Length())) {
+    *outHash = nullptr;
+    return;
+  }
+  *outHash = mHashes[aIndex];
+}
+
+void
+SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
+{
+  // these constants are defined in security/nss/lib/util/hasht.h and
+  // netwerk/base/public/nsICryptoHash.idl
+  switch (mAlgorithmType) {
+    case nsICryptoHash::SHA256:
+      *outLength = SHA256_LENGTH;
+      break;
+    case nsICryptoHash::SHA384:
+      *outLength = SHA384_LENGTH;
+      break;
+    case nsICryptoHash::SHA512:
+      *outLength = SHA512_LENGTH;
+      break;
+    default:
+      *outLength = 0;
+  }
+  *outType = mAlgorithmType;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/security/SRIMetadata.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SRIMetadata_h
+#define mozilla_dom_SRIMetadata_h
+
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class SRIMetadata final
+{
+public:
+  static const uint32_t MAX_ALTERNATE_HASHES = 256;
+  static const int8_t UNKNOWN_ALGORITHM = -1;
+
+  /**
+   * Create an empty metadata object.
+   */
+  SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
+
+  /**
+   * Split a string token into the components of an SRI metadata
+   * attribute.
+   */
+  explicit SRIMetadata(const nsACString& aToken);
+
+  /**
+   * Returns true when this object's hash algorithm is weaker than the
+   * other object's hash algorithm.
+   */
+  bool operator<(const SRIMetadata& aOther) const;
+
+  /**
+   * Not implemented. Should not be used.
+   */
+  bool operator>(const SRIMetadata& aOther) const;
+
+  /**
+   * Add another metadata's hash to this one.
+   */
+  SRIMetadata& operator+=(const SRIMetadata& aOther);
+
+  /**
+   * Returns true when the two metadata use the same hash algorithm.
+   */
+  bool operator==(const SRIMetadata& aOther) const;
+
+  bool IsEmpty() const { return mEmpty; }
+  bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
+  bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
+  bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
+
+  uint32_t HashCount() const { return mHashes.Length(); }
+  void GetHash(uint32_t aIndex, nsCString* outHash) const;
+  void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
+  void GetHashType(int8_t* outType, uint32_t* outLength) const;
+
+private:
+  nsTArray<nsCString> mHashes;
+  nsCString mAlgorithm;
+  int8_t mAlgorithmType;
+  bool mEmpty;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRIMetadata_h
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -7,31 +7,35 @@
 TEST_DIRS += ['test']
 
 EXPORTS.mozilla.dom += [
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
+    'SRICheck.h',
+    'SRIMetadata.h',
 ]
 
 EXPORTS += [
     'nsContentSecurityManager.h',
     'nsCORSListenerProxy.h'
 ]
 
 UNIFIED_SOURCES += [
     'nsContentSecurityManager.cpp',
     'nsCORSListenerProxy.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
     'nsMixedContentBlocker.cpp',
+    'SRICheck.cpp',
+    'SRIMetadata.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/caps',
     '/netwerk/base',
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -11,16 +11,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 GeckoCppUnitTests([
      'TestCSPParser',
 ])
 
 MOCHITEST_MANIFESTS += [
     'cors/mochitest.ini',
     'csp/mochitest.ini',
     'mixedcontentblocker/mochitest.ini',
+    'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'csp/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'csp/browser.ini',
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/iframe_script_crossdomain.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <script type="application/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" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  window.hasCORSLoaded = false;
+  window.hasNonCORSLoaded = false;
+
+  function good_nonsriLoaded() {
+    ok(true, "Non-eligible non-SRI resource was loaded correctly.");
+  }
+  function bad_nonsriBlocked() {
+    ok(false, "Non-eligible non-SRI resources should be loaded!");
+  }
+
+  function good_nonCORSInvalidLoaded() {
+    ok(true, "A non-CORS resource with invalid metadata was correctly loaded.");
+  }
+  function bad_nonCORSInvalidBlocked() {
+    ok(false, "Non-CORS resources with invalid metadata should be loaded!");
+  }
+
+  window.onerrorCalled = false;
+  window.onloadCalled = false;
+
+  function bad_onloadCalled() {
+    window.onloadCalled = true;
+  }
+
+  function good_onerrorCalled() {
+    window.onerrorCalled = true;
+  }
+
+  window.onload = function() {
+    SimpleTest.finish()
+  }
+</script>
+
+<!-- cors-enabled. should be loaded -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
+        crossorigin=""
+        integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q=="></script>
+
+<!-- not cors-enabled. should be blocked -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain2.js"
+        crossorigin="anonymous"
+        integrity="sha256-ntgU2U1xv7HfK1XWMTSWz6vJkyVtGzMrIAxQkux1I94="
+        onload="bad_onloadCalled()"
+        onerror="good_onerrorCalled()"></script>
+
+<!-- non-cors but not actually using SRI. should trigger onload -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain3.js"
+        integrity="    "
+        onload="good_nonsriLoaded()"
+        onerror="bad_nonsriBlocked()"></script>
+
+<!-- non-cors with invalid metadata. should trigger onload -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain4.js"
+        integrity="sha256-bogus"
+        onload="good_nonCORSInvalidLoaded()"
+        onerror="bad_nonCORSInvalidBlocked()"></script>
+
+<script>
+  ok(window.hasCORSLoaded, "CORS-enabled resource with a correct hash");
+  ok(!window.hasNonCORSLoaded, "Correct hash, but non-CORS, should be blocked");
+  ok(!window.onloadCalled, "Failed loads should not call onload when they're cross-domain");
+  ok(window.onerrorCalled, "Failed loads should call onerror when they're cross-domain");
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/iframe_script_sameorigin.html
@@ -0,0 +1,209 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <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();
+    window.onload = function() {
+      SimpleTest.finish();
+    }
+  </script>
+  <script>
+    function good_correctHashLoaded() {
+      ok(true, "A script was correctly loaded when integrity matched")
+    }
+    function bad_correctHashBlocked() {
+      ok(false, "We should load scripts with hashes that match!");
+    }
+
+    function good_correctHashArrayLoaded() {
+      ok(true, "A script was correctly loaded when one of the hashes in the integrity attribute matched")
+    }
+    function bad_correctHashArrayBlocked() {
+      ok(false, "We should load scripts with at least one hash that match!");
+    }
+
+    function good_emptyIntegrityLoaded() {
+      ok(true, "A script was correctly loaded when the integrity attribute was empty")
+    }
+    function bad_emptyIntegrityBlocked() {
+      ok(false, "We should load scripts with empty integrity attributes!");
+    }
+
+    function good_whitespaceIntegrityLoaded() {
+      ok(true, "A script was correctly loaded when the integrity attribute only contained whitespace")
+    }
+    function bad_whitespaceIntegrityBlocked() {
+      ok(false, "We should load scripts with integrity attributes containing only whitespace!");
+    }
+
+    function good_incorrectHashBlocked() {
+      ok(true, "A script was correctly blocked, because the hash digest was wrong");
+    }
+    function bad_incorrectHashLoaded() {
+      ok(false, "We should not load scripts with hashes that do not match the content!");
+    }
+
+    function good_incorrectHashArrayBlocked() {
+      ok(true, "A script was correctly blocked, because all the hashes were wrong");
+    }
+    function bad_incorrectHashArrayLoaded() {
+      ok(false, "We should not load scripts when none of the hashes match the content!");
+    }
+
+    function good_incorrectHashLengthBlocked() {
+      ok(true, "A script was correctly blocked, because the hash length was wrong");
+    }
+    function bad_incorrectHashLengthLoaded() {
+      ok(false, "We should not load scripts with hashes that don't have the right length!");
+    }
+
+    function bad_incorrectHashFunctionBlocked() {
+      ok(false, "We should load scripts with invalid/unsupported hash functions!");
+    }
+    function good_incorrectHashFunctionLoaded() {
+      ok(true, "A script was correctly loaded, despite the hash function being invalid/unsupported.");
+    }
+
+    function bad_missingHashFunctionBlocked() {
+      ok(false, "We should load scripts with missing hash functions!");
+    }
+    function good_missingHashFunctionLoaded() {
+      ok(true, "A script was correctly loaded, despite a missing hash function.");
+    }
+
+    function bad_missingHashValueBlocked() {
+      ok(false, "We should load scripts with missing hash digests!");
+    }
+    function good_missingHashValueLoaded() {
+      ok(true, "A script was correctly loaded, despite the missing hash digest.");
+    }
+
+    function good_401Blocked() {
+      ok(true, "A script was not loaded because of 401 response.");
+    }
+    function bad_401Loaded() {
+      ok(false, "We should nt load scripts with a 401 response!");
+    }
+
+    function good_valid302Loaded() {
+      ok(true, "A script was loaded successfully despite a 302 response.");
+    }
+    function bad_valid302Blocked() {
+      ok(false, "We should load scripts with a 302 response and the right hash!");
+    }
+
+    function good_invalid302Blocked() {
+      ok(true, "A script was blocked successfully after a 302 response.");
+    }
+    function bad_invalid302Loaded() {
+      ok(false, "We should not load scripts with a 302 response and the wrong hash!");
+    }
+  </script>
+</head>
+<body>
+  <!-- valid hash. should trigger onload -->
+  <!-- the hash value comes from running this command:
+       cat script.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
+  -->
+  <script src="script.js"
+          integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_correctHashBlocked()"
+          onload="good_correctHashLoaded()"></script>
+
+  <!-- valid sha512 hash. should trigger onload -->
+  <script src="script.js"
+          integrity="sha512-mzSqH+vC6qrXX46JX2WEZ0FtY/lGj/5+5yYCBlk0jfYHLm0vP6XgsURbq83mwMApsnwbDLXdgjp5J8E93GT6Mw==?ignore=this"
+          onerror="bad_correctHashBlocked()"
+          onload="good_correctHashLoaded()"></script>
+
+  <!-- one valid sha256 hash. should trigger onload -->
+  <script src="script.js"
+          integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_correctHashArrayBlocked()"
+          onload="good_correctHashArrayLoaded()"></script>
+
+  <!-- empty integrity. should trigger onload -->
+  <script src="script.js"
+          integrity=""
+          onerror="bad_emptyIntegrityBlocked()"
+          onload="good_emptyIntegrityLoaded()"></script>
+
+  <!-- whitespace integrity. should trigger onload -->
+  <script src="script.js"
+          integrity="  
+	
+"
+          onerror="bad_whitespaceIntegrityBlocked()"
+          onload="good_whitespaceIntegrityLoaded()"></script>
+
+  <!-- invalid sha256 hash but valid sha384 hash. should trigger onload -->
+  <script src="script.js"
+          integrity="sha256-bogus sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo?"
+          onerror="bad_correctHashBlocked()"
+          onload="good_correctHashLoaded()"></script>
+
+  <!-- valid sha256 and invalid sha384. should trigger onerror -->
+  <script src="script.js"
+          integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha384-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="good_incorrectHashLengthBlocked()"
+          onload="bad_incorrectHashLengthLoaded()"></script>
+
+  <!-- invalid hash. should trigger onerror -->
+  <script src="script.js"
+          integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="good_incorrectHashBlocked()"
+          onload="bad_incorrectHashLoaded()"></script>
+
+  <!-- invalid hashes. should trigger onerror -->
+  <script src="script.js"
+          integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-ZkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-zkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="good_incorrectHashBlocked()"
+          onload="bad_incorrectHashLoaded()"></script>
+
+  <!-- invalid hash function. should trigger onload -->
+  <script src="script.js"
+          integrity="rot13-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_incorrectHashFunctionBlocked()"
+          onload="good_incorrectHashFunctionLoaded()"></script>
+
+  <!-- missing hash function. should trigger onload -->
+  <script src="script.js"
+          integrity="RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_missingHashFunctionBlocked()"
+          onload="good_missingHashFunctionLoaded()"></script>
+
+  <!-- missing hash value. should trigger onload -->
+  <script src="script.js"
+          integrity="sha512-"
+          onerror="bad_missingHashValueBlocked()"
+          onload="good_missingHashValueLoaded()"></script>
+
+  <!-- 401 response. should trigger onerror -->
+  <script src="script_401.js"
+          integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="good_401Blocked()"
+          onload="bad_401Loaded()"></script>
+
+  <!-- valid sha256 after a redirection. should trigger onload -->
+  <script src="script_302.js"
+          integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_valid302Blocked()"
+          onload="good_valid302Loaded()"></script>
+
+  <!-- invalid sha256 after a redirection. should trigger onerror -->
+  <script src="script_302.js"
+          integrity="sha256-JSi74NSN8WQNr9syBGmNg2APJp9PnHUO5ioZo5hmIiQ="
+          onerror="good_invalid302Blocked()"
+          onload="bad_invalid302Loaded()"></script>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/iframe_sri_disabled.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <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();
+    window.onload = function() {
+      SimpleTest.finish();
+    }
+  </script>
+  <script>
+    function good_correctHashLoaded() {
+      ok(true, "A script was correctly loaded when integrity matched")
+    }
+    function bad_correctHashBlocked() {
+      ok(false, "We should load scripts with hashes that match!");
+    }
+
+    function good_incorrectHashLoaded() {
+      ok(true, "A script was correctly loaded despite the incorrect hash because SRI is disabled.");
+    }
+    function bad_incorrectHashBlocked() {
+      ok(false, "We should load scripts with hashes that do not match the content when SRI is disabled!");
+    }
+
+    function good_correctStyleHashLoaded() {
+      ok(true, "A stylesheet was correctly loaded when integrity matched")
+    }
+    function bad_correctStyleHashBlocked() {
+      ok(false, "We should load stylesheets with hashes that match!");
+    }
+
+    function good_incorrectStyleHashLoaded() {
+      ok(true, "A stylesheet was correctly loaded despite the incorrect hash because SRI is disabled.");
+    }
+    function bad_incorrectStyleHashBlocked() {
+      ok(false, "We should load stylesheets with hashes that do not match the content when SRI is disabled!");
+    }
+  </script>
+
+  <!-- valid sha256 hash. should trigger onload -->
+  <link rel="stylesheet" href="style1.css?disabled"
+        integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+        onerror="bad_correctStyleHashBlocked()"
+        onload="good_correctStyleHashLoaded()">
+
+  <!-- invalid sha256 hash. should trigger onerror -->
+  <link rel="stylesheet" href="style2.css?disabled"
+        integrity="sha256-bogus"
+        onerror="bad_incorrectStyleHashBlocked()"
+        onload="good_incorrectStyleHashLoaded()">
+</head>
+<body>
+  <!-- valid hash. should trigger onload -->
+  <script src="script.js"
+          integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_correctHashBlocked()"
+          onload="good_correctHashLoaded()"></script>
+
+  <!-- invalid hash. should trigger onerror -->
+  <script src="script.js"
+          integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+          onerror="bad_incorrectHashBlocked()"
+          onload="good_incorrectHashLoaded()"></script>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/iframe_style_sameorigin.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <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">
+    function check_styles() {
+      var redText = document.getElementById('red-text');
+      var blackText = document.getElementById('black-text');
+      var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
+      var blackTextColor = window.getComputedStyle(blackText, null).getPropertyValue('color');
+      ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
+      ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    window.onload = function() {
+      check_styles();
+      SimpleTest.finish();
+    }
+  </script>
+  <script>
+    function good_correctHashLoaded() {
+      ok(true, "A stylesheet was correctly loaded when integrity matched");
+    }
+    function bad_correctHashBlocked() {
+      ok(false, "We should load stylesheets with hashes that match!");
+    }
+
+    function good_emptyIntegrityLoaded() {
+      ok(true, "A stylesheet was correctly loaded when the integrity attribute was empty");
+    }
+    function bad_emptyIntegrityBlocked() {
+      ok(false, "We should load stylesheets with empty integrity attributes!");
+    }
+
+    function good_incorrectHashBlocked() {
+      ok(true, "A stylesheet was correctly blocked, because the hash digest was wrong");
+    }
+    function bad_incorrectHashLoaded() {
+      ok(false, "We should not load stylesheets with hashes that do not match the content!");
+    }
+  </script>
+
+  <!-- valid sha256 hash. should trigger onload -->
+  <link rel="stylesheet" href="style1.css"
+        integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+        onerror="bad_correctHashBlocked()"
+        onload="good_correctHashLoaded()">
+
+  <!-- empty metadata. should trigger onload -->
+  <link rel="stylesheet" href="style2.css"
+        integrity=""
+        onerror="bad_emptyIntegrityBlocked()"
+        onload="good_emptyIntegrityLoaded()">
+
+  <!-- invalid sha256 hash. should trigger onerror -->
+  <link rel="stylesheet" href="style3.css"
+        integrity="sha256-bogus"
+        onerror="good_incorrectHashBlocked()"
+        onload="bad_incorrectHashLoaded()">
+</head>
+<body>
+<p><span id="red-text">This should be red </span> and
+  <span id="black-text">this should stay black.</p>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/mochitest.ini
@@ -0,0 +1,30 @@
+[DEFAULT]
+support-files =
+  iframe_script_crossdomain.html
+  iframe_script_sameorigin.html
+  iframe_sri_disabled.html
+  iframe_style_sameorigin.html
+  script_crossdomain1.js
+  script_crossdomain1.js^headers^
+  script_crossdomain2.js
+  script_crossdomain3.js
+  script_crossdomain3.js^headers^
+  script_crossdomain4.js
+  script_crossdomain4.js^headers^
+  script.js
+  script.js^headers^
+  script_302.js
+  script_302.js^headers^
+  script_401.js
+  script_401.js^headers^
+  style1.css
+  style2.css
+  style3.css
+
+[test_script_sameorigin.html]
+
+[test_script_crossdomain.html]
+
+[test_sri_disabled.html]
+
+[test_style_sameorigin.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script.js
@@ -0,0 +1,1 @@
+var load=true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script.js^headers^
@@ -0,0 +1,1 @@
+Cache-control: public
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_302.js
@@ -0,0 +1,1 @@
+var load=false;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_302.js^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: /tests/dom/security/test/sri/script.js
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_401.js
@@ -0,0 +1,1 @@
+var load=true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_401.js^headers^
@@ -0,0 +1,2 @@
+HTTP 401 Authorization Required
+Cache-control: public
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain1.js
@@ -0,0 +1,4 @@
+/*
+ * this file should be loaded, because it has CORS enabled.
+*/
+window.hasCORSLoaded = true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain1.js^headers^
@@ -0,0 +1,1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain2.js
@@ -0,0 +1,5 @@
+/*
+ * this file should not be loaded, because it does not have CORS
+ * enabled.
+ */
+window.hasNonCORSLoaded = true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain3.js
@@ -0,0 +1,1 @@
+// This script intentionally left blank
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain3.js^headers^
@@ -0,0 +1,1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain4.js
@@ -0,0 +1,1 @@
+// This script intentionally left blank
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain4.js^headers^
@@ -0,0 +1,1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/style1.css
@@ -0,0 +1,3 @@
+#red-text {
+  color: red;
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/style2.css
@@ -0,0 +1,1 @@
+; A valid but somewhat uninteresting stylesheet
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/style3.css
@@ -0,0 +1,3 @@
+#black-text {
+  color: green;
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_script_crossdomain.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Cross-domain script tests for Bug 992096</title>
+  <script>
+    SpecialPowers.setBoolPref("security.sri.enable", true);
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+  <iframe src="iframe_script_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_script_sameorigin.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Same-origin script tests for Bug 992096</title>
+  <script>
+    SpecialPowers.setBoolPref("security.sri.enable", true);
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+  <iframe src="iframe_script_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_sri_disabled.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>security.sri.enable tests for Bug 992096</title>
+  <script>
+    SpecialPowers.setBoolPref("security.sri.enable", false);
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+  <iframe src="iframe_sri_disabled.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_style_sameorigin.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Same-origin stylesheet tests for Bug 992096</title>
+  <script>
+    SpecialPowers.setBoolPref("security.sri.enable", true);
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+  <iframe src="iframe_style_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
--- a/dom/webidl/HTMLLinkElement.webidl
+++ b/dom/webidl/HTMLLinkElement.webidl
@@ -43,8 +43,13 @@ partial interface HTMLLinkElement {
 };
 
 // http://w3c.github.io/webcomponents/spec/imports/#interface-import
 partial interface HTMLLinkElement {
     [Func="nsDocument::IsWebComponentsEnabled"]
     readonly attribute Document? import;
 };
 
+// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
+partial interface HTMLLinkElement {
+  [SetterThrows]
+  attribute DOMString integrity;
+};
--- a/dom/webidl/HTMLScriptElement.webidl
+++ b/dom/webidl/HTMLScriptElement.webidl
@@ -28,8 +28,13 @@ interface HTMLScriptElement : HTMLElemen
 // http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
 partial interface HTMLScriptElement {
   [SetterThrows]
   attribute DOMString event;
   [SetterThrows]
   attribute DOMString htmlFor;
 };
 
+// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmlscriptelement-1
+partial interface HTMLScriptElement {
+  [SetterThrows]
+  attribute DOMString integrity;
+};
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -161,17 +161,17 @@ AndroidSurfaceTexture::Init(GLContext* a
 
   mAttachedContext = aContext;
 
   if (NS_WARN_IF(NS_FAILED(
       Surface::New(mSurfaceTexture, ReturnTo(&mSurface))))) {
     return false;
   }
 
-  mNativeWindow = AndroidNativeWindow::CreateFromSurface(GetJNIForThread(),
+  mNativeWindow = AndroidNativeWindow::CreateFromSurface(jni::GetEnvForThread(),
                                                          mSurface.Get());
   MOZ_ASSERT(mNativeWindow, "Failed to create native window from surface");
 
   mID = ++sNextID;
   sInstances.insert(std::pair<int, AndroidSurfaceTexture*>(mID, this));
 
   return true;
 }
@@ -202,17 +202,17 @@ void
 AndroidSurfaceTexture::UpdateTexImage()
 {
   mSurfaceTexture->UpdateTexImage();
 }
 
 void
 AndroidSurfaceTexture::GetTransformMatrix(gfx::Matrix4x4& aMatrix)
 {
-  JNIEnv* env = GetJNIForThread();
+  JNIEnv* const env = jni::GetEnvForThread();
 
   auto jarray = FloatArray::LocalRef::Adopt(env, env->NewFloatArray(16));
   mSurfaceTexture->GetTransformMatrix(jarray);
 
   jfloat* array = env->GetFloatArrayElements(jarray.Get(), nullptr);
 
   aMatrix._11 = array[0];
   aMatrix._12 = array[1];
--- a/gfx/skia/generate_mozbuild.py
+++ b/gfx/skia/generate_mozbuild.py
@@ -134,19 +134,24 @@ elif CONFIG['CLANG_CL']:
 
 DEFINES['SKIA_IMPLEMENTATION'] = 1
 DEFINES['GR_IMPLEMENTATION'] = 1
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-overloaded-virtual',
         '-Wno-unused-function',
+        '-Wno-deprecated-declarations',
     ]
     if CONFIG['CLANG_CXX']:
-        CXXFLAGS += ['-Wno-inconsistent-missing-override']
+        CXXFLAGS += [
+            '-Wno-inconsistent-missing-override',
+            '-Wno-macro-redefined',
+            '-Wno-unused-private-field',
+        ]
     else:
         CXXFLAGS += ['-Wno-logical-op']
     if CONFIG['CPU_ARCH'] == 'arm':
         SOURCES['skia/src/opts/SkBlitRow_opts_arm.cpp'].flags += ['-fomit-frame-pointer']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'android', 'gonk', 'qt'):
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
     CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
--- a/gfx/skia/moz.build
+++ b/gfx/skia/moz.build
@@ -662,19 +662,24 @@ elif CONFIG['CLANG_CL']:
 
 DEFINES['SKIA_IMPLEMENTATION'] = 1
 DEFINES['GR_IMPLEMENTATION'] = 1
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += [
         '-Wno-overloaded-virtual',
         '-Wno-unused-function',
+        '-Wno-deprecated-declarations',
     ]
     if CONFIG['CLANG_CXX']:
-        CXXFLAGS += ['-Wno-inconsistent-missing-override']
+        CXXFLAGS += [
+            '-Wno-inconsistent-missing-override',
+            '-Wno-macro-redefined',
+            '-Wno-unused-private-field',
+        ]
     else:
         CXXFLAGS += ['-Wno-logical-op']
     if CONFIG['CPU_ARCH'] == 'arm':
         SOURCES['skia/src/opts/SkBlitRow_opts_arm.cpp'].flags += ['-fomit-frame-pointer']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'android', 'gonk', 'qt'):
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
     CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -699,17 +699,17 @@ gfxDWriteFontEntry::AddSizeOfIncludingTh
     aSizes->mFontListSize += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // gfxDWriteFontList
 
 gfxDWriteFontList::gfxDWriteFontList()
-    : mInitialized(false), mForceGDIClassicMaxFontSize(0.0)
+    : mForceGDIClassicMaxFontSize(0.0)
 {
 }
 
 // bug 602792 - CJK systems default to large CJK fonts which cause excessive
 //   I/O strain during cold startup due to dwrite caching bugs.  Default to
 //   Arial to avoid this.
 
 gfxFontFamily *
@@ -839,138 +839,74 @@ gfxDWriteFontList::MakePlatformFont(cons
         // We don't know how to deal with 0 faces either.
         delete entry;
         return nullptr;
     }
 
     return entry;
 }
 
-#ifdef DEBUG_DWRITE_STARTUP
-
-#define LOGREGISTRY(msg) LogRegistryEvent(msg)
-
-// for use when monitoring process
-static void LogRegistryEvent(const wchar_t *msg)
-{
-    HKEY dummyKey;
-    HRESULT hr;
-    wchar_t buf[512];
-
-    wsprintfW(buf, L" log %s", msg);
-    hr = RegOpenKeyExW(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &dummyKey);
-    if (SUCCEEDED(hr)) {
-        RegCloseKey(dummyKey);
-    }
-}
-#else
-
-#define LOGREGISTRY(msg)
-
-#endif
-
 nsresult
 gfxDWriteFontList::InitFontList()
 {
-    LOGREGISTRY(L"InitFontList start");
-
-    mInitialized = false;
-
-    LARGE_INTEGER frequency;        // ticks per second
-    LARGE_INTEGER t1, t2, t3;           // ticks
+    LARGE_INTEGER frequency;          // ticks per second
+    LARGE_INTEGER t1, t2, t3, t4, t5; // ticks
     double elapsedTime, upTime;
     char nowTime[256], nowDate[256];
 
-    if (LOG_FONTINIT_ENABLED()) {    
-        GetTimeFormat(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, 
+    if (LOG_FONTINIT_ENABLED()) {
+        GetTimeFormat(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT,
                       nullptr, nullptr, nowTime, 256);
         GetDateFormat(LOCALE_INVARIANT, 0, nullptr, nullptr, nowDate, 256);
+        upTime = (double) GetTickCount();
     }
-    upTime = (double) GetTickCount();
     QueryPerformanceFrequency(&frequency);
-    QueryPerformanceCounter(&t1);
+    QueryPerformanceCounter(&t1); // start
 
     HRESULT hr;
-    mGDIFontTableAccess = Preferences::GetBool("gfx.font_rendering.directwrite.use_gdi_table_loading", false);
+    mGDIFontTableAccess =
+        Preferences::GetBool("gfx.font_rendering.directwrite.use_gdi_table_loading",
+                             false);
 
     gfxPlatformFontList::InitFontList();
 
     mFontSubstitutes.Clear();
     mNonExistingFonts.Clear();
 
-    QueryPerformanceCounter(&t2);
-
     hr = gfxWindowsPlatform::GetPlatform()->GetDWriteFactory()->
         GetGdiInterop(getter_AddRefs(mGDIInterop));
     if (FAILED(hr)) {
         return NS_ERROR_FAILURE;
     }
 
-    LOGREGISTRY(L"InitFontList end");
-
-    QueryPerformanceCounter(&t3);
-
-    if (LOG_FONTINIT_ENABLED()) {
-        // determine dwrite version
-        nsAutoString dwriteVers;
-        gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", dwriteVers);
-        LOG_FONTINIT(("InitFontList\n"));
-        LOG_FONTINIT(("Start: %s %s\n", nowDate, nowTime));
-        LOG_FONTINIT(("Uptime: %9.3f s\n", upTime/1000));
-        LOG_FONTINIT(("dwrite version: %s\n", 
-                      NS_ConvertUTF16toUTF8(dwriteVers).get()));
-    }
-
-    elapsedTime = (t3.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
-    LOG_FONTINIT(("Total time in InitFontList:    %9.3f ms\n", elapsedTime));
-    elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
-    LOG_FONTINIT((" --- gfxPlatformFontList init: %9.3f ms\n", elapsedTime));
-    elapsedTime = (t3.QuadPart - t2.QuadPart) * 1000.0 / frequency.QuadPart;
-    LOG_FONTINIT((" --- GdiInterop object:        %9.3f ms\n", elapsedTime));
+    QueryPerformanceCounter(&t2); // base-class/interop initialization
 
-    return NS_OK;
-}
-
-nsresult
-gfxDWriteFontList::DelayedInitFontList()
-{
-    LOGREGISTRY(L"DelayedInitFontList start");
-
-    LARGE_INTEGER frequency;        // ticks per second
-    LARGE_INTEGER t1, t2, t3;           // ticks
-    double elapsedTime, upTime;
-    char nowTime[256], nowDate[256];
-
-    if (LOG_FONTINIT_ENABLED()) {    
-        GetTimeFormat(LOCALE_INVARIANT, TIME_FORCE24HOURFORMAT, 
-                      nullptr, nullptr, nowTime, 256);
-        GetDateFormat(LOCALE_INVARIANT, 0, nullptr, nullptr, nowDate, 256);
-    }
-
-    upTime = (double) GetTickCount();
-    QueryPerformanceFrequency(&frequency);
-    QueryPerformanceCounter(&t1);
-
-    HRESULT hr;
     nsRefPtr<IDWriteFactory> factory =
         gfxWindowsPlatform::GetPlatform()->GetDWriteFactory();
 
-    LOGREGISTRY(L"calling GetSystemFontCollection");
     hr = factory->GetSystemFontCollection(getter_AddRefs(mSystemFonts));
     NS_ASSERTION(SUCCEEDED(hr), "GetSystemFontCollection failed!");
-    LOGREGISTRY(L"GetSystemFontCollection done");
 
     if (FAILED(hr)) {
         return NS_ERROR_FAILURE;
     }
 
-    QueryPerformanceCounter(&t2);
+    QueryPerformanceCounter(&t3); // system font collection
 
     GetFontsFromCollection(mSystemFonts);
 
+    // if no fonts found, something is out of whack, bail and use GDI backend
+    NS_ASSERTION(mFontFamilies.Count() != 0,
+                 "no fonts found in the system fontlist -- holy crap batman!");
+    if (mFontFamilies.Count() == 0) {
+        return NS_ERROR_FAILURE;
+    }
+
+    QueryPerformanceCounter(&t4); // iterate over system fonts
+
 #ifdef MOZ_BUNDLED_FONTS
     mBundledFonts = CreateBundledFontsCollection(factory);
     if (mBundledFonts) {
         GetFontsFromCollection(mBundledFonts);
     }
 #endif
 
     mOtherFamilyNamesInitialized = true;
@@ -978,17 +914,17 @@ gfxDWriteFontList::DelayedInitFontList()
 
     // bug 642093 - DirectWrite does not support old bitmap (.fon)
     // font files, but a few of these such as "Courier" and "MS Sans Serif"
     // are frequently specified in shoddy CSS, without appropriate fallbacks.
     // By mapping these to TrueType equivalents, we provide better consistency
     // with both pre-DW systems and with IE9, which appears to do the same.
     GetDirectWriteSubstitutes();
 
-    // bug 551313 - DirectWrite creates a Gill Sans family out of 
+    // bug 551313 - DirectWrite creates a Gill Sans family out of
     // poorly named members of the Gill Sans MT family containing
     // only Ultra Bold weights.  This causes big problems for pages
     // using Gill Sans which is usually only available on OSX
 
     nsAutoString nameGillSans(L"Gill Sans");
     nsAutoString nameGillSansMT(L"Gill Sans MT");
     BuildKeyNameFromFontName(nameGillSans);
     BuildKeyNameFromFontName(nameGillSansMT);
@@ -1050,46 +986,49 @@ gfxDWriteFontList::DelayedInitFontList()
         }
     }
     mForceGDIClassicMaxFontSize =
         Preferences::GetInt("gfx.font_rendering.cleartype_params.force_gdi_classic_max_size",
                             mForceGDIClassicMaxFontSize);
 
     GetPrefsAndStartLoader();
 
-    LOGREGISTRY(L"DelayedInitFontList end");
-
-    QueryPerformanceCounter(&t3);
+    QueryPerformanceCounter(&t5); // misc initialization
 
     if (LOG_FONTINIT_ENABLED()) {
         // determine dwrite version
         nsAutoString dwriteVers;
         gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", dwriteVers);
-        LOG_FONTINIT(("DelayedInitFontList\n"));
-        LOG_FONTINIT(("Start: %s %s\n", nowDate, nowTime));
-        LOG_FONTINIT(("Uptime: %9.3f s\n", upTime/1000));
-        LOG_FONTINIT(("dwrite version: %s\n", 
+        LOG_FONTINIT(("(fontinit) Start: %s %s\n", nowDate, nowTime));
+        LOG_FONTINIT(("(fontinit) Uptime: %9.3f s\n", upTime/1000));
+        LOG_FONTINIT(("(fontinit) dwrite version: %s\n",
                       NS_ConvertUTF16toUTF8(dwriteVers).get()));
     }
 
-    elapsedTime = (t3.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
+    elapsedTime = (t5.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
     Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_TOTAL, elapsedTime);
     Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COUNT,
                           mSystemFonts->GetFontFamilyCount());
     LOG_FONTINIT((
-       "Total time in DelayedInitFontList:    %9.3f ms (families: %d, %s)\n",
+       "(fontinit) Total time in InitFontList:    %9.3f ms (families: %d, %s)\n",
        elapsedTime, mSystemFonts->GetFontFamilyCount(),
        (mGDIFontTableAccess ? "gdi table access" : "dwrite table access")));
 
     elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
-    Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COLLECT, elapsedTime);
-    LOG_FONTINIT((" --- GetSystemFontCollection:  %9.3f ms\n", elapsedTime));
+    LOG_FONTINIT(("(fontinit)  --- base/interop obj initialization init: %9.3f ms\n", elapsedTime));
 
     elapsedTime = (t3.QuadPart - t2.QuadPart) * 1000.0 / frequency.QuadPart;
-    LOG_FONTINIT((" --- iterate over families:    %9.3f ms\n", elapsedTime));
+    Telemetry::Accumulate(Telemetry::DWRITEFONT_DELAYEDINITFONTLIST_COLLECT, elapsedTime);
+    LOG_FONTINIT(("(fontinit)  --- GetSystemFontCollection:  %9.3f ms\n", elapsedTime));
+
+    elapsedTime = (t4.QuadPart - t3.QuadPart) * 1000.0 / frequency.QuadPart;
+    LOG_FONTINIT(("(fontinit)  --- iterate over families:    %9.3f ms\n", elapsedTime));
+
+    elapsedTime = (t5.QuadPart - t4.QuadPart) * 1000.0 / frequency.QuadPart;
+    LOG_FONTINIT(("(fontinit)  --- misc initialization:    %9.3f ms\n", elapsedTime));
 
     return NS_OK;
 }
 
 void
 gfxDWriteFontList::GetFontsFromCollection(IDWriteFontCollection* aCollection)
 {
     for (UINT32 i = 0; i < aCollection->GetFontFamilyCount(); i++) {
@@ -1303,48 +1242,32 @@ gfxDWriteFontList::GetStandardFamilyName
     return false;
 }
 
 gfxFontFamily*
 gfxDWriteFontList::FindFamily(const nsAString& aFamily,
                               nsIAtom* aLanguage,
                               bool aUseSystemFonts)
 {
-    if (!mInitialized) {
-        mInitialized = true;
-        DelayedInitFontList();
-    }
-
     nsAutoString keyName(aFamily);
     BuildKeyNameFromFontName(keyName);
 
     gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName);
     if (ff) {
         return ff;
     }
 
     if (mNonExistingFonts.Contains(keyName)) {
         return nullptr;
     }
 
     return gfxPlatformFontList::FindFamily(aFamily);
 }
 
 void
-gfxDWriteFontList::GetFontFamilyList(nsTArray<nsRefPtr<gfxFontFamily> >& aFamilyArray)
-{
-    if (!mInitialized) {
-        mInitialized = true;
-        DelayedInitFontList();
-    }
-
-    return gfxPlatformFontList::GetFontFamilyList(aFamilyArray);
-}
-
-void
 gfxDWriteFontList::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                           FontListSizes* aSizes) const
 {
     gfxPlatformFontList::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
 
     aSizes->mFontListSize +=
         SizeOfFontFamilyTableExcludingThis(mFontSubstitutes, aMallocSizeOf);
 
--- a/gfx/thebes/gfxDWriteFontList.h
+++ b/gfx/thebes/gfxDWriteFontList.h
@@ -363,18 +363,16 @@ public:
 
     IDWriteGdiInterop *GetGDIInterop() { return mGDIInterop; }
     bool UseGDIFontTableAccess() { return mGDIFontTableAccess; }
 
     virtual gfxFontFamily* FindFamily(const nsAString& aFamily,
                                       nsIAtom* aLanguage = nullptr,
                                       bool aUseSystemFonts = false);
 
-    virtual void GetFontFamilyList(nsTArray<nsRefPtr<gfxFontFamily> >& aFamilyArray);
-
     gfxFloat GetForceGDIClassicMaxFontSize() { return mForceGDIClassicMaxFontSize; }
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontListSizes* aSizes) const;
 
 private:
@@ -407,19 +405,16 @@ private:
     nsTArray<nsString> mNonExistingFonts;
 
     /**
      * Table of font substitutes, we grab this from the registry to get
      * alternative font names.
      */
     FontFamilyTable mFontSubstitutes;
 
-    bool mInitialized;
-    virtual nsresult DelayedInitFontList();
-
     virtual already_AddRefed<FontInfoData> CreateFontInfoData();
 
     gfxFloat mForceGDIClassicMaxFontSize;
 
     // whether to use GDI font table access routines
     bool mGDIFontTableAccess;
     nsRefPtr<IDWriteGdiInterop> mGDIInterop;
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -24,16 +24,20 @@
 #include "mozilla/Likely.h"
 #include "gfx2DGlue.h"
 #include "mozilla/gfx/Logging.h"        // for gfxCriticalError
 
 #if defined(MOZ_WIDGET_GTK)
 #include "gfxPlatformGtk.h" // xxx - for UseFcFontList
 #endif
 
+#ifdef XP_WIN
+#include "gfxWindowsPlatform.h"
+#endif
+
 #include "cairo.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::unicode;
 using mozilla::services::GetObserverService;
 
 static const char16_t kEllipsisChar[] = { 0x2026, 0x0 };
@@ -1906,16 +1910,22 @@ gfxFontGroup::GetDefaultFont()
     if (!mDefaultFont) {
         // an empty font list at this point is fatal; we're not going to
         // be able to do even the most basic layout operations
 
         // annotate crash report with fontlist info
         nsAutoCString fontInitInfo;
         fontInitInfo.AppendPrintf("no fonts - init: %d fonts: %d loader: %d",
                                   numInits, numFonts, loaderState);
+#ifdef XP_WIN
+        bool dwriteEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
+        double upTime = (double) GetTickCount();
+        fontInitInfo.AppendPrintf(" backend: %s system-uptime: %9.3f sec",
+                                  dwriteEnabled ? "directwrite" : "gdi", upTime/1000);
+#endif
         gfxCriticalError() << fontInitInfo.get();
 
         char msg[256]; // CHECK buffer length if revising message below
         nsAutoString families;
         mFamilyList.ToString(families);
         snprintf_literal(msg, "unable to find a usable font (%.220s)",
                          NS_ConvertUTF16toUTF8(families).get());
         NS_RUNTIMEABORT(msg);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1668,17 +1668,17 @@ gfxWindowsPlatform::GetDXGIAdapter()
   }
 
   // We leak this module everywhere, we might as well do so here as well.
   dxgiModule.disown();
 
   return mAdapter;
 }
 
-bool DoesD3D11DeviceWork(ID3D11Device *device)
+bool CouldD3D11DeviceWork()
 {
   static bool checked = false;
   static bool result = false;
 
   if (checked)
       return result;
   checked = true;
 
@@ -1982,16 +1982,21 @@ gfxWindowsPlatform::CheckD3D11Support(bo
 // We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h,
 // since it doesn't include d3d11.h, so we use a static here. It should only
 // be used within InitializeD3D11.
 decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr;
 
 void
 gfxWindowsPlatform::AttemptD3D11DeviceCreation()
 {
+  if (!CouldD3D11DeviceWork()) {
+    mD3D11Device = nullptr;
+    return;
+  }
+
   RefPtr<IDXGIAdapter1> adapter = GetDXGIAdapter();
   if (!adapter) {
     return;
   }
 
   HRESULT hr = E_INVALIDARG;
   MOZ_SEH_TRY {
     hr =
@@ -2002,17 +2007,17 @@ gfxWindowsPlatform::AttemptD3D11DeviceCr
                            D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS,
                            mFeatureLevels.Elements(), mFeatureLevels.Length(),
                            D3D11_SDK_VERSION, byRef(mD3D11Device), nullptr, nullptr);
   } MOZ_SEH_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
     gfxCriticalError() << "Crash during D3D11 device creation";
     return;
   }
 
-  if (FAILED(hr) || !DoesD3D11DeviceWork(mD3D11Device)) {
+  if (FAILED(hr)) {
     gfxCriticalError() << "D3D11 device creation failed" << hexa(hr);
     return;
   }
   if (!mD3D11Device) {
     return;
   }
 
   CheckIfRenderTargetViewNeedsRecreating(mD3D11Device);
@@ -2429,16 +2434,20 @@ gfxWindowsPlatform::InitializeD2D1()
 
   mD2D1Status = FeatureStatus::Available;
   d2d1_1.SetSuccessful();
 }
 
 already_AddRefed<ID3D11Device>
 gfxWindowsPlatform::CreateD3D11DecoderDevice()
 {
+  if (!CouldD3D11DeviceWork()) {
+    return nullptr;
+  }
+
   nsModuleHandle d3d11Module(LoadLibrarySystem32(L"d3d11.dll"));
   decltype(D3D11CreateDevice)* d3d11CreateDevice = (decltype(D3D11CreateDevice)*)
     GetProcAddress(d3d11Module, "D3D11CreateDevice");
 
    if (!d3d11CreateDevice) {
     // We should just be on Windows Vista or XP in this case.
     return nullptr;
   }
@@ -2466,17 +2475,17 @@ gfxWindowsPlatform::CreateD3D11DecoderDe
     hr = d3d11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr,
                            D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
                            featureLevels.Elements(), featureLevels.Length(),
                            D3D11_SDK_VERSION, byRef(device), nullptr, nullptr);
   } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
     return nullptr;
   }
 
-  if (FAILED(hr) || !DoesD3D11DeviceWork(device)) {
+  if (FAILED(hr)) {
     return nullptr;
   }
 
   nsRefPtr<ID3D10Multithread> multi;
   device->QueryInterface(__uuidof(ID3D10Multithread), getter_AddRefs(multi));
 
   multi->SetMultithreadProtected(TRUE);
 
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -210,10 +210,35 @@ DecoderFactory::CreateAnonymousDecoder(D
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
 
+/* static */ already_AddRefed<Decoder>
+DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
+                                               SourceBuffer* aSourceBuffer)
+{
+  if (aType == DecoderType::UNKNOWN) {
+    return nullptr;
+  }
+
+  nsRefPtr<Decoder> decoder =
+    GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
+  MOZ_ASSERT(decoder, "Should have a decoder now");
+
+  // Initialize the decoder.
+  decoder->SetMetadataDecode(true);
+  decoder->SetIterator(aSourceBuffer->Iterator());
+  decoder->SetIsFirstFrameDecode();
+
+  decoder->Init();
+  if (NS_FAILED(decoder->GetDecoderError())) {
+    return nullptr;
+  }
+
+  return decoder.forget();
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -96,21 +96,47 @@ public:
    */
   static already_AddRefed<Decoder>
   CreateMetadataDecoder(DecoderType aType,
                         RasterImage* aImage,
                         SourceBuffer* aSourceBuffer,
                         int aSampleSize,
                         const gfx::IntSize& aResolution);
 
+  /**
+   * 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
+   *                      from.
+   * @param aFlags Flags specifying what type of output the decoder should
+   *               produce; see GetDecodeFlags() in RasterImage.h.
+   */
   static already_AddRefed<Decoder>
   CreateAnonymousDecoder(DecoderType aType,
                          SourceBuffer* aSourceBuffer,
                          uint32_t aFlags);
 
+  /**
+   * 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.
+   * @param aFlags Flags specifying what type of output the decoder should
+   *               produce; see GetDecodeFlags() in RasterImage.h.
+   */
+  static already_AddRefed<Decoder>
+  CreateAnonymousMetadataDecoder(DecoderType aType,
+                                 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,
                                               RasterImage* aImage,
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -364,16 +364,25 @@ nsBMPDecoder::WriteInternal(const char* 
 
       // Post our size to the superclass
       PostSize(mBIH.width, real_height);
       if (HasError()) {
         // Setting the size led to an error.
         return;
       }
 
+      // We treat BMPs as transparent if they're 32bpp and alpha is enabled, but
+      // also if they use RLE encoding, because the 'delta' mode can skip pixels
+      // and cause implicit transparency.
+      if ((mBIH.compression == BMPINFOHEADER::RLE8) ||
+          (mBIH.compression == BMPINFOHEADER::RLE4) ||
+          (mBIH.bpp == 32 && mUseAlphaData)) {
+        PostHasTransparency();
+      }
+
       // We have the size. If we're doing a metadata decode, we're done.
       if (IsMetadataDecode()) {
         return;
       }
 
       // We're doing a real decode.
       mOldLine = mCurLine = real_height;
 
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -45,16 +45,18 @@ mailing address.
 #include "RasterImage.h"
 
 #include "gfxColor.h"
 #include "gfxPlatform.h"
 #include "qcms.h"
 #include <algorithm>
 #include "mozilla/Telemetry.h"
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace image {
 
 // GETN(n, s) requests at least 'n' bytes available from 'q', at start of state
 // 's'. Colormaps are directly copied in the resp. global_colormap or the
 // local_colormap of the PAL image frame So a fixed buffer in gif_struct is
 // good enough. This buffer is only needed to copy left-over data from one
 // GifWrite call to the next
@@ -156,47 +158,63 @@ nsGIFDecoder2::BeginGIF()
     return;
   }
 
   mGIFOpen = true;
 
   PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
 }
 
+void
+nsGIFDecoder2::CheckForTransparency(IntRect aFrameRect)
+{
+  // Check if the image has a transparent color in its palette.
+  if (mGIFStruct.is_transparent) {
+    PostHasTransparency();
+    return;
+  }
+
+  if (mGIFStruct.images_decoded > 0) {
+    return;  // We only care about first frame padding below.
+  }
+
+  // If we need padding on the first frame, that means we don't draw into part
+  // of the image at all. Report that as transparency.
+  IntRect imageRect(0, 0, mGIFStruct.screen_width, mGIFStruct.screen_height);
+  if (!imageRect.IsEqualEdges(aFrameRect)) {
+    PostHasTransparency();
+  }
+}
+
 //******************************************************************************
 nsresult
 nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
 {
   MOZ_ASSERT(HasSize());
 
   gfx::SurfaceFormat format;
   if (mGIFStruct.is_transparent) {
     format = gfx::SurfaceFormat::B8G8R8A8;
-    PostHasTransparency();
   } else {
     format = gfx::SurfaceFormat::B8G8R8X8;
   }
 
-  nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
-                      mGIFStruct.width, mGIFStruct.height);
+  IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
+                    mGIFStruct.width, mGIFStruct.height);
+
+  CheckForTransparency(frameRect);
 
   // Use correct format, RGB for first frame, PAL for following frames
   // and include transparency to allow for optimization of opaque images
   nsresult rv = NS_OK;
   if (mGIFStruct.images_decoded) {
     // Image data is stored with original depth and palette.
     rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
                        frameRect, format, aDepth);
   } else {
-    if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
-      // We need padding on the first frame, which means that we don't draw into
-      // part of the image at all. Report that as transparency.
-      PostHasTransparency();
-    }
-
     // Regardless of depth of input, the first frame is decoded into 24bit RGB.
     rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
                        frameRect, format);
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 
   return rv;
@@ -684,22 +702,16 @@ nsGIFDecoder2::WriteInternal(const char*
       // individual images can be smaller than the
       // screen size and located with an origin anywhere
       // within the screen.
 
       mGIFStruct.screen_width = GETINT16(q);
       mGIFStruct.screen_height = GETINT16(q + 2);
       mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1;
 
-      if (IsMetadataDecode()) {
-        MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point");
-        PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
-        return;
-      }
-
       // screen_bgcolor is not used
       //mGIFStruct.screen_bgcolor = q[5];
       // q[6] = Pixel Aspect Ratio
       //   Not used
       //   float aspect = (float)((q[6] + 15) / 64.0);
 
       if (q[4] & 0x80) {
         // Get the global colormap
@@ -726,16 +738,19 @@ nsGIFDecoder2::WriteInternal(const char*
       ConvertColormap(mGIFStruct.global_colormap,
                       1<<mGIFStruct.global_colormap_depth);
       GETN(1, gif_image_start);
       break;
 
     case gif_image_start:
       switch (*q) {
         case GIF_TRAILER:
+          if (IsMetadataDecode()) {
+            return;
+          }
           mGIFStruct.state = gif_done;
           break;
 
         case GIF_EXTENSION_INTRODUCER:
           GETN(2, gif_extension);
           break;
 
         case GIF_IMAGE_SEPARATOR:
@@ -938,16 +953,19 @@ nsGIFDecoder2::WriteInternal(const char*
         if (HasError()) {
           // Setting the size led to an error.
           mGIFStruct.state = gif_error;
           return;
         }
 
         // If we were doing a metadata decode, we're done.
         if (IsMetadataDecode()) {
+          IntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
+                            mGIFStruct.width, mGIFStruct.height);
+          CheckForTransparency(frameRect);
           return;
         }
       }
 
       // Work around more broken GIF files that have zero image width or height
       if (!mGIFStruct.height || !mGIFStruct.width) {
         mGIFStruct.height = mGIFStruct.screen_height;
         mGIFStruct.width = mGIFStruct.screen_width;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -42,16 +42,17 @@ private:
   void      FlushImageData();
   void      FlushImageData(uint32_t fromRow, uint32_t rows);
 
   nsresult  GifWrite(const uint8_t* buf, uint32_t numbytes);
   uint32_t  OutputRow();
   bool      DoLzw(const uint8_t* q);
   bool      SetHold(const uint8_t* buf, uint32_t count,
                     const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
+  void      CheckForTransparency(gfx::IntRect aFrameRect);
 
   inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
 
   int32_t mCurrentRow;
   int32_t mLastFlushedRow;
 
   uint32_t mOldColor;        // The old value of the transparent pixel
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -14,16 +14,18 @@
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
 #include "RasterImage.h"
 #include "mozilla/Telemetry.h"
 
 #include <algorithm>
 
+using namespace mozilla::gfx;
+
 namespace mozilla {
 namespace image {
 
 static PRLogModuleInfo*
 GetPNGLog()
 {
   static PRLogModuleInfo* sPNGLog;
   if (!sPNGLog) {
@@ -131,36 +133,41 @@ nsPNGDecoder::~nsPNGDecoder()
 
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
+void
+nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
+                                   const IntRect& aFrameRect)
+{
+  // Check if the image has a transparent color in its palette.
+  if (aFormat == SurfaceFormat::B8G8R8A8) {
+    PostHasTransparency();
+  }
+
+  // PNGs shouldn't have first-frame padding.
+  MOZ_ASSERT_IF(mNumFrames == 0,
+                IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect));
+}
+
 // CreateFrame() is used for both simple and animated images
 nsresult
 nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
                           int32_t aWidth, int32_t aHeight,
                           gfx::SurfaceFormat aFormat)
 {
   MOZ_ASSERT(HasSize());
   MOZ_ASSERT(!IsMetadataDecode());
 
-  if (aFormat == gfx::SurfaceFormat::B8G8R8A8) {
-    PostHasTransparency();
-  }
-
-  nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
-  if (mNumFrames == 0 &&
-      !nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
-    // We need padding on the first frame, which means that we don't draw into
-    // part of the image at all. Report that as transparency.
-    PostHasTransparency();
-  }
+  IntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
+  CheckForTransparency(aFormat, frameRect);
 
   // XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8.
   // This is something we should fix.
   gfx::SurfaceFormat format = aFormat;
   if (mNumFrames == 0) {
     format = gfx::SurfaceFormat::B8G8R8A8;
   }
 
@@ -263,17 +270,17 @@ nsPNGDecoder::InitInternal()
   if (!mInfo) {
     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
     png_destroy_read_struct(&mPNG, nullptr, nullptr);
     return;
   }
 
 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   // Ignore unused chunks
-  if (mCMSMode == eCMSMode_Off) {
+  if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) {
     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
   }
 
   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
                               (int)sizeof(unused_chunks)/5);
 #endif
 
 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
@@ -481,22 +488,16 @@ nsPNGDecoder::info_callback(png_structp 
 
   // Post our size to the superclass
   decoder->PostSize(width, height);
   if (decoder->HasError()) {
     // Setting the size led to an error.
     png_longjmp(decoder->mPNG, 1);
   }
 
-  if (decoder->IsMetadataDecode()) {
-    // We have the size, so we don't need to decode any further.
-    decoder->mSuccessfulEarlyFinish = true;
-    png_longjmp(decoder->mPNG, 1);
-  }
-
   if (color_type == PNG_COLOR_TYPE_PALETTE) {
     png_set_expand(png_ptr);
   }
 
   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
     png_set_expand(png_ptr);
   }
 
@@ -589,16 +590,26 @@ nsPNGDecoder::info_callback(png_structp 
   if (channels == 1 || channels == 3) {
     decoder->format = gfx::SurfaceFormat::B8G8R8X8;
   } else if (channels == 2 || channels == 4) {
     decoder->format = gfx::SurfaceFormat::B8G8R8A8;
   } else {
     png_longjmp(decoder->mPNG, 1); // invalid number of channels
   }
 
+  if (decoder->IsMetadataDecode()) {
+    decoder->CheckForTransparency(decoder->format,
+                                  IntRect(0, 0, width, height));
+
+    // We have the size and transparency information we're looking for, so we
+    // don't need to decode any further.
+    decoder->mSuccessfulEarlyFinish = true;
+    png_longjmp(decoder->mPNG, 1);
+  }
+
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     decoder->mFrameIsHidden = true;
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -30,16 +30,19 @@ public:
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual Telemetry::ID SpeedHistogram() override;
 
   nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
                        int32_t aWidth, int32_t aHeight,
                        gfx::SurfaceFormat aFormat);
   void EndImageFrame();
 
+  void CheckForTransparency(gfx::SurfaceFormat aFormat,
+                            const gfx::IntRect& aFrameRect);
+
   // Check if PNG is valid ICO (32bpp RGBA)
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   bool IsValidICO() const
   {
     // If there are errors in the call to png_get_IHDR, the error_callback in
     // nsPNGDecoder.cpp is called.  In this error callback we do a longjmp, so
     // we need to save the jump buffer here. Oterwise we'll end up without a
     // proper callstack.
--- a/image/test/gtest/Common.cpp
+++ b/image/test/gtest/Common.cpp
@@ -121,37 +121,81 @@ ImageTestCase GreenPNGTestCase()
 ImageTestCase GreenGIFTestCase()
 {
   return ImageTestCase("green.gif", "image/gif", IntSize(100, 100));
 }
 
 ImageTestCase GreenJPGTestCase()
 {
   return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
-                       /* aFuzzy = */ true);
+                       TEST_CASE_IS_FUZZY);
 }
 
 ImageTestCase GreenBMPTestCase()
 {
   return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100));
 }
 
 ImageTestCase GreenICOTestCase()
 {
-  return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100));
+  // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
+  // when the BMP is embedded in an ICO, so it's transparent.
+  return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
+                       TEST_CASE_IS_TRANSPARENT);
 }
 
 ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
 {
   return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100));
 }
 
 ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
 {
-  return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100));
+  return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
+                       TEST_CASE_IS_TRANSPARENT);
 }
 
 ImageTestCase CorruptTestCase()
 {
-  return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100));
+  return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
+                       TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase TransparentPNGTestCase()
+{
+  return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
+                       TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentGIFTestCase()
+{
+  return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
+                       TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase FirstFramePaddingGIFTestCase()
+{
+  return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
+                       TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase()
+{
+  // Note that we only decode this test case as transparent when the BMP decoder
+  // is set to use alpha data. (That's not the default, which is why it's not marked
+  // TEST_CASE_IS_TRANSPARENT; tests that want to treat this testcase as
+  // transparent need to handle this case manually.)
+  return ImageTestCase("transparent.bmp", "image/bmp", IntSize(32, 32));
+}
+
+ImageTestCase RLE4BMPTestCase()
+{
+  return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
+                       TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase RLE8BMPTestCase()
+{
+  return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
+                       TEST_CASE_IS_TRANSPARENT);
 }
 
 } // namespace mozilla
--- a/image/test/gtest/Common.h
+++ b/image/test/gtest/Common.h
@@ -12,32 +12,40 @@
 class nsIInputStream;
 
 namespace mozilla {
 
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
 
+enum TestCaseFlags
+{
+  TEST_CASE_DEFAULT_FLAGS   = 0,
+  TEST_CASE_IS_FUZZY        = 1 << 0,
+  TEST_CASE_IS_TRANSPARENT  = 1 << 1,
+  TEST_CASE_HAS_ERROR       = 1 << 2
+};
+
 struct ImageTestCase
 {
   ImageTestCase(const char* aPath,
                 const char* aMimeType,
                 gfx::IntSize aSize,
-                bool aFuzzy = false)
+                uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
     : mPath(aPath)
     , mMimeType(aMimeType)
     , mSize(aSize)
-    , mFuzzy(aFuzzy)
+    , mFlags(aFlags)
   { }
 
   const char* mPath;
   const char* mMimeType;
   gfx::IntSize mSize;
-  bool mFuzzy;
+  uint32_t mFlags;
 };
 
 struct BGRAColor
 {
   BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha)
     : mBlue(aBlue)
     , mGreen(aGreen)
     , mRed(aRed)
@@ -81,11 +89,19 @@ ImageTestCase GreenJPGTestCase();
 ImageTestCase GreenBMPTestCase();
 ImageTestCase GreenICOTestCase();
 
 ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
 ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
 
 ImageTestCase CorruptTestCase();
 
+ImageTestCase TransparentPNGTestCase();
+ImageTestCase TransparentGIFTestCase();
+ImageTestCase FirstFramePaddingGIFTestCase();
+
+ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
+ImageTestCase RLE4BMPTestCase();
+ImageTestCase RLE8BMPTestCase();
+
 } // namespace mozilla
 
 #endif // mozilla_image_test_gtest_Common_h
--- a/image/test/gtest/TestDecodeToSurface.cpp
+++ b/image/test/gtest/TestDecodeToSurface.cpp
@@ -56,17 +56,18 @@ public:
                                 imgIContainer::DECODE_FLAGS_DEFAULT);
     ASSERT_TRUE(surface != nullptr);
 
     EXPECT_EQ(SurfaceType::DATA, surface->GetType());
     EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
                 surface->GetFormat() == SurfaceFormat::B8G8R8A8);
     EXPECT_EQ(mTestCase.mSize, surface->GetSize());
 
-    EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), mTestCase.mFuzzy));
+    EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
+                             mTestCase.mFlags & TEST_CASE_IS_FUZZY));
   }
 
 private:
   nsCOMPtr<nsIInputStream> mInputStream;
   ImageTestCase mTestCase;
 };
 
 static void
new file mode 100644
--- /dev/null
+++ b/image/test/gtest/TestMetadata.cpp
@@ -0,0 +1,180 @@
+/* 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 "gtest/gtest.h"
+
+#include "Common.h"
+#include "Decoder.h"
+#include "DecoderFactory.h"
+#include "decoders/nsBMPDecoder.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "mozilla/gfx/2D.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "mozilla/nsRefPtr.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "ProgressTracker.h"
+#include "SourceBuffer.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+TEST(ImageMetadata, ImageModuleAvailable)
+{
+  // We can run into problems if XPCOM modules get initialized in the wrong
+  // order. It's important that this test run first, both as a sanity check and
+  // to ensure we get the module initialization order we want.
+  nsCOMPtr<imgITools> imgTools =
+    do_CreateInstance("@mozilla.org/image/tools;1");
+  EXPECT_TRUE(imgTools != nullptr);
+}
+
+static void
+CheckMetadata(const ImageTestCase& aTestCase, bool aEnableBMPAlpha = false)
+{
+  nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
+  ASSERT_TRUE(inputStream != nullptr);
+
+  // Prepare the input stream.
+  nsresult rv;
+  if (!NS_InputStreamIsBuffered(inputStream)) {
+    nsCOMPtr<nsIInputStream> bufStream;
+    rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
+                                   inputStream, 1024);
+    if (NS_SUCCEEDED(rv)) {
+      inputStream = bufStream;
+    }
+  }
+
+  // Figure out how much data we have.
+  uint64_t length;
+  rv = inputStream->Available(&length);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Write the data into a SourceBuffer.
+  nsRefPtr<SourceBuffer> sourceBuffer = 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);
+  nsRefPtr<Decoder> decoder =
+    DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
+  ASSERT_TRUE(decoder != nullptr);
+
+  if (aEnableBMPAlpha) {
+    static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
+  }
+
+  // Run the metadata decoder synchronously.
+  decoder->Decode();
+  
+  // 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)));
+
+  // If the test case is corrupt, assert what we can and return early.
+  if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
+    EXPECT_TRUE(decoder->GetDecodeDone());
+    EXPECT_TRUE(decoder->HasError());
+    return;
+  }
+
+  EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
+
+  // Check that we got the expected metadata.
+  EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
+
+  IntSize metadataSize = decoder->GetSize();
+  EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
+  EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
+
+  bool expectTransparency = aEnableBMPAlpha
+                          ? true
+                          : bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
+  EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
+
+  // Create a full decoder, so we can compare the result.
+  decoder =
+    DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
+                                           imgIContainer::DECODE_FLAGS_DEFAULT);
+  ASSERT_TRUE(decoder != nullptr);
+
+  if (aEnableBMPAlpha) {
+    static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
+  }
+
+  // Run the full decoder synchronously.
+  decoder->Decode();
+  
+  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);
+
+  // The full decoder and the metadata decoder should agree on the image's size.
+  IntSize fullSize = decoder->GetSize();
+  EXPECT_EQ(metadataSize.width, fullSize.width);
+  EXPECT_EQ(metadataSize.height, fullSize.height);
+
+  // We should not discover transparency during the full decode that we didn't
+  // discover during the metadata decode, unless the image is animated.
+  EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) ||
+              (metadataProgress & FLAG_HAS_TRANSPARENCY) ||
+              (fullProgress & FLAG_IS_ANIMATED));
+}
+
+TEST(ImageMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); }
+TEST(ImageMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); }
+TEST(ImageMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); }
+TEST(ImageMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); }
+TEST(ImageMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); }
+TEST(ImageMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); }
+TEST(ImageMetadata, ICO) { CheckMetadata(GreenICOTestCase()); }
+
+TEST(ImageMetadata, AnimatedGIF)
+{
+  CheckMetadata(GreenFirstFrameAnimatedGIFTestCase());
+}
+
+TEST(ImageMetadata, AnimatedPNG)
+{
+  CheckMetadata(GreenFirstFrameAnimatedPNGTestCase());
+}
+
+TEST(ImageMetadata, FirstFramePaddingGIF)
+{
+  CheckMetadata(FirstFramePaddingGIFTestCase());
+}
+
+TEST(ImageMetadata, TransparentBMPWithBMPAlphaOff)
+{
+  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(),
+                /* aEnableBMPAlpha = */ false);
+}
+
+TEST(ImageMetadata, TransparentBMPWithBMPAlphaOn)
+{
+  CheckMetadata(TransparentBMPWhenBMPAlphaEnabledTestCase(),
+                /* aEnableBMPAlpha = */ true);
+}
+
+TEST(ImageMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
+TEST(ImageMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
+
+TEST(ImageMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
new file mode 100644
index 0000000000000000000000000000000000000000..e6d7c49322b6a0a4402d2ab9c7c1d64ce22d84f8
GIT binary patch
literal 49
zc${<hbhEHb6krfw_`t}(@SoxT|Nn|VSr|Y-hnazyfeQ#3m^fPcSDt>$pRCPb4FEkY
B3~&Gd
--- a/image/test/gtest/moz.build
+++ b/image/test/gtest/moz.build
@@ -6,26 +6,33 @@
 
 Library('imagetest')
 
 FAIL_ON_WARNINGS = True
 
 UNIFIED_SOURCES = [
     'Common.cpp',
     'TestDecodeToSurface.cpp',
+    'TestMetadata.cpp',
 ]
 
 TEST_HARNESS_FILES.gtest += [
     'corrupt.jpg',
     'first-frame-green.gif',
     'first-frame-green.png',
+    'first-frame-padding.gif',
     'green.bmp',
     'green.gif',
     'green.ico',
     'green.jpg',
     'green.png',
+    'rle4.bmp',
+    'rle8.bmp',
+    'transparent.bmp',
+    'transparent.gif',
+    'transparent.png',
 ]
 
 LOCAL_INCLUDES += [
     '/image',
 ]
 
 FINAL_LIBRARY = 'xul-gtest'
new file mode 100644
index 0000000000000000000000000000000000000000..78a09278704143f846a944af2d59fe8f6d75fcf5
GIT binary patch
literal 3686
zc%1E*F%H5o3`M`=(82&BS72gfWJHLSF;_sS2jG-B2xD&$oTL#Tp;9D9w|#P6^kZ4s
zdOTlN$j?pK3I_susRRonwO;~vF04xwQQo>5KExOiLYRa!rh>tB1Jj{9NeeYKH8nLg
zH8nLgHMJjX?{=f!IPx>co~16F^(I4pE;JvqR!CWs^XY8i|9R`5vvFR3>(4L!r_MCx
Y47*mcX^uKmy{A_8#_#vthrc{}0P4ZRqyPW_
new file mode 100644
index 0000000000000000000000000000000000000000..bd793b6b66637a97073cbf180b0f21fb4e523316
GIT binary patch
literal 1288
zc${66|5Dmq5Qg874MC(-tW~O3Q=&iw<W~vC5;#^XXs7p(zk3y4$v0dIGvAHmPVBo0
zopyR)*^_;q-9s{yuRlr-vh~vf+RWZIER-0)e>U)cHIE<2L)Yu;D~z{ctYMHcbpOJ<
z#!D9bc>0?#C#1|qv*9&u4W9JMSsTwI(;D~2V}RGp33Ea}A=mwjh6l3-%!ZNe8QRD}
zW^Gtwum>q?sX;a@Wiy8v9kOPmYh(jA%;`pNhB@W+COgC=%6R=kp77AM?#UAE<38bC
zXgx{s=g)A@=K(L(rvt;fF`UJF)@LOvI3kYYKwPUNJCNPo-8b?U#r^$vT%cizMTLUv
z{$uQC2Fk0|YPnn%3Qj?6DWh7cRQBH1YPExdgF`i}m9>&3<grkofl9GhE~~S*SF6c^
zb!gEkDu2wlD3=N(=Y>N(W4ZjsGajkrMsaKGZH?kL_CGv4$df!vRo=@{p`KMsEZ*Ng
zJ+Th^d$kJZJ&GTWj_UP{%L7C)seC6Er5WB*_n!FqS+TXHAL_?Szlve+d_I>u!ke2L
zIUzkgJv~0osmvi??(Xj72GTw`IgwNAqxCoYvSn>$g^x<$8e`)5tzv6S+rQ~TV!4&6
zT%+7-x7v;K^YgQ_KXur{>uX30Zlj^(OkW9^%@#9Rz=`AA_!hKeI2>N*R%2@k4bGU#
z5S4zb)oK#?8Il=f5iLj|;dGiC{eG)S#rL!3ce7bETPzmwB8+1yBhH8PvD0k&&4%au
zzWxt8ie_*k)Z;J=N2BRz)a&&+oqmUs?|D97j8Y__jHKsu;Ihl(V&hH(n=Me{cHsCI
z7Z+NG7ftwyqH!3G!$COe_PV{^Rj0#m=ac^_lRQ->{EpQbj0U|fwJSOSB`%&ifju6N
z;S8iJS59EFW=*Wm?nK5ifZgqOAy>tqp!KW@doUQlp<N32gg#4VYPSTC%gf+0ul}B!
W&u~bE#QDpI8w9~67yg|?)A|MPyt^*|
new file mode 100644
index 0000000000000000000000000000000000000000..c3ee2289598752d21f1f922520f5430f689382f3
GIT binary patch
literal 4234
zc$~eLF;4<P5QQZg6Rk{)EwM7xQrplN3Ja~R`2)7s+K>=x5`F<cfY@S7P3&kXEU2v4
z`+#T0b&uOSk0TCd@AhWiym>QwbRPN_N1IPSwIsFcoi)<$V{Yv3|Nc1rZ5EyTr<dXT
zSL^YudDVRVY_!|$%U-W{liKNMG&;LG|2S?v4{xo96VKyxI*qc$V&Rs{rHkEPwasQT
zmn4ZBkH-!yb;c$hu`zh>cDrsenbd}Z+=-jkbrX9lge{Ibawo20iUVuHaVS1)tWbOz
zIN_>F9G36A_;ybfIM7%qM_Z45HW&=td_FIY!OvZ_#&0YJ3@)n3UEE^C*Z&wEifTTT
zyZVnAgozJug!Y&1Q~3_%E*ySeh>t8Bv9ZxhR&QC~)YPttEnS3ga4n`E@^Y7<51I5u
zd|xN_nY{R%Z~JoB`9nNe-aN86z?v67q-Vudj>4mJAxm?%hhoo8%b}jbS4dav&+0$?
z_}s8J?;XOgzjPJind>3F__<rn#PoolJ2A56LFgXWd|p_`=wCI(C(mT%u06K4nuj4y
zip)MgcVEw7f=i1_=K|M#4aMfJ9#HO@v#PVWusQ0iWo{8$oY@)_J%6m9&@59AvTwCo
z{p8HEtnIvroqPTa&gv`Gg!}9D`p-V~xYlLQe_;;!SMK^v;Oh-s;;D*vF=A;3DHhk%
zd(;;k(w%z$Kkq&vK7>QMvu7ooum*>n*Hs%h#DO$#b&3AUXs#>{^(-dc9xC^&Ua|9r
U`qcgVP&nW{_KZ1HO^W9J4b4F%8~^|S
new file mode 100644
index 0000000000000000000000000000000000000000..48f5c7caf1ee52766257792fcf2c01ff277bdb39
GIT binary patch
literal 355
zc${6(Ar8VY6vpwdUD*a9O9f(L0^9@wS%R5@aEoz+9icna6s8Go8aFe6LgG-R%N}&0
zHO)V-Kk-iIquO62Bwm1RnyUqFwrz2Dx9c9z8qdvJ%z+47xHHTmwqQc;bWsOUlBudh
z&BG*vEQ}t@T5Bd2sj5&=>7l_xnU}qxU&cLvaB$dn=I4Nq#XDgC4#QXmhhsHy%*2^d
g@lHU+JIp&V)YJ*Qv!vpjoS>MJhlGbx=~i|400ymArvLx|
new file mode 100644
index 0000000000000000000000000000000000000000..fc8002053a39d720cec152a82a97ab068e14a366
GIT binary patch
literal 419
zc$@*F0bKrxP)<h;3K|Lk000e1NJLTq001BW001Ba00000li|oI00004XF*Lt00D-e
zG3b_G00002bW%=J00-#zSepO<00Lr5M??Sss*NKu0003#Nkl<Zc$`(y%}WA77{~GN
zzGN8$TWUzBEj1_zv^6MCk%z9E35F-3S2`J#TkECh5}tx0uMyZDIz>fvE$HuR_SJSS
z55Et?JTvoPh0AulLIih*akytD27~Tb;Xe$LE%nD4!n3x<!doMP&ZN1YeNQAqaCB<N
zA55r@4;MOQmUNo*edy#%sPn&@->HfZuZ4Pj*<5|d*T#H!ESYk7m-HU8<H6-r6(nW5
z0RE1)lF^X27|~huz@%J6HIZ5*JXoisF)Dgjkv|1=b(F?N7MNzmaRKDi&P`DiBN3cI
zk%OaC48}dUg;}mB=QJuFoPi>@5jnVrEpCx9QzA>j^IKr#L|fDVP9W`Og2!1Mm~*q*
z55G_oeZ|SW=ug`&$i3kho&-p?HH33nlfnslU>nkL46N&>6^EB7TYn&gqWCOVz#0Gm
N002ovPDHLkV1ktEtrY+O
--- a/image/test/mochitest/test_has_transparency.html
+++ b/image/test/mochitest/test_has_transparency.html
@@ -43,18 +43,22 @@ function testFiles() {
   yield ["red.png", false];
   yield ["transparent.png", true];
   yield ["red.gif", false];
   yield ["transparent.gif", true];
 
   // GIFs with padding on the first frame are always transparent.
   yield ["first-frame-padding.gif", true];
 
-  // JPEGs and BMPs are never transparent.
+  // JPEGs are never transparent.
   yield ["damon.jpg", false];
+
+  // Most BMPs are not transparent. (The TestMetadata GTest, which will
+  // eventually replace this test totally, has coverage for the kinds that can be
+  // transparent.)
   yield ["opaque.bmp", false];
 
   // ICO files which contain BMPs have an additional type of transparency - the
   // AND mask - that warrants separate testing.
   yield ["ico-bmp-opaque.ico", false];
   yield ["ico-bmp-transparent.ico", true];
 
   // SVGs are always transparent.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1161332.js
@@ -0,0 +1,16 @@
+// |jit-test| error: Error
+
+var g = newGlobal();
+g.eval('function f(a) { if (a == 1) debugger; evaluate("f(" + a + " - 1);", {newContext: true}); }');
+var N = 9;
+var dbg = new Debugger(g);
+var frames = [];
+dbg.onEnterFrame = function (frame) {
+   frames.push(frame);
+   frame.onPop = function () { assertEq(frame.onPop, frame.onPop); };
+};
+dbg.onDebuggerStatement = function (frame) {
+    for (var f of frames)
+        f.eval('a').return;
+};
+evaluate("g.f(N);");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1188334.js
@@ -0,0 +1,17 @@
+var evalInFrame = (function (global) {
+  var dbgGlobal = newGlobal();
+  var dbg = new dbgGlobal.Debugger();
+  return function evalInFrame(upCount, code) {
+    dbg.addDebuggee(global);
+    var frame = dbg.getNewestFrame().older;
+    var completion = frame.eval(code);
+  };
+})(this);
+function f() {
+    let ({} = "xxx") {
+        yield evalInFrame(0, "x");
+    }
+}
+var gen = f();
+gen.next()
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1191499.js
@@ -0,0 +1,17 @@
+
+setJitCompilerOption('ion.warmup.trigger', 2);
+setJitCompilerOption('offthread-compilation.enable', 0);
+var g = newGlobal();
+var dbg2 = new Debugger;
+g.toggle = function toggle(x, d) {
+  if (d) {
+    dbg2.addDebuggee(g);
+    dbg2.getNewestFrame().environment.getVariable("x");
+  }
+};
+g.eval("" + function f(x, d) { toggle(++arguments, d); });
+g.eval("(" + function test() {
+  for (var i = 0; i < 30; i++)
+    f(42, false);
+  f(42, true);
+} + ")();");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/optimized-out-02.js
@@ -0,0 +1,38 @@
+// Test that prevUpToDate on frames are cleared.
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+
+g.eval(`
+function outer(unaliasedArg) {
+  var unaliasedVar = unaliasedArg + 42;
+  var aliasedVar = unaliasedArg;
+
+  inner();
+  return;
+
+  function inner() {
+    aliasedVar++;
+  }
+}
+`);
+
+var log = "";
+for (var script of dbg.findScripts()) {
+  if (script.displayName === "inner") {
+    script.setBreakpoint(0, { hit: function(frame) {
+      // Force updateLiveScopes.
+      var outerEnv = frame.environment;
+
+      // Get the environment of outer's frame on the stack, so that we may
+      // recover unaliased bindings in the debug scope.
+      outerEnv = frame.older.environment;
+      log += outerEnv.getVariable('unaliasedArg'); // 42
+      log += outerEnv.getVariable('unaliasedVar'); // 84
+      log += outerEnv.getVariable('aliasedVar');   // 42
+    }});
+  }
+}
+
+g.outer(42);
+assertEq(log, "428442");
--- a/js/src/jit/BacktrackingAllocator.cpp
+++ b/js/src/jit/BacktrackingAllocator.cpp
@@ -541,16 +541,17 @@ BacktrackingAllocator::buildLivenessInfo
         // Add successor phis.
         if (mblock->successorWithPhis()) {
             LBlock* phiSuccessor = mblock->successorWithPhis()->lir();
             for (unsigned int j = 0; j < phiSuccessor->numPhis(); j++) {
                 LPhi* phi = phiSuccessor->getPhi(j);
                 LAllocation* use = phi->getOperand(mblock->positionInPhiSuccessor());
                 uint32_t reg = use->toUse()->virtualRegister();
                 live.insert(reg);
+                vreg(use).setUsedByPhi();
             }
         }
 
         // Registers are assumed alive for the entire block, a define shortens
         // the range to the point of definition.
         for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) {
             if (!vregs[*liveRegId].addInitialRange(alloc(), entryOf(block), exitOf(block).next()))
                 return false;
@@ -1666,44 +1667,88 @@ BacktrackingAllocator::insertAllRanges(L
         LiveRange* range = LiveRange::get(*iter);
         if (!set.insert(range))
             return false;
     }
     return true;
 }
 
 bool
+BacktrackingAllocator::deadRange(LiveRange* range)
+{
+    // Check for direct uses of this range.
+    if (range->hasUses() || range->hasDefinition())
+        return false;
+
+    CodePosition start = range->from();
+    LNode* ins = insData[start];
+    if (start == entryOf(ins->block()))
+        return false;
+
+    VirtualRegister& reg = vregs[range->vreg()];
+
+    // Check if there are later ranges for this vreg.
+    LiveRange::RegisterLinkIterator iter = reg.rangesBegin(range);
+    for (iter++; iter; iter++) {
+        LiveRange* laterRange = LiveRange::get(*iter);
+        if (laterRange->from() > range->from())
+            return false;
+    }
+
+    // Check if this range ends at a loop backedge.
+    LNode* last = insData[range->to().previous()];
+    if (last->isGoto() && last->toGoto()->target()->id() < last->block()->mir()->id())
+        return false;
+
+    // Check if there are phis which this vreg flows to.
+    if (reg.usedByPhi())
+        return false;
+
+    return true;
+}
+
+bool
 BacktrackingAllocator::resolveControlFlow()
 {
     // Add moves to handle changing assignments for vregs over their lifetime.
     JitSpew(JitSpew_RegAlloc, "Resolving control flow (vreg loop)");
 
     // Look for places where a register's assignment changes in the middle of a
     // basic block.
     MOZ_ASSERT(!vregs[0u].hasRanges());
     for (size_t i = 1; i < graph.numVirtualRegisters(); i++) {
         VirtualRegister& reg = vregs[i];
 
         if (mir->shouldCancel("Backtracking Resolve Control Flow (vreg loop)"))
             return false;
 
-        for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; iter++) {
+        for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; ) {
             LiveRange* range = LiveRange::get(*iter);
 
+            // Remove ranges which will never be used.
+            if (deadRange(range)) {
+                reg.removeRangeAndIncrement(iter);
+                continue;
+            }
+
             // The range which defines the register does not have a predecessor
             // to add moves from.
-            if (range->hasDefinition())
+            if (range->hasDefinition()) {
+                iter++;
                 continue;
+            }
 
             // Ignore ranges that start at block boundaries. We will handle
             // these in the next phase.
             CodePosition start = range->from();
             LNode* ins = insData[start];
-            if (start == entryOf(ins->block()))
+            if (start == entryOf(ins->block())) {
+                iter++;
                 continue;
+            }
 
             // If we already saw a range which covers the start of this range
             // and has the same allocation, we don't need an explicit move at
             // the start of this range.
             bool skip = false;
             for (LiveRange::RegisterLinkIterator prevIter = reg.rangesBegin();
                  prevIter != iter;
                  prevIter++)
@@ -1711,27 +1756,31 @@ BacktrackingAllocator::resolveControlFlo
                 LiveRange* prevRange = LiveRange::get(*prevIter);
                 if (prevRange->covers(start) &&
                     prevRange->bundle()->allocation() == range->bundle()->allocation())
                 {
                     skip = true;
                     break;
                 }
             }
-            if (skip)
+            if (skip) {
+                iter++;
                 continue;
+            }
 
             LiveRange* predecessorRange = reg.rangeFor(start.previous(), /* preferRegister = */ true);
             if (start.subpos() == CodePosition::INPUT) {
                 if (!moveInput(ins->toInstruction(), predecessorRange, range, reg.type()))
                     return false;
             } else {
                 if (!moveAfter(ins->toInstruction(), predecessorRange, range, reg.type()))
                     return false;
             }
+
+            iter++;
         }
     }
 
     JitSpew(JitSpew_RegAlloc, "Resolving control flow (block loop)");
 
     for (size_t i = 0; i < graph.numBlocks(); i++) {
         if (mir->shouldCancel("Backtracking Resolve Control Flow (block loop)"))
             return false;
@@ -2411,51 +2460,60 @@ BacktrackingAllocator::computeSpillWeigh
 {
     // Minimal bundles have an extremely high spill weight, to ensure they
     // can evict any other bundles and be allocated to a register.
     bool fixed;
     if (minimalBundle(bundle, &fixed))
         return fixed ? 2000000 : 1000000;
 
     size_t usesTotal = 0;
+    fixed = false;
 
     for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; iter++) {
         LiveRange* range = LiveRange::get(*iter);
 
         if (range->hasDefinition()) {
             VirtualRegister& reg = vregs[range->vreg()];
-            if (reg.def()->policy() == LDefinition::FIXED && reg.def()->output()->isRegister())
+            if (reg.def()->policy() == LDefinition::FIXED && reg.def()->output()->isRegister()) {
                 usesTotal += 2000;
-            else if (!reg.ins()->isPhi())
+                fixed = true;
+            } else if (!reg.ins()->isPhi()) {
                 usesTotal += 2000;
+            }
         }
 
         for (UsePositionIterator iter = range->usesBegin(); iter; iter++) {
             LUse* use = iter->use;
 
             switch (use->policy()) {
               case LUse::ANY:
                 usesTotal += 1000;
                 break;
 
+              case LUse::FIXED:
+                fixed = true;
               case LUse::REGISTER:
-              case LUse::FIXED:
                 usesTotal += 2000;
                 break;
 
               case LUse::KEEPALIVE:
                 break;
 
               default:
                 // Note: RECOVERED_INPUT will not appear in UsePositionIterator.
                 MOZ_CRASH("Bad use");
             }
         }
     }
 
+    // Bundles with fixed uses are given a higher spill weight, since they must
+    // be allocated to a specific register.
+    if (testbed && fixed)
+        usesTotal *= 2;
+
     // Compute spill weight as a use density, lowering the weight for long
     // lived bundles with relatively few uses.
     size_t lifetimeTotal = computePriority(bundle);
     return lifetimeTotal ? usesTotal / lifetimeTotal : 0;
 }
 
 size_t
 BacktrackingAllocator::maximumSpillWeight(const LiveBundleVector& bundles)
--- a/js/src/jit/BacktrackingAllocator.h
+++ b/js/src/jit/BacktrackingAllocator.h
@@ -458,16 +458,20 @@ class VirtualRegister
 
     // All live ranges for this register. These may overlap each other, and are
     // ordered by their start position.
     InlineForwardList<LiveRange::RegisterLink> ranges_;
 
     // Whether def_ is a temp or an output.
     bool isTemp_;
 
+    // Whether this vreg is an input for some phi. This use is not reflected in
+    // any range on the vreg.
+    bool usedByPhi_;
+
     // If this register's definition is MUST_REUSE_INPUT, whether a copy must
     // be introduced before the definition that relaxes the policy.
     bool mustCopyInput_;
 
     void operator=(const VirtualRegister&) = delete;
     VirtualRegister(const VirtualRegister&) = delete;
 
   public:
@@ -500,39 +504,53 @@ class VirtualRegister
     }
     bool isCompatible(const VirtualRegister& vr) const {
         return def_->isCompatibleDef(*vr.def_);
     }
     bool isTemp() const {
         return isTemp_;
     }
 
+    void setUsedByPhi() {
+        usedByPhi_ = true;
+    }
+    bool usedByPhi() {
+        return usedByPhi_;
+    }
+
     void setMustCopyInput() {
         mustCopyInput_ = true;
     }
     bool mustCopyInput() {
         return mustCopyInput_;
     }
 
     LiveRange::RegisterLinkIterator rangesBegin() const {
         return ranges_.begin();
     }
+    LiveRange::RegisterLinkIterator rangesBegin(LiveRange* range) const {
+        return ranges_.begin(&range->registerLink);
+    }
     bool hasRanges() const {
         return !!rangesBegin();
     }
     LiveRange* firstRange() const {
         return LiveRange::get(*rangesBegin());
     }
     LiveRange* lastRange() const {
         return LiveRange::get(ranges_.back());
     }
     LiveRange* rangeFor(CodePosition pos, bool preferRegister = false) const;
     void removeRange(LiveRange* range);
     void addRange(LiveRange* range);
 
+    void removeRangeAndIncrement(LiveRange::RegisterLinkIterator& iter) {
+        ranges_.removeAndIncrement(iter);
+    }
+
     LiveBundle* firstBundle() const {
         return firstRange()->bundle();
     }
 
     bool addInitialRange(TempAllocator& alloc, CodePosition from, CodePosition to);
     void addInitialUse(UsePosition* use);
     void setInitialDefinition(CodePosition from);
 };
@@ -660,16 +678,17 @@ class BacktrackingAllocator : protected 
     bool insertAllRanges(LiveRangeSet& set, LiveBundle* bundle);
 
     // Reification methods.
     bool pickStackSlots();
     bool resolveControlFlow();
     bool reifyAllocations();
     bool populateSafepoints();
     bool annotateMoveGroups();
+    bool deadRange(LiveRange* range);
     size_t findFirstNonCallSafepoint(CodePosition from);
     size_t findFirstSafepoint(CodePosition pos, size_t startFrom);
     void addLiveRegistersForRange(VirtualRegister& reg, LiveRange* range);
 
     bool addMove(LMoveGroup* moves, LiveRange* from, LiveRange* to, LDefinition::Type type) {
         LAllocation fromAlloc = from->bundle()->allocation();
         LAllocation toAlloc = to->bundle()->allocation();
         MOZ_ASSERT(fromAlloc != toAlloc);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1,14 +1,15 @@
 /* -*- 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 "mozilla/ScopeExit.h"
 #include "mozilla/SizePrintfMacros.h"
 
 #include "jsprf.h"
 #include "jsutil.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/CompileInfo.h"
@@ -1401,16 +1402,23 @@ jit::BailoutIonToBaseline(JSContext* cx,
 {
     MOZ_ASSERT(bailoutInfo != nullptr);
     MOZ_ASSERT(*bailoutInfo == nullptr);
 
     TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
     TraceLogStopEvent(logger, TraceLogger_IonMonkey);
     TraceLogStartEvent(logger, TraceLogger_Baseline);
 
+    // Ion bailout can fail due to overrecursion and OOM. In such cases we
+    // cannot honor any further Debugger hooks on the frame, and need to
+    // ensure that its Debugger.Frame entry is cleaned up.
+    auto guardRemoveRematerializedFramesFromDebugger = mozilla::MakeScopeExit([&] {
+        activation->removeRematerializedFramesFromDebugger(cx, iter.fp());
+    });
+
     // The caller of the top frame must be one of the following:
     //      IonJS - Ion calling into Ion.
     //      BaselineStub - Baseline calling into Ion.
     //      Entry - Interpreter or other calling into Ion.
     //      Rectifier - Arguments rectifier calling into Ion.
     MOZ_ASSERT(iter.isBailoutJS());
     mozilla::DebugOnly<FrameType> prevFrameType = iter.prevType();
     MOZ_ASSERT(prevFrameType == JitFrame_IonJS ||
@@ -1586,16 +1594,17 @@ jit::BailoutIonToBaseline(JSContext* cx,
         return BAILOUT_RETURN_OVERRECURSED;
     }
 
     // Take the reconstructed baseline stack so it doesn't get freed when builder destructs.
     info = builder.takeBuffer();
     info->numFrames = frameNo + 1;
     info->bailoutKind = bailoutKind;
     *bailoutInfo = info;
+    guardRemoveRematerializedFramesFromDebugger.release();
     return BAILOUT_RETURN_OK;
 }
 
 static bool
 InvalidateAfterBailout(JSContext* cx, HandleScript outerScript, const char* reason)
 {
     // In some cases, the computation of recover instruction can invalidate the
     // Ion script before we reach the end of the bailout. Thus, if the outer
--- a/js/src/jit/InlineList.h
+++ b/js/src/jit/InlineList.h
@@ -59,16 +59,19 @@ class InlineForwardList : protected Inli
 
   public:
     typedef InlineForwardListIterator<T> iterator;
 
   public:
     iterator begin() const {
         return iterator(this);
     }
+    iterator begin(Node* item) const {
+        return iterator(this, item);
+    }
     iterator end() const {
         return iterator(nullptr);
     }
     void removeAt(iterator where) {
         removeAfter(where.prev, where.iter);
     }
     void pushFront(Node* t) {
         insertAfter(this, t);
@@ -161,16 +164,25 @@ private:
       : prev(const_cast<Node*>(static_cast<const Node*>(owner))),
         iter(owner ? owner->next : nullptr)
 #ifdef DEBUG
       , owner_(owner),
         modifyCount_(owner ? owner->modifyCount_ : 0)
 #endif
     { }
 
+    InlineForwardListIterator<T>(const InlineForwardList<T>* owner, Node* node)
+      : prev(nullptr),
+        iter(node)
+#ifdef DEBUG
+      , owner_(owner),
+        modifyCount_(owner ? owner->modifyCount_ : 0)
+#endif
+    { }
+
 public:
     InlineForwardListIterator<T> & operator ++() {
         MOZ_ASSERT(modifyCount_ == owner_->modifyCount_);
         prev = iter;
         iter = iter->next;
         return *this;
     }
     InlineForwardListIterator<T> operator ++(int) {
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -461,23 +461,16 @@ HandleExceptionIon(JSContext* cx, const 
             // exception due to debug mode, which BailoutIonToBaseline needs to
             // know. This is because we might not be able to fully reconstruct up
             // to the stack depth at the snapshot, as we could've thrown in the
             // middle of a call.
             ExceptionBailoutInfo propagateInfo;
             uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed);
             if (retval == BAILOUT_RETURN_OK)
                 return;
-
-            // If bailout failed (e.g., due to overrecursion), clean up any
-            // Debugger.Frame instances here. Normally this should happen
-            // inside the debug epilogue, but due to bailout failure, we
-            // cannot honor any Debugger hooks.
-            if (rematFrame)
-                Debugger::handleUnrecoverableIonBailoutError(cx, rematFrame);
         }
 
         MOZ_ASSERT_IF(rematFrame, !Debugger::inFrameMaps(rematFrame));
     }
 
     RootedScript script(cx, frame.script());
     if (!script->hasTrynotes())
         return;
--- a/js/src/jit/RematerializedFrame.cpp
+++ b/js/src/jit/RematerializedFrame.cpp
@@ -154,16 +154,18 @@ RematerializedFrame::initFunctionScopeOb
 
 void
 RematerializedFrame::mark(JSTracer* trc)
 {
     TraceRoot(trc, &script_, "remat ion frame script");
     TraceRoot(trc, &scopeChain_, "remat ion frame scope chain");
     if (callee_)
         TraceRoot(trc, &callee_, "remat ion frame callee");
+    if (argsObj_)
+        TraceRoot(trc, &argsObj_, "remat ion frame argsobj");
     TraceRoot(trc, &returnValue_, "remat ion frame return value");
     TraceRoot(trc, &thisValue_, "remat ion frame this");
     TraceRootRange(trc, numActualArgs_ + isConstructing_ + script_->nfixed(),
                    slots_, "remat ion frame stack");
 }
 
 void
 RematerializedFrame::dump()
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1960,36 +1960,47 @@ Debugger::updateExecutionObservabilityOf
     {
         jit::JitContext jctx(cx, nullptr);
         if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
+    AbstractFramePtr oldestEnabledFrame;
     for (ScriptFrameIter iter(cx, ScriptFrameIter::ALL_CONTEXTS,
                               ScriptFrameIter::GO_THROUGH_SAVED);
          !iter.done();
          ++iter)
     {
         if (obs.shouldMarkAsDebuggee(iter)) {
             if (observing) {
-                iter.abstractFramePtr().setIsDebuggee();
+                if (!iter.abstractFramePtr().isDebuggee()) {
+                    oldestEnabledFrame = iter.abstractFramePtr();
+                    oldestEnabledFrame.setIsDebuggee();
+                }
             } else {
 #ifdef DEBUG
                 // Debugger.Frame lifetimes are managed by the debug epilogue,
                 // so in general it's unsafe to unmark a frame if it has a
                 // Debugger.Frame associated with it.
                 FrameRange r(iter.abstractFramePtr());
                 MOZ_ASSERT(r.empty());
 #endif
                 iter.abstractFramePtr().unsetIsDebuggee();
             }
         }
     }
+
+    // See comment in unsetPrevUpToDateUntil.
+    if (oldestEnabledFrame) {
+        AutoCompartment ac(cx, oldestEnabledFrame.compartment());
+        DebugScopes::unsetPrevUpToDateUntil(cx, oldestEnabledFrame);
+    }
+
     return true;
 }
 
 static inline void
 MarkBaselineScriptActiveIfObservable(JSScript* script, const Debugger::ExecutionObservableSet& obs)
 {
     if (obs.shouldRecompileOrInvalidate(script))
         script->baselineScript()->setActive();
@@ -5892,27 +5903,27 @@ CheckThisFrame(JSContext* cx, const Call
     RootedNativeObject thisobj(cx, CheckThisFrame(cx, args, fnname, true));    \
     if (!thisobj)                                                              \
         return false
 
 #define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame)                 \
     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
     if (frame.isScriptFrameIterData()) {                                       \
-        ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw()));          \
+        ScriptFrameIter iter(cx, *(ScriptFrameIter::Data*)(frame.raw()));      \
         frame = iter.abstractFramePtr();                                       \
     }
 
 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter)  \
     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
     Maybe<ScriptFrameIter> maybeIter;                                          \
     {                                                                          \
         AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
         if (f.isScriptFrameIterData()) {                                       \
-            maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw()));             \
+            maybeIter.emplace(cx, *(ScriptFrameIter::Data*)(f.raw()));         \
         } else {                                                               \
             maybeIter.emplace(cx, ScriptFrameIter::ALL_CONTEXTS,               \
                               ScriptFrameIter::GO_THROUGH_SAVED,               \
                               ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
             ScriptFrameIter& iter = *maybeIter;                                \
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
                 ++iter;                                                        \
             AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();         \
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -2032,18 +2032,16 @@ DebugScopes::hasDebugScope(JSContext* cx
     return nullptr;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
 {
     MOZ_ASSERT(!si.hasSyntacticScopeObject());
     MOZ_ASSERT(cx->compartment() == debugScope.compartment());
-    MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
-                  !si.initialFrame().callee()->isGenerator());
     // Generators should always reify their scopes.
     MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
 
     DebugScopes* scopes = ensureCompartmentData(cx);
     if (!scopes)
@@ -2284,23 +2282,24 @@ DebugScopes::hasLiveScope(ScopeObject& s
         return &p->value();
 
     return nullptr;
 }
 
 /* static */ void
 DebugScopes::unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr until)
 {
-    // This is the one exception where fp->prevUpToDate() is cleared without
-    // popping the frame. When a frame is rematerialized, all frames younger
-    // than the rematerialized frame have their prevUpToDate set to
-    // false. This is because unrematerialized Ion frames have no usable
-    // AbstractFramePtr, and so are skipped by the updateLiveScopes. If in the
-    // future a frame suddenly gains a usable AbstractFramePtr via
-    // rematerialization, the prevUpToDate invariant will no longer hold.
+    // This are two exceptions where fp->prevUpToDate() is cleared without
+    // popping the frame. When a frame is rematerialized or has its
+    // debuggeeness toggled off->on, all frames younger than the frame must
+    // have their prevUpToDate set to false. This is because unrematerialized
+    // Ion frames and non-debuggee frames are skipped by updateLiveScopes. If
+    // in the future a frame suddenly gains a usable AbstractFramePtr via
+    // rematerialization or becomes a debuggee, the prevUpToDate invariant
+    // will no longer hold for older frames on its stack.
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (!i.hasUsableAbstractFramePtr())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame == until)
             return;
 
@@ -2414,18 +2413,25 @@ GetDebugScopeForMissing(JSContext* cx, c
             if (!enclosingDebug)
                 return nullptr;
         }
 
         debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug);
         break;
       }
       case ScopeIter::Block: {
-        // Generators should always reify their scopes.
-        MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
+        // Generators should always reify their scopes, except in this one
+        // weird case of deprecated let expressions where we can create a
+        // 0-variable StaticBlockObject inside a generator that does not need
+        // cloning.
+        //
+        // For example, |let ({} = "") { yield evalInFrame("foo"); }|.
+        MOZ_ASSERT_IF(si.staticBlock().numVariables() > 0 &&
+                      si.withinInitialFrame() &&
+                      si.initialFrame().isFunctionFrame(),
                       !si.initialFrame().callee()->isGenerator());
 
         Rooted<StaticBlockObject*> staticBlock(cx, &si.staticBlock());
         ClonedBlockObject* block;
         if (si.withinInitialFrame())
             block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
         else
             block = ClonedBlockObject::createHollowForDebug(cx, staticBlock);
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -12,16 +12,17 @@
 
 #include "asmjs/AsmJSFrameIterator.h"
 #include "asmjs/AsmJSModule.h"
 #include "gc/Marking.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitCompartment.h"
 #include "js/GCAPI.h"
+#include "vm/Debugger.h"
 #include "vm/Opcodes.h"
 
 #include "jit/JitFrameIterator-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/ScopeObject-inl.h"
 
 using namespace js;
@@ -588,17 +589,17 @@ FrameIter::Data::Data(JSContext* cx, Sav
     activations_(cx->runtime()),
     jitFrames_(),
     ionInlineFrameNo_(0),
     asmJSFrames_()
 {
 }
 
 FrameIter::Data::Data(const FrameIter::Data& other)
-  : cx_(other.cx_),
+  : cx_(nullptr),
     savedOption_(other.savedOption_),
     contextOption_(other.contextOption_),
     debuggerEvalOption_(other.debuggerEvalOption_),
     principals_(other.principals_),
     state_(other.state_),
     pc_(other.pc_),
     interpFrames_(other.interpFrames_),
     activations_(other.activations_),
@@ -638,21 +639,22 @@ FrameIter::FrameIter(JSContext* cx, Cont
 
 FrameIter::FrameIter(const FrameIter& other)
   : data_(other.data_),
     ionInlineFrames_(other.data_.cx_,
                      data_.jitFrames_.isIonScripted() ? &other.ionInlineFrames_ : nullptr)
 {
 }
 
-FrameIter::FrameIter(const Data& data)
+FrameIter::FrameIter(JSContext* cx, const Data& data)
   : data_(data),
-    ionInlineFrames_(data.cx_, data_.jitFrames_.isIonScripted() ? &data_.jitFrames_ : nullptr)
+    ionInlineFrames_(cx, data_.jitFrames_.isIonScripted() ? &data_.jitFrames_ : nullptr)
 {
-    MOZ_ASSERT(data.cx_);
+    MOZ_ASSERT(!data.cx_);
+    data_.cx_ = cx;
 
     if (data_.jitFrames_.isIonScripted()) {
         while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_)
             ++ionInlineFrames_;
     }
 }
 
 void
@@ -1546,18 +1548,17 @@ jit::JitActivation::getRematerializedFra
         AutoCompartment ac(cx, compartment_);
 
         if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter, recover,
                                                             p->value()))
         {
             return nullptr;
         }
 
-        // All frames younger than the rematerialized frame need to have their
-        // prevUpToDate flag cleared.
+        // See comment in unsetPrevUpToDateUntil.
         DebugScopes::unsetPrevUpToDateUntil(cx, p->value()[inlineDepth]);
     }
 
     return p->value()[inlineDepth];
 }
 
 jit::RematerializedFrame*
 jit::JitActivation::lookupRematerializedFrame(uint8_t* top, size_t inlineDepth)
@@ -1565,16 +1566,30 @@ jit::JitActivation::lookupRematerialized
     if (!rematerializedFrames_)
         return nullptr;
     if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top))
         return inlineDepth < p->value().length() ? p->value()[inlineDepth] : nullptr;
     return nullptr;
 }
 
 void
+jit::JitActivation::removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top)
+{
+    // Ion bailout can fail due to overrecursion and OOM. In such cases we
+    // cannot honor any further Debugger hooks on the frame, and need to
+    // ensure that its Debugger.Frame entry is cleaned up.
+    if (!cx->compartment()->isDebuggee() || !rematerializedFrames_)
+        return;
+    if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
+        for (uint32_t i = 0; i < p->value().length(); i++)
+            Debugger::handleUnrecoverableIonBailoutError(cx, p->value()[i]);
+    }
+}
+
+void
 jit::JitActivation::markRematerializedFrames(JSTracer* trc)
 {
     if (!rematerializedFrames_)
         return;
     for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); e.popFront())
         RematerializedFrame::MarkInVector(trc, e.front().value());
 }
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1599,16 +1599,20 @@ class JitActivation : public Activation
     // The inlineDepth must be within bounds of the frame pointed to by iter.
     RematerializedFrame* getRematerializedFrame(JSContext* cx, const JitFrameIterator& iter,
                                                 size_t inlineDepth = 0);
 
     // Look up a rematerialized frame by the fp. If inlineDepth is out of
     // bounds of what has been rematerialized, nullptr is returned.
     RematerializedFrame* lookupRematerializedFrame(uint8_t* top, size_t inlineDepth = 0);
 
+    // Remove all rematerialized frames associated with the fp top from the
+    // Debugger.
+    void removeRematerializedFramesFromDebugger(JSContext* cx, uint8_t* top);
+
     bool hasRematerializedFrame(uint8_t* top, size_t inlineDepth = 0) {
         return !!lookupRematerializedFrame(top, inlineDepth);
     }
 
     // Remove a previous rematerialization by fp.
     void removeRematerializedFrame(uint8_t* top);
 
     void markRematerializedFrames(JSTracer* trc);
@@ -1842,17 +1846,17 @@ class FrameIter
         Data(const Data& other);
     };
 
     MOZ_IMPLICIT FrameIter(JSContext* cx, SavedOption = STOP_AT_SAVED);
     FrameIter(JSContext* cx, ContextOption, SavedOption,
               DebuggerEvalOption = FOLLOW_DEBUGGER_EVAL_PREV_LINK);
     FrameIter(JSContext* cx, ContextOption, SavedOption, DebuggerEvalOption, JSPrincipals*);
     FrameIter(const FrameIter& iter);
-    MOZ_IMPLICIT FrameIter(const Data& data);
+    FrameIter(JSContext* cx, const Data& data);
     MOZ_IMPLICIT FrameIter(AbstractFramePtr frame);
 
     bool done() const { return data_.state_ == DONE; }
 
     // -------------------------------------------------------
     // The following functions can only be called when !done()
     // -------------------------------------------------------
 
@@ -1954,16 +1958,21 @@ class FrameIter
 
     // -----------------------------------------------------------
     // The following functions can only be called when isInterp(),
     // isBaseline(), or isIon(). Further, abstractFramePtr() can
     // only be called when hasUsableAbstractFramePtr().
     // -----------------------------------------------------------
 
     AbstractFramePtr abstractFramePtr() const;
+
+    // N.B. Copying the internal data nulls out the saved cx_, as the
+    // JSContext's lifetime is not tied to the Data lifetime. When
+    // re-instantiating a new FrameIter with a saved data, a new cx must be
+    // provided.
     AbstractFramePtr copyDataAsAbstractFramePtr() const;
     Data* copyData() const;
 
     // This can only be called when isInterp():
     inline InterpreterFrame* interpFrame() const;
 
     // This can only be called when isPhysicalIonFrame():
     inline jit::CommonFrameLayout* physicalIonFrame() const;
@@ -2009,17 +2018,17 @@ class ScriptFrameIter : public FrameIter
                     DebuggerEvalOption debuggerEvalOption,
                     JSPrincipals* prin)
       : FrameIter(cx, cxOption, savedOption, debuggerEvalOption, prin)
     {
         settle();
     }
 
     ScriptFrameIter(const ScriptFrameIter& iter) : FrameIter(iter) { settle(); }
-    explicit ScriptFrameIter(const FrameIter::Data& data) : FrameIter(data) { settle(); }
+    ScriptFrameIter(JSContext* cx, const FrameIter::Data& data) : FrameIter(cx, data) { settle(); }
     explicit ScriptFrameIter(AbstractFramePtr frame) : FrameIter(frame) { settle(); }
 
     ScriptFrameIter& operator++() {
         FrameIter::operator++();
         settle();
         return *this;
     }
 };
@@ -2069,18 +2078,18 @@ class NonBuiltinFrameIter : public Frame
 
     NonBuiltinFrameIter(JSContext* cx, JSPrincipals* principals)
         : FrameIter(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED,
                     FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, principals)
     {
         settle();
     }
 
-    explicit NonBuiltinFrameIter(const FrameIter::Data& data)
-      : FrameIter(data)
+    explicit NonBuiltinFrameIter(JSContext* cx, const FrameIter::Data& data)
+      : FrameIter(cx, data)
     {}
 
     NonBuiltinFrameIter& operator++() {
         FrameIter::operator++();
         settle();
         return *this;
     }
 };
@@ -2114,18 +2123,18 @@ class NonBuiltinScriptFrameIter : public
                               ScriptFrameIter::SavedOption savedOption,
                               ScriptFrameIter::DebuggerEvalOption debuggerEvalOption,
                               JSPrincipals* principals)
       : ScriptFrameIter(cx, contextOption, savedOption, debuggerEvalOption, principals)
     {
         settle();
     }
 
-    explicit NonBuiltinScriptFrameIter(const ScriptFrameIter::Data& data)
-      : ScriptFrameIter(data)
+    explicit NonBuiltinScriptFrameIter(JSContext* cx, const ScriptFrameIter::Data& data)
+      : ScriptFrameIter(cx, data)
     {}
 
     NonBuiltinScriptFrameIter& operator++() {
         ScriptFrameIter::operator++();
         settle();
         return *this;
     }
 };
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -810,20 +810,22 @@ namespace mozilla {
 
 // -------------------------------
 // CSS Style Sheet Inner Data Container
 //
 
 
 CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
                                        CORSMode aCORSMode,
-                                       ReferrerPolicy aReferrerPolicy)
+                                       ReferrerPolicy aReferrerPolicy,
+                                       const SRIMetadata& aIntegrity)
   : mSheets()
   , mCORSMode(aCORSMode)
   , mReferrerPolicy (aReferrerPolicy)
+  , mIntegrity(aIntegrity)
   , mComplete(false)
 #ifdef DEBUG
   , mPrincipalSet(false)
 #endif
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
   mSheets.AppendElement(aPrimarySheet);
 
@@ -935,16 +937,17 @@ CSSStyleSheetInner::CSSStyleSheetInner(C
                                        CSSStyleSheet* aPrimarySheet)
   : mSheets(),
     mSheetURI(aCopy.mSheetURI),
     mOriginalSheetURI(aCopy.mOriginalSheetURI),
     mBaseURI(aCopy.mBaseURI),
     mPrincipal(aCopy.mPrincipal),
     mCORSMode(aCopy.mCORSMode),
     mReferrerPolicy(aCopy.mReferrerPolicy),
+    mIntegrity(aCopy.mIntegrity),
     mComplete(aCopy.mComplete)
 #ifdef DEBUG
     , mPrincipalSet(aCopy.mPrincipalSet)
 #endif
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
   AddSheet(aPrimarySheet);
   aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules);
@@ -1075,17 +1078,36 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aC
     mDocument(nullptr),
     mOwningNode(nullptr),
     mDisabled(false),
     mDirty(false),
     mInRuleProcessorCache(false),
     mScopeElement(nullptr),
     mRuleProcessors(nullptr)
 {
-  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy);
+  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+                                  SRIMetadata());
+}
+
+CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode,
+                             ReferrerPolicy aReferrerPolicy,
+                             const SRIMetadata& aIntegrity)
+  : mTitle(),
+    mParent(nullptr),
+    mOwnerRule(nullptr),
+    mDocument(nullptr),
+    mOwningNode(nullptr),
+    mDisabled(false),
+    mDirty(false),
+    mInRuleProcessorCache(false),
+    mScopeElement(nullptr),
+    mRuleProcessors(nullptr)
+{
+  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+                                  aIntegrity);
 }
 
 CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy,
                              CSSStyleSheet* aParentToUse,
                              css::ImportRule* aOwnerRuleToUse,
                              nsIDocument* aDocumentToUse,
                              nsINode* aOwningNodeToUse)
   : mTitle(aCopy.mTitle),
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -21,16 +21,17 @@
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsString.h"
 #include "mozilla/CORSMode.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/dom/SRIMetadata.h"
 
 class CSSRuleListImpl;
 class nsCSSRuleProcessor;
 class nsIPrincipal;
 class nsIURI;
 class nsMediaList;
 class nsMediaQueryResultCacheKey;
 class nsPresContext;
@@ -58,17 +59,18 @@ class CSSStyleSheetInner
 public:
   friend class mozilla::CSSStyleSheet;
   friend class ::nsCSSRuleProcessor;
   typedef net::ReferrerPolicy ReferrerPolicy;
 
 private:
   CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
                      CORSMode aCORSMode,
-                     ReferrerPolicy aReferrerPolicy);
+                     ReferrerPolicy aReferrerPolicy,
+                     const dom::SRIMetadata& aIntegrity);
   CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
                      CSSStyleSheet* aPrimarySheet);
   ~CSSStyleSheetInner();
 
   CSSStyleSheetInner* CloneFor(CSSStyleSheet* aPrimarySheet);
   void AddSheet(CSSStyleSheet* aSheet);
   void RemoveSheet(CSSStyleSheet* aSheet);
 
@@ -91,16 +93,17 @@ private:
   // currently this is the case) that any time page JS can get ts hands on a
   // child sheet that means we've already ensured unique inners throughout its
   // parent chain and things are good.
   nsRefPtr<CSSStyleSheet> mFirstChild;
   CORSMode               mCORSMode;
   // The Referrer Policy of a stylesheet is used for its child sheets, so it is
   // stored here.
   ReferrerPolicy         mReferrerPolicy;
+  dom::SRIMetadata       mIntegrity;
   bool                   mComplete;
 
 #ifdef DEBUG
   bool                   mPrincipalSet;
 #endif
 };
 
 
@@ -118,16 +121,18 @@ private:
 class CSSStyleSheet final : public nsIStyleSheet,
                             public nsIDOMCSSStyleSheet,
                             public nsICSSLoaderObserver,
                             public nsWrapperCache
 {
 public:
   typedef net::ReferrerPolicy ReferrerPolicy;
   CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy);
+  CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy,
+                const dom::SRIMetadata& aIntegrity);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CSSStyleSheet,
                                                          nsIStyleSheet)
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_STYLE_SHEET_IMPL_CID)
 
   // nsIStyleSheet interface
@@ -254,16 +259,19 @@ public:
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   // Get this style sheet's CORS mode
   CORSMode GetCORSMode() const { return mInner->mCORSMode; }
 
   // Get this style sheet's Referrer Policy
   ReferrerPolicy GetReferrerPolicy() const { return mInner->mReferrerPolicy; }
 
+  // Get this style sheet's integrity metadata
+  dom::SRIMetadata GetIntegrity() const { return mInner->mIntegrity; }
+
   dom::Element* GetScopeElement() const { return mScopeElement; }
   void SetScopeElement(dom::Element* aScopeElement)
   {
     mScopeElement = aScopeElement;
   }
 
   // WebIDL StyleSheet API
   // Our nsIStyleSheet::GetType is a const method, so it ends up
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -57,16 +57,17 @@
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIMediaList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsError.h"
 
 #include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/SRICheck.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 using mozilla::dom::EncodingUtils;
 
 using namespace mozilla::dom;
 
 /**
  * OVERALL ARCHITECTURE
@@ -260,16 +261,26 @@ static PRLogModuleInfo *
 GetLoaderLog()
 {
   static PRLogModuleInfo *sLog;
   if (!sLog)
     sLog = PR_NewLogModule("nsCSSLoader");
   return sLog;
 }
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 #define LOG_ERROR(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Error, args)
 #define LOG_WARN(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Warning, args)
 #define LOG_DEBUG(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Debug, args)
 #define LOG(args) LOG_DEBUG(args)
 
 #define LOG_ERROR_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Error)
 #define LOG_WARN_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Warning)
 #define LOG_DEBUG_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Debug)
@@ -923,16 +934,28 @@ SheetLoadData::OnStreamComplete(nsIUnich
     if (errorFlag == nsIScriptError::errorFlag) {
       LOG_WARN(("  Ignoring sheet with improper MIME type %s",
                 contentType.get()));
       mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
   }
 
+  SRIMetadata sriMetadata = mSheet->GetIntegrity();
+  if (!sriMetadata.IsEmpty() &&
+      NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, channelURI,
+                                          mSheet->GetCORSMode(), aBuffer,
+                                          mLoader->mDocument))) {
+    LOG(("  Load was blocked by SRI"));
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+            ("css::Loader::OnStreamComplete, bad metadata"));
+    mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
+    return NS_OK;
+  }
+
   // Enough to set the URIs on mSheet, since any sibling datas we have share
   // the same mInner as mSheet and will thus get the same URI.
   mSheet->SetURIs(channelURI, originalURI, channelURI);
 
   bool completed;
   result = mLoader->ParseSheet(aBuffer, this, completed);
   NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
   return result;
@@ -1052,16 +1075,17 @@ Loader::CheckLoadAllowed(nsIPrincipal* a
  * CreateSheet().
  */
 nsresult
 Loader::CreateSheet(nsIURI* aURI,
                     nsIContent* aLinkingContent,
                     nsIPrincipal* aLoaderPrincipal,
                     CORSMode aCORSMode,
                     ReferrerPolicy aReferrerPolicy,
+                    const nsAString& aIntegrity,
                     bool aSyncLoad,
                     bool aHasAlternateRel,
                     const nsAString& aTitle,
                     StyleSheetState& aSheetState,
                     bool *aIsAlternate,
                     CSSStyleSheet** aSheet)
 {
   LOG(("css::Loader::CreateSheet"));
@@ -1199,17 +1223,27 @@ Loader::CreateSheet(nsIURI* aURI,
       sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
       originalURI = nullptr;
     } else {
       baseURI = aURI;
       sheetURI = aURI;
       originalURI = aURI;
     }
 
-    nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode, aReferrerPolicy);
+    SRIMetadata sriMetadata;
+    if (!aIntegrity.IsEmpty()) {
+      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+              ("css::Loader::CreateSheet, integrity=%s",
+               NS_ConvertUTF16toUTF8(aIntegrity).get()));
+      SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
+    }
+
+    nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode,
+                                                      aReferrerPolicy,
+                                                      sriMetadata);
     sheet->SetURIs(sheetURI, originalURI, baseURI);
     sheet.forget(aSheet);
   }
 
   NS_ASSERTION(*aSheet, "We should have a sheet by now!");
   NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
   LOG(("  State: %s", gStateStrings[aSheetState]));
 
@@ -1410,16 +1444,18 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
        (nsContentUtils::URIIsLocalFile(aLoadData->mURI) &&
         NS_SUCCEEDED(aLoadData->mLoaderPrincipal->
                      CheckMayLoad(aLoadData->mURI, false, false))));
   }
   else {
     triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
   }
 
+  SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity();
+
   if (aLoadData->mSyncLoad) {
     LOG(("  Synchronous load"));
     NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
     NS_ASSERTION(aSheetState == eSheetNeedsParser,
                  "Sync loads can't reuse existing async loads");
 
     // Create a nsIUnicharStreamLoader instance to which we will feed
     // the data from the sync load.  Do this before creating the
@@ -1594,17 +1630,17 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
     nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
     if (cos) {
       cos->AddClassFlags(nsIClassOfService::Leader);
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
-    // send a minimal Accept header for text/css
+    // Send a minimal Accept header for text/css
     httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                   NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
                                   false);
     nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
     if (referrerURI)
       httpChannel->SetReferrerWithPolicy(referrerURI,
                                          aLoadData->mSheet->GetReferrerPolicy());
 
@@ -1927,18 +1963,20 @@ Loader::LoadInlineStyle(nsIContent* aEle
   NS_ASSERTION(owningElement, "Element is not a style linking element!");
 
   // Since we're not planning to load a URI, no need to hand a principal to the
   // load data or to CreateSheet().  Also, OK to use CORS_NONE for the CORS
   // mode and mDocument's ReferrerPolicy.
   StyleSheetState state;
   nsRefPtr<CSSStyleSheet> sheet;
   nsresult rv = CreateSheet(nullptr, aElement, nullptr, CORS_NONE,
-                            mDocument->GetReferrerPolicy(), false, false,
-                            aTitle, state, aIsAlternate, getter_AddRefs(sheet));
+                            mDocument->GetReferrerPolicy(),
+                            EmptyString(), // no inline integrity checks
+                            false, false, aTitle, state, aIsAlternate,
+                            getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ASSERTION(state == eSheetNeedsParser,
                "Inline sheets should not be cached");
 
   LOG(("  Sheet is alternate: %d", *aIsAlternate));
 
   PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
 
@@ -1974,16 +2012,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
 nsresult
 Loader::LoadStyleLink(nsIContent* aElement,
                       nsIURI* aURL,
                       const nsAString& aTitle,
                       const nsAString& aMedia,
                       bool aHasAlternateRel,
                       CORSMode aCORSMode,
                       ReferrerPolicy aReferrerPolicy,
+                      const nsAString& aIntegrity,
                       nsICSSLoaderObserver* aObserver,
                       bool* aIsAlternate)
 {
   LOG(("css::Loader::LoadStyleLink"));
   NS_PRECONDITION(aURL, "Must have URL to load");
   NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
 
   LOG_URI("  Link uri: '%s'", aURL);
@@ -2008,17 +2047,17 @@ Loader::LoadStyleLink(nsIContent* aEleme
   nsresult rv = CheckLoadAllowed(principal, aURL, context);
   if (NS_FAILED(rv)) return rv;
 
   LOG(("  Passed load check"));
 
   StyleSheetState state;
   nsRefPtr<CSSStyleSheet> sheet;
   rv = CreateSheet(aURL, aElement, principal, aCORSMode,
-                   aReferrerPolicy, false,
+                   aReferrerPolicy, aIntegrity, false,
                    aHasAlternateRel, aTitle, state, aIsAlternate,
                    getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   LOG(("  Sheet is alternate: %d", *aIsAlternate));
 
   PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
 
@@ -2172,16 +2211,17 @@ Loader::LoadChildSheet(CSSStyleSheet* aP
   // loop) do so.
   nsRefPtr<CSSStyleSheet> sheet;
   bool isAlternate;
   StyleSheetState state;
   const nsSubstring& empty = EmptyString();
   // For now, use CORS_NONE for child sheets
   rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
                    aParentSheet->GetReferrerPolicy(),
+                   EmptyString(), // integrity is only checked on main sheet
                    parentData ? parentData->mSyncLoad : false,
                    false, empty, state, &isAlternate, getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
 
   rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2238,35 +2278,37 @@ Loader::LoadSheet(nsIURI* aURL,
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
                   nsIPrincipal* aOriginPrincipal,
                   const nsCString& aCharset,
                   nsICSSLoaderObserver* aObserver,
                   CORSMode aCORSMode,
-                  ReferrerPolicy aReferrerPolicy)
+                  ReferrerPolicy aReferrerPolicy,
+                  const nsAString& aIntegrity)
 {
   LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
   return InternalLoadNonDocumentSheet(aURL, false, false,
                                       aOriginPrincipal, aCharset,
                                       nullptr, aObserver, aCORSMode,
-                                      aReferrerPolicy);
+                                      aReferrerPolicy, aIntegrity);
 }
 
 nsresult
 Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
                                      bool aAllowUnsafeRules,
                                      bool aUseSystemPrincipal,
                                      nsIPrincipal* aOriginPrincipal,
                                      const nsCString& aCharset,
                                      CSSStyleSheet** aSheet,
                                      nsICSSLoaderObserver* aObserver,
                                      CORSMode aCORSMode,
-                                     ReferrerPolicy aReferrerPolicy)
+                                     ReferrerPolicy aReferrerPolicy,
+                                     const nsAString& aIntegrity)
 {
   NS_PRECONDITION(aURL, "Must have a URI to load");
   NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
   NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
                   "Shouldn't load system-principal sheets async");
   NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
 
   LOG_URI("  Non-document sheet uri: '%s'", aURL);
@@ -2287,17 +2329,17 @@ Loader::InternalLoadNonDocumentSheet(nsI
 
   StyleSheetState state;
   bool isAlternate;
   nsRefPtr<CSSStyleSheet> sheet;
   bool syncLoad = (aObserver == nullptr);
   const nsSubstring& empty = EmptyString();
 
   rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aCORSMode,
-                   aReferrerPolicy, syncLoad, false,
+                   aReferrerPolicy, aIntegrity, syncLoad, false,
                    empty, state, &isAlternate, getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
 
   if (state == eSheetComplete) {
     LOG(("  Sheet already complete"));
     if (aObserver || !mObservers.IsEmpty()) {
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -218,16 +218,17 @@ public:
    */
   nsresult LoadStyleLink(nsIContent* aElement,
                          nsIURI* aURL,
                          const nsAString& aTitle,
                          const nsAString& aMedia,
                          bool aHasAlternateRel,
                          CORSMode aCORSMode,
                          ReferrerPolicy aReferrerPolicy,
+                         const nsAString& aIntegrity,
                          nsICSSLoaderObserver* aObserver,
                          bool* aIsAlternate);
 
   /**
    * Load a child (@import-ed) style sheet.  In addition to loading the sheet,
    * this method will insert it into the child sheet list of aParentSheet.  If
    * there is no sheet currently being parsed and the child sheet is not
    * complete when this method returns, then when the child sheet becomes
@@ -315,17 +316,18 @@ public:
    * Same as above, to be used when the caller doesn't care about the
    * not-yet-loaded sheet.
    */
   nsresult LoadSheet(nsIURI* aURL,
                      nsIPrincipal* aOriginPrincipal,
                      const nsCString& aCharset,
                      nsICSSLoaderObserver* aObserver,
                      CORSMode aCORSMode = CORS_NONE,
-                     ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
+                     ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+                     const nsAString& aIntegrity = EmptyString());
 
   /**
    * Stop loading all sheets.  All nsICSSLoaderObservers involved will be
    * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
    */
   nsresult Stop(void);
 
   /**
@@ -412,16 +414,17 @@ private:
   // must be non-null then.  The loader principal must never be null
   // if aURI is not null.
   // *aIsAlternate is set based on aTitle and aHasAlternateRel.
   nsresult CreateSheet(nsIURI* aURI,
                        nsIContent* aLinkingContent,
                        nsIPrincipal* aLoaderPrincipal,
                        CORSMode aCORSMode,
                        ReferrerPolicy aReferrerPolicy,
+                       const nsAString& aIntegrity,
                        bool aSyncLoad,
                        bool aHasAlternateRel,
                        const nsAString& aTitle,
                        StyleSheetState& aSheetState,
                        bool *aIsAlternate,
                        CSSStyleSheet** aSheet);
 
   // Pass in either a media string or the nsMediaList from the
@@ -445,17 +448,18 @@ private:
   nsresult InternalLoadNonDocumentSheet(nsIURI* aURL,
                                         bool aAllowUnsafeRules,
                                         bool aUseSystemPrincipal,
                                         nsIPrincipal* aOriginPrincipal,
                                         const nsCString& aCharset,
                                         CSSStyleSheet** aSheet,
                                         nsICSSLoaderObserver* aObserver,
                                         CORSMode aCORSMode = CORS_NONE,
-                                        ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
+                                        ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+                                        const nsAString& aIntegrity = EmptyString());
 
   // Post a load event for aObserver to be notified about aSheet.  The
   // notification will be sent with status NS_OK unless the load event is
   // canceled at some point (in which case it will be sent with
   // NS_BINDING_ABORTED).  aWasAlternate indicates the state when the load was
   // initiated, not the state at some later time.  aURI should be the URI the
   // sheet was loaded from (may be null for inline sheets).  aElement is the
   // owning element for this sheet.
--- a/media/mtransport/nr_socket_prsock.cpp
+++ b/media/mtransport/nr_socket_prsock.cpp
@@ -733,34 +733,49 @@ void NrSocket::close() {
   mCondition = NS_BASE_STREAM_CLOSED;
 }
 
 
 int NrSocket::connect(nr_transport_addr *addr) {
   ASSERT_ON_THREAD(ststhread_);
   int r,_status;
   PRNetAddr naddr;
-  int32_t status;
+  int32_t connect_status, getsockname_status;
 
   if ((r=nr_transport_addr_to_praddr(addr, &naddr)))
     ABORT(r);
 
   if(!fd_)
     ABORT(R_EOD);
 
   // Note: this just means we tried to connect, not that we
   // are actually live.
   connect_invoked_ = true;
-  status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT);
+  connect_status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT);
+  if (connect_status != PR_SUCCESS) {
+    if (PR_GetError() != PR_IN_PROGRESS_ERROR)
+      ABORT(R_IO_ERROR);
+  }
 
-  if (status != PR_SUCCESS) {
-    if (PR_GetError() == PR_IN_PROGRESS_ERROR)
-      ABORT(R_WOULDBLOCK);
+  // If our local address is wildcard, then fill in the
+  // address now.
+  if(nr_transport_addr_is_wildcard(&my_addr_)){
+    getsockname_status = PR_GetSockName(fd_, &naddr);
+    if (getsockname_status != PR_SUCCESS){
+      r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket");
+      ABORT(R_INTERNAL);
+    }
 
-    ABORT(R_IO_ERROR);
+    if((r=nr_praddr_to_transport_addr(&naddr,&my_addr_,addr->protocol,1)))
+      ABORT(r);
+  }
+
+  // Now return the WOULDBLOCK if needed.
+  if (connect_status != PR_SUCCESS) {
+    ABORT(R_WOULDBLOCK);
   }
 
   _status=0;
 abort:
   return(_status);
 }
 
 
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -377,19 +377,19 @@ void NrIceCtx::trickle_cb(void *arg, nr_
 }
 
 RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& name,
                                   bool offerer,
                                   bool set_interface_priorities,
                                   bool allow_loopback,
                                   bool tcp_enabled,
                                   bool allow_link_local,
+                                  bool hide_non_default,
                                   Policy policy) {
-
-  RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer, policy);
+   RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer, policy);
 
   // Initialize the crypto callbacks and logging stuff
   if (!initialized) {
     NR_reg_init(NR_REG_MODE_LOCAL);
     RLogRingBuffer::CreateInstance();
     nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
     initialized = true;
 
@@ -430,16 +430,17 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
       NR_reg_set_uchar((char *)"ice.pref.interface.virbr0", 233);
       NR_reg_set_uchar((char *)"ice.pref.interface.wlan0", 232);
     }
 
     int32_t stun_client_maximum_transmits = 7;
     int32_t ice_trickle_grace_period = 5000;
     int32_t ice_tcp_so_sock_count = 3;
     int32_t ice_tcp_listen_backlog = 10;
+    nsAutoCString force_net_interface;
 #ifndef MOZILLA_XPCOMRT_API
     nsresult res;
     nsCOMPtr<nsIPrefService> prefs =
       do_GetService("@mozilla.org/preferences-service;1", &res);
 
     if (NS_SUCCEEDED(res)) {
       nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
       if (branch) {
@@ -450,16 +451,19 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
             "media.peerconnection.ice.trickle_grace_period",
             &ice_trickle_grace_period);
         branch->GetIntPref(
             "media.peerconnection.ice.tcp_so_sock_count",
             &ice_tcp_so_sock_count);
         branch->GetIntPref(
             "media.peerconnection.ice.tcp_listen_backlog",
             &ice_tcp_listen_backlog);
+        branch->GetCharPref(
+            "media.peerconnection.ice.force_interface",
+            getter_Copies(force_net_interface));
       }
     }
 #endif
     NR_reg_set_uint4((char *)"stun.client.maximum_transmits",
                      stun_client_maximum_transmits);
     NR_reg_set_uint4((char *)NR_ICE_REG_TRICKLE_GRACE_PERIOD,
                      ice_trickle_grace_period);
     NR_reg_set_int4((char *)NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT,
@@ -473,28 +477,36 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
 
     if (allow_loopback) {
       NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, 1);
     }
 
     if (allow_link_local) {
       NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS, 1);
     }
+    if (force_net_interface.Length() > 0) {
+      // Stupid cast.... but needed
+      const nsCString& flat = PromiseFlatCString(static_cast<nsACString&>(force_net_interface));
+      NR_reg_set_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, const_cast<char*>(flat.get()));
+    }
   }
 
   // Create the ICE context
   int r;
 
   UINT4 flags = offerer ? NR_ICE_CTX_FLAGS_OFFERER:
       NR_ICE_CTX_FLAGS_ANSWERER;
   flags |= NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION;
   if (policy == ICE_POLICY_RELAY) {
     flags |= NR_ICE_CTX_FLAGS_RELAY_ONLY;
   }
 
+  if (hide_non_default)
+    flags |= NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS;
+
   r = nr_ice_ctx_create(const_cast<char *>(name.c_str()), flags,
                         &ctx->ctx_);
   if (r) {
     MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name << "'");
     return nullptr;
   }
 
 #ifdef USE_INTERFACE_PRIORITIZER
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -208,22 +208,24 @@ class NrIceCtx {
                      ICE_CONTROLLED
   };
 
   enum Policy { ICE_POLICY_NONE,
                 ICE_POLICY_RELAY,
                 ICE_POLICY_ALL
   };
 
+  // TODO(ekr@rtfm.com): Too many bools here. Bug 1193437.
   static RefPtr<NrIceCtx> Create(const std::string& name,
                                  bool offerer,
                                  bool set_interface_priorities = true,
                                  bool allow_loopback = false,
                                  bool tcp_enabled = true,
                                  bool allow_link_local = false,
+                                 bool hide_non_default = false,
                                  Policy policy = ICE_POLICY_ALL);
 
   // Deinitialize all ICE global state. Used only for testing.
   static void internal_DeinitializeGlobal();
 
 
   nr_ice_ctx *ctx() { return ctx_; }
   nr_ice_peer_ctx *peer() { return peer_; }
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -245,22 +245,24 @@ class SchedulableTrickleCandidate {
     std::string candidate_;
     void *timer_handle_;
 
     DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
 };
 
 class IceTestPeer : public sigslot::has_slots<> {
  public:
-
+  // TODO(ekr@rtfm.com): Convert to flags when NrIceCtx::Create() does.
+  // Bug 1193437.
   IceTestPeer(const std::string& name, bool offerer, bool set_priorities,
-              bool allow_loopback = false, bool enable_tcp = true) :
+              bool allow_loopback = false, bool enable_tcp = true,
+              bool allow_link_local = false, bool hide_non_default = false) :
       name_(name),
       ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities, allow_loopback,
-                                enable_tcp)),
+                                enable_tcp, allow_link_local, hide_non_default)),
       streams_(),
       candidates_(),
       gathering_complete_(false),
       ready_ct_(0),
       ice_complete_(false),
       ice_reached_checking_(false),
       received_(0),
       sent_(0),
@@ -1056,16 +1058,32 @@ class IceTestPeer : public sigslot::has_
     ice_ctx_->peer()->tiebreaker = tiebreaker;
   }
 
   void SimulateIceLite() {
     simulate_ice_lite_ = true;
     SetControlling(NrIceCtx::ICE_CONTROLLED);
   }
 
+  nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) {
+    nsresult rv;
+
+    test_utils->sts_target()->Dispatch(
+        WrapRunnableRet(&rv, this,
+                        &IceTestPeer::GetDefaultCandidate_s,
+                        stream, cand),
+        NS_DISPATCH_SYNC);
+
+    return rv;
+  }
+
+  nsresult GetDefaultCandidate_s(unsigned int stream, NrIceCandidate* cand) {
+    return streams_[stream]->GetDefaultCandidate(1, cand);
+  }
+
  private:
   std::string name_;
   nsRefPtr<NrIceCtx> ice_ctx_;
   std::vector<mozilla::RefPtr<NrIceMediaStream> > streams_;
   std::map<std::string, std::vector<std::string> > candidates_;
   // Maps from stream id to list of remote trickle candidates
   std::map<size_t, std::vector<SchedulableTrickleCandidate*> >
     controlled_trickle_candidates_;
@@ -1240,16 +1258,27 @@ class IceGatherTest : public ::testing::
             std::string::npos != candidates[c].find(match2)) {
           return true;
         }
       }
     }
     return false;
   }
 
+  void DumpCandidates(unsigned int stream) {
+    std::vector<std::string> candidates = peer_->GetCandidates(stream);
+
+    std::cerr << "Candidates for stream " << stream << "->"
+              << candidates.size() << std::endl;
+
+    for (auto c : candidates) {
+      std::cerr << "Candidate: " << c << std::endl;
+    }
+  }
+
  protected:
   mozilla::ScopedDeletePtr<IceTestPeer> peer_;
 };
 
 class IceConnectTest : public ::testing::Test {
  public:
   IceConnectTest() :
     initted_(false),
@@ -1283,49 +1312,53 @@ class IceConnectTest : public ::testing:
     p2_->AddStream(components);
   }
 
   void RemoveStream(size_t index) {
     p1_->RemoveStream(index);
     p2_->RemoveStream(index);
   }
 
-  void Init(bool set_priorities, bool allow_loopback, bool enable_tcp) {
+  void Init(bool set_priorities, bool allow_loopback, bool enable_tcp,
+            bool default_only = false) {
     if (!initted_) {
       p1_ = new IceTestPeer("P1", true, set_priorities, allow_loopback,
-                            enable_tcp);
+                            enable_tcp, false, default_only);
       p2_ = new IceTestPeer("P2", false, set_priorities, allow_loopback,
-                            enable_tcp);
+                            enable_tcp, false, default_only);
     }
     initted_ = true;
   }
 
-  bool Gather(unsigned int waitTime = kDefaultTimeout) {
+  bool Gather(unsigned int waitTime = kDefaultTimeout,
+              bool setupStunServers = true) {
     Init(false, false, false);
     if (use_nat_) {
       // If we enable nat simulation, but still use a real STUN server somewhere
       // on the internet, we will see failures if there is a real NAT in
       // addition to our simulated one, particularly if it disallows
       // hairpinning.
-      UseTestStunServer();
+      if (setupStunServers) {
+        UseTestStunServer();
+      }
       p1_->UseNat();
       p2_->UseNat();
       p1_->SetFilteringType(filtering_type_);
       p2_->SetFilteringType(filtering_type_);
       p1_->SetMappingType(mapping_type_);
       p2_->SetMappingType(mapping_type_);
       p1_->SetBlockUdp(block_udp_);
       p2_->SetBlockUdp(block_udp_);
-    } else {
+    } else if (setupStunServers) {
       std::vector<NrIceStunServer> stun_servers;
 
       stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address,
-        kDefaultStunServerPort, kNrIceTransportUdp));
+                                                      kDefaultStunServerPort, kNrIceTransportUdp));
       stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address,
-        kDefaultStunServerPort, kNrIceTransportTcp));
+                                                      kDefaultStunServerPort, kNrIceTransportTcp));
 
       p1_->SetStunServers(stun_servers);
       p2_->SetStunServers(stun_servers);
     }
 
     p1_->Gather();
     p2_->Gather();
 
@@ -1640,16 +1673,29 @@ TEST_F(IceGatherTest, TestGatherFakeStun
   }
 
   EnsurePeer();
   peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
+TEST_F(IceGatherTest, TestGatherStunServerIpAddressDefaultRouteOnly) {
+  if (g_stun_server_address.empty()) {
+    return;
+  }
+
+  peer_ = new IceTestPeer("P1", true, false, false, false, false, true);
+  peer_->AddStream(1);
+  peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
+  peer_->SetFakeResolver();
+  Gather();
+  ASSERT_FALSE(StreamHasMatchingCandidate(0, " host "));
+}
+
 TEST_F(IceGatherTest, TestGatherFakeStunServerHostname) {
   if (g_stun_server_hostname.empty()) {
     return;
   }
 
   EnsurePeer();
   peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   peer_->SetFakeResolver();
@@ -1763,16 +1809,24 @@ TEST_F(IceGatherTest, TestGatherDNSStunB
 TEST_F(IceGatherTest, TestGatherDNSStunBogusHostnameTcp) {
   EnsurePeer();
   peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort,
     kNrIceTransportTcp);
   peer_->SetDNSResolver();
   Gather();
 }
 
+TEST_F(IceGatherTest, TestDefaultCandidate) {
+  EnsurePeer();
+  peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
+  Gather();
+  NrIceCandidate default_candidate;
+  ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate)));
+}
+
 TEST_F(IceGatherTest, TestGatherTurn) {
   EnsurePeer();
   if (g_turn_server.empty())
     return;
   peer_->SetTurnServer(g_turn_server, kDefaultStunServerPort,
                        g_turn_user, g_turn_password, kNrIceTransportUdp);
   Gather();
 }
@@ -1916,16 +1970,47 @@ TEST_F(IceGatherTest, TestStunServerTric
   TestStunServer::GetInstance(AF_INET)->SetActive(false);
   Gather(0);
   ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1"));
   TestStunServer::GetInstance(AF_INET)->SetActive(true);
   WaitForGather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1"));
 }
 
+// Test default route only with our fake STUN server and
+// apparently NATted.
+TEST_F(IceGatherTest, TestFakeStunServerNatedDefaultRouteOnly) {
+  peer_ = new IceTestPeer("P1", true, false, false, false, false, true);
+  peer_->AddStream(1);
+  UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
+  Gather(0);
+  WaitForGather();
+  DumpCandidates(0);
+  ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
+  ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+  NrIceCandidate default_candidate;
+  nsresult rv = peer_->GetDefaultCandidate(0, &default_candidate);
+  if (NS_SUCCEEDED(rv)) {
+    ASSERT_NE(NrIceCandidate::ICE_HOST, default_candidate.type);
+  }
+}
+
+// Test default route only with our fake STUN server and
+// apparently non-NATted.
+TEST_F(IceGatherTest, TestFakeStunServerNoNatDefaultRouteOnly) {
+  peer_ = new IceTestPeer("P1", true, false, false, false, false, true);
+  peer_->AddStream(1);
+  UseTestStunServer();
+  Gather(0);
+  WaitForGather();
+  DumpCandidates(0);
+  ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
+  ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
 TEST_F(IceGatherTest, TestStunTcpServerTrickle) {
   UseFakeStunTcpServerWithResponse("192.0.3.1", 3333);
   TestStunTcpServer::GetInstance(AF_INET)->SetActive(false);
   Gather(0);
   ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
   TestStunTcpServer::GetInstance(AF_INET)->SetActive(true);
   WaitForGather();
   ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
@@ -1986,26 +2071,35 @@ TEST_F(IceConnectTest, DISABLED_TestConn
   AddStream("first", 1);
   ASSERT_TRUE(Gather());
   SetCandidateFilter(IsTcpSoCandidate);
   SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
     NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
   Connect();
 }
 
+// Disabled because this breaks with hairpinning.
+TEST_F(IceConnectTest, DISABLED_TestConnectDefaultRouteOnly) {
+  Init(false, false, false, true);
+  AddStream("first", 1);
+  ASSERT_TRUE(Gather());
+  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, kNrIceTransportTcp);
+  Connect();
+}
+
 TEST_F(IceConnectTest, TestLoopbackOnlySortOf) {
   Init(false, true, false);
   AddStream("first", 1);
   SetCandidateFilter(IsLoopbackCandidate);
   ASSERT_TRUE(Gather());
   SetExpectedRemoteCandidateAddr("127.0.0.1");
   Connect();
 }
 
-
 TEST_F(IceConnectTest, TestConnectBothControllingP1Wins) {
   AddStream("first", 1);
   p1_->SetTiebreaker(1);
   p2_->SetTiebreaker(0);
   ASSERT_TRUE(Gather());
   p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
   Connect();
@@ -2091,16 +2185,41 @@ TEST_F(IceConnectTest, TestConnectFullCo
   SetFilteringType(TestNat::ENDPOINT_INDEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
   SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
   ASSERT_TRUE(Gather());
   Connect();
 }
 
+TEST_F(IceConnectTest, TestConnectNoNatRouteOnly) {
+  Init(false, false, false, true);
+  AddStream("first", 1);
+  UseTestStunServer();
+  // Because we are connecting from our host candidate to the
+  // other side's apparent srflx (which is also their host)
+  // we see a host/srflx pair.
+  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+  ASSERT_TRUE(Gather(kDefaultTimeout, false));
+  Connect();
+}
+
+TEST_F(IceConnectTest, TestConnectFullConeDefaultRouteOnly) {
+  Init(false, false, false, true);
+  AddStream("first", 1);
+  UseNat();
+  SetFilteringType(TestNat::ENDPOINT_INDEPENDENT);
+  SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+  ASSERT_TRUE(Gather());
+  Connect();
+}
+
 TEST_F(IceConnectTest, TestGatherAddressRestrictedCone) {
   AddStream("first", 1);
   UseNat();
   SetFilteringType(TestNat::ADDRESS_DEPENDENT);
   SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
   ASSERT_TRUE(Gather());
 }
 
--- a/media/mtransport/test/stunserver.cpp
+++ b/media/mtransport/test/stunserver.cpp
@@ -534,17 +534,17 @@ int TestStunTcpServer::TryOpenListenSock
   addr->addr.protocol=IPPROTO_TCP;
 
   int r = SetInternalPort(addr, port);
 
   if (r)
     return r;
 
   if (ice_ctx_ == NULL)
-    ice_ctx_ = NrIceCtx::Create("stun", true);
+    ice_ctx_ = NrIceCtx::Create("stun", false, false, false, false, false, false);
 
   //TODO (nils@mozilla.com) can we replace this with a more basic TCP socket
   // alternative which would allow us to remove the framing argument from the
   // nr_socket_multi_tcp_create() call?
   if(nr_socket_multi_tcp_create(ice_ctx_->ctx(),
      &addr->addr, TCP_TYPE_PASSIVE, 0, 0, 2048,
      &listen_sock_)) {
      MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket");
--- a/media/mtransport/test/turn_unittest.cpp
+++ b/media/mtransport/test/turn_unittest.cpp
@@ -499,17 +499,17 @@ int main(int argc, char **argv)
   NSS_NoDB_Init(nullptr);
   NSS_SetDomesticPolicy();
 
   // Set up the ICE registry, etc.
   // TODO(ekr@rtfm.com): Clean up
   std::string dummy("dummy");
   RUN_ON_THREAD(test_utils->sts_target(),
                 WrapRunnableNM(&NrIceCtx::Create,
-                               dummy, false, false, false, false, false,
+                               dummy, false, false, false, false, false, false,
                                NrIceCtx::ICE_POLICY_ALL),
                 NS_DISPATCH_SYNC);
 
   // Start the tests
   ::testing::InitGoogleTest(&argc, argv);
 
   int rv = RUN_ALL_TESTS();
   delete test_utils;
--- a/media/mtransport/test_nr_socket.cpp
+++ b/media/mtransport/test_nr_socket.cpp
@@ -375,17 +375,21 @@ bool TestNrSocket::allow_ingress(const n
 int TestNrSocket::connect(nr_transport_addr *addr) {
   ASSERT_ON_THREAD(ststhread_);
 
   if (connect_invoked_ || !port_mappings_.empty()) {
     MOZ_CRASH("TestNrSocket::connect() called more than once!");
     return R_INTERNAL;
   }
 
-  if (!nat_->enabled_ || nat_->is_an_internal_tuple(*addr)) {
+  if (!nat_->enabled_
+      || addr->protocol==IPPROTO_UDP  // Horrible hack to allow default address
+                                      // discovery to work. Only works because
+                                      // we don't normally connect on UDP.
+      || nat_->is_an_internal_tuple(*addr)) {
     // This will set connect_invoked_
     return NrSocket::connect(addr);
   }
 
   nsRefPtr<NrSocket> external_socket(create_external_socket(*addr));
   if (!external_socket) {
     return R_INTERNAL;
   }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -936,29 +936,29 @@ int nr_ice_format_candidate_attribute(nr
       port=9;
     snprintf(attr,maxlen,"candidate:%s %d %s %u %s %d typ %s",
       cand->foundation, cand->component_id, cand->addr.protocol==IPPROTO_UDP?"UDP":"TCP",cand->priority, addr, port,
       nr_ctype_name(cand->type));
 
     len=strlen(attr); attr+=len; maxlen-=len;
 
     /* raddr, rport */
-    raddr = (cand->stream->ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) ?
+    raddr = (cand->stream->ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY |
+             NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) ?
       &cand->addr : &cand->base;
 
     switch(cand->type){
       case HOST:
         break;
       case SERVER_REFLEXIVE:
       case PEER_REFLEXIVE:
         if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr)))
           ABORT(r);
         if(r=nr_transport_addr_get_port(raddr,&port))
           ABORT(r);
-
         snprintf(attr,maxlen," raddr %s rport %d",addr,port);
         break;
       case RELAYED:
         // comes from XorMappedAddress via AllocateResponse
         if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr)))
           ABORT(r);
         if(r=nr_transport_addr_get_port(raddr,&port))
           ABORT(r);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -662,18 +662,19 @@ int nr_ice_component_maybe_prune_candida
     c2 = TAILQ_FIRST(&comp->candidates);
     while(c2){
       if((c1 != c2) &&
          (c2->state == NR_ICE_CAND_STATE_INITIALIZED) &&
          !nr_transport_addr_cmp(&c1->base,&c2->base,NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
          !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
 
         if((c1->type == c2->type) ||
-           (c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
-           (c2->type==HOST && c1->type == SERVER_REFLEXIVE)){
+           (!(ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) &&
+            ((c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
+             (c2->type==HOST && c1->type == SERVER_REFLEXIVE)))){
 
           /*
              These are redundant. Remove the lower pri one, or if pairing has
              already occurred, remove the newest one.
 
              Since this algorithmis run whenever a new candidate
              is initialized, there should at most one duplicate.
            */
@@ -1358,17 +1359,17 @@ int nr_ice_component_get_default_candida
 
     /* We have the component. Now find the "best" candidate, making
        use of the fact that more "reliable" candidate types have
        higher numbers. So, we sort by type and then priority within
        type
     */
     cand=TAILQ_FIRST(&comp->candidates);
     while(cand){
-      if (cand->state == NR_ICE_CAND_STATE_INITIALIZED &&
+      if (!nr_ice_ctx_hide_candidate(comp->ctx, cand) &&
           cand->addr.ip_version == ip_version) {
         if (!best_cand) {
           best_cand = cand;
         }
         else if (best_cand->type < cand->type) {
           best_cand = cand;
         } else if (best_cand->type == cand->type &&
                    best_cand->priority < cand->priority) {
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -1,8 +1,9 @@
+
 /*
 Copyright (c) 2007, Adobe Systems, Incorporated
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met:
 
@@ -310,17 +311,17 @@ int nr_ice_fetch_turn_servers(int ct, nr
   abort:
     RFREE(data.data);
     RFREE(addr);
     if (_status) RFREE(servers);
     return(_status);
   }
 #endif /* USE_TURN */
 
-#define MAXADDRS 100 // Ridiculously high
+#define MAXADDRS 100 /* Ridiculously high */
 int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp)
   {
     nr_ice_ctx *ctx=0;
     int r,_status;
     char buf[100];
 
     if(r=r_log_register("ice", &LOG_ICE))
       ABORT(r);
@@ -394,16 +395,24 @@ int nr_ice_ctx_create(char *label, UINT4
 #endif /* USE_TURN */
 
 
     ctx->Ta = 20;
 
     if (r=nr_socket_factory_create_int(NULL, &default_socket_factory_vtbl, &ctx->socket_factory))
       ABORT(r);
 
+    if ((r=NR_reg_get_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, ctx->force_net_interface, sizeof(ctx->force_net_interface)))) {
+      if (r == R_NOT_FOUND) {
+        ctx->force_net_interface[0] = 0;
+      } else {
+        ABORT(r);
+      }
+    }
+
     STAILQ_INIT(&ctx->streams);
     STAILQ_INIT(&ctx->sockets);
     STAILQ_INIT(&ctx->foundations);
     STAILQ_INIT(&ctx->peers);
     STAILQ_INIT(&ctx->ids);
 
     *ctxp=ctx;
 
@@ -486,31 +495,32 @@ void nr_ice_gather_finished_cb(NR_SOCKET
 
     assert(cb_arg);
     if (!cb_arg)
       return;
     ctx = cand->ctx;
 
     ctx->uninitialized_candidates--;
 
-    // Avoid the need for yet another initialization function
+    /* Avoid the need for yet another initialization function */
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZING && cand->type == HOST)
       cand->state = NR_ICE_CAND_STATE_INITIALIZED;
 
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
       int was_pruned = 0;
 
       if (r=nr_ice_component_maybe_prune_candidate(ctx, cand->component,
                                                    cand, &was_pruned)) {
           r_log(LOG_ICE, LOG_NOTICE, "ICE(%s): Problem pruning candidates",ctx->label);
       }
 
       /* If we are initialized, the candidate wasn't pruned,
          and we have a trickle ICE callback fire the callback */
-      if (ctx->trickle_cb && !was_pruned) {
+      if (ctx->trickle_cb && !was_pruned &&
+          !nr_ice_ctx_hide_candidate(ctx, cand)) {
         ctx->trickle_cb(ctx->trickle_cb_arg, ctx, cand->stream, cand->component_id, cand);
 
         if (nr_ice_ctx_pair_new_trickle_candidates(ctx, cand)) {
           r_log(LOG_ICE,LOG_ERR, "ICE(%s): All could not pair new trickle candidate",ctx->label);
           /* But continue */
         }
       }
     }
@@ -545,31 +555,138 @@ static int nr_ice_ctx_pair_new_trickle_c
       pctx=STAILQ_NEXT(pctx,entry);
     }
 
     _status=0;
  abort:
     return(_status);
   }
 
+/* Get the default address by doing a connect to a known public IP address,
+   in this case Google public DNS:
 
-int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg)
+   IPv4: 8.8.8.8
+   IPv6: 2001:4860:4860::8888
+
+   Then we can do getsockname to get the address. No packets get sent
+   since this is UDP. It's just a way to get the address.
+*/
+static int nr_ice_get_default_address(nr_ice_ctx *ctx, int ip_version, nr_transport_addr* addrp)
   {
     int r,_status;
-    nr_ice_media_stream *stream;
-    nr_local_addr addrs[MAXADDRS];
+    nr_transport_addr addr;
+    nr_transport_addr remote_addr;
+    nr_socket *sock=0;
+
+    switch(ip_version) {
+      case NR_IPV4:
+        if ((r=nr_str_port_to_transport_addr("0.0.0.0", 0, IPPROTO_UDP, &addr)))
+          ABORT(r);
+        if ((r=nr_str_port_to_transport_addr("8.8.8.8", 53, IPPROTO_UDP, &remote_addr)))
+          ABORT(r);
+        break;
+      case NR_IPV6:
+        if ((r=nr_str_port_to_transport_addr("::0", 0, IPPROTO_UDP, &addr)))
+          ABORT(r);
+        if ((r=nr_str_port_to_transport_addr("2001:4860:4860::8888", 53, IPPROTO_UDP, &remote_addr)))
+          ABORT(r);
+        break;
+      default:
+        assert(0);
+        ABORT(R_INTERNAL);
+    }
+
+    if ((r=nr_socket_factory_create_socket(ctx->socket_factory, &addr, &sock)))
+      ABORT(r);
+    if ((r=nr_socket_connect(sock, &remote_addr)))
+      ABORT(r);
+    if ((r=nr_socket_getaddr(sock, addrp)))
+      ABORT(r);
+
+    _status=0;
+  abort:
+    nr_socket_destroy(&sock);
+    return(_status);
+  }
+
+static int nr_ice_get_default_local_address(nr_ice_ctx *ctx, int ip_version, nr_local_addr* addrs, int addr_ct, nr_local_addr *addrp)
+  {
+    int r,_status;
+    nr_transport_addr default_addr;
+    int i;
+
+    if ((r=nr_ice_get_default_address(ctx, ip_version, &default_addr)))
+        ABORT(r);
+
+    for(i=0; i<addr_ct; ++i) {
+      if (!nr_transport_addr_cmp(&default_addr, &addrs[i].addr,
+                                 NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) {
+        if ((r=nr_local_addr_copy(addrp, &addrs[i])))
+          ABORT(r);
+        break;
+      }
+    }
+    if (i==addr_ct)
+      ABORT(R_NOT_FOUND);
+
+    _status=0;
+  abort:
+    return(_status);
+  }
+
+static int nr_ice_get_local_addresses(nr_ice_ctx *ctx)
+  {
+    int r,_status;
+    nr_local_addr local_addrs[MAXADDRS];
+    nr_local_addr *addrs = 0;
     int i,addr_ct;
+    nr_local_addr default_addrs[2];
+    int default_addr_ct = 0;
 
     if (!ctx->local_addrs) {
       /* First, gather all the local addresses we have */
-      if(r=nr_stun_find_local_addresses(addrs,MAXADDRS,&addr_ct)) {
+      if((r=nr_stun_find_local_addresses(local_addrs,MAXADDRS,&addr_ct))) {
         r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to find local addresses",ctx->label);
         ABORT(r);
       }
 
+      if (ctx->force_net_interface[0]) {
+        /* Limit us to only addresses on a single interface */
+        int force_addr_ct = 0;
+        for(i=0;i<addr_ct;i++){
+          if (!strcmp(local_addrs[i].addr.ifname, ctx->force_net_interface)) {
+            // copy it down in the array, if needed
+            if (i != force_addr_ct) {
+              if (r=nr_local_addr_copy(&local_addrs[force_addr_ct], &local_addrs[i])) {
+                ABORT(r);
+              }
+            }
+            force_addr_ct++;
+          }
+        }
+        addr_ct = force_addr_ct;
+      }
+
+      if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
+        /* Get just the default IPv4 and IPv6 addrs */
+        if(!nr_ice_get_default_local_address(ctx, NR_IPV4, local_addrs, addr_ct,
+                                             &default_addrs[default_addr_ct])) {
+          ++default_addr_ct;
+        }
+        if(!nr_ice_get_default_local_address(ctx, NR_IPV6, local_addrs, addr_ct,
+                                             &default_addrs[default_addr_ct])) {
+          ++default_addr_ct;
+        }
+        addrs = default_addrs;
+        addr_ct = default_addr_ct;
+      }
+      else {
+        addrs = local_addrs;
+      }
+
       /* Sort interfaces by preference */
       if(ctx->interface_prioritizer) {
         for(i=0;i<addr_ct;i++){
           if(r=nr_interface_prioritizer_add_interface(ctx->interface_prioritizer,addrs+i)) {
             r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to add interface ",ctx->label);
             ABORT(r);
           }
         }
@@ -579,16 +696,29 @@ int nr_ice_gather(nr_ice_ctx *ctx, NR_as
         }
       }
 
       if (r=nr_ice_ctx_set_local_addrs(ctx,addrs,addr_ct)) {
         ABORT(r);
       }
     }
 
+    _status=0;
+  abort:
+    return(_status);
+  }
+
+int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg)
+  {
+    int r,_status;
+    nr_ice_media_stream *stream;
+
+    if ((r=nr_ice_get_local_addresses(ctx)))
+      ABORT(r);
+
     if(STAILQ_EMPTY(&ctx->streams)) {
       r_log(LOG_ICE,LOG_ERR,"ICE(%s): Missing streams to initialize",ctx->label);
       ABORT(R_BAD_ARGS);
     }
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Initializing candidates",ctx->label);
     ctx->done_cb=done_cb;
     ctx->cb_arg=cb_arg;
@@ -691,18 +821,16 @@ static int nr_ice_random_string(char *st
     int needed;
     int r,_status;
 
     if(len%2) ABORT(R_BAD_ARGS);
     needed=len/2;
 
     if(needed>sizeof(bytes)) ABORT(R_BAD_ARGS);
 
-    //memset(bytes,0,needed);
-
     if(r=nr_crypto_random_bytes(bytes,needed))
       ABORT(r);
 
     if(r=nr_bin2hex(bytes,needed,(unsigned char *)str))
       ABORT(r);
 
     _status=0;
   abort:
@@ -806,8 +934,22 @@ int nr_ice_ctx_finalize(nr_ice_ctx *ctx,
 
 int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg)
 {
   ctx->trickle_cb = cb;
   ctx->trickle_cb_arg = cb_arg;
 
   return 0;
 }
+
+int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand)
+  {
+    if (cand->state != NR_ICE_CAND_STATE_INITIALIZED) {
+      return 1;
+    }
+
+    if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
+      if (cand->type == HOST)
+        return 1;
+    }
+
+    return 0;
+  }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -145,24 +145,27 @@ struct nr_ice_ctx_ {
   nr_ice_peer_ctx_head peers;
   nr_ice_stun_id_head ids;
 
   NR_async_cb done_cb;
   void *cb_arg;
 
   nr_ice_trickle_candidate_cb trickle_cb;
   void *trickle_cb_arg;
+
+  char force_net_interface[MAXIFNAME];
 };
 
 int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
 #define NR_ICE_CTX_FLAGS_OFFERER                           1
 #define NR_ICE_CTX_FLAGS_ANSWERER                          (1<<1)
 #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION             (1<<2)
 #define NR_ICE_CTX_FLAGS_LITE                              (1<<3)
 #define NR_ICE_CTX_FLAGS_RELAY_ONLY                        (1<<4)
+#define NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS                (1<<5)
 
 int nr_ice_ctx_destroy(nr_ice_ctx **ctxp);
 int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg);
 int nr_ice_add_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
 void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg);
 int nr_ice_add_media_stream(nr_ice_ctx *ctx,char *label,int components, nr_ice_media_stream **streamp);
 int nr_ice_remove_media_stream(nr_ice_ctx *ctx,nr_ice_media_stream **streamp);
 int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp);
@@ -172,16 +175,17 @@ int nr_ice_ctx_remember_id(nr_ice_ctx *c
 int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx);
 int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
 int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
 int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver);
 int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer);
 int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_factory *wrapper);
 void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory);
 int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg);
+int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
 
 #define NR_ICE_MAX_ATTRIBUTE_SIZE 256
 
 extern int LOG_ICE;
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -135,16 +135,17 @@ int nr_ice_media_stream_initialize(nr_ic
       comp=STAILQ_NEXT(comp,entry);
     }
 
     _status=0;
   abort:
     return(_status);
   }
 
+
 int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp, int *attrctp)
   {
     int attrct=0;
     nr_ice_component *comp;
     char **attrs=0;
     int index=0;
     nr_ice_candidate *cand;
     int r,_status;
@@ -152,17 +153,17 @@ int nr_ice_media_stream_get_attributes(n
     *attrctp=0;
 
     /* First find out how many attributes we need */
     comp=STAILQ_FIRST(&stream->components);
     while(comp){
       if (comp->state != NR_ICE_COMPONENT_DISABLED) {
         cand = TAILQ_FIRST(&comp->candidates);
         while(cand){
-          if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+          if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) {
             ++attrct;
           }
 
           cand = TAILQ_NEXT(cand, entry_comp);
         }
       }
       comp=STAILQ_NEXT(comp,entry);
     }
@@ -184,17 +185,17 @@ int nr_ice_media_stream_get_attributes(n
     /* Now format the attributes */
     comp=STAILQ_FIRST(&stream->components);
     while(comp){
       if (comp->state != NR_ICE_COMPONENT_DISABLED) {
         nr_ice_candidate *cand;
 
         cand=TAILQ_FIRST(&comp->candidates);
         while(cand){
-          if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+          if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) {
             assert(index < attrct);
 
             if (index >= attrct)
               ABORT(R_INTERNAL);
 
             if(r=nr_ice_format_candidate_attribute(cand, attrs[index],NR_ICE_MAX_ATTRIBUTE_SIZE))
               ABORT(r);
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_reg.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_reg.h
@@ -65,13 +65,15 @@ extern "C" {
 
 #define NR_ICE_REG_ICE_TCP_DISABLE          "ice.tcp.disable"
 #define NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT    "ice.tcp.so_sock_count"
 #define NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG   "ice.tcp.listen_backlog"
 
 #define NR_ICE_REG_KEEPALIVE_TIMER          "ice.keepalive_timer"
 
 #define NR_ICE_REG_TRICKLE_GRACE_PERIOD     "ice.trickle_grace_period"
+#define NR_ICE_REG_PREF_FORCE_INTERFACE_NAME "ice.forced_interface_name"
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif
 
--- a/media/mtransport/third_party/nICEr/src/net/transport_addr.h
+++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.h
@@ -70,17 +70,17 @@ typedef struct nr_transport_addr_ {
      56 = 5 ("IP6:[") + 39 (ipv6 address) + 2 ("]:") + 5 (port) + 4 (/UDP) + 1 (null) */
   char as_string[56];
 } nr_transport_addr;
 
 int nr_sockaddr_to_transport_addr(struct sockaddr *saddr, int protocol, int keep, nr_transport_addr *addr);
 
 // addresses, ports in local byte order
 int nr_ip4_port_to_transport_addr(UINT4 ip4, UINT2 port, int protocol, nr_transport_addr *addr);
-int nr_str_port_to_transport_addr(const char *ip4, UINT2 port, int protocol, nr_transport_addr *addr);
+int nr_str_port_to_transport_addr(const char *str, UINT2 port, int protocol, nr_transport_addr *addr);
 int nr_ip6_port_to_transport_addr(struct in6_addr* addr6, UINT2 port, int protocol, nr_transport_addr *addr);
 
 int nr_transport_addr_get_addrstring(nr_transport_addr *addr, char *str, int maxlen);
 int nr_transport_addr_get_port(nr_transport_addr *addr, int *port);
 int nr_transport_addr_cmp(nr_transport_addr *addr1,nr_transport_addr *addr2,int mode);
 #define NR_TRANSPORT_ADDR_CMP_MODE_VERSION   1
 #define NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL  2
 #define NR_TRANSPORT_ADDR_CMP_MODE_ADDR      3
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -299,28 +299,33 @@ nsresult PeerConnectionMedia::Init(const
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv);
     return NS_ERROR_FAILURE;
   }
 #endif // defined(MOZILLA_XPCOMRT_API)
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
+  bool default_address_only = Preferences::GetBool(
+    "media.peerconnection.ice.default_address_only", false);
 #else
   bool ice_tcp = false;
+  bool default_address_only = false;
 #endif
 
+
   // TODO(ekr@rtfm.com): need some way to set not offerer later
   // Looks like a bug in the NrIceCtx API.
   mIceCtx = NrIceCtx::Create("PC:" + mParentName,
                              true, // Offerer
                              true, // Explicitly set priorities
                              mParent->GetAllowIceLoopback(),
                              ice_tcp,
                              mParent->GetAllowIceLinkLocal(),
+                             default_address_only,
                              policy);
   if(!mIceCtx) {
     CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
   if (NS_FAILED(rv = mIceCtx->SetStunServers(stun_servers))) {
     CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__);
--- a/mobile/android/base/ANRReporter.java
+++ b/mobile/android/base/ANRReporter.java
@@ -474,17 +474,17 @@ public final class ANRReporter extends B
         }
     }
 
     private static void processTraces(Reader traces, File pingFile) {
 
         // Only get native stack if Gecko is running.
         // Also, unwinding is memory intensive, so only unwind if we have enough memory.
         final boolean haveNativeStack =
-            GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning) ?
+            GeckoThread.isRunning() ?
             requestNativeStack(/* unwind */ SysInfo.getMemSize() >= 640) : false;
 
         try {
             OutputStream ping = new BufferedOutputStream(
                 new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
             try {
                 fillPingHeader(ping, pingFile.getName());
                 // Traces file has the format
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -3161,17 +3161,17 @@ public class BrowserApp extends GeckoApp
             return false;
 
         // Hide the tab history panel when hardware menu button is pressed.
         TabHistoryFragment frag = (TabHistoryFragment) getSupportFragmentManager().findFragmentByTag(TAB_HISTORY_FRAGMENT_TAG);
         if (frag != null) {
             frag.dismiss();
         }
 
-        if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+        if (!GeckoThread.isRunning()) {
             aMenu.findItem(R.id.settings).setEnabled(false);
             aMenu.findItem(R.id.help).setEnabled(false);
         }
 
         Tab tab = Tabs.getInstance().getSelectedTab();
         final MenuItem bookmark = aMenu.findItem(R.id.bookmark);
         final MenuItem reader = aMenu.findItem(R.id.reading_list);
         final MenuItem back = aMenu.findItem(R.id.back);
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1578,19 +1578,16 @@ public abstract class GeckoApp
                 if (rec != null) {
                     rec.recordJavaStartupTime(javaDuration);
                 }
 
                 // Kick off our background services. We do this by invoking the broadcast
                 // receiver, which uses the system alarm infrastructure to perform tasks at
                 // intervals.
                 GeckoPreferences.broadcastHealthReportUploadPref(GeckoApp.this);
-                if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
-                    return;
-                }
             }
         }, 50);
 
         final int updateServiceDelay = 30 * 1000;
         ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
             @Override
             public void run() {
                 UpdateServiceHelper.registerForUpdates(GeckoApp.this);
@@ -1598,17 +1595,17 @@ public abstract class GeckoApp
         }, updateServiceDelay);
 
         if (mIsRestoringActivity) {
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
             if (selectedTab != null) {
                 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
             }
 
-            if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+            if (GeckoThread.isRunning()) {
                 geckoConnected();
                 GeckoAppShell.sendEventToGecko(
                         GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
             }
         }
 
         if (ACTION_ALERT_CALLBACK.equals(action)) {
             processAlertCallback(intent);
@@ -2115,17 +2112,17 @@ public abstract class GeckoApp
 
         Tabs.unregisterOnTabsChangedListener(this);
 
         if (!isFinishing()) {
             // GeckoApp was not intentionally destroyed, so keep our process alive.
             return;
         }
 
-        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+        if (GeckoThread.isRunning()) {
             // Let the Gecko thread prepare for exit.
             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createAppBackgroundingEvent());
         }
 
         if (mRestartIntent != null) {
             // Restarting, so let Restarter kill us.
             final Intent intent = new Intent();
             intent.setClass(getApplicationContext(), Restarter.class)
@@ -2233,17 +2230,17 @@ public abstract class GeckoApp
             }
         });
     }
 
     public void handleNotification(String action, String alertName, String alertCookie) {
         // If Gecko isn't running yet, we ignore the notification. Note that
         // even if Gecko is running but it was restarted since the notification
         // was created, the notification won't be handled (bug 849653).
-        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+        if (GeckoThread.isRunning()) {
             GeckoAppShell.handleNotification(action, alertName, alertCookie);
         }
     }
 
     private void checkMigrateProfile() {
         final File profileDir = getProfile().getDir();
 
         if (profileDir != null) {
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -36,17 +36,16 @@ import org.mozilla.gecko.annotation.Wrap
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.mozglue.ContextUtils;
-import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoRequest;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
@@ -92,17 +91,16 @@ import android.location.LocationManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Browser;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
@@ -151,17 +149,17 @@ public class GeckoAppShell
             extras.putString("BuildID", AppConstants.MOZ_APP_BUILDID);
             extras.putString("Vendor", AppConstants.MOZ_APP_VENDOR);
             extras.putString("ReleaseChannel", AppConstants.MOZ_UPDATE_CHANNEL);
             return extras;
         }
 
         @Override
         public void uncaughtException(final Thread thread, final Throwable exc) {
-            if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) {
+            if (GeckoThread.isStateAtLeast(GeckoThread.State.EXITING)) {
                 // We've called System.exit. All exceptions after this point are Android
                 // berating us for being nasty to it.
                 return;
             }
 
             super.uncaughtException(thread, exc);
         }
 
@@ -254,17 +252,16 @@ public class GeckoAppShell
     static public final int LINK_TYPE_2G = 5;
     static public final int LINK_TYPE_3G = 6;
     static public final int LINK_TYPE_4G = 7;
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void registerJavaUiThread();
-    public static native void nativeInit(ClassLoader clsLoader, MessageQueue msgQueue);
 
     // helper methods
     public static native void onResume();
     public static void callObserver(String observerKey, String topic, String data) {
         sendEventToGecko(GeckoEvent.createCallObserverEvent(observerKey, topic, data));
     }
     public static void removeObserver(String observerKey) {
         sendEventToGecko(GeckoEvent.createRemoveObserverEvent(observerKey));
@@ -334,66 +331,16 @@ public class GeckoAppShell
         }
     }
 
     @RobocopTarget
     public static LayerView getLayerView() {
         return sLayerView;
     }
 
-    public static void runGecko(String apkPath, String args, String url, String type) {
-        // Preparation for pumpMessageLoop()
-        MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
-            @Override public boolean queueIdle() {
-                final Handler geckoHandler = ThreadUtils.sGeckoHandler;
-                Message idleMsg = Message.obtain(geckoHandler);
-                // Use |Message.obj == GeckoHandler| to identify our "queue is empty" message
-                idleMsg.obj = geckoHandler;
-                geckoHandler.sendMessageAtFrontOfQueue(idleMsg);
-                // Keep this IdleHandler
-                return true;
-            }
-        };
-        Looper.myQueue().addIdleHandler(idleHandler);
-
-        // Initialize AndroidBridge.
-        nativeInit(GeckoAppShell.class.getClassLoader(), Looper.myQueue());
-
-        // First argument is the .apk path
-        String combinedArgs = apkPath + " -greomni " + apkPath;
-        if (args != null)
-            combinedArgs += " " + args;
-        if (url != null)
-            combinedArgs += " -url " + url;
-        if (type != null)
-            combinedArgs += " " + type;
-
-        // In un-official builds, we want to load Javascript resources fresh
-        // with each build.  In official builds, the startup cache is purged by
-        // the buildid mechanism, but most un-official builds don't bump the
-        // buildid, so we purge here instead.
-        if (!AppConstants.MOZILLA_OFFICIAL) {
-            Log.w(LOGTAG, "STARTUP PERFORMANCE WARNING: un-official build: purging the " +
-                          "startup (JavaScript) caches.");
-            combinedArgs += " -purgecaches";
-        }
-
-        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
-        combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
-
-        if (!AppConstants.MOZILLA_OFFICIAL) {
-            Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
-        }
-        // and go
-        GeckoLoader.nativeRun(combinedArgs);
-
-        // Remove pumpMessageLoop() idle handler
-        Looper.myQueue().removeIdleHandler(idleHandler);
-    }
-
     /**
      * If the Gecko thread is running, immediately dispatches the event to
      * Gecko.
      *
      * If the Gecko thread is not running, queues the event. If the queue is
      * full, throws {@link IllegalStateException}.
      *
      * Queued events will be dispatched in order of arrival when the Gecko
@@ -405,17 +352,17 @@ public class GeckoAppShell
      *            the event to dispatch. Cannot be null.
      */
     @RobocopTarget
     public static void sendEventToGecko(GeckoEvent e) {
         if (e == null) {
             throw new IllegalArgumentException("e cannot be null.");
         }
 
-        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+        if (GeckoThread.isRunning()) {
             notifyGeckoOfEvent(e);
             // Gecko will copy the event data into a normal C++ object.
             // We can recycle the event now.
             e.recycle();
             return;
         }
 
         GeckoThread.addPendingEvent(e);
@@ -506,18 +453,17 @@ public class GeckoAppShell
                 // should never happen since we always leave it as false when we exit this function.
                 Log.e(LOGTAG, "geckoEventSync() may have been called twice concurrently!", new Exception());
                 // fall through for graceful handling
             }
 
             sendEventToGecko(e);
             sWaitingForEventAck = true;
             while (true) {
-                if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting) ||
-                        GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) {
+                if (GeckoThread.isStateAtLeast(GeckoThread.State.EXITING)) {
                     // Gecko is quitting; don't do anything.
                     Log.d(LOGTAG, "Skipping Gecko event sync during exit");
                     sWaitingForEventAck = false;
                     return;
                 }
 
                 try {
                     sEventAckLock.wait(1000);
@@ -2499,34 +2445,16 @@ public class GeckoAppShell
     }
 
     @WrapForJNI
     public static void unlockScreenOrientation() {
         GeckoScreenOrientation.getInstance().unlock();
     }
 
     @WrapForJNI
-    public static boolean pumpMessageLoop(final Message msg) {
-        final Handler geckoHandler = ThreadUtils.sGeckoHandler;
-
-        if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
-            // Our "queue is empty" message; see runGecko()
-            return false;
-        }
-
-        if (msg.getTarget() == null) {
-            Looper.myLooper().quit();
-        } else {
-            msg.getTarget().dispatchMessage(msg);
-        }
-
-        return true;
-    }
-
-    @WrapForJNI
     public static void notifyWakeLockChanged(String topic, String state) {
         if (getGeckoInterface() != null)
             getGeckoInterface().notifyWakeLockChanged(topic, state);
     }
 
     @WrapForJNI(allowMultithread = true)
     public static void registerSurfaceTextureFrameListener(Object surfaceTexture, final int id) {
         ((SurfaceTexture)surfaceTexture).setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
--- a/mobile/android/base/GeckoConnectivityReceiver.java
+++ b/mobile/android/base/GeckoConnectivityReceiver.java
@@ -76,14 +76,14 @@ public class GeckoConnectivityReceiver e
         if (info == null) {
             status = LINK_DATA_UNKNOWN;
         } else if (!info.isConnected()) {
             status = LINK_DATA_DOWN;
         } else {
             status = LINK_DATA_UP;
         }
 
-        if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
+        if (GeckoThread.isRunning()) {
             GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkLinkChangeEvent(status));
             GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkLinkChangeEvent(LINK_DATA_CHANGED));
         }
     }
 }
--- a/mobile/android/base/GeckoJavaSampler.java
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -14,29 +14,28 @@ import org.mozilla.gecko.annotation.Wrap
 import java.lang.Thread;
 import java.util.Set;
 
 public class GeckoJavaSampler {
     private static final String LOGTAG = "JavaSampler";
     private static Thread sSamplingThread;
     private static SamplingThread sSamplingRunnable;
     private static Thread sMainThread;
-    private static volatile boolean sLibsLoaded;
 
     // Use the same timer primitive as the profiler
     // to get a perfect sample syncing.
     private static native double getProfilerTime();
 
     private static class Sample {
         public Frame[] mFrames;
         public double mTime;
         public long mJavaTime; // non-zero if Android system time is used
         public Sample(StackTraceElement[] aStack) {
             mFrames = new Frame[aStack.length];
-            if (sLibsLoaded) {
+            if (GeckoThread.isStateAtLeast(GeckoThread.State.LIBS_READY)) {
                 mTime = getProfilerTime();
             }
             if (mTime == 0.0d) {
                 // getProfilerTime is not available yet; either libs are not loaded,
                 // or profiling hasn't started on the Gecko side yet
                 mJavaTime = SystemClock.elapsedRealtime();
             }
             for (int i = 0; i < aStack.length; i++) {
@@ -203,16 +202,12 @@ public class GeckoJavaSampler {
                 sSamplingThread.join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             sSamplingThread = null;
             sSamplingRunnable = null;
         }
     }
-
-    public static void setLibsLoaded() {
-        sLibsLoaded = true;
-    }
 }
 
 
 
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -1,55 +1,112 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
 import android.os.SystemClock;
+import android.util.DisplayMetrics;
 import android.util.Log;
 
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicReference;
 
 public class GeckoThread extends Thread implements GeckoEventListener {
     private static final String LOGTAG = "GeckoThread";
 
-    @RobocopTarget
-    public enum LaunchState {
-        Launching,
-        Launched,
-        GeckoRunning,
-        GeckoExiting,
-        GeckoExited
+    @WrapForJNI
+    public enum State {
+        // After being loaded by class loader.
+        INITIAL,
+        // After launching Gecko thread
+        LAUNCHED,
+        // After loading the mozglue library.
+        MOZGLUE_READY,
+        // After loading the libxul library.
+        LIBS_READY,
+        // After initializing nsAppShell and JNI calls.
+        JNI_READY,
+        // After initializing frontend JS (corresponding to "Gecko:Ready" event)
+        RUNNING,
+        // After leaving Gecko event loop
+        EXITING,
+        // After exiting GeckoThread (corresponding to "Gecko:Exited" event)
+        EXITED;
+
+        public boolean is(final State other) {
+            return this == other;
+        }
+
+        public boolean isAtLeast(final State other) {
+            return ordinal() >= other.ordinal();
+        }
+
+        public boolean isAtMost(final State other) {
+            return ordinal() <= other.ordinal();
+        }
+
+        // Inclusive
+        public boolean isBetween(final State min, final State max) {
+            final int ord = ordinal();
+            return ord >= min.ordinal() && ord <= max.ordinal();
+        }
     }
 
-    private static final AtomicReference<LaunchState> sLaunchState =
-                                            new AtomicReference<LaunchState>(LaunchState.Launching);
-    private static final Queue<GeckoEvent> PENDING_EVENTS = new ConcurrentLinkedQueue<GeckoEvent>();
+    public static final State MIN_STATE = State.INITIAL;
+    public static final State MAX_STATE = State.EXITED;
+
+    private static final AtomicReference<State> sState = new AtomicReference<>(State.INITIAL);
+
+    private static class QueuedCall {
+        public Method method;
+        public Object target;
+        public Object[] args;
+
+        public QueuedCall(final Method method, final Object target, final Object[] args) {
+            this.method = method;
+            this.target = target;
+            this.args = args;
+        }
+    }
+
+    private static final int QUEUED_CALLS_COUNT = 16;
+    private static final ArrayList<QueuedCall> QUEUED_CALLS = new ArrayList<>(QUEUED_CALLS_COUNT);
 
     private static GeckoThread sGeckoThread;
 
+    @WrapForJNI
+    private static final ClassLoader clsLoader = GeckoThread.class.getClassLoader();
+    @WrapForJNI
+    private static MessageQueue msgQueue;
+
     private final String mArgs;
     private final String mAction;
     private final String mUri;
     private final boolean mDebugging;
 
     GeckoThread(String args, String action, String uri, boolean debugging) {
         mArgs = args;
         mAction = action;
@@ -61,78 +118,171 @@ public class GeckoThread extends Thread 
     }
 
     public static boolean ensureInit(String args, String action, String uri) {
         return ensureInit(args, action, uri, /* debugging */ false);
     }
 
     public static boolean ensureInit(String args, String action, String uri, boolean debugging) {
         ThreadUtils.assertOnUiThread();
-        if (checkLaunchState(LaunchState.Launching) && sGeckoThread == null) {
+        if (isState(State.INITIAL) && sGeckoThread == null) {
             sGeckoThread = new GeckoThread(args, action, uri, debugging);
             return true;
         }
         return false;
     }
 
     public static boolean launch() {
         ThreadUtils.assertOnUiThread();
-        if (checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched)) {
+        if (checkAndSetState(State.INITIAL, State.LAUNCHED)) {
             sGeckoThread.start();
             return true;
         }
         return false;
     }
 
     public static boolean isLaunched() {
-        return !checkLaunchState(LaunchState.Launching);
+        return !isState(State.INITIAL);
+    }
+
+    @RobocopTarget
+    public static boolean isRunning() {
+        return isState(State.RUNNING);
+    }
+
+    // Invoke the given Method and handle checked Exceptions.
+    private static void invokeMethod(final Method method, final Object obj, final Object[] args) {
+        try {
+            method.invoke(obj, args);
+        } catch (final IllegalAccessException e) {
+            throw new IllegalStateException("Unexpected exception", e);
+        } catch (final InvocationTargetException e) {
+            throw new UnsupportedOperationException("Cannot make call", e.getCause());
+        }
+    }
+
+    // Queue a call to the given method.
+    private static void queueNativeCallLocked(final Class<?> cls, final String methodName,
+                                              final Object obj, final Object[] args) {
+        final Class<?>[] argTypes = new Class<?>[args.length];
+        for (int i = 0; i < args.length; i++) {
+            Class<?> argType = args[i].getClass();
+            if (argType == Boolean.class) argType = Boolean.TYPE;
+            else if (argType == Byte.class) argType = Byte.TYPE;
+            else if (argType == Character.class) argType = Character.TYPE;
+            else if (argType == Double.class) argType = Double.TYPE;
+            else if (argType == Float.class) argType = Float.TYPE;
+            else if (argType == Integer.class) argType = Integer.TYPE;
+            else if (argType == Long.class) argType = Long.TYPE;
+            else if (argType == Short.class) argType = Short.TYPE;
+            argTypes[i] = argType;
+        }
+        final Method method;
+        try {
+            method = cls.getDeclaredMethod(methodName, argTypes);
+        } catch (final NoSuchMethodException e) {
+            throw new UnsupportedOperationException("Cannot find method", e);
+        }
+
+        if (QUEUED_CALLS.size() == 0 && isRunning()) {
+            invokeMethod(method, obj, args);
+            return;
+        }
+        QUEUED_CALLS.add(new QueuedCall(method, obj, args));
     }
 
-    private String initGeckoEnvironment() {
-        final Locale locale = Locale.getDefault();
+    /**
+     * Queue a call to the given static method until Gecko is in RUNNING state.
+     *
+     * @param cls Class that declares the static method.
+     * @param methodName Name of the static method.
+     * @param args Args to call the static method with.
+     */
+    public static void queueNativeCall(final Class<?> cls, final String methodName,
+                                       final Object... args) {
+        synchronized (QUEUED_CALLS) {
+            queueNativeCallLocked(cls, methodName, null, args);
+        }
+    }
 
+    /**
+     * Queue a call to the given instance method until Gecko is in RUNNING state.
+     *
+     * @param obj Object that declares the instance method.
+     * @param methodName Name of the instance method.
+     * @param args Args to call the instance method with.
+     */
+    public static void queueNativeCall(final Object obj, final String methodName,
+                                       final Object... args) {
+        synchronized (QUEUED_CALLS) {
+            queueNativeCallLocked(obj.getClass(), methodName, obj, args);
+        }
+    }
+
+    // Run all queued methods
+    private static void flushQueuedNativeCalls(final State state) {
+        if (!state.is(State.RUNNING)) {
+            return;
+        }
+        synchronized (QUEUED_CALLS) {
+            for (QueuedCall call : QUEUED_CALLS) {
+                if (call.method == null) {
+                    final GeckoEvent e = (GeckoEvent) call.target;
+                    GeckoAppShell.notifyGeckoOfEvent(e);
+                    e.recycle();
+                    continue;
+                }
+                invokeMethod(call.method, call.target, call.args);
+            }
+            QUEUED_CALLS.clear();
+            QUEUED_CALLS.trimToSize();
+        }
+    }
+
+    private static String initGeckoEnvironment() {
         final Context context = GeckoAppShell.getContext();
         GeckoLoader.loadMozGlue(context);
+        setState(State.MOZGLUE_READY);
 
+        final Locale locale = Locale.getDefault();
         final Resources res = context.getResources();
         if (locale.toString().equalsIgnoreCase("zh_hk")) {
             final Locale mappedLocale = Locale.TRADITIONAL_CHINESE;
             Locale.setDefault(mappedLocale);
             Configuration config = res.getConfiguration();
             config.locale = mappedLocale;
             res.updateConfiguration(config, null);
         }
 
-        String resourcePath = "";
         String[] pluginDirs = null;
         try {
             pluginDirs = GeckoAppShell.getPluginDirectories();
         } catch (Exception e) {
             Log.w(LOGTAG, "Caught exception getting plugin dirs.", e);
         }
 
-        resourcePath = context.getPackageResourcePath();
+        final String resourcePath = context.getPackageResourcePath();
         GeckoLoader.setupGeckoEnvironment(context, pluginDirs, context.getFilesDir().getPath());
 
         GeckoLoader.loadSQLiteLibs(context, resourcePath);
         GeckoLoader.loadNSSLibs(context, resourcePath);
         GeckoLoader.loadGeckoLibs(context, resourcePath);
-        GeckoJavaSampler.setLibsLoaded();
+        setState(State.LIBS_READY);
 
         return resourcePath;
     }
 
-    private String getTypeFromAction(String action) {
+    private static String getTypeFromAction(String action) {
         if (GeckoApp.ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
             return "-bookmark";
         }
         return null;
     }
 
-    private String addCustomProfileArg(String args) {
+    private static String addCustomProfileArg(String args) {
         String profileArg = "";
         String guestArg = "";
         if (GeckoAppShell.getGeckoInterface() != null) {
             final GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
 
             if (profile.inGuestMode()) {
                 try {
                     profileArg = " -profile " + profile.getDir().getCanonicalPath();
@@ -148,102 +298,204 @@ public class GeckoThread extends Thread 
                 // force Gecko to use the default profile for this activity
                 profileArg = " -P " + profile.forceCreate().getName();
             }
         }
 
         return (args != null ? args : "&q