Merge inbound to central, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 10 May 2016 15:20:22 -0700
changeset 296870 674a552743785c28c75866969aad513bd8eaf6ae
parent 296808 ee562525573f8896fe4f7a5ac053de3d97ae4ccb (current diff)
parent 296869 a70f5cb17432cd02a4a030cddcf1101da29ea24a (diff)
child 296871 5467a8d9d844fff2b8916999ebd1f4e90289e2d1
child 296895 efb1646528c5af39cca14890d30900d01fcd8196
push id30247
push userkwierso@gmail.com
push dateTue, 10 May 2016 22:20:29 +0000
treeherdermozilla-central@674a55274378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.0a1
first release with
nightly linux32
674a55274378 / 49.0a1 / 20160511030221 / files
nightly linux64
674a55274378 / 49.0a1 / 20160511030221 / files
nightly mac
674a55274378 / 49.0a1 / 20160511030221 / files
nightly win32
674a55274378 / 49.0a1 / 20160511030221 / files
nightly win64
674a55274378 / 49.0a1 / 20160511030221 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge CLOSED TREE
dom/svg/SVGAltGlyphElement.cpp
dom/svg/SVGAltGlyphElement.h
dom/webidl/SVGAltGlyphElement.webidl
layout/reftests/svg/altGlyph-01-ref.svg
layout/reftests/svg/altGlyph-01.svg
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -96,17 +96,17 @@ public:
   {
     if (!mSubpropertyCountInitialized) {
       PodZero(&mSubpropertyCount);
       mSubpropertyCountInitialized = true;
     }
     if (mSubpropertyCount[aProperty] == 0) {
       uint32_t count = 0;
       CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
-          p, aProperty, nsCSSProps::eEnabledForAllContent) {
+          p, aProperty, CSSEnabledState::eForAllContent) {
         ++count;
       }
       mSubpropertyCount[aProperty] = count;
     }
     return mSubpropertyCount[aProperty];
   }
 
 private:
@@ -487,23 +487,23 @@ KeyframeUtils::GetAnimationPropertiesFro
       // a KeyframeValueEntry for each value.
       nsTArray<PropertyStyleAnimationValuePair> values;
 
       // For shorthands, we store the string as a token stream so we need to
       // extract that first.
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
-              nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
+              CSSEnabledState::eForAllContent, aElement, aStyleContext,
               tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
           continue;
         }
       } else {
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
-              nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
+              CSSEnabledState::eForAllContent, aElement, aStyleContext,
               pair.mValue, /* aUseSVGMode */ false, values)) {
           continue;
         }
         MOZ_ASSERT(values.Length() == 1,
                    "Longhand properties should produce a single"
                    " StyleAnimationValue");
       }
 
@@ -690,17 +690,17 @@ GetPropertyValuesPairs(JSContext* aCx,
   }
   for (size_t i = 0, n = ids.length(); i < n; i++) {
     nsAutoJSString propName;
     if (!propName.init(aCx, ids[i])) {
       return false;
     }
     nsCSSProperty property =
       nsCSSProps::LookupPropertyByIDLName(propName,
-                                          nsCSSProps::eEnabledForAllContent);
+                                          CSSEnabledState::eForAllContent);
     if (property != eCSSProperty_UNKNOWN &&
         (nsCSSProps::IsShorthand(property) ||
          nsCSSProps::kAnimTypeTable[property] != eStyleAnimType_None)) {
       // Only need to check for longhands being animatable, as the
       // StyleAnimationValue::ComputeValues calls later on will check for
       // a shorthand's components being animatable.
       AdditionalProperty* p = properties.AppendElement();
       p->mProperty = property;
@@ -1105,17 +1105,17 @@ RequiresAdditiveAnimation(const nsTArray
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
         nsCSSParser parser(aDocument->CSSLoader());
         if (!parser.IsValueValidForProperty(pair.mProperty,
                                             tokenStream->mTokenStream)) {
           continue;
         }
         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
-            prop, pair.mProperty, nsCSSProps::eEnabledForAllContent) {
+            prop, pair.mProperty, CSSEnabledState::eForAllContent) {
           addToPropertySets(*prop, offsetToUse);
         }
       } else {
         if (pair.mValue.GetUnit() == eCSSUnit_TokenStream) {
           continue;
         }
         addToPropertySets(pair.mProperty, offsetToUse);
       }
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -541,16 +541,20 @@ protected:
                                          mClonedData.mBlobs.Length()))) {
         return false;
       }
 
       mClonedData.mBlobs.AppendElement(blob->Impl());
       return true;
     }
 
+    if (!JS_ObjectNotWritten(aWriter, aObj)) {
+      return false;
+    }
+
     JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
     JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
     if (NS_WARN_IF(!jsString)) {
       return false;
     }
 
     if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
       return false;
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -95,16 +95,20 @@ WindowNamedPropertiesHandler::getOwnProp
     return true;
   }
 
   nsAutoJSString str;
   if (!str.init(aCx, JSID_TO_STRING(aId))) {
     return false;
   }
 
+  if(str.IsEmpty()) {
+    return true;
+  }
+
   // Grab the DOM window.
   JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
   nsGlobalWindow* win = xpc::WindowOrNull(global);
   if (win->Length() > 0) {
     nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(str);
     if (childWin && ShouldExposeChildWindow(str, childWin)) {
       // We found a subframe of the right name. Shadowing via |var foo| in
       // global scope is still allowed, since |var| only looks up |own|
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -166,18 +166,21 @@ SelectionCopyHelper(nsISelection *aSel, 
   }
 
   // Second, prepare the text/html flavor.
   nsAutoString textHTMLBuf;
   nsAutoString htmlParentsBuf;
   nsAutoString htmlInfoBuf;
   if (encodedTextHTML) {
     // Redo the encoding, but this time use the passed-in flags.
+    // Don't allow wrapping of CJK strings.
     mimeType.AssignLiteral(kHTMLMime);
-    rv = docEncoder->Init(domDoc, mimeType, aFlags);
+    rv = docEncoder->Init(domDoc, mimeType,
+                          aFlags |
+                          nsIDocumentEncoder::OutputDisallowLineBreaking);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = docEncoder->SetSelection(aSel);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = docEncoder->EncodeToStringWithContext(htmlParentsBuf, htmlInfoBuf,
                                                textHTMLBuf);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1761,108 +1761,16 @@ nsWindowSH::GlobalResolve(nsGlobalWindow
 
   // The class_name had better match our name
   MOZ_ASSERT(name.Equals(class_name));
 
   NS_ENSURE_TRUE(class_name, NS_ERROR_UNEXPECTED);
 
   nsresult rv = NS_OK;
 
-  if (name_struct->mType == nsGlobalNameStruct::eTypeNewDOMBinding ||
-      name_struct->mType == nsGlobalNameStruct::eTypeClassProto ||
-      name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
-    // Lookup new DOM bindings.
-    DefineInterface getOrCreateInterfaceObject =
-      name_struct->mDefineDOMInterface;
-    if (getOrCreateInterfaceObject) {
-      if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor &&
-          !OldBindingConstructorEnabled(name_struct, aWin, cx)) {
-        return NS_OK;
-      }
-
-      ConstructorEnabled* checkEnabledForScope = name_struct->mConstructorEnabled;
-      // We do the enabled check on the current compartment of cx, but for the
-      // actual object we pass in the underlying object in the Xray case.  That
-      // way the callee can decide whether to allow access based on the caller
-      // or the window being touched.
-      JS::Rooted<JSObject*> global(cx,
-        js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
-      if (!global) {
-        return NS_ERROR_DOM_SECURITY_ERR;
-      }
-      if (checkEnabledForScope && !checkEnabledForScope(cx, global)) {
-        return NS_OK;
-      }
-
-      // The DOM constructor resolve machinery interacts with Xrays in tricky
-      // ways, and there are some asymmetries that are important to understand.
-      //
-      // In the regular (non-Xray) case, we only want to resolve constructors
-      // once (so that if they're deleted, they don't reappear). We do this by
-      // stashing the constructor in a slot on the global, such that we can see
-      // during resolve whether we've created it already. This is rather
-      // memory-intensive, so we don't try to maintain these semantics when
-      // manipulating a global over Xray (so the properties just re-resolve if
-      // they've been deleted).
-      //
-      // Unfortunately, there's a bit of an impedance-mismatch between the Xray
-      // and non-Xray machinery. The Xray machinery wants an API that returns a
-      // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
-      // snared up with trying to define a property on the Xray holder. At the
-      // same time, the DefineInterface callbacks are set up to define things
-      // directly on the global.  And re-jiggering them to return property
-      // descriptors is tricky, because some DefineInterface callbacks define
-      // multiple things (like the Image() alias for HTMLImageElement).
-      //
-      // So the setup is as-follows:
-      //
-      // * The resolve function takes a JS::PropertyDescriptor, but in the
-      //   non-Xray case, callees may define things directly on the global, and
-      //   set the value on the property descriptor to |undefined| to indicate
-      //   that there's nothing more for the caller to do. We assert against
-      //   this behavior in the Xray case.
-      //
-      // * We make sure that we do a non-Xray resolve first, so that all the
-      //   slots are set up. In the Xray case, this means unwrapping and doing
-      //   a non-Xray resolve before doing the Xray resolve.
-      //
-      // This all could use some grand refactoring, but for now we just limp
-      // along.
-      if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
-        JS::Rooted<JSObject*> interfaceObject(cx);
-        {
-          JSAutoCompartment ac(cx, global);
-          interfaceObject = getOrCreateInterfaceObject(cx, global, id, false);
-        }
-        if (NS_WARN_IF(!interfaceObject)) {
-          return NS_ERROR_FAILURE;
-        }
-        if (!JS_WrapObject(cx, &interfaceObject)) {
-          return NS_ERROR_FAILURE;
-        }
-
-        FillPropertyDescriptor(desc, obj, 0, JS::ObjectValue(*interfaceObject));
-      } else {
-        JS::Rooted<JSObject*> interfaceObject(cx,
-          getOrCreateInterfaceObject(cx, obj, id, true));
-        if (NS_WARN_IF(!interfaceObject)) {
-          return NS_ERROR_FAILURE;
-        }
-        // We've already defined the property.  We indicate this to the caller
-        // by filling a property descriptor with JS::UndefinedValue() as the
-        // value.  We still have to fill in a property descriptor, though, so
-        // that the caller knows the property is in fact on this object.  It
-        // doesn't matter what we pass for the "readonly" argument here.
-        FillPropertyDescriptor(desc, obj, JS::UndefinedValue(), false);
-      }
-
-      return NS_OK;
-    }
-  }
-
   if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
     if (!OldBindingConstructorEnabled(name_struct, aWin, cx)) {
       return NS_OK;
     }
 
     // Create the XPConnect prototype for our classinfo, PostCreateProto will
     // set up the prototype chain.  This will go ahead and define things on the
     // actual window's global.
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2498,17 +2498,17 @@ nsDOMWindowUtils::ComputeAnimationDistan
                                            const nsAString& aValue2,
                                            double* aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCSSProperty property =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState);
+    nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eIgnoreEnabledState);
   if (property != eCSSProperty_UNKNOWN && nsCSSProps::IsShorthand(property)) {
     property = eCSSProperty_UNKNOWN;
   }
 
   MOZ_ASSERT(property == eCSSProperty_UNKNOWN ||
              !nsCSSProps::IsShorthand(property),
              "should not have shorthand");
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1326,19 +1326,16 @@ GK_ATOM(yes, "yes")
 GK_ATOM(z_index, "z-index")
 GK_ATOM(zeroDigit, "zero-digit")
 
 
 GK_ATOM(percentage, "%")
 GK_ATOM(A, "A")
 GK_ATOM(alignment_baseline, "alignment-baseline")
 GK_ATOM(allowReorder, "allowReorder")
-GK_ATOM(altGlyph, "altGlyph")
-GK_ATOM(altGlyphDef, "altGlyphDef")
-GK_ATOM(altGlyphItem, "altGlyphItem")
 GK_ATOM(amplitude, "amplitude")
 GK_ATOM(animate, "animate")
 GK_ATOM(animateColor, "animateColor")
 GK_ATOM(animateMotion, "animateMotion")
 GK_ATOM(animateTransform, "animateTransform")
 GK_ATOM(arithmetic, "arithmetic")
 GK_ATOM(atop, "atop")
 GK_ATOM(azimuth, "azimuth")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -224,16 +224,17 @@
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ServiceWorkerRegistration.h"
 #include "mozilla/dom/U2F.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
 #endif
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
@@ -4494,16 +4495,25 @@ nsGlobalWindow::DoResolve(JSContext* aCx
 
   // Note: Keep this in sync with MayResolve.
 
   // Note: The infallibleInit call in GlobalResolve depends on this check.
   if (!JSID_IS_STRING(aId)) {
     return true;
   }
 
+  bool found;
+  if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) {
+    return false;
+  }
+
+  if (found) {
+    return true;
+  }
+
   nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc);
   if (NS_FAILED(rv)) {
     return Throw(aCx, rv);
   }
 
   return true;
 }
 
@@ -4522,16 +4532,20 @@ nsGlobalWindow::MayResolve(jsid aId)
   }
 
   if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSRuntime::IDX_CONTROLLERS)) {
     // We only resolve .controllers in release builds and on non-chrome windows,
     // but let's not worry about any of that stuff.
     return true;
   }
 
+  if (WebIDLGlobalNameHash::MayResolve(aId)) {
+    return true;
+  }
+
   nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
   if (!nameSpaceManager) {
     // Really shouldn't happen.  Fail safe.
     return true;
   }
 
   nsAutoString name;
   AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));
@@ -4545,22 +4559,23 @@ nsGlobalWindow::GetOwnPropertyNames(JSCo
 {
   MOZ_ASSERT(IsInnerWindow());
   // "Components" is marked as enumerable but only resolved on demand :-/.
   //aNames.AppendElement(NS_LITERAL_STRING("Components"));
 
   nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
   if (nameSpaceManager) {
     JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
+
+    WebIDLGlobalNameHash::GetNames(aCx, wrapper, aNames);
+
     for (auto i = nameSpaceManager->GlobalNameIter(); !i.Done(); i.Next()) {
       const GlobalNameMapEntry* entry = i.Get();
       if (nsWindowSH::NameStructEnabled(aCx, this, entry->mKey,
-                                        entry->mGlobalName) &&
-          (!entry->mGlobalName.mConstructorEnabled ||
-           entry->mGlobalName.mConstructorEnabled(aCx, wrapper))) {
+                                        entry->mGlobalName)) {
         aNames.AppendElement(entry->mKey);
       }
     }
   }
 }
 
 /* static */ bool
 nsGlobalWindow::IsPrivilegedChromeWindow(JSContext* aCx, JSObject* aObj)
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -20,25 +20,27 @@
 #include "nsXPIDLString.h"
 #include "nsPrintfCString.h"
 #include "nsReadableUtils.h"
 #include "nsHashKeys.h"
 #include "nsDOMClassInfo.h"
 #include "nsCRT.h"
 #include "nsIObserverService.h"
 #include "nsISimpleEnumerator.h"
-
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #define NS_INTERFACE_PREFIX "nsI"
 #define NS_DOM_INTERFACE_PREFIX "nsIDOM"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 static PLDHashNumber
 GlobalNameHashHashKey(const void *key)
 {
   const nsAString *str = static_cast<const nsAString *>(key);
   return HashString(*str);
 }
 
@@ -90,40 +92,43 @@ static const PLDHashTableOps hash_table_
 {
   GlobalNameHashHashKey,
   GlobalNameHashMatchEntry,
   PLDHashTable::MoveEntryStub,
   GlobalNameHashClearEntry,
   GlobalNameHashInitEntry
 };
 
-#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH   512
+#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH          32
 
 nsScriptNameSpaceManager::nsScriptNameSpaceManager()
   : mGlobalNames(&hash_table_ops, sizeof(GlobalNameMapEntry),
                  GLOBALNAME_HASHTABLE_INITIAL_LENGTH)
 {
   MOZ_COUNT_CTOR(nsScriptNameSpaceManager);
 }
 
 nsScriptNameSpaceManager::~nsScriptNameSpaceManager()
 {
   UnregisterWeakMemoryReporter(this);
   MOZ_COUNT_DTOR(nsScriptNameSpaceManager);
 }
 
 nsGlobalNameStruct *
-nsScriptNameSpaceManager::AddToHash(const nsAString *aKey,
+nsScriptNameSpaceManager::AddToHash(const char *aKey,
                                     const char16_t **aClassName)
 {
-  auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Add(aKey, fallible));
+  NS_ConvertASCIItoUTF16 key(aKey);
+  auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Add(&key, fallible));
   if (!entry) {
     return nullptr;
   }
 
+  WebIDLGlobalNameHash::Remove(aKey, key.Length());
+
   if (aClassName) {
     *aClassName = entry->mKey.get();
   }
 
   return &entry->mGlobalName;
 }
 
 void
@@ -225,18 +230,17 @@ nsScriptNameSpaceManager::RegisterClassN
 
   // If a external constructor is already defined with aClassName we
   // won't overwrite it.
 
   if (s->mType == nsGlobalNameStruct::eTypeExternalConstructor) {
     return NS_OK;
   }
 
-  NS_ASSERTION(s->mType == nsGlobalNameStruct::eTypeNotInitialized ||
-               s->mType == nsGlobalNameStruct::eTypeNewDOMBinding,
+  NS_ASSERTION(s->mType == nsGlobalNameStruct::eTypeNotInitialized,
                "Whaaa, JS environment name clash!");
 
   s->mType = nsGlobalNameStruct::eTypeClassConstructor;
   s->mDOMClassInfoID = aDOMClassInfoID;
   s->mChromeOnly = aPrivileged;
   s->mAllowXBL = aXBLAllowed;
 
   return NS_OK;
@@ -249,18 +253,17 @@ nsScriptNameSpaceManager::RegisterClassP
 {
   NS_ENSURE_ARG_POINTER(aConstructorProtoIID);
 
   *aFoundOld = false;
 
   nsGlobalNameStruct *s = AddToHash(aClassName);
   NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
 
-  if (s->mType != nsGlobalNameStruct::eTypeNotInitialized &&
-      s->mType != nsGlobalNameStruct::eTypeNewDOMBinding) {
+  if (s->mType != nsGlobalNameStruct::eTypeNotInitialized) {
     *aFoundOld = true;
 
     return NS_OK;
   }
 
   s->mType = nsGlobalNameStruct::eTypeClassProto;
   s->mIID = *aConstructorProtoIID;
 
@@ -344,18 +347,17 @@ nsScriptNameSpaceManager::OperateCategor
   // Copy CID onto the stack, so we can free it right away and avoid having
   // to add cleanup code at every exit point from this function.
   nsCID cid = *cidPtr;
   free(cidPtr);
 
   nsGlobalNameStruct *s = AddToHash(categoryEntry.get());
   NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
 
-  if (s->mType == nsGlobalNameStruct::eTypeNotInitialized ||
-      s->mType == nsGlobalNameStruct::eTypeNewDOMBinding) {
+  if (s->mType == nsGlobalNameStruct::eTypeNotInitialized) {
     s->mType = type;
     s->mCID = cid;
     s->mChromeOnly =
       strcmp(aCategory, JAVASCRIPT_GLOBAL_PRIVILEGED_PROPERTY_CATEGORY) == 0;
   } else {
     NS_WARNING("Global script name not overwritten!");
   }
 
@@ -409,31 +411,16 @@ nsScriptNameSpaceManager::Observe(nsISup
   }
 
   // TODO: we could observe NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
   // but we are safe without it. See bug 600460.
 
   return NS_OK;
 }
 
-void
-nsScriptNameSpaceManager::RegisterDefineDOMInterface(const nsAFlatString& aName,
-    mozilla::dom::DefineInterface aDefineDOMInterface,
-    mozilla::dom::ConstructorEnabled* aConstructorEnabled)
-{
-  nsGlobalNameStruct *s = AddToHash(&aName);
-  if (s) {
-    if (s->mType == nsGlobalNameStruct::eTypeNotInitialized) {
-      s->mType = nsGlobalNameStruct::eTypeNewDOMBinding;
-    }
-    s->mDefineDOMInterface = aDefineDOMInterface;
-    s->mConstructorEnabled = aConstructorEnabled;
-  }
-}
-
 MOZ_DEFINE_MALLOC_SIZE_OF(ScriptNameSpaceManagerMallocSizeOf)
 
 NS_IMETHODIMP
 nsScriptNameSpaceManager::CollectReports(
   nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
 {
   return MOZ_COLLECT_REPORT(
     "explicit/script-namespace-manager", KIND_HEAP, UNITS_BYTES,
--- a/dom/base/nsScriptNameSpaceManager.h
+++ b/dom/base/nsScriptNameSpaceManager.h
@@ -28,45 +28,34 @@
 #include "nsString.h"
 #include "nsID.h"
 #include "PLDHashTable.h"
 #include "nsDOMClassInfo.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "xpcpublic.h"
 
-
 struct nsGlobalNameStruct
 {
   enum nametype {
     eTypeNotInitialized,
-    eTypeNewDOMBinding,
     eTypeProperty,
     eTypeExternalConstructor,
     eTypeClassConstructor,
     eTypeClassProto,
   } mType;
 
-  // mChromeOnly is only used for structs that define non-WebIDL things
-  // (possibly in addition to WebIDL ones).  In particular, it's not even
-  // initialized for eTypeNewDOMBinding structs.
   bool mChromeOnly : 1;
   bool mAllowXBL : 1;
 
   union {
     int32_t mDOMClassInfoID; // eTypeClassConstructor
     nsIID mIID; // eTypeClassProto
-    nsCID mCID; // All other types except eTypeNewDOMBinding
+    nsCID mCID; // All other types
   };
-
-  // For new style DOM bindings.
-  mozilla::dom::DefineInterface mDefineDOMInterface;
-
-  // May be null if enabled unconditionally
-  mozilla::dom::ConstructorEnabled* mConstructorEnabled;
 };
 
 class GlobalNameMapEntry : public PLDHashEntryHdr
 {
 public:
   // Our hash table ops don't care about the order of these members.
   nsString mKey;
   nsGlobalNameStruct mGlobalName;
@@ -107,29 +96,16 @@ public:
                              bool aPrivileged,
                              bool aXBLAllowed,
                              const char16_t **aResult);
 
   nsresult RegisterClassProto(const char *aClassName,
                               const nsIID *aConstructorProtoIID,
                               bool *aFoundOld);
 
-  void RegisterDefineDOMInterface(const nsAFlatString& aName,
-    mozilla::dom::DefineInterface aDefineDOMInterface,
-    mozilla::dom::ConstructorEnabled* aConstructorEnabled);
-  template<size_t N>
-  void RegisterDefineDOMInterface(const char16_t (&aKey)[N],
-    mozilla::dom::DefineInterface aDefineDOMInterface,
-    mozilla::dom::ConstructorEnabled* aConstructorEnabled)
-  {
-    nsLiteralString key(aKey);
-    return RegisterDefineDOMInterface(key, aDefineDOMInterface,
-                                      aConstructorEnabled);
-  }
-
   class NameIterator : public PLDHashTable::Iterator
   {
   public:
     typedef PLDHashTable::Iterator Base;
     explicit NameIterator(PLDHashTable* aTable) : Base(aTable) {}
     NameIterator(NameIterator&& aOther) : Base(mozilla::Move(aOther.mTable)) {}
 
     const GlobalNameMapEntry* Get() const
@@ -150,32 +126,24 @@ public:
 
 private:
   virtual ~nsScriptNameSpaceManager();
 
   // Adds a new entry to the hash and returns the nsGlobalNameStruct
   // that aKey will be mapped to. If mType in the returned
   // nsGlobalNameStruct is != eTypeNotInitialized, an entry for aKey
   // already existed.
-  nsGlobalNameStruct *AddToHash(const nsAString *aKey,
+  nsGlobalNameStruct *AddToHash(const char *aKey,
                                 const char16_t **aClassName = nullptr);
-  nsGlobalNameStruct *AddToHash(const char *aKey,
-                                const char16_t **aClassName = nullptr)
-  {
-    NS_ConvertASCIItoUTF16 key(aKey);
-    return AddToHash(&key, aClassName);
-  }
+
   // Removes an existing entry from the hash.
   void RemoveFromHash(const nsAString *aKey);
 
   nsresult FillHash(nsICategoryManager *aCategoryManager,
                     const char *aCategory);
-  nsresult RegisterInterface(const char* aIfName,
-                             const nsIID *aIfIID,
-                             bool* aFoundOld);
 
   /**
    * Add a new category entry into the hash table.
    * Only some categories can be added (see the beginning of the definition).
    * The other ones will be ignored.
    *
    * @aCategoryManager Instance of the category manager service.
    * @aCategory        Category where the entry comes from.
--- a/dom/base/nsTreeSanitizer.cpp
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -275,19 +275,16 @@ nsIAtom** const kURLAttributesHTML[] = {
   &nsGkAtoms::longdesc,
   &nsGkAtoms::cite,
   &nsGkAtoms::background,
   nullptr
 };
 
 nsIAtom** const kElementsSVG[] = {
   &nsGkAtoms::a, // a
-  &nsGkAtoms::altGlyph, // altGlyph
-  &nsGkAtoms::altGlyphDef, // altGlyphDef
-  &nsGkAtoms::altGlyphItem, // altGlyphItem
   &nsGkAtoms::circle, // circle
   &nsGkAtoms::clipPath, // clipPath
   &nsGkAtoms::colorProfile, // color-profile
   &nsGkAtoms::cursor, // cursor
   &nsGkAtoms::defs, // defs
   &nsGkAtoms::desc, // desc
   &nsGkAtoms::ellipse, // ellipse
   &nsGkAtoms::elevation, // elevation
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -328,16 +328,18 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_bug311681.xml]
 [test_bug313646.html]
 [test_bug320799.html]
 [test_bug322317.html]
 [test_bug326337.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug330925.xhtml]
 [test_bug331959.html]
+[test_bug333064.html]
+skip-if = toolkit == 'android'
 [test_bug333198.html]
 [test_bug333673.html]
 [test_bug337631.html]
 [test_bug338541.xhtml]
 [test_bug338583.html]
 # b2g(https not working, bug 907770) b2g-debug(https not working, bug 907770) b2g-desktop(43 total - bug 901343, specialpowers.wrap issue createsystemxhr)
 # e10s - bug 970589, bug 1091934
 skip-if = buildapp == 'b2g' || toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug333064.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=333064
+-->
+<head>
+  <title>Test for Bug 333064</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=333064">Mozilla Bug 333064</a>
+<p id="display"></p>
+
+<div id="display">
+</div>
+<div id="korean-text">안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안안</div>
+
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 333064 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  var div = document.getElementById("korean-text");
+  var sel = window.getSelection();
+
+  // Select text node in div.
+  var r = document.createRange();
+  r.setStart(div, 0);
+  r.setEnd(div, 1);
+  sel.addRange(r);
+
+  SimpleTest.waitForClipboard(
+    function compare(value) {
+      // Make sure we got the HTML flavour we asked for and that our
+      // string is included without additional spaces.
+      return value.includes("korean-text") && value.includes("안".repeat(160));
+    },
+    function setup() {
+      synthesizeKey("C", {accelKey: true});
+    },
+    function onSuccess() {
+      SimpleTest.finish();
+    },
+    function onFailure() {
+      SimpleTest.finish();
+    },
+    "text/html"
+  );
+});
+
+</script>
+
+</body>
+</html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLSharedObjectElement.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
 #include "mozilla/dom/HTMLAppletElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "nsDOMClassInfo.h"
 #include "ipc/ErrorIPCUtils.h"
 #include "mozilla/UseCounter.h"
 
 namespace mozilla {
@@ -2931,30 +2932,25 @@ static bool sRegisteredDOMNames = false;
 
 nsresult
 RegisterDOMNames()
 {
   if (sRegisteredDOMNames) {
     return NS_OK;
   }
 
+  // Register new DOM bindings
+  WebIDLGlobalNameHash::Init();
+
   nsresult rv = nsDOMClassInfo::Init();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize nsDOMClassInfo");
     return rv;
   }
 
-  // Register new DOM bindings
-  nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
-  if (!nameSpaceManager) {
-    NS_ERROR("Could not initialize nsScriptNameSpaceManager");
-    return NS_ERROR_FAILURE;
-  }
-  mozilla::dom::Register(nameSpaceManager);
-
   sRegisteredDOMNames = true;
 
   return NS_OK;
 }
 
 /* static */
 bool
 CreateGlobalOptions<nsGlobalWindow>::PostCreateGlobal(JSContext* aCx,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -12051,24 +12051,24 @@ class CGDescriptor(CGThing):
             cgThings.append(CGEnumerateHook(descriptor))
 
         if descriptor.hasNamedPropertiesObject:
             cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
 
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor, properties))
 
-        if descriptor.interface.hasInterfaceObject():
-            cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
-
         if ((descriptor.interface.hasInterfaceObject() or descriptor.interface.isNavigatorProperty()) and
             not descriptor.interface.isExternal() and
             descriptor.isExposedConditionally()):
             cgThings.append(CGConstructorEnabled(descriptor))
 
+        if descriptor.registersGlobalNamesOnWindow:
+            cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
+
         if (descriptor.interface.hasMembersInSlots() and
             descriptor.interface.hasChildInterfaces()):
             raise TypeError("We don't support members in slots on "
                             "non-leaf interfaces like %s" %
                             descriptor.interface.identifier.name)
 
         if descriptor.concrete:
             if descriptor.proxy:
@@ -13051,58 +13051,63 @@ class CGResolveSystemBinding(CGAbstractM
         return CGList([CGGeneric("MOZ_ASSERT(NS_IsMainThread());\n"),
                        jsidDecls,
                        jsidInits,
                        definitions,
                        CGGeneric("return true;\n")],
                       "\n").define()
 
 
-class CGRegisterProtos(CGAbstractMethod):
+def getGlobalNames(config):
+    names = []
+    for desc in config.getDescriptors(registersGlobalNamesOnWindow=True):
+        names.append((desc.name, desc))
+        names.extend((n.identifier.name, desc) for n in desc.interface.namedConstructors)
+    return names
+
+class CGGlobalNamesString(CGGeneric):
     def __init__(self, config):
-        CGAbstractMethod.__init__(self, None, 'Register', 'void',
-                                  [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
+        globalNames = getGlobalNames(config)
+        currentOffset = 0
+        strings = []
+        for (name, _) in globalNames:
+            strings.append('/* %i */ "%s\\0"' % (currentOffset, name))
+            currentOffset += len(name) + 1 # Add trailing null.
+        define = fill("""
+            const uint32_t WebIDLGlobalNameHash::sCount = ${count};
+
+            const char WebIDLGlobalNameHash::sNames[] =
+              $*{strings}
+
+            """,
+            count=len(globalNames),
+            strings="\n".join(strings) + ";\n")
+
+        CGGeneric.__init__(self, define=define)
+
+
+class CGRegisterGlobalNames(CGAbstractMethod):
+    def __init__(self, config):
+        CGAbstractMethod.__init__(self, None, 'RegisterWebIDLGlobalNames',
+                                  'void', [])
         self.config = config
 
-    def _defineMacro(self):
-        return dedent("""
-            #define REGISTER_PROTO(_dom_class, _ctor_check) \\
-              aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_class), _dom_class##Binding::DefineDOMInterface, _ctor_check);
-            #define REGISTER_CONSTRUCTOR(_dom_constructor, _dom_class, _ctor_check) \\
-              aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_constructor), _dom_class##Binding::DefineDOMInterface, _ctor_check);
-            """)
-
-    def _undefineMacro(self):
-        return dedent("""
-            #undef REGISTER_CONSTRUCTOR
-            #undef REGISTER_PROTO
-            """)
-
-    def _registerProtos(self):
+    def definition_body(self):
         def getCheck(desc):
             if not desc.isExposedConditionally():
                 return "nullptr"
             return "%sBinding::ConstructorEnabled" % desc.name
-        lines = []
-        for desc in self.config.getDescriptors(hasInterfaceObject=True,
-                                               isExternal=False,
-                                               workers=False,
-                                               isExposedInWindow=True,
-                                               register=True):
-            lines.append("REGISTER_PROTO(%s, %s);\n" % (desc.name, getCheck(desc)))
-            lines.extend("REGISTER_CONSTRUCTOR(%s, %s, %s);\n" % (n.identifier.name, desc.name, getCheck(desc))
-                         for n in desc.interface.namedConstructors)
-        return ''.join(lines)
-
-    def indent_body(self, body):
-        # Don't indent the body of this method, as it's all preprocessor gunk.
-        return body
-
-    def definition_body(self):
-        return "\n" + self._defineMacro() + "\n" + self._registerProtos() + "\n" + self._undefineMacro()
+
+        define = ""
+        currentOffset = 0
+        for (name, desc) in getGlobalNames(self.config):
+            length = len(name)
+            define += "WebIDLGlobalNameHash::Register(%i, %i, %sBinding::DefineDOMInterface, %s);\n" % (currentOffset, length, desc.name, getCheck(desc))
+            currentOffset += length + 1 # Add trailing null.
+        return define
 
 
 def dependencySortObjects(objects, dependencyGetter, nameGetter):
     """
     Sort IDL objects with dependencies on each other such that if A
     depends on B then B will come before A.  This is needed for
     declaring C++ classes in the right order, for example.  Objects
     that have no dependencies are just sorted by name.
@@ -16189,30 +16194,30 @@ class GlobalGenRoots():
         curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
 
         # Done.
         return curr
 
     @staticmethod
     def RegisterBindings(config):
 
-        curr = CGRegisterProtos(config)
+        curr = CGList([CGGlobalNamesString(config), CGRegisterGlobalNames(config)])
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'],
                                  CGWrapper(curr, post='\n'))
         curr = CGWrapper(curr, post='\n')
 
         # Add the includes
         defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
                           for desc in config.getDescriptors(hasInterfaceObject=True,
                                                             workers=False,
                                                             isExposedInWindow=True,
                                                             register=True)]
-        defineIncludes.append('nsScriptNameSpaceManager.h')
+        defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h')
         defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface)
                                for desc in config.getDescriptors(isNavigatorProperty=True,
                                                                  workers=False,
                                                                  register=True)])
         curr = CGHeaders([], [], [], [], [], defineIncludes, 'RegisterBindings',
                          curr)
 
         # Add include guards.
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -819,16 +819,24 @@ class Descriptor(DescriptorProvider):
         assert self.supportsNamedProperties()
         iface = self.interface
         while iface:
             if iface.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
                 return False
             iface = iface.parent
         return True
 
+    @property
+    def registersGlobalNamesOnWindow(self):
+        return (not self.interface.isExternal() and
+                self.interface.hasInterfaceObject() and
+                not self.workers and
+                self.interface.isExposedInWindow() and
+                self.register)
+
 
 # Some utility methods
 def getTypesFromDescriptor(descriptor):
     """
     Get all argument and return types for all members of the descriptor
     """
     members = [m for m in descriptor.interface.members]
     if descriptor.interface.ctor():
new file mode 100644
--- /dev/null
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -0,0 +1,313 @@
+/* -*- 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 "WebIDLGlobalNameHash.h"
+#include "js/GCAPI.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/dom/RegisterBindings.h"
+#include "nsIMemoryReporter.h"
+#include "nsTHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+struct MOZ_STACK_CLASS WebIDLNameTableKey
+{
+  explicit WebIDLNameTableKey(JSFlatString* aJSString)
+    : mLength(js::GetFlatStringLength(aJSString))
+  {
+    mNogc.emplace();
+    JSLinearString* jsString = js::FlatStringToLinearString(aJSString);
+    if (js::LinearStringHasLatin1Chars(jsString)) {
+      mLatin1String = reinterpret_cast<const char*>(
+        js::GetLatin1LinearStringChars(*mNogc, jsString));
+      mTwoBytesString = nullptr;
+      mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0;
+    } else {
+      mLatin1String = nullptr;
+      mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString);
+      mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0;
+    }
+  }
+  explicit WebIDLNameTableKey(const char* aString, size_t aLength)
+    : mLatin1String(aString),
+      mTwoBytesString(nullptr),
+      mLength(aLength),
+      mHash(HashString(aString, aLength))
+  {
+    MOZ_ASSERT(aString[aLength] == '\0');
+  }
+
+  Maybe<JS::AutoCheckCannotGC> mNogc;
+  const char* mLatin1String;
+  const char16_t* mTwoBytesString;
+  size_t mLength;
+  uint32_t mHash;
+};
+
+struct WebIDLNameTableEntry : public PLDHashEntryHdr
+{
+  typedef const WebIDLNameTableKey& KeyType;
+  typedef const WebIDLNameTableKey* KeyTypePointer;
+
+  explicit WebIDLNameTableEntry(KeyTypePointer aKey)
+  {}
+  WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry)
+    : mNameOffset(aEntry.mNameOffset),
+      mNameLength(aEntry.mNameLength),
+      mDefine(aEntry.mDefine),
+      mEnabled(aEntry.mEnabled)
+  {}
+  ~WebIDLNameTableEntry()
+  {}
+
+  bool KeyEquals(KeyTypePointer aKey) const
+  {
+    if (mNameLength != aKey->mLength) {
+      return false;
+    }
+
+    const char* name = WebIDLGlobalNameHash::sNames + mNameOffset;
+
+    if (aKey->mLatin1String) {
+      return PodEqual(aKey->mLatin1String, name, aKey->mLength);
+    }
+
+    return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name,
+                                                aKey->mLength) == 0;
+  }
+
+  static KeyTypePointer KeyToPointer(KeyType aKey)
+  {
+    return &aKey;
+  }
+
+  static PLDHashNumber HashKey(KeyTypePointer aKey)
+  {
+    return aKey->mHash;
+  }
+
+  enum { ALLOW_MEMMOVE = true };
+
+  uint16_t mNameOffset;
+  uint16_t mNameLength;
+  WebIDLGlobalNameHash::DefineGlobalName mDefine;
+  // May be null if enabled unconditionally
+  WebIDLGlobalNameHash::ConstructorEnabled* mEnabled;
+};
+
+static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames;
+
+class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter
+{
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+  ~WebIDLGlobalNamesHashReporter() {}
+
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+                            nsISupports* aData, bool aAnonymize) override
+  {
+    int64_t amount =
+      sWebIDLGlobalNames ?
+      sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0;
+
+    return MOZ_COLLECT_REPORT("explicit/dom/webidl-globalnames", KIND_HEAP,
+                              UNITS_BYTES, amount,
+                              "Memory used by the hash table for WebIDL's "
+                              "global names.");
+  }
+};
+
+NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter)
+
+/* static */
+void
+WebIDLGlobalNameHash::Init()
+{
+  sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount);
+  RegisterWebIDLGlobalNames();
+
+  RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter());
+}
+
+/* static */
+void
+WebIDLGlobalNameHash::Shutdown()
+{
+  delete sWebIDLGlobalNames;
+}
+
+/* static */
+void
+WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength,
+                               DefineGlobalName aDefine,
+                               ConstructorEnabled* aEnabled)
+{
+  const char* name = sNames + aNameOffset;
+  WebIDLNameTableKey key(name, aNameLength);
+  WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key);
+  entry->mNameOffset = aNameOffset;
+  entry->mNameLength = aNameLength;
+  entry->mDefine = aDefine;
+  entry->mEnabled = aEnabled;
+}
+
+/* static */
+void
+WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength)
+{
+  WebIDLNameTableKey key(aName, aLength);
+  sWebIDLGlobalNames->RemoveEntry(key);
+}
+
+/* static */
+bool
+WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx,
+                                      JS::Handle<JSObject*> aObj,
+                                      JS::Handle<jsid> aId,
+                                      JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+                                      bool* aFound)
+{
+  MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!");
+
+  const WebIDLNameTableEntry* entry;
+  {
+    WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
+    // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it
+    // ends up calling through PLDHashTableOps' matchEntry function pointer, but
+    // we know WebIDLNameTableEntry::KeyEquals can't cause a GC.
+    JS::AutoSuppressGCAnalysis suppress;
+    entry = sWebIDLGlobalNames->GetEntry(key);
+  }
+
+  if (!entry) {
+    *aFound = false;
+    return true;
+  }
+
+  *aFound = true;
+
+  ConstructorEnabled* checkEnabledForScope = entry->mEnabled;
+  // We do the enabled check on the current compartment of aCx, but for the
+  // actual object we pass in the underlying object in the Xray case.  That
+  // way the callee can decide whether to allow access based on the caller
+  // or the window being touched.
+  JS::Rooted<JSObject*> global(aCx,
+    js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
+  if (!global) {
+    return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
+  }
+
+  {
+    DebugOnly<nsGlobalWindow*> win;
+    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win)));
+  }
+
+  if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
+    return true;
+  }
+
+  // The DOM constructor resolve machinery interacts with Xrays in tricky
+  // ways, and there are some asymmetries that are important to understand.
+  //
+  // In the regular (non-Xray) case, we only want to resolve constructors
+  // once (so that if they're deleted, they don't reappear). We do this by
+  // stashing the constructor in a slot on the global, such that we can see
+  // during resolve whether we've created it already. This is rather
+  // memory-intensive, so we don't try to maintain these semantics when
+  // manipulating a global over Xray (so the properties just re-resolve if
+  // they've been deleted).
+  //
+  // Unfortunately, there's a bit of an impedance-mismatch between the Xray
+  // and non-Xray machinery. The Xray machinery wants an API that returns a
+  // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
+  // snared up with trying to define a property on the Xray holder. At the
+  // same time, the DefineInterface callbacks are set up to define things
+  // directly on the global.  And re-jiggering them to return property
+  // descriptors is tricky, because some DefineInterface callbacks define
+  // multiple things (like the Image() alias for HTMLImageElement).
+  //
+  // So the setup is as-follows:
+  //
+  // * The resolve function takes a JS::PropertyDescriptor, but in the
+  //   non-Xray case, callees may define things directly on the global, and
+  //   set the value on the property descriptor to |undefined| to indicate
+  //   that there's nothing more for the caller to do. We assert against
+  //   this behavior in the Xray case.
+  //
+  // * We make sure that we do a non-Xray resolve first, so that all the
+  //   slots are set up. In the Xray case, this means unwrapping and doing
+  //   a non-Xray resolve before doing the Xray resolve.
+  //
+  // This all could use some grand refactoring, but for now we just limp
+  // along.
+  if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
+    JS::Rooted<JSObject*> interfaceObject(aCx);
+    {
+      JSAutoCompartment ac(aCx, global);
+      interfaceObject = entry->mDefine(aCx, global, aId, false);
+    }
+    if (NS_WARN_IF(!interfaceObject)) {
+      return Throw(aCx, NS_ERROR_FAILURE);
+    }
+    if (!JS_WrapObject(aCx, &interfaceObject)) {
+      return Throw(aCx, NS_ERROR_FAILURE);
+    }
+
+    FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*interfaceObject));
+    return true;
+  }
+
+  JS::Rooted<JSObject*> interfaceObject(aCx,
+                                        entry->mDefine(aCx, aObj, aId, true));
+  if (NS_WARN_IF(!interfaceObject)) {
+    return Throw(aCx, NS_ERROR_FAILURE);
+  }
+
+  // We've already defined the property.  We indicate this to the caller
+  // by filling a property descriptor with JS::UndefinedValue() as the
+  // value.  We still have to fill in a property descriptor, though, so
+  // that the caller knows the property is in fact on this object.  It
+  // doesn't matter what we pass for the "readonly" argument here.
+  FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false);
+
+  return true;
+}
+
+/* static */
+bool
+WebIDLGlobalNameHash::MayResolve(jsid aId)
+{
+  WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId));
+  // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends
+  // up calling through PLDHashTableOps' matchEntry function pointer, but we
+  // know WebIDLNameTableEntry::KeyEquals can't cause a GC.
+  JS::AutoSuppressGCAnalysis suppress;
+  return sWebIDLGlobalNames->Contains(key);
+}
+
+/* static */
+void
+WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
+                               nsTArray<nsString>& aNames)
+{
+  for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) {
+    const WebIDLNameTableEntry* entry = iter.Get();
+    if (!entry->mEnabled || entry->mEnabled(aCx, aObj)) {
+      AppendASCIItoUTF16(nsDependentCString(sNames + entry->mNameOffset,
+                                            entry->mNameLength),
+                         *aNames.AppendElement());
+    }
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/bindings/WebIDLGlobalNameHash.h
@@ -0,0 +1,70 @@
+/* -*- 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_WebIDLGlobalNameHash_h__
+#define mozilla_dom_WebIDLGlobalNameHash_h__
+
+#include "js/RootingAPI.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+struct WebIDLNameTableEntry;
+
+class WebIDLGlobalNameHash
+{
+public:
+  static void Init();
+  static void Shutdown();
+
+  typedef JSObject*
+  (*DefineGlobalName)(JSContext* cx, JS::Handle<JSObject*> global,
+                      JS::Handle<jsid> id, bool defineOnGlobal);
+
+  // Check whether a constructor should be enabled for the given object.
+  // Note that the object should NOT be an Xray, since Xrays will end up
+  // defining constructors on the underlying object.
+  // This is a typedef for the function type itself, not the function
+  // pointer, so it's more obvious that pointers to a ConstructorEnabled
+  // can be null.
+  typedef bool
+  (ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj);
+
+  static void Register(uint16_t aNameOffset, uint16_t aNameLength,
+                       DefineGlobalName aDefine, ConstructorEnabled* aEnabled);
+
+  static void Remove(const char* aName, uint32_t aLength);
+
+  // Returns false if something failed. aFound is set to true if the name is in
+  // the hash, whether it's enabled or not.
+  static bool DefineIfEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
+                              JS::Handle<jsid> aId,
+                              JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+                              bool* aFound);
+
+  static bool MayResolve(jsid aId);
+
+  static void GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
+                       nsTArray<nsString>& aNames);
+
+private:
+  friend struct WebIDLNameTableEntry;
+
+  // The total number of names that we will add to the hash.
+  // The value of sCount is generated by Codegen.py in RegisterBindings.cpp.
+  static const uint32_t sCount;
+
+  // The names that will be registered in the hash, concatenated as one big
+  // string with \0 as a separator between names.
+  // The value of sNames is generated by Codegen.py in RegisterBindings.cpp.
+  static const char sNames[];
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebIDLGlobalNameHash_h__
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -34,16 +34,17 @@ EXPORTS.mozilla.dom += [
     'Nullable.h',
     'PrimitiveConversions.h',
     'RootedDictionary.h',
     'SimpleGlobalObject.h',
     'StructuredClone.h',
     'ToJSValue.h',
     'TypedArray.h',
     'UnionMember.h',
+    'WebIDLGlobalNameHash.h',
 ]
 
 # Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
 # since we generate exported bindings directly to $(DIST)/include, we need
 # to add that path to the search list.
 #
 # Ideally, binding generation uses the prefixed header file names.
 # Bug 932082 tracks.
@@ -84,16 +85,17 @@ UNIFIED_SOURCES += [
     'CallbackInterface.cpp',
     'CallbackObject.cpp',
     'Date.cpp',
     'DOMJSProxyHandler.cpp',
     'Exceptions.cpp',
     'IterableIterator.cpp',
     'SimpleGlobalObject.cpp',
     'ToJSValue.cpp',
+    'WebIDLGlobalNameHash.cpp',
 ]
 
 SOURCES += [
     'StructuredClone.cpp',
 ]
 
 # Tests for maplike and setlike require bindings to be built, which means they
 # must be included in libxul. This breaks the "no test classes are exported"
--- a/dom/cache/CacheTypes.ipdlh
+++ b/dom/cache/CacheTypes.ipdlh
@@ -74,17 +74,17 @@ union CacheRequestOrVoid
 {
   void_t;
   CacheRequest;
 };
 
 struct CacheResponse
 {
   ResponseType type;
-  nsCString url;
+  nsCString[] urlList;
   uint32_t status;
   nsCString statusText;
   HeadersEntry[] headers;
   HeadersGuardEnum headersGuard;
   CacheReadStreamOrVoid body;
   IPCChannelInfo channelInfo;
   OptionalPrincipalInfo principalInfo;
 };
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -32,17 +32,17 @@ namespace dom {
 namespace cache {
 namespace db {
 
 const int32_t kFirstShippedSchemaVersion = 15;
 
 namespace {
 
 // Update this whenever the DB schema is changed.
-const int32_t kLatestSchemaVersion = 20;
+const int32_t kLatestSchemaVersion = 21;
 
 // ---------
 // The following constants define the SQL schema.  These are defined in the
 // same order the SQL should be executed in CreateOrMigrateSchema().  They are
 // broken out as constants for convenient use in validation and migration.
 // ---------
 
 // The caches table is the single source of truth about what Cache
@@ -90,17 +90,16 @@ const char* const kTableEntries =
     "request_referrer TEXT NOT NULL, "
     "request_headers_guard INTEGER NOT NULL, "
     "request_mode INTEGER NOT NULL, "
     "request_credentials INTEGER NOT NULL, "
     "request_contentpolicytype INTEGER NOT NULL, "
     "request_cache INTEGER NOT NULL, "
     "request_body_id TEXT NULL, "
     "response_type INTEGER NOT NULL, "
-    "response_url TEXT NOT NULL, "
     "response_status INTEGER NOT NULL, "
     "response_status_text TEXT NOT NULL, "
     "response_headers_guard INTEGER NOT NULL, "
     "response_body_id TEXT NULL, "
     "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
     "response_principal_info TEXT NOT NULL, "
     "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
 
@@ -138,16 +137,22 @@ const char* const kTableResponseHeaders 
   ")";
 
 // We need an index on response_headers, but not on request_headers,
 // because we quickly need to determine if a VARY header is present.
 const char* const kIndexResponseHeadersName =
   "CREATE INDEX response_headers_name_index "
             "ON response_headers (name)";
 
+const char* const kTableResponseUrlList =
+  "CREATE TABLE response_url_list ("
+    "url TEXT NOT NULL, "
+    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+  ")";
+
 // NOTE: key allows NULL below since that is how "" is represented
 //       in a BLOB column.  We use BLOB to avoid encoding issues
 //       with storing DOMStrings.
 const char* const kTableStorage =
   "CREATE TABLE storage ("
     "namespace INTEGER NOT NULL, "
     "key BLOB NULL, "
     "cache_id INTEGER NOT NULL REFERENCES caches(id), "
@@ -453,16 +458,19 @@ CreateOrMigrateSchema(mozIStorageConnect
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseHeaders));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
+    rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseUrlList));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->GetSchemaVersion(&schemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -1648,17 +1656,16 @@ InsertEntry(mozIStorageConnection* aConn
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
       "request_contentpolicytype, "
       "request_cache, "
       "request_redirect, "
       "request_body_id, "
       "response_type, "
-      "response_url, "
       "response_status, "
       "response_status_text, "
       "response_headers_guard, "
       "response_body_id, "
       "response_security_info_id, "
       "response_principal_info, "
       "cache_id "
     ") VALUES ("
@@ -1672,17 +1679,16 @@ InsertEntry(mozIStorageConnection* aConn
       ":request_headers_guard, "
       ":request_mode, "
       ":request_credentials, "
       ":request_contentpolicytype, "
       ":request_cache, "
       ":request_redirect, "
       ":request_body_id, "
       ":response_type, "
-      ":response_url, "
       ":response_status, "
       ":response_status_text, "
       ":response_headers_guard, "
       ":response_body_id, "
       ":response_security_info_id, "
       ":response_principal_info, "
       ":cache_id "
     ");"
@@ -1750,20 +1756,16 @@ InsertEntry(mozIStorageConnection* aConn
 
   rv = BindId(state, NS_LITERAL_CSTRING("request_body_id"), aRequestBodyId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_type"),
                               static_cast<int32_t>(aResponse.type()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_url"),
-                                   aResponse.url());
-  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_status"),
                               aResponse.status());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_status_text"),
                                    aResponse.statusText());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -1868,32 +1870,52 @@ InsertEntry(mozIStorageConnection* aConn
 
     rv = state->BindInt32ByName(NS_LITERAL_CSTRING("entry_id"), entryId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = state->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT INTO response_url_list ("
+      "url, "
+      "entry_id "
+    ") VALUES (:url, :entry_id)"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  const nsTArray<nsCString>& responseUrlList = aResponse.urlList();
+  for (uint32_t i = 0; i < responseUrlList.Length(); ++i) {
+    rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("url"),
+                                     responseUrlList[i]);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->BindInt64ByName(NS_LITERAL_CSTRING("entry_id"), entryId);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    rv = state->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
   return rv;
 }
 
 nsresult
 ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId,
              SavedResponse* aSavedResponseOut)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
   MOZ_ASSERT(aSavedResponseOut);
 
   nsCOMPtr<mozIStorageStatement> state;
   nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "entries.response_type, "
-      "entries.response_url, "
       "entries.response_status, "
       "entries.response_status_text, "
       "entries.response_headers_guard, "
       "entries.response_body_id, "
       "entries.response_principal_info, "
       "security_info.data "
     "FROM entries "
     "LEFT OUTER JOIN security_info "
@@ -1909,61 +1931,58 @@ ReadResponse(mozIStorageConnection* aCon
   rv = state->ExecuteStep(&hasMoreData);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   int32_t type;
   rv = state->GetInt32(0, &type);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedResponseOut->mValue.type() = static_cast<ResponseType>(type);
 
-  rv = state->GetUTF8String(1, aSavedResponseOut->mValue.url());
-  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
   int32_t status;
-  rv = state->GetInt32(2, &status);
+  rv = state->GetInt32(1, &status);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedResponseOut->mValue.status() = status;
 
-  rv = state->GetUTF8String(3, aSavedResponseOut->mValue.statusText());
+  rv = state->GetUTF8String(2, aSavedResponseOut->mValue.statusText());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   int32_t guard;
-  rv = state->GetInt32(4, &guard);
+  rv = state->GetInt32(3, &guard);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedResponseOut->mValue.headersGuard() =
     static_cast<HeadersGuardEnum>(guard);
 
   bool nullBody = false;
-  rv = state->GetIsNull(5, &nullBody);
+  rv = state->GetIsNull(4, &nullBody);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedResponseOut->mHasBodyId = !nullBody;
 
   if (aSavedResponseOut->mHasBodyId) {
-    rv = ExtractId(state, 5, &aSavedResponseOut->mBodyId);
+    rv = ExtractId(state, 4, &aSavedResponseOut->mBodyId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   nsAutoCString serializedInfo;
-  rv = state->GetUTF8String(6, serializedInfo);
+  rv = state->GetUTF8String(5, serializedInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   aSavedResponseOut->mValue.principalInfo() = void_t();
   if (!serializedInfo.IsEmpty()) {
     nsAutoCString originNoSuffix;
     PrincipalOriginAttributes attrs;
     if (!attrs.PopulateFromOrigin(serializedInfo, originNoSuffix)) {
       NS_WARNING("Something went wrong parsing a serialized principal!");
       return NS_ERROR_FAILURE;
     }
 
     aSavedResponseOut->mValue.principalInfo() =
       mozilla::ipc::ContentPrincipalInfo(attrs, originNoSuffix);
   }
 
-  rv = state->GetBlobAsUTF8String(7, aSavedResponseOut->mValue.channelInfo().securityInfo());
+  rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value "
     "FROM response_headers "
     "WHERE entry_id=:entry_id;"
@@ -1980,16 +1999,36 @@ ReadResponse(mozIStorageConnection* aCon
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = state->GetUTF8String(1, header.value());
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     aSavedResponseOut->mValue.headers().AppendElement(header);
   }
 
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT "
+      "url "
+    "FROM response_url_list "
+    "WHERE entry_id=:entry_id;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32ByName(NS_LITERAL_CSTRING("entry_id"), aEntryId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
+    nsCString url;
+
+    rv = state->GetUTF8String(0, url);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    aSavedResponseOut->mValue.urlList().AppendElement(url);
+  }
+
   return rv;
 }
 
 nsresult
 ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
             SavedRequest* aSavedRequestOut)
 {
   MOZ_ASSERT(!NS_IsMainThread());
@@ -2352,16 +2391,17 @@ Validate(mozIStorageConnection* aConn)
     Expect("sqlite_sequence", "table"), // auto-gen by sqlite
     Expect("security_info", "table", kTableSecurityInfo),
     Expect("security_info_hash_index", "index", kIndexSecurityInfoHash),
     Expect("entries", "table", kTableEntries),
     Expect("entries_request_match_index", "index", kIndexEntriesRequest),
     Expect("request_headers", "table", kTableRequestHeaders),
     Expect("response_headers", "table", kTableResponseHeaders),
     Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
+    Expect("response_url_list", "table", kTableResponseUrlList),
     Expect("storage", "table", kTableStorage),
     Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite
   };
   const uint32_t expectLength = sizeof(expect) / sizeof(Expect);
 
   // Read the schema from the sqlite_master table and compare.
   nsCOMPtr<mozIStorageStatement> state;
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
@@ -2431,24 +2471,26 @@ struct Migration
 
 // Declare migration functions here.  Each function should upgrade
 // the version by a single increment.  Don't skip versions.
 nsresult MigrateFrom15To16(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom16To17(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema);
 nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema);
+nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema);
 
 // Configure migration functions to run for the given starting version.
 Migration sMigrationList[] = {
   Migration(15, MigrateFrom15To16),
   Migration(16, MigrateFrom16To17),
   Migration(17, MigrateFrom17To18),
   Migration(18, MigrateFrom18To19),
   Migration(19, MigrateFrom19To20),
+  Migration(20, MigrateFrom20To21),
 };
 
 uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
 
 nsresult
 RewriteEntriesSchema(mozIStorageConnection* aConn)
 {
   nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
@@ -2525,19 +2567,16 @@ Migrate(mozIStorageConnection* aConn)
   return rv;
 }
 
 nsresult MigrateFrom15To16(mozIStorageConnection* aConn, bool& aRewriteSchema)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
-  mozStorageTransaction trans(aConn, true,
-                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
   // Add the request_redirect column with a default value of "follow".  Note,
   // we only use a default value here because its required by ALTER TABLE and
   // we need to apply the default "follow" to existing records in the table.
   // We don't actually want to keep the default in the schema for future
   // INSERTs.
   nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "ALTER TABLE entries "
     "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0"
@@ -2685,19 +2724,16 @@ MigrateFrom16To17(mozIStorageConnection*
 }
 
 nsresult
 MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
-  mozStorageTransaction trans(aConn, true,
-                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
   // This migration is needed in order to remove "only-if-cached" RequestCache
   // values from the database.  This enum value was removed from the spec in
   // https://github.com/whatwg/fetch/issues/39 but we unfortunately happily
   // accepted this value in the Request constructor.
   //
   // There is no good value to upgrade this to, so we just stick to "default".
 
   static_assert(int(RequestCache::Default) == 0,
@@ -2715,19 +2751,16 @@ MigrateFrom17To18(mozIStorageConnection*
 }
 
 nsresult
 MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
-  mozStorageTransaction trans(aConn, true,
-                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
   // This migration is needed in order to update the RequestMode values for
   // Request objects corresponding to a navigation content policy type to
   // "navigate".
 
   static_assert(int(nsIContentPolicy::TYPE_DOCUMENT) == 6 &&
                 int(nsIContentPolicy::TYPE_SUBDOCUMENT) == 7 &&
                 int(nsIContentPolicy::TYPE_INTERNAL_FRAME) == 28 &&
                 int(nsIContentPolicy::TYPE_INTERNAL_IFRAME) == 29 &&
@@ -2746,19 +2779,16 @@ MigrateFrom18To19(mozIStorageConnection*
   return rv;
 }
 
 nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
-  mozStorageTransaction trans(aConn, true,
-                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
   // Add the request_referrer_policy column with a default value of
   // "no-referrer-when-downgrade".  Note, we only use a default value here
   // because its required by ALTER TABLE and we need to apply the default
   // "no-referrer-when-downgrade" to existing records in the table. We don't
   // actually want to keep the default in the schema for future INSERTs.
   nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "ALTER TABLE entries "
     "ADD COLUMN request_referrer_policy INTEGER NOT NULL DEFAULT 2"
@@ -2768,15 +2798,168 @@ nsresult MigrateFrom19To20(mozIStorageCo
   rv = aConn->SetSchemaVersion(20);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   aRewriteSchema = true;
 
   return rv;
 }
 
+nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  // This migration creates response_url_list table to store response_url and
+  // removes the response_url column from the entries table.
+  // sqlite doesn't support removing a column from a table using ALTER TABLE,
+  // so we need to create a new table without those columns, fill it up with the
+  // existing data, and then drop the original table and rename the new one to
+  // the old one.
+
+  // Create a new_entries table with the new fields as of version 21.
+  nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE new_entries ("
+      "id INTEGER NOT NULL PRIMARY KEY, "
+      "request_method TEXT NOT NULL, "
+      "request_url_no_query TEXT NOT NULL, "
+      "request_url_no_query_hash BLOB NOT NULL, "
+      "request_url_query TEXT NOT NULL, "
+      "request_url_query_hash BLOB NOT NULL, "
+      "request_referrer TEXT NOT NULL, "
+      "request_headers_guard INTEGER NOT NULL, "
+      "request_mode INTEGER NOT NULL, "
+      "request_credentials INTEGER NOT NULL, "
+      "request_contentpolicytype INTEGER NOT NULL, "
+      "request_cache INTEGER NOT NULL, "
+      "request_body_id TEXT NULL, "
+      "response_type INTEGER NOT NULL, "
+      "response_status INTEGER NOT NULL, "
+      "response_status_text TEXT NOT NULL, "
+      "response_headers_guard INTEGER NOT NULL, "
+      "response_body_id TEXT NULL, "
+      "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
+      "response_principal_info TEXT NOT NULL, "
+      "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
+      "request_redirect INTEGER NOT NULL, "
+      "request_referrer_policy INTEGER NOT NULL"
+    ")"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Create a response_url_list table with the new fields as of version 21.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE response_url_list ("
+      "url TEXT NOT NULL, "
+      "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
+    ")"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Copy all of the data to the newly created entries table.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO new_entries ("
+      "id, "
+      "request_method, "
+      "request_url_no_query, "
+      "request_url_no_query_hash, "
+      "request_url_query, "
+      "request_url_query_hash, "
+      "request_referrer, "
+      "request_headers_guard, "
+      "request_mode, "
+      "request_credentials, "
+      "request_contentpolicytype, "
+      "request_cache, "
+      "request_redirect, "
+      "request_referrer_policy, "
+      "request_body_id, "
+      "response_type, "
+      "response_status, "
+      "response_status_text, "
+      "response_headers_guard, "
+      "response_body_id, "
+      "response_security_info_id, "
+      "response_principal_info, "
+      "cache_id "
+    ") SELECT "
+      "id, "
+      "request_method, "
+      "request_url_no_query, "
+      "request_url_no_query_hash, "
+      "request_url_query, "
+      "request_url_query_hash, "
+      "request_referrer, "
+      "request_headers_guard, "
+      "request_mode, "
+      "request_credentials, "
+      "request_contentpolicytype, "
+      "request_cache, "
+      "request_redirect, "
+      "request_referrer_policy, "
+      "request_body_id, "
+      "response_type, "
+      "response_status, "
+      "response_status_text, "
+      "response_headers_guard, "
+      "response_body_id, "
+      "response_security_info_id, "
+      "response_principal_info, "
+      "cache_id "
+    "FROM entries;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Copy reponse_url to the newly created response_url_list table.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "INSERT INTO response_url_list ("
+      "url, "
+      "entry_id "
+    ") SELECT "
+      "response_url, "
+      "id "
+    "FROM entries;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Remove the old table.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TABLE entries;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Rename new_entries to entries.
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "ALTER TABLE new_entries RENAME to entries;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Now, recreate our indices.
+  rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  // Revalidate the foreign key constraints, and ensure that there are no
+  // violations.
+  nsCOMPtr<mozIStorageStatement> state;
+  rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+    "PRAGMA foreign_key_check;"
+  ), getter_AddRefs(state));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  bool hasMoreData = false;
+  rv = state->ExecuteStep(&hasMoreData);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  if (NS_WARN_IF(hasMoreData)) { return NS_ERROR_FAILURE; }
+
+  rv = aConn->SetSchemaVersion(21);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  aRewriteSchema = true;
+
+  return rv;
+}
 
 } // anonymous namespace
 
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -198,25 +198,25 @@ TypeUtils::ToCacheRequest(CacheRequest& 
 }
 
 void
 TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
                                       InternalResponse& aIn, ErrorResult& aRv)
 {
   aOut.type() = aIn.Type();
 
-  aIn.GetUnfilteredUrl(aOut.url());
+  aIn.GetUnfilteredURLList(aOut.urlList());
+  AutoTArray<nsCString, 4> urlList;
+  aIn.GetURLList(urlList);
 
-  if (aOut.url() != EmptyCString()) {
+  for (uint32_t i = 0; i < aOut.urlList().Length(); i++) {
+    MOZ_ASSERT(!aOut.urlList()[i].IsEmpty());
     // Pass all Response URL schemes through... The spec only requires we take
     // action on invalid schemes for Request objects.
-    ProcessURL(aOut.url(), nullptr, nullptr, nullptr, aRv);
-    if (aRv.Failed()) {
-      return;
-    }
+    ProcessURL(aOut.urlList()[i], nullptr, nullptr, nullptr, aRv);
   }
 
   aOut.status() = aIn.GetUnfilteredStatus();
   aOut.statusText() = aIn.GetUnfilteredStatusText();
   RefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
   MOZ_ASSERT(headers);
   if (HasVaryStar(headers)) {
     aRv.ThrowTypeError<MSG_RESPONSE_HAS_VARY_STAR>();
@@ -280,17 +280,17 @@ TypeUtils::ToResponse(const CacheRespons
   if (aIn.type() == ResponseType::Error) {
     RefPtr<InternalResponse> error = InternalResponse::NetworkError();
     RefPtr<Response> r = new Response(GetGlobalObject(), error);
     return r.forget();
   }
 
   RefPtr<InternalResponse> ir = new InternalResponse(aIn.status(),
                                                        aIn.statusText());
-  ir->SetUrl(aIn.url());
+  ir->SetURLList(aIn.urlList());
 
   RefPtr<InternalHeaders> internalHeaders =
     ToInternalHeaders(aIn.headers(), aIn.headersGuard());
   ErrorResult result;
   ir->Headers()->SetGuard(aIn.headersGuard(), result);
   MOZ_ASSERT(!result.Failed());
   ir->Headers()->Fill(*internalHeaders, result);
   MOZ_ASSERT(!result.Failed());
@@ -327,24 +327,23 @@ TypeUtils::ToResponse(const CacheRespons
 
   RefPtr<Response> ref = new Response(GetGlobalObject(), ir);
   return ref.forget();
 }
 
 already_AddRefed<InternalRequest>
 TypeUtils::ToInternalRequest(const CacheRequest& aIn)
 {
-  RefPtr<InternalRequest> internalRequest = new InternalRequest();
+  nsAutoCString url(aIn.urlWithoutQuery());
+  url.Append(aIn.urlQuery());
+
+  RefPtr<InternalRequest> internalRequest = new InternalRequest(url);
 
   internalRequest->SetMethod(aIn.method());
 
-  nsAutoCString url(aIn.urlWithoutQuery());
-  url.Append(aIn.urlQuery());
-  internalRequest->SetURL(url);
-
   internalRequest->SetReferrer(aIn.referrer());
   internalRequest->SetReferrerPolicy(aIn.referrerPolicy());
   internalRequest->SetMode(aIn.mode());
   internalRequest->SetCredentialsMode(aIn.credentials());
   internalRequest->SetContentPolicyType(aIn.contentPolicyType());
   internalRequest->SetCacheMode(aIn.requestCache());
   internalRequest->SetRedirectMode(aIn.requestRedirect());
 
--- a/dom/cache/test/xpcshell/test_migration.js
+++ b/dom/cache/test/xpcshell/test_migration.js
@@ -26,16 +26,22 @@ function run_test() {
     });
     return Promise.all(requestList.map(function(request) {
       return cache.match(request);
     }));
   }).then(function(responseList) {
     ok(responseList.length > 0, 'should have at least one response in cache');
     responseList.forEach(function(response) {
       ok(response, 'each response in list should be non-null');
+      // reponse.url is a empty string in current test file. It should test for
+      // not being a empty string once thet test file is updated.
+      ok(typeof response.url === 'string', 'each response.url in list should be a string');
+      // reponse.redirected may be changed once test file is updated. It should
+      // be false since current reponse.url is a empty string.
+      ok(response.redirected === false, 'each response.redirected in list should be false');
       do_check_eq(response.headers.get('Content-Type'), 'text/plain;charset=UTF-8',
                   'the response should have the correct header');
     });
   }).then(function() {
     do_test_finished();
   }).catch(function(e) {
     ok(false, 'caught exception ' + e);
     do_test_finished();
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DragEvent.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/UIEvent.h"
 
 #include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "WheelHandlingHelper.h"
 
 #include "nsCOMPtr.h"
@@ -2005,86 +2006,90 @@ EventStateManager::DoDefaultDragStart(ns
   return true;
 }
 
 nsresult
 EventStateManager::GetContentViewer(nsIContentViewer** aCv)
 {
   *aCv = nullptr;
 
-  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
-  if(!fm) return NS_ERROR_FAILURE;
-
-  nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
-  fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
-  if (!focusedWindow) return NS_ERROR_FAILURE;
-
-  auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow);
-
-  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = ourWindow->GetPrivateRoot();
-  if(!rootWindow) return NS_ERROR_FAILURE;
+  nsCOMPtr<nsPIDOMWindowOuter> rootWindow;
+  rootWindow = mDocument->GetWindow()->GetPrivateRoot();
+  if (!rootWindow) return NS_ERROR_FAILURE;
+
+  TabChild* tabChild = TabChild::GetFrom(rootWindow);
+  if (!tabChild) {
+    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+    if (!fm) return NS_ERROR_FAILURE;
+
+    nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+    fm->GetActiveWindow(getter_AddRefs(activeWindow));
+    if (rootWindow != activeWindow) return NS_OK;
+  } else {
+    if (!tabChild->ParentIsActive()) return NS_OK;
+  }
 
   nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent();
-  if(!contentWindow) return NS_ERROR_FAILURE;
+  if (!contentWindow) return NS_ERROR_FAILURE;
 
   nsIDocument *doc = contentWindow->GetDoc();
-  if(!doc) return NS_ERROR_FAILURE;
-
-  nsIPresShell *presShell = doc->GetShell();
-  if(!presShell) return NS_ERROR_FAILURE;
-  nsPresContext *presContext = presShell->GetPresContext();
-  if(!presContext) return NS_ERROR_FAILURE;
-
-  nsCOMPtr<nsIDocShell> docshell(presContext->GetDocShell());
-  if(!docshell) return NS_ERROR_FAILURE;
-
+  if (!doc) return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsISupports> container = doc->GetContainer();
+  if (!container) return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(container);
   docshell->GetContentViewer(aCv);
-  if(!*aCv) return NS_ERROR_FAILURE;
+  if (!*aCv) return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
 nsresult
 EventStateManager::ChangeTextSize(int32_t change)
 {
   nsCOMPtr<nsIContentViewer> cv;
   nsresult rv = GetContentViewer(getter_AddRefs(cv));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  float textzoom;
-  float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
-  float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
-  cv->GetTextZoom(&textzoom);
-  textzoom += ((float)change) / 10;
-  if (textzoom < zoomMin)
-    textzoom = zoomMin;
-  else if (textzoom > zoomMax)
-    textzoom = zoomMax;
-  cv->SetTextZoom(textzoom);
+  if (cv) {
+    float textzoom;
+    float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
+    float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
+    cv->GetTextZoom(&textzoom);
+    textzoom += ((float)change) / 10;
+    if (textzoom < zoomMin)
+      textzoom = zoomMin;
+    else if (textzoom > zoomMax)
+      textzoom = zoomMax;
+    cv->SetTextZoom(textzoom);
+  }
 
   return NS_OK;
 }
 
 nsresult
 EventStateManager::ChangeFullZoom(int32_t change)
 {
   nsCOMPtr<nsIContentViewer> cv;
   nsresult rv = GetContentViewer(getter_AddRefs(cv));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  float fullzoom;
-  float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
-  float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
-  cv->GetFullZoom(&fullzoom);
-  fullzoom += ((float)change) / 10;
-  if (fullzoom < zoomMin)
-    fullzoom = zoomMin;
-  else if (fullzoom > zoomMax)
-    fullzoom = zoomMax;
-  cv->SetFullZoom(fullzoom);
+  if (cv) {
+    float fullzoom;
+    float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
+    float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
+    cv->GetFullZoom(&fullzoom);
+    fullzoom += ((float)change) / 10;
+    if (fullzoom < zoomMin)
+      fullzoom = zoomMin;
+    else if (fullzoom > zoomMax)
+      fullzoom = zoomMax;
+    cv->SetFullZoom(fullzoom);
+  }
 
   return NS_OK;
 }
 
 void
 EventStateManager::DoScrollHistory(int32_t direction)
 {
   nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
@@ -2108,22 +2113,22 @@ EventStateManager::DoScrollZoom(nsIFrame
   nsIContent *content = aTargetFrame->GetContent();
   if (content &&
       !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
       !nsContentUtils::IsInChromeDocshell(content->OwnerDoc()))
     {
       // positive adjustment to decrease zoom, negative to increase
       int32_t change = (adjustment > 0) ? -1 : 1;
 
+      EnsureDocument(mPresContext);
       if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
         ChangeFullZoom(change);
       } else {
         ChangeTextSize(change);
       }
-      EnsureDocument(mPresContext);
       nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
                                           NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"),
                                           true, true);
     }
 }
 
 static nsIFrame*
 GetParentFrameToScroll(nsIFrame* aFrame)
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -90,17 +90,17 @@ nsresult
 FetchDriver::ContinueFetch()
 {
   workers::AssertIsOnMainThread();
 
   nsresult rv = HttpFetch();
   if (NS_FAILED(rv)) {
     FailWithNetworkError();
   }
- 
+
   return rv;
 }
 
 // This function implements the "HTTP Fetch" algorithm from the Fetch spec.
 // Functionality is often split between here, the CORS listener proxy and the
 // Necko HTTP implementation.
 nsresult
 FetchDriver::HttpFetch()
@@ -368,28 +368,25 @@ FetchDriver::HttpFetch()
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
   return NS_OK;
 }
 
 already_AddRefed<InternalResponse>
 FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse,
-                                         nsIURI* aFinalURI,
                                          bool aFoundOpaqueRedirect)
 {
   MOZ_ASSERT(aResponse);
-  nsAutoCString reqURL;
-  if (aFinalURI) {
-    aFinalURI->GetSpec(reqURL);
-  } else {
-    mRequest->GetURL(reqURL);
-  }
-  DebugOnly<nsresult> rv = aResponse->StripFragmentAndSetUrl(reqURL);
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  AutoTArray<nsCString, 4> reqURLList;
+  mRequest->GetURLList(reqURLList);
+
+  MOZ_ASSERT(!reqURLList.IsEmpty());
+  aResponse->SetURLList(reqURLList);
 
   RefPtr<InternalResponse> filteredResponse;
   if (aFoundOpaqueRedirect) {
     filteredResponse = aResponse->OpaqueRedirectResponse();
   } else {
     switch (mRequest->GetResponseTainting()) {
       case LoadTainting::Basic:
         filteredResponse = aResponse->BasicResponse();
@@ -484,30 +481,16 @@ FetchDriver::OnStartRequest(nsIRequest* 
 
   RefPtr<InternalResponse> response;
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
 
   // On a successful redirect we perform the following substeps of HTTP Fetch,
   // step 5, "redirect status", step 11.
 
-  // Step 11.5 "Append locationURL to request's url list." so that when we set the
-  // Response's URL from the Request's URL in Main Fetch, step 15, we get the
-  // final value. Note, we still use a single URL value instead of a list.
-  // Because of that we only need to do this after the request finishes.
-  nsCOMPtr<nsIURI> newURI;
-  rv = NS_GetFinalChannelURI(channel, getter_AddRefs(newURI));
-  if (NS_FAILED(rv)) {
-    FailWithNetworkError();
-    return rv;
-  }
-  nsAutoCString newUrl;
-  newURI->GetSpec(newUrl);
-  mRequest->SetURL(newUrl);
-
   bool foundOpaqueRedirect = false;
 
   if (httpChannel) {
     uint32_t responseStatus;
     httpChannel->GetResponseStatus(&responseStatus);
 
     if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) {
       if (mRequest->GetRedirectMode() == RequestRedirect::Error) {
@@ -602,18 +585,17 @@ FetchDriver::OnStartRequest(nsIRequest* 
   // FetchEvent.respondWith() just passes the already-tainted Response back to
   // the outer fetch().  In gecko, however, we serialize the Response through
   // the channel and must regenerate the tainting from the channel in the
   // interception case.
   mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting());
 
   // Resolves fetch() promise which may trigger code running in a worker.  Make
   // sure the Response is fully initialized before calling this.
-  mResponse = BeginAndGetFilteredResponse(response, channelURI,
-                                          foundOpaqueRedirect);
+  mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect);
 
   nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
   }
 
@@ -682,16 +664,34 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
                                     uint32_t aFlags,
                                     nsIAsyncVerifyRedirectCallback *aCallback)
 {
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
   if (httpChannel) {
     SetRequestHeaders(httpChannel);
   }
 
+  // "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list."
+  nsCOMPtr<nsIURI> uri;
+  MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri)));
+
+  nsCOMPtr<nsIURI> uriClone;
+  nsresult rv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
+  if(NS_WARN_IF(NS_FAILED(rv))){
+    return rv;
+  }
+
+  nsCString spec;
+  rv = uriClone->GetSpec(spec);
+  if(NS_WARN_IF(NS_FAILED(rv))){
+    return rv;
+  }
+
+  mRequest->AddURL(spec);
+
   aCallback->OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FetchDriver::CheckListenerChain()
 {
   return NS_OK;
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -91,19 +91,18 @@ private:
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
 
   nsresult ContinueFetch();
   nsresult HttpFetch();
   // Returns the filtered response sent to the observer.
-  // Callers who don't have access to a channel can pass null for aFinalURI.
   already_AddRefed<InternalResponse>
-  BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI,
+  BeginAndGetFilteredResponse(InternalResponse* aResponse,
                               bool aFoundOpaqueRedirect);
   // Utility since not all cases need to do any post processing of the filtered
   // response.
   void FailWithNetworkError();
 
   void SetRequestHeaders(nsIHttpChannel* aChannel) const;
 };
 
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -18,18 +18,19 @@
 
 namespace mozilla {
 namespace dom {
 
 // The global is used to extract the principal.
 already_AddRefed<InternalRequest>
 InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const
 {
-  RefPtr<InternalRequest> copy = new InternalRequest();
-  copy->mURL.Assign(mURL);
+  MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty when copied from constructor.");
+
+  RefPtr<InternalRequest> copy = new InternalRequest(mURLList.LastElement());
   copy->SetMethod(mMethod);
   copy->mHeaders = new InternalHeaders(*mHeaders);
   copy->SetUnsafeRequest();
 
   copy->mBodyStream = mBodyStream;
   copy->mForceOriginHeader = true;
   // The "client" is not stored in our implementation. Fetch API users should
   // use the appropriate window/document/principal and other Gecko security
@@ -72,17 +73,17 @@ InternalRequest::Clone()
     mBodyStream.swap(replacementBody);
   }
 
   return clone.forget();
 }
 
 InternalRequest::InternalRequest(const InternalRequest& aOther)
   : mMethod(aOther.mMethod)
-  , mURL(aOther.mURL)
+  , mURLList(aOther.mURLList)
   , mHeaders(new InternalHeaders(*aOther.mHeaders))
   , mContentPolicyType(aOther.mContentPolicyType)
   , mReferrer(aOther.mReferrer)
   , mReferrerPolicy(aOther.mReferrerPolicy)
   , mMode(aOther.mMode)
   , mCredentialsMode(aOther.mCredentialsMode)
   , mResponseTainting(aOther.mResponseTainting)
   , mCacheMode(aOther.mCacheMode)
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -83,17 +83,17 @@ class Request;
 
 class InternalRequest final
 {
   friend class Request;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
 
-  InternalRequest()
+  explicit InternalRequest(const nsACString& aURL)
     : mMethod("GET")
     , mHeaders(new InternalHeaders(HeadersGuardEnum::None))
     , mContentPolicyType(nsIContentPolicy::TYPE_FETCH)
     , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
     , mReferrerPolicy(ReferrerPolicy::_empty)
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
     , mResponseTainting(LoadTainting::Basic)
@@ -107,30 +107,31 @@ public:
       // how certain contexts will override it to set it to true. Fetch
       // specification does not handle this yet.
     , mSameOriginDataURL(true)
     , mSkipServiceWorker(false)
     , mSynchronous(false)
     , mUnsafeRequest(false)
     , mUseURLCredentials(false)
   {
+    MOZ_ASSERT(!aURL.IsEmpty());
+    AddURL(aURL);
   }
 
   InternalRequest(const nsACString& aURL,
                   const nsACString& aMethod,
                   already_AddRefed<InternalHeaders> aHeaders,
                   RequestCache aCacheMode,
                   RequestMode aMode,
                   RequestRedirect aRequestRedirect,
                   RequestCredentials aRequestCredentials,
                   const nsAString& aReferrer,
                   ReferrerPolicy aReferrerPolicy,
                   nsContentPolicyType aContentPolicyType)
     : mMethod(aMethod)
-    , mURL(aURL)
     , mHeaders(aHeaders)
     , mContentPolicyType(aContentPolicyType)
     , mReferrer(aReferrer)
     , mReferrerPolicy(aReferrerPolicy)
     , mMode(aMode)
     , mCredentialsMode(aRequestCredentials)
     , mResponseTainting(LoadTainting::Basic)
     , mCacheMode(aCacheMode)
@@ -140,22 +141,18 @@ public:
     , mPreserveContentCodings(false)
       // FIXME See the above comment in the default constructor.
     , mSameOriginDataURL(true)
     , mSkipServiceWorker(false)
     , mSynchronous(false)
     , mUnsafeRequest(false)
     , mUseURLCredentials(false)
   {
-    // Normally we strip the fragment from the URL in Request::Constructor.
-    // If internal code is directly constructing this object they must
-    // strip the fragment first.  Since these should be well formed URLs we
-    // can use a simple check for a fragment here.  The full parser is
-    // difficult to use off the main thread.
-    MOZ_ASSERT(mURL.Find(NS_LITERAL_CSTRING("#")) == kNotFound);
+    MOZ_ASSERT(!aURL.IsEmpty());
+    AddURL(aURL);
   }
 
   already_AddRefed<InternalRequest> Clone();
 
   void
   GetMethod(nsCString& aMethod) const
   {
     aMethod.Assign(mMethod);
@@ -170,26 +167,44 @@ public:
   bool
   HasSimpleMethod() const
   {
     return mMethod.LowerCaseEqualsASCII("get") ||
            mMethod.LowerCaseEqualsASCII("post") ||
            mMethod.LowerCaseEqualsASCII("head");
   }
 
+  // GetURL should get the request's current url. A request has an associated
+  // current url. It is a pointer to the last fetch URL in request's url list.
   void
-  GetURL(nsCString& aURL) const
+  GetURL(nsACString& aURL) const
   {
-    aURL.Assign(mURL);
+    MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty.");
+
+    aURL.Assign(mURLList.LastElement());
+  }
+
+  // AddURL should append the url into url list.
+  // Normally we strip the fragment from the URL in Request::Constructor.
+  // If internal code is directly constructing this object they must
+  // strip the fragment first.  Since these should be well formed URLs we
+  // can use a simple check for a fragment here.  The full parser is
+  // difficult to use off the main thread.
+  void
+  AddURL(const nsACString& aURL)
+  {
+    MOZ_ASSERT(!aURL.IsEmpty());
+    mURLList.AppendElement(aURL);
+    MOZ_ASSERT(mURLList.LastElement().Find(NS_LITERAL_CSTRING("#")) == kNotFound);
   }
 
   void
-  SetURL(const nsACString& aURL)
+  GetURLList(nsTArray<nsCString>& aURLList)
   {
-    mURL.Assign(aURL);
+    aURLList.Assign(mURLList);
   }
 
   void
   GetReferrer(nsAString& aReferrer) const
   {
     aReferrer.Assign(mReferrer);
   }
 
@@ -454,18 +469,18 @@ private:
 
   static bool
   IsNavigationContentPolicy(nsContentPolicyType aContentPolicyType);
 
   static bool
   IsWorkerContentPolicy(nsContentPolicyType aContentPolicyType);
 
   nsCString mMethod;
-  // mURL always stores the url with the ref stripped
-  nsCString mURL;
+  // mURLList: a list of one or more fetch URLs
+  nsTArray<nsCString> mURLList;
   RefPtr<InternalHeaders> mHeaders;
   nsCOMPtr<nsIInputStream> mBodyStream;
 
   nsContentPolicyType mContentPolicyType;
 
   // Empty string: no-referrer
   // "about:client": client (default)
   // URL: an URL
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -82,47 +82,16 @@ InternalResponse::CORSResponse()
 }
 
 void
 InternalResponse::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo)
 {
   mPrincipalInfo = Move(aPrincipalInfo);
 }
 
-nsresult
-InternalResponse::StripFragmentAndSetUrl(const nsACString& aUrl)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIURI> iuri;
-  nsresult rv;
-
-  rv = NS_NewURI(getter_AddRefs(iuri), aUrl);
-  if(NS_WARN_IF(NS_FAILED(rv))){
-    return rv;
-  }
-
-  nsCOMPtr<nsIURI> iuriClone;
-  // We use CloneIgnoringRef to strip away the fragment even if the original URI
-  // is immutable.
-  rv = iuri->CloneIgnoringRef(getter_AddRefs(iuriClone));
-  if(NS_WARN_IF(NS_FAILED(rv))){
-    return rv;
-  }
-
-  nsCString spec;
-  rv = iuriClone->GetSpec(spec);
-  if(NS_WARN_IF(NS_FAILED(rv))){
-    return rv;
-  }
-
-  SetUrl(spec);
-  return NS_OK;
-}
-
 LoadTainting
 InternalResponse::GetTainting() const
 {
   switch (mType) {
     case ResponseType::Cors:
       return LoadTainting::CORS;
     case ResponseType::Opaque:
       return LoadTainting::Opaque;
@@ -155,29 +124,30 @@ InternalResponse::OpaqueResponse()
   response->mWrappedResponse = this;
   return response.forget();
 }
 
 already_AddRefed<InternalResponse>
 InternalResponse::OpaqueRedirectResponse()
 {
   MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response");
+  MOZ_ASSERT(!mURLList.IsEmpty(), "URLList should not be emtpy for internalResponse");
   RefPtr<InternalResponse> response = OpaqueResponse();
   response->mType = ResponseType::Opaqueredirect;
-  response->mURL = mURL;
+  response->mURLList = mURLList;
   return response.forget();
 }
 
 already_AddRefed<InternalResponse>
 InternalResponse::CreateIncompleteCopy()
 {
   RefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
   copy->mType = mType;
   copy->mTerminationReason = mTerminationReason;
-  copy->mURL = mURL;
+  copy->mURLList = mURLList;
   copy->mChannelInfo = mChannelInfo;
   if (mPrincipalInfo) {
     copy->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
   }
   return copy.forget();
 }
 
 } // namespace dom
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -70,38 +70,66 @@ public:
   }
 
   bool
   IsError() const
   {
     return Type() == ResponseType::Error;
   }
 
-  // FIXME(nsm): Return with exclude fragment.
+  // GetUrl should return last fetch URL in response's url list and null if
+  // response's url list is the empty list.
   void
-  GetUrl(nsCString& aURL) const
+  GetURL(nsCString& aURL) const
   {
-    aURL.Assign(mURL);
+    // Empty urlList when response is a synthetic response.
+    if (mURLList.IsEmpty()) {
+      aURL.Truncate();
+      return;
+    }
+
+    aURL.Assign(mURLList.LastElement());
+  }
+
+  void
+  GetURLList(nsTArray<nsCString>& aURLList) const
+  {
+    aURLList.Assign(mURLList);
   }
 
   void
-  GetUnfilteredUrl(nsCString& aURL) const
+  GetUnfilteredURL(nsCString& aURL) const
   {
     if (mWrappedResponse) {
-      return mWrappedResponse->GetUrl(aURL);
+      return mWrappedResponse->GetURL(aURL);
     }
 
-    return GetUrl(aURL);
+    return GetURL(aURL);
   }
 
-  // SetUrl should only be called when the fragment has alredy been stripped
   void
-  SetUrl(const nsACString& aURL)
+  GetUnfilteredURLList(nsTArray<nsCString>& aURLList) const
   {
-    mURL.Assign(aURL);
+    if (mWrappedResponse) {
+      return mWrappedResponse->GetURLList(aURLList);
+    }
+
+    return GetURLList(aURLList);
+  }
+
+  void
+  SetURLList(const nsTArray<nsCString>& aURLList)
+  {
+    mURLList.Assign(aURLList);
+
+#ifdef DEBUG
+    for(uint32_t i = 0; i < mURLList.Length(); ++i) {
+      MOZ_ASSERT(mURLList[i].Find(NS_LITERAL_CSTRING("#")) == kNotFound);
+    }
+#endif
   }
 
   uint16_t
   GetStatus() const
   {
     return mStatus;
   }
 
@@ -206,23 +234,26 @@ public:
   }
 
   const UniquePtr<mozilla::ipc::PrincipalInfo>&
   GetPrincipalInfo() const
   {
     return mPrincipalInfo;
   }
 
+  bool
+  IsRedirected() const
+  {
+    return mURLList.Length() > 1;
+  }
+
   // Takes ownership of the principal info.
   void
   SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo);
 
-  nsresult
-  StripFragmentAndSetUrl(const nsACString& aUrl);
-
   LoadTainting
   GetTainting() const;
 
   already_AddRefed<InternalResponse>
   Unfiltered();
 
 private:
   ~InternalResponse();
@@ -232,17 +263,20 @@ private:
 
   // Returns an instance of InternalResponse which is a copy of this
   // InternalResponse, except headers, body and wrapped response (if any) which
   // are left uninitialized. Used for cloning and filtering.
   already_AddRefed<InternalResponse> CreateIncompleteCopy();
 
   ResponseType mType;
   nsCString mTerminationReason;
-  nsCString mURL;
+  // A response has an associated url list (a list of zero or more fetch URLs).
+  // Unless stated otherwise, it is the empty list. The current url is the last
+  // element in mURLlist
+  nsTArray<nsCString> mURLList;
   const uint16_t mStatus;
   const nsCString mStatusText;
   RefPtr<InternalHeaders> mHeaders;
   nsCOMPtr<nsIInputStream> mBody;
   ChannelInfo mChannelInfo;
   UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
 
   // For filtered responses.
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -280,28 +280,18 @@ Request::Constructor(const GlobalObject&
     }
     if (body) {
       temporaryBody = body;
     }
 
     request = inputReq->GetInternalRequest();
 
   } else {
-    request = new InternalRequest();
-  }
-
-  request = request->GetRequestConstructorCopy(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  RequestMode fallbackMode = RequestMode::EndGuard_;
-  RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
-  RequestCache fallbackCache = RequestCache::EndGuard_;
-  if (aInput.IsUSVString()) {
+    // aInput is USVString.
+    // We need to get url before we create a InternalRequest.
     nsAutoString input;
     input.Assign(aInput.GetAsUSVString());
 
     nsAutoString requestURL;
     if (NS_IsMainThread()) {
       nsIDocument* doc = GetEntryDocument();
       if (doc) {
         GetRequestURLFromDocument(doc, input, requestURL, aRv);
@@ -312,17 +302,28 @@ Request::Constructor(const GlobalObject&
     } else {
       GetRequestURLFromWorker(aGlobal, input, requestURL, aRv);
     }
 
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
-    request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
+    request = new InternalRequest(NS_ConvertUTF16toUTF8(requestURL));
+  }
+
+  request = request->GetRequestConstructorCopy(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  RequestMode fallbackMode = RequestMode::EndGuard_;
+  RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
+  RequestCache fallbackCache = RequestCache::EndGuard_;
+  if (aInput.IsUSVString()) {
     fallbackMode = RequestMode::Cors;
     fallbackCredentials = RequestCredentials::Omit;
     fallbackCache = RequestCache::Default;
   }
 
   RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
   RequestCredentials credentials =
     aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -41,17 +41,19 @@ public:
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
   {
     return RequestBinding::Wrap(aCx, this, aGivenProto);
   }
 
   void
   GetUrl(nsAString& aUrl) const
   {
-    CopyUTF8toUTF16(mRequest->mURL, aUrl);
+    nsAutoCString url;
+    mRequest->GetURL(url);
+    CopyUTF8toUTF16(url, aUrl);
   }
 
   void
   GetMethod(nsCString& aMethod) const
   {
     aMethod = mRequest->mMethod;
   }
 
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -48,20 +48,26 @@ public:
   {
     return mInternalResponse->Type();
   }
 
   void
   GetUrl(nsAString& aUrl) const
   {
     nsCString url;
-    mInternalResponse->GetUrl(url);
+    mInternalResponse->GetURL(url);
     CopyUTF8toUTF16(url, aUrl);
   }
 
+  bool
+  Redirected() const
+  {
+    return mInternalResponse->IsRedirected();
+  }
+
   uint16_t
   Status() const
   {
     return mInternalResponse->GetStatus();
   }
 
   bool
   Ok() const
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -269,25 +269,23 @@ HTMLTrackElement::BindToTree(nsIDocument
   }
 
   return NS_OK;
 }
 
 void
 HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  if (mMediaParent) {
+  if (mMediaParent && aNullParent) {
     // mTrack can be null if HTMLTrackElement::LoadResource has never been
     // called.
     if (mTrack) {
       mMediaParent->RemoveTextTrack(mTrack);
     }
-    if (aNullParent) {
-      mMediaParent = nullptr;
-    }
+    mMediaParent = nullptr;
   }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 uint16_t
 HTMLTrackElement::ReadyState() const
 {
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -1810,60 +1810,22 @@ IDBObjectStore::DeleteInternal(JSContext
 
   mTransaction->StartRequest(request, params);
 
   return request.forget();
 }
 
 already_AddRefed<IDBIndex>
 IDBObjectStore::CreateIndex(const nsAString& aName,
-                            const nsAString& aKeyPath,
-                            const IDBIndexParameters& aOptionalParameters,
-                            ErrorResult& aRv)
-{
-  AssertIsOnOwningThread();
-
-  KeyPath keyPath(0);
-  if (NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
-      !keyPath.IsValid()) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return nullptr;
-  }
-
-  return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
-}
-
-already_AddRefed<IDBIndex>
-IDBObjectStore::CreateIndex(const nsAString& aName,
-                            const Sequence<nsString >& aKeyPath,
+                            const StringOrStringSequence& aKeyPath,
                             const IDBIndexParameters& aOptionalParameters,
                             ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  KeyPath keyPath(0);
-  if (aKeyPath.IsEmpty() ||
-      NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
-      !keyPath.IsValid()) {
-    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
-    return nullptr;
-  }
-
-  return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
-}
-
-already_AddRefed<IDBIndex>
-IDBObjectStore::CreateIndexInternal(
-                                  const nsAString& aName,
-                                  const KeyPath& aKeyPath,
-                                  const IDBIndexParameters& aOptionalParameters,
-                                  ErrorResult& aRv)
-{
-  AssertIsOnOwningThread();
-
   if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
       mDeletedSpec) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
   IDBTransaction* transaction = IDBTransaction::GetCurrent();
   if (!transaction || transaction != mTransaction) {
@@ -1878,17 +1840,34 @@ IDBObjectStore::CreateIndexInternal(
        index < count;
        index++) {
     if (aName == indexes[index].name()) {
       aRv.Throw(NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR);
       return nullptr;
     }
   }
 
-  if (aOptionalParameters.mMultiEntry && aKeyPath.IsArray()) {
+  KeyPath keyPath(0);
+  if (aKeyPath.IsString()) {
+    if (NS_FAILED(KeyPath::Parse(aKeyPath.GetAsString(), &keyPath)) ||
+        !keyPath.IsValid()) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return nullptr;
+    }
+  } else {
+    MOZ_ASSERT(aKeyPath.IsStringSequence());
+    if (aKeyPath.GetAsStringSequence().IsEmpty() ||
+        NS_FAILED(KeyPath::Parse(aKeyPath.GetAsStringSequence(), &keyPath)) ||
+        !keyPath.IsValid()) {
+      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+      return nullptr;
+    }
+  }
+
+  if (aOptionalParameters.mMultiEntry && keyPath.IsArray()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return nullptr;
   }
 
 #ifdef DEBUG
   for (uint32_t count = mIndexes.Length(), index = 0;
        index < count;
        index++) {
@@ -1907,17 +1886,17 @@ IDBObjectStore::CreateIndexInternal(
   bool autoLocale = locale.EqualsASCII("auto");
 #ifdef ENABLE_INTL_API
   if (autoLocale) {
     locale = IndexedDatabaseManager::GetLocale();
   }
 #endif
 
   IndexMetadata* metadata = indexes.AppendElement(
-    IndexMetadata(transaction->NextIndexId(), nsString(aName), aKeyPath,
+    IndexMetadata(transaction->NextIndexId(), nsString(aName), keyPath,
                   locale,
                   aOptionalParameters.mUnique,
                   aOptionalParameters.mMultiEntry,
                   autoLocale));
 
   if (oldMetadataElements &&
       oldMetadataElements != indexes.Elements()) {
     MOZ_ASSERT(indexes.Length() > 1);
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -25,16 +25,17 @@ namespace mozilla {
 class ErrorResult;
 
 namespace dom {
 
 class DOMStringList;
 class IDBCursor;
 class IDBRequest;
 class IDBTransaction;
+class StringOrStringSequence;
 template <typename> class Sequence;
 
 namespace indexedDB {
 class Key;
 class KeyPath;
 class IndexUpdateInfo;
 class ObjectStoreSpec;
 struct StructuredCloneReadInfo;
@@ -204,23 +205,17 @@ public:
   already_AddRefed<IDBRequest>
   Get(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   Clear(JSContext* aCx, ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
   CreateIndex(const nsAString& aName,
-              const nsAString& aKeyPath,
-              const IDBIndexParameters& aOptionalParameters,
-              ErrorResult& aRv);
-
-  already_AddRefed<IDBIndex>
-  CreateIndex(const nsAString& aName,
-              const Sequence<nsString>& aKeyPath,
+              const StringOrStringSequence& aKeyPath,
               const IDBIndexParameters& aOptionalParameters,
               ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
   Index(const nsAString& aName, ErrorResult &aRv);
 
   void
   DeleteIndex(const nsAString& aIndexName, ErrorResult& aRv);
@@ -340,22 +335,16 @@ private:
 
   already_AddRefed<IDBRequest>
   GetAllInternal(bool aKeysOnly,
                  JSContext* aCx,
                  JS::Handle<JS::Value> aKey,
                  const Optional<uint32_t>& aLimit,
                  ErrorResult& aRv);
 
-  already_AddRefed<IDBIndex>
-  CreateIndexInternal(const nsAString& aName,
-                      const KeyPath& aKeyPath,
-                      const IDBIndexParameters& aOptionalParameters,
-                      ErrorResult& aRv);
-
   already_AddRefed<IDBRequest>
   OpenCursorInternal(bool aKeysOnly,
                      JSContext* aCx,
                      JS::Handle<JS::Value> aRange,
                      IDBCursorDirection aDirection,
                      ErrorResult& aRv);
 };
 
--- a/dom/interfaces/push/nsIPushNotifier.idl
+++ b/dom/interfaces/push/nsIPushNotifier.idl
@@ -10,17 +10,17 @@
 #include "mozilla/Maybe.h"
 
 #define PUSHNOTIFIER_CONTRACTID \
   "@mozilla.org/push/Notifier;1"
 
 // These constants are duplicated in `PushComponents.js`.
 #define OBSERVER_TOPIC_PUSH "push-message"
 #define OBSERVER_TOPIC_SUBSCRIPTION_CHANGE "push-subscription-change"
-#define OBSERVER_TOPIC_SUBSCRIPTION_LOST "push-subscription-lost"
+#define OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED "push-subscription-modified"
 %}
 
 interface nsIPrincipal;
 
 [ref] native MaybeData(const mozilla::Maybe<nsTArray<uint8_t>>);
 
 /**
  * Fires XPCOM observer notifications and service worker events for
@@ -50,73 +50,81 @@ interface nsIPushNotifier : nsISupports
   /**
    * Fires a `push-subscription-change` observer notification, and sends a
    * `pushsubscriptionchange` event to the service worker registered for the
    * |scope|.
    */
   void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal);
 
   /**
-   * Fires a `push-subscription-lost` observer notification. Chrome code can
-   * listen for this notification to see when the Push service removes or
-   * expires a subscription for the |scope|.
+   * Fires a `push-subscription-modified` observer notification. Chrome code
+   * can listen for this notification to see when a subscription is added,
+   * updated, removed, or expired for any |scope|.
    *
-   * This is useful for Dev Tools and debugging add-ons that need to be
-   * notified when a subscription changes state. Other chrome callers should
-   * listen for `push-subscription-change` and resubscribe instead.
-   *
-   * |reason| is an `nsIPushErrorReporter` unsubscribe reason. The reason is
-   * wrapped in an `nsISupportsPRUint16` and passed as the subject.
+   * This is useful for Dev Tools and debugging add-ons that passively observe
+   * when subscriptions are created or dropped. Other callers should listen for
+   * `push-subscription-change` and resubscribe instead.
    */
-  void notifySubscriptionLost(in ACString scope, in nsIPrincipal principal,
-                              [optional] in uint16_t reason);
+  void notifySubscriptionModified(in ACString scope, in nsIPrincipal principal);
 
   void notifyError(in ACString scope, in nsIPrincipal principal,
                    in DOMString message, in uint32_t flags);
 
   /**
    * Internal methods used to fire service worker events and observer
    * notifications. These are not exposed to JavaScript.
    */
 
   [noscript, nostdcall]
   void notifyPushWorkers(in ACString scope,
                          in nsIPrincipal principal,
                          in DOMString messageId,
                          in MaybeData data);
 
   [noscript, nostdcall]
-  void notifyPushObservers(in ACString scope, in MaybeData data);
+  void notifyPushObservers(in ACString scope, in nsIPrincipal principal,
+                           in MaybeData data);
 
   [noscript, nostdcall]
   void notifySubscriptionChangeWorkers(in ACString scope,
-                                           in nsIPrincipal principal);
+                                       in nsIPrincipal principal);
 
   [noscript, nostdcall]
-  void notifySubscriptionChangeObservers(in ACString scope);
+  void notifySubscriptionChangeObservers(in ACString scope,
+                                         in nsIPrincipal principal);
 
   [noscript, nostdcall]
-  void notifySubscriptionLostObservers(in ACString scope, in uint16_t reason);
+  void notifySubscriptionModifiedObservers(in ACString scope,
+                                           in nsIPrincipal principal);
 
   [noscript, nostdcall, notxpcom]
   void notifyErrorWorkers(in ACString scope, in DOMString message,
                           in uint32_t flags);
 };
 
 /**
- * A push message sent to a subscription, used as the subject of a
- * `push-message` observer notification.
- *
+ * Provides methods for retrieving push message data in different formats.
  * This interface resembles the `PushMessageData` WebIDL interface.
  */
-[scriptable, builtinclass, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
-interface nsIPushMessage : nsISupports
+[scriptable, builtinclass, uuid(dfc4f151-cead-40df-8eb7-7a7a67c54b16)]
+interface nsIPushData : nsISupports
 {
   /** Extracts the data as a UTF-8 text string. */
   DOMString text();
 
   /** Extracts the data as a JSON value. */
   [implicit_jscontext] jsval json();
 
   /** Extracts the raw binary data. */
   void binary([optional] out uint32_t dataLen,
               [array, retval, size_is(dataLen)] out uint8_t data);
 };
+
+/**
+ * The subject of a `push-message` observer notification. |data| may be |null|
+ * for messages without data.
+ */
+[scriptable, builtinclass, uuid(b9d063ca-0e3f-4fee-be4b-ea9103263433)]
+interface nsIPushMessage : nsISupports
+{
+  readonly attribute nsIPrincipal principal;
+  readonly attribute nsIPushData data;
+};
--- a/dom/interfaces/push/nsIPushService.idl
+++ b/dom/interfaces/push/nsIPushService.idl
@@ -72,33 +72,33 @@ interface nsIPushClearResultCallback : n
  * workers.
  */
 [scriptable, uuid(678ef584-bf25-47aa-ac84-03efc0865b68)]
 interface nsIPushService : nsISupports
 {
   /** Observer topic names, exported for convenience. */
   readonly attribute DOMString pushTopic;
   readonly attribute DOMString subscriptionChangeTopic;
-  readonly attribute DOMString subscriptionLostTopic;
+  readonly attribute DOMString subscriptionModifiedTopic;
 
   /**
    * Creates a push subscription for the given |scope| URL and |principal|.
    * If a subscription already exists for this |(scope, principal)| pair,
    * the callback will receive the existing record as the second argument.
    *
    * The |endpoint| property of the subscription record is a URL string
    * that can be used to send push messages to subscribers.
    *
    * Each incoming message fires a `push-message` observer notification, with
    * an `nsIPushMessage` as the subject and the |scope| as the data.
    *
    * If the server drops a subscription, a `push-subscription-change` observer
-   * will be fired, with the subject set to `null` and the data set to |scope|.
-   * Servers may drop subscriptions at any time, so callers should recreate
-   * subscriptions if desired.
+   * will be fired, with the subject set to |principal| and the data set to
+   * |scope|. Servers may drop subscriptions at any time, so callers should
+   * recreate subscriptions if desired.
    */
   void subscribe(in DOMString scope, in nsIPrincipal principal,
                  in nsIPushSubscriptionCallback callback);
 
   /**
    * Creates a restricted push subscription with the given public |key|. The
    * application server must use the corresponding private key to authenticate
    * message delivery requests, as described in draft-thomson-webpush-vapid.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3249,17 +3249,18 @@ ContentChild::RecvPush(const nsCString& 
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifyPushObservers(aScope, Nothing());
+  nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
+                                                  Nothing());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
                                        aMessageId, Nothing());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
@@ -3272,17 +3273,18 @@ ContentChild::RecvPushWithData(const nsC
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifyPushObservers(aScope, Some(aData));
+  nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
+                                                  Some(aData));
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
                                        aMessageId, Some(aData));
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
@@ -3293,17 +3295,18 @@ ContentChild::RecvPushSubscriptionChange
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope);
+  nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope,
+                                                                aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   rv = pushNotifier->NotifySubscriptionChangeWorkers(aScope, aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5779,77 +5779,84 @@ ContentParent::StartProfiler(nsIProfiler
   nsCOMPtr<nsISupports> gatherer;
   profiler->GetProfileGatherer(getter_AddRefs(gatherer));
   mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
 #endif
 }
 
 bool
 ContentParent::RecvNotifyPushObservers(const nsCString& aScope,
+                                       const IPC::Principal& aPrincipal,
                                        const nsString& aMessageId)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifyPushObservers(aScope, Nothing());
+  nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
+                                                  Nothing());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
 ContentParent::RecvNotifyPushObserversWithData(const nsCString& aScope,
+                                               const IPC::Principal& aPrincipal,
                                                const nsString& aMessageId,
                                                InfallibleTArray<uint8_t>&& aData)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifyPushObservers(aScope, Some(aData));
+  nsresult rv = pushNotifier->NotifyPushObservers(aScope, aPrincipal,
+                                                  Some(aData));
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
-ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope)
+ContentParent::RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
+                                                         const IPC::Principal& aPrincipal)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope);
+  nsresult rv = pushNotifier->NotifySubscriptionChangeObservers(aScope,
+                                                                aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
-ContentParent::RecvNotifyPushSubscriptionLostObservers(const nsCString& aScope,
-                                                       const uint16_t& aReason)
+ContentParent::RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
+                                                           const IPC::Principal& aPrincipal)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifier =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifier)) {
       return true;
   }
 
-  nsresult rv = pushNotifier->NotifySubscriptionLostObservers(aScope, aReason);
+  nsresult rv = pushNotifier->NotifySubscriptionModifiedObservers(aScope,
+                                                                  aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1085,26 +1085,29 @@ private:
   virtual bool RecvGetDeviceStorageLocations(DeviceStorageLocationInfo* info) override;
 
   virtual bool RecvGetAndroidSystemInfo(AndroidSystemInfo* aInfo) override;
 
   virtual bool RecvNotifyBenchmarkResult(const nsString& aCodecName,
                                          const uint32_t& aDecodeFPS) override;
 
   virtual bool RecvNotifyPushObservers(const nsCString& aScope,
-                                   const nsString& aMessageId) override;
+                                       const IPC::Principal& aPrincipal,
+                                       const nsString& aMessageId) override;
 
   virtual bool RecvNotifyPushObserversWithData(const nsCString& aScope,
-                                           const nsString& aMessageId,
-                                           InfallibleTArray<uint8_t>&& aData) override;
+                                               const IPC::Principal& aPrincipal,
+                                               const nsString& aMessageId,
+                                               InfallibleTArray<uint8_t>&& aData) override;
 
-  virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope) override;
+  virtual bool RecvNotifyPushSubscriptionChangeObservers(const nsCString& aScope,
+                                                         const IPC::Principal& aPrincipal) override;
 
-  virtual bool RecvNotifyPushSubscriptionLostObservers(const nsCString& aScope,
-                                                       const uint16_t& aReason) override;
+  virtual bool RecvNotifyPushSubscriptionModifiedObservers(const nsCString& aScope,
+                                                           const IPC::Principal& aPrincipal) override;
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
   // details.
 
   GeckoChildProcessHost* mSubprocess;
   ContentParent* mOpener;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1172,33 +1172,36 @@ parent:
      * Tell the parent that a decoder's' benchmark has been completed.
      * The result can then be stored in permanent storage.
      */
     async NotifyBenchmarkResult(nsString aCodecName, uint32_t aDecodeFPS);
 
     /**
      * Notify `push-message` observers without data in the parent.
      */
-    async NotifyPushObservers(nsCString scope, nsString messageId);
+    async NotifyPushObservers(nsCString scope, Principal principal,
+                              nsString messageId);
 
     /**
      * Notify `push-message` observers with data in the parent.
      */
-    async NotifyPushObserversWithData(nsCString scope, nsString messageId,
-                                      uint8_t[] data);
+    async NotifyPushObserversWithData(nsCString scope, Principal principal,
+                                      nsString messageId, uint8_t[] data);
 
     /**
      * Notify `push-subscription-change` observers in the parent.
      */
-    async NotifyPushSubscriptionChangeObservers(nsCString scope);
+    async NotifyPushSubscriptionChangeObservers(nsCString scope,
+                                                Principal principal);
 
     /**
-     * Notify `push-subscription-lost` observers in the parent.
+     * Notify `push-subscription-modified` observers in the parent.
      */
-    async NotifyPushSubscriptionLostObservers(nsCString scope, uint16_t reason);
+    async NotifyPushSubscriptionModifiedObservers(nsCString scope,
+                                                  Principal principal);
 
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 };
 
 }
 }
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -189,16 +189,18 @@ InterceptionFailedWithURL=Failed to load ‘%S’. A ServiceWorker intercepted the request and encountered an unexpected error.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "FetchEvent.respondWith()", "FetchEvent", "no-cors", "opaque", "Response", or "RequestMode". %1$S is a URL. %2$S is a RequestMode value.
 BadOpaqueInterceptionRequestModeWithURL=Failed to load ‘%1$S’. A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while handling a ‘%2$S’ FetchEvent. Opaque Response objects are only valid when the RequestMode is ‘no-cors’.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Error", "Response", "FetchEvent.respondWith()", or "fetch()". %S is a URL.
 InterceptedErrorResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". %S is a URL.
 InterceptedUsedResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent". %s is a URL.
 BadOpaqueRedirectInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while handling a non-navigation FetchEvent.
+# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "RedirectMode" or "follow". %S is a URL.
+BadRedirectModeInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed a redirected Response to FetchEvent.respondWith() while RedirectMode is not ‘follow’.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker" or "FetchEvent.preventDefault()". %S is a URL.
 InterceptionCanceledWithURL=Failed to load ‘%S’. A ServiceWorker canceled the load by calling FetchEvent.preventDefault().
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", or "FetchEvent.respondWith()". %1$S is a URL. %2$S is an error string.
 InterceptionRejectedResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that rejected with ‘%2$S’.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", "FetchEvent.respondWith()", or "Response". %1$S is a URL. %2$S is an error string.
 InterceptedNonResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value ‘%2$S’.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Service-Worker-Allowed" or "HTTP". %1$S and %2$S are URLs.
 ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope ‘%1$S’ is not under the max scope allowed ‘%2$S’. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -91,105 +91,87 @@ CopyAndPackAudio(AVFrame* aFrame, uint32
         *tmp++ = AudioSampleToFloat(data[channel][frame]);
       }
     }
   }
 
   return audio;
 }
 
-void
-FFmpegAudioDecoder<LIBAV_VER>::DecodePacket(MediaRawData* aSample)
+FFmpegAudioDecoder<LIBAV_VER>::DecodeResult
+FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample)
 {
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   AVPacket packet;
   mLib->av_init_packet(&packet);
 
   packet.data = const_cast<uint8_t*>(aSample->Data());
   packet.size = aSample->Size();
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg audio decoder failed to allocate frame.");
-    mCallback->Error();
-    return;
+    return DecodeResult::DECODE_ERROR;
   }
 
   int64_t samplePosition = aSample->mOffset;
   media::TimeUnit pts = media::TimeUnit::FromMicroseconds(aSample->mTime);
 
   while (packet.size > 0) {
     int decoded;
     int bytesConsumed =
       mLib->avcodec_decode_audio4(mCodecContext, mFrame, &decoded, &packet);
 
     if (bytesConsumed < 0) {
       NS_WARNING("FFmpeg audio decoder error.");
-      mCallback->Error();
-      return;
+      return DecodeResult::DECODE_ERROR;
     }
 
     if (decoded) {
       uint32_t numChannels = mCodecContext->channels;
       AudioConfig::ChannelLayout layout(numChannels);
       if (!layout.IsValid()) {
-        mCallback->Error();
-        return;
+        return DecodeResult::DECODE_ERROR;
       }
 
       uint32_t samplingRate = mCodecContext->sample_rate;
 
       AlignedAudioBuffer audio =
         CopyAndPackAudio(mFrame, numChannels, mFrame->nb_samples);
 
       media::TimeUnit duration =
         FramesToTimeUnit(mFrame->nb_samples, samplingRate);
       if (!audio || !duration.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
-        mCallback->Error();
-        return;
+        return DecodeResult::DECODE_ERROR;
       }
 
       RefPtr<AudioData> data = new AudioData(samplePosition,
                                              pts.ToMicroseconds(),
                                              duration.ToMicroseconds(),
                                              mFrame->nb_samples,
                                              Move(audio),
                                              numChannels,
                                              samplingRate);
       mCallback->Output(data);
       pts += duration;
       if (!pts.IsValid()) {
         NS_WARNING("Invalid count of accumulated audio samples");
-        mCallback->Error();
-        return;
+        return DecodeResult::DECODE_ERROR;
       }
     }
     packet.data += bytesConsumed;
     packet.size -= bytesConsumed;
     samplePosition += bytesConsumed;
   }
 
-  if (mTaskQueue->IsEmpty()) {
-    mCallback->InputExhausted();
-  }
-}
-
-nsresult
-FFmpegAudioDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
-{
-  nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod<RefPtr<MediaRawData>>(
-    this, &FFmpegAudioDecoder::DecodePacket, RefPtr<MediaRawData>(aSample)));
-  mTaskQueue->Dispatch(runnable.forget());
-  return NS_OK;
+  return DecodeResult::DECODE_FRAME;
 }
 
 void
 FFmpegAudioDecoder<LIBAV_VER>::ProcessDrain()
 {
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   ProcessFlush();
   mCallback->DrainComplete();
 }
 
 AVCodecID
 FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType)
 {
   if (aMimeType.EqualsLiteral("audio/mpeg")) {
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.h
@@ -22,24 +22,23 @@ class FFmpegAudioDecoder<LIBAV_VER> : pu
 {
 public:
   FFmpegAudioDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const AudioInfo& aConfig);
   virtual ~FFmpegAudioDecoder();
 
   RefPtr<InitPromise> Init() override;
-  nsresult Input(MediaRawData* aSample) override;
-  void ProcessDrain() override;
   void InitCodecContext() override;
   static AVCodecID GetCodecId(const nsACString& aMimeType);
   const char* GetDescriptionName() const override
   {
     return "ffmpeg audio decoder";
   }
 
 private:
-  void DecodePacket(MediaRawData* aSample);
+  DecodeResult DoDecode(MediaRawData* aSample) override;
+  void ProcessDrain() override;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegAACDecoder_h__
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -21,22 +21,22 @@ namespace mozilla
 
 StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
 
   FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(FFmpegLibWrapper* aLib,
                                                   FlushableTaskQueue* aTaskQueue,
                                                   MediaDataDecoderCallback* aCallback,
                                                   AVCodecID aCodecID)
   : mLib(aLib)
-  , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mCodecContext(nullptr)
   , mFrame(NULL)
   , mExtraData(nullptr)
   , mCodecID(aCodecID)
+  , mTaskQueue(aTaskQueue)
 {
   MOZ_ASSERT(aLib);
   MOZ_COUNT_CTOR(FFmpegDataDecoder);
 }
 
 FFmpegDataDecoder<LIBAV_VER>::~FFmpegDataDecoder()
 {
   MOZ_COUNT_DTOR(FFmpegDataDecoder);
@@ -106,16 +106,39 @@ FFmpegDataDecoder<LIBAV_VER>::Shutdown()
       NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessShutdown);
     mTaskQueue->Dispatch(runnable.forget());
   } else {
     ProcessShutdown();
   }
   return NS_OK;
 }
 
+void
+FFmpegDataDecoder<LIBAV_VER>::ProcessDecode(MediaRawData* aSample)
+{
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  switch (DoDecode(aSample)) {
+    case DecodeResult::DECODE_ERROR:
+      mCallback->Error();
+      break;
+    default:
+      if (mTaskQueue->IsEmpty()) {
+        mCallback->InputExhausted();
+      }
+  }
+}
+
+nsresult
+FFmpegDataDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
+{
+  mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+    this, &FFmpegDataDecoder::ProcessDecode, aSample));
+  return NS_OK;
+}
+
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Flush()
 {
   MOZ_ASSERT(mCallback->OnReaderTaskQueue());
   mTaskQueue->Flush();
   nsCOMPtr<nsIRunnable> runnable =
     NewRunnableMethod(this, &FFmpegDataDecoder<LIBAV_VER>::ProcessFlush);
   SyncRunnable::DispatchToThread(mTaskQueue, runnable);
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.h
@@ -27,40 +27,49 @@ public:
   FFmpegDataDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
                     MediaDataDecoderCallback* aCallback,
                     AVCodecID aCodecID);
   virtual ~FFmpegDataDecoder();
 
   static bool Link();
 
   RefPtr<InitPromise> Init() override = 0;
-  nsresult Input(MediaRawData* aSample) override = 0;
+  nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
 
   static AVCodec* FindAVCodec(FFmpegLibWrapper* aLib, AVCodecID aCodec);
 
 protected:
+  enum DecodeResult {
+    DECODE_FRAME,
+    DECODE_NO_FRAME,
+    DECODE_ERROR
+  };
+
   // Flush and Drain operation, always run
   virtual void ProcessFlush();
-  virtual void ProcessDrain() = 0;
   virtual void ProcessShutdown();
   virtual void InitCodecContext() {}
   AVFrame*        PrepareFrame();
   nsresult        InitDecoder();
 
   FFmpegLibWrapper* mLib;
-  RefPtr<FlushableTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
   AVCodecContext* mCodecContext;
   AVFrame*        mFrame;
   RefPtr<MediaByteBuffer> mExtraData;
   AVCodecID mCodecID;
 
 private:
+  void ProcessDecode(MediaRawData* aSample);
+  virtual DecodeResult DoDecode(MediaRawData* aSample) = 0;
+  virtual void ProcessDrain() = 0;
+
   static StaticMutex sMonitor;
+  const RefPtr<FlushableTaskQueue> mTaskQueue;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegDataDecoder_h__
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp
@@ -154,20 +154,18 @@ FFmpegVideoDecoder<LIBAV_VER>::InitCodec
 
   mCodecParser = mLib->av_parser_init(mCodecID);
   if (mCodecParser) {
     mCodecParser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
   }
 }
 
 FFmpegVideoDecoder<LIBAV_VER>::DecodeResult
-FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample)
+FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample)
 {
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-
   uint8_t* inputData = const_cast<uint8_t*>(aSample->Data());
   size_t inputSize = aSample->Size();
 
 #if LIBAVCODEC_VERSION_MAJOR >= 54
   if (inputSize && mCodecParser && (mCodecID == AV_CODEC_ID_VP8
 #if LIBAVCODEC_VERSION_MAJOR >= 55
       || mCodecID == AV_CODEC_ID_VP9
 #endif
@@ -176,45 +174,42 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
     while (inputSize) {
       uint8_t* data;
       int size;
       int len = mLib->av_parser_parse2(mCodecParser, mCodecContext, &data, &size,
                                        inputData, inputSize,
                                        aSample->mTime, aSample->mTimecode,
                                        aSample->mOffset);
       if (size_t(len) > inputSize) {
-        mCallback->Error();
         return DecodeResult::DECODE_ERROR;
       }
       inputData += len;
       inputSize -= len;
       if (size) {
-        switch (DoDecodeFrame(aSample, data, size)) {
+        switch (DoDecode(aSample, data, size)) {
           case DecodeResult::DECODE_ERROR:
             return DecodeResult::DECODE_ERROR;
           case DecodeResult::DECODE_FRAME:
             gotFrame = true;
             break;
           default:
             break;
         }
       }
     }
     return gotFrame ? DecodeResult::DECODE_FRAME : DecodeResult::DECODE_NO_FRAME;
   }
 #endif
-  return DoDecodeFrame(aSample, inputData, inputSize);
+  return DoDecode(aSample, inputData, inputSize);
 }
 
 FFmpegVideoDecoder<LIBAV_VER>::DecodeResult
-FFmpegVideoDecoder<LIBAV_VER>::DoDecodeFrame(MediaRawData* aSample,
-                                            uint8_t* aData, int aSize)
+FFmpegVideoDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
+                                        uint8_t* aData, int aSize)
 {
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-
   AVPacket packet;
   mLib->av_init_packet(&packet);
 
   packet.data = aData;
   packet.size = aSize;
   packet.dts = aSample->mTimecode;
   packet.pts = aSample->mTime;
   packet.flags = aSample->mKeyframe ? AV_PKT_FLAG_KEY : 0;
@@ -224,17 +219,16 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
   // (FFmpeg >= 1.0 provides av_frame_get_pkt_duration)
   // As such we instead use a map using the dts as key that we will retrieve
   // later.
   // The map will have a typical size of 16 entry.
   mDurationMap.Insert(aSample->mTimecode, aSample->mDuration);
 
   if (!PrepareFrame()) {
     NS_WARNING("FFmpeg h264 decoder failed to allocate frame.");
-    mCallback->Error();
     return DecodeResult::DECODE_ERROR;
   }
 
   // Required with old version of FFmpeg/LibAV
   mFrame->reordered_opaque = AV_NOPTS_VALUE;
 
   int decoded;
   int bytesConsumed =
@@ -243,17 +237,16 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
   FFMPEG_LOG("DoDecodeFrame:decode_video: rv=%d decoded=%d "
              "(Input: pts(%lld) dts(%lld) Output: pts(%lld) "
              "opaque(%lld) pkt_pts(%lld) pkt_dts(%lld))",
              bytesConsumed, decoded, packet.pts, packet.dts, mFrame->pts,
              mFrame->reordered_opaque, mFrame->pkt_pts, mFrame->pkt_dts);
 
   if (bytesConsumed < 0) {
     NS_WARNING("FFmpeg video decoder error.");
-    mCallback->Error();
     return DecodeResult::DECODE_ERROR;
   }
 
   // If we've decoded a frame then we need to output it
   if (decoded) {
     int64_t pts = mPtsContext.GuessCorrectPts(mFrame->pkt_pts, mFrame->pkt_dts);
     // Retrieve duration from dts.
     // We use the first entry found matching this dts (this is done to
@@ -302,55 +295,30 @@ FFmpegVideoDecoder<LIBAV_VER>::DoDecodeF
                                             b,
                                             !!mFrame->key_frame,
                                             -1,
                                             mInfo.ScaledImageRect(mFrame->width,
                                                                   mFrame->height));
 
     if (!v) {
       NS_WARNING("image allocation error.");
-      mCallback->Error();
       return DecodeResult::DECODE_ERROR;
     }
     mCallback->Output(v);
     return DecodeResult::DECODE_FRAME;
   }
   return DecodeResult::DECODE_NO_FRAME;
 }
 
 void
-FFmpegVideoDecoder<LIBAV_VER>::DecodeFrame(MediaRawData* aSample)
-{
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-
-  if (DoDecodeFrame(aSample) != DecodeResult::DECODE_ERROR &&
-      mTaskQueue->IsEmpty()) {
-    mCallback->InputExhausted();
-  }
-}
-
-nsresult
-FFmpegVideoDecoder<LIBAV_VER>::Input(MediaRawData* aSample)
-{
-  nsCOMPtr<nsIRunnable> runnable(
-    NewRunnableMethod<RefPtr<MediaRawData>>(
-      this, &FFmpegVideoDecoder<LIBAV_VER>::DecodeFrame,
-      RefPtr<MediaRawData>(aSample)));
-  mTaskQueue->Dispatch(runnable.forget());
-
-  return NS_OK;
-}
-
-void
 FFmpegVideoDecoder<LIBAV_VER>::ProcessDrain()
 {
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   RefPtr<MediaRawData> empty(new MediaRawData());
   empty->mTimecode = mPtsContext.LastDts();
-  while (DoDecodeFrame(empty) == DecodeResult::DECODE_FRAME) {
+  while (DoDecode(empty) == DecodeResult::DECODE_FRAME) {
   }
   mCallback->DrainComplete();
 }
 
 void
 FFmpegVideoDecoder<LIBAV_VER>::ProcessFlush()
 {
   mPtsContext.Reset();
--- a/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
+++ b/dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h
@@ -21,49 +21,40 @@ class FFmpegVideoDecoder : public FFmpeg
 };
 
 template <>
 class FFmpegVideoDecoder<LIBAV_VER> : public FFmpegDataDecoder<LIBAV_VER>
 {
   typedef mozilla::layers::Image Image;
   typedef mozilla::layers::ImageContainer ImageContainer;
 
-  enum DecodeResult {
-    DECODE_FRAME,
-    DECODE_NO_FRAME,
-    DECODE_ERROR
-  };
-
 public:
   FFmpegVideoDecoder(FFmpegLibWrapper* aLib, FlushableTaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const VideoInfo& aConfig,
                      ImageContainer* aImageContainer);
   virtual ~FFmpegVideoDecoder();
 
   RefPtr<InitPromise> Init() override;
-  nsresult Input(MediaRawData* aSample) override;
-  void ProcessDrain() override;
-  void ProcessFlush() override;
   void InitCodecContext() override;
   const char* GetDescriptionName() const override
   {
 #ifdef USING_MOZFFVPX
     return "ffvpx video decoder";
 #else
     return "ffmpeg video decoder";
 #endif
   }
   static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
-  void DecodeFrame(MediaRawData* aSample);
-  DecodeResult DoDecodeFrame(MediaRawData* aSample);
-  DecodeResult DoDecodeFrame(MediaRawData* aSample, uint8_t* aData, int aSize);
-  void DoDrain();
+  DecodeResult DoDecode(MediaRawData* aSample) override;
+  DecodeResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize);
+  void ProcessDrain() override;
+  void ProcessFlush() override;
   void OutputDelayedFrames();
 
   /**
    * This method allocates a buffer for FFmpeg's decoder, wrapped in an Image.
    * Currently it only supports Planar YUV420, which appears to be the only
    * non-hardware accelerated image format that FFmpeg's H264 decoder is
    * capable of outputting.
    */
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -604,16 +604,17 @@ skip-if = os == 'win' && !debug # bug 11
 [test_bug895091.html]
 [test_bug895305.html]
 [test_bug919265.html]
 [test_bug957847.html]
 [test_bug1018933.html]
 [test_bug1113600.html]
 tags=capturestream
 [test_bug1242338.html]
+[test_bug1242594.html]
 [test_bug1248229.html]
 tags=capturestream
 [test_can_play_type.html]
 [test_can_play_type_mpeg.html]
 skip-if = buildapp == 'b2g' # bug 1021675
 [test_can_play_type_no_ogg.html]
 [test_can_play_type_ogg.html]
 [test_chaining.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_bug1242594.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242594
+-->
+<head>
+  <meta charset='utf-8'>
+  <title>Bug 1242594 - Unbind a video element with HTMLTrackElement
+  should not remove the TextTrack</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var video = document.createElement("video");
+video.src = "seek.webm";
+video.preload = "auto";
+
+var trackElement = document.createElement("track");
+trackElement.src = "basic.vtt";
+trackElement.kind = "subtitles";
+
+document.getElementById("content").appendChild(video);
+video.appendChild(trackElement);
+
+video.addEventListener("loadedmetadata", function run_tests() {
+  is(video.textTracks.length, 1, "Video should have one TextTrack.");
+  var parent = video.parentNode;
+  parent.removeChild(video);
+  is(video.textTracks.length, 1, "After unbind the video element, should have one TextTrack.");
+  parent.appendChild(video);
+  is(video.textTracks.length, 1, "After bind the video element, should have one TextTrack.");
+  SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -219,37 +219,47 @@ function realCreateHTML(meta) {
   document.body.insertBefore(display, test);
 
   var content = document.createElement('div');
   content.setAttribute('id', 'content');
   content.style.display = meta.visible ? 'block' : "none";
   document.body.appendChild(content);
 }
 
+function getMediaElement(label, direction, streamId) {
+  var id = label + '_' + direction + '_' + streamId;
+  return document.getElementById(id);
+}
 
 /**
  * Create the HTML element if it doesn't exist yet and attach
  * it to the content node.
  *
- * @param {string} type
- *        Type of media element to create ('audio' or 'video')
  * @param {string} label
- *        Description to use for the element
+ *        Prefix to use for the element
+ * @param {direction} "local" or "remote"
+ * @param {stream} A MediaStream id.
+ * @param {audioOnly} Use <audio> element instead of <video>
  * @return {HTMLMediaElement} The created HTML media element
  */
-function createMediaElement(type, label) {
-  var id = label + '_' + type;
+function createMediaElement(label, direction, streamId, audioOnly) {
+  var id = label + '_' + direction + '_' + streamId;
   var element = document.getElementById(id);
 
   // Sanity check that we haven't created the element already
   if (element) {
     return element;
   }
 
-  element = document.createElement(type === 'audio' ? 'audio' : 'video');
+  if (!audioOnly) {
+    // Even if this is just audio now, we might add video later.
+    element = document.createElement('video');
+  } else {
+    element = document.createElement('audio');
+  }
   element.setAttribute('id', id);
   element.setAttribute('height', 100);
   element.setAttribute('width', 150);
   element.setAttribute('controls', 'controls');
   element.setAttribute('autoplay', 'autoplay');
   document.getElementById('content').appendChild(element);
 
   return element;
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -716,27 +716,26 @@ function PeerConnectionWrapper(label, co
   if (configuration && configuration.label_suffix) {
     label = label + "_" + configuration.label_suffix;
   }
   this.label = label;
   this.whenCreated = Date.now();
 
   this.constraints = [ ];
   this.offerOptions = {};
-  this.streams = [ ];
-  this.streamsForLocalTracks = [ ];
-  this.mediaElements = [ ];
 
   this.dataChannels = [ ];
 
   this._local_ice_candidates = [];
   this._remote_ice_candidates = [];
   this.localRequiresTrickleIce = false;
   this.remoteRequiresTrickleIce = false;
   this.localMediaElements = [];
+  this.remoteMediaElements = [];
+  this.audioElementsOnly = false;
 
   this.expectedLocalTrackInfoById = {};
   this.expectedRemoteTrackInfoById = {};
   this.observedRemoteTrackInfoById = {};
 
   this.disableRtpCountChecking = false;
 
   this.iceCheckingRestartExpected = false;
@@ -826,16 +825,36 @@ PeerConnectionWrapper.prototype = {
   get iceConnectionState() {
     return this._pc.iceConnectionState;
   },
 
   setIdentityProvider: function(provider, protocol, identity) {
     this._pc.setIdentityProvider(provider, protocol, identity);
   },
 
+  ensureMediaElement : function(track, stream, direction) {
+    var element = getMediaElement(this.label, direction, stream.id);
+
+    if (!element) {
+      element = createMediaElement(this.label, direction, stream.id,
+                                   this.audioElementsOnly);
+      if (direction == "local") {
+        this.localMediaElements.push(element);
+      } else if (direction == "remote") {
+        this.remoteMediaElements.push(element);
+      }
+    }
+
+    // We do this regardless, because sometimes we end up with a new stream with
+    // an old id (ie; the rollback tests cause the same stream to be added
+    // twice)
+    element.srcObject = stream;
+    element.play();
+  },
+
   /**
    * Attaches a local track to this RTCPeerConnection using
    * RTCPeerConnection.addTrack().
    *
    * Also creates a media element playing a MediaStream containing all
    * tracks that have been added to `stream` using `attachLocalTrack()`.
    *
    * @param {MediaStreamTrack} track
@@ -853,93 +872,56 @@ PeerConnectionWrapper.prototype = {
     ok(track.id, "track has id");
     ok(track.kind, "track has kind");
     ok(stream.id, "stream has id");
     this.expectedLocalTrackInfoById[track.id] = {
       type: track.kind,
       streamId: stream.id,
     };
 
-    var mappedStream =
-      this.streamsForLocalTracks.find(o => o.fromStreamId == stream.id);
-
-    if (mappedStream) {
-      mappedStream.stream.addTrack(track);
-      return this.observedNegotiationNeeded;
-    }
+    this.ensureMediaElement(track, stream, "local");
 
-    mappedStream = new MediaStream([track]);
-    this.streamsForLocalTracks.push(
-      { fromStreamId: stream.id
-      , stream: mappedStream
-      }
-    );
-
-    var element = createMediaElement(track.kind, this.label + '_tracks_' + this.streamsForLocalTracks.length);
-    this.mediaElements.push(element);
-    element.srcObject = mappedStream;
-    element.play();
-
-    // Store local media elements so that we can stop them when done.
-    // Don't store remote ones because they should stop when the PC does.
-    this.localMediaElements.push(element);
     return this.observedNegotiationNeeded;
   },
 
   /**
-   * Callback when we get media from either side. Also an appropriate
-   * HTML media element will be created.
+   * Callback when we get local media. Also an appropriate HTML media element
+   * will be created, which may be obtained later with |getMediaElement|.
    *
    * @param {MediaStream} stream
    *        Media stream to handle
-   * @param {string} type
-   *        The type of media stream ('audio' or 'video')
-   * @param {string} side
-   *        The location the stream is coming from ('local' or 'remote')
    */
-  attachMedia : function(stream, type, side) {
-    info("Got media stream: " + type + " (" + side + ")");
-    this.streams.push(stream);
+  attachLocalStream : function(stream) {
+    info("Got local media stream: (" + stream.id + ")");
 
-    if (side === 'local') {
-      this.expectNegotiationNeeded();
-      // In order to test both the addStream and addTrack APIs, we do video one
-      // way and audio + audiovideo the other.
-      if (type == "video") {
-        this._pc.addStream(stream);
-        ok(this._pc.getSenders().find(sender => sender.track == stream.getVideoTracks()[0]),
-           "addStream adds sender");
-      } else {
-        stream.getTracks().forEach(track => {
-          var sender = this._pc.addTrack(track, stream);
-          is(sender.track, track, "addTrack returns sender");
-        });
-      }
-
+    this.expectNegotiationNeeded();
+    // In order to test both the addStream and addTrack APIs, we do half one
+    // way, half the other, at random.
+    if (Math.random() < 0.5) {
+      info("Using addStream.");
+      this._pc.addStream(stream);
+      ok(this._pc.getSenders().find(sender => sender.track == stream.getTracks()[0]),
+         "addStream returns sender");
+    } else {
+      info("Using addTrack (on PC).");
       stream.getTracks().forEach(track => {
-        ok(track.id, "track has id");
-        ok(track.kind, "track has kind");
-        this.expectedLocalTrackInfoById[track.id] = {
-            type: track.kind,
-            streamId: stream.id
-          };
+        var sender = this._pc.addTrack(track, stream);
+        is(sender.track, track, "addTrack returns sender");
       });
     }
 
-    var element = createMediaElement(type, this.label + '_' + side + this.streams.length);
-    this.mediaElements.push(element);
-    element.srcObject = stream;
-    element.play();
-
-    // Store local media elements so that we can stop them when done.
-    // Don't store remote ones because they should stop when the PC does.
-    if (side === 'local') {
-      this.localMediaElements.push(element);
-      return this.observedNegotiationNeeded;
-    }
+    stream.getTracks().forEach(track => {
+      ok(track.id, "track has id");
+      ok(track.kind, "track has kind");
+      this.expectedLocalTrackInfoById[track.id] = {
+          type: track.kind,
+          streamId: stream.id
+        };
+      this.ensureMediaElement(track, stream, "local");
+    });
   },
 
   removeSender : function(index) {
     var sender = this._pc.getSenders()[index];
     delete this.expectedLocalTrackInfoById[sender.track.id];
     this.expectNegotiationNeeded();
     this._pc.removeTrack(sender);
     return this.observedNegotiationNeeded;
@@ -965,32 +947,29 @@ PeerConnectionWrapper.prototype = {
     if (constraintsList.length === 0) {
       info("Skipping GUM: no UserMedia requested");
       return Promise.resolve();
     }
 
     info("Get " + constraintsList.length + " local streams");
     return Promise.all(constraintsList.map(constraints => {
       return getUserMedia(constraints).then(stream => {
-        var type = '';
         if (constraints.audio) {
-          type = 'audio';
           stream.getAudioTracks().map(track => {
             info(this + " gUM local stream " + stream.id +
               " with audio track " + track.id);
           });
         }
         if (constraints.video) {
-          type += 'video';
           stream.getVideoTracks().map(track => {
             info(this + " gUM local stream " + stream.id +
               " with video track " + track.id);
           });
         }
-        return this.attachMedia(stream, type, 'local');
+        return this.attachLocalStream(stream);
       });
     }));
   },
 
   /**
    * Create a new data channel instance.  Also creates a promise called
    * `this.nextDataChannel` that resolves when the next data channel arrives.
    */
@@ -1149,56 +1128,36 @@ PeerConnectionWrapper.prototype = {
         observedKind + ", which matches " + expectedKind);
     observedTrackInfoById[track.id] = expectedTrackInfoById[track.id];
   },
 
   allExpectedTracksAreObserved: function(expected, observed) {
     return Object.keys(expected).every(trackId => observed[trackId]);
   },
 
-  setupAddStreamEventHandler: function() {
-    var resolveAllAddStreamEventsDone;
+  setupTrackEventHandler: function() {
+    var resolveAllTrackEventsDone;
 
     // checkMediaTracks waits on this promise later on in the test.
-    this.allAddStreamEventsDonePromise =
-      new Promise(resolve => resolveAllAddStreamEventsDone = resolve);
-
-    this._pc.addEventListener('addstream', event => {
-      info(this + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
+    this.allTrackEventsDonePromise =
+      new Promise(resolve => resolveAllTrackEventsDone = resolve);
 
-      // TODO(bug 1130185): We need to handle addtrack events once we start
-      // testing addTrack on pre-existing streams.
+    this._pc.addEventListener('track', event => {
+      info(this + ": 'ontrack' event fired for " + JSON.stringify(event.track));
 
-      event.stream.getTracks().forEach(track => {
-        this.checkTrackIsExpected(track,
-                                  this.expectedRemoteTrackInfoById,
-                                  this.observedRemoteTrackInfoById);
-      });
+      this.checkTrackIsExpected(event.track,
+                                this.expectedRemoteTrackInfoById,
+                                this.observedRemoteTrackInfoById);
 
       if (this.allExpectedTracksAreObserved(this.expectedRemoteTrackInfoById,
                                             this.observedRemoteTrackInfoById)) {
-        resolveAllAddStreamEventsDone();
+        resolveAllTrackEventsDone();
       }
 
-      var type = '';
-      if (event.stream.getAudioTracks().length > 0) {
-        type = 'audio';
-        event.stream.getAudioTracks().map(track => {
-          info(this + " remote stream " + event.stream.id + " with audio track " +
-               track.id);
-        });
-      }
-      if (event.stream.getVideoTracks().length > 0) {
-        type += 'video';
-        event.stream.getVideoTracks().map(track => {
-          info(this + " remote stream " + event.stream.id + " with video track " +
-            track.id);
-        });
-      }
-      this.attachMedia(event.stream, type, 'remote');
+      this.ensureMediaElement(event.track, event.streams[0], 'remote');
     });
   },
 
   /**
    * Either adds a given ICE candidate right away or stores it to be added
    * later, depending on the state of the PeerConnection.
    *
    * @param {object} candidate
@@ -1384,17 +1343,17 @@ PeerConnectionWrapper.prototype = {
          JSON.stringify(this.expectedRemoteTrackInfoById));
 
     // No tracks are expected
     if (this.allExpectedTracksAreObserved(this.expectedRemoteTrackInfoById,
                                           this.observedRemoteTrackInfoById)) {
       return;
     }
 
-    return timerGuard(this.allAddStreamEventsDonePromise, 60000, "onaddstream never fired");
+    return timerGuard(this.allTrackEventsDonePromise, 60000, "The expected ontrack events never fired");
   },
 
   checkMsids: function() {
     var checkSdpForMsids = (desc, expectedTrackInfo, side) => {
       Object.keys(expectedTrackInfo).forEach(trackId => {
         var streamId = expectedTrackInfo[trackId].streamId;
         ok(desc.sdp.match(new RegExp("a=msid:" + streamId + " " + trackId)),
            this + ": " + side + " SDP contains stream " + streamId +
@@ -1403,16 +1362,26 @@ PeerConnectionWrapper.prototype = {
     };
 
     checkSdpForMsids(this.localDescription, this.expectedLocalTrackInfoById,
                      "local");
     checkSdpForMsids(this.remoteDescription, this.expectedRemoteTrackInfoById,
                      "remote");
   },
 
+  markRemoteTracksAsNegotiated: function() {
+    Object.values(this.observedRemoteTrackInfoById).forEach(
+        trackInfo => trackInfo.negotiated = true);
+  },
+
+  rollbackRemoteTracksIfNotNegotiated: function() {
+    Object.keys(this.observedRemoteTrackInfoById).forEach(
+        id => delete this.observedRemoteTrackInfoById[id]);
+  },
+
   /**
    * Check that media flow is present on the given media element by waiting for
    * it to reach ready state HAVE_ENOUGH_DATA and progress time further than
    * the start of the check.
    *
    * This ensures, that the stream being played is producing
    * data and that at least one video frame has been displayed.
    *
@@ -1485,17 +1454,18 @@ PeerConnectionWrapper.prototype = {
    * Wait for presence of video flow on all media elements and rtp flow on
    * all sending and receiving track involved in this test.
    *
    * @returns {Promise}
    *        A promise that resolves when media flows for all elements and tracks
    */
   waitForMediaFlow : function() {
     return Promise.all([].concat(
-      this.mediaElements.map(element => this.waitForMediaElementFlow(element)),
+      this.localMediaElements.map(element => this.waitForMediaElementFlow(element)),
+      this.remoteMediaElements.map(element => this.waitForMediaElementFlow(element)),
       this._pc.getSenders().map(sender => this.waitForRtpFlow(sender.track)),
       this._pc.getReceivers().map(receiver => this.waitForRtpFlow(receiver.track))));
   },
 
   /**
    * Check that correct audio (typically a flat tone) is flowing to this
    * PeerConnection. Uses WebAudio AnalyserNodes to compare input and output
    * audio data in the frequency domain.
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -160,22 +160,22 @@ var commandsPeerConnectionInitial = [
   function PC_LOCAL_SETUP_SIGNALING_LOGGER(test) {
     test.pcLocal.logSignalingState();
   },
 
   function PC_REMOTE_SETUP_SIGNALING_LOGGER(test) {
     test.pcRemote.logSignalingState();
   },
 
-  function PC_LOCAL_SETUP_ADDSTREAM_HANDLER(test) {
-    test.pcLocal.setupAddStreamEventHandler();
+  function PC_LOCAL_SETUP_TRACK_HANDLER(test) {
+    test.pcLocal.setupTrackEventHandler();
   },
 
-  function PC_REMOTE_SETUP_ADDSTREAM_HANDLER(test) {
-    test.pcRemote.setupAddStreamEventHandler();
+  function PC_REMOTE_SETUP_TRACK_HANDLER(test) {
+    test.pcRemote.setupTrackEventHandler();
   },
 
   function PC_LOCAL_CHECK_INITIAL_SIGNALINGSTATE(test) {
     is(test.pcLocal.signalingState, STABLE,
        "Initial local signalingState is 'stable'");
   },
 
   function PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE(test) {
@@ -348,17 +348,18 @@ var commandsPeerConnectionOfferAnswer = 
       });
   },
 
   function PC_REMOTE_SET_LOCAL_DESCRIPTION(test) {
     return test.setLocalDescription(test.pcRemote, test.originalAnswer, STABLE)
       .then(() => {
         is(test.pcRemote.signalingState, STABLE,
            "signalingState after remote setLocalDescription is 'stable'");
-      });
+      })
+      .then(() => test.pcRemote.markRemoteTracksAsNegotiated());
   },
 
   function PC_LOCAL_GET_ANSWER(test) {
     if (!test.testOptions.steeplechase) {
       test._remote_answer = test.originalAnswer;
       test._answer_constraints = test.pcRemote.constraints;
       return Promise.resolve();
     }
@@ -370,17 +371,18 @@ var commandsPeerConnectionOfferAnswer = 
     });
   },
 
   function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) {
     return test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
       .then(() => {
         is(test.pcLocal.signalingState, STABLE,
            "signalingState after local setRemoteDescription is 'stable'");
-      });
+      })
+      .then(() => test.pcLocal.markRemoteTracksAsNegotiated());
   },
   function PC_REMOTE_SANE_LOCAL_SDP(test) {
     test.pcRemote.localRequiresTrickleIce =
       sdputils.verifySdp(test._remote_answer, "answer",
                          test._offer_constraints, test._offer_options,
                          test.testOptions);
   },
   function PC_LOCAL_SANE_REMOTE_SDP(test) {
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudio.html
@@ -10,14 +10,16 @@
     bug: "796892",
     title: "Basic audio-only peer connection"
   });
 
   var test;
   runNetworkTest(function (options) {
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    // pc.js uses video elements by default, we want to test audio elements here
+    test.pcLocal.audioElementsOnly = true;
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_2d.html
@@ -10,52 +10,50 @@
 createHTML({
   bug: "1032848",
   title: "Canvas(2D)::CaptureStream as video-only input to peerconnection",
   visible: true
 });
 
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
-  var vremote;
+  var mediaElement;
   var h = new CaptureStreamTestHelper2D();
   var canvas = document.createElement('canvas');
   var stream;
   canvas.id = 'source_canvas';
   canvas.width = canvas.height = 10;
   document.getElementById('content').appendChild(canvas);
 
   test.setMediaConstraints([{video: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
-    function DRAW_INITIAL_LOCAL_GREEN(test) {
+    function PC_LOCAL_DRAW_INITIAL_LOCAL_GREEN(test) {
       h.drawColor(canvas, h.green);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
       stream = canvas.captureStream(0);
-      test.pcLocal.attachMedia(stream, 'video', 'local');
+      test.pcLocal.attachLocalStream(stream);
     }
   ]);
   test.chain.append([
-    function FIND_REMOTE_VIDEO() {
-      vremote = document.getElementById('pcRemote_remote1_video');
-      ok(!!vremote, "Should have remote video element for pcRemote");
-    },
-    function WAIT_FOR_REMOTE_GREEN() {
-      return h.waitForPixelColor(vremote, h.green, 128,
+    function PC_REMOTE_WAIT_FOR_REMOTE_GREEN() {
+      mediaElement = test.pcRemote.remoteMediaElements[0];
+      ok(!!mediaElement, "Should have remote video element for pcRemote");
+      return h.waitForPixelColor(mediaElement, h.green, 128,
                                  "pcRemote's remote should become green");
     },
-    function DRAW_LOCAL_RED() {
+    function PC_LOCAL_DRAW_LOCAL_RED() {
       // After requesting a frame it will be captured at the time of next render.
       // Next render will happen at next stable state, at the earliest,
       // i.e., this order of `requestFrame(); draw();` should work.
       stream.requestFrame();
       h.drawColor(canvas, h.red);
     },
-    function WAIT_FOR_REMOTE_RED() {
-      return h.waitForPixelColor(vremote, h.red, 128,
+    function PC_REMOTE_WAIT_FOR_REMOTE_RED() {
+      return h.waitForPixelColor(mediaElement, h.red, 128,
                                  "pcRemote's remote should become red");
     }
   ]);
   test.run();
 });
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
+++ b/dom/media/tests/mochitest/test_peerConnection_captureStream_canvas_webgl.html
@@ -75,22 +75,22 @@ runNetworkTest(() => {
       gl.vertexAttribPointer(program.aPosition, squareBuffer.itemSize, gl.FLOAT, false, 0, 0);
     },
     function DRAW_LOCAL_GREEN(test) {
       h.drawColor(canvas, h.green);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
       test.pcLocal.canvasStream = canvas.captureStream(0.0);
       is(test.pcLocal.canvasStream.canvas, canvas, "Canvas attribute is correct");
-      test.pcLocal.attachMedia(test.pcLocal.canvasStream, 'video', 'local');
+      test.pcLocal.attachLocalStream(test.pcLocal.canvasStream);
     }
   ]);
   test.chain.append([
     function FIND_REMOTE_VIDEO() {
-      vremote = document.getElementById('pcRemote_remote1_video');
+      vremote = test.pcRemote.remoteMediaElements[0];
       ok(!!vremote, "Should have remote video element for pcRemote");
     },
     function WAIT_FOR_REMOTE_GREEN() {
       return h.waitForPixelColor(vremote, h.green, 128,
                                  "pcRemote's remote should become green");
     },
     function REQUEST_FRAME(test) {
       // After requesting a frame it will be captured at the time of next render.
--- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
@@ -56,20 +56,17 @@ function startTest(media, token) {
                                       config_remote: { label_suffix: media.name } });
       test.setOfferOptions({ offerToReceiveVideo: false,
                              offerToReceiveAudio: false });
       var hasVideo = stream.getVideoTracks().length > 0;
       var hasAudio = stream.getAudioTracks().length > 0;
       test.setMediaConstraints([{ video: hasVideo, audio: hasAudio }], []);
       test.chain.replace("PC_LOCAL_GUM", [
         function PC_LOCAL_CAPTUREVIDEO(test) {
-          var type = "";
-          if (hasAudio) { type += "audio"; }
-          if (hasVideo) { type += "video"; }
-          test.pcLocal.attachMedia(stream, type, "local");
+          test.pcLocal.attachLocalStream(stream);
           video.play();
         }
       ]);
       return test.chain.execute();
     });
   })
   // Handle both MediaErrors (with the `code` attribute) and other errors.
   .catch(e => ok(false, "Error (" + e + ")" +
--- a/dom/media/tests/mochitest/test_peerConnection_forwarding_basicAudioVideoCombined.html
+++ b/dom/media/tests/mochitest/test_peerConnection_forwarding_basicAudioVideoCombined.html
@@ -20,17 +20,17 @@ runNetworkTest(function() {
 
   gumTest.setMediaConstraints([{audio: true, video: true}], []);
   forwardingTest.setMediaConstraints([{audio: true, video: true}], []);
   forwardingTest.chain.replace("PC_LOCAL_GUM", [
     function PC_FORWARDING_CAPTUREVIDEO(test) {
       var streams = gumTest.pcRemote._pc.getRemoteStreams();
       is(streams.length, 1, "One stream to forward");
       is(streams[0].getTracks().length, 2, "Forwarded stream has 2 tracks");
-      forwardingTest.pcLocal.attachMedia(streams[0], 'audiovideo', 'local');
+      forwardingTest.pcLocal.attachLocalStream(streams[0]);
       return Promise.resolve();
     }
   ]);
   gumTest.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
   gumTest.chain.execute()
     .then(() => forwardingTest.chain.execute())
     .then(() => gumTest.close())
     .then(() => forwardingTest.close())
--- a/dom/media/tests/mochitest/test_peerConnection_multiple_captureStream_canvas_2d.html
+++ b/dom/media/tests/mochitest/test_peerConnection_multiple_captureStream_canvas_2d.html
@@ -33,28 +33,27 @@ runNetworkTest(() => {
     function DRAW_INITIAL_LOCAL1_GREEN(test) {
       h.drawColor(canvas1, h.green);
     },
     function DRAW_INITIAL_LOCAL2_BLUE(test) {
       h.drawColor(canvas2, h.blue);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
       stream1 = canvas1.captureStream(0); // fps = 0 to capture single frame
-      test.pcLocal.attachMedia(stream1, 'video', 'local');
+      test.pcLocal.attachLocalStream(stream1);
       stream2 = canvas2.captureStream(0); // fps = 0 to capture single frame
-      test.pcLocal.attachMedia(stream2, 'video', 'local');
+      test.pcLocal.attachLocalStream(stream2);
     }
   ]);
 
   test.chain.append([
     function CHECK_REMOTE_VIDEO() {
-      var testremote = document.getElementById('pcRemote_remote3_video');
-      ok(!testremote, "Should not have remote3 video element for pcRemote");
-      vremote1 = document.getElementById('pcRemote_remote1_video');
-      vremote2 = document.getElementById('pcRemote_remote2_video');
+      is(test.pcRemote.remoteMediaElements.length, 2, "pcRemote Should have 2 remote media elements");
+      vremote1 = test.pcRemote.remoteMediaElements[0];
+      vremote2 = test.pcRemote.remoteMediaElements[1];
 
       // since we don't know which remote video is created first, we don't know
       // which should be blue or green, but this will make sure that one is
       // green and one is blue
       return Promise.race([
                Promise.all([
                  h.waitForPixelColor(vremote1, h.green, 128,
                                      "pcRemote's remote1 should become green"),
--- a/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
@@ -34,17 +34,18 @@
             });
           }
         },
 
         function PC_REMOTE_ROLLBACK(test) {
           return test.setRemoteDescription(
               test.pcRemote,
               new RTCSessionDescription({ type: "rollback" }),
-              STABLE);
+              STABLE)
+            .then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
         },
 
         function PC_LOCAL_ROLLBACK(test) {
           // We haven't negotiated the new stream yet.
           test.pcLocal.expectNegotiationNeeded();
           return test.setLocalDescription(
               test.pcLocal,
               new RTCSessionDescription({ type: "rollback", sdp: ""}),
--- a/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
@@ -18,17 +18,18 @@
     test.chain.removeAfter('PC_REMOTE_CHECK_CAN_TRICKLE_SYNC');
     test.chain.append([
       function PC_REMOTE_ROLLBACK(test) {
         // We still haven't negotiated the tracks
         test.pcRemote.expectNegotiationNeeded();
         return test.setRemoteDescription(
           test.pcRemote,
           new RTCSessionDescription({ type: "rollback" }),
-          STABLE);
+          STABLE)
+          .then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
       },
 
       function PC_REMOTE_CHECK_CAN_TRICKLE_REVERT_SYNC(test) {
         is(test.pcRemote._pc.canTrickleIceCandidates, null,
            "Remote canTrickleIceCandidates is reverted to null");
       },
 
       function PC_LOCAL_ROLLBACK(test) {
--- a/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
+++ b/dom/media/tests/mochitest/test_peerConnection_simulcastOffer.html
@@ -61,17 +61,17 @@
       helper.drawColor(canvas, helper.green); // Make sure this is initted
 
       test = new PeerConnectionTest({bundle: false});
       test.setMediaConstraints([{video: true}], []);
 
       test.chain.replace("PC_LOCAL_GUM", [
         function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
           stream = canvas.captureStream(10);
-          test.pcLocal.attachMedia(stream, 'video', 'local');
+          test.pcLocal.attachLocalStream(stream);
         },
         function PC_LOCAL_CANVAS_ALTERNATE_COLOR(test) {
           alternateRedAndGreen(helper, canvas, stream);
         }
       ]);
 
       test.chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
         function PC_LOCAL_SET_RIDS(test) {
@@ -102,60 +102,60 @@
           // Cause pcRemote to filter out everything but the first SSRC. This
           // lets only one of the simulcast streams through.
           selectRecvSsrc(test.pcRemote, 0);
         }
       ]);
 
       test.chain.append([
         function PC_REMOTE_WAIT_FOR_COLOR_CHANGE_1() {
-          var vremote = document.getElementById('pcRemote_remote1_video');
+          var vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
           return waitForColorChange(helper, vremote);
         },
         function PC_REMOTE_CHECK_SIZE_1() {
-          var vlocal = document.getElementById('pcLocal_local1_video');
-          var vremote = document.getElementById('pcRemote_remote1_video');
+          var vlocal = test.pcLocal.localMediaElements[0];
+          var vremote = test.pcRemote.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcLocal");
           ok(vremote, "Should have remote video element for pcRemote");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth, "sink is same width as source");
           is(vremote.videoHeight, vlocal.videoHeight, "sink is same height as source");
         },
         function PC_REMOTE_SET_RTP_SECOND_RID(test) {
           // Now, cause pcRemote to filter out everything but the second SSRC.
           // This lets only the other simulcast stream through.
           selectRecvSsrc(test.pcRemote, 1);
         },
         function PC_REMOTE_WAIT_FOR_SECOND_MEDIA_FLOW(test) {
           return test.pcRemote.waitForMediaFlow();
         },
         function PC_REMOTE_WAIT_FOR_COLOR_CHANGE_2() {
-          var vremote = document.getElementById('pcRemote_remote1_video');
+          var vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
           return waitForColorChange(helper, vremote);
         },
         function PC_REMOTE_CHECK_SIZE_2() {
-          var vlocal = document.getElementById('pcLocal_local1_video');
-          var vremote = document.getElementById('pcRemote_remote1_video');
+          var vlocal = test.pcLocal.localMediaElements[0];
+          var vremote = test.pcRemote.remoteMediaElements[0];
           ok(vlocal, "Should have local video element for pcLocal");
           ok(vremote, "Should have remote video element for pcRemote");
           ok(vlocal.videoWidth > 0, "source width is positive");
           ok(vlocal.videoHeight > 0, "source height is positive");
           is(vremote.videoWidth, vlocal.videoWidth / 2, "sink is 1/2 width of source");
           is(vremote.videoHeight, vlocal.videoHeight / 2,  "sink is 1/2 height of source");
         },
         function PC_REMOTE_SET_RTP_NONEXISTENT_RID(test) {
           // Now, cause pcRemote to filter out everything, just to make sure
           // selectRecvSsrc is working.
           selectRecvSsrc(test.pcRemote, 2);
         },
         function PC_REMOTE_ENSURE_NO_COLOR_CHANGE() {
-          var vremote = document.getElementById('pcRemote_remote1_video');
+          var vremote = test.pcRemote.remoteMediaElements[0];
           ok(vremote, "Should have remote video element for pcRemote");
           return ensureNoColorChange(helper, vremote);
         },
       ]);
 
       return test.run();
   })
   .catch(e => ok(false, "unexpected failure: " + e)));
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html
+++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling.html
@@ -15,29 +15,29 @@ createHTML({
 runNetworkTest(() => {
   var test = new PeerConnectionTest();
 
   // Always use fake tracks since we depend on video to be somewhat green and
   // audio to have a large 1000Hz component (or 440Hz if using fake devices).
   test.setMediaConstraints([{audio: true, video: true, fake: true}], []);
   test.chain.append([
     function CHECK_ASSUMPTIONS() {
-      is(test.pcLocal.mediaElements.length, 1,
-         "pcLocal should only have one media element");
-      is(test.pcRemote.mediaElements.length, 1,
-         "pcRemote should only have one media element");
-      is(test.pcLocal.streams.length, 1,
-         "pcLocal should only have one stream (the local one)");
-      is(test.pcRemote.streams.length, 1,
-         "pcRemote should only have one stream (the remote one)");
+      is(test.pcLocal.localMediaElements.length, 1,
+         "pcLocal should have one media element");
+      is(test.pcRemote.remoteMediaElements.length, 1,
+         "pcRemote should have one media element");
+      is(test.pcLocal._pc.getLocalStreams().length, 1,
+         "pcLocal should have one stream");
+      is(test.pcRemote._pc.getRemoteStreams().length, 1,
+         "pcRemote should have one stream");
     },
     function CHECK_VIDEO() {
       var h = new CaptureStreamTestHelper2D();
-      var localVideo = test.pcLocal.mediaElements[0];
-      var remoteVideo = test.pcRemote.mediaElements[0];
+      var localVideo = test.pcLocal.localMediaElements[0];
+      var remoteVideo = test.pcRemote.remoteMediaElements[0];
       // We check a pixel somewhere away from the top left corner since
       // MediaEngineDefault puts semi-transparent time indicators there.
       const offsetX = 50;
       const offsetY = 50;
       const threshold = 128;
 
       // We're regarding black as disabled here, and we're setting the alpha
       // channel of the pixel to 255 to disregard alpha when testing.
@@ -49,43 +49,43 @@ runNetworkTest(() => {
                        px => (px[3] = 255, h.isPixel(px, h.black, threshold, offsetX*2, offsetY*2)));
       return Promise.resolve()
         .then(() => info("Checking local video enabled"))
         .then(() => checkVideoEnabled(localVideo))
         .then(() => info("Checking remote video enabled"))
         .then(() => checkVideoEnabled(remoteVideo))
 
         .then(() => info("Disabling original"))
-        .then(() => test.pcLocal.streams[0].getVideoTracks()[0].enabled = false)
+        .then(() => test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false)
 
         .then(() => info("Checking local video disabled"))
         .then(() => checkVideoDisabled(localVideo))
         .then(() => info("Checking remote video disabled"))
         .then(() => checkVideoDisabled(remoteVideo))
     },
     function CHECK_AUDIO() {
       var ac = new AudioContext();
-      var localAnalyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
-      var remoteAnalyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
+      var localAnalyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
+      var remoteAnalyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
 
       var checkAudio = (analyser, fun) => analyser.waitForAnalysisSuccess(fun);
 
       var freq1k = localAnalyser.binIndexForFrequency(1000);
       var checkAudioEnabled = analyser =>
         checkAudio(analyser, array => array[freq1k] > 200);
       var checkAudioDisabled = analyser =>
         checkAudio(analyser, array => array[freq1k] < 50);
 
       return Promise.resolve()
         .then(() => info("Checking local audio enabled"))
         .then(() => checkAudioEnabled(localAnalyser))
         .then(() => info("Checking remote audio enabled"))
         .then(() => checkAudioEnabled(remoteAnalyser))
 
-        .then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
+        .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
 
         .then(() => info("Checking local audio disabled"))
         .then(() => checkAudioDisabled(localAnalyser))
         .then(() => info("Checking remote audio disabled"))
         .then(() => checkAudioDisabled(remoteAnalyser))
     }
   ]);
   test.run();
--- a/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html
+++ b/dom/media/tests/mochitest/test_peerConnection_trackDisabling_clones.html
@@ -23,36 +23,36 @@ runNetworkTest(() => {
   test.setMediaConstraints([{audio: true, video: true, fake: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
     function PC_LOCAL_GUM_CLONE() {
       return getUserMedia(test.pcLocal.constraints[0]).then(stream => {
         originalStream = stream;
         localVideoOriginal =
           createMediaElement("audiovideo", "local-original");
         localVideoOriginal.srcObject = stream;
-        test.pcLocal.attachMedia(originalStream.clone(), "audiovideo", "local");
+        test.pcLocal.attachLocalStream(originalStream.clone());
       });
     }
   ]);
   test.chain.append([
     function CHECK_ASSUMPTIONS() {
-      is(test.pcLocal.mediaElements.length, 1,
+      is(test.pcLocal.localMediaElements.length, 1,
          "pcLocal should have one media element");
-      is(test.pcRemote.mediaElements.length, 1,
+      is(test.pcRemote.remoteMediaElements.length, 1,
          "pcRemote should have one media element");
-      is(test.pcLocal.streams.length, 1,
+      is(test.pcLocal._pc.getLocalStreams().length, 1,
          "pcLocal should have one stream");
-      is(test.pcRemote.streams.length, 1,
+      is(test.pcRemote._pc.getRemoteStreams().length, 1,
          "pcRemote should have one stream");
     },
     function CHECK_VIDEO() {
       info("Checking video");
       var h = new CaptureStreamTestHelper2D();
-      var localVideoClone = test.pcLocal.mediaElements[0];
-      var remoteVideoClone = test.pcRemote.mediaElements[0];
+      var localVideoClone = test.pcLocal.localMediaElements[0];
+      var remoteVideoClone = test.pcRemote.remoteMediaElements[0];
 
       // We check a pixel somewhere away from the top left corner since
       // MediaEngineDefault puts semi-transparent time indicators there.
       const offsetX = 50;
       const offsetY = 50;
       const threshold = 128;
       const remoteDisabledColor = h.black;
 
@@ -80,33 +80,33 @@ runNetworkTest(() => {
         .then(() => checkVideoDisabled(localVideoOriginal))
         .then(() => info("Checking local clone enabled"))
         .then(() => checkVideoEnabled(localVideoClone))
         .then(() => info("Checking remote clone enabled"))
         .then(() => checkVideoEnabled(remoteVideoClone))
 
         .then(() => info("Re-enabling original; disabling clone"))
         .then(() => originalStream.getVideoTracks()[0].enabled = true)
-        .then(() => test.pcLocal.streams[0].getVideoTracks()[0].enabled = false)
+        .then(() => test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false)
 
         .then(() => info("Checking local original enabled"))
         .then(() => checkVideoEnabled(localVideoOriginal))
         .then(() => info("Checking local clone disabled"))
         .then(() => checkVideoDisabled(localVideoClone))
         .then(() => info("Checking remote clone disabled"))
         .then(() => checkVideoDisabled(remoteVideoClone))
     },
     function CHECK_AUDIO() {
       info("Checking audio");
       var ac = new AudioContext();
       var localAnalyserOriginal = new AudioStreamAnalyser(ac, originalStream);
       var localAnalyserClone =
-        new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
+        new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
       var remoteAnalyserClone =
-        new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
+        new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
 
       var freq1k = localAnalyserOriginal.binIndexForFrequency(1000);
       var checkAudioEnabled = analyser =>
         analyser.waitForAnalysisSuccess(array => array[freq1k] > 200);
       var checkAudioDisabled = analyser =>
         analyser.waitForAnalysisSuccess(array => array[freq1k] < 50);
 
       return Promise.resolve()
@@ -124,17 +124,17 @@ runNetworkTest(() => {
         .then(() => checkAudioDisabled(localAnalyserOriginal))
         .then(() => info("Checking local clone enabled"))
         .then(() => checkAudioEnabled(localAnalyserClone))
         .then(() => info("Checking remote clone enabled"))
         .then(() => checkAudioEnabled(remoteAnalyserClone))
 
         .then(() => info("Re-enabling original; disabling clone"))
         .then(() => originalStream.getAudioTracks()[0].enabled = true)
-        .then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
+        .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
 
         .then(() => info("Checking local original enabled"))
         .then(() => checkAudioEnabled(localAnalyserOriginal))
         .then(() => info("Checking local clone disabled"))
         .then(() => checkAudioDisabled(localAnalyserClone))
         .then(() => info("Checking remote clone disabled"))
         .then(() => checkAudioDisabled(remoteAnalyserClone))
     }
--- a/dom/media/tests/mochitest/test_peerConnection_verifyAudioAfterRenegotiation.html
+++ b/dom/media/tests/mochitest/test_peerConnection_verifyAudioAfterRenegotiation.html
@@ -26,38 +26,38 @@
       checkAudio(analyser, array => array[freq] < 50);
 
     var ac = new AudioContext();
     var local1Analyser;
     var remote1Analyser;
 
     test.chain.append([
       function CHECK_ASSUMPTIONS() {
-        is(test.pcLocal.mediaElements.length, 1,
-           "pcLocal should only have one media element");
-        is(test.pcRemote.mediaElements.length, 1,
-           "pcRemote should only have one media element");
-        is(test.pcLocal.streams.length, 1,
-           "pcLocal should only have one stream (the local one)");
-        is(test.pcRemote.streams.length, 1,
-           "pcRemote should only have one stream (the remote one)");
+        is(test.pcLocal.localMediaElements.length, 1,
+           "pcLocal should have one media element");
+        is(test.pcRemote.remoteMediaElements.length, 1,
+           "pcRemote should have one media element");
+        is(test.pcLocal._pc.getLocalStreams().length, 1,
+           "pcLocal should have one stream");
+        is(test.pcRemote._pc.getRemoteStreams().length, 1,
+           "pcRemote should have one stream");
       },
       function CHECK_AUDIO() {
-        local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[0]);
-        remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[0]);
+        local1Analyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
+        remote1Analyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
 
         freq = local1Analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
 
         return Promise.resolve()
           .then(() => info("Checking local audio enabled"))
           .then(() => checkAudioEnabled(local1Analyser, freq))
           .then(() => info("Checking remote audio enabled"))
           .then(() => checkAudioEnabled(remote1Analyser, freq))
 
-          .then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = false)
+          .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
 
           .then(() => info("Checking local audio disabled"))
           .then(() => checkAudioDisabled(local1Analyser, freq))
           .then(() => info("Checking remote audio disabled"))
           .then(() => checkAudioDisabled(remote1Analyser, freq))
       }
     ]);
 
@@ -68,44 +68,44 @@
                                    []);
           return test.pcLocal.getAllUserMedia([{audio: true}]);
         },
       ]
     );
 
     test.chain.append([
       function CHECK_ASSUMPTIONS2() {
-        is(test.pcLocal.mediaElements.length, 2,
+        is(test.pcLocal.localMediaElements.length, 2,
            "pcLocal should have two media elements");
-        is(test.pcRemote.mediaElements.length, 2,
+        is(test.pcRemote.remoteMediaElements.length, 2,
            "pcRemote should have two media elements");
-        is(test.pcLocal.streams.length, 2,
+        is(test.pcLocal._pc.getLocalStreams().length, 2,
            "pcLocal should have two streams");
-        is(test.pcRemote.streams.length, 2,
+        is(test.pcRemote._pc.getRemoteStreams().length, 2,
            "pcRemote should have two streams");
       },
       function RE_CHECK_AUDIO() {
-        local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal.streams[1]);
-        remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote.streams[1]);
+        local2Analyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[1]);
+        remote2Analyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[1]);
 
         freq = local2Analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
 
         return Promise.resolve()
           .then(() => info("Checking local audio disabled"))
           .then(() => checkAudioDisabled(local1Analyser, freq))
           .then(() => info("Checking remote audio disabled"))
           .then(() => checkAudioDisabled(remote1Analyser, freq))
 
           .then(() => info("Checking local2 audio enabled"))
           .then(() => checkAudioEnabled(local2Analyser, freq))
           .then(() => info("Checking remote2 audio enabled"))
           .then(() => checkAudioEnabled(remote2Analyser, freq))
 
-          .then(() => test.pcLocal.streams[1].getAudioTracks()[0].enabled = false)
-          .then(() => test.pcLocal.streams[0].getAudioTracks()[0].enabled = true)
+          .then(() => test.pcLocal._pc.getLocalStreams()[1].getAudioTracks()[0].enabled = false)
+          .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = true)
 
           .then(() => info("Checking local2 audio disabled"))
           .then(() => checkAudioDisabled(local2Analyser, freq))
           .then(() => info("Checking remote2 audio disabled"))
           .then(() => checkAudioDisabled(remote2Analyser, freq))
 
           .then(() => info("Checking local audio enabled"))
           .then(() => checkAudioEnabled(local1Analyser, freq))
--- a/dom/media/tests/mochitest/test_peerConnection_verifyVideoAfterRenegotiation.html
+++ b/dom/media/tests/mochitest/test_peerConnection_verifyVideoAfterRenegotiation.html
@@ -27,23 +27,23 @@ runNetworkTest(() => {
 
   test.setMediaConstraints([{video: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
     function DRAW_INITIAL_LOCAL_GREEN(test) {
       h1.drawColor(canvas1, h1.green);
     },
     function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
       stream1 = canvas1.captureStream(0);
-      test.pcLocal.attachMedia(stream1, 'video', 'local');
+      test.pcLocal.attachLocalStream(stream1);
     }
   ]);
 
   test.chain.append([
     function FIND_REMOTE_VIDEO() {
-      vremote1 = document.getElementById('pcRemote_remote1_video');
+      vremote1 = test.pcRemote.remoteMediaElements[0];
       ok(!!vremote1, "Should have remote video element for pcRemote");
     },
     function WAIT_FOR_REMOTE_GREEN() {
       return h1.waitForPixelColor(vremote1, h1.green, 128,
                                  "pcRemote's remote should become green");
     },
     function DRAW_LOCAL_RED() {
       // After requesting a frame it will be captured at the time of next render.
@@ -62,24 +62,24 @@ runNetworkTest(() => {
     [
       function PC_LOCAL_ADD_SECOND_STREAM(test) {
         canvas2 = h2.createAndAppendElement('canvas', 'source_canvas2');
         h2.drawColor(canvas2, h2.blue);
         stream2 = canvas2.captureStream(0);
 
         // can't use test.pcLocal.getAllUserMedia([{video: true}]);
         // because it doesn't let us substitute the capture stream
-        return test.pcLocal.attachMedia(stream2, 'video', 'local');
+        test.pcLocal.attachLocalStream(stream2);
       }
     ]
   );
 
   test.chain.append([
     function FIND_REMOTE2_VIDEO() {
-      vremote2 = document.getElementById('pcRemote_remote2_video');
+      vremote2 = test.pcRemote.remoteMediaElements[1];
       ok(!!vremote2, "Should have remote2 video element for pcRemote");
     },
     function WAIT_FOR_REMOTE2_BLUE() {
       return h2.waitForPixelColor(vremote2, h2.blue, 128,
                                  "pcRemote's remote2 should become blue");
     },
     function DRAW_NEW_LOCAL_GREEN(test) {
       stream1.requestFrame();
--- a/dom/media/tests/mochitest/test_peerConnection_webAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_webAudio.html
@@ -22,17 +22,17 @@ runNetworkTest(function() {
   test.chain.replace("PC_LOCAL_GUM", [
     function PC_LOCAL_WEBAUDIO_SOURCE(test) {
       var oscillator = test.audioContext.createOscillator();
       oscillator.type = 'sine';
       oscillator.frequency.value = 700;
       oscillator.start();
       var dest = test.audioContext.createMediaStreamDestination();
       oscillator.connect(dest);
-      test.pcLocal.attachMedia(dest.stream, 'audio', 'local');
+      test.pcLocal.attachLocalStream(dest.stream);
     }
   ]);
   test.chain.append([
     function CHECK_AUDIO_FLOW(test) {
       return test.pcRemote.checkReceivingToneFrom(test.audioContext, test.pcLocal);
     }
   ]);
   test.run();
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -510,18 +510,16 @@ public:
 protected:
   ~MediaEngineWebRTCMicrophoneSource() {
     Shutdown();
   }
 
 private:
   void Init();
 
-  void AppendSamplesToTrack(int16_t *audio10ms, int length);
-
   webrtc::VoiceEngine* mVoiceEngine;
   RefPtr<mozilla::AudioInput> mAudioInput;
   RefPtr<WebRTCAudioDataListener> mListener;
 
   ScopedCustomReleasePtr<webrtc::VoEBase> mVoEBase;
   ScopedCustomReleasePtr<webrtc::VoEExternalMedia> mVoERender;
   ScopedCustomReleasePtr<webrtc::VoENetwork> mVoENetwork;
   ScopedCustomReleasePtr<webrtc::VoEAudioProcessing> mVoEProcessing;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -490,21 +490,17 @@ MediaEngineWebRTCMicrophoneSource::Notif
     uint32_t samplesPerPacket = mPacketizer->PacketSize() *
                                 mPacketizer->Channels();
     if (mInputBufferLen < samplesPerPacket) {
       mInputBuffer = MakeUnique<int16_t[]>(samplesPerPacket);
     }
     int16_t *packet = mInputBuffer.get();
     mPacketizer->Output(packet);
 
-    if (mEchoOn || mAgcOn || mNoiseOn) {
-      mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
-    } else {
-      AppendSamplesToTrack(packet, samplesPerPacket);
-    }
+    mVoERender->ExternalRecordingInsertData(packet, samplesPerPacket, aRate, 0);
   }
 }
 
 #define ResetProcessingIfNeeded(_processing)                        \
 do {                                                                \
   webrtc::_processing##Modes mode;                                  \
   int rv = mVoEProcessing->Get##_processing##Status(enabled, mode); \
   if (rv) {                                                         \
@@ -703,26 +699,19 @@ MediaEngineWebRTCMicrophoneSource::Proce
       free(buffer);
       if (res == -1) {
         return;
       }
     }
   }
 
   MonitorAutoLock lock(mMonitor);
-  if (mState != kStarted) {
+  if (mState != kStarted)
     return;
-  }
 
-  AppendSamplesToTrack(audio10ms, length);
-}
-
-void
-MediaEngineWebRTCMicrophoneSource::AppendSamplesToTrack(sample *audio10ms, int length)
-{
   uint32_t len = mSources.Length();
   for (uint32_t i = 0; i < len; i++) {
     RefPtr<SharedBuffer> buffer = SharedBuffer::Create(length * sizeof(sample));
 
     sample* dest = static_cast<sample*>(buffer->Data());
     memcpy(dest, audio10ms, length * sizeof(sample));
 
     nsAutoPtr<AudioSegment> segment(new AudioSegment());
@@ -743,16 +732,18 @@ MediaEngineWebRTCMicrophoneSource::Appen
       // or Destroyed.
       // Note: due to evil magic, the nsAutoPtr<AudioSegment>'s ownership transfers to
       // the Runnable (AutoPtr<> = AutoPtr<>)
       RUN_ON_THREAD(mThread, WrapRunnable(mSources[i], &SourceMediaStream::AppendToTrack,
                                           mTrackID, segment, (AudioSegment *) nullptr),
                     NS_DISPATCH_NORMAL);
     }
   }
+
+  return;
 }
 
 void
 MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName)
 {
   aName.AssignLiteral("AudioCapture");
 }
 
--- a/dom/push/PushComponents.js
+++ b/dom/push/PushComponents.js
@@ -24,17 +24,17 @@ XPCOMUtils.defineLazyGetter(this, "PushS
   return PushService;
 });
 
 // Observer notification topics for push messages and subscription status
 // changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
 // on `nsIPushService` so that JS callers only need to import this service.
 const OBSERVER_TOPIC_PUSH = "push-message";
 const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
-const OBSERVER_TOPIC_SUBSCRIPTION_LOST = "push-subscription-lost";
+const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";
 
 /**
  * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
  * implement the `nsIPushService` interface. This interface provides calls
  * similar to the Push DOM API, but does not require service workers.
  *
  * Push service methods may be called from the parent or content process. The
  * parent process implementation loads `PushService.jsm` at app startup, and
@@ -56,17 +56,17 @@ PushServiceBase.prototype = {
     Ci.nsISupportsWeakReference,
     Ci.nsIPushService,
     Ci.nsIPushQuotaManager,
     Ci.nsIPushErrorReporter,
   ]),
 
   pushTopic: OBSERVER_TOPIC_PUSH,
   subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
-  subscriptionLostTopic: OBSERVER_TOPIC_SUBSCRIPTION_LOST,
+  subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
 
   _handleReady() {},
 
   _addListeners() {
     for (let message of this._messages) {
       this._mm.addMessageListener(message, this);
     }
   },
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -2,18 +2,16 @@
  * 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 "PushNotifier.h"
 
 #include "nsContentUtils.h"
 #include "nsCOMPtr.h"
 #include "nsICategoryManager.h"
-#include "nsIPushErrorReporter.h"
-#include "nsISupportsPrimitives.h"
 #include "nsIXULRuntime.h"
 #include "nsNetUtil.h"
 #include "nsXPCOM.h"
 #include "ServiceWorkerManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 
@@ -65,54 +63,49 @@ PushNotifier::NotifyPush(const nsACStrin
 {
   return NotifyPush(aScope, aPrincipal, aMessageId, Nothing());
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
                                        nsIPrincipal* aPrincipal)
 {
-  nsresult rv = NotifySubscriptionChangeObservers(aScope);
+  nsresult rv = NotifySubscriptionChangeObservers(aScope, aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   if (XRE_IsContentProcess()) {
     // Forward XPCOM observer notifications to the parent.
     ContentChild* parentActor = ContentChild::GetSingleton();
     if (!NS_WARN_IF(!parentActor)) {
       Unused << NS_WARN_IF(
         !parentActor->SendNotifyPushSubscriptionChangeObservers(
-          PromiseFlatCString(aScope)));
+          PromiseFlatCString(aScope), IPC::Principal(aPrincipal)));
     }
   }
 
   rv = NotifySubscriptionChangeWorkers(aScope, aPrincipal);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PushNotifier::NotifySubscriptionLost(const nsACString& aScope,
-                                     nsIPrincipal* aPrincipal,
-                                     uint16_t aReason)
+PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
+                                         nsIPrincipal* aPrincipal)
 {
-  if (NS_WARN_IF(aReason < nsIPushErrorReporter::UNSUBSCRIBE_MANUAL ||
-                 aReason > nsIPushErrorReporter::UNSUBSCRIBE_PERMISSION_REVOKED)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-  nsresult rv = NotifySubscriptionLostObservers(aScope, aReason);
+  nsresult rv = NotifySubscriptionModifiedObservers(aScope, aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   if (XRE_IsContentProcess()) {
     ContentChild* parentActor = ContentChild::GetSingleton();
     if (!NS_WARN_IF(!parentActor)) {
       Unused << NS_WARN_IF(
-        !parentActor->SendNotifyPushSubscriptionLostObservers(
-          PromiseFlatCString(aScope), aReason));
+        !parentActor->SendNotifyPushSubscriptionModifiedObservers(
+          PromiseFlatCString(aScope), IPC::Principal(aPrincipal)));
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
@@ -136,34 +129,35 @@ PushNotifier::NotifyError(const nsACStri
 }
 
 nsresult
 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
                          const nsAString& aMessageId,
                          const Maybe<nsTArray<uint8_t>>& aData)
 {
   // Notify XPCOM observers in the current process.
-  nsresult rv = NotifyPushObservers(aScope, aData);
+  nsresult rv = NotifyPushObservers(aScope, aPrincipal, aData);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   if (XRE_IsContentProcess()) {
     // If we're in the content process, forward the notification to the parent.
     // We don't need to do anything if we're already in the parent;
     // `ContentChild::RecvPush` will notify content process observers.
     ContentChild* parentActor = ContentChild::GetSingleton();
     if (!NS_WARN_IF(!parentActor)) {
       if (aData) {
         Unused << NS_WARN_IF(
           !parentActor->SendNotifyPushObserversWithData(
-            PromiseFlatCString(aScope), PromiseFlatString(aMessageId),
-            aData.ref()));
+            PromiseFlatCString(aScope), IPC::Principal(aPrincipal),
+            PromiseFlatString(aMessageId), aData.ref()));
       } else {
         Unused << NS_WARN_IF(
           !parentActor->SendNotifyPushObservers(
-            PromiseFlatCString(aScope), PromiseFlatString(aMessageId)));
+            PromiseFlatCString(aScope), IPC::Principal(aPrincipal),
+            PromiseFlatString(aMessageId)));
       }
     }
   }
 
   rv = NotifyPushWorkers(aScope, aPrincipal, aMessageId, aData);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -172,19 +166,17 @@ PushNotifier::NotifyPush(const nsACStrin
 
 nsresult
 PushNotifier::NotifyPushWorkers(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal,
                                 const nsAString& aMessageId,
                                 const Maybe<nsTArray<uint8_t>>& aData)
 {
   AssertIsOnMainThread();
-  if (!aPrincipal) {
-    return NS_ERROR_INVALID_ARG;
-  }
+  NS_ENSURE_ARG(aPrincipal);
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Notify the worker from the current process. Either we're running in
     // the content process and received a message from the parent, or e10s
     // is disabled.
     if (!ShouldNotifyWorkers(aPrincipal)) {
       return NS_OK;
     }
@@ -217,19 +209,17 @@ PushNotifier::NotifyPushWorkers(const ns
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                               nsIPrincipal* aPrincipal)
 {
   AssertIsOnMainThread();
-  if (!aPrincipal) {
-    return NS_ERROR_INVALID_ARG;
-  }
+  NS_ENSURE_ARG(aPrincipal);
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Content process or e10s disabled.
     if (!ShouldNotifyWorkers(aPrincipal)) {
       return NS_OK;
     }
     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     if (!swm) {
@@ -303,41 +293,41 @@ PushNotifier::NotifyErrorWorkers(const n
                                                 EmptyString(), /* aLine */
                                                 0, /* aLineNumber */
                                                 0, /* aColumnNumber */
                                                 nsContentUtils::eOMIT_LOCATION)));
 }
 
 nsresult
 PushNotifier::NotifyPushObservers(const nsACString& aScope,
+                                  nsIPrincipal* aPrincipal,
                                   const Maybe<nsTArray<uint8_t>>& aData)
 {
-  nsCOMPtr<nsIPushMessage> message = nullptr;
+  nsCOMPtr<nsIPushData> data;
   if (aData) {
-    message = new PushMessage(aData.ref());
+    data = new PushData(aData.ref());
   }
+  nsCOMPtr<nsIPushMessage> message = new PushMessage(aPrincipal, data);
   return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, aScope);
 }
 
 nsresult
-PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope)
+PushNotifier::NotifySubscriptionChangeObservers(const nsACString& aScope,
+                                                nsIPrincipal* aPrincipal)
 {
-  return DoNotifyObservers(nullptr, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE, aScope);
+  return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
+                           aScope);
 }
 
 nsresult
-PushNotifier::NotifySubscriptionLostObservers(const nsACString& aScope,
-                                              uint16_t aReason)
+PushNotifier::NotifySubscriptionModifiedObservers(const nsACString& aScope,
+                                                  nsIPrincipal* aPrincipal)
 {
-  nsCOMPtr<nsISupportsPRUint16> wrapper =
-    do_CreateInstance(NS_SUPPORTS_PRUINT16_CONTRACTID);
-  if (NS_WARN_IF(!wrapper || NS_FAILED(wrapper->SetData(aReason)))) {
-    return NS_ERROR_FAILURE;
-  }
-  return DoNotifyObservers(wrapper, OBSERVER_TOPIC_SUBSCRIPTION_LOST, aScope);
+  return DoNotifyObservers(aPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
+                           aScope);
 }
 
 nsresult
 PushNotifier::DoNotifyObservers(nsISupports *aSubject, const char *aTopic,
                                 const nsACString& aScope)
 {
   nsCOMPtr<nsIObserverService> obsService =
     mozilla::services::GetObserverService();
@@ -367,35 +357,35 @@ PushNotifier::ShouldNotifyWorkers(nsIPri
 {
   // System subscriptions use observer notifications instead of service worker
   // events. The `testing.notifyWorkers` pref disables worker events for
   // non-system subscriptions.
   return !nsContentUtils::IsSystemPrincipal(aPrincipal) &&
          Preferences::GetBool("dom.push.testing.notifyWorkers", true);
 }
 
-PushMessage::PushMessage(const nsTArray<uint8_t>& aData)
+PushData::PushData(const nsTArray<uint8_t>& aData)
   : mData(aData)
 {}
 
-PushMessage::~PushMessage()
+PushData::~PushData()
 {}
 
-NS_IMPL_CYCLE_COLLECTION_0(PushMessage)
+NS_IMPL_CYCLE_COLLECTION_0(PushData)
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
-  NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData)
+  NS_INTERFACE_MAP_ENTRY(nsIPushData)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData)
 
 nsresult
-PushMessage::EnsureDecodedText()
+PushData::EnsureDecodedText()
 {
   if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
     return NS_OK;
   }
   nsresult rv = BodyUtil::ConsumeText(
     mData.Length(),
     reinterpret_cast<uint8_t*>(mData.Elements()),
     mDecodedText
@@ -403,55 +393,93 @@ PushMessage::EnsureDecodedText()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mDecodedText.Truncate();
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PushMessage::Text(nsAString& aText)
+PushData::Text(nsAString& aText)
 {
   nsresult rv = EnsureDecodedText();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   aText = mDecodedText;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PushMessage::Json(JSContext* aCx,
-                  JS::MutableHandle<JS::Value> aResult)
+PushData::Json(JSContext* aCx,
+               JS::MutableHandle<JS::Value> aResult)
 {
   nsresult rv = EnsureDecodedText();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   ErrorResult error;
   BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
   return error.StealNSResult();
 }
 
 NS_IMETHODIMP
-PushMessage::Binary(uint32_t* aDataLen, uint8_t** aData)
+PushData::Binary(uint32_t* aDataLen, uint8_t** aData)
 {
-  if (!aDataLen || !aData) {
-    return NS_ERROR_INVALID_ARG;
-  }
+  NS_ENSURE_ARG_POINTER(aDataLen);
+  NS_ENSURE_ARG_POINTER(aData);
+
   *aData = nullptr;
   if (mData.IsEmpty()) {
     *aDataLen = 0;
     return NS_OK;
   }
   uint32_t length = mData.Length();
   uint8_t* data = static_cast<uint8_t*>(NS_Alloc(length * sizeof(uint8_t)));
   if (!data) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   memcpy(data, mData.Elements(), length * sizeof(uint8_t));
   *aDataLen = length;
   *aData = data;
   return NS_OK;
 }
 
+PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
+  : mPrincipal(aPrincipal)
+  , mData(aData)
+{}
+
+PushMessage::~PushMessage()
+{}
+
+NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
+  NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
+
+NS_IMETHODIMP
+PushMessage::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+  NS_ENSURE_ARG_POINTER(aPrincipal);
+
+  nsCOMPtr<nsIPrincipal> principal = mPrincipal;
+  principal.forget(aPrincipal);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PushMessage::GetData(nsIPushData** aData)
+{
+  NS_ENSURE_ARG_POINTER(aData);
+
+  nsCOMPtr<nsIPushData> data = mData;
+  data.forget(aData);
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -38,35 +38,54 @@ private:
                       const nsAString& aMessageId,
                       const Maybe<nsTArray<uint8_t>>& aData);
   nsresult DoNotifyObservers(nsISupports *aSubject, const char *aTopic,
                              const nsACString& aScope);
   bool ShouldNotifyWorkers(nsIPrincipal* aPrincipal);
 };
 
 /**
- * `PushMessage` implements the `nsIPushMessage` interface, similar to
- * the `PushMessageData` WebIDL interface. Instances of this class are
- * passed as the subject of `push-message` observer notifications.
+ * `PushData` provides methods for retrieving push message data in different
+ * formats. This class is similar to the `PushMessageData` WebIDL interface.
  */
-class PushMessage final : public nsIPushMessage
+class PushData final : public nsIPushData
 {
 public:
-  explicit PushMessage(const nsTArray<uint8_t>& aData);
+  explicit PushData(const nsTArray<uint8_t>& aData);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushMessage,
-                                           nsIPushMessage)
-  NS_DECL_NSIPUSHMESSAGE
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushData, nsIPushData)
+  NS_DECL_NSIPUSHDATA
 
 private:
-  virtual ~PushMessage();
+  virtual ~PushData();
 
   nsresult EnsureDecodedText();
 
   nsTArray<uint8_t> mData;
   nsString mDecodedText;
 };
 
+/**
+ * `PushMessage` exposes the subscription principal and data for a push
+ * message. Each `push-message` observer receives an instance of this class
+ * as the subject.
+ */
+class PushMessage final : public nsIPushMessage
+{
+public:
+  PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData);
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushMessage, nsIPushMessage)
+  NS_DECL_NSIPUSHMESSAGE
+
+private:
+  virtual ~PushMessage();
+
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsIPushData> mData;
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PushNotifier_h
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -352,18 +352,17 @@ this.PushService = {
     console.debug("backgroundUnregister()");
 
     if (!this._service.isConnected() || !record) {
       return;
     }
 
     console.debug("backgroundUnregister: Notifying server", record);
     this._sendUnregister(record, reason).then(() => {
-      gPushNotifier.notifySubscriptionLost(record.scope, record.principal,
-                                           reason);
+      gPushNotifier.notifySubscriptionModified(record.scope, record.principal);
     }).catch(e => {
       console.error("backgroundUnregister: Error notifying server", e);
     });
   },
 
   // utility function used to add/remove observers in startObservers() and
   // stopObservers()
   getNetworkStateChangeEventName: function() {
@@ -843,16 +842,19 @@ this.PushService = {
           // the quota.
           if (newRecord.isExpired()) {
             return null;
           }
           newRecord.receivedPush(lastVisit);
           return newRecord;
         });
       });
+    }).then(record => {
+      gPushNotifier.notifySubscriptionModified(record.scope, record.principal);
+      return record;
     });
   },
 
   /**
    * Decrypts an incoming message and notifies the associated service worker.
    *
    * @param {PushRecord} record The receiving registration.
    * @param {String} messageID The message ID.
@@ -894,23 +896,28 @@ this.PushService = {
       }
       // If there are visible notifications, don't apply the quota penalty
       // for the message.
       if (record.uri && !this._visibleNotifications.has(record.uri.prePath)) {
         record.reduceQuota();
       }
       return record;
     }).then(record => {
-      if (record && record.isExpired()) {
-        this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
-        // Drop the registration in the background. If the user returns to the
-        // site, the service worker will be notified on the next `idle-daily`
-        // event.
-        this._backgroundUnregister(record,
-          Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
+      if (record) {
+        if (record.isExpired()) {
+          this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
+          // Drop the registration in the background. If the user returns to the
+          // site, the service worker will be notified on the next `idle-daily`
+          // event.
+          this._backgroundUnregister(record,
+            Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
+        } else {
+          gPushNotifier.notifySubscriptionModified(record.scope,
+                                                   record.principal);
+        }
       }
       if (this._updateQuotaTestCallback) {
         // Callback so that test may be notified when the quota update is complete.
         this._updateQuotaTestCallback();
       }
     }).catch(error => {
       console.debug("updateQuota: Error while trying to update quota", error);
     });
@@ -1027,16 +1034,18 @@ this.PushService = {
     console.debug("registerWithServer()", aPageRecord);
 
     Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_ATTEMPT").add();
     return this._sendRequest("register", aPageRecord)
       .then(record => this._onRegisterSuccess(record),
             err => this._onRegisterError(err))
       .then(record => {
         this._deletePendingRequest(aPageRecord);
+        gPushNotifier.notifySubscriptionModified(record.scope,
+                                                 record.principal);
         return record.toSubscription();
       }, err => {
         this._deletePendingRequest(aPageRecord);
         throw err;
      });
   },
 
   _sendUnregister(aRecord, aReason) {
@@ -1174,18 +1183,18 @@ this.PushService = {
           return false;
         }
 
         let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL;
         return Promise.all([
           this._sendUnregister(record, reason),
           this._db.delete(record.keyID),
         ]).then(() => {
-          gPushNotifier.notifySubscriptionLost(record.scope, record.principal,
-                                               reason);
+          gPushNotifier.notifySubscriptionModified(record.scope,
+                                                   record.principal);
           return true;
         });
       });
   },
 
   clear: function(info) {
     if (info.domain == "*") {
       return this._clearAll();
@@ -1279,39 +1288,53 @@ this.PushService = {
       ));
     });
   },
 
   _onPermissionChange: function(subject, data) {
     console.debug("onPermissionChange()");
 
     if (data == "cleared") {
-      // If the permission list was cleared, drop all registrations
-      // that are subject to quota.
-      return this._db.clearIf(record => {
-        if (record.quotaApplies()) {
-          if (!record.isExpired()) {
-            // Drop the registration in the background.
-            this._backgroundUnregister(record,
-              Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
-          }
-          return true;
-        }
-        return false;
-      });
+      return this._clearPermissions();
     }
 
     let permission = subject.QueryInterface(Ci.nsIPermission);
     if (permission.type != "desktop-notification") {
       return Promise.resolve();
     }
 
     return this._updatePermission(permission, data);
   },
 
+  _clearPermissions() {
+    console.debug("clearPermissions()");
+
+    let subscriptionModifications = [];
+    return this._db.clearIf(record => {
+      if (!record.quotaApplies()) {
+        // Only drop registrations that are subject to quota.
+        return false;
+      }
+      if (record.isExpired()) {
+        // Fire subscription modified notifications for expired
+        // records after the IndexedDB transaction has committed.
+        subscriptionModifications.push(record);
+      } else {
+        // Drop unexpired registrations in the background.
+        this._backgroundUnregister(record,
+          Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
+      }
+      return true;
+    }).then(() => {
+      subscriptionModifications.forEach(record =>
+        gPushNotifier.notifySubscriptionModified(record.scope, record.principal)
+      );
+    });
+  },
+
   _updatePermission: function(permission, type) {
     console.debug("updatePermission()");
 
     let isAllow = permission.capability ==
                   Ci.nsIPermissionManager.ALLOW_ACTION;
     let isChange = type == "added" || type == "changed";
 
     if (isAllow && isChange) {
--- a/dom/push/test/xpcshell/PushServiceHandler.js
+++ b/dom/push/test/xpcshell/PushServiceHandler.js
@@ -11,17 +11,17 @@ let pushService = Cc["@mozilla.org/push/
 
 function PushServiceHandler() {
   // So JS code can reach into us.
   this.wrappedJSObject = this;
   // Register a push observer.
   this.observed = [];
   Services.obs.addObserver(this, pushService.pushTopic, false);
   Services.obs.addObserver(this, pushService.subscriptionChangeTopic, false);
-  Services.obs.addObserver(this, pushService.subscriptionLostTopic, false);
+  Services.obs.addObserver(this, pushService.subscriptionModifiedTopic, false);
 }
 
 PushServiceHandler.prototype = {
   classID: Components.ID("{bb7c5199-c0f7-4976-9f6d-1306e32c5591}"),
   QueryInterface: XPCOMUtils.generateQI([]),
 
   observe(subject, topic, data) {
     this.observed.push({ subject, topic, data });
--- a/dom/push/test/xpcshell/test_handler_service.js
+++ b/dom/push/test/xpcshell/test_handler_service.js
@@ -19,27 +19,29 @@ add_test(function test_service_instantia
 
   // Now get a handle to our service and check it received the notification.
   let handlerService = Cc[kServiceContractID]
                        .getService(Ci.nsISupports)
                        .wrappedJSObject;
 
   equal(handlerService.observed.length, 1);
   equal(handlerService.observed[0].topic, pushService.pushTopic);
+  let message = handlerService.observed[0].subject.QueryInterface(Ci.nsIPushMessage);
+  equal(message.principal, principal);
+  strictEqual(message.data, null);
   equal(handlerService.observed[0].data, scope);
 
   // and a subscription change.
   pushNotifier.notifySubscriptionChange(scope, principal);
   equal(handlerService.observed.length, 2);
   equal(handlerService.observed[1].topic, pushService.subscriptionChangeTopic);
+  equal(handlerService.observed[1].subject, principal);
   equal(handlerService.observed[1].data, scope);
 
-  // and a subscription lost event.
-  let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED;
-  pushNotifier.notifySubscriptionLost(scope, principal, reason);
+  // and a subscription modified event.
+  pushNotifier.notifySubscriptionModified(scope, principal);
   equal(handlerService.observed.length, 3);
-  equal(handlerService.observed[2].topic, pushService.subscriptionLostTopic);
-  let wrapper = handlerService.observed[2].subject.QueryInterface(Ci.nsISupportsPRUint16);
-  equal(wrapper.data, reason);
+  equal(handlerService.observed[2].topic, pushService.subscriptionModifiedTopic);
+  equal(handlerService.observed[2].subject, principal);
   equal(handlerService.observed[2].data, scope);
 
   run_next_test();
 });
--- a/dom/push/test/xpcshell/test_handler_service_parent.js
+++ b/dom/push/test/xpcshell/test_handler_service_parent.js
@@ -7,31 +7,44 @@ function run_test() {
 
 add_task(function* test_observer_notifications() {
   // Push observer notifications dispatched in the child should be forwarded to
   // the parent.
   let notifyPromise = promiseObserverNotification(
     PushServiceComponent.pushTopic);
   let subChangePromise = promiseObserverNotification(
     PushServiceComponent.subscriptionChangeTopic);
-  let subLostPromise = promiseObserverNotification(
-    PushServiceComponent.subscriptionLostTopic);
+  let subModifiedPromise = promiseObserverNotification(
+    PushServiceComponent.subscriptionModifiedTopic);
 
   yield run_test_in_child('./test_handler_service.js');
 
-  let {data: notifyScope} = yield notifyPromise;
+  let principal = Services.scriptSecurityManager.getSystemPrincipal();
+
+  let {
+    data: notifyScope,
+    subject: notifySubject,
+  } = yield notifyPromise;
   equal(notifyScope, 'chrome://test-scope',
     'Should forward push notifications with the correct scope');
-
-  let {data: subChangeScope} = yield subChangePromise;
-  equal(subChangeScope, 'chrome://test-scope',
-    'Should forward subscription change notifications with the correct scope');
+  let message = notifySubject.QueryInterface(Ci.nsIPushMessage);
+  equal(message.principal, principal,
+    'Should include the principal in the push message');
+  strictEqual(message.data, null, 'Should not include data');
 
   let {
-    subject: subLostSubject,
-    data: subLostScope
-  } = yield subLostPromise;
-  equal(subLostScope, 'chrome://test-scope',
-    'Should forward subscription lost notifications with the correct scope');
-  let wrapper = subLostSubject.QueryInterface(Ci.nsISupportsPRUint16);
-  equal(wrapper.data, Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED,
-    'Should forward subscription lost reasons');
+    data: subChangeScope,
+    subject: subChangePrincipal,
+  } = yield subChangePromise;
+  equal(subChangeScope, 'chrome://test-scope',
+    'Should forward subscription change notifications with the correct scope');
+  equal(subChangePrincipal, principal,
+    'Should pass the principal as the subject of a change notification');
+
+  let {
+    data: subModifiedScope,
+    subject: subModifiedPrincipal,
+  } = yield subModifiedPromise;
+  equal(subModifiedScope, 'chrome://test-scope',
+    'Should forward subscription modified notifications with the correct scope');
+  equal(subModifiedPrincipal, principal,
+    'Should pass the principal as the subject of a modified notification');
 });
--- a/dom/push/test/xpcshell/test_notification_data.js
+++ b/dom/push/test/xpcshell/test_notification_data.js
@@ -240,17 +240,17 @@ add_task(function* test_notification_ack
       },
       ackCode: 101,
       receive: null,
     },
   ];
 
   let sendAndReceive = testData => {
     let messageReceived = testData.receive ? promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
-      let notification = subject.QueryInterface(Ci.nsIPushMessage);
+      let notification = subject.QueryInterface(Ci.nsIPushMessage).data;
       equal(notification.text(), testData.receive.data,
             'Check data for notification ' + testData.version);
       equal(data, testData.receive.scope,
             'Check scope for notification ' + testData.version);
       return true;
     }) : Promise.resolve();
 
     let ackReceived = new Promise(resolve => ackDone = resolve)
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -145,38 +145,38 @@ add_task(function* test_pushNotification
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   let notifyPromise = Promise.all([
     promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
-      var message = subject.QueryInterface(Ci.nsIPushMessage);
+      var message = subject.QueryInterface(Ci.nsIPushMessage).data;
       if (message && (data == "https://example.com/page/1")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     }),
     promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
-      var message = subject.QueryInterface(Ci.nsIPushMessage);
+      var message = subject.QueryInterface(Ci.nsIPushMessage).data;
       if (message && (data == "https://example.com/page/2")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     }),
     promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
-      var message = subject.QueryInterface(Ci.nsIPushMessage);
+      var message = subject.QueryInterface(Ci.nsIPushMessage).data;
       if (message && (data == "https://example.com/page/3")){
         equal(message.text(), "Some message", "decoded message is incorrect");
         return true;
       }
     }),
     promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) {
-      var message = subject.QueryInterface(Ci.nsIPushMessage);
+      var message = subject.QueryInterface(Ci.nsIPushMessage).data;
       if (message && (data == "https://example.com/page/4")){
         equal(message.text(), "Yet another message", "decoded message is incorrect");
         return true;
       }
     }),
   ]);
 
   PushService.init({
--- a/dom/push/test/xpcshell/test_notification_version_string.js
+++ b/dom/push/test/xpcshell/test_notification_version_string.js
@@ -51,18 +51,19 @@ add_task(function* test_notification_ver
             }]
           }));
         },
         onACK: ackDone
       });
     }
   });
 
-  let {subject: notification, data: scope} = yield notifyPromise;
-  equal(notification, null, 'Unexpected data for Simple Push message');
+  let {subject: message, data: scope} = yield notifyPromise;
+  equal(message.QueryInterface(Ci.nsIPushMessage).data, null,
+    'Unexpected data for Simple Push message');
 
   yield ackPromise;
 
   let storeRecord = yield db.getByKeyID(
     '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b');
   strictEqual(storeRecord.version, 4, 'Wrong record version');
   equal(storeRecord.quota, Infinity, 'Wrong quota');
 });
--- a/dom/push/test/xpcshell/test_observer_data.js
+++ b/dom/push/test/xpcshell/test_observer_data.js
@@ -5,36 +5,38 @@ var pushNotifier = Cc['@mozilla.org/push
 var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function* test_notifyWithData() {
   let textData = '{"hello":"world"}';
-  let data = new TextEncoder('utf-8').encode(textData);
+  let payload = new TextEncoder('utf-8').encode(textData);
 
   let notifyPromise =
     promiseObserverNotification(PushServiceComponent.pushTopic);
   pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal,
-    '' /* messageId */, data.length, data);
+    '' /* messageId */, payload.length, payload);
 
-  let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage);
-  deepEqual(message.json(), {
+  let data = (yield notifyPromise).subject.QueryInterface(
+    Ci.nsIPushMessage).data;
+  deepEqual(data.json(), {
     hello: 'world',
   }, 'Should extract JSON values');
-  deepEqual(message.binary(), Array.from(data),
+  deepEqual(data.binary(), Array.from(payload),
     'Should extract raw binary data');
-  equal(message.text(), textData, 'Should extract text data');
+  equal(data.text(), textData, 'Should extract text data');
 });
 
 add_task(function* test_empty_notifyWithData() {
   let notifyPromise =
     promiseObserverNotification(PushServiceComponent.pushTopic);
   pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal,
     '' /* messageId */, 0, null);
 
-  let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage);
-  throws(_ => message.json(),
+  let data = (yield notifyPromise).subject.QueryInterface(
+    Ci.nsIPushMessage).data;
+  throws(_ => data.json(),
     'Should throw an error when parsing an empty string as JSON');
-  strictEqual(message.text(), '', 'Should return an empty string');
-  deepEqual(message.binary(), [], 'Should return an empty array');
+  strictEqual(data.text(), '', 'Should return an empty string');
+  deepEqual(data.binary(), [], 'Should return an empty array');
 });
--- a/dom/push/test/xpcshell/test_permissions.js
+++ b/dom/push/test/xpcshell/test_permissions.js
@@ -45,25 +45,35 @@ function makePushPermission(url, capabil
     expireType: Ci.nsIPermissionManager.EXPIRE_NEVER,
     principal: Services.scriptSecurityManager.getCodebasePrincipal(
       Services.io.newURI(url, null, null)
     ),
     type: 'desktop-notification',
   };
 }
 
-function promiseSubscriptionChanges(count) {
+function promiseObserverNotifications(topic, count) {
   let notifiedScopes = [];
-  let subChangePromise = promiseObserverNotification(PushServiceComponent.subscriptionChangeTopic, (subject, data) => {
+  let subChangePromise = promiseObserverNotification(topic, (subject, data) => {
     notifiedScopes.push(data);
     return notifiedScopes.length == count;
   });
   return subChangePromise.then(_ => notifiedScopes.sort());
 }
 
+function promiseSubscriptionChanges(count) {
+  return promiseObserverNotifications(
+    PushServiceComponent.subscriptionChangeTopic, count);
+}
+
+function promiseSubscriptionModifications(count) {
+  return promiseObserverNotifications(
+    PushServiceComponent.subscriptionModifiedTopic, count);
+}
+
 function allExpired(...keyIDs) {
   return Promise.all(keyIDs.map(
     keyID => db.getByKeyID(keyID)
   )).then(records =>
     records.every(record => record.isExpired())
   );
 }
 
@@ -145,45 +155,60 @@ add_task(function* test_permissions_allo
   equal(record.quota, 16,
     'Should reset quota for active records after adding allow');
 
   record = yield db.getByKeyID('expired-allow');
   ok(!record, 'Should drop expired records after adding allow');
 });
 
 add_task(function* test_permissions_allow_deleted() {
+  let subModifiedPromise = promiseSubscriptionModifications(1);
+
   let unregisterPromise = new Promise(resolve => unregisterDefers[
     'active-allow'] = resolve);
 
   yield PushService._onPermissionChange(
     makePushPermission('https://example.info', 'ALLOW_ACTION'),
     'deleted'
   );
 
   yield unregisterPromise;
 
+  let notifiedScopes = yield subModifiedPromise;
+  deepEqual(notifiedScopes, [
+    'https://example.info/page/1',
+  ], 'Wrong scopes modified after deleting allow');
+
   let record = yield db.getByKeyID('active-allow');
   ok(record.isExpired(),
     'Should expire active record after deleting allow');
 });
 
 add_task(function* test_permissions_deny_added() {
+  let subModifiedPromise = promiseSubscriptionModifications(2);
+
   let unregisterPromise = Promise.all([
     new Promise(resolve => unregisterDefers[
       'active-deny-added-1'] = resolve),
     new Promise(resolve => unregisterDefers[
       'active-deny-added-2'] = resolve),
   ]);
 
   yield PushService._onPermissionChange(
     makePushPermission('https://example.net', 'DENY_ACTION'),
     'added'
   );
   yield unregisterPromise;
 
+  let notifiedScopes = yield subModifiedPromise;
+  deepEqual(notifiedScopes, [
+    'https://example.net/green',
+    'https://example.net/ham',
+  ], 'Wrong scopes modified after adding deny');
+
   let isExpired = yield allExpired(
     'active-deny-added-1',
     'expired-deny-added'
   );
   ok(isExpired, 'Should expire all registrations after adding deny');
 });
 
 add_task(function* test_permissions_deny_deleted() {
@@ -220,44 +245,60 @@ add_task(function* test_permissions_allo
     db.getByKeyID('active-deny-added-2'),
     db.getByKeyID('expired-deny-added'),
   ]);
   ok(!droppedRecords.some(Boolean),
     'Should drop all expired registrations after changing to allow');
 });
 
 add_task(function* test_permissions_deny_changed() {
+  let subModifiedPromise = promiseSubscriptionModifications(1);
+
   let unregisterPromise = new Promise(resolve => unregisterDefers[
     'active-deny-changed'] = resolve);
 
   yield PushService._onPermissionChange(
     makePushPermission('https://example.xyz', 'DENY_ACTION'),
     'changed'
   );
 
   yield unregisterPromise;
 
+  let notifiedScopes = yield subModifiedPromise;
+  deepEqual(notifiedScopes, [
+    'https://example.xyz/page/1',
+  ], 'Wrong scopes modified after changing to deny');
+
   let record = yield db.getByKeyID('active-deny-changed');
   ok(record.isExpired(),
-    'Should expire active record after changing to allow');
+    'Should expire active record after changing to deny');
 });
 
 add_task(function* test_permissions_clear() {
+  let subModifiedPromise = promiseSubscriptionModifications(3);
+
   let records = yield db.getAllKeyIDs();
   deepEqual(records.map(record => record.keyID).sort(), [
     'active-allow',
     'active-deny-changed',
     'drop-on-clear',
     'never-expires',
   ], 'Wrong records in database before clearing');
 
   let unregisterPromise = new Promise(resolve => unregisterDefers[
       'drop-on-clear'] = resolve);
 
   yield PushService._onPermissionChange(null, 'cleared');
 
   yield unregisterPromise;
 
+  let notifiedScopes = yield subModifiedPromise;
+  deepEqual(notifiedScopes, [
+    'https://example.edu/lonely',
+    'https://example.info/page/1',
+    'https://example.xyz/page/1',
+  ], 'Wrong scopes modified after clearing registrations');
+
   records = yield db.getAllKeyIDs();
   deepEqual(records.map(record => record.keyID).sort(), [
     'never-expires',
   ], 'Unrestricted registrations should not be dropped');
 });
--- a/dom/push/test/xpcshell/test_quota_with_notification.js
+++ b/dom/push/test/xpcshell/test_quota_with_notification.js
@@ -53,16 +53,24 @@ add_task(function* test_expiration_origi
   let numMessages = 10;
 
   let updates = 0;
   let notifyPromise = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
     updates++;
     return updates == numMessages;
   });
 
+  let modifications = 0;
+  let modifiedPromise = promiseObserverNotification(PushServiceComponent.subscriptionModifiedTopic, (subject, data) => {
+    // Each subscription should be modified twice: once to update the message
+    // count and last push time, and the second time to update the quota.
+    modifications++;
+    return modifications == numMessages * 2;
+  });
+
   let updateQuotaPromise = new Promise((resolve, reject) => {
     let quotaUpdateCount = 0;
     PushService._updateQuotaTestCallback = function() {
       quotaUpdateCount++;
       if (quotaUpdateCount == 10) {
         resolve();
       }
     };
@@ -100,12 +108,13 @@ add_task(function* test_expiration_origi
         onACK(request) {},
       });
     },
   });
 
   yield notifyPromise;
 
   yield updateQuotaPromise;
+  yield modifiedPromise;
 
   let expiredRecord = yield db.getByKeyID('f56645a9-1f32-4655-92ad-ddc37f6d54fb');
   notStrictEqual(expiredRecord.quota, 0, 'Expired record not updated');
 });
--- a/dom/push/test/xpcshell/test_register_success.js
+++ b/dom/push/test/xpcshell/test_register_success.js
@@ -47,24 +47,31 @@ add_task(function* test_register_success
             uaid: userAgentID,
             pushEndpoint: 'https://example.com/update/1',
           }));
         }
       });
     }
   });
 
+  let subModifiedPromise = promiseObserverNotification(
+    PushServiceComponent.subscriptionModifiedTopic);
+
   let newRecord = yield PushService.register({
     scope: 'https://example.org/1',
     originAttributes: ChromeUtils.originAttributesToSuffix(
       { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   equal(newRecord.endpoint, 'https://example.com/update/1',
     'Wrong push endpoint in registration record');
 
+  let {data: subModifiedScope} = yield subModifiedPromise;
+  equal(subModifiedScope, 'https://example.org/1',
+    'Should fire a subscription modified event after subscribing');
+
   let record = yield db.getByKeyID(channelID);
   equal(record.channelID, channelID,
     'Wrong channel ID in database record');
   equal(record.pushEndpoint, 'https://example.com/update/1',
     'Wrong push endpoint in database record');
   equal(record.quota, 16,
     'Wrong quota in database record');
 });
--- a/dom/push/test/xpcshell/test_unregister_success.js
+++ b/dom/push/test/xpcshell/test_unregister_success.js
@@ -48,17 +48,25 @@ add_task(function* test_unregister_succe
             channelID
           }));
           unregisterDone();
         }
       });
     }
   });
 
+  let subModifiedPromise = promiseObserverNotification(
+    PushServiceComponent.subscriptionModifiedTopic);
+
   yield PushService.unregister({
     scope: 'https://example.com/page/unregister-success',
     originAttributes: '',
   });
+
+  let {data: subModifiedScope} = yield subModifiedPromise;
+  equal(subModifiedScope, 'https://example.com/page/unregister-success',
+    'Should fire a subscription modified event after unsubscribing');
+
   let record = yield db.getByKeyID(channelID);
   ok(!record, 'Unregister did not remove record');
 
   yield unregisterPromise;
 });
--- a/dom/smil/nsSMILAnimationController.cpp
+++ b/dom/smil/nsSMILAnimationController.cpp
@@ -681,17 +681,17 @@ nsSMILAnimationController::GetTargetIden
       // width/height are special as they may be attributes or for
       // outer-<svg> elements, mapped into style.
       if (attributeName == nsGkAtoms::width ||
           attributeName == nsGkAtoms::height) {
         isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
       } else {
         nsCSSProperty prop =
           nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
-                                     nsCSSProps::eEnabledForAllContent);
+                                     CSSEnabledState::eForAllContent);
         isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
       }
     }
   } else {
     isCSS = (attributeType == eSMILTargetAttrType_CSS);
   }
 
   // Construct the key
--- a/dom/smil/nsSMILCompositor.cpp
+++ b/dom/smil/nsSMILCompositor.cpp
@@ -121,17 +121,17 @@ nsSMILCompositor::ClearAnimationEffects(
 // Protected Helper Functions
 // --------------------------
 nsISMILAttr*
 nsSMILCompositor::CreateSMILAttr()
 {
   if (mKey.mIsCSS) {
     nsCSSProperty propId =
       nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
-                                 nsCSSProps::eEnabledForAllContent);
+                                 CSSEnabledState::eForAllContent);
     if (nsSMILCSSProperty::IsPropertyAnimatable(propId)) {
       return new nsSMILCSSProperty(propId, mKey.mElement.get());
     }
   } else {
     return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
                                           mKey.mAttributeName);
   }
   return nullptr;
deleted file mode 100644
--- a/dom/svg/SVGAltGlyphElement.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/dom/SVGAltGlyphElement.h"
-#include "mozilla/dom/SVGAltGlyphElementBinding.h"
-
-NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(AltGlyph)
-
-namespace mozilla {
-namespace dom {
-
-JSObject*
-SVGAltGlyphElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return SVGAltGlyphElementBinding::Wrap(aCx, this, aGivenProto);
-}
-
-nsSVGElement::StringInfo SVGAltGlyphElement::sStringInfo[1] =
-{
-  { &nsGkAtoms::href, kNameSpaceID_XLink, false }
-};
-
-
-//----------------------------------------------------------------------
-// Implementation
-
-SVGAltGlyphElement::SVGAltGlyphElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
-  : SVGAltGlyphElementBase(aNodeInfo)
-{
-}
-
-nsSVGElement::EnumAttributesInfo
-SVGAltGlyphElement::GetEnumInfo()
-{
-  return EnumAttributesInfo(mEnumAttributes, sEnumInfo,
-                            ArrayLength(sEnumInfo));
-}
-
-nsSVGElement::LengthAttributesInfo
-SVGAltGlyphElement::GetLengthInfo()
-{
-  return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
-                              ArrayLength(sLengthInfo));
-}
-
-//----------------------------------------------------------------------
-// nsIDOMNode methods
-
-NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAltGlyphElement)
-
-already_AddRefed<SVGAnimatedString>
-SVGAltGlyphElement::Href()
-{
-  return mStringAttributes[HREF].ToDOMAnimatedString(this);
-}
-
-void
-SVGAltGlyphElement::GetGlyphRef(nsAString & aGlyphRef)
-{
-  GetAttr(kNameSpaceID_None, nsGkAtoms::glyphRef, aGlyphRef);
-}
-
-void
-SVGAltGlyphElement::SetGlyphRef(const nsAString & aGlyphRef, ErrorResult& rv)
-{
-  rv = SetAttr(kNameSpaceID_None, nsGkAtoms::glyphRef, aGlyphRef, true);
-}
-
-void
-SVGAltGlyphElement::GetFormat(nsAString & aFormat)
-{
-  GetAttr(kNameSpaceID_None, nsGkAtoms::format, aFormat);
-}
-
-void
-SVGAltGlyphElement::SetFormat(const nsAString & aFormat, ErrorResult& rv)
-{
-  rv = SetAttr(kNameSpaceID_None, nsGkAtoms::format, aFormat, true);
-}
-
-//----------------------------------------------------------------------
-// nsIContent methods
-
-NS_IMETHODIMP_(bool)
-SVGAltGlyphElement::IsAttributeMapped(const nsIAtom* name) const
-{
-  static const MappedAttributeEntry* const map[] = {
-    sColorMap,
-    sFillStrokeMap,
-    sFontSpecificationMap,
-    sGraphicsMap,
-    sTextContentElementsMap
-  };
-
-  return FindAttributeDependence(name, map) ||
-    SVGAltGlyphElementBase::IsAttributeMapped(name);
-}
-
-//----------------------------------------------------------------------
-// nsSVGElement overrides
-
-nsSVGElement::StringAttributesInfo
-SVGAltGlyphElement::GetStringInfo()
-{
-  return StringAttributesInfo(mStringAttributes, sStringInfo,
-                              ArrayLength(sStringInfo));
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/svg/SVGAltGlyphElement.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- 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_SVGAltGlyphElement_h
-#define mozilla_dom_SVGAltGlyphElement_h
-
-#include "mozilla/dom/SVGTextPositioningElement.h"
-#include "nsSVGString.h"
-
-nsresult NS_NewSVGAltGlyphElement(nsIContent **aResult,
-                                  already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
-
-namespace mozilla {
-namespace dom {
-
-typedef SVGTextPositioningElement SVGAltGlyphElementBase;
-
-class SVGAltGlyphElement final : public SVGAltGlyphElementBase
-{
-protected:
-  friend nsresult (::NS_NewSVGAltGlyphElement(nsIContent **aResult,
-                                              already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
-  explicit SVGAltGlyphElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
-  virtual JSObject* WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
-
-public:
-  // nsIContent interface
-  NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
-
-  virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
-
-  // WebIDL
-  already_AddRefed<SVGAnimatedString> Href();
-  void GetGlyphRef(nsAString & aGlyphRef);
-  void SetGlyphRef(const nsAString & aGlyphRef, ErrorResult& rv);
-  void GetFormat(nsAString & aFormat);
-  void SetFormat(const nsAString & aFormat, ErrorResult& rv);
-
-protected:
-
-  // nsSVGElement overrides
-  virtual EnumAttributesInfo GetEnumInfo() override;
-  virtual LengthAttributesInfo GetLengthInfo() override;
-  virtual StringAttributesInfo GetStringInfo() override;
-
-  enum { HREF };
-  nsSVGString mStringAttributes[1];
-  static StringInfo sStringInfo[1];
-
-  nsSVGEnum mEnumAttributes[1];
-  virtual nsSVGEnum* EnumAttributes() override
-    { return mEnumAttributes; }
-
-  nsSVGLength2 mLengthAttributes[1];
-  virtual nsSVGLength2* LengthAttributes() override
-    { return mLengthAttributes; }
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_SVGAltGlyphElement_h
--- a/dom/svg/SVGTagList.h
+++ b/dom/svg/SVGTagList.h
@@ -24,17 +24,16 @@
   property and the atom name. The second argument is the "creator"
   method of the form NS_New$TAGNAMEElement, that will be used by
   SVGElementFactory.cpp to create a content object for a tag of that
   type.
 
  ******/
 
 SVG_TAG(a, A)
-SVG_TAG(altGlyph, AltGlyph)
 SVG_TAG(animate, Animate)
 SVG_TAG(animateMotion, AnimateMotion)
 SVG_TAG(animateTransform, AnimateTransform)
 SVG_TAG(circle, Circle)
 SVG_TAG(clipPath, ClipPath)
 SVG_TAG(defs, Defs)
 SVG_TAG(desc, Desc)
 SVG_TAG(ellipse, Ellipse)
--- a/dom/svg/moz.build
+++ b/dom/svg/moz.build
@@ -14,17 +14,16 @@ EXPORTS += [
     'SVGContentUtils.h',
     'SVGPreserveAspectRatio.h',
     'SVGStringList.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'nsSVGAnimatedTransformList.h',
     'SVGAElement.h',
-    'SVGAltGlyphElement.h',
     'SVGAngle.h',
     'SVGAnimatedAngle.h',
     'SVGAnimatedBoolean.h',
     'SVGAnimatedEnumeration.h',
     'SVGAnimatedInteger.h',
     'SVGAnimatedLength.h',
     'SVGAnimatedNumber.h',
     'SVGAnimatedRect.h',
@@ -132,17 +131,16 @@ UNIFIED_SOURCES += [
     'nsSVGNumberPair.cpp',
     'nsSVGPathDataParser.cpp',
     'nsSVGPathGeometryElement.cpp',
     'nsSVGPolyElement.cpp',
     'nsSVGString.cpp',
     'nsSVGTransform.cpp',
     'nsSVGViewBox.cpp',
     'SVGAElement.cpp',
-    'SVGAltGlyphElement.cpp',
     'SVGAngle.cpp',
     'SVGAnimatedAngle.cpp',
     'SVGAnimatedBoolean.cpp',
     'SVGAnimatedEnumeration.cpp',
     'SVGAnimatedInteger.cpp',
     'SVGAnimatedLength.cpp',
     'SVGAnimatedLengthList.cpp',
     'SVGAnimatedNumber.cpp',
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1181,27 +1181,27 @@ MappedAttrParser::ParseMappedAttrValue(n
   if (!mDecl) {
     mDecl = new css::Declaration();
     mDecl->InitializeEmpty();
   }
 
   // Get the nsCSSProperty ID for our mapped attribute.
   nsCSSProperty propertyID =
     nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName),
-                               nsCSSProps::eEnabledForAllContent);
+                               CSSEnabledState::eForAllContent);
   if (propertyID != eCSSProperty_UNKNOWN) {
     bool changed = false; // outparam for ParseProperty.
     mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
                           mElement->NodePrincipal(), mDecl, &changed, false, true);
     if (changed) {
       // The normal reporting of use counters by the nsCSSParser won't happen
       // since it doesn't have a sheet.
       if (nsCSSProps::IsShorthand(propertyID)) {
         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID,
-                                             nsCSSProps::eEnabledForAllContent) {
+                                             CSSEnabledState::eForAllContent) {
           UseCounter useCounter = nsCSSProps::UseCounterFor(*subprop);
           if (useCounter != eUseCounter_UNKNOWN) {
             mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter);
           }
         }
       } else {
         UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID);
         if (useCounter != eUseCounter_UNKNOWN) {
@@ -2539,17 +2539,17 @@ nsSVGElement::GetAnimatedAttr(int32_t aN
     // targeting width/height on outer-<svg> don't appear to be ignored
     // because we returned a nsISMILAttr for the corresponding
     // SVGAnimatedLength.
 
     // Mapped attributes:
     if (IsAttributeMapped(aName)) {
       nsCSSProperty prop =
         nsCSSProps::LookupProperty(nsDependentAtomString(aName),
-                                   nsCSSProps::eEnabledForAllContent);
+                                   CSSEnabledState::eForAllContent);
       // Check IsPropertyAnimatable to avoid attributes that...
       //  - map to explicitly unanimatable properties (e.g. 'direction')
       //  - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')
       if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
         return new nsSMILMappedAttribute(prop, this);
       }
     }
 
--- a/dom/svg/test/test_SVG_namespace_ids.html
+++ b/dom/svg/test/test_SVG_namespace_ids.html
@@ -27,19 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   </script>
 </head>
 <body onload="runTests()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589640">Mozilla Bug 589640</a>
 <pre id="debug"></pre>
 <!-- NOTE: This test relies on the ids being the same as the element names -->
 <svg id="svg1">
   <a id="a" />
-  <altGlyph id="altGlyph" />
-  <altGlyphDef id="altGlyphDef" />
-  <altGlyphItem id="altGlyphItem" />
   <animate id="animate" />
   <animateColor id="animateColor" />
   <animateMotion id="animateMotion" />
   <animateTransform id="animateTransform" />
   <circle id="circle" />
   <clipPath id="clipPath" />
   <color-profile id="color-profile" />
   <cursor id="cursor" />
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -1054,18 +1054,16 @@ var interfaceNamesInGlobalScope =
     "StyleSheet",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "StyleSheetList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SubtleCrypto",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SVGAElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "SVGAltGlyphElement",
-// IMPORTANT: Do not change this list without review from a DOM peer!
     "SVGAngle",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SVGAnimatedAngle",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SVGAnimatedBoolean",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SVGAnimatedEnumeration",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/HashChangeEvent.webidl
+++ b/dom/webidl/HashChangeEvent.webidl
@@ -2,23 +2,23 @@
 /* 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/.
  */
 
 [Constructor(DOMString type, optional HashChangeEventInit eventInitDict), LegacyEventInit]
 interface HashChangeEvent : Event
 {
-  readonly attribute DOMString? oldURL;
-  readonly attribute DOMString? newURL;
+  readonly attribute DOMString oldURL;
+  readonly attribute DOMString newURL;
 
   void initHashChangeEvent(DOMString typeArg,
                            boolean canBubbleArg,
                            boolean cancelableArg,
-                           DOMString? oldURLArg,
-                           DOMString? newURLArg);
+                           DOMString oldURLArg,
+                           DOMString newURLArg);
 };
 
 dictionary HashChangeEventInit : EventInit
 {
   DOMString oldURL = "";
   DOMString newURL = "";
 };
--- a/dom/webidl/IDBObjectStore.webidl
+++ b/dom/webidl/IDBObjectStore.webidl
@@ -37,24 +37,18 @@ interface IDBObjectStore {
     IDBRequest get (any key);
 
     [Throws]
     IDBRequest clear ();
 
     [Throws]
     IDBRequest openCursor (optional any range, optional IDBCursorDirection direction = "next");
 
-    // Bug 899972
-    // IDBIndex   createIndex (DOMString name, (DOMString or sequence<DOMString>) keyPath, optional IDBIndexParameters optionalParameters);
-
     [Throws]
-    IDBIndex   createIndex (DOMString name, DOMString keyPath, optional IDBIndexParameters optionalParameters);
-
-    [Throws]
-    IDBIndex   createIndex (DOMString name, sequence<DOMString> keyPath, optional IDBIndexParameters optionalParameters);
+    IDBIndex   createIndex (DOMString name, (DOMString or sequence<DOMString>) keyPath, optional IDBIndexParameters optionalParameters);
 
     [Throws]
     IDBIndex   index (DOMString name);
 
     [Throws]
     void       deleteIndex (DOMString indexName);
 
     [Throws]
--- a/dom/webidl/Response.webidl
+++ b/dom/webidl/Response.webidl
@@ -12,16 +12,17 @@
 interface Response {
   [NewObject] static Response error();
   [Throws,
    NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
 
   readonly attribute ResponseType type;
 
   readonly attribute USVString url;
+  readonly attribute boolean redirected;
   readonly attribute unsigned short status;
   readonly attribute boolean ok;
   readonly attribute ByteString statusText;
   [SameObject] readonly attribute Headers headers;
 
   [Throws,
    NewObject] Response clone();
 
deleted file mode 100644
--- a/dom/webidl/SVGAltGlyphElement.webidl
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * The origin of this IDL file is
- * http://www.w3.org/TR/SVG2/
- *
- * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
- * liability, trademark and document use rules apply.
- */
-
-interface SVGAltGlyphElement : SVGTextPositioningElement {
-  [SetterThrows]
-  attribute DOMString glyphRef;
-  [SetterThrows]
-  attribute DOMString format;
-};
-
-SVGAltGlyphElement implements SVGURIReference;
-
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -418,17 +418,16 @@ WEBIDL_FILES = [
     'StereoPannerNode.webidl',
     'Storage.webidl',
     'StorageEvent.webidl',
     'StorageType.webidl',
     'StyleSheet.webidl',
     'StyleSheetList.webidl',
     'SubtleCrypto.webidl',
     'SVGAElement.webidl',
-    'SVGAltGlyphElement.webidl',
     'SVGAngle.webidl',
     'SVGAnimatedAngle.webidl',
     'SVGAnimatedBoolean.webidl',
     'SVGAnimatedEnumeration.webidl',
     'SVGAnimatedInteger.webidl',
     'SVGAnimatedLength.webidl',
     'SVGAnimatedLengthList.webidl',
     'SVGAnimatedNumber.webidl',
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -245,17 +245,17 @@ public:
   bool CSPPermitsResponse(nsILoadInfo* aLoadInfo)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aLoadInfo);
 
     nsresult rv;
     nsCOMPtr<nsIURI> uri;
     nsAutoCString url;
-    mInternalResponse->GetUnfilteredUrl(url);
+    mInternalResponse->GetUnfilteredURL(url);
     if (url.IsEmpty()) {
       // Synthetic response. The buck stops at the worker script.
       url = mScriptSpec;
     }
     rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr);
     NS_ENSURE_SUCCESS(rv, false);
 
     int16_t decision = nsIContentPolicy::ACCEPT;
@@ -582,16 +582,18 @@ RespondWithHandler::ResolvedCallback(JSC
   worker->AssertIsOnWorkerThread();
 
   // Section "HTTP Fetch", step 3.3:
   //  If one of the following conditions is true, return a network error:
   //    * response's type is "error".
   //    * request's mode is not "no-cors" and response's type is "opaque".
   //    * request's redirect mode is not "manual" and response's type is
   //      "opaqueredirect".
+  //    * request's redirect mode is not "follow" and response's url list
+  //      has more than one item.
 
   if (response->Type() == ResponseType::Error) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("InterceptedErrorResponseWithURL"), mRequestURL);
     return;
   }
 
   MOZ_ASSERT_IF(mIsClientRequest, mRequestMode == RequestMode::Same_origin ||
@@ -610,33 +612,39 @@ RespondWithHandler::ResolvedCallback(JSC
 
   if (mRequestRedirectMode != RequestRedirect::Manual &&
       response->Type() == ResponseType::Opaqueredirect) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("BadOpaqueRedirectInterceptionWithURL"), mRequestURL);
     return;
   }
 
+  if (mRequestRedirectMode != RequestRedirect::Follow && response->Redirected()) {
+    autoCancel.SetCancelMessage(
+      NS_LITERAL_CSTRING("BadRedirectModeInterceptionWithURL"), mRequestURL);
+    return;
+  }
+
   if (NS_WARN_IF(response->BodyUsed())) {
     autoCancel.SetCancelMessage(
       NS_LITERAL_CSTRING("InterceptedUsedResponseWithURL"), mRequestURL);
     return;
   }
 
   RefPtr<InternalResponse> ir = response->GetInternalResponse();
   if (NS_WARN_IF(!ir)) {
     return;
   }
 
   // When an opaque response is encountered, we need the original channel's principal
   // to reflect the final URL. Non-opaque responses are either same-origin or CORS-enabled
   // cross-origin responses, which are treated as same-origin by consumers.
   nsCString responseURL;
   if (response->Type() == ResponseType::Opaque) {
-    ir->GetUnfilteredUrl(responseURL);
+    ir->GetUnfilteredURL(responseURL);
     if (NS_WARN_IF(responseURL.IsEmpty())) {
       return;
     }
   }
 
   nsAutoPtr<RespondWithClosure> closure(new RespondWithClosure(mInterceptedChannel,
                                                                mRegistration, ir,
                                                                worker->GetChannelInfo(),
--- a/editor/libeditor/nsHTMLCSSUtils.cpp
+++ b/editor/libeditor/nsHTMLCSSUtils.cpp
@@ -517,17 +517,17 @@ nsHTMLCSSUtils::GetCSSInlinePropertyBase
 
   MOZ_ASSERT(aStyleType == eSpecified);
   RefPtr<css::Declaration> decl = element->GetInlineStyleDeclaration();
   if (!decl) {
     return NS_OK;
   }
   nsCSSProperty prop =
     nsCSSProps::LookupProperty(nsDependentAtomString(aProperty),
-                               nsCSSProps::eEnabledForAllContent);
+                               CSSEnabledState::eForAllContent);
   MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
   decl->GetValue(prop, aValue);
 
   return NS_OK;
 }
 
 already_AddRefed<nsComputedDOMStyle>
 nsHTMLCSSUtils::GetComputedStyle(dom::Element* aElement)
--- a/extensions/pref/autoconfig/src/prefcalls.js
+++ b/extensions/pref/autoconfig/src/prefcalls.js
@@ -109,16 +109,22 @@ function unlockPref(prefName) {
 function getPref(prefName) {
     
     try {
         var prefBranch = getPrefBranch();
         
         switch (prefBranch.getPrefType(prefName)) {
             
         case prefBranch.PREF_STRING:
+            if (gIsUTF8) {
+                const nsISupportsString = Components.interfaces.nsISupportsString;
+                let string = Components.classes["@mozilla.org/supports-string;1"]
+                                       .createInstance(nsISupportsString);
+                return prefBranch.getComplexValue(prefName, nsISupportsString).data;
+            }
             return prefBranch.getCharPref(prefName);
             
         case prefBranch.PREF_INT:
             return prefBranch.getIntPref(prefName);
             
         case prefBranch.PREF_BOOL:
             return prefBranch.getBoolPref(prefName);
         default:
--- a/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg
@@ -1,5 +1,6 @@
 // # don't remove this comment! (the first line is ignored by Mozilla)
 // 
 lockPref("_test.string.ASCII", "ASCII");
 lockPref("_test.string.non-ASCII", "日本語");
+lockPref("_test.string.getPref", getPref("_test.string.non-ASCII"));
 lockPref("_test.string.gIsUTF8", String(this.gIsUTF8));
--- a/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg
@@ -1,5 +1,6 @@
 // # don't remove this comment! (the first line is ignored by Mozilla)
 
 lockPref("_test.string.ASCII", "UTF-8");
 lockPref("_test.string.non-ASCII", "日本語");
+lockPref("_test.string.getPref", getPref("_test.string.non-ASCII"));
 lockPref("_test.string.gIsUTF8", String(this.gIsUTF8));
--- a/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
@@ -26,23 +26,25 @@ function run_test() {
     Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports);
     ps.resetPrefs();
 
     var tests = [{
       filename: "autoconfig-utf8.cfg",
       prefs: {
         "_test.string.ASCII": "UTF-8",
         "_test.string.non-ASCII": "日本語",
+        "_test.string.getPref": "日本語",
         "_test.string.gIsUTF8": "true"
       }
     }, {
       filename: "autoconfig-latin1.cfg",
       prefs: {
         "_test.string.ASCII": "ASCII",
         "_test.string.non-ASCII": "日本語",
+        "_test.string.getPref": "日本語",
         "_test.string.gIsUTF8": "false",
       }
     }];
 
     function testAutoConfig(test) {
       // Make sure pref values are unset.
       for (let prefName in test.prefs) {
         do_check_eq(Ci.nsIPrefBranch.PREF_INVALID, prefs.getPrefType(prefName));
--- a/js/public/StructuredClone.h
+++ b/js/public/StructuredClone.h
@@ -276,9 +276,12 @@ JS_PUBLIC_API(bool)
 JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, size_t len);
 
 JS_PUBLIC_API(bool)
 JS_WriteString(JSStructuredCloneWriter* w, JS::HandleString str);
 
 JS_PUBLIC_API(bool)
 JS_WriteTypedArray(JSStructuredCloneWriter* w, JS::HandleValue v);
 
+JS_PUBLIC_API(bool)
+JS_ObjectNotWritten(JSStructuredCloneWriter* w, JS::HandleObject obj);
+
 #endif  /* js_StructuredClone_h */
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -8274,16 +8274,28 @@ bool
 js::IsAsmJSFunction(JSFunction* fun)
 {
     if (IsExportedFunction(fun))
         return ExportedFunctionToModuleObject(fun)->module().isAsmJS();
     return false;
 }
 
 bool
+js::IsAsmJSStrictModeModuleOrFunction(JSFunction* fun)
+{
+    if (IsAsmJSModule(fun))
+        return AsmJSModuleToModuleObject(fun)->module().asAsmJS().strict();
+
+    if (IsAsmJSFunction(fun))
+        return ExportedFunctionToModuleObject(fun)->module().asAsmJS().strict();
+
+    return false;
+}
+
+bool
 js::IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // See EstablishPreconditions.
     bool available = HasCompilerSupport(cx) && cx->runtime()->options().asmJS();
 
     args.rval().set(BooleanValue(available));
--- a/js/src/asmjs/AsmJS.h
+++ b/js/src/asmjs/AsmJS.h
@@ -50,16 +50,19 @@ extern bool
 IsAsmJSModuleNative(JSNative native);
 
 extern bool
 IsAsmJSModule(JSFunction* fun);
 
 extern bool
 IsAsmJSFunction(JSFunction* fun);
 
+extern bool
+IsAsmJSStrictModeModuleOrFunction(JSFunction* fun);
+
 // asm.js testing natives:
 
 extern bool
 IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, JS::Value* vp);
 
 extern bool
 IsAsmJSModule(JSContext* cx, unsigned argc, JS::Value* vp);
 
--- a/js/src/asmjs/WasmBinaryIterator.h
+++ b/js/src/asmjs/WasmBinaryIterator.h
@@ -167,22 +167,22 @@ class TypeAndValue
     }
     void setValue(Value value) {
         value_ = value;
     }
 };
 
 // Specialization for when there is no additional data needed.
 template <>
-struct TypeAndValue<Nothing>
+class TypeAndValue<Nothing>
 {
     ExprType type_;
 
   public:
-    TypeAndValue() {}
+    TypeAndValue() = default;
     explicit TypeAndValue(ExprType type) : type_(type) {}
 
     TypeAndValue(ExprType type, Nothing value)
       : type_(type)
     {}
 
     ExprType type() const { return type_; }
     Nothing value() const { return Nothing(); }
--- a/js/src/asmjs/WasmFrameIterator.cpp
+++ b/js/src/asmjs/WasmFrameIterator.cpp
@@ -90,17 +90,16 @@ FrameIterator::settle()
         MOZ_ASSERT(callsite_);
         break;
       case CodeRange::Entry:
         fp_ = nullptr;
         MOZ_ASSERT(done());
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::ErrorExit:
       case CodeRange::Inline:
       case CodeRange::CallThunk:
         MOZ_CRASH("Should not encounter an exit during iteration");
     }
 }
 
 JSAtom*
 FrameIterator::functionDisplayAtom() const
@@ -478,17 +477,16 @@ ProfilingFrameIterator::initFromFP(const
       case CodeRange::Function:
         fp = CallerFPFromFP(fp);
         callerPC_ = ReturnAddressFromFP(fp);
         callerFP_ = CallerFPFromFP(fp);
         AssertMatchesCallSite(*module_, callerPC_, callerFP_, fp);
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::ErrorExit:
       case CodeRange::Inline:
       case CodeRange::CallThunk:
         MOZ_CRASH("Unexpected CodeRange kind");
     }
 
     // The iterator inserts a pretend innermost frame for non-None ExitReasons.
     // This allows the variety of exit reasons to show up in the callstack.
     exitReason_ = activation.exitReason();
@@ -533,18 +531,17 @@ ProfilingFrameIterator::ProfilingFrameIt
     // Note: fp may be null while entering and leaving the activation.
     uint8_t* fp = activation.fp();
 
     const CodeRange* codeRange = module_->lookupCodeRange(state.pc);
     switch (codeRange->kind()) {
       case CodeRange::Function:
       case CodeRange::CallThunk:
       case CodeRange::ImportJitExit:
-      case CodeRange::ImportInterpExit:
-      case CodeRange::ErrorExit: {
+      case CodeRange::ImportInterpExit: {
         // When the pc is inside the prologue/epilogue, the innermost
         // call's AsmJSFrame is not complete and thus fp points to the the
         // second-to-innermost call's AsmJSFrame. Since fp can only tell you
         // about its caller (via ReturnAddressFromFP(fp)), naively unwinding
         // while pc is in the prologue/epilogue would skip the second-to-
         // innermost call. To avoid this problem, we use the static structure of
         // the code in the prologue and epilogue to do the Right Thing.
         MOZ_ASSERT(module_->containsCodePC(state.pc));
@@ -648,17 +645,16 @@ ProfilingFrameIterator::operator++()
     switch (codeRange->kind()) {
       case CodeRange::Entry:
         MOZ_ASSERT(callerFP_ == nullptr);
         callerPC_ = nullptr;
         break;
       case CodeRange::Function:
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::ErrorExit:
       case CodeRange::Inline:
       case CodeRange::CallThunk:
         stackAddress_ = callerFP_;
         callerPC_ = ReturnAddressFromFP(callerFP_);
         AssertMatchesCallSite(*module_, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
         callerFP_ = CallerFPFromFP(callerFP_);
         break;
     }
@@ -694,17 +690,16 @@ ProfilingFrameIterator::label() const
         return nativeDescription;
     }
 
     switch (codeRange_->kind()) {
       case CodeRange::Function:         return module_->profilingLabel(codeRange_->funcIndex());
       case CodeRange::Entry:            return "entry trampoline (in asm.js)";
       case CodeRange::ImportJitExit:    return importJitDescription;
       case CodeRange::ImportInterpExit: return importInterpDescription;
-      case CodeRange::ErrorExit:        return errorDescription;
       case CodeRange::Inline:           return "inline stub (in asm.js)";
       case CodeRange::CallThunk:        return "call thunk (in asm.js)";
     }
 
     MOZ_CRASH("bad code range kind");
 }
 
 /*****************************************************************************/
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -359,17 +359,16 @@ ModuleGenerator::finishCodegen(StaticLin
     // Generate stubs in a separate MacroAssembler since, otherwise, for modules
     // larger than the JumpImmediateRange, even local uses of Label will fail
     // due to the large absolute offsets temporarily stored by Label::bind().
 
     Vector<Offsets> entries(cx_);
     Vector<ProfilingOffsets> interpExits(cx_);
     Vector<ProfilingOffsets> jitExits(cx_);
     EnumeratedArray<JumpTarget, JumpTarget::Limit, Offsets> jumpTargets;
-    ProfilingOffsets badIndirectCallExit;
     Offsets interruptExit;
 
     {
         TempAllocator alloc(&lifo_);
         MacroAssembler masm(MacroAssembler::AsmJSToken(), alloc);
 
         if (!entries.resize(numExports()))
             return false;
@@ -386,17 +385,16 @@ ModuleGenerator::finishCodegen(StaticLin
         for (uint32_t i = 0; i < numImports(); i++) {
             interpExits[i] = GenerateInterpExit(masm, module_->imports[i], i);
             jitExits[i] = GenerateJitExit(masm, module_->imports[i], usesHeap());
         }
 
         for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit))
             jumpTargets[target] = GenerateJumpTarget(masm, target);
 
-        badIndirectCallExit = GenerateBadIndirectCallExit(masm);
         interruptExit = GenerateInterruptStub(masm);
 
         if (masm.oom() || !masm_.asmMergeWith(masm))
             return false;
     }
 
     // Adjust each of the resulting Offsets (to account for being merged into
     // masm_) and then create code ranges for all the stubs.
@@ -421,29 +419,27 @@ ModuleGenerator::finishCodegen(StaticLin
     }
 
     for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit)) {
         jumpTargets[target].offsetBy(offsetInWhole);
         if (!module_->codeRanges.emplaceBack(CodeRange::Inline, jumpTargets[target]))
             return false;
     }
 
-    badIndirectCallExit.offsetBy(offsetInWhole);
-    if (!module_->codeRanges.emplaceBack(CodeRange::ErrorExit, badIndirectCallExit))
-        return false;
-
     interruptExit.offsetBy(offsetInWhole);
     if (!module_->codeRanges.emplaceBack(CodeRange::Inline, interruptExit))
         return false;
 
     // Fill in StaticLinkData with the offsets of these stubs.
 
     link->pod.outOfBoundsOffset = jumpTargets[JumpTarget::OutOfBounds].begin;
     link->pod.interruptOffset = interruptExit.begin;
 
+    Offsets badIndirectCallExit = jumpTargets[JumpTarget::BadIndirectCall];
+
     for (uint32_t sigIndex = 0; sigIndex < numSigs_; sigIndex++) {
         const TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
         if (table.elemFuncIndices.empty())
             continue;
 
         Uint32Vector elemOffsets;
         if (!elemOffsets.resize(table.elemFuncIndices.length()))
             return false;
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -78,16 +78,18 @@ class FunctionCompiler
     const ValTypeVector&       locals_;
     size_t                     lastReadCallSite_;
 
     TempAllocator&             alloc_;
     MIRGraph&                  graph_;
     const CompileInfo&         info_;
     MIRGenerator&              mirGen_;
 
+    MInstruction*              dummyIns_;
+
     MBasicBlock*               curBlock_;
     CallVector                 callStack_;
     uint32_t                   maxStackArgBytes_;
 
     uint32_t                   loopDepth_;
     uint32_t                   blockDepth_;
     ControlFlowPatchsVector    blockPatches_;
 
@@ -104,16 +106,17 @@ class FunctionCompiler
         iter_(IonCompilePolicy(), decoder),
         func_(func),
         locals_(locals),
         lastReadCallSite_(0),
         alloc_(mirGen.alloc()),
         graph_(mirGen.graph()),
         info_(mirGen.info()),
         mirGen_(mirGen),
+        dummyIns_(nullptr),
         curBlock_(nullptr),
         maxStackArgBytes_(0),
         loopDepth_(0),
         blockDepth_(0),
         compileResults_(compileResults)
     {}
 
     const ModuleGeneratorData& mg() const    { return mg_; }
@@ -171,16 +174,19 @@ class FunctionCompiler
             }
 
             curBlock_->add(ins);
             curBlock_->initSlot(info().localSlot(i), ins);
             if (!mirGen_.ensureBallast())
                 return false;
         }
 
+        dummyIns_ = MConstant::NewAsmJS(alloc(), Int32Value(0), MIRType::Int32);
+        curBlock_->add(dummyIns_);
+
         addInterruptCheck();
 
         return true;
     }
 
     void finish()
     {
         mirGen().initWasmMaxStackArgBytes(maxStackArgBytes_);
@@ -455,30 +461,32 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return nullptr;
         MMul* ins = MMul::New(alloc(), lhs, rhs, type, mode);
         curBlock_->add(ins);
         return ins;
     }
 
-    MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
+    MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd,
+                     bool trapOnError)
     {
         if (inDeadCode())
             return nullptr;
-        MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
+        MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd, trapOnError);
         curBlock_->add(ins);
         return ins;
     }
 
-    MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
+    MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd,
+                     bool trapOnError)
     {
         if (inDeadCode())
             return nullptr;
-        MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
+        MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd, trapOnError);
         curBlock_->add(ins);
         return ins;
     }
 
     template <class T>
     MDefinition* bitwise(MDefinition* lhs, MDefinition* rhs, MIRType type)
     {
         if (inDeadCode())
@@ -959,53 +967,55 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return;
         MOZ_ASSERT(!hasPushed(curBlock_));
         if (def && def->type() != MIRType::None)
             curBlock_->push(def);
     }
 
-    MDefinition* popDefIfPushed()
+    MDefinition* popDefIfPushed(bool shouldReturn = true)
     {
         if (!hasPushed(curBlock_))
             return nullptr;
         MDefinition* def = curBlock_->pop();
-        MOZ_ASSERT(def->type() != MIRType::Value);
-        return def;
+        MOZ_ASSERT_IF(def->type() == MIRType::Value, !shouldReturn);
+        return shouldReturn ? def : nullptr;
     }
 
     template <typename GetBlock>
-    void ensurePushInvariants(const GetBlock& getBlock, size_t numBlocks)
+    bool ensurePushInvariants(const GetBlock& getBlock, size_t numBlocks)
     {
         // Preserve the invariant that, for every iterated MBasicBlock, either:
         // every MBasicBlock has a pushed expression with the same type (to
-        // prevent creating phis with type Value) OR no MBasicBlock has any
+        // prevent creating used phis with type Value) OR no MBasicBlock has any
         // pushed expression. This is required by MBasicBlock::addPredecessor.
         if (numBlocks < 2)
-            return;
+            return true;
 
         MBasicBlock* block = getBlock(0);
 
         bool allPushed = hasPushed(block);
         if (allPushed) {
             MIRType type = peekPushedDef(block)->type();
             for (size_t i = 1; allPushed && i < numBlocks; i++) {
                 block = getBlock(i);
                 allPushed = hasPushed(block) && peekPushedDef(block)->type() == type;
             }
         }
 
         if (!allPushed) {
             for (size_t i = 0; i < numBlocks; i++) {
                 block = getBlock(i);
-                if (hasPushed(block))
-                    block->pop();
+                if (!hasPushed(block))
+                    block->push(dummyIns_);
             }
         }
+
+        return allPushed;
     }
 
   private:
     void addJoinPredecessor(MDefinition* def, MBasicBlock** joinPred)
     {
         *joinPred = curBlock_;
         if (inDeadCode())
             return;
@@ -1066,33 +1076,33 @@ class FunctionCompiler
             mozilla::Array<MBasicBlock*, 2> blocks;
             size_t numJoinPreds = 0;
             if (thenJoinPred)
                 blocks[numJoinPreds++] = thenJoinPred;
             if (elseJoinPred)
                 blocks[numJoinPreds++] = elseJoinPred;
 
             auto getBlock = [&](size_t i) -> MBasicBlock* { return blocks[i]; };
-            ensurePushInvariants(getBlock, numJoinPreds);
+            bool yieldsValue = ensurePushInvariants(getBlock, numJoinPreds);
 
             if (numJoinPreds == 0) {
                 *def = nullptr;
                 return true;
             }
 
             MBasicBlock* join;
             if (!goToNewBlock(blocks[0], &join))
                 return false;
             for (size_t i = 1; i < numJoinPreds; ++i) {
                 if (!goToExistingBlock(blocks[i], join))
                     return false;
             }
 
             curBlock_ = join;
-            *def = popDefIfPushed();
+            *def = popDefIfPushed(yieldsValue);
         }
 
         return true;
     }
 
     bool startBlock()
     {
         MOZ_ASSERT_IF(blockDepth_ < blockPatches_.length(), blockPatches_[blockDepth_].empty());
@@ -1393,17 +1403,18 @@ class FunctionCompiler
 
         ControlFlowPatchVector& patches = blockPatches_[absolute];
 
         auto getBlock = [&](size_t i) -> MBasicBlock* {
             if (i < patches.length())
                 return patches[i].ins->block();
             return curBlock_;
         };
-        ensurePushInvariants(getBlock, patches.length() + !!curBlock_);
+
+        bool yieldsValue = ensurePushInvariants(getBlock, patches.length() + !!curBlock_);
 
         MBasicBlock* join = nullptr;
         MControlInstruction* ins = patches[0].ins;
         MBasicBlock* pred = ins->block();
         if (!newBlock(pred, &join))
             return false;
 
         pred->mark();
@@ -1423,19 +1434,20 @@ class FunctionCompiler
         }
 
         MOZ_ASSERT_IF(curBlock_, !curBlock_->isMarked());
         for (uint32_t i = 0; i < join->numPredecessors(); i++)
             join->getPredecessor(i)->unmark();
 
         if (curBlock_ && !goToExistingBlock(curBlock_, join))
             return false;
+
         curBlock_ = join;
 
-        *def = popDefIfPushed();
+        *def = popDefIfPushed(yieldsValue);
 
         patches.clear();
         return true;
     }
 };
 
 } // end anonymous namespace
 
@@ -1969,29 +1981,31 @@ EmitMul(FunctionCompiler& f, ValType ope
 static bool
 EmitDiv(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsigned)
 {
     MDefinition* lhs;
     MDefinition* rhs;
     if (!f.iter().readBinary(operandType, &lhs, &rhs))
         return false;
 
-    f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned));
+    bool trapOnError = f.mg().kind == ModuleKind::Wasm;
+    f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned, trapOnError));
     return true;
 }
 
 static bool
 EmitRem(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsigned)
 {
     MDefinition* lhs;
     MDefinition* rhs;
     if (!f.iter().readBinary(operandType, &lhs, &rhs))
         return false;
 
-    f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned));
+    bool trapOnError = f.mg().kind == ModuleKind::Wasm;
+    f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned, trapOnError));
     return true;
 }
 
 static bool
 EmitMinMax(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isMax)
 {
     MDefinition* lhs;
     MDefinition* rhs;
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -378,17 +378,17 @@ CodeRange::CodeRange(Kind kind, Profilin
     profilingReturn_(offsets.profilingReturn),
     end_(offsets.end)
 {
     PodZero(&u);  // zero padding for Valgrind
     u.kind_ = kind;
 
     MOZ_ASSERT(begin_ < profilingReturn_);
     MOZ_ASSERT(profilingReturn_ < end_);
-    MOZ_ASSERT(u.kind_ == ImportJitExit || u.kind_ == ImportInterpExit || u.kind_ == ErrorExit);
+    MOZ_ASSERT(u.kind_ == ImportJitExit || u.kind_ == ImportInterpExit);
 }
 
 CodeRange::CodeRange(uint32_t funcIndex, uint32_t funcLineOrBytecode, FuncOffsets offsets)
   : funcIndex_(funcIndex),
     funcLineOrBytecode_(funcLineOrBytecode)
 {
     PodZero(&u);  // zero padding for Valgrind
     u.kind_ = Function;
@@ -813,17 +813,17 @@ Module::setProfilingEnabled(JSContext* c
     }
 
     // Update the function-pointer tables to point to profiling prologues.
     for (FuncPtrTable& table : funcPtrTables_) {
         auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
         for (size_t i = 0; i < table.numElems; i++) {
             const CodeRange* codeRange = lookupCodeRange(array[i]);
             // Don't update entries for the BadIndirectCall exit.
-            if (codeRange->isErrorExit())
+            if (codeRange->isInline())
                 continue;
             void* from = code() + codeRange->funcNonProfilingEntry();
             void* to = code() + codeRange->funcProfilingEntry();
             if (!enabled)
                 Swap(from, to);
             MOZ_ASSERT(array[i] == from);
             array[i] = to;
         }
@@ -1076,17 +1076,17 @@ Module::staticallyLink(ExclusiveContext*
         }
     }
 
     for (const StaticLinkData::FuncPtrTable& table : linkData.funcPtrTables) {
         auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
         for (size_t i = 0; i < table.elemOffsets.length(); i++) {
             uint8_t* elem = code() + table.elemOffsets[i];
             const CodeRange* codeRange = lookupCodeRange(elem);
-            if (profilingEnabled_ && !codeRange->isErrorExit())
+            if (profilingEnabled_ && !codeRange->isInline())
                 elem = code() + codeRange->funcProfilingEntry();
             array[i] = elem;
         }
     }
 
     // CodeRangeVector, CallSiteVector and the code technically have all the
     // necessary info to do all the updates necessary in setProfilingEnabled.
     // However, to simplify the finding of function-pointer table sizes and
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -209,17 +209,17 @@ class CodeRange
             uint8_t profilingEpilogueToProfilingReturn_;
         } func;
         uint8_t kind_;
     } u;
 
     void assertValid();
 
   public:
-    enum Kind { Function, Entry, ImportJitExit, ImportInterpExit, ErrorExit, Inline, CallThunk };
+    enum Kind { Function, Entry, ImportJitExit, ImportInterpExit, Inline, CallThunk };
 
     CodeRange() = default;
     CodeRange(Kind kind, Offsets offsets);
     CodeRange(Kind kind, ProfilingOffsets offsets);
     CodeRange(uint32_t funcIndex, uint32_t lineOrBytecode, FuncOffsets offsets);
 
     // All CodeRanges have a begin and end.
 
@@ -246,18 +246,18 @@ class CodeRange
     // profiling prologues/epilogues.
 
     bool isFunction() const {
         return kind() == Function;
     }
     bool isImportExit() const {
         return kind() == ImportJitExit || kind() == ImportInterpExit;
     }
-    bool isErrorExit() const {
-        return kind() == ErrorExit;
+    bool isInline() const {
+        return kind() == Inline;
     }
     uint32_t funcProfilingEntry() const {
         MOZ_ASSERT(isFunction());
         return begin();
     }
     uint32_t funcNonProfilingEntry() const {
         MOZ_ASSERT(isFunction());
         return begin_ + u.func.beginToEntry_;
--- a/js/src/asmjs/WasmStubs.cpp
+++ b/js/src/asmjs/WasmStubs.cpp
@@ -831,32 +831,47 @@ GenerateStackOverflow(MacroAssembler& ma
     masm.assertStackAlignment(ABIStackAlignment);
     masm.call(SymbolicAddress::ReportOverRecursed);
     masm.jump(JumpTarget::Throw);
 
     offsets.end = masm.currentOffset();
     return offsets;
 }
 
-// Generate a stub that is jumped to from function bodies to throw an exception.
+// Generate a stub that calls into HandleTrap with the right trap reason.
 static Offsets
-GenerateErrorStub(MacroAssembler& masm, SymbolicAddress address)
+GenerateTrapStub(MacroAssembler& masm, Trap reason)
 {
     masm.haltingAlign(CodeAlignment);
 
     Offsets offsets;
     offsets.begin = masm.currentOffset();
 
     // sp can be anything at this point, so ensure it is aligned when calling
     // into C++.  We unconditionally jump to throw so don't worry about
     // restoring sp.
     masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
+    if (ShadowStackSpace)
+        masm.subFromStackPtr(Imm32(ShadowStackSpace));
 
-    masm.assertStackAlignment(ABIStackAlignment);
-    masm.call(address);
+    MIRTypeVector args;
+    JS_ALWAYS_TRUE(args.append(MIRType::Int32));
+
+    ABIArgMIRTypeIter i(args);
+    if (i->kind() == ABIArg::GPR) {
+        masm.move32(Imm32(int32_t(reason)), i->gpr());
+    } else {
+        masm.store32(Imm32(int32_t(reason)),
+                     Address(masm.getStackPointer(), i->offsetFromArgBase()));
+    }
+
+    i++;
+    MOZ_ASSERT(i.done());
+
+    masm.call(SymbolicAddress::HandleTrap);
     masm.jump(JumpTarget::Throw);
 
     offsets.end = masm.currentOffset();
     return offsets;
 }
 
 // If an exception is thrown, simply pop all frames (since asm.js does not
 // contain try/catch). To do this:
@@ -892,55 +907,32 @@ GenerateThrow(MacroAssembler& masm)
 }
 
 Offsets
 wasm::GenerateJumpTarget(MacroAssembler& masm, JumpTarget target)
 {
     switch (target) {
       case JumpTarget::StackOverflow:
         return GenerateStackOverflow(masm);
-      case JumpTarget::ConversionError:
-        return GenerateErrorStub(masm, SymbolicAddress::OnImpreciseConversion);
-      case JumpTarget::OutOfBounds:
-        return GenerateErrorStub(masm, SymbolicAddress::OnOutOfBounds);
-      case JumpTarget::BadIndirectCall:
-        return GenerateErrorStub(masm, SymbolicAddress::BadIndirectCall);
-      case JumpTarget::UnreachableTrap:
-        return GenerateErrorStub(masm, SymbolicAddress::UnreachableTrap);
-      case JumpTarget::InvalidConversionToIntegerTrap:
-        return GenerateErrorStub(masm, SymbolicAddress::InvalidConversionToIntegerTrap);
-      case JumpTarget::IntegerOverflowTrap:
-        return GenerateErrorStub(masm, SymbolicAddress::IntegerOverflowTrap);
       case JumpTarget::Throw:
         return GenerateThrow(masm);
+      case JumpTarget::BadIndirectCall:
+      case JumpTarget::OutOfBounds:
+      case JumpTarget::Unreachable:
+      case JumpTarget::IntegerOverflow:
+      case JumpTarget::InvalidConversionToInteger:
+      case JumpTarget::IntegerDivideByZero:
+      case JumpTarget::ImpreciseSimdConversion:
+        return GenerateTrapStub(masm, Trap(target));
       case JumpTarget::Limit:
         break;
     }
     MOZ_CRASH("bad JumpTarget");
 }
 
-ProfilingOffsets
-wasm::GenerateBadIndirectCallExit(MacroAssembler& masm)
-{
-    MIRTypeVector args;
-    unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, args);
-
-    ProfilingOffsets offsets;
-    GenerateExitPrologue(masm, framePushed, ExitReason::Error, &offsets);
-
-    AssertStackAlignment(masm, ABIStackAlignment);
-    masm.call(SymbolicAddress::BadIndirectCall);
-    masm.jump(JumpTarget::Throw);
-
-    GenerateExitEpilogue(masm, framePushed, ExitReason::Error, &offsets);
-
-    offsets.end = masm.currentOffset();
-    return offsets;
-}
-
 static const LiveRegisterSet AllRegsExceptSP(
     GeneralRegisterSet(Registers::AllMask&
                        ~(uint32_t(1) << Registers::StackPointer)),
     FloatRegisterSet(FloatRegisters::AllMask));
 
 // The async interrupt-callback exit is called from arbitrarily-interrupted asm.js
 // code. That means we must first save *all* registers and restore *all*
 // registers (except the stack pointer) when we resume. The address to resume to
--- a/js/src/asmjs/WasmStubs.h
+++ b/js/src/asmjs/WasmStubs.h
@@ -31,18 +31,15 @@ extern ProfilingOffsets
 GenerateInterpExit(jit::MacroAssembler& masm, const Import& import, uint32_t importIndex);
 
 extern ProfilingOffsets
 GenerateJitExit(jit::MacroAssembler& masm, const Import& import, bool usesHeap);
 
 extern Offsets
 GenerateJumpTarget(jit::MacroAssembler& masm, JumpTarget target);
 
-extern ProfilingOffsets
-GenerateBadIndirectCallExit(jit::MacroAssembler& masm);
-
 extern Offsets
 GenerateInterruptStub(jit::MacroAssembler& masm);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_stubs_h
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -54,55 +54,51 @@ WasmReportOverRecursed()
 
 static bool
 WasmHandleExecutionInterrupt()
 {
     return CheckForInterrupt(JSRuntime::innermostWasmActivation()->cx());
 }
 
 static void
-OnOutOfBounds()
-{
-    JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
-}
-
-static void
-OnImpreciseConversion()
-{
-    JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SIMD_FAILED_CONVERSION);
-}
-
-static void
-BadIndirectCall()
+HandleTrap(int32_t trapIndex)
 {
     JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IND_CALL);
-}
 
-static void
-UnreachableTrap()
-{
-    JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_UNREACHABLE);
-}
+    MOZ_ASSERT(trapIndex < int32_t(Trap::Limit) && trapIndex >= 0);
+    Trap trap = Trap(trapIndex);
 
-static void
-IntegerOverflowTrap()
-{
-    JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_INTEGER_OVERFLOW);
-}
+    unsigned errorNumber;
+    switch (trap) {
+      case Trap::Unreachable:
+        errorNumber = JSMSG_WASM_UNREACHABLE;
+        break;
+      case Trap::IntegerOverflow:
+        errorNumber = JSMSG_WASM_INTEGER_OVERFLOW;
+        break;
+      case Trap::InvalidConversionToInteger:
+        errorNumber = JSMSG_WASM_INVALID_CONVERSION;
+        break;
+      case Trap::IntegerDivideByZero:
+        errorNumber = JSMSG_WASM_INT_DIVIDE_BY_ZERO;
+        break;
+      case Trap::BadIndirectCall:
+        errorNumber = JSMSG_WASM_BAD_IND_CALL;
+        break;
+      case Trap::ImpreciseSimdConversion:
+        errorNumber = JSMSG_SIMD_FAILED_CONVERSION;
+        break;
+      case Trap::OutOfBounds:
+        errorNumber = JSMSG_BAD_INDEX;
+        break;
+      default:
+        MOZ_CRASH("unexpected trap");
+    }
 
-static void
-InvalidConversionToIntegerTrap()
-{
-    JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
-    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_INVALID_CONVERSION);
+    JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber);
 }
 
 static int32_t
 CoerceInPlace_ToInt32(MutableHandleValue val)
 {
     JSContext* cx = JSRuntime::innermostWasmActivation()->cx();
 
     int32_t i32;
@@ -239,30 +235,20 @@ wasm::AddressOf(SymbolicAddress imm, Exc
       case SymbolicAddress::Runtime:
         return cx->runtimeAddressForJit();
       case SymbolicAddress::RuntimeInterruptUint32:
         return cx->runtimeAddressOfInterruptUint32();
       case SymbolicAddress::StackLimit:
         return cx->stackLimitAddressForJitCode(StackForUntrustedScript);
       case SymbolicAddress::ReportOverRecursed:
         return FuncCast(WasmReportOverRecursed, Args_General0);
-      case SymbolicAddress::OnOutOfBounds:
-        return FuncCast(OnOutOfBounds, Args_General0);
-      case SymbolicAddress::OnImpreciseConversion:
-        return FuncCast(OnImpreciseConversion, Args_General0);
-      case SymbolicAddress::BadIndirectCall:
-        return FuncCast(BadIndirectCall, Args_General0);
-      case SymbolicAddress::UnreachableTrap:
-        return FuncCast(UnreachableTrap, Args_General0);
-      case SymbolicAddress::IntegerOverflowTrap:
-        return FuncCast(IntegerOverflowTrap, Args_General0);
-      case SymbolicAddress::InvalidConversionToIntegerTrap:
-        return FuncCast(InvalidConversionToIntegerTrap, Args_General0);
       case SymbolicAddress::HandleExecutionInterrupt:
         return FuncCast(WasmHandleExecutionInterrupt, Args_General0);
+      case SymbolicAddress::HandleTrap:
+        return FuncCast(HandleTrap, Args_General1);
       case SymbolicAddress::InvokeImport_Void:
         return FuncCast(InvokeImport_Void, Args_General3);
       case SymbolicAddress::InvokeImport_I32:
         return FuncCast(InvokeImport_I32, Args_General3);
       case SymbolicAddress::InvokeImport_I64:
         return FuncCast(InvokeImport_I64, Args_General3);
       case SymbolicAddress::InvokeImport_F64:
         return FuncCast(InvokeImport_F64, Args_General3);
--- a/js/src/asmjs/WasmTypes.h
+++ b/js/src/asmjs/WasmTypes.h
@@ -622,53 +622,76 @@ enum class SymbolicAddress
     ExpD,
     LogD,
     PowD,
     ATan2D,
     Runtime,
     RuntimeInterruptUint32,
     StackLimit,
     ReportOverRecursed,
-    OnOutOfBounds,
-    OnImpreciseConversion,
-    BadIndirectCall,
-    UnreachableTrap,
-    IntegerOverflowTrap,
-    InvalidConversionToIntegerTrap,
     HandleExecutionInterrupt,
+    HandleTrap,
     InvokeImport_Void,
     InvokeImport_I32,
     InvokeImport_I64,
     InvokeImport_F64,
     CoerceInPlace_ToInt32,
     CoerceInPlace_ToNumber,
     Limit
 };
 
 void*
 AddressOf(SymbolicAddress imm, ExclusiveContext* cx);
 
 // Extracts low and high from an int64 object {low: int32, high: int32}, for
 // testing purposes mainly.
 MOZ_MUST_USE bool ReadI64Object(JSContext* cx, HandleValue v, int64_t* val);
 
+// A wasm::Trap is a reason for why we reached a trap in executed code. Each
+// different trap is mapped to a different error message.
+
+enum class Trap
+{
+    // The Unreachable opcode has been executed.
+    Unreachable,
+    // An integer arithmetic operation led to an overflow.
+    IntegerOverflow,
+    // Trying to coerce NaN to an integer.
+    InvalidConversionToInteger,
+    // Integer division by zero.
+    IntegerDivideByZero,
+    // Out of bounds on wasm memory accesses and asm.js SIMD/atomic accesses.
+    OutOfBounds,
+    // Bad signature for an indirect call.
+    BadIndirectCall,
+
+    // (asm.js only) SIMD float to int conversion failed because the input
+    // wasn't in bounds.
+    ImpreciseSimdConversion,
+
+    Limit
+};
+
 // A wasm::JumpTarget represents one of a special set of stubs that can be
 // jumped to from any function. Because wasm modules can be larger than the
 // range of a plain jump, these potentially out-of-range jumps must be recorded
 // and patched specially by the MacroAssembler and ModuleGenerator.
 
 enum class JumpTarget
 {
+    // Traps
+    Unreachable = unsigned(Trap::Unreachable),
+    IntegerOverflow = unsigned(Trap::IntegerOverflow),
+    InvalidConversionToInteger = unsigned(Trap::InvalidConversionToInteger),
+    IntegerDivideByZero = unsigned(Trap::IntegerDivideByZero),
+    OutOfBounds = unsigned(Trap::OutOfBounds),
+    BadIndirectCall = unsigned(Trap::BadIndirectCall),
+    ImpreciseSimdConversion = unsigned(Trap::ImpreciseSimdConversion),
+    // Non-traps
     StackOverflow,
-    OutOfBounds,
-    ConversionError,
-    BadIndirectCall,
-    UnreachableTrap,
-    IntegerOverflowTrap,
-    InvalidConversionToIntegerTrap,
     Throw,
     Limit
 };
 
 typedef EnumeratedArray<JumpTarget, JumpTarget::Limit, Uint32Vector> JumpSiteArray;
 
 // The CompileArgs struct captures global parameters that affect all wasm code
 // generation. It also currently is the single source of truth for whether or
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/bug1268955-usestrict-semantics.js
@@ -0,0 +1,20 @@
+// |jit-test| test-also-noasmjs
+
+var scope = {};
+scope.mod = eval(`"use strict"; (function() { "use asm"; function f() {} return f; });`);
+
+scope.fun = scope.mod();
+
+var caught = false;
+for (let callee of ['mod', 'fun']) {
+    for (let getter of ['caller', 'arguments']) {
+        caught = false;
+        try {
+            scope[callee][getter];
+        } catch (e) {
+            caught = true;
+            assertEq(e instanceof TypeError, true);
+        }
+        assertEq(caught, true);
+    }
+}
--- a/js/src/jit-test/tests/wasm/basic-integer.js
+++ b/js/src/jit-test/tests/wasm/basic-integer.js
@@ -43,16 +43,27 @@ function testComparison64(opcode, lhs, r
     assertEq(wasmEvalText(`(module
                             (func (param i64) (param i64) (result i32)
                              (if (i64.${opcode} (get_local 0) (get_local 1))
                               (i32.const 1)
                               (i32.const 0)))
                               (export "" 0))`)(lobj, robj), expect);
 }
 
+function testTrap64(opcode, lhs, rhs, expect) {
+    let lobj = createI64(lhs);
+    let robj = createI64(rhs);
+
+    assertErrorMessage(() => wasmEvalText(`(module (func (param i64) (param i64) (result i64) (i64.${opcode} (get_local 0) (get_local 1))) (export "" 0))`)(lobj, robj), Error, expect);
+    // The same, but now the RHS is a constant.
+    assertErrorMessage(() => wasmEvalText(`(module (func (param i64) (result i64) (i64.${opcode} (get_local 0) (i64.const ${rhs}))) (export "" 0))`)(lobj), Error, expect);
+    // LHS and RHS are constants.
+    assertErrorMessage(wasmEvalText(`(module (func (result i64) (i64.${opcode} (i64.const ${lhs}) (i64.const ${rhs}))) (export "" 0))`), Error, expect);
+}
+
 testUnary('i32', 'clz', 40, 26);
 testUnary('i32', 'ctz', 40, 3);
 testUnary('i32', 'ctz', 0, 32);
 testUnary('i32', 'ctz', -2147483648, 31);
 
 testUnary('i32', 'popcnt', 40, 2);
 testUnary('i32', 'popcnt', 0, 0);
 testUnary('i32', 'popcnt', 0xFFFFFFFF, 32);
@@ -119,22 +130,21 @@ if (hasI64()) {
     testBinary64('rem_s', "0x7fffffffffffffff", -1, 0);
     testBinary64('rem_s', "0x8000000000000001", 1000, -807);
     testBinary64('rem_s', "0x8000000000000000", -1, 0);
     testBinary64('rem_u', 40, -3, 40);
     testBinary64('rem_u', "0x1234567887654321", "0x1000000000", "0x887654321");
     testBinary64('rem_u', "0x8000000000000000", -1, "0x8000000000000000");
     testBinary64('rem_u', "0x8ff00ff00ff00ff0", "0x100000001", "0x80000001");
 
-    // These should trap, but for now we match the i32 version.
-    testBinary64('div_s', 10, 0, 0);
-    testBinary64('div_s', "0x8000000000000000", -1, "0x8000000000000000");
-    testBinary64('div_u', 0, 0, 0);
-    testBinary64('rem_s', 10, 0, 0);
-    testBinary64('rem_u', 10, 0, 0);
+    testTrap64('div_s', 10, 0, /integer divide by zero/);
+    testTrap64('div_s', "0x8000000000000000", -1, /integer overflow/);
+    testTrap64('div_u', 0, 0, /integer divide by zero/);
+    testTrap64('rem_s', 10, 0, /integer divide by zero/);
+    testTrap64('rem_u', 10, 0, /integer divide by zero/);
 
     testBinary64('and', 42, 6, 2);
     testBinary64('or', 42, 6, 46);
     testBinary64('xor', 42, 2, 40);
     testBinary64('and', "0x8765432112345678", "0xffff0000ffff0000", "0x8765000012340000");
     testBinary64('or', "0x8765432112345678", "0xffff0000ffff0000", "0xffff4321ffff5678");
     testBinary64('xor', "0x8765432112345678", "0xffff0000ffff0000", "0x789a4321edcb5678");
     testBinary64('shl', 40, 2, 160);
--- a/js/src/jit-test/tests/wasm/random-control-flow.js
+++ b/js/src/jit-test/tests/wasm/random-control-flow.js
@@ -143,8 +143,27 @@ wasmEvalText(`
      (f32.const 0)
      (i32.const 0)
     )
     (i32.const 0)
    )
   )
 )
 `);
+
+wasmEvalText(`
+(module
+ (func
+  (i32.add
+   (block $outer
+    (block $middle
+     (block $inner
+      (br_table $middle $outer $inner (i32.const 42) (i32.const 1))
+     )
+     (nop)
+    )
+    (i32.const 0)
+   )
+   (i32.const 13)
+  )
+ )
+)
+`);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -11198,17 +11198,17 @@ CodeGenerator::visitAsmJSInterruptCheck(
 
     masm.bind(&rejoin);
 }
 
 void
 CodeGenerator::visitAsmThrowUnreachable(LAsmThrowUnreachable* lir)
 {
     MOZ_ASSERT(gen->compilingAsmJS());
-    masm.jump(wasm::JumpTarget::UnreachableTrap);
+    masm.jump(wasm::JumpTarget::Unreachable);
 }
 
 typedef bool (*RecompileFn)(JSContext*);
 static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile);
 
 typedef bool (*ForcedRecompileFn)(JSContext*);
 static const VMFunction ForcedRecompileFnInfo = FunctionInfo<ForcedRecompileFn>(ForcedRecompile);
 
--- a/js/src/jit/FlowAliasAnalysis.cpp
+++ b/js/src/jit/FlowAliasAnalysis.cpp
@@ -75,17 +75,18 @@ class GraphStoreInfo : public TempObject
     // Only keeping the info during iteration if needed, else contains nullptr.
     GraphStoreVector stores_;
 
     // All BlockStoreInfo's that aren't needed anymore and can be reused.
     GraphStoreVector empty_;
 
   public:
     explicit GraphStoreInfo(TempAllocator& alloc)
-      : stores_(alloc),
+      : current_(nullptr),
+        stores_(alloc),
         empty_(alloc)
     { }
 
     bool reserve(size_t num) {
         return stores_.appendN(nullptr, num);
     }
 
     BlockStoreInfo& current() {
--- a/js/src/jit/LoopUnroller.cpp
+++ b/js/src/jit/LoopUnroller.cpp
@@ -16,17 +16,20 @@ using mozilla::ArrayLength;
 namespace {
 
 struct LoopUnroller
 {
     typedef HashMap<MDefinition*, MDefinition*,
                     PointerHasher<MDefinition*, 2>, SystemAllocPolicy> DefinitionMap;
 
     explicit LoopUnroller(MIRGraph& graph)
-      : graph(graph), alloc(graph.alloc())
+      : graph(graph), alloc(graph.alloc()),
+        header(nullptr), backedge(nullptr),
+        unrolledHeader(nullptr), unrolledBackedge(nullptr),
+        oldPreheader(nullptr), newPreheader(nullptr)
     {}
 
     MIRGraph& graph;
     TempAllocator& alloc;
 
     // Header and body of the original loop.
     MBasicBlock* header;
     MBasicBlock* backedge;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6881,43 +6881,46 @@ class MMul : public MBinaryArithInstruct
 
 class MDiv : public MBinaryArithInstruction
 {
     bool canBeNegativeZero_;
     bool canBeNegativeOverflow_;
     bool canBeDivideByZero_;
     bool canBeNegativeDividend_;
     bool unsigned_;
+    bool trapOnError_;
 
     MDiv(MDefinition* left, MDefinition* right, MIRType type)
       : MBinaryArithInstruction(left, right),
         canBeNegativeZero_(true),
         canBeNegativeOverflow_(true),
         canBeDivideByZero_(true),
         canBeNegativeDividend_(true),
-        unsigned_(false)
+        unsigned_(false),
+        trapOnError_(false)
     {
         if (type != MIRType::Value)
             specialization_ = type;
         setResultType(type);
     }
 
   public:
     INSTRUCTION_HEADER(Div)
     static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) {
         return new(alloc) MDiv(left, right, MIRType::Value);
     }
     static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type) {
         return new(alloc) MDiv(left, right, type);
     }
     static MDiv* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right,
-                          MIRType type, bool unsignd)
+                          MIRType type, bool unsignd, bool trapOnError = false)
     {
         MDiv* div = new(alloc) MDiv(left, right, type);
         div->unsigned_ = unsignd;
+        div->trapOnError_ = trapOnError;
         if (type == MIRType::Int32)
             div->setTruncateKind(Truncate);
         return div;
     }
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
     void analyzeEdgeCasesForward() override;
     void analyzeEdgeCasesBackward() override;
@@ -6971,67 +6974,77 @@ class MDiv : public MBinaryArithInstruct
     }
     bool canTruncateOverflow() const {
         return isTruncated() || isTruncatedIndirectly();
     }
     bool canTruncateNegativeZero() const {
         return isTruncated() || isTruncatedIndirectly();
     }
 
+    bool trapOnError() const {
+        return trapOnError_;
+    }
+
     bool isFloat32Commutative() const override { return true; }
 
     void computeRange(TempAllocator& alloc) override;
     bool fallible() const;
     bool needTruncation(TruncateKind kind) override;
     void truncate() override;
     void collectRangeInfoPreTrunc() override;
     TruncateKind operandTruncateKind(size_t index) const override;
 
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         return specialization_ < MIRType::Object;
     }
 
     bool congruentTo(const MDefinition* ins) const override {
-        return MBinaryArithInstruction::congruentTo(ins) &&
-               unsigned_ == ins->toDiv()->isUnsigned();
+        if (!MBinaryArithInstruction::congruentTo(ins))
+            return false;
+        const MDiv* other = ins->toDiv();
+        MOZ_ASSERT(other->trapOnError() == trapOnError_);
+        return unsigned_ == other->isUnsigned();
     }
 
     ALLOW_CLONE(MDiv)
 };
 
 class MMod : public MBinaryArithInstruction
 {
     bool unsigned_;
     bool canBeNegativeDividend_;
     bool canBePowerOfTwoDivisor_;
     bool canBeDivideByZero_;
+    bool trapOnError_;
 
     MMod(MDefinition* left, MDefinition* right, MIRType type)
       : MBinaryArithInstruction(left, right),
         unsigned_(false),
         canBeNegativeDividend_(true),
         canBePowerOfTwoDivisor_(true),
-        canBeDivideByZero_(true)
+        canBeDivideByZero_(true),
+        trapOnError_(false)
     {
         if (type != MIRType::Value)
             specialization_ = type;
         setResultType(type);
     }
 
   public:
     INSTRUCTION_HEADER(Mod)
     static MMod* New(TempAllocator& alloc, MDefinition* left, MDefinition* right) {
         return new(alloc) MMod(left, right, MIRType::Value);
     }
     static MMod* NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right,
-                          MIRType type, bool unsignd)
+                          MIRType type, bool unsignd, bool trapOnError = false)
     {
         MMod* mod = new(alloc) MMod(left, right, type);
         mod->unsigned_ = unsignd;
+        mod->trapOnError_ = trapOnError;
         if (type == MIRType::Int32)
             mod->setTruncateKind(Truncate);
         return mod;
     }
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
 
     double getIdentity() override {
@@ -7055,16 +7068,20 @@ class MMod : public MBinaryArithInstruct
     }
 
     void analyzeEdgeCasesForward() override;
 
     bool isUnsigned() const {
         return unsigned_;
     }
 
+    bool trapOnError() const {
+        return trapOnError_;
+    }
+
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         return specialization_ < MIRType::Object;
     }
 
     bool fallible() const;
 
     void computeRange(TempAllocator& alloc) override;
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -496,38 +496,46 @@ CodeGeneratorARM::divICommon(MDiv* mir, 
         // Handle INT32_MIN / -1;
         // The integer division will give INT32_MIN, but we want -(double)INT32_MIN.
 
         // Sets EQ if lhs == INT32_MIN.
         masm.ma_cmp(lhs, Imm32(INT32_MIN));
         // If EQ (LHS == INT32_MIN), sets EQ if rhs == -1.
         masm.ma_cmp(rhs, Imm32(-1), Assembler::Equal);
         if (mir->canTruncateOverflow()) {
-            // (-INT32_MIN)|0 = INT32_MIN
-            Label skip;
-            masm.ma_b(&skip, Assembler::NotEqual);
-            masm.ma_mov(Imm32(INT32_MIN), output);
-            masm.ma_b(&done);
-            masm.bind(&skip);
+            if (mir->trapOnError()) {
+                masm.ma_b(wasm::JumpTarget::IntegerOverflow, Assembler::Equal);
+            } else {
+                // (-INT32_MIN)|0 = INT32_MIN
+                Label skip;
+                masm.ma_b(&skip, Assembler::NotEqual);
+                masm.ma_mov(Imm32(INT32_MIN), output);
+                masm.ma_b(&done);
+                masm.bind(&skip);
+            }
         } else {
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Equal, snapshot);
         }
     }
 
     // Handle divide by zero.
     if (mir->canBeDivideByZero()) {
         masm.ma_cmp(rhs, Imm32(0));
         if (mir->canTruncateInfinities()) {
-            // Infinity|0 == 0
-            Label skip;
-            masm.ma_b(&skip, Assembler::NotEqual);
-            masm.ma_mov(Imm32(0), output);
-            masm.ma_b(&done);
-            masm.bind(&skip);
+            if (mir->trapOnError()) {
+                masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal);
+            } else {
+                // Infinity|0 == 0
+                Label skip;
+                masm.ma_b(&skip, Assembler::NotEqual);
+                masm.ma_mov(Imm32(0), output);
+                masm.ma_b(&done);
+                masm.bind(&skip);
+            }
         } else {
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Equal, snapshot);
         }
     }
 
     // Handle negative 0.
     if (!mir->canTruncateNegativeZero() && mir->canBeNegativeZero()) {
@@ -539,17 +547,16 @@ CodeGeneratorARM::divICommon(MDiv* mir, 
         bailoutIf(Assembler::LessThan, snapshot);
         masm.bind(&nonzero);
     }
 }
 
 void
 CodeGeneratorARM::visitDivI(LDivI* ins)
 {
-    // Extract the registers from this instruction.
     Register lhs = ToRegister(ins->lhs());
     Register rhs = ToRegister(ins->rhs());
     Register temp = ToRegister(ins->getTemp(0));
     Register output = ToRegister(ins->output());
     MDiv* mir = ins->mir();
 
     Label done;
     divICommon(mir, lhs, rhs, output, ins->snapshot(), done);
@@ -573,17 +580,16 @@ CodeGeneratorARM::visitDivI(LDivI* ins)
 extern "C" {
     extern MOZ_EXPORT int64_t __aeabi_idivmod(int,int);
     extern MOZ_EXPORT int64_t __aeabi_uidivmod(int,int);
 }
 
 void
 CodeGeneratorARM::visitSoftDivI(LSoftDivI* ins)
 {
-    // Extract the registers from this instruction.
     Register lhs = ToRegister(ins->lhs());
     Register rhs = ToRegister(ins->rhs());
     Register output = ToRegister(ins->output());
     MDiv* mir = ins->mir();
 
     Label done;
     divICommon(mir, lhs, rhs, output, ins->snapshot(), done);
 
@@ -668,22 +674,26 @@ CodeGeneratorARM::modICommon(MMod* mir, 
     // flags necessary for LT to trigger, we don't test X, and take the bailout
     // because the EQ flag is set.
     // If (Y > 0), we don't set EQ, and we don't trigger LT, so we don't take
     // the bailout.
     if (mir->canBeDivideByZero() || mir->canBeNegativeDividend()) {
         masm.ma_cmp(rhs, Imm32(0));
         masm.ma_cmp(lhs, Imm32(0), Assembler::LessThan);
         if (mir->isTruncated()) {
-            // NaN|0 == 0 and (0 % -X)|0 == 0
-            Label skip;
-            masm.ma_b(&skip, Assembler::NotEqual);
-            masm.ma_mov(Imm32(0), output);
-            masm.ma_b(&done);
-            masm.bind(&skip);
+            if (mir->trapOnError()) {
+                masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal);
+            } else {
+                // NaN|0 == 0 and (0 % -X)|0 == 0
+                Label skip;
+                masm.ma_b(&skip, Assembler::NotEqual);
+                masm.ma_mov(Imm32(0), output);
+                masm.ma_b(&done);
+                masm.bind(&skip);
+            }
         } else {
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Equal, snapshot);
         }
     }
 }
 
 void
@@ -2643,22 +2653,26 @@ void
 CodeGeneratorARM::generateUDivModZeroCheck(Register rhs, Register output, Label* done,
                                            LSnapshot* snapshot, T* mir)
 {
     if (!mir)
         return;
     if (mir->canBeDivideByZero()) {
         masm.ma_cmp(rhs, Imm32(0));
         if (mir->isTruncated()) {
-            Label skip;
-            masm.ma_b(&skip, Assembler::NotEqual);
-            // Infinity|0 == 0
-            masm.ma_mov(Imm32(0), output);
-            masm.ma_b(done);
-            masm.bind(&skip);
+            if (mir->trapOnError()) {
+                masm.ma_b(wasm::JumpTarget::IntegerDivideByZero, Assembler::Equal);
+            } else {
+                Label skip;
+                masm.ma_b(&skip, Assembler::NotEqual);
+                // Infinity|0 == 0
+                masm.ma_mov(Imm32(0), output);
+                masm.ma_b(done);
+                masm.bind(&skip);
+            }
         } else {
             // Bailout for divide by zero
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Equal, snapshot);
         }
     }
 }
 
@@ -2960,13 +2974,13 @@ CodeGeneratorARM::visitOutOfLineWasmTrun
         masm.branchFloat(Assembler::DoubleGreaterThanOrEqual, input, scratch, &fail);
     }
 
     // We had an actual correct value, get back to where we were.
     masm.ma_b(ool->rejoin());
 
     // Handle errors.
     masm.bind(&fail);
-    masm.jump(wasm::JumpTarget::IntegerOverflowTrap);
+    masm.jump(wasm::JumpTarget::IntegerOverflow);
 
     masm.bind(&inputIsNaN);
-    masm.jump(wasm::JumpTarget::InvalidConversionToIntegerTrap);
+    masm.jump(wasm::JumpTarget::InvalidConversionToInteger);
 }
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -309,16 +309,17 @@ LIRGeneratorARM::lowerModI(MMod* mod)
         if (rhs > 0 && 1 << shift == rhs) {
             LModPowTwoI* lir = new(alloc()) LModPowTwoI(useRegister(mod->lhs()), shift);
             if (mod->fallible())
                 assignSnapshot(lir, Bailout_DoubleOutput);
             define(lir, mod);
             return;
         }
         if (shift < 31 && (1 << (shift+1)) - 1 == rhs) {
+            MOZ_ASSERT(rhs);
             LModMaskI* lir = new(alloc()) LModMaskI(useRegister(mod->lhs()), temp(), temp(), shift+1);
             if (mod->fallible())
                 assignSnapshot(lir, Bailout_DoubleOutput);
             define(lir, mod);
             return;
         }
     }
 
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -14,16 +14,18 @@
 #include "jsscriptinlines.h"
 
 #include "jit/MacroAssembler-inl.h"
 #include "jit/shared/CodeGenerator-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
+using mozilla::DebugOnly;
+
 CodeGeneratorX64::CodeGeneratorX64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorX86Shared(gen, graph, masm)
 {
 }
 
 ValueOperand
 CodeGeneratorX64::ToValue(LInstruction* ins, size_t pos)
 {
@@ -427,76 +429,64 @@ CodeGeneratorX64::visitDivOrModI64(LDivO
     MOZ_ASSERT_IF(output == rdx, ToRegister(lir->remainder()) == rax);
 
     Label done;
 
     // Put the lhs in rax.
     if (lhs != rax)
         masm.mov(lhs, rax);
 
-    // Handle divide by zero. For now match asm.js and return 0, but
-    // eventually this should trap.
+    // Handle divide by zero.
     if (lir->canBeDivideByZero()) {
-        Label nonZero;
-        masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero);
-        masm.xorl(output, output);
-        masm.jump(&done);
-        masm.bind(&nonZero);
+        masm.testPtr(rhs, rhs);
+        masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
     }
 
-    // Handle an integer overflow exception from INT64_MIN / -1. Eventually
-    // signed integer division should trap, instead of returning the
-    // LHS (INT64_MIN).
+    // Handle an integer overflow exception from INT64_MIN / -1.
     if (lir->canBeNegativeOverflow()) {
         Label notmin;
         masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), &notmin);
         masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), &notmin);
-        if (lir->mir()->isMod()) {
+        if (lir->mir()->isMod())
             masm.xorl(output, output);
-        } else {
-            if (lhs != output)
-                masm.mov(lhs, output);
-        }
+        else
+            masm.jump(wasm::JumpTarget::IntegerOverflow);
         masm.jump(&done);
         masm.bind(&notmin);
     }
 
     // Sign extend the lhs into rdx to make rdx:rax.
     masm.cqo();
     masm.idivq(rhs);
 
     masm.bind(&done);
 }
 
 void
 CodeGeneratorX64::visitUDivOrMod64(LUDivOrMod64* lir)
 {
     Register lhs = ToRegister(lir->lhs());
     Register rhs = ToRegister(lir->rhs());
-    Register output = ToRegister(lir->output());
 
+    DebugOnly<Register> output = ToRegister(lir->output());
     MOZ_ASSERT_IF(lhs != rhs, rhs != rax);
     MOZ_ASSERT(rhs != rdx);
-    MOZ_ASSERT_IF(output == rax, ToRegister(lir->remainder()) == rdx);
-    MOZ_ASSERT_IF(output == rdx, ToRegister(lir->remainder()) == rax);
+    MOZ_ASSERT_IF(output.value == rax, ToRegister(lir->remainder()) == rdx);
+    MOZ_ASSERT_IF(output.value == rdx, ToRegister(lir->remainder()) == rax);
 
     // Put the lhs in rax.
     if (lhs != rax)
         masm.mov(lhs, rax);
 
     Label done;
 
-    // Prevent divide by zero. For now match asm.js and return 0, but
-    // eventually this should trap.
+    // Prevent divide by zero.
     if (lir->canBeDivideByZero()) {
-        Label nonZero;
-        masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero);
-        masm.xorl(output, output);
-        masm.jump(&done);
-        masm.bind(&nonZero);
+        masm.testPtr(rhs, rhs);
+        masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
     }
 
     // Zero extend the lhs into rdx to make (rdx:rax).
     masm.xorl(rdx, rdx);
     masm.udivq(rhs);
 
     masm.bind(&done);
 }
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -1110,19 +1110,23 @@ CodeGeneratorX86Shared::visitUDivOrMod(L
     // Put the lhs in eax.
     if (lhs != eax)
         masm.mov(lhs, eax);
 
     // Prevent divide by zero.
     if (ins->canBeDivideByZero()) {
         masm.test32(rhs, rhs);
         if (ins->mir()->isTruncated()) {
-            if (!ool)
-                ool = new(alloc()) ReturnZero(output);
-            masm.j(Assembler::Zero, ool->entry());
+            if (ins->trapOnError()) {
+                masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
+            } else {
+                if (!ool)
+                    ool = new(alloc()) ReturnZero(output);
+                masm.j(Assembler::Zero, ool->entry());
+            }
         } else {
             bailoutIf(Assembler::Zero, ins->snapshot());
         }
     }
 
     // Zero extend the lhs into edx to make (edx:eax), since udiv is 64-bit.
     masm.mov(ImmWord(0), edx);
     masm.udiv(rhs);
@@ -1154,21 +1158,24 @@ CodeGeneratorX86Shared::visitUDivOrModCo
     uint32_t d = ins->denominator();
 
     // This emits the division answer into edx or the modulus answer into eax.
     MOZ_ASSERT(output == eax || output == edx);
     MOZ_ASSERT(lhs != eax && lhs != edx);
     bool isDiv = (output == edx);
 
     if (d == 0) {
-        if (ins->mir()->isTruncated())
-            masm.xorl(output, output);
-        else
+        if (ins->mir()->isTruncated()) {
+            if (ins->trapOnError())
+                masm.jump(wasm::JumpTarget::IntegerDivideByZero);
+            else
+                masm.xorl(output, output);
+        } else {
             bailout(ins->snapshot());
-
+        }
         return;
     }
 
     // The denominator isn't a power of 2 (see LDivPowTwoI and LModPowTwoI).
     MOZ_ASSERT((d & (d - 1)) != 0);
 
     ReciprocalMulConstants rmc = computeDivisionConstants(d, /* maxLog = */ 32);
 
@@ -1256,17 +1263,17 @@ CodeGeneratorX86Shared::visitDivPowTwoI(
     MOZ_ASSERT(lhs == output);
 
     if (!mir->isTruncated() && negativeDivisor) {
         // 0 divided by a negative number must return a double.
         masm.test32(lhs, lhs);
         bailoutIf(Assembler::Zero, ins->snapshot());
     }
 
-    if (shift != 0) {
+    if (shift) {
         if (!mir->isTruncated()) {
             // If the remainder is != 0, bailout since this must be a double.
             masm.test32(lhs, Imm32(UINT32_MAX >> (32 - shift)));
             bailoutIf(Assembler::NonZero, ins->snapshot());
         }
 
         if (mir->isUnsigned()) {
             masm.shrl(Imm32(shift), lhs);
@@ -1283,30 +1290,29 @@ CodeGeneratorX86Shared::visitDivPowTwoI(
                 masm.shrl(Imm32(32 - shift), lhs);
                 masm.addl(lhsCopy, lhs);
             }
             masm.sarl(Imm32(shift), lhs);
 
             if (negativeDivisor)
                 masm.negl(lhs);
         }
-    } else if (shift == 0) {
-        if (negativeDivisor) {
-            // INT32_MIN / -1 overflows.
-            masm.negl(lhs);
-            if (!mir->isTruncated())
-                bailoutIf(Assembler::Overflow, ins->snapshot());
-        }
-
-        else if (mir->isUnsigned() && !mir->isTruncated()) {
-            // Unsigned division by 1 can overflow if output is not
-            // truncated.
-            masm.test32(lhs, lhs);
-            bailoutIf(Assembler::Signed, ins->snapshot());
-        }
+        return;
+    }
+
+    if (negativeDivisor) {
+        // INT32_MIN / -1 overflows.
+        masm.negl(lhs);
+        if (!mir->isTruncated())
+            bailoutIf(Assembler::Overflow, ins->snapshot());
+    } else if (mir->isUnsigned() && !mir->isTruncated()) {
+        // Unsigned division by 1 can overflow if output is not
+        // truncated.
+        masm.test32(lhs, lhs);
+        bailoutIf(Assembler::Signed, ins->snapshot());
     }
 }
 
 void
 CodeGeneratorX86Shared::visitDivOrModConstantI(LDivOrModConstantI* ins) {
     Register lhs = ToRegister(ins->numerator());
     Register output = ToRegister(ins->output());
     int32_t d = ins->denominator();
@@ -1409,34 +1415,38 @@ CodeGeneratorX86Shared::visitDivI(LDivI*
     // Put the lhs in eax, for either the negative overflow case or the regular
     // divide case.
     if (lhs != eax)
         masm.mov(lhs, eax);
 
     // Handle divide by zero.
     if (mir->canBeDivideByZero()) {
         masm.test32(rhs, rhs);
-        if (mir->canTruncateInfinities()) {
+        if (mir->trapOnError()) {
+            masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
+        } else if (mir->canTruncateInfinities()) {
             // Truncated division by zero is zero (Infinity|0 == 0)
             if (!ool)
                 ool = new(alloc()) ReturnZero(output);
             masm.j(Assembler::Zero, ool->entry());
         } else {
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Zero, ins->snapshot());
         }
     }
 
     // Handle an integer overflow exception from -2147483648 / -1.
     if (mir->canBeNegativeOverflow()) {
         Label notmin;
         masm.cmp32(lhs, Imm32(INT32_MIN));
         masm.j(Assembler::NotEqual, &notmin);
         masm.cmp32(rhs, Imm32(-1));
-        if (mir->canTruncateOverflow()) {
+        if (mir->trapOnError()) {
+            masm.j(Assembler::Equal, wasm::JumpTarget::IntegerOverflow);
+        } else if (mir->canTruncateOverflow()) {
             // (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in the
             // output register (lhs == eax).
             masm.j(Assembler::Equal, &done);
         } else {
             MOZ_ASSERT(mir->fallible());
             bailoutIf(Assembler::Equal, ins->snapshot());
         }
         masm.bind(&notmin);
@@ -1572,19 +1582,23 @@ CodeGeneratorX86Shared::visitModI(LModI*
     // Set up eax in preparation for doing a div.
     if (lhs != eax)
         masm.mov(lhs, eax);
 
     // Prevent divide by zero.
     if (ins->mir()->canBeDivideByZero()) {
         masm.test32(rhs, rhs);
         if (ins->mir()->isTruncated()) {
-            if (!ool)
-                ool = new(alloc()) ReturnZero(edx);
-            masm.j(Assembler::Zero, ool->entry());
+            if (ins->mir()->trapOnError()) {
+                masm.j(Assembler::Zero, wasm::JumpTarget::IntegerDivideByZero);
+            } else {
+                if (!ool)
+                    ool = new(alloc()) ReturnZero(edx);
+                masm.j(Assembler::Zero, ool->entry());
+            }
         } else {
             bailoutIf(Assembler::Zero, ins->snapshot());
         }
     }
 
     Label negative;
 
     // Switch based on sign of the lhs.
@@ -2493,17 +2507,17 @@ CodeGeneratorX86Shared::visitOutOfLineSi
     masm.vcmpleps(Operand(input), scratch, scratch);
     masm.vmovmskps(scratch, temp);
     masm.cmp32(temp, Imm32(0));
     masm.j(Assembler::NotEqual, &onConversionError);
 
     masm.jump(ool->rejoin());
 
     if (gen->compilingAsmJS()) {
-        masm.bindLater(&onConversionError, wasm::JumpTarget::ConversionError);
+        masm.bindLater(&onConversionError, wasm::JumpTarget::ImpreciseSimdConversion);
     } else {
         masm.bind(&onConversionError);
         bailout(ool->ins()->snapshot());
     }
 }
 
 // Convert Float32x4 to Uint32x4.
 //
@@ -2572,17 +2586,17 @@ CodeGeneratorX86Shared::visitFloat32x4To
 
     // We still need to filter out the V-lanes. They would show up as 0x80000000
     // in both A and B. Since we cleared the valid A-lanes in B, the V-lanes are
     // the remaining negative lanes in B.
     masm.vmovmskps(scratch, temp);
     masm.cmp32(temp, Imm32(0));
 
     if (gen->compilingAsmJS())
-        masm.j(Assembler::NotEqual, wasm::JumpTarget::ConversionError);
+        masm.j(Assembler::NotEqual, wasm::JumpTarget::ImpreciseSimdConversion);
     else
         bailoutIf(Assembler::NotEqual, ins->snapshot());
 }
 
 void
 CodeGeneratorX86Shared::visitSimdValueInt32x4(LSimdValueInt32x4* ins)
 {
     MOZ_ASSERT(ins->mir()->type() == MIRType::Int32x4 || ins->mir()->type() == MIRType::Bool32x4);
@@ -4066,20 +4080,20 @@ CodeGeneratorX86Shared::visitOutOfLineWa
                 masm.branchFloat(Assembler::DoubleNotEqual, input, ScratchFloat32Reg, &fail);
             }
         }
         masm.jump(ool->rejoin());
     }
 
     // Handle errors.
     masm.bind(&fail);
-    masm.jump(wasm::JumpTarget::IntegerOverflowTrap);
+    masm.jump(wasm::JumpTarget::IntegerOverflow);
 
     masm.bind(&inputIsNaN);
-    masm.jump(wasm::JumpTarget::InvalidConversionToIntegerTrap);
+    masm.jump(wasm::JumpTarget::InvalidConversionToInteger);
 }
 
 void
 CodeGeneratorX86Shared::emitWasmSignedTruncateToInt32(OutOfLineWasmTruncateCheck* ool,
                                                       Register output)
 {
     FloatRegister input = ool->input();
     MIRType fromType = ool->fromType();
--- a/js/src/jit/x86-shared/LIR-x86-shared.h
+++ b/js/src/jit/x86-shared/LIR-x86-shared.h
@@ -156,16 +156,22 @@ class LUDivOrMod : public LBinaryMath<1>
         return static_cast<MBinaryArithInstruction*>(mir_);
     }
 
     bool canBeDivideByZero() const {
         if (mir_->isMod())
             return mir_->toMod()->canBeDivideByZero();
         return mir_->toDiv()->canBeDivideByZero();
     }
+
+    bool trapOnError() const {
+        if (mir_->isMod())
+            return mir_->toMod()->trapOnError();
+        return mir_->toDiv()->trapOnError();
+    }
 };
 
 class LUDivOrModConstant : public LInstructionHelper<1, 1, 1>
 {
     const uint32_t denominator_;
 
   public:
     LIR_HEADER(UDivOrModConstant)
@@ -187,16 +193,21 @@ class LUDivOrModConstant : public LInstr
         MOZ_ASSERT(mir_->isDiv() || mir_->isMod());
         return static_cast<MBinaryArithInstruction *>(mir_);
     }
     bool canBeNegativeDividend() const {
         if (mir_->isMod())
             return mir_->toMod()->canBeNegativeDividend();
         return mir_->toDiv()->canBeNegativeDividend();
     }
+    bool trapOnError() const {
+        if (mir_->isMod())
+            return mir_->toMod()->trapOnError();
+        return mir_->toDiv()->trapOnError();
+    }
 };
 
 class LModPowTwoI : public LInstructionHelper<1,1,0>
 {
     const int32_t shift_;
 
   public:
     LIR_HEADER(ModPowTwoI)
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -345,16 +345,17 @@ MSG_DEF(JSMSG_WASM_FAIL,               1
 MSG_DEF(JSMSG_WASM_DECODE_FAIL,        2, JSEXN_TYPEERR, "wasm validation error at offset {0}: {1}")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR, "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_BAD_IND_CALL,       0, JSEXN_ERR,     "wasm indirect call signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR, "first argument must be a typed array")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR, "second argument, if present, must be an object")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_ERR,     "reached unreachable trap")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_ERR,     "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR,     "invalid conversion to integer")
+MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR,     "integer divide by zero")
 
 // Proxy
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,   2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
 MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
 MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
 MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
 MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -109,32 +109,41 @@ AdvanceToActiveCallLinear(JSContext* cx,
 
 static void
 ThrowTypeErrorBehavior(JSContext* cx)
 {
     JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
                                  JSMSG_THROW_TYPE_ERROR);
 }
 
+static bool
+IsFunctionInStrictMode(JSFunction* fun)
+{
+    // Interpreted functions have a strict flag.
+    if (fun->isInterpreted() && fun->strict())
+        return true;
+
+    // Only asm.js functions can also be strict.
+    return IsAsmJSStrictModeModuleOrFunction(fun);
+}
+
 // Beware: this function can be invoked on *any* function! That includes
 // natives, strict mode functions, bound functions, arrow functions,
 // self-hosted functions and constructors, asm.js functions, functions with
 // destructuring arguments and/or a rest argument, and probably a few more I
 // forgot. Turn back and save yourself while you still can. It's too late for
 // me.
 static bool
 ArgumentsRestrictions(JSContext* cx, HandleFunction fun)
 {
     // Throw if the function is a builtin (note: this doesn't include asm.js),
-    // a strict mode function (FIXME: needs work handle strict asm.js functions
-    // correctly, should fall out of bug 1057208), or a bound function.
-    if (fun->isBuiltin() ||
-        (fun->isInterpreted() && fun->strict()) ||
-        fun->isBoundFunction())
-    {
+    // a strict mode function, or a bound function.
+    // TODO (bug 1057208): ensure semantics are correct for all possible
+    // pairings of callee/caller.
+    if (fun->isBuiltin() || IsFunctionInStrictMode(fun) || fun->isBoundFunction()) {
         ThrowTypeErrorBehavior(cx);
         return false;
     }
 
     // Otherwise emit a strict warning about |f.arguments| to discourage use of
     // this non-standard, performance-harmful feature.
     if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING | JSREPORT_STRICT, GetErrorMessage,
                                       nullptr, JSMSG_DEPRECATED_USAGE, js_arguments_str))
@@ -208,22 +217,20 @@ ArgumentsSetter(JSContext* cx, unsigned 
 // self-hosted functions and constructors, asm.js functions, functions with
 // destructuring arguments and/or a rest argument, and probably a few more I
 // forgot. Turn back and save yourself while you still can. It's too late for
 // me.
 static bool
 CallerRestrictions(JSContext* cx, HandleFunction fun)
 {
     // Throw if the function is a builtin (note: this doesn't include asm.js),
-    // a strict mode function (FIXME: needs work handle strict asm.js functions
-    // correctly, should fall out of bug 1057208), or a bound function.
-    if (fun->isBuiltin() ||
-        (fun->isInterpreted() && fun->strict()) ||
-        fun->isBoundFunction())
-    {
+    // a strict mode function, or a bound function.
+    // TODO (bug 1057208): ensure semantics are correct for all possible
+    // pairings of callee/caller.
+    if (fun->isBuiltin() || IsFunctionInStrictMode(fun) || fun->isBoundFunction()) {
         ThrowTypeErrorBehavior(cx);
         return false;
     }
 
     // Otherwise emit a strict warning about |f.caller| to discourage use of
     // this non-standard, performance-harmful feature.
     if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING | JSREPORT_STRICT, GetErrorMessage,
                                       nullptr, JSMSG_DEPRECATED_USAGE, js_caller_str))
--- a/js/src/threading/posix/ConditionVariable.cpp
+++ b/js/src/threading/posix/ConditionVariable.cpp
@@ -32,17 +32,17 @@ static const long NanoSecPerSec = 100000
 // The C++ specification defines std::condition_variable::wait_for in terms of
 // std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC.
 static const clockid_t WhichClock = CLOCK_MONOTONIC;
 
 // While timevaladd is widely available to work with timevals, the newer
 // timespec structure is largely lacking such conveniences. Thankfully, the
 // utilities available in MFBT make implementing our own quite easy.
 static void
-timespecadd(struct timespec* lhs, struct timespec* rhs, struct timespec* result)
+moz_timespecadd(struct timespec* lhs, struct timespec* rhs, struct timespec* result)
 {
   // Add nanoseconds. This may wrap, but not above 2 billion.
   MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec);
   MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec);
   result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec;
 
   // Add seconds, checking for overflow in the platform specific time_t type.
   CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec;
@@ -144,17 +144,17 @@ js::ConditionVariable::wait_for(UniqueLo
   rel_ts.tv_nsec = static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec;
 
 #ifdef USE_CLOCK_API
   struct timespec now_ts;
   r = clock_gettime(WhichClock, &now_ts);
   MOZ_RELEASE_ASSERT(!r);
 
   struct timespec abs_ts;
-  timespecadd(&now_ts, &rel_ts, &abs_ts);
+  moz_timespecadd(&now_ts, &rel_ts, &abs_ts);
 
   r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts);
 #else
   // Our non-clock-supporting platforms, OS X and Android, do support waiting
   // on a condition variable with a relative timeout.
   r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts);
 #endif
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -351,16 +351,17 @@ struct JSStructuredCloneWriter {
     void* closure;
 
     // Set of transferable objects
     RootedValue transferable;
     Rooted<GCHashSet<JSObject*>> transferableObjects;
 
     friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
     friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
+    friend bool JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj);
 };
 
 JS_FRIEND_API(uint64_t)
 js::GetSCOffset(JSStructuredCloneWriter* writer)
 {
     MOZ_ASSERT(writer);
     return writer->output().count() * sizeof(uint64_t);
 }
@@ -1758,17 +1759,17 @@ JSStructuredCloneReader::startRead(Mutab
                         : (JSObject*) NewBuiltinClassInstance<PlainObject>(context());
         if (!obj || !objs.append(ObjectValue(*obj)))
             return false;
         vp.setObject(*obj);
         break;
       }
 
       case SCTAG_BACK_REFERENCE_OBJECT: {
-        if (data >= allObjs.length()) {
+        if (data >= allObjs.length() || !allObjs[data].isObject()) {
             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA,
                                  "invalid back reference in input");
             return false;
         }
         vp.set(allObjs[data]);
         return true;
       }
@@ -2417,8 +2418,16 @@ JS_WriteString(JSStructuredCloneWriter* 
 JS_PUBLIC_API(bool)
 JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v)
 {
     MOZ_ASSERT(v.isObject());
     assertSameCompartment(w->context(), v);
     RootedObject obj(w->context(), &v.toObject());
     return w->writeTypedArray(obj);
 }
+
+JS_PUBLIC_API(bool)
+JS_ObjectNotWritten(JSStructuredCloneWriter* w, HandleObject obj)
+{
+    w->memory.remove(w->memory.lookup(obj));
+
+    return true;
+}
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -573,35 +573,16 @@ GetJSRuntime();
 void AddGCCallback(xpcGCCallback cb);
 void RemoveGCCallback(xpcGCCallback cb);
 
 } // namespace xpc
 
 namespace mozilla {
 namespace dom {
 
-typedef JSObject*
-(*DefineInterface)(JSContext* cx, JS::Handle<JSObject*> global,
-                   JS::Handle<jsid> id, bool defineOnGlobal);
-
-typedef JSObject*
-(*ConstructNavigatorProperty)(JSContext* cx, JS::Handle<JSObject*> naviObj);
-
-// Check whether a constructor should be enabled for the given object.
-// Note that the object should NOT be an Xray, since Xrays will end up
-// defining constructors on the underlying object.
-// This is a typedef for the function type itself, not the function
-// pointer, so it's more obvious that pointers to a ConstructorEnabled
-// can be null.
-typedef bool
-(ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj);
-
-void
-Register(nsScriptNameSpaceManager* aNameSpaceManager);
-
 /**
  * A test for whether WebIDL methods that should only be visible to
  * chrome or XBL scopes should be exposed.
  */
 bool IsChromeOrXBL(JSContext* cx, JSObject* /* unused */);
 
 } // namespace dom
 } // namespace mozilla
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -38,16 +38,19 @@
 #include "nsTransitionManager.h"
 #include "mozilla/LayerTimelineMarker.h"
 
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/Move.h"
 #include "mozilla/ReverseIterator.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Tools.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TextureWrapperImage.h"
 #include "mozilla/unused.h"
 #include "GeckoProfiler.h"
 #include "LayersLogging.h"
 #include "gfxPrefs.h"
 
 #include <algorithm>
 
 using namespace mozilla::layers;
@@ -89,29 +92,42 @@ uint8_t gImageLayerUserData;
 uint8_t gLayerManagerUserData;
 /**
  * The address of gMaskLayerUserData is used as the user
  * data key for mask layers managed by FrameLayerBuilder.
  * The user data is a MaskLayerUserData.
  */
 uint8_t gMaskLayerUserData;
 
+// a global cache of image containers used for mask layers
+static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
+
+static inline MaskLayerImageCache* GetMaskLayerImageCache()
+{
+  if (!gMaskLayerImageCache) {
+    gMaskLayerImageCache = new MaskLayerImageCache();
+  }
+
+  return gMaskLayerImageCache;
+}
+
 FrameLayerBuilder::FrameLayerBuilder()
   : mRetainingManager(nullptr)
   , mDetectedDOMModification(false)
   , mInvalidateAllLayers(false)
   , mInLayerTreeCompressionMode(false)
   , mContainerLayerGeneration(0)
   , mMaxContainerLayerGeneration(0)
 {
   MOZ_COUNT_CTOR(FrameLayerBuilder);
 }
 
 FrameLayerBuilder::~FrameLayerBuilder()
 {
+  GetMaskLayerImageCache()->Sweep();
   MOZ_COUNT_DTOR(FrameLayerBuilder);
 }
 
 FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
                                                     Layer* aLayer, nsIFrame* aFrame)
 
   : mParent(aParent)
   , mLayer(aLayer)
@@ -366,28 +382,16 @@ public:
 
 /* static */ void
 FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame)
 {
   FrameProperties props = aFrame->Properties();
   props.Delete(LayerManagerDataProperty());
 }
 
-// a global cache of image containers used for mask layers
-static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
-
-static inline MaskLayerImageCache* GetMaskLayerImageCache()
-{
-  if (!gMaskLayerImageCache) {
-    gMaskLayerImageCache = new MaskLayerImageCache();
-  }
-
-  return gMaskLayerImageCache;
-}
-
 struct AssignedDisplayItem
 {
   AssignedDisplayItem(nsDisplayItem* aItem,
                       const DisplayItemClip& aClip,
                       LayerState aLayerState)
     : mItem(aItem)
     , mClip(aClip)
     , mLayerState(aLayerState)
@@ -1526,16 +1530,101 @@ struct MaskLayerUserData : public LayerU
   nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
   // scale from the masked layer which is applied to the mask
   float mScaleX, mScaleY;
   // The ContainerLayerParameters offset which is applied to the mask's transform.
   nsIntPoint mOffset;
   int32_t mAppUnitsPerDevPixel;
 };
 
+class MaskImageData
+{
+public:
+  MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
+    : mTextureClientLocked(false)
+    , mSize(aSize)
+    , mLayerManager(aLayerManager)
+  {
+  }
+
+  ~MaskImageData()
+  {
+    if (mTextureClientLocked) {
+      MOZ_ASSERT(mTextureClient);
+      // Clear DrawTarget before Unlock.
+      mDrawTarget = nullptr;
+      mTextureClient->Unlock();
+    }
+  }
+
+  gfx::DrawTarget* CreateDrawTarget()
+  {
+    MOZ_ASSERT(mLayerManager);
+    if (mDrawTarget) {
+      return mDrawTarget;
+    }
+
+    if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+      mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
+      return mDrawTarget;
+    }
+
+    MOZ_ASSERT(mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT);
+
+    ShadowLayerForwarder* fwd = mLayerManager->AsShadowForwarder();
+    if (!fwd) {
+      return nullptr;
+    }
+    mTextureClient =
+      TextureClient::CreateForDrawing(fwd,
+                                      SurfaceFormat::A8,
+                                      mSize,
+                                      BackendSelector::Content,
+                                      TextureFlags::DISALLOW_BIGIMAGE,
+                                      TextureAllocationFlags::ALLOC_CLEAR_BUFFER);
+    if (!mTextureClient) {
+      return nullptr;
+    }
+
+    mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
+    if (!mTextureClientLocked) {
+      return nullptr;
+    }
+
+    mDrawTarget = mTextureClient->BorrowDrawTarget();
+    return mDrawTarget;
+  }
+
+  already_AddRefed<Image> CreateImage()
+  {
+    if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
+        mDrawTarget) {
+      RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
+      RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
+      return image.forget();
+    }
+
+    if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT &&
+        mTextureClient &&
+        mDrawTarget) {
+      RefPtr<TextureWrapperImage> image =
+          new TextureWrapperImage(mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
+      return image.forget();
+    }
+    return nullptr;
+  }
+
+private:
+  bool mTextureClientLocked;
+  gfx::IntSize mSize;
+  LayerManager* mLayerManager;
+  RefPtr<gfx::DrawTarget> mDrawTarget;
+  RefPtr<TextureClient> mTextureClient;
+};
+
 /**
   * Helper functions for getting user data and casting it to the correct type.
   * aLayer is the layer where the user data is stored.
   */
 MaskLayerUserData* GetMaskLayerUserData(Layer* aLayer)
 {
   return static_cast<MaskLayerUserData*>(aLayer->GetUserData(&gMaskLayerUserData));
 }
@@ -5934,30 +6023,31 @@ ContainerState::CreateMaskLayer(Layer *a
 
   // copy and transform the rounded rects
   for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) {
     newKey->mRoundedClipRects.AppendElement(
       MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i],
                                             mContainerFrame->PresContext()));
     newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform);
   }
+  newKey->mForwarder = mManager->AsShadowForwarder();
 
   const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey;
 
   // check to see if we can reuse a mask image
   RefPtr<ImageContainer> container =
     GetMaskLayerImageCache()->FindImageFor(&lookupKey);
 
   if (!container) {
     // Make mask image width aligned to 4. See Bug 1245552.
     IntSize surfaceSizeInt(GetAlignedStride<4>(NSToIntCeil(surfaceSize.width)),
                            NSToIntCeil(surfaceSize.height));
     // no existing mask image, so build a new one
-    RefPtr<DrawTarget> dt =
-      aLayer->Manager()->CreateOptimalMaskDrawTarget(surfaceSizeInt);
+    MaskImageData imageData(surfaceSizeInt, mManager);
+    RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
 
     // fail if we can't get the right surface
     if (!dt || !dt->IsValid()) {
       NS_WARNING("Could not create DrawTarget for mask layer.");
       return nullptr;
     }
 
     RefPtr<gfxContext> context = gfxContext::ForDrawTarget(dt);
@@ -5966,23 +6056,24 @@ ContainerState::CreateMaskLayer(Layer *a
 
     // paint the clipping rects with alpha to create the mask
     aClip.FillIntersectionOfRoundedRectClips(context,
                                              Color(1.f, 1.f, 1.f, 1.f),
                                              newData.mAppUnitsPerDevPixel,
                                              0,
                                              aRoundedRectClipCount);
 
-    RefPtr<SourceSurface> surface = dt->Snapshot();
-
     // build the image and container
     container = aLayer->Manager()->CreateImageContainer();
     NS_ASSERTION(container, "Could not create image container for mask layer.");
 
-    RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surfaceSizeInt, surface);
+    RefPtr<Image> image = imageData.CreateImage();
+    if (!image) {
+      return nullptr;
+    }
     container->SetCurrentImageInTransaction(image);
 
     GetMaskLayerImageCache()->PutImage(newKey.forget(), container);
   }
 
   maskLayer->SetContainer(container);
 
   maskTransform.Invert();
--- a/layout/base/MaskLayerImageCache.h
+++ b/layout/base/MaskLayerImageCache.h
@@ -9,16 +9,17 @@
 #include "DisplayItemClip.h"
 #include "nsPresContext.h"
 #include "mozilla/gfx/Matrix.h"
 
 namespace mozilla {
 
 namespace layers {
 class ImageContainer;
+class ShadowLayerForwarder;
 } // namespace layers
 
 /**
  * Keeps a record of image containers for mask layers, containers are mapped
  * from the rounded rects used to create them.
  * The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys.
  * Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey
  * (heap-allocated so that a mask layer's userdata can keep a pointer to the
@@ -27,16 +28,17 @@ class ImageContainer;
  * an nsRefPtr to the ImageContainer containing the image, and a count
  * of the number of layers currently using this ImageContainer.
  * When the key's layer count is zero, the cache
  * may remove the entry, which deletes the key object.
  */
 class MaskLayerImageCache
 {
   typedef mozilla::layers::ImageContainer ImageContainer;
+  typedef mozilla::layers::ShadowLayerForwarder ShadowLayerForwarder;
 public:
   MaskLayerImageCache();
   ~MaskLayerImageCache();
 
   /**
    * Representation of a rounded rectangle in device pixel coordinates, in
    * contrast to DisplayItemClip::RoundedRect, which uses app units.
    * In particular, our internal representation uses a gfxRect, rather than
@@ -146,26 +148,29 @@ public:
 
     PLDHashNumber Hash() const
     {
       PLDHashNumber hash = 0;
 
       for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
         hash = AddToHash(hash, mRoundedClipRects[i].Hash());
       }
+      hash = AddToHash(hash, mForwarder.get());
 
       return hash;
     }
 
     bool operator==(const MaskLayerImageKey& aOther) const
     {
-      return mRoundedClipRects == aOther.mRoundedClipRects;
+      return mForwarder == aOther.mForwarder &&
+             mRoundedClipRects == aOther.mRoundedClipRects;
     }
 
     nsTArray<PixelRoundedRect> mRoundedClipRects;
+    RefPtr<ShadowLayerForwarder> mForwarder;
   private:
     void IncLayerCount() const { ++mLayerCount; }
     void DecLayerCount() const
     {
       NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count");
       --mLayerCount;
     }
     mutable uint32_t mLayerCount;
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -5368,30 +5368,28 @@ nsCSSFrameConstructor::FindSVGData(Eleme
                   FCDATA_IS_INLINE |
                   FCDATA_USE_CHILD_ITEMS,
                   NS_NewInlineFrame);
     if (aTag == nsGkAtoms::textPath) {
       if (aAllowsTextPathChild) {
         return &sTSpanData;
       }
     } else if (aTag == nsGkAtoms::tspan ||
-               aTag == nsGkAtoms::altGlyph ||
                aTag == nsGkAtoms::a) {
       return &sTSpanData;
     }
     return &sSuppressData;
   } else if (aTag == nsGkAtoms::text) {
     static const FrameConstructionData sTextData =
       FCDATA_WITH_WRAPPING_BLOCK(FCDATA_DISALLOW_OUT_OF_FLOW |
                                  FCDATA_ALLOW_BLOCK_STYLES,
                                  NS_NewSVGTextFrame,
                                  nsCSSAnonBoxes::mozSVGText);
     return &sTextData;
   } else if (aTag == nsGkAtoms::tspan ||
-             aTag == nsGkAtoms::altGlyph ||
              aTag == nsGkAtoms::textPath) {
     return &sSuppressData;
   }
 
   static const FrameConstructionDataByTag sSVGData[] = {
     SIMPLE_SVG_CREATE(svg, NS_NewSVGInnerSVGFrame),
     SIMPLE_SVG_CREATE(g, NS_NewSVGGFrame),
     SIMPLE_SVG_CREATE(svgSwitch, NS_NewSVGSwitchFrame),
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -4985,19 +4985,27 @@ nsImageRenderer::ComputeConstrainedSize(
 
   float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
   float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
   nsSize size;
   if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
     size.width = aConstrainingSize.width;
     size.height = NSCoordSaturatingNonnegativeMultiply(
                     aIntrinsicRatio.height, scaleX);
+    // If we're reducing the size by less than one css pixel, then just use the
+    // constraining size.
+    if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
+      size.height = aConstrainingSize.height;
+    }
   } else {
     size.width = NSCoordSaturatingNonnegativeMultiply(
                    aIntrinsicRatio.width, scaleY);
+    if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
+      size.width = aConstrainingSize.width;
+    }
     size.height = aConstrainingSize.height;
   }
   return size;
 }
 
 /**
  * mSize is the image's "preferred" size for this particular rendering, while
  * the drawn (aka concrete) size is the actual rendered size after accounting
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -126,16 +126,17 @@ using namespace mozilla::system;
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "CameraPreferences.h"
 #include "TouchManager.h"
 #include "MediaDecoder.h"
 #include "mozilla/layers/CompositorLRU.h"
 #include "mozilla/dom/devicestorage/DeviceStorageStatics.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
 
 #ifdef MOZ_B2G_BT
 #include "mozilla/dom/BluetoothUUID.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::net;
 using namespace mozilla::dom;
@@ -379,16 +380,17 @@ nsLayoutStatics::Shutdown()
   nsAttrValue::Shutdown();
   nsContentUtils::Shutdown();
   nsLayoutStylesheetCache::Shutdown();
   RuleProcessorCache::Shutdown();
 
   ShutdownJSEnvironment();
   nsGlobalWindow::ShutDown();
   nsDOMClassInfo::ShutDown();
+  WebIDLGlobalNameHash::Shutdown();
   nsListControlFrame::Shutdown();
   nsXBLService::Shutdown();
   nsAutoCopyListener::Shutdown();
   FrameLayerBuilder::Shutdown();
 
 #ifdef MOZ_ANDROID_OMX
   AndroidMediaPluginHost::Shutdown();
 #endif
--- a/layout/inspector/inCSSValueSearch.cpp
+++ b/layout/inspector/inCSSValueSearch.cpp
@@ -219,17 +219,17 @@ inCSSValueSearch::SetNormalizeChromeURLs
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 inCSSValueSearch::AddPropertyCriteria(const char16_t *aPropName)
 {
   nsCSSProperty prop =
     nsCSSProps::LookupProperty(nsDependentString(aPropName),
-                               nsCSSProps::eIgnoreEnabledState);
+                               CSSEnabledState::eIgnoreEnabledState);
   mProperties[mPropertyCount] = prop;
   mPropertyCount++;
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 inCSSValueSearch::GetTextCriteria(char16_t** aTextCriteria)
 {
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -454,18 +454,18 @@ inDOMUtils::SelectorMatchesElement(nsIDO
     *aMatches = false;
     return NS_OK;
   }
 
   if (!aPseudo.IsEmpty()) {
     // We need to make sure that the requested pseudo element type
     // matches the selector pseudo element type before proceeding.
     nsCOMPtr<nsIAtom> pseudoElt = NS_Atomize(aPseudo);
-    if (sel->mSelectors->PseudoType() !=
-        nsCSSPseudoElements::GetPseudoType(pseudoElt)) {
+    if (sel->mSelectors->PseudoType() != nsCSSPseudoElements::
+          GetPseudoType(pseudoElt, CSSEnabledState::eIgnoreEnabledState)) {
       *aMatches = false;
       return NS_OK;
     }
 
     // We have a matching pseudo element, now remove it so we can compare
     // directly against |element| when proceeding into SelectorListMatches.
     // It's OK to do this - we just cloned sel and nothing else is using it.
     sel->RemoveRightmostSelector();
@@ -480,18 +480,18 @@ inDOMUtils::SelectorMatchesElement(nsIDO
   *aMatches = nsCSSRuleProcessor::SelectorListMatches(element, matchingContext,
                                                       sel);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::IsInheritedProperty(const nsAString &aPropertyName, bool *_retval)
 {
-  nsCSSProperty prop =
-    nsCSSProps::LookupProperty(aPropertyName, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty prop = nsCSSProps::
+    LookupProperty(aPropertyName, CSSEnabledState::eIgnoreEnabledState);
   if (prop == eCSSProperty_UNKNOWN) {
     *_retval = false;
     return NS_OK;
   }
 
   if (prop == eCSSPropertyExtra_variable) {
     *_retval = true;
     return NS_OK;
@@ -523,24 +523,24 @@ inDOMUtils::GetCSSPropertyNames(uint32_t
 
   if (aFlags & INCLUDE_ALIASES) {
     maxCount += (eCSSProperty_COUNT_with_aliases - eCSSProperty_COUNT);
   }
 
   char16_t** props =
     static_cast<char16_t**>(moz_xmalloc(maxCount * sizeof(char16_t*)));
 
-#define DO_PROP(_prop)                                                       \
-  PR_BEGIN_MACRO                                                             \
-    nsCSSProperty cssProp = nsCSSProperty(_prop);                            \
-    if (nsCSSProps::IsEnabled(cssProp, nsCSSProps::eEnabledForAllContent)) { \
-      props[propCount] =                                                     \
-        ToNewUnicode(nsDependentCString(kCSSRawProperties[_prop]));          \
-      ++propCount;                                                           \
-    }                                                                        \
+#define DO_PROP(_prop)                                                      \
+  PR_BEGIN_MACRO                                                            \
+    nsCSSProperty cssProp = nsCSSProperty(_prop);                           \
+    if (nsCSSProps::IsEnabled(cssProp, CSSEnabledState::eForAllContent)) {  \
+      props[propCount] =                                                    \
+        ToNewUnicode(nsDependentCString(kCSSRawProperties[_prop]));         \
+      ++propCount;                                                          \
+    }                                                                       \
   PR_END_MACRO
 
   // prop is the property id we're considering; propCount is how many properties
   // we've put into props so far.
   uint32_t prop = 0, propCount = 0;
   for ( ; prop < eCSSProperty_COUNT_no_shorthands; ++prop) {
     if (nsCSSProps::PropertyParseType(nsCSSProperty(prop)) !=
         CSS_PROPERTY_PARSE_INACCESSIBLE) {
@@ -669,17 +669,17 @@ static void GetOtherValuesForProperty(co
 }
 
 NS_IMETHODIMP
 inDOMUtils::GetSubpropertiesForCSSProperty(const nsAString& aProperty,
                                            uint32_t* aLength,
                                            char16_t*** aValues)
 {
   nsCSSProperty propertyID =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eEnabledForAllContent);
+    nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
 
   if (propertyID == eCSSProperty_UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
   if (propertyID == eCSSPropertyExtra_variable) {
     *aValues = static_cast<char16_t**>(moz_xmalloc(sizeof(char16_t*)));
     (*aValues)[0] = ToNewUnicode(aProperty);
@@ -711,17 +711,17 @@ inDOMUtils::GetSubpropertiesForCSSProper
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::CssPropertyIsShorthand(const nsAString& aProperty, bool *_retval)
 {
   nsCSSProperty propertyID =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eEnabledForAllContent);
+    nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
   if (propertyID == eCSSProperty_UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
   if (propertyID == eCSSPropertyExtra_variable) {
     *_retval = false;
   } else {
     *_retval = nsCSSProps::IsShorthand(propertyID);
@@ -856,17 +856,17 @@ PropertySupportsVariant(nsCSSProperty aP
   return (nsCSSProps::ParserVariant(aPropertyID) & aVariant) != 0;
 }
 
 NS_IMETHODIMP
 inDOMUtils::CssPropertySupportsType(const nsAString& aProperty, uint32_t aType,
                                     bool *_retval)
 {
   nsCSSProperty propertyID =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eEnabledForAllContent);
+    nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
   if (propertyID == eCSSProperty_UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
   if (propertyID >= eCSSProperty_COUNT) {
     *_retval = false;
     return NS_OK;
   }
@@ -916,18 +916,18 @@ inDOMUtils::CssPropertySupportsType(cons
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::GetCSSValuesForProperty(const nsAString& aProperty,
                                     uint32_t* aLength,
                                     char16_t*** aValues)
 {
-  nsCSSProperty propertyID = nsCSSProps::LookupProperty(aProperty,
-                                                        nsCSSProps::eEnabledForAllContent);
+  nsCSSProperty propertyID = nsCSSProps::
+    LookupProperty(aProperty, CSSEnabledState::eForAllContent);
   if (propertyID == eCSSProperty_UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
   nsTArray<nsString> array;
   // We start collecting the values, BUT colors need to go in first, because array
   // needs to stay sorted, and the colors are sorted, so we just append them.
   if (propertyID == eCSSPropertyExtra_variable) {
@@ -939,26 +939,26 @@ inDOMUtils::GetCSSValuesForProperty(cons
     GetColorsForProperty(propertyParserVariant, array);
     if (propertyParserVariant & VARIANT_KEYWORD) {
       GetKeywordsForProperty(propertyID, array);
     }
     GetOtherValuesForProperty(propertyParserVariant, array);
   } else {
     // Property is shorthand.
     CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID,
-                                         nsCSSProps::eEnabledForAllContent) {
+                                         CSSEnabledState::eForAllContent) {
       // Get colors (once) first.
       uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty);
       if (propertyParserVariant & VARIANT_COLOR) {
         GetColorsForProperty(propertyParserVariant, array);
         break;
       }
     }
     CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subproperty, propertyID,
-                                         nsCSSProps::eEnabledForAllContent) {
+                                         CSSEnabledState::eForAllContent) {
       uint32_t propertyParserVariant = nsCSSProps::ParserVariant(*subproperty);
       if (propertyParserVariant & VARIANT_KEYWORD) {
         GetKeywordsForProperty(*subproperty, array);
       }
       GetOtherValuesForProperty(propertyParserVariant, array);
     }
   }
   // All CSS properties take initial, inherit and unset.
@@ -1051,18 +1051,18 @@ inDOMUtils::IsValidCSSColor(const nsAStr
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::CssPropertyIsValid(const nsAString& aPropertyName,
                                const nsAString& aPropertyValue,
                                bool *_retval)
 {
-  nsCSSProperty propertyID =
-    nsCSSProps::LookupProperty(aPropertyName, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty propertyID = nsCSSProps::
+    LookupProperty(aPropertyName, CSSEnabledState::eIgnoreEnabledState);
 
   if (propertyID == eCSSProperty_UNKNOWN) {
     *_retval = false;
     return NS_OK;
   }
 
   if (propertyID == eCSSPropertyExtra_variable) {
     *_retval = true;
@@ -1206,17 +1206,18 @@ GetStatesForPseudoClass(const nsAString&
     EventStates(),
     EventStates()
   };
   static_assert(MOZ_ARRAY_LENGTH(sPseudoClassStates) ==
                 static_cast<size_t>(CSSPseudoClassType::MAX),
                 "Length of PseudoClassStates array is incorrect");
 
   nsCOMPtr<nsIAtom> atom = NS_Atomize(aStatePseudo);
-  CSSPseudoClassType type = nsCSSPseudoClasses::GetPseudoType(atom, true, true);
+  CSSPseudoClassType type = nsCSSPseudoClasses::
+    GetPseudoType(atom, CSSEnabledState::eIgnoreEnabledState);
 
   // Ignore :moz-any-link so we don't give the element simultaneous
   // visited and unvisited style state
   if (type == CSSPseudoClassType::mozAnyLink) {
     return EventStates();
   }
   // Our array above is long enough that indexing into it with
   // NotPseudo is ok.
@@ -1227,17 +1228,17 @@ NS_IMETHODIMP
 inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames)
 {
   nsTArray<nsIAtom*> array;
 
   const CSSPseudoElementTypeBase pseudoCount =
     static_cast<CSSPseudoElementTypeBase>(CSSPseudoElementType::Count);
   for (CSSPseudoElementTypeBase i = 0; i < pseudoCount; ++i) {
     CSSPseudoElementType type = static_cast<CSSPseudoElementType>(i);
-    if (!nsCSSPseudoElements::PseudoElementIsUASheetOnly(type)) {
+    if (nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::eForAllContent)) {
       nsIAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type);
       array.AppendElement(atom);
     }
   }
 
   *aLength = array.Length();
   char16_t** ret =
     static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
--- a/layout/reftests/border-radius/reftest.list
+++ b/layout/reftests/border-radius/reftest.list
@@ -38,32 +38,32 @@ fuzzy-if(skiaContent,1,2728) == corner-4
 # Tests for border clipping
 fails == clipping-1.html clipping-1-ref.html # background color should completely fill box; bug 466572
 != clipping-2.html about:blank # background color clipped to inner/outer border, can't get
 # great tests for this due to antialiasing problems described in bug 466572
 fuzzy-if(skiaContent,1,13) == clipping-3.html clipping-3-ref.xhtml # edge of border-radius clips an underlying object's background
 
 # Tests for clipping the contents of replaced elements and overflow!=visible
 != clipping-4-ref.html clipping-4-notref.html
-fuzzy-if(true,1,20) fuzzy-if(cocoaWidget,1,180) fuzzy-if(Android,140,237) == clipping-4-canvas.html clipping-4-ref.html # bug 732535
+fuzzy-if(true,1,20) fuzzy-if(d2d,64,196) fuzzy-if(cocoaWidget,1,180) fuzzy-if(Android,140,237) == clipping-4-canvas.html clipping-4-ref.html # bug 732535
 fuzzy-if(Android,5,54) fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,10) == clipping-4-image.html clipping-4-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,10) fuzzy-if(skiaContent,1,77) == clipping-4-overflow-hidden.html clipping-4-ref.html
 == clipping-5-canvas.html clipping-5-refc.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) == clipping-5-image.html clipping-5-refi.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(skiaContent,1,77) == clipping-5-overflow-hidden.html clipping-5-ref.html
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,5,21) fuzzy-if(skiaContent,1,77) == clipping-5-refi.html clipping-5-ref.html
-fuzzy-if(true,1,7) fuzzy-if(cocoaWidget,1,99) fuzzy-if(Android,99,115) fuzzy-if(skiaContent,1,77) == clipping-5-refc.html clipping-5-ref.html # bug 732535
+fuzzy-if(true,1,7) fuzzy-if(d2d,48,94) fuzzy-if(cocoaWidget,1,99) fuzzy-if(Android,99,115) fuzzy-if(skiaContent,1,77) == clipping-5-refc.html clipping-5-ref.html # bug 732535
 fuzzy-if(winWidget,105,71) fuzzy-if(Android,8,469) == clipping-6.html clipping-6-ref.html # PaintedLayer and MaskLayer with transforms that aren't identical
-fuzzy-if(true,2,29) fuzzy-if(Android,255,586) fuzzy-if(skiaContent,16,27) == clipping-7.html clipping-7-ref.html # ColorLayer and MaskLayer with transforms that aren't identical. Reference image rendered without using layers (which causes fuzzy failures).
+fuzzy-if(true,2,29) fuzzy-if(d2d,46,50) fuzzy-if(Android,255,586) fuzzy-if(skiaContent,16,27) == clipping-7.html clipping-7-ref.html # ColorLayer and MaskLayer with transforms that aren't identical. Reference image rendered without using layers (which causes fuzzy failures).
 fuzzy-if(/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) == clipping-and-zindex-1.html clipping-and-zindex-1-ref.html
 fuzzy-if(cocoaWidget,1,4) == intersecting-clipping-1-canvas.html intersecting-clipping-1-refc.html
 == intersecting-clipping-1-image.html intersecting-clipping-1-refi.html
 == intersecting-clipping-1-overflow-hidden.html intersecting-clipping-1-ref.html
 fuzzy-if(Android,5,105) fuzzy-if(d2d,1,20) fuzzy-if(skiaContent,1,135) == intersecting-clipping-1-refi.html intersecting-clipping-1-ref.html
-fuzzy-if(true,1,33) fuzzy-if(cocoaWidget,1,332) fuzzy-if(Android,124,440) fuzzy-if(skiaContent,1,135) == intersecting-clipping-1-refc.html intersecting-clipping-1-ref.html # bug 732535
+fuzzy-if(true,1,33) fuzzy-if(d2d,48,350) fuzzy-if(cocoaWidget,1,332) fuzzy-if(Android,124,440) fuzzy-if(skiaContent,1,135) == intersecting-clipping-1-refc.html intersecting-clipping-1-ref.html # bug 732535
 
 # Inheritance
 == inherit-1.html inherit-1-ref.html # border-radius shouldn't inherit
 
 # Table elements
 == table-collapse-1.html table-collapse-1-ref.html # border-radius is ignored on internal table elements
 # when border-collapse: collapse
 
@@ -73,17 +73,17 @@ fuzzy-if(azureQuartz,1,3) skip-if(B2G||M
 # test that border-radius is reduced for scrollbars
 skip-if(B2G||Mulet) fails-if(Android) fuzzy-if(asyncPan&&!layersGPUAccelerated,12,12) fuzzy-if(browserIsRemote&&layersGPUAccelerated&&/^Windows\x20NT\x206\.1/.test(http.oscpu),12,12) fuzzy-if(skiaContent,1,50) == scrollbar-clamping-1.html scrollbar-clamping-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fails-if(Android) == scrollbar-clamping-2.html scrollbar-clamping-2-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 
 # Test for bad corner joins.
 fuzzy-if(true,1,1) == corner-joins-1.xhtml corner-joins-1-ref.xhtml
 fuzzy(255,20) skip-if(B2G||Mulet) random-if(winWidget) fuzzy-if(skiaContent,255,610) HTTP(..) == corner-joins-2.xhtml corner-joins-2-ref.xhtml # Initial mulet triage: parity with B2G/B2G Desktop
 
-skip-if(B2G||Mulet) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,20) fuzzy-if(Android,166,400) fuzzy-if(skiaContent,64,70) == scroll-1.html scroll-1-ref.html # see bug 732535 #Bug 959166 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,20) fuzzy-if(d2d,64,157) fuzzy-if(Android,166,400) fuzzy-if(skiaContent,64,70) == scroll-1.html scroll-1-ref.html # see bug 732535 #Bug 959166 # Initial mulet triage: parity with B2G/B2G Desktop
 
 == transforms-1.html transforms-1-ref.html
 
 == zero-radius-clip-1.html zero-radius-clip-ref.html
 
 == iframe-1.html iframe-1-ref.html
 
 # Test for antialiasing gaps between background and border
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1704,17 +1704,17 @@ needs-focus != 703186-1.html 703186-2.ht
 == 711359-1.html 711359-1-ref.html
 == 712849-1.html 712849-1-ref.html
 == 713856-static.html  713856-ref.html
 == 713856-dynamic.html 713856-ref.html
 == 714519-1-as.html 714519-1-ref.html
 == 714519-1-q.html 714519-1-ref.html
 == 714519-2-as.html 714519-2-ref.html
 == 714519-2-q.html 714519-2-ref.html
-skip-if(B2G||Mulet) fuzzy-if(true,1,21) fuzzy-if(cocoaWidget,1,170) == 718521.html 718521-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) fuzzy-if(true,1,21) fuzzy-if(d2d,68,173) fuzzy-if(cocoaWidget,1,170) == 718521.html 718521-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
 == 720987.html 720987-ref.html
 == 722888-1.html 722888-1-ref.html
 == 722923-1.html 722923-1-ref.html
 == 723484-1.html 723484-1-ref.html
 random-if(Android||(B2G&&browserIsRemote)||Mulet) == 728983-1.html 728983-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) == 729143-1.html 729143-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
 == 731521-1.html 731521-1-ref.html
 needs-focus == 731726-1.html 731726-1-ref.html
deleted file mode 100644
--- a/layout/reftests/svg/altGlyph-01-ref.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
-     font-family="monospace" font-size="20" xml:space="preserve">
-
-  <title>Reference to check that that altGlyph falls back to tspan behaviour</title>
-
-  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=456286 -->
-
-  <text><tspan x="20" y="20" rotate="90">T</tspan>EST1</text>
-
-</svg>
deleted file mode 100644
--- a/layout/reftests/svg/altGlyph-01.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
-     font-family="monospace" font-size="20" xml:space="preserve">
-
-  <title>Testcase to check that altGlyph falls back to tspan behaviour</title>
-
-  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=456286 -->
-
-  <text><altGlyph x="20" y="20" rotate="90">T</altGlyph>EST1</text>
-
-</svg>
--- a/layout/reftests/svg/nesting-invalid-01.js
+++ b/layout/reftests/svg/nesting-invalid-01.js
@@ -1,14 +1,14 @@
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 
-// These are all of the SVG 1.1 element names, except for container elements,
+// These are all of the SVG 1.1 and Filter Effects element names, except for container and deprecated elements,
 // with the addition of an "UNKNOWN" element in the SVG namespace.
-var es = 'altGlyph altGlyphDef altGlyphItem animate animateColor animateMotion animateTransform circle color-profile cursor desc ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font font-face font-face-format font-face-name font-face-src font-face-uri glyph glyphRef hkern image line linearGradient metadata missing-glyph mpath path polygon polyline radialGradient rect script set stop style text textPath title tref tspan use view vkern UNKNOWN'.split(' ');
+var es = 'animate animateMotion animateTransform circle color-profile cursor desc ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter image line linearGradient metadata mpath path polygon polyline radialGradient rect script set stop style text textPath title tspan use view UNKNOWN'.split(' ');
 var colwidth = 200;
 var size = 40;
 var rows = 14;
 
 function makeElement(localName, attrs, children) {
   var e = document.createElementNS('http://www.w3.org/2000/svg', localName);
   for (var an in attrs) {
     e.setAttribute(an, attrs[an]);
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -20,17 +20,16 @@ include text/reftest.list
 include load-only/reftest.list
 
 # Mozilla only tests (i.e. those containing XUL/XBL/etc.)
 include moz-only/reftest.list
 
 # svg-integration tests (using svg effects in e.g. HTML)
 include svg-integration/reftest.list
 
-== altGlyph-01.svg altGlyph-01-ref.svg
 == baseline-middle-01.svg pass.svg
 == border-radius-01.html pass.svg
 == cssComment-in-attribute-01.svg cssComment-in-attribute-01-ref.svg
 == clip-01.svg pass.svg
 == clip-02a.svg clip-02-ref.svg
 == clip-02b.svg clip-02-ref.svg
 == clipPath-advanced-01.svg pass.svg
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,6) fuzzy-if(skiaContent,1,300) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
--- a/layout/style/AnimationCommon.h
+++ b/layout/style/AnimationCommon.h
@@ -7,22 +7,20 @@
 #define mozilla_css_AnimationCommon_h
 
 #include <algorithm> // For <std::stable_sort>
 #include "mozilla/AnimationCollection.h"
 #include "mozilla/AnimationComparator.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/Animation.h"
 #include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF
 #include "mozilla/Assertions.h"
 #include "nsContentUtils.h"
-#include "nsCSSProperty.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCycleCollectionParticipant.h"
 
 class nsIFrame;
 class nsPresContext;
 
 namespace mozilla {
 
@@ -47,21 +45,16 @@ public:
   void Disconnect()
   {
     // Content nodes might outlive the transition or animation manager.
     RemoveAllElementCollections();
 
     mPresContext = nullptr;
   }
 
-  static bool ExtractComputedValueForTransition(
-                  nsCSSProperty aProperty,
-                  nsStyleContext* aStyleContext,
-                  StyleAnimationValue& aComputedValue);
-
 protected:
   virtual ~CommonAnimationManager()
   {
     MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
   }
 
   void AddElementCollection(AnimationCollection<AnimationType>* aCollection)
   {
@@ -74,29 +67,16 @@ protected:
       head->Destroy(); // Note: this removes 'head' from mElementCollections.
     }
   }
 
   LinkedList<AnimationCollection<AnimationType>> mElementCollections;
   nsPresContext *mPresContext; // weak (non-null from ctor to Disconnect)
 };
 
-template <class AnimationType>
-/* static */ bool
-CommonAnimationManager<AnimationType>::ExtractComputedValueForTransition(
-  nsCSSProperty aProperty,
-  nsStyleContext* aStyleContext,
-  StyleAnimationValue& aComputedValue)
-{
-  bool result = StyleAnimationValue::ExtractComputedValue(aProperty,
-                                                          aStyleContext,
-                                                          aComputedValue);
-  return result;
-}
-
 /**
  * Utility class for referencing the element that created a CSS animation or
  * transition. It is non-owning (i.e. it uses a raw pointer) since it is only
  * expected to be set by the owned animation while it actually being managed
  * by the owning element.
  *
  * This class also abstracts the comparison of an element/pseudo-class pair
  * for the sake of composite ordering since this logic is common to both CSS
new file mode 100644
--- /dev/null
+++ b/layout/style/CSSEnabledState.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+/*
+ * enum for whether a CSS feature (property, pseudo-class, etc.) is
+ * enabled in a specific context
+ */
+
+#ifndef mozilla_CSSEnabledState_h
+#define mozilla_CSSEnabledState_h
+
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+
+enum class CSSEnabledState
+{
+  // The default CSSEnabledState: only enable what's enabled for all
+  // content, given the current values of preferences.
+  eForAllContent = 0,
+  // Enable features available in UA sheets.
+  eInUASheets = 0x01,
+  // Enable features available in chrome code.
+  eInChrome = 0x02,
+  // Special value to unconditionally enable everything. This implies
+  // all the bits above, but is strictly more than just their OR-ed
+  // union. This just skips any test so a feature will be enabled even
+  // if it would have been disabled with all the bits above set.
+  eIgnoreEnabledState = 0xff
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSEnabledState)
+
+} // namespace mozilla
+
+#endif // mozilla_CSSEnabledState_h
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -141,17 +141,17 @@ Declaration::RemoveProperty(nsCSSPropert
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT);
 
   nsCSSExpandedDataBlock data;
   ExpandTo(&data);
   MOZ_ASSERT(!mData && !mImportantData, "Expand didn't null things out");
 
   if (nsCSSProps::IsShorthand(aProperty)) {
     CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
-                                         nsCSSProps::eEnabledForAllContent) {
+                                         CSSEnabledState::eForAllContent) {
       data.ClearLonghandProperty(*p);
       mOrder.RemoveElement(static_cast<uint32_t>(*p));
     }
   } else {
     data.ClearLonghandProperty(aProperty);
     mOrder.RemoveElement(static_cast<uint32_t>(aProperty));
   }
 
@@ -507,17 +507,17 @@ Declaration::GetValue(nsCSSProperty aPro
   // variable reference and none of its component longhand properties were
   // then overridden on the declaration, we return the token stream
   // assigned to the shorthand.
   const nsCSSValue* tokenStream = nullptr;
   uint32_t totalCount = 0, importantCount = 0,
            initialCount = 0, inheritCount = 0, unsetCount = 0,
            matchingTokenStreamCount = 0, nonMatchingTokenStreamCount = 0;
   CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
-                                       nsCSSProps::eEnabledForAllContent) {
+                                       CSSEnabledState::eForAllContent) {
     if (*p == eCSSProperty__x_system_font) {
       // The system-font subproperty doesn't count.
       continue;
     }
     ++totalCount;
     const nsCSSValue *val = mData->ValueFor(*p);
     MOZ_ASSERT(!val || !mImportantData || !mImportantData->ValueFor(*p),
                "can't be in both blocks");
@@ -1427,18 +1427,18 @@ Declaration::GetValue(nsCSSProperty aPro
       MOZ_ASSERT(false, "no other shorthands");
       break;
   }
 }
 
 bool
 Declaration::GetValueIsImportant(const nsAString& aProperty) const
 {
-  nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty propID = nsCSSProps::
+    LookupProperty(aProperty, CSSEnabledState::eIgnoreEnabledState);
   if (propID == eCSSProperty_UNKNOWN) {
     return false;
   }
   if (propID == eCSSPropertyExtra_variable) {
     const nsSubstring& variableName =
       Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH);
     return GetVariableValueIsImportant(variableName);
   }
@@ -1453,17 +1453,17 @@ Declaration::GetValueIsImportant(nsCSSPr
 
   // Calling ValueFor is inefficient, but we can assume '!important' is rare.
 
   if (!nsCSSProps::IsShorthand(aProperty)) {
     return mImportantData->ValueFor(aProperty) != nullptr;
   }
 
   CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty,
-                                       nsCSSProps::eEnabledForAllContent) {
+                                       CSSEnabledState::eForAllContent) {
     if (*p == eCSSProperty__x_system_font) {
       // The system_font subproperty doesn't count.
       continue;
     }
     if (!mImportantData->ValueFor(*p)) {
       return false;
     }
   }
@@ -1567,17 +1567,17 @@ Declaration::ToString(nsAString& aString
     nsCSSProperty property = GetPropertyAt(index);
 
     if (property == eCSSPropertyExtra_variable) {
       uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT;
       AppendVariableAndValueToString(mVariableOrder[variableIndex], aString);
       continue;
     }
 
-    if (!nsCSSProps::IsEnabled(property, nsCSSProps::eEnabledForAllContent)) {
+    if (!nsCSSProps::IsEnabled(property, CSSEnabledState::eForAllContent)) {
       continue;
     }
     bool doneProperty = false;
 
     // If we already used this property in a shorthand, skip it.
     if (shorthandsUsed.Length() > 0) {
       for (const nsCSSProperty *shorthands =
              nsCSSProps::ShorthandsContaining(property);
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -2816,17 +2816,17 @@ BuildStyleRule(nsCSSProperty aProperty,
   declaration->CompressFrom(&block);
 
   RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, declaration, 0, 0);
   return rule.forget();
 }
 
 static bool
 ComputeValuesFromStyleRule(nsCSSProperty aProperty,
-                           nsCSSProps::EnabledState aEnabledState,
+                           CSSEnabledState aEnabledState,
                            dom::Element* aTargetElement,
                            nsStyleContext* aStyleContext,
                            css::StyleRule* aStyleRule,
                            nsTArray<PropertyStyleAnimationValuePair>& aValues,
                            bool* aIsContextSensitive)
 {
   MOZ_ASSERT(aStyleContext);
   if (!nsCSSProps::IsEnabled(aProperty, aEnabledState)) {
@@ -2937,17 +2937,17 @@ StyleAnimationValue::ComputeValue(nsCSSP
       // to change depending on the context
       *aIsContextSensitive = false;
     }
     return true;
   }
 
   AutoTArray<PropertyStyleAnimationValuePair,1> values;
   bool ok = ComputeValuesFromStyleRule(aProperty,
-                                       nsCSSProps::eIgnoreEnabledState,
+                                       CSSEnabledState::eIgnoreEnabledState,
                                        aTargetElement, aStyleContext, styleRule,
                                        values, aIsContextSensitive);
   if (!ok) {
     return false;
   }
 
   MOZ_ASSERT(values.Length() == 1);
   MOZ_ASSERT(values[0].mProperty == aProperty);
@@ -2955,17 +2955,17 @@ StyleAnimationValue::ComputeValue(nsCSSP
   aComputedValue = values[0].mValue;
   return true;
 }
 
 template <class T>
 bool
 ComputeValuesFromSpecifiedValue(
     nsCSSProperty aProperty,
-    nsCSSProps::EnabledState aEnabledState,
+    CSSEnabledState aEnabledState,
     dom::Element* aTargetElement,
     nsStyleContext* aStyleContext,
     T& aSpecifiedValue,
     bool aUseSVGMode,
     nsTArray<PropertyStyleAnimationValuePair>& aResult)
 {
   MOZ_ASSERT(aTargetElement, "null target element");
 
@@ -2983,33 +2983,33 @@ ComputeValuesFromSpecifiedValue(
   return ComputeValuesFromStyleRule(aProperty, aEnabledState, aTargetElement,
                                     aStyleContext, styleRule, aResult,
                                     /* aIsContextSensitive */ nullptr);
 }
 
 /* static */ bool
 StyleAnimationValue::ComputeValues(
     nsCSSProperty aProperty,
-    nsCSSProps::EnabledState aEnabledState,
+    CSSEnabledState aEnabledState,
     dom::Element* aTargetElement,
     nsStyleContext* aStyleContext,
     const nsAString& aSpecifiedValue,
     bool aUseSVGMode,
     nsTArray<PropertyStyleAnimationValuePair>& aResult)
 {
   return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState,
                                          aTargetElement, aStyleContext,
                                          aSpecifiedValue, aUseSVGMode,
                                          aResult);
 }
 
 /* static */ bool
 StyleAnimationValue::ComputeValues(
     nsCSSProperty aProperty,
-    nsCSSProps::EnabledState aEnabledState,
+    CSSEnabledState aEnabledState,
     dom::Element* aTargetElement,
     nsStyleContext* aStyleContext,
     const nsCSSValue& aSpecifiedValue,
     bool aUseSVGMode,
     nsTArray<PropertyStyleAnimationValuePair>& aResult)
 {
   return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState,
                                          aTargetElement, aStyleContext,
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -171,29 +171,29 @@ public:
    * On success, when aProperty is a longhand, aResult will have a single
    * value in it.  When aProperty is a shorthand, aResult will be filled with
    * values for all of aProperty's longhand components.  aEnabledState
    * is used to filter the longhand components that will be appended
    * to aResult.  On failure, aResult might still have partial results
    * in it.
    */
   static bool ComputeValues(nsCSSProperty aProperty,
-                            nsCSSProps::EnabledState aEnabledState,
+                            mozilla::CSSEnabledState aEnabledState,
                             mozilla::dom::Element* aTargetElement,
                             nsStyleContext* aStyleContext,
                             const nsAString& aSpecifiedValue,
                             bool aUseSVGMode,
                             nsTArray<PropertyStyleAnimationValuePair>& aResult);
 
   /**
    * A variant on ComputeValues that takes an nsCSSValue as the specified
    * value. Only longhand properties are supported.
    */
   static bool ComputeValues(nsCSSProperty aProperty,
-                            nsCSSProps::EnabledState aEnabledState,
+                            mozilla::CSSEnabledState aEnabledState,
                             mozilla::dom::Element* aTargetElement,
                             nsStyleContext* aStyleContext,
                             const nsCSSValue& aSpecifiedValue,
                             bool aUseSVGMode,
                             nsTArray<PropertyStyleAnimationValuePair>& aResult);
 
   /**
    * Creates a specified value for the given computed value.
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -76,16 +76,17 @@ EXPORTS += [
     'nsStyleStructFwd.h',
     'nsStyleStructInlines.h',
     'nsStyleTransformMatrix.h',
     'nsStyleUtil.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationCollection.h',
+    'CSSEnabledState.h',
     'CSSStyleSheet.h',
     'CSSVariableDeclarations.h',
     'CSSVariableResolver.h',
     'CSSVariableValues.h',
     'HandleRefPtr.h',
     'IncrementalClearCOMRuleArray.h',
     'LayerAnimationInfo.h',
     'RuleNodeCacheConditions.h',
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -1026,18 +1026,19 @@ CSSAnimationBuilder::GetComputedValue(ns
     MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
                "ServoStyleSet should not use nsAnimationManager for "
                "animations");
     mStyleWithoutAnimation = aPresContext->StyleSet()->AsGecko()->
       ResolveStyleWithoutAnimation(mTarget, mStyleContext,
                                    eRestyle_AllHintsWithAnimations);
   }
 
-  if (CommonAnimationManager<CSSAnimation>::ExtractComputedValueForTransition(
-        aProperty, mStyleWithoutAnimation, computedValue) &&
+  if (StyleAnimationValue::ExtractComputedValue(aProperty,
+                                                mStyleWithoutAnimation,
+                                                computedValue) &&
       StyleAnimationValue::UncomputeValue(
         aProperty, Move(computedValue), aResult)) {
     // If we hit this assertion or the MOZ_ASSERT_UNREACHABLE below, it
     // probably means we are fetching a value from the computed style that
     // we don't know how to represent as a StyleAnimationValue.
     MOZ_ASSERT(aResult.GetUnit() != eCSSUnit_Null,
                "Got null computed value");
     return;
--- a/layout/style/nsCSSDataBlock.cpp
+++ b/layout/style/nsCSSDataBlock.cpp
@@ -618,18 +618,18 @@ nsCSSExpandedDataBlock::Clear()
 
   AssertInitialState();
 }
 
 void
 nsCSSExpandedDataBlock::ClearProperty(nsCSSProperty aPropID)
 {
   if (nsCSSProps::IsShorthand(aPropID)) {