merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 20 Oct 2015 12:00:53 +0200
changeset 268496 f7b746b4e91307448cb0746a41f677bfc23908b0
parent 268344 3f17d8a0a2018d70ff8e0b24a2d322bf58841dbf (current diff)
parent 268495 a4334a6c531326e239293511a3bdb670b83effdf (diff)
child 268520 60f4276010c0847ed2e737b1b009ec019ff8888f
child 268550 2a9c0cfc260c69b2ffb608f3332b3a03e13da9bf
child 268589 cf88cfee3b8bcb8880ae2845a98b61a705a8f107
push id29551
push usercbook@mozilla.com
push dateTue, 20 Oct 2015 10:01:15 +0000
treeherdermozilla-central@f7b746b4e913 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
f7b746b4e913 / 44.0a1 / 20151020031317 / files
nightly linux64
f7b746b4e913 / 44.0a1 / 20151020031317 / files
nightly mac
f7b746b4e913 / 44.0a1 / 20151020031317 / files
nightly win32
f7b746b4e913 / 44.0a1 / 20151020031317 / files
nightly win64
f7b746b4e913 / 44.0a1 / 20151020031317 / 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 mozilla-inbound to mozilla-central a=merge
browser/components/nsBrowserGlue.js
dom/base/test/test_bug438519.html
dom/tests/mochitest/dom-level2-core/test_documenttypeinternalSubset01.html
dom/tests/mochitest/dom-level2-core/test_internalSubset01.html
modules/libpref/init/all.js
testing/web-platform/meta/content-security-policy/blink-contrib/shared-worker-connect-src-allowed.sub.html.ini
testing/web-platform/meta/cors/redirect-userinfo.htm.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-request-xhr.https.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/request-end-to-end.https.html.ini
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1210154 - Update the clang toolchain
+Bug 1215696 - Update mp4parse to v0.1.1
--- a/browser/modules/ContentWebRTC.jsm
+++ b/browser/modules/ContentWebRTC.jsm
@@ -90,30 +90,40 @@ this.ContentWebRTC = {
     }
   }
 };
 
 function handlePCRequest(aSubject, aTopic, aData) {
   let { windowID, innerWindowID, callID, isSecure } = aSubject;
   let contentWindow = Services.wm.getOuterWindowWithId(windowID);
 
+  let mm = getMessageManagerForWindow(contentWindow);
+  if (!mm) {
+    // Workaround for Bug 1207784. To use WebRTC, add-ons right now use
+    // hiddenWindow.mozRTCPeerConnection which is only privileged on OSX. Other
+    // platforms end up here without a message manager.
+    // TODO: Remove once there's a better way (1215591).
+
+    // Skip permission check in the absence of a message manager.
+    Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
+    return;
+  }
+
   if (!contentWindow.pendingPeerConnectionRequests) {
     setupPendingListsInitially(contentWindow);
   }
   contentWindow.pendingPeerConnectionRequests.add(callID);
 
   let request = {
     windowID: windowID,
     innerWindowID: innerWindowID,
     callID: callID,
     documentURI: contentWindow.document.documentURI,
     secure: isSecure,
   };
-
-  let mm = getMessageManagerForWindow(contentWindow);
   mm.sendAsyncMessage("rtcpeer:Request", request);
 }
 
 function handleGUMRequest(aSubject, aTopic, aData) {
   let constraints = aSubject.getConstraints();
   let secure = aSubject.isSecure;
   let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
 
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -161,13 +161,13 @@ browser.jar:
   skin/classic/browser/syncQuota.css
   skin/classic/browser/syncProgress-horizontalbar.png
   skin/classic/browser/syncProgress-horizontalbar@2x.png
 #endif
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
 
-../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -270,17 +270,17 @@ browser.jar:
   skin/classic/browser/yosemite/sync-horizontalbar.png      (sync-horizontalbar-yosemite.png)
   skin/classic/browser/yosemite/sync-horizontalbar@2x.png   (sync-horizontalbar-yosemite@2x.png)
   skin/classic/browser/yosemite/thumburger.png              (customizableui/thumburger-yosemite.png)
   skin/classic/browser/yosemite/thumburger@2x.png           (customizableui/thumburger-yosemite@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
 
-../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/toolbarbutton-dropmarker.png              chrome://browser/skin/lion/toolbarbutton-dropmarker.png                 os=Darwin osversion>=10.7
 % override chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png     chrome://browser/skin/lion/tabbrowser/alltabs-box-bkgnd-icon.png        os=Darwin osversion>=10.7
 % override chrome://browser/skin/tabview/tabview.png                       chrome://browser/skin/lion/tabview/tabview.png                          os=Darwin osversion>=10.7
 % override chrome://browser/skin/places/toolbar.png                        chrome://browser/skin/lion/places/toolbar.png                           os=Darwin osversion>=10.7
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -280,17 +280,17 @@ browser.jar:
   skin/classic/browser/syncProgress-toolbar-inverted@2x.png
   skin/classic/browser/syncProgress-toolbar-XPVista7.png
   skin/classic/browser/syncProgress-toolbar-XPVista7@2x.png
 #endif
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
 
-../extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.jar:
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/page-livemarks.png                   chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 
 % override chrome://browser/skin/aboutSessionRestore-window-icon.png  chrome://browser/skin/preferences/application.png                 os!=WINNT
 % override chrome://browser/skin/aboutSessionRestore-window-icon.png  chrome://browser/skin/preferences/application.png                 os=WINNT osversion<6
--- a/build/docs/jar-manifests.rst
+++ b/build/docs/jar-manifests.rst
@@ -26,16 +26,23 @@ more complex ``jar.mn`` is at ``toolkit/
 Shipping Chrome Files
 =====================
 
 To ship chrome files in a JAR, an indented line indicates a file to be packaged::
 
    <jarfile>.jar:
      path/in/jar/file_name.xul     (source/tree/location/file_name.xul)
 
+The JAR location may be preceded with a base path between square brackets::
+   [base/path] <jarfile>.jar:
+     path/in/jar/file_name.xul     (source/tree/location/file_name.xul)
+
+In this case, the jar will be directly located under the given ``base/bath``,
+while without a base path, it will be under a ``chrome`` directory.
+
 If the JAR manifest and packaged file live in the same directory, the path and
 parenthesis can be omitted. In other words, the following two lines are
 equivalent::
 
    path/in/jar/same_place.xhtml     (same_place.xhtml)
    path/in/jar/same_place.xhtml
 
 The source tree location may also be an *absolute* path (taken from the
--- a/build/stlport/moz.build
+++ b/build/stlport/moz.build
@@ -65,9 +65,10 @@ if CONFIG['GNU_CXX']:
         '-Wno-type-limits',
         '-Wno-unused-local-typedefs',
     ]
 
 # Force to build a static library, instead of a fake library, without
 # installing it in dist/lib.
 NO_EXPAND_LIBS = True
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1251,17 +1251,17 @@ ifneq (,$(DIST_SUBDIR))
 ifndef XPI_ROOT_APPID
 $(error XPI_ROOT_APPID is not defined - langpacks will break.)
 endif
 endif
 endif
 
 libs realchrome:: $(FINAL_TARGET)/chrome
 	$(call py_action,jar_maker,\
-	  $(QUIET) -j $(FINAL_TARGET)/chrome \
+	  $(QUIET) -d $(FINAL_TARGET) \
 	  $(MAKE_JARS_FLAGS) $(DEFINES) $(ACDEFINES) $(MOZ_DEBUG_DEFINES) \
 	  $(JAR_MANIFEST))
 
 endif
 
 # This is a temporary check to ensure patches relying on the old behavior
 # of silently picking up jar.mn files continue to work.
 else # No JAR_MANIFEST
--- a/db/sqlite3/src/moz.build
+++ b/db/sqlite3/src/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 NO_VISIBILITY_FLAGS = True
 
 EXPORTS += [
     'sqlite3.h',
 ]
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 if CONFIG['MOZ_FOLD_LIBS']:
     # When folding libraries, sqlite is actually in the nss library.
     FINAL_LIBRARY = 'nss'
 else:
     # The final library is in config/external/sqlite
     FINAL_LIBRARY = 'sqlite'
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -782,17 +782,17 @@ NetworkMonitor.prototype = {
         return true;
       }
     }
 
     // The following check is necessary because beacon channels don't come
     // associated with a load group. Bug 1160837 will hopefully introduce a
     // platform fix that will render the following code entirely useless.
     if (aChannel.loadInfo &&
-        aChannel.loadInfo.contentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
+        aChannel.loadInfo.externalContentPolicyType == Ci.nsIContentPolicy.TYPE_BEACON) {
       let nonE10sMatch = this.window &&
                          aChannel.loadInfo.loadingDocument === this.window.document;
       let e10sMatch = this.topFrame &&
                       this.topFrame.contentPrincipal &&
                       this.topFrame.contentPrincipal.equals(aChannel.loadInfo.loadingPrincipal) &&
                       this.topFrame.contentPrincipal.URI.spec == aChannel.referrer.spec;
       let b2gMatch = this.appId &&
                      aChannel.loadInfo.loadingPrincipal.appId === this.appId;
@@ -833,17 +833,17 @@ NetworkMonitor.prototype = {
     event.fromCache = fromCache;
 
     if (extraStringData) {
       event.headersSize = extraStringData.length;
     }
 
     // Determine if this is an XHR request.
     httpActivity.isXHR = event.isXHR =
-        (aChannel.loadInfo.contentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
+        (aChannel.loadInfo.externalContentPolicyType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST);
 
     // Determine the HTTP version.
     let httpVersionMaj = {};
     let httpVersionMin = {};
     aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
     aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
 
     event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
--- a/docshell/test/unit/test_bug442584.js
+++ b/docshell/test/unit/test_bug442584.js
@@ -9,26 +9,22 @@ function run_test() {
   // Fill up the queue
   prefs.setBoolPref("network.prefetch-next", true);
   for (var i = 0; i < 5; i++) {
     var uri = ios.newURI("http://localhost/" + i, null, null);
     prefetch.prefetchURI(uri, uri, null, true);
   }
 
   // Make sure the queue has items in it...
-  var queue = prefetch.enumerateQueue();
-  do_check_true(queue.hasMoreElements());
+  do_check_true(prefetch.hasMoreElements());
 
   // Now disable the pref to force the queue to empty...
   prefs.setBoolPref("network.prefetch-next", false);
-  queue = prefetch.enumerateQueue();
-  do_check_false(queue.hasMoreElements());
+  do_check_false(prefetch.hasMoreElements());
 
   // Now reenable the pref, and add more items to the queue.
   prefs.setBoolPref("network.prefetch-next", true);
   for (var i = 0; i < 5; i++) {
     var uri = ios.newURI("http://localhost/" + i, null, null);
     prefetch.prefetchURI(uri, uri, null, true);
   }
-  queue = prefetch.enumerateQueue();
-  do_check_true(queue.hasMoreElements());
+  do_check_true(prefetch.hasMoreElements());
 }
-
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1715,30 +1715,30 @@ Element::UnbindFromTree(bool aDeep, bool
                   "kids!");
 
   RemoveFromIdTable();
 
   // Make sure to unbind this node before doing the kids
   nsIDocument* document =
     HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
 
+  if (HasPointerLock()) {
+    nsIDocument::UnlockPointer();
+  }
   if (aNullParent) {
     if (IsFullScreenAncestor()) {
       // The element being removed is an ancestor of the full-screen element,
       // exit full-screen state.
       nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                       NS_LITERAL_CSTRING("DOM"), OwnerDoc(),
                                       nsContentUtils::eDOM_PROPERTIES,
                                       "RemovedFullScreenElement");
       // Fully exit full-screen.
       nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
     }
-    if (HasPointerLock()) {
-      nsIDocument::UnlockPointer();
-    }
 
     if (GetParent() && GetParent()->IsInUncomposedDoc()) {
       // Update the editable descendant count in the ancestors before we
       // lose the reference to the parent.
       int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this);
       if (editableDescendantChange != 0) {
         nsIContent* parent = GetParent();
         while (parent) {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -663,21 +663,16 @@ public:
   already_AddRefed<nsIHTMLCollection>
     GetElementsByTagName(const nsAString& aQualifiedName);
   already_AddRefed<nsIHTMLCollection>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName,
                            ErrorResult& aError);
   already_AddRefed<nsIHTMLCollection>
     GetElementsByClassName(const nsAString& aClassNames);
-  bool MozMatchesSelector(const nsAString& aSelector,
-                          ErrorResult& aError)
-  {
-    return Matches(aSelector, aError);
-  }
   void SetPointerCapture(int32_t aPointerId, ErrorResult& aError)
   {
     bool activeState = false;
     if (!nsIPresShell::GetPointerInfo(aPointerId, activeState)) {
       aError.Throw(NS_ERROR_DOM_INVALID_POINTER_ERR);
       return;
     }
     if (!IsInDoc()) {
@@ -1764,18 +1759,18 @@ NS_IMETHOD GetScrollTopMax(int32_t* aScr
 {                                                                             \
   *aScrollTopMax = Element::ScrollTopMax();                                   \
   return NS_OK;                                                               \
 }                                                                             \
 NS_IMETHOD MozMatchesSelector(const nsAString& selector,                      \
                               bool* _retval) final override                   \
 {                                                                             \
   mozilla::ErrorResult rv;                                                    \
-  *_retval = Element::MozMatchesSelector(selector, rv);                       \
-  return rv.StealNSResult();                                                      \
+  *_retval = Element::Matches(selector, rv);                                  \
+  return rv.StealNSResult();                                                  \
 }                                                                             \
 NS_IMETHOD SetCapture(bool retargetToElement) final override                  \
 {                                                                             \
   Element::SetCapture(retargetToElement);                                     \
   return NS_OK;                                                               \
 }                                                                             \
 NS_IMETHOD ReleaseCapture(void) final override                                \
 {                                                                             \
--- a/dom/base/URLSearchParams.cpp
+++ b/dom/base/URLSearchParams.cpp
@@ -413,10 +413,28 @@ URLSearchParams::Serialize(nsAString& aV
 void
 URLSearchParams::NotifyObserver()
 {
   if (mObserver) {
     mObserver->URLSearchParamsUpdated(this);
   }
 }
 
+uint32_t
+URLSearchParams::GetIterableLength() const
+{
+  return mParams->Length();
+}
+
+const nsAString&
+URLSearchParams::GetKeyAtIndex(uint32_t aIndex) const
+{
+  return mParams->GetKeyAtIndex(aIndex);
+}
+
+const nsAString&
+URLSearchParams::GetValueAtIndex(uint32_t aIndex) const
+{
+  return mParams->GetValueAtIndex(aIndex);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/URLSearchParams.h
+++ b/dom/base/URLSearchParams.h
@@ -86,16 +86,33 @@ public:
   // Returns true if aName was found and deleted, false otherwise.
   bool Delete(const nsAString& aName);
 
   void DeleteAll()
   {
     mParams.Clear();
   }
 
+  uint32_t Length() const
+  {
+    return mParams.Length();
+  }
+
+  const nsAString& GetKeyAtIndex(uint32_t aIndex) const
+  {
+    MOZ_ASSERT(aIndex < mParams.Length());
+    return mParams[aIndex].mKey;
+  }
+
+  const nsAString& GetValueAtIndex(uint32_t aIndex) const
+  {
+    MOZ_ASSERT(aIndex < mParams.Length());
+    return mParams[aIndex].mValue;
+  }
+
 private:
   void DecodeString(const nsACString& aInput, nsAString& aOutput);
   void ConvertString(const nsACString& aInput, nsAString& aOutput);
 
   struct Param
   {
     nsString mKey;
     nsString mValue;
@@ -148,16 +165,20 @@ public:
   void Set(const nsAString& aName, const nsAString& aValue);
 
   void Append(const nsAString& aName, const nsAString& aValue);
 
   bool Has(const nsAString& aName);
 
   void Delete(const nsAString& aName);
 
+  uint32_t GetIterableLength() const;
+  const nsAString& GetKeyAtIndex(uint32_t aIndex) const;
+  const nsAString& GetValueAtIndex(uint32_t aIndex) const;
+
   void Stringify(nsString& aRetval) const
   {
     Serialize(aRetval);
   }
 
   typedef URLParams::ForEachIterator ForEachIterator;
 
   bool
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3191,17 +3191,17 @@ convertSheetType(uint32_t aSheetType)
       return nsIDocument::eAgentSheet;
     case nsDOMWindowUtils::USER_SHEET:
       return nsIDocument::eUserSheet;
     case nsDOMWindowUtils::AUTHOR_SHEET:
       return nsIDocument::eAuthorSheet;
     default:
       NS_ASSERTION(false, "wrong type");
       // we must return something although this should never happen
-      return nsIDocument::SheetTypeCount;
+      return nsIDocument::AdditionalSheetTypeCount;
   }
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::LoadSheet(nsIURI *aSheetURI, uint32_t aSheetType)
 {
   NS_ENSURE_ARG_POINTER(aSheetURI);
   NS_ENSURE_ARG(aSheetType == AGENT_SHEET ||
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2408,17 +2408,17 @@ nsDocument::RemoveDocStyleSheetsFromStyl
         shell->StyleSet()->RemoveDocStyleSheet(sheet);
       }
     }
     // XXX Tell observers?
   }
 }
 
 void
-nsDocument::RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, nsStyleSet::sheetType aType)
+nsDocument::RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, SheetType aType)
 {
   // The stylesheets should forget us
   int32_t indx = aSheets.Count();
   while (--indx >= 0) {
     nsIStyleSheet* sheet = aSheets[indx];
     sheet->SetOwningDocument(nullptr);
 
     if (sheet->IsApplicable()) {
@@ -2435,26 +2435,27 @@ nsDocument::RemoveStyleSheetsFromStyleSe
 
 void
 nsDocument::ResetStylesheetsToURI(nsIURI* aURI)
 {
   MOZ_ASSERT(aURI);
 
   mozAutoDocUpdate upd(this, UPDATE_STYLE, true);
   RemoveDocStyleSheetsFromStyleSets();
-  RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, nsStyleSet::eAgentSheet);
-  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], nsStyleSet::eAgentSheet);
-  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], nsStyleSet::eUserSheet);
-  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], nsStyleSet::eDocSheet);
+  RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, SheetType::Agent);
+  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], SheetType::Agent);
+  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], SheetType::User);
+  RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], SheetType::Doc);
 
   // Release all the sheets
   mStyleSheets.Clear();
   mOnDemandBuiltInUASheets.Clear();
-  for (uint32_t i = 0; i < SheetTypeCount; ++i)
-    mAdditionalSheets[i].Clear();
+  for (auto& sheets : mAdditionalSheets) {
+    sheets.Clear();
+  }
 
   // NOTE:  We don't release the catalog sheets.  It doesn't really matter
   // now, but it could in the future -- in which case not releasing them
   // is probably the right thing to do.
 
   // Now reset our inline style and attribute sheets.
   if (mAttrStyleSheet) {
     mAttrStyleSheet->Reset();
@@ -2478,42 +2479,42 @@ nsDocument::ResetStylesheetsToURI(nsIURI
     FillStyleSet(shell->StyleSet());
   }
 }
 
 static bool
 AppendAuthorSheet(nsIStyleSheet *aSheet, void *aData)
 {
   nsStyleSet *styleSet = static_cast<nsStyleSet*>(aData);
-  styleSet->AppendStyleSheet(nsStyleSet::eDocSheet, aSheet);
+  styleSet->AppendStyleSheet(SheetType::Doc, aSheet);
   return true;
 }
 
 static void
 AppendSheetsToStyleSet(nsStyleSet* aStyleSet,
                        const nsCOMArray<nsIStyleSheet>& aSheets,
-                       nsStyleSet::sheetType aType)
+                       SheetType aType)
 {
   for (int32_t i = aSheets.Count() - 1; i >= 0; --i) {
     aStyleSet->AppendStyleSheet(aType, aSheets[i]);
   }
 }
 
 
 void
 nsDocument::FillStyleSet(nsStyleSet* aStyleSet)
 {
   NS_PRECONDITION(aStyleSet, "Must have a style set");
-  NS_PRECONDITION(aStyleSet->SheetCount(nsStyleSet::eDocSheet) == 0,
+  NS_PRECONDITION(aStyleSet->SheetCount(SheetType::Doc) == 0,
                   "Style set already has document sheets?");
 
   // We could consider moving this to nsStyleSet::Init, to match its
   // handling of the eAnimationSheet and eTransitionSheet levels.
-  aStyleSet->DirtyRuleProcessors(nsStyleSet::ePresHintSheet);
-  aStyleSet->DirtyRuleProcessors(nsStyleSet::eStyleAttrSheet);
+  aStyleSet->DirtyRuleProcessors(SheetType::PresHint);
+  aStyleSet->DirtyRuleProcessors(SheetType::StyleAttr);
 
   int32_t i;
   for (i = mStyleSheets.Count() - 1; i >= 0; --i) {
     nsIStyleSheet* sheet = mStyleSheets[i];
     if (sheet->IsApplicable()) {
       aStyleSet->AddDocStyleSheet(sheet, this);
     }
   }
@@ -2523,26 +2524,26 @@ nsDocument::FillStyleSet(nsStyleSet* aSt
     sheetService->AuthorStyleSheets()->EnumerateForwards(AppendAuthorSheet,
                                                          aStyleSet);
   }
 
   // Iterate backwards to maintain order
   for (i = mOnDemandBuiltInUASheets.Count() - 1; i >= 0; --i) {
     nsIStyleSheet* sheet = mOnDemandBuiltInUASheets[i];
     if (sheet->IsApplicable()) {
-      aStyleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, sheet);
+      aStyleSet->PrependStyleSheet(SheetType::Agent, sheet);
     }
   }
 
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
-                         nsStyleSet::eAgentSheet);
+                         SheetType::Agent);
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet],
-                         nsStyleSet::eUserSheet);
+                         SheetType::User);
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
-                         nsStyleSet::eDocSheet);
+                         SheetType::Doc);
 }
 
 static void
 WarnIfSandboxIneffective(nsIDocShell* aDocShell,
                          uint32_t aSandboxFlags,
                          nsIChannel* aChannel)
 {
   // If the document is sandboxed (via the HTML5 iframe sandbox
@@ -4136,17 +4137,17 @@ nsDocument::AddOnDemandBuiltInUASheet(CS
   if (aSheet->IsApplicable()) {
     // This is like |AddStyleSheetToStyleSets|, but for an agent sheet.
     nsCOMPtr<nsIPresShell> shell = GetShell();
     if (shell) {
       // Note that prepending here is necessary to make sure that html.css etc.
       // do not override Firefox OS/Mobile's content.css sheet. Maybe we should
       // have an insertion point to match the order of
       // nsDocumentViewer::CreateStyleSet though?
-      shell->StyleSet()->PrependStyleSheet(nsStyleSet::eAgentSheet, aSheet);
+      shell->StyleSet()->PrependStyleSheet(SheetType::Agent, aSheet);
     }
   }
 
   NotifyStyleSheetAdded(aSheet, false);
 }
 
 int32_t
 nsDocument::GetNumberOfStyleSheets() const
@@ -4368,30 +4369,30 @@ nsDocument::NotifyStyleSheetApplicableSt
     mozilla::services::GetObserverService();
   if (observerService) {
     observerService->NotifyObservers(static_cast<nsIDocument*>(this),
                                      "style-sheet-applicable-state-changed",
                                      nullptr);
   }
 }
 
-static nsStyleSet::sheetType
+static SheetType
 ConvertAdditionalSheetType(nsIDocument::additionalSheetType aType)
 {
   switch(aType) {
     case nsIDocument::eAgentSheet:
-      return nsStyleSet::eAgentSheet;
+      return SheetType::Agent;
     case nsIDocument::eUserSheet:
-      return nsStyleSet::eUserSheet;
+      return SheetType::User;
     case nsIDocument::eAuthorSheet:
-      return nsStyleSet::eDocSheet;
+      return SheetType::Doc;
     default:
-      NS_ASSERTION(false, "wrong type");
+      MOZ_ASSERT(false, "wrong type");
       // we must return something although this should never happen
-      return nsStyleSet::eSheetTypeCount;
+      return SheetType::Count;
   }
 }
 
 static int32_t
 FindSheet(const nsCOMArray<nsIStyleSheet>& aSheets, nsIURI* aSheetURI)
 {
   for (int32_t i = aSheets.Count() - 1; i >= 0; i-- ) {
     bool bEqual;
@@ -4455,17 +4456,17 @@ nsDocument::AddAdditionalStyleSheet(addi
   if (!aSheet->IsApplicable())
     return NS_ERROR_INVALID_ARG;
 
   mAdditionalSheets[aType].AppendObject(aSheet);
 
   BeginUpdate(UPDATE_STYLE);
   nsCOMPtr<nsIPresShell> shell = GetShell();
   if (shell) {
-    nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType);
+    SheetType type = ConvertAdditionalSheetType(aType);
     shell->StyleSet()->AppendStyleSheet(type, aSheet);
   }
 
   // Passing false, so documet.styleSheets.length will not be affected by
   // these additional sheets.
   NotifyStyleSheetAdded(aSheet, false);
   EndUpdate(UPDATE_STYLE);
   return NS_OK;
@@ -4483,17 +4484,17 @@ nsDocument::RemoveAdditionalStyleSheet(a
     nsCOMPtr<nsIStyleSheet> sheetRef = sheets[i];
     sheets.RemoveObjectAt(i);
 
     BeginUpdate(UPDATE_STYLE);
     if (!mIsGoingAway) {
       MOZ_ASSERT(sheetRef->IsApplicable());
       nsCOMPtr<nsIPresShell> shell = GetShell();
       if (shell) {
-        nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType);
+        SheetType type = ConvertAdditionalSheetType(aType);
         shell->StyleSet()->RemoveStyleSheet(type, sheetRef);
       }
     }
 
     // Passing false, so documet.styleSheets.length will not be affected by
     // these additional sheets.
     NotifyStyleSheetRemoved(sheetRef, false);
     EndUpdate(UPDATE_STYLE);
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1511,17 +1511,17 @@ public:
 protected:
   already_AddRefed<nsIPresShell> doCreateShell(nsPresContext* aContext,
                                                nsViewManager* aViewManager,
                                                nsStyleSet* aStyleSet,
                                                nsCompatibility aCompatMode);
 
   void RemoveDocStyleSheetsFromStyleSets();
   void RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, 
-                                      nsStyleSet::sheetType aType);
+                                      mozilla::SheetType aType);
   void ResetStylesheetsToURI(nsIURI* aURI);
   void FillStyleSet(nsStyleSet* aStyleSet);
 
   // Return whether all the presshells for this document are safe to flush
   bool IsSafeToFlush() const;
 
   void DispatchPageTransition(mozilla::dom::EventTarget* aDispatchTarget,
                               const nsAString& aType,
@@ -1566,17 +1566,17 @@ protected:
 
   // Weak reference to our sink for in case we no longer have a parser.  This
   // will allow us to flush out any pending stuff from the sink even if
   // EndLoad() has already happened.
   nsWeakPtr mWeakSink;
 
   nsCOMArray<nsIStyleSheet> mStyleSheets;
   nsCOMArray<nsIStyleSheet> mOnDemandBuiltInUASheets;
-  nsCOMArray<nsIStyleSheet> mAdditionalSheets[SheetTypeCount];
+  nsCOMArray<nsIStyleSheet> mAdditionalSheets[AdditionalSheetTypeCount];
 
   // Array of observers
   nsTObserverArray<nsIDocumentObserver*> mObservers;
 
   // Tracker for animations that are waiting to start.
   // nullptr until GetOrCreatePendingAnimationTracker is called.
   RefPtr<mozilla::PendingAnimationTracker> mPendingAnimationTracker;
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4956,26 +4956,26 @@ nsGlobalWindow::GetInnerWidth(int32_t* a
 
   ErrorResult rv;
   *aInnerWidth = GetInnerWidth(rv);
 
   return rv.StealNSResult();
 }
 
 void
-nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError)
+nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     aError.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
-  CheckSecurityWidthAndHeight(&aInnerWidth, nullptr);
+  CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerIsChrome);
 
   RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
 
   if (presShell && presShell->GetIsViewportOverridden())
   {
     nscoord height = 0;
 
     RefPtr<nsPresContext> presContext;
@@ -4994,38 +4994,38 @@ nsGlobalWindow::SetInnerWidthOuter(int32
   nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
   docShellAsWin->GetSize(&unused, &height);
   aError = SetDocShellWidthAndHeight(CSSToDevIntPixels(aInnerWidth), height);
 }
 
 void
 nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
                               ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerWidth,
                             aValue, "innerWidth", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth)
 {
-  FORWARD_TO_INNER(SetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetInnerWidth(aInnerWidth, rv);
+  SetInnerWidthOuter(aInnerWidth, rv, /* aCallerIsChrome = */ true);
 
   return rv.StealNSResult();
 }
 
 int32_t
 nsGlobalWindow::GetInnerHeightOuter(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
@@ -5057,17 +5057,17 @@ nsGlobalWindow::GetInnerHeight(int32_t* 
 
   ErrorResult rv;
   *aInnerHeight = GetInnerHeight(rv);
 
   return rv.StealNSResult();
 }
 
 void
-nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError)
+nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     aError.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
@@ -5076,56 +5076,56 @@ nsGlobalWindow::SetInnerHeightOuter(int3
   if (presShell && presShell->GetIsViewportOverridden())
   {
     RefPtr<nsPresContext> presContext;
     presContext = presShell->GetPresContext();
 
     nsRect shellArea = presContext->GetVisibleArea();
     nscoord height = aInnerHeight;
     nscoord width = shellArea.width;
-    CheckSecurityWidthAndHeight(nullptr, &height);
+    CheckSecurityWidthAndHeight(nullptr, &height, aCallerIsChrome);
     SetCSSViewportWidthAndHeight(width,
                                  nsPresContext::CSSPixelsToAppUnits(height));
     return;
   }
 
   int32_t height = 0;
   int32_t width  = 0;
 
   nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
   docShellAsWin->GetSize(&width, &height);
-  CheckSecurityWidthAndHeight(nullptr, &aInnerHeight);
+  CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerIsChrome);
   aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight));
 }
 
 void
 nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
                                ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerHeight,
                             aValue, "innerHeight", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight)
 {
-  FORWARD_TO_INNER(SetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetInnerHeight(aInnerHeight, rv);
+  SetInnerHeightOuter(aInnerHeight, rv, /* aCallerIsChrome = */ true);
 
   return rv.StealNSResult();
 }
 
 nsIntSize
 nsGlobalWindow::GetOuterSize(ErrorResult& aError)
 {
   MOZ_ASSERT(IsOuterWindow());
@@ -5220,28 +5220,29 @@ nsGlobalWindow::GetOuterHeight(int32_t* 
   ErrorResult rv;
   *aOuterHeight = GetOuterHeight(rv);
 
   return rv.StealNSResult();
 }
 
 void
 nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
-                             ErrorResult& aError)
+                             ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
-                              aIsWidth ? nullptr : &aLengthCSSPixels);
+                              aIsWidth ? nullptr : &aLengthCSSPixels,
+                              aCallerIsChrome);
 
   int32_t width, height;
   aError = treeOwnerAsWin->GetSize(&width, &height);
   if (aError.Failed()) {
     return;
   }
 
   int32_t lengthDevPixels = CSSToDevIntPixels(aLengthCSSPixels);
@@ -5249,85 +5250,85 @@ nsGlobalWindow::SetOuterSize(int32_t aLe
     width = lengthDevPixels;
   } else {
     height = lengthDevPixels;
   }
   aError = treeOwnerAsWin->SetSize(width, height, true);    
 }
 
 void
-nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError)
+nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
-  SetOuterSize(aOuterWidth, true, aError);
+  SetOuterSize(aOuterWidth, true, aError, aCallerIsChrome);
 }
 
 void
 nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
                               ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterWidth,
                             aValue, "outerWidth", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth)
 {
-  FORWARD_TO_INNER(SetOuterWidth, (aOuterWidth), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetOuterWidth, (aOuterWidth), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetOuterWidth(aOuterWidth, rv);
-
-  return rv.StealNSResult();
-}
-
-void
-nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError)
+  SetOuterWidthOuter(aOuterWidth, rv, /* aCallerIsChrome = */ true);
+
+  return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
-  SetOuterSize(aOuterHeight, false, aError);
+  SetOuterSize(aOuterHeight, false, aError, aCallerIsChrome);
 }
 
 void
 nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
                                ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterHeight,
                             aValue, "outerHeight", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight)
 {
-  FORWARD_TO_INNER(SetOuterHeight, (aOuterHeight), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetOuterHeight, (aOuterHeight), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetOuterHeight(aOuterHeight, rv);
+  SetOuterHeightOuter(aOuterHeight, rv, /* aCallerIsChrome = */ true);
 
   return rv.StealNSResult();
 }
 
 nsIntPoint
 nsGlobalWindow::GetScreenXY(ErrorResult& aError)
 {
   MOZ_ASSERT(IsOuterWindow());
@@ -5639,63 +5640,63 @@ nsGlobalWindow::MatchMedia(const nsAStri
   ErrorResult rv;
   RefPtr<MediaQueryList> mediaQueryList = MatchMedia(aMediaQueryList, rv);
   mediaQueryList.forget(aResult);
 
   return rv.StealNSResult();
 }
 
 void
-nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError)
+nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   int32_t x, y;
   aError = treeOwnerAsWin->GetPosition(&x, &y);
   if (aError.Failed()) {
     return;
   }
 
-  CheckSecurityLeftAndTop(&aScreenX, nullptr);
+  CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerIsChrome);
   x = CSSToDevIntPixels(aScreenX);
 
   aError = treeOwnerAsWin->SetPosition(x, y);
 }
 
 void
 nsGlobalWindow::SetScreenX(int32_t aScreenX, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue,
                            ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenX,
                             aValue, "screenX", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetScreenX(int32_t aScreenX)
 {
-  FORWARD_TO_INNER(SetScreenX, (aScreenX), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetScreenX, (aScreenX), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetScreenX(aScreenX, rv);
+  SetScreenXOuter(aScreenX, rv, /* aCallerIsChrome = */ true);
 
   return rv.StealNSResult();
 }
 
 int32_t
 nsGlobalWindow::GetScreenYOuter(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
@@ -5725,76 +5726,76 @@ nsGlobalWindow::GetScreenY(JSContext* aC
                            JS::MutableHandle<JS::Value> aValue,
                            ErrorResult& aError)
 {
   GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenY, aValue,
                             aError);
 }
 
 void
-nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError)
+nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   int32_t x, y;
   aError = treeOwnerAsWin->GetPosition(&x, &y);
   if (aError.Failed()) {
     return;
   }
 
-  CheckSecurityLeftAndTop(nullptr, &aScreenY);
+  CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerIsChrome);
   y = CSSToDevIntPixels(aScreenY);
 
   aError = treeOwnerAsWin->SetPosition(x, y);
 }
 
 void
 nsGlobalWindow::SetScreenY(int32_t aScreenY, ErrorResult& aError)
 {
-  FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError), aError, );
+  FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError, nsContentUtils::IsCallerChrome()), aError, );
 }
 
 void
 nsGlobalWindow::SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue,
                            ErrorResult& aError)
 {
   SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenY,
                             aValue, "screenY", aError);
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::SetScreenY(int32_t aScreenY)
 {
-  FORWARD_TO_INNER(SetScreenY, (aScreenY), NS_ERROR_UNEXPECTED);
+  FORWARD_TO_OUTER(SetScreenY, (aScreenY), NS_ERROR_UNEXPECTED);
 
   if (IsFrame()) {
     return NS_OK;
   }
 
   ErrorResult rv;
-  SetScreenY(aScreenY, rv);
+  SetScreenYOuter(aScreenY, rv, /* aCallerIsChrome = */ true);
 
   return rv.StealNSResult();
 }
 
 // NOTE: Arguments to this function should have values scaled to
 // CSS pixels, not device pixels.
 void
-nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight)
+nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, bool aCallerIsChrome)
 {
   MOZ_ASSERT(IsOuterWindow());
 
 #ifdef MOZ_XUL
-  if (!nsContentUtils::IsCallerChrome()) {
+  if (!aCallerIsChrome) {
     // if attempting to resize the window, hide any open popups
     nsContentUtils::HidePopupsInDocument(mDoc);
   }
 #endif
 
   // This one is easy. Just ensure the variable is greater than 100;
   if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
     // Check security state for use in determing window dimensions
@@ -5843,25 +5844,25 @@ nsGlobalWindow::SetCSSViewportWidthAndHe
   shellArea.width = aInnerWidth;
 
   presContext->SetVisibleArea(shellArea);
 }
 
 // NOTE: Arguments to this function should have values scaled to
 // CSS pixels, not device pixels.
 void
-nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop)
+nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCallerIsChrome)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   // This one is harder. We have to get the screen size and window dimensions.
 
   // Check security state for use in determing window dimensions
 
-  if (!nsContentUtils::IsCallerChrome()) {
+  if (!aCallerIsChrome) {
 #ifdef MOZ_XUL
     // if attempting to move the window, hide any open popups
     nsContentUtils::HidePopupsInDocument(mDoc);
 #endif
 
     nsGlobalWindow* rootWindow =
       static_cast<nsGlobalWindow*>(GetPrivateRoot());
     if (rootWindow) {
@@ -7618,17 +7619,17 @@ nsGlobalWindow::MoveToOuter(int32_t aXPo
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   // Mild abuse of a "size" object so we don't need more helper functions.
   nsIntSize cssPos(aXPos, aYPos);
-  CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height);
+  CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome);
 
   nsIntSize devPos = CSSToDevIntPixels(cssPos);
 
   aError = treeOwnerAsWin->SetPosition(devPos.width, devPos.height);
 }
 
 void
 nsGlobalWindow::MoveTo(int32_t aXPos, int32_t aYPos, ErrorResult& aError)
@@ -7678,17 +7679,17 @@ nsGlobalWindow::MoveByOuter(int32_t aXDi
   }
 
   // mild abuse of a "size" object so we don't need more helper functions
   nsIntSize cssPos(DevToCSSIntPixels(nsIntSize(x, y)));
 
   cssPos.width += aXDif;
   cssPos.height += aYDif;
   
-  CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height);
+  CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome);
 
   nsIntSize newDevPos(CSSToDevIntPixels(cssPos));
 
   aError = treeOwnerAsWin->SetPosition(newDevPos.width, newDevPos.height);
 }
 
 void
 nsGlobalWindow::MoveBy(int32_t aXDif, int32_t aYDif, ErrorResult& aError)
@@ -7736,17 +7737,17 @@ nsGlobalWindow::ResizeToOuter(int32_t aW
 
   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
   if (!treeOwnerAsWin) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
   
   nsIntSize cssSize(aWidth, aHeight);
-  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
+  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
 
   nsIntSize devSz(CSSToDevIntPixels(cssSize));
 
   aError = treeOwnerAsWin->SetSize(devSz.width, devSz.height, true);
 }
 
 void
 nsGlobalWindow::ResizeTo(int32_t aWidth, int32_t aHeight, ErrorResult& aError)
@@ -7816,17 +7817,17 @@ nsGlobalWindow::ResizeByOuter(int32_t aW
   // into CSS pixels, add the arguments, do the security check, and
   // then convert back to device pixels for the call to SetSize.
 
   nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
 
   cssSize.width += aWidthDif;
   cssSize.height += aHeightDif;
 
-  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
+  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
 
   nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
 
   aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);
 }
 
 void
 nsGlobalWindow::ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
@@ -7883,17 +7884,17 @@ nsGlobalWindow::SizeToContentOuter(Error
   // rules.
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
   if (!treeOwner) {
     aError.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
-  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height);
+  CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
 
   nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
 
   aError = treeOwner->SizeShellTo(mDocShell, newDevSize.width,
                                   newDevSize.height);
 }
 
 void
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1202,37 +1202,37 @@ protected:
                                  mozilla::ErrorResult& aError);
   void SetReplaceableWindowCoord(JSContext* aCx, WindowCoordSetter aSetter,
                                  JS::Handle<JS::Value> aValue,
                                  const char* aPropName,
                                  mozilla::ErrorResult& aError);
   // And the implementations of WindowCoordGetter/WindowCoordSetter.
   int32_t GetInnerWidthOuter(mozilla::ErrorResult& aError);
   int32_t GetInnerWidth(mozilla::ErrorResult& aError);
-  void SetInnerWidthOuter(int32_t aInnerWidth, mozilla::ErrorResult& aError);
+  void SetInnerWidthOuter(int32_t aInnerWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetInnerWidth(int32_t aInnerWidth, mozilla::ErrorResult& aError);
   int32_t GetInnerHeightOuter(mozilla::ErrorResult& aError);
   int32_t GetInnerHeight(mozilla::ErrorResult& aError);
-  void SetInnerHeightOuter(int32_t aInnerHeight, mozilla::ErrorResult& aError);
+  void SetInnerHeightOuter(int32_t aInnerHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetInnerHeight(int32_t aInnerHeight, mozilla::ErrorResult& aError);
   int32_t GetScreenXOuter(mozilla::ErrorResult& aError);
   int32_t GetScreenX(mozilla::ErrorResult& aError);
-  void SetScreenXOuter(int32_t aScreenX, mozilla::ErrorResult& aError);
+  void SetScreenXOuter(int32_t aScreenX, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetScreenX(int32_t aScreenX, mozilla::ErrorResult& aError);
   int32_t GetScreenYOuter(mozilla::ErrorResult& aError);
   int32_t GetScreenY(mozilla::ErrorResult& aError);
-  void SetScreenYOuter(int32_t aScreenY, mozilla::ErrorResult& aError);
+  void SetScreenYOuter(int32_t aScreenY, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetScreenY(int32_t aScreenY, mozilla::ErrorResult& aError);
   int32_t GetOuterWidthOuter(mozilla::ErrorResult& aError);
   int32_t GetOuterWidth(mozilla::ErrorResult& aError);
-  void SetOuterWidthOuter(int32_t aOuterWidth, mozilla::ErrorResult& aError);
+  void SetOuterWidthOuter(int32_t aOuterWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetOuterWidth(int32_t aOuterWidth, mozilla::ErrorResult& aError);
   int32_t GetOuterHeightOuter(mozilla::ErrorResult& aError);
   int32_t GetOuterHeight(mozilla::ErrorResult& aError);
-  void SetOuterHeightOuter(int32_t aOuterHeight, mozilla::ErrorResult& aError);
+  void SetOuterHeightOuter(int32_t aOuterHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
   void SetOuterHeight(int32_t aOuterHeight, mozilla::ErrorResult& aError);
 
   // Array of idle observers that are notified of idle events.
   nsTObserverArray<IdleObserverHolder> mIdleObservers;
 
   // Idle timer used for function callbacks to notify idle observers.
   nsCOMPtr<nsITimer> mIdleTimer;
 
@@ -1452,18 +1452,18 @@ public:
 
   // Inner windows only.
   nsresult FireHashchange(const nsAString &aOldURL, const nsAString &aNewURL);
 
   void FlushPendingNotifications(mozFlushType aType);
 
   // Outer windows only.
   void EnsureReflowFlushAndPaint();
-  void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height);
-  void CheckSecurityLeftAndTop(int32_t* left, int32_t* top);
+  void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height, bool aCallerIsChrome);
+  void CheckSecurityLeftAndTop(int32_t* left, int32_t* top, bool aCallerIsChrome);
 
   // Outer windows only.
   // Arguments to this function should have values in app units
   void SetCSSViewportWidthAndHeight(nscoord width, nscoord height);
   // Arguments to this function should have values in device pixels
   nsresult SetDocShellWidthAndHeight(int32_t width, int32_t height);
 
   static bool CanSetProperty(const char *aPrefName);
@@ -1481,17 +1481,17 @@ public:
   void GetScrollMaxXYOuter(int32_t* aScrollMaxX, int32_t* aScrollMaxY);
   void GetScrollMaxXY(int32_t* aScrollMaxX, int32_t* aScrollMaxY,
                       mozilla::ErrorResult& aError);
 
   // Outer windows only.
   nsresult GetInnerSize(mozilla::CSSIntSize& aSize);
   nsIntSize GetOuterSize(mozilla::ErrorResult& aError);
   void SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
-                    mozilla::ErrorResult& aError);
+                    mozilla::ErrorResult& aError, bool aCallerIsChrome);
   nsRect GetInnerScreenRect();
 
   void ScrollTo(const mozilla::CSSIntPoint& aScroll,
                 const mozilla::dom::ScrollOptions& aOptions);
 
   bool IsFrame()
   {
     return GetParentInternal() != nullptr;
--- a/dom/base/nsHostObjectProtocolHandler.cpp
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -566,21 +566,24 @@ nsHostObjectProtocolHandler::NewChannel2
 
   ErrorResult rv;
   nsCOMPtr<nsIInputStream> stream;
   blob->GetInternalStream(getter_AddRefs(stream), rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
+  nsAutoString contentType;
+  blob->GetType(contentType);
+
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
                                         uri,
                                         stream,
-                                        EmptyCString(), // aContentType
+                                        NS_ConvertUTF16toUTF8(contentType),
                                         EmptyCString(), // aContentCharset
                                         aLoadInfo);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   nsString type;
   blob->GetType(type);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -939,17 +939,17 @@ public:
    */
   virtual void SetStyleSheetApplicableState(nsIStyleSheet* aSheet,
                                             bool aApplicable) = 0;
 
   enum additionalSheetType {
     eAgentSheet,
     eUserSheet,
     eAuthorSheet,
-    SheetTypeCount
+    AdditionalSheetTypeCount
   };
 
   virtual nsresult LoadAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheetURI) = 0;
   virtual nsresult AddAdditionalStyleSheet(additionalSheetType aType, nsIStyleSheet* aSheet) = 0;
   virtual void RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* sheetURI) = 0;
   virtual nsIStyleSheet* FirstAdditionalAuthorSheet() = 0;
 
   /**
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -270,59 +270,92 @@ nsScriptLoader::ShouldLoadScript(nsIDocu
   rv = CheckContentPolicy(aDocument, aContext, aURI, aType, aIsPreLoad);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return NS_OK;
 }
 
+class ContextMediator : public nsIStreamLoaderObserver
+{
+public:
+  explicit ContextMediator(nsScriptLoader *aScriptLoader, nsISupports *aContext)
+  : mScriptLoader(aScriptLoader)
+  , mContext(aContext) {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+  virtual ~ContextMediator() {}
+  RefPtr<nsScriptLoader> mScriptLoader;
+  nsCOMPtr<nsISupports>  mContext;
+};
+
+NS_IMPL_ISUPPORTS(ContextMediator, nsIStreamLoaderObserver)
+
+NS_IMETHODIMP
+ContextMediator::OnStreamComplete(nsIStreamLoader* aLoader,
+                                  nsISupports* aContext,
+                                  nsresult aStatus,
+                                  uint32_t aStringLen,
+                                  const uint8_t* aString)
+{
+  // pass arguments through except for the aContext,
+  // we have to mediate and use mContext instead.
+  return mScriptLoader->OnStreamComplete(aLoader, mContext, aStatus,
+                                         aStringLen, aString);
+}
+
 nsresult
 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
                           bool aScriptFromHead)
 {
-  nsISupports *context = aRequest->mElement.get()
-                         ? static_cast<nsISupports *>(aRequest->mElement.get())
-                         : static_cast<nsISupports *>(mDocument);
-  nsresult rv = ShouldLoadScript(mDocument, context, aRequest->mURI, aType, aRequest->IsPreload());
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
-
-  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->MasterDocument()->GetWindow()));
-
-  if (!window) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  nsIDocShell *docshell = window->GetDocShell();
-
-  nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
-
   // If this document is sandboxed without 'allow-scripts', abort.
   if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
     return NS_OK;
   }
 
   nsContentPolicyType contentPolicyType = aRequest->IsPreload()
                                           ? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
                                           : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
+  nsCOMPtr<nsINode> context;
+  if (aRequest->mElement) {
+    context = do_QueryInterface(aRequest->mElement);
+  }
+  else {
+    context = mDocument;
+  }
+
+  nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
+  nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->MasterDocument()->GetWindow()));
+  NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
+  nsIDocShell *docshell = window->GetDocShell();
+  nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
+
+  nsSecurityFlags securityFlags =
+    aRequest->mCORSMode == CORS_NONE
+    ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+    : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+  if (aRequest->mCORSMode == CORS_USE_CREDENTIALS) {
+    securityFlags |= nsILoadInfo::SEC_REQUIRE_CORS_WITH_CREDENTIALS;
+  }
+  securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
 
   nsCOMPtr<nsIChannel> channel;
-  rv = NS_NewChannel(getter_AddRefs(channel),
-                     aRequest->mURI,
-                     mDocument,
-                     nsILoadInfo::SEC_NORMAL,
-                     contentPolicyType,
-                     loadGroup,
-                     prompter,
-                     nsIRequest::LOAD_NORMAL |
-                     nsIChannel::LOAD_CLASSIFY_URI);
+  nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+                              aRequest->mURI,
+                              context,
+                              securityFlags,
+                              contentPolicyType,
+                              loadGroup,
+                              prompter,
+                              nsIRequest::LOAD_NORMAL |
+                              nsIChannel::LOAD_CLASSIFY_URI);
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsIScriptElement *script = aRequest->mElement;
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
 
   if (cos) {
     if (aScriptFromHead &&
@@ -351,36 +384,23 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
       nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadContext);
 
   // Set the initiator type
   nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
   if (timedChannel) {
     timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
   }
 
+  RefPtr<ContextMediator> mediator = new ContextMediator(this, aRequest);
+
   nsCOMPtr<nsIStreamLoader> loader;
-  rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+  rv = NS_NewStreamLoader(getter_AddRefs(loader), mediator);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIStreamListener> listener = loader.get();
-
-  if (aRequest->mCORSMode != CORS_NONE) {
-    bool withCredentials = (aRequest->mCORSMode == CORS_USE_CREDENTIALS);
-    RefPtr<nsCORSListenerProxy> corsListener =
-      new nsCORSListenerProxy(listener, mDocument->NodePrincipal(),
-                              withCredentials);
-    rv = corsListener->Init(channel, DataURIHandling::Allow);
-    NS_ENSURE_SUCCESS(rv, rv);
-    listener = corsListener;
-  }
-
-  rv = channel->AsyncOpen(listener, aRequest);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
+  return channel->AsyncOpen2(loader);
 }
 
 bool
 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
                                              nsIURI * const &aURI) const
 {
   bool same;
   return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -46,20 +46,17 @@
 #include "nsIDOMWindow.h"
 #include "nsIVariant.h"
 #include "nsVariant.h"
 #include "nsIScriptError.h"
 #include "nsIStreamConverterService.h"
 #include "nsICachingChannel.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIContentPolicy.h"
-#include "nsContentPolicyUtils.h"
 #include "nsError.h"
-#include "nsCORSListenerProxy.h"
 #include "nsIHTMLDocument.h"
 #include "nsIStorageStream.h"
 #include "nsIPromptFactory.h"
 #include "nsIWindowWatcher.h"
 #include "nsIConsoleService.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "nsStringBuffer.h"
@@ -121,17 +118,17 @@ using namespace mozilla::dom;
 // The above states are mutually exclusive, change with ChangeState() only.
 // The states below can be combined.
 #define XML_HTTP_REQUEST_ABORTED        (1 << 7)  // Internal
 #define XML_HTTP_REQUEST_ASYNC          (1 << 8)  // Internal
 #define XML_HTTP_REQUEST_PARSEBODY      (1 << 9)  // Internal
 #define XML_HTTP_REQUEST_SYNCLOOPING    (1 << 10) // Internal
 #define XML_HTTP_REQUEST_BACKGROUND     (1 << 13) // Internal
 #define XML_HTTP_REQUEST_USE_XSITE_AC   (1 << 14) // Internal
-#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 15) // Internal
+#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE (1 << 15) // Internal
 #define XML_HTTP_REQUEST_AC_WITH_CREDENTIALS (1 << 16) // Internal
 #define XML_HTTP_REQUEST_TIMED_OUT (1 << 17) // Internal
 #define XML_HTTP_REQUEST_DELETED (1 << 18) // Internal
 
 #define XML_HTTP_REQUEST_LOADSTATES         \
   (XML_HTTP_REQUEST_UNSENT |                \
    XML_HTTP_REQUEST_OPENED |                \
    XML_HTTP_REQUEST_HEADERS_RECEIVED |      \
@@ -1528,57 +1525,25 @@ nsXMLHttpRequest::GetCurrentJARChannel()
 }
 
 bool
 nsXMLHttpRequest::IsSystemXHR()
 {
   return mIsSystem || nsContentUtils::IsSystemPrincipal(mPrincipal);
 }
 
-nsresult
+void
 nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
 {
   // A system XHR (chrome code or a web app with the right permission) can
-  // always perform cross-site requests. In the web app case, however, we
-  // must still check for protected URIs like file:///.
-  if (IsSystemXHR()) {
-    if (!nsContentUtils::IsSystemPrincipal(mPrincipal)) {
-      nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
-      nsCOMPtr<nsIURI> uri;
-      aChannel->GetOriginalURI(getter_AddRefs(uri));
-      return secMan->CheckLoadURIWithPrincipal(
-        mPrincipal, uri, nsIScriptSecurityManager::STANDARD);
-    }
-    return NS_OK;
-  }
-
-  // If this is a same-origin request or the channel's URI inherits
-  // its principal, it's allowed.
-  if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
-    return NS_OK;
+  // load anything, and same-origin loads are always allowed.
+  if (!IsSystemXHR() &&
+      !nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
+    mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
   }
-
-  // This is a cross-site request
-  mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
-
-  // Check if we need to do a preflight request.
-  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
-  NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
-
-  nsAutoCString method;
-  httpChannel->GetRequestMethod(method);
-  if (!mCORSUnsafeHeaders.IsEmpty() ||
-      (mUpload && mUpload->HasListeners()) ||
-      (!method.LowerCaseEqualsLiteral("get") &&
-       !method.LowerCaseEqualsLiteral("post") &&
-       !method.LowerCaseEqualsLiteral("head"))) {
-    mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
-  }
-
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url,
                        bool async, const nsAString& user,
                        const nsAString& password, uint8_t optional_argc)
 {
   if (!optional_argc) {
@@ -1675,31 +1640,16 @@ nsXMLHttpRequest::Open(const nsACString&
     baseURI = doc->GetBaseURI();
   }
 
   rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, baseURI);
   if (NS_FAILED(rv)) return rv;
 
   rv = CheckInnerWindowCorrectness();
   NS_ENSURE_SUCCESS(rv, rv);
-  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
-                                 uri,
-                                 mPrincipal,
-                                 doc,
-                                 EmptyCString(), //mime guess
-                                 nullptr,         //extra
-                                 &shouldLoad,
-                                 nsContentUtils::GetContentPolicy(),
-                                 nsContentUtils::GetSecurityManager());
-  if (NS_FAILED(rv)) return rv;
-  if (NS_CP_REJECTED(shouldLoad)) {
-    // Disallowed by content policy
-    return NS_ERROR_CONTENT_BLOCKED;
-  }
 
   // XXXbz this is wrong: we should only be looking at whether
   // user/password were passed, not at the values!  See bug 759624.
   if (user.WasPassed() && !user.Value().IsEmpty()) {
     nsAutoCString userpass;
     CopyUTF16toUTF8(user.Value(), userpass);
     if (password.WasPassed() && !password.Value().IsEmpty()) {
       userpass.Append(':');
@@ -1712,30 +1662,37 @@ nsXMLHttpRequest::Open(const nsACString&
   // operations will merge/override correctly.
   mAlreadySetHeaders.Clear();
 
   // When we are called from JS we can find the load group for the page,
   // and add ourselves to it. This way any pending requests
   // will be automatically aborted if the user leaves the page.
   nsCOMPtr<nsILoadGroup> loadGroup = GetLoadGroup();
 
-  nsSecurityFlags secFlags = nsILoadInfo::SEC_NORMAL;
+  nsSecurityFlags secFlags;
   nsLoadFlags loadFlags = nsIRequest::LOAD_BACKGROUND;
-  if (IsSystemXHR()) {
-    // Don't give this document the system principal.  We need to keep track of
-    // mPrincipal being system because we use it for various security checks
-    // that should be passing, but the document data shouldn't get a system
-    // principal.  Hence we set the sandbox flag in loadinfo, so that 
-    // GetChannelResultPrincipal will give us the nullprincipal.
-    secFlags |= nsILoadInfo::SEC_SANDBOXED;
-
-    //For a XHR, disable interception
+  if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+    // When chrome is loading we want to make sure to sandbox any potential
+    // result document. We also want to allow cross-origin loads.
+    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL |
+               nsILoadInfo::SEC_SANDBOXED;
+  }
+  else if (IsSystemXHR()) {
+    // For pages that have appropriate permissions, we want to still allow
+    // cross-origin loads, but make sure that the any potential result
+    // documents get the same principal as the loader.
+    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
     loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
-  } else {
-    secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+  }
+  else {
+    // Otherwise use CORS. Again, make sure that potential result documents
+    // use the same principal as the loader.
+    secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
+               nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
   }
 
   // If we have the document, use it
   if (doc) {
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        uri,
                        doc,
                        secFlags,
@@ -1750,36 +1707,36 @@ nsXMLHttpRequest::Open(const nsACString&
                        mPrincipal,
                        secFlags,
                        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
                        loadGroup,
                        nullptr,   // aCallbacks
                        loadFlags);
   }
 
-  if (NS_FAILED(rv)) return rv;
+  NS_ENSURE_SUCCESS(rv, rv);
 
   mState &= ~(XML_HTTP_REQUEST_USE_XSITE_AC |
-              XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
+              XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE);
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
   if (httpChannel) {
     rv = httpChannel->SetRequestMethod(method);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the initiator type
     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
     if (timedChannel) {
       timedChannel->SetInitiatorType(NS_LITERAL_STRING("xmlhttprequest"));
     }
   }
 
   ChangeState(XML_HTTP_REQUEST_OPENED);
 
-  return rv;
+  return NS_OK;
 }
 
 void
 nsXMLHttpRequest::PopulateNetworkInterfaceId()
 {
   if (mNetworkInterfaceId.IsEmpty()) {
     return;
   }
@@ -2813,24 +2770,31 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
       if (!nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader)) {
         mCORSUnsafeHeaders.AppendElement(NS_LITERAL_CSTRING("Content-Type"));
       }
     }
   }
 
   ResetResponse();
 
-  rv = CheckChannelForCrossSiteRequest(mChannel);
-  NS_ENSURE_SUCCESS(rv, rv);
+  CheckChannelForCrossSiteRequest(mChannel);
 
   bool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
 
-  // Hook us up to listen to redirects and the like
-  mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
-  mChannel->SetNotificationCallbacks(this);
+  if (!IsSystemXHR() && withCredentials) {
+    // This is quite sad. We have to create the channel in .open(), since the
+    // chrome-only xhr.channel API depends on that. However .withCredentials
+    // can be modified after, so we don't know what to set the
+    // SEC_REQUIRE_CORS_WITH_CREDENTIALS flag to when the channel is
+    // created. So set it here using a hacky internal API.
+
+    // Not doing this for system XHR uses since those don't use CORS.
+    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+    static_cast<LoadInfo*>(loadInfo.get())->SetWithCredentialsSecFlag();
+  }
 
   // Blocking gets are common enough out of XHR that we should mark
   // the channel slow by default for pipeline purposes
   AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE);
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
   if (cos) {
     // we never let XHR be blocked by head CSS/JS loads to avoid
@@ -2841,44 +2805,23 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
 
   nsCOMPtr<nsIHttpChannelInternal>
     internalHttpChannel(do_QueryInterface(mChannel));
   if (internalHttpChannel) {
     // Disable Necko-internal response timeouts.
     internalHttpChannel->SetResponseTimeoutEnabled(false);
   }
 
-  nsCOMPtr<nsIStreamListener> listener = this;
-  if (!IsSystemXHR()) {
-    // Always create a nsCORSListenerProxy here even if it's
-    // a same-origin request right now, since it could be redirected.
-    RefPtr<nsCORSListenerProxy> corsListener =
-      new nsCORSListenerProxy(listener, mPrincipal, withCredentials);
-    rv = corsListener->Init(mChannel, DataURIHandling::Allow);
-    NS_ENSURE_SUCCESS(rv, rv);
-    listener = corsListener;
-  }
-  else {
-    // Because of bug 682305, we can't let listener be the XHR object itself
-    // because JS wouldn't be able to use it. So if we haven't otherwise
-    // created a listener around 'this', do so now.
-
-    listener = new nsStreamListenerWrapper(listener);
-  }
-
   if (mIsAnon) {
     AddLoadFlags(mChannel, nsIRequest::LOAD_ANONYMOUS);
   }
   else {
     AddLoadFlags(mChannel, nsIChannel::LOAD_EXPLICIT_CREDENTIALS);
   }
 
-  NS_ASSERTION(listener != this,
-               "Using an object as a listener that can't be exposed to JS");
-
   // When we are sync loading, we need to bypass the local cache when it would
   // otherwise block us waiting for exclusive access to the cache.  If we don't
   // do this, then we could dead lock in some cases (see bug 309424).
   //
   // Also don't block on the cache entry on async if it is busy - favoring parallelism
   // over cache hit rate for xhr. This does not disable the cache everywhere -
   // only in cases where more than one channel for the same URI is accessed
   // simultanously.
@@ -2901,20 +2844,29 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
       contentType.Equals(UNKNOWN_CONTENT_TYPE)) {
     mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
   }
 
   // We're about to send the request.  Start our timeout.
   mRequestSentTime = PR_Now();
   StartTimeoutTimer();
 
+  // Check if we need to do a preflight request.
+  if (!mCORSUnsafeHeaders.IsEmpty() ||
+      (mUpload && mUpload->HasListeners()) ||
+      (!method.LowerCaseEqualsLiteral("get") &&
+       !method.LowerCaseEqualsLiteral("post") &&
+       !method.LowerCaseEqualsLiteral("head"))) {
+    mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE;
+  }
+
   // Set up the preflight if needed
-  if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) {
-    // Check to see if this initial OPTIONS request has already been cached
-    // in our special Access Control Cache.
+  if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
+      (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE)) {
+    NS_ENSURE_TRUE(internalHttpChannel, NS_ERROR_DOM_BAD_URI);
 
     rv = internalHttpChannel->SetCorsPreflightParameters(mCORSUnsafeHeaders,
                                                          withCredentials, mPrincipal);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mIsMappedArrayBuffer = false;
   if (mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER &&
@@ -2927,26 +2879,40 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
       uri->GetScheme(scheme);
       if (scheme.LowerCaseEqualsLiteral("app") ||
           scheme.LowerCaseEqualsLiteral("jar")) {
         mIsMappedArrayBuffer = true;
       }
     }
   }
 
+  // Hook us up to listen to redirects and the like
+  // Only do this very late since this creates a cycle between
+  // the channel and us. This cycle has to be manually broken if anything
+  // below fails.
+  mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
+  mChannel->SetNotificationCallbacks(this);
+
   // Start reading from the channel
-  rv = mChannel->AsyncOpen(listener, nullptr);
+  // Because of bug 682305, we can't let listener be the XHR object itself
+  // because JS wouldn't be able to use it. So create a listener around 'this'.
+  // Make sure to hold a strong reference so that we don't leak the wrapper.
+  nsCOMPtr<nsIStreamListener> listener = new nsStreamListenerWrapper(this);
+  rv = mChannel->AsyncOpen2(listener);
+  listener = nullptr;
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    // Drop our ref to the channel to avoid cycles
+    // Drop our ref to the channel to avoid cycles. Also drop channel's
+    // ref to us to be extra safe.
+    mChannel->SetNotificationCallbacks(mNotificationCallbacks);
     mChannel = nullptr;
+
     return rv;
   }
 
-  // Either AsyncOpen was called, or CORS will open the channel later.
   mWaitingForOnStopRequest = true;
 
   // If we're synchronous, spin an event loop here and wait
   if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
     mState |= XML_HTTP_REQUEST_SYNCLOOPING;
 
     nsCOMPtr<nsIDocument> suspendedDoc;
     nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
@@ -3379,27 +3345,22 @@ nsXMLHttpRequest::AsyncOnChannelRedirect
                                          uint32_t    aFlags,
                                          nsIAsyncVerifyRedirectCallback *callback)
 {
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
   nsresult rv;
 
   if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
-    rv = CheckChannelForCrossSiteRequest(aNewChannel);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("nsXMLHttpRequest::OnChannelRedirect: "
-                 "CheckChannelForCrossSiteRequest returned failure");
-      return rv;
-    }
+    CheckChannelForCrossSiteRequest(aNewChannel);
 
     // Disable redirects for preflighted cross-site requests entirely for now
-    // Note, do this after the call to CheckChannelForCrossSiteRequest
-    // to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date
-    if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) {
+    if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
+        (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE)) {
+       aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
        return NS_ERROR_DOM_BAD_URI;
     }
   }
 
   // Prepare to receive callback
   mRedirectCallback = callback;
   mNewRedirectChannel = aNewChannel;
 
@@ -3550,17 +3511,17 @@ nsXMLHttpRequest::OnStatus(nsIRequest *a
 
   return NS_OK;
 }
 
 bool
 nsXMLHttpRequest::AllowUploadProgress()
 {
   return !(mState & XML_HTTP_REQUEST_USE_XSITE_AC) ||
-    (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT);
+    (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT_IF_XSITE);
 }
 
 /////////////////////////////////////////////////////
 // nsIInterfaceRequestor methods:
 //
 NS_IMETHODIMP
 nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult)
 {
--- a/dom/base/nsXMLHttpRequest.h
+++ b/dom/base/nsXMLHttpRequest.h
@@ -621,17 +621,17 @@ protected:
   void ChangeStateToDone();
 
   /**
    * Check if aChannel is ok for a cross-site request by making sure no
    * inappropriate headers are set, and no username/password is set.
    *
    * Also updates the XML_HTTP_REQUEST_USE_XSITE_AC bit.
    */
-  nsresult CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
+  void CheckChannelForCrossSiteRequest(nsIChannel* aChannel);
 
   void StartProgressEventTimer();
 
   friend class AsyncVerifyRedirectCallbackForwarder;
   void OnRedirectVerifyCallback(nsresult result);
 
   nsresult Open(const nsACString& method, const nsACString& url, bool async,
                 const mozilla::dom::Optional<nsAString>& user,
--- a/dom/base/test/file_XHRResponseURL.js
+++ b/dom/base/test/file_XHRResponseURL.js
@@ -45,38 +45,16 @@ function info(aMessage) {
   var obj = {
     type: "info",
     message: aMessage
   };
   ++message.ping;
   message(obj);
 }
 
-function todo(aBool, aMessage) {
-  var obj = {
-    type: "todo",
-    bool: aBool,
-    message: aMessage
-  };
-  ++message.ping;
-  message(obj);
-}
-
-function todo_is(aActual, aExpected, aMessage) {
-  var obj = {
-    type: "todo_is",
-    actual: aActual,
-    expected: aExpected,
-    message: aMessage
-  };
-  ++message.ping;
-  message(obj);
-}
-
-
 function request(aURL) {
   return new Promise(function (aResolve, aReject) {
     var xhr = new XMLHttpRequest();
     xhr.open("GET", aURL);
     xhr.addEventListener("load", function () {
       xhr.succeeded = true;
       aResolve(xhr);
     });
@@ -120,25 +98,21 @@ function testSuccessResponse() {
       message: "request to same-origin redirects several times and finally go to same-origin URL",
       requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"
     },
     {
       message: "request to same-origin redirect to cross-origin URL",
       requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
       responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to same-origin redirects several times and finally go to cross-origin URL",
       requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
 
     // tests that start with cross-origin request
     {
       message: "request to cross-origin without redirect",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
       responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text"
     },
@@ -146,53 +120,41 @@ function testSuccessResponse() {
       message: "request to cross-origin redirect back to same-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text",
       responseURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"
     },
     {
       message: "request to cross-origin redirect to the same cross-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
       responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to cross-origin redirect to another cross-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
       responseURL: "http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to cross-origin redirects several times and finally go to same-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to cross-origin redirects several times and finally go to the same cross-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.com/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to cross-origin redirects several times and finally go to another cross-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://example.org/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://example.org/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request to cross-origin redirects to another cross-origin and finally go to the other cross-origin URL",
       requestURL: "http://example.com/tests/dom/base/test/file_XHRResponseURL.sjs?url=" + encodeURIComponent("http://example.org/tests/dom/base/test/file_XHRResponseURL.sjs?url=http://test1.example.com/tests/dom/base/test/file_XHRResponseURL.text"),
       responseURL: "http://test1.example.com/tests/dom/base/test/file_XHRResponseURL.text",
-      skip: isInWorker(),
-      reason: "cross-origin redirect request not works on Workers, see bug 882458"
     },
     {
       message: "request URL has fragment",
       requestURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text#fragment",
       responseURL: "http://mochi.test:8888/tests/dom/base/test/file_XHRResponseURL.text"
     },
 
     // tests for non-http(s) URL
@@ -204,23 +166,18 @@ function testSuccessResponse() {
     {
       message: "request to blob: URL",
       requestURL: blobURL,
       responseURL: blobURL
     }
   ];
 
   var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
-    if (aParam.skip) {
-      todo(aXHR.succeeded, aParam.reason);
-      todo_is(aXHR.responseURL, aParam.responseURL, aParam.reason);
-    } else {
-      ok(aXHR.succeeded, "assert request succeeded");
-      is(aXHR.responseURL, aParam.responseURL, aParam.message);
-    }
+    ok(aXHR.succeeded, "assert request succeeded");
+    is(aXHR.responseURL, aParam.responseURL, aParam.message);
   });
 
   sequence.then(function () {
     URL.revokeObjectURL(blobURL);
   });
 
   return sequence;
 }
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -505,17 +505,16 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug428847.html]
 [test_bug429157.html]
 [test_bug431082.html]
 [test_bug431701.html]
 [test_bug431833.html]
 [test_bug433533.html]
 [test_bug433662.html]
 [test_bug435425.html]
-[test_bug438519.html]
 [test_bug444030.xhtml]
 [test_bug444322.html]
 [test_bug444722.html]
 [test_bug448993.html]
 [test_bug450160.html]
 [test_bug451376.html]
 [test_bug453521.html]
 [test_bug453736.html]
deleted file mode 100644
--- a/dom/base/test/test_bug438519.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=438519
--->
-<head>
-  <title>Test for Bug 438519</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="doTest()">
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=438519">Mozilla Bug 438519</a>
-<p id="display"></p>
-<div id="content" style="display:none">
-
-<iframe id="empty" src="data:text/xml,<!DOCTYPE HTML []><html></html>"></iframe>
-<iframe id="missing" src="data:text/xml,<!DOCTYPE HTML><html></html>"></iframe>
-<iframe id="entity" src="data:text/xml,<!DOCTYPE HTML [ <!ENTITY foo 'foo'> ]><html></html>"></iframe>
-
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Bug 218236 **/
-
-SimpleTest.waitForExplicitFinish();
-
-function doTest() {
-  function checkInternalSubset(id, expected) {
-    var e = document.getElementById(id);
-    is(e.contentDocument.doctype.internalSubset, expected, "checking '" + id + "'");
-  }
-
-  checkInternalSubset("empty", "");
-  checkInternalSubset("missing", null);
-  checkInternalSubset("entity", " <!ENTITY foo 'foo'> ");
-  SimpleTest.finish();
-}
-
-</script>
-</pre>
-</body>
-</html>
-
--- a/dom/base/test/test_createHTMLDocument.html
+++ b/dom/base/test/test_createHTMLDocument.html
@@ -19,17 +19,16 @@ function checkDoc(title, expectedtitle, 
   var doc = document.implementation.createHTMLDocument(title);
   is(doc.readyState, "complete");
   is(doc.compatMode, "CSS1Compat");
   // Opera doesn't have a doctype: DSK-311092
   ok(doc.doctype, "Need a doctype");
   is(doc.doctype.name, "html");
   is(doc.doctype.publicId, "");
   is(doc.doctype.systemId, "");
-  is(doc.doctype.internalSubset, null, "internalSubset should be null!");
   isElement(doc.documentElement, "html");
   isElement(doc.documentElement.firstChild, "head");
   if (title !== undefined) {
     is(doc.documentElement.firstChild.childNodes.length, 1);
     isElement(doc.documentElement.firstChild.firstChild, "title");
     // Doesn't always work out in WebKit.
     ok(doc.documentElement.firstChild.firstChild.firstChild, "Need a text node.");
     is(doc.documentElement.firstChild.firstChild.firstChild.data, expectedtitle);
--- a/dom/base/test/test_urlSearchParams.html
+++ b/dom/base/test/test_urlSearchParams.html
@@ -232,28 +232,81 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(u.getAll('name1').length, 3, "URLSearchParams.getAll('name1').length should be 3");
     u.set('name1','firstPair');
     is(u.get('name1'), 'firstPair', "URL.searchParams.get('name1') should return firstPair");
     is(u.getAll('name1').length, 1, "URLSearchParams.getAll('name1').length should be 1");
 
     runTest();
   }
 
+  function testIterable() {
+    var u = new URLSearchParams();
+    u.set('1','2');
+    u.set('2','4');
+    u.set('3','6');
+    u.set('4','8');
+    u.set('5','10');
+
+    var key_iter = u.keys();
+    var value_iter = u.values();
+    var entries_iter = u.entries();
+    for (var i = 0; i < 5; ++i) {
+      var v = i + 1;
+      var key = key_iter.next();
+      var value = value_iter.next();
+      var entry = entries_iter.next();
+      is(key.value, v.toString(), "Correct Key iterator: " + v.toString());
+      ok(!key.done, "Key.done is false");
+      is(value.value, (v * 2).toString(), "Correct Value iterator: " + (v * 2).toString());
+      ok(!value.done, "Value.done is false");
+      is(entry.value[0], v.toString(), "Correct Entry 0 iterator: " + v.toString());
+      is(entry.value[1], (v * 2).toString(), "Correct Entry 1 iterator: " + (v * 2).toString());
+      ok(!entry.done, "Entry.done is false");
+    }
+
+    var last = key_iter.next();
+    ok(last.done, "Nothing more to read.");
+    is(last.value, undefined, "Undefined is the last key");
+
+    last = value_iter.next();
+    ok(last.done, "Nothing more to read.");
+    is(last.value, undefined, "Undefined is the last value");
+
+    last = entries_iter.next();
+    ok(last.done, "Nothing more to read.");
+
+    key_iter = u.keys();
+    key_iter.next();
+    key_iter.next();
+    u.delete('1');
+    u.delete('2');
+    u.delete('3');
+    u.delete('4');
+    u.delete('5');
+
+    last = key_iter.next();
+    ok(last.done, "Nothing more to read.");
+    is(last.value, undefined, "Undefined is the last key");
+
+    runTest();
+  }
+
   var tests = [
     testSimpleURLSearchParams,
     testCopyURLSearchParams,
     testParserURLSearchParams,
     testURL,
     function() { testElement(document.getElementById('anchor')) },
     function() { testElement(document.getElementById('area')) },
     testEncoding,
     testOrdering,
     testDelete,
     testGetNULL,
-    testSet
+    testSet,
+    testIterable
   ];
 
   function runTest() {
     if (!tests.length) {
       SimpleTest.finish();
       return;
     }
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -695,20 +695,16 @@ DOMInterfaces = {
     'wrapperCache': False,
 },
 
 'InputStream': {
     'nativeType': 'nsIInputStream',
     'notflattened': True
 },
 
-'IterableIterator': {
-    'skipGen': True
-},
-
 'KeyEvent': {
     'concrete': False
 },
 
 'LegacyMozTCPSocket': {
     'headerFile': 'TCPSocket.h',
     'wrapperCache': False,
 },
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -735,17 +735,17 @@ class IDLInterface(IDLObjectWithScope, I
                 # Check that we only have one interface declaration (currently
                 # there can only be one maplike/setlike declaration per
                 # interface)
                 if self.maplikeOrSetlikeOrIterable:
                     raise WebIDLError("%s declaration used on "
                                       "interface that already has %s "
                                       "declaration" %
                                       (member.maplikeOrSetlikeOrIterableType,
-                                       self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType),
+                                       self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType),
                                       [self.maplikeOrSetlikeOrIterable.location,
                                        member.location])
                 self.maplikeOrSetlikeOrIterable = member
                 # If we've got a maplike or setlike declaration, we'll be building all of
                 # our required methods in Codegen. Generate members now.
                 self.maplikeOrSetlikeOrIterable.expand(self.members, self.isJSImplemented())
 
         # Now that we've merged in our partial interfaces, set the
@@ -6562,54 +6562,52 @@ class Parser(Tokenizer):
         self._filename = None
 
     def finish(self):
         # If we have interfaces that are iterable, create their
         # iterator interfaces and add them to the productions array.
         interfaceStatements = [p for p in self._productions if
                                isinstance(p, IDLInterface)]
 
+        iterableIteratorIface = None
         for iface in interfaceStatements:
             iterable = None
             # We haven't run finish() on the interface yet, so we don't know
             # whether our interface is maplike/setlike/iterable or not. This
             # means we have to loop through the members to see if we have an
             # iterable member.
             for m in iface.members:
                 if isinstance(m, IDLIterable):
                     iterable = m
                     break
             if iterable:
+                def simpleExtendedAttr(str):
+                    return IDLExtendedAttribute(iface.location, (str, ))
+                nextMethod = IDLMethod(
+                    iface.location,
+                    IDLUnresolvedIdentifier(iface.location, "next"),
+                    BuiltinTypes[IDLBuiltinType.Types.object], [])
+                nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
                 itr_ident = IDLUnresolvedIdentifier(iface.location,
                                                     iface.identifier.name + "Iterator")
                 itr_iface = IDLInterface(iface.location, self.globalScope(),
-                                         itr_ident, None, [],
+                                         itr_ident, None, [nextMethod],
                                          isKnownNonPartial=True)
-                itr_iface.addExtendedAttributes([IDLExtendedAttribute(iface.location,
-                                                                      ("NoInterfaceObject", ))])
+                itr_iface.addExtendedAttributes([simpleExtendedAttr("NoInterfaceObject")])
                 # Make sure the exposure set for the iterator interface is the
                 # same as the exposure set for the iterable interface, because
                 # we're going to generate methods on the iterable that return
                 # instances of the iterator.
                 itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames)
-                # Always append generated iterable interfaces and their
-                # matching implements statements after the interface they're a
-                # member of, otherwise nativeType generation won't work
-                # correctly.
+                # Always append generated iterable interfaces after the
+                # interface they're a member of, otherwise nativeType generation
+                # won't work correctly.
                 itr_iface.iterableInterface = iface
                 self._productions.append(itr_iface)
                 iterable.iteratorType = IDLWrapperType(iface.location, itr_iface)
-                itrPlaceholder = IDLIdentifierPlaceholder(iface.location,
-                                                          IDLUnresolvedIdentifier(iface.location,
-                                                                                  "IterableIterator"))
-                implements = IDLImplementsStatement(iface.location,
-                                                    IDLIdentifierPlaceholder(iface.location,
-                                                                             itr_ident),
-                                                    itrPlaceholder)
-                self._productions.append(implements)
 
         # Then, finish all the IDLImplementsStatements.  In particular, we
         # have to make sure we do those before we do the IDLInterfaces.
         # XXX khuey hates this bit and wants to nuke it from orbit.
         implementsStatements = [p for p in self._productions if
                                 isinstance(p, IDLImplementsStatement)]
         otherStatements = [p for p in self._productions if
                            not isinstance(p, IDLImplementsStatement)]
--- a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
+++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
@@ -5,100 +5,112 @@ def WebIDLTest(parser, harness):
     def shouldPass(prefix, iface, expectedMembers, numProductions=1):
         p = parser.reset()
         p.parse(iface)
         results = p.finish()
         harness.check(len(results), numProductions,
                       "%s - Should have production count %d" % (prefix, numProductions))
         harness.ok(isinstance(results[0], WebIDL.IDLInterface),
                    "%s - Should be an IDLInterface" % (prefix))
-        harness.check(len(results[0].members), len(expectedMembers),
-                      "%s - Should be %d members" % (prefix,
-                                                     len(expectedMembers)))
+        # Make a copy, since we plan to modify it
+        expectedMembers = list(expectedMembers)
         for m in results[0].members:
             name = m.identifier.name
             if (name, type(m)) in expectedMembers:
                 harness.ok(True, "%s - %s - Should be a %s" % (prefix, name,
                                                                type(m)))
-            elif isinstance(m, WebIDL.IDLMaplikeOrSetlike):
-                harness.ok(True, "%s - %s - Should be a MaplikeOrSetlike" %
-                           (prefix, name))
+                expectedMembers.remove((name, type(m)))
             else:
                 harness.ok(False, "%s - %s - Unknown symbol of type %s" %
                            (prefix, name, type(m)))
+        # A bit of a hoop because we can't generate the error string if we pass
+        if len(expectedMembers) == 0:
+            harness.ok(True, "Found all the members")
+        else:
+            harness.ok(False,
+                       "Expected member not found: %s of type %s" %
+                       (expectedMembers[0][0], expectedMembers[0][1]))
         return results
 
     def shouldFail(prefix, iface):
         try:
             p = parser.reset()
             p.parse(iface)
             p.finish()
             harness.ok(False,
                        prefix + " - Interface passed when should've failed")
         except WebIDL.WebIDLError, e:
             harness.ok(True,
                        prefix + " - Interface failed as expected")
         except Exception, e:
             harness.ok(False,
-                       prefix + " - Interface failed but not as a WebIDLError exception")
+                       prefix + " - Interface failed but not as a WebIDLError exception: %s" % e)
 
     iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys",
                                                        "values"]]
-    setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "foreach"]] +
+    setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "forEach"]] +
                     [("__setlike", WebIDL.IDLMaplikeOrSetlike)] +
                     iterableMembers)
     setROMembers.extend([("size", WebIDL.IDLAttribute)])
     setRWMembers = ([(x, WebIDL.IDLMethod) for x in ["add",
                                                      "clear",
                                                      "delete"]] +
                     setROMembers)
     setROChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add",
                                                            "__clear",
                                                            "__delete"]] +
                           setROMembers)
     setRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add",
                                                            "__clear",
                                                            "__delete"]] +
                           setRWMembers)
-    mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "foreach"]] +
+    mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "forEach"]] +
                     [("__maplike", WebIDL.IDLMaplikeOrSetlike)] +
                     iterableMembers)
     mapROMembers.extend([("size", WebIDL.IDLAttribute)])
     mapRWMembers = ([(x, WebIDL.IDLMethod) for x in ["set",
                                                      "clear",
                                                      "delete"]] + mapROMembers)
     mapRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__set",
                                                            "__clear",
                                                            "__delete"]] +
                           mapRWMembers)
 
+    # OK, now that we've used iterableMembers to set up the above, append
+    # __iterable to it for the iterable<> case.
+    iterableMembers.append(("__iterable", WebIDL.IDLIterable))
+
     disallowedIterableNames = ["keys", "entries", "values"]
     disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
     mapDisallowedMemberNames = ["get"] + disallowedMemberNames
     disallowedNonMethodNames = ["clear", "delete"]
     mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames
     setDisallowedNonMethodNames = ["add"] + disallowedNonMethodNames
 
     #
     # Simple Usage Tests
     #
 
     shouldPass("Iterable (key only)",
                """
                interface Foo1 {
                iterable<long>;
                };
-               """, iterableMembers)
+               """, iterableMembers,
+               # numProductions == 2 because of the generated iterator iface,
+               numProductions=2)
 
     shouldPass("Iterable (key and value)",
                """
                interface Foo1 {
                iterable<long, long>;
                };
-               """, iterableMembers)
+               """, iterableMembers,
+               # numProductions == 2 because of the generated iterator iface,
+               numProductions=2)
 
     shouldPass("Maplike (readwrite)",
                """
                interface Foo1 {
                maplike<long, long>;
                };
                """, mapRWMembers)
 
@@ -569,10 +581,10 @@ def WebIDLTest(parser, harness):
                    long delete(long a, long b, double c, double d);
                    };
                    """, mapRWMembers)
 
     for m in r[0].members:
         if m.identifier.name in ["clear", "set", "delete"]:
             harness.ok(m.isMethod(), "%s should be a method" % m.identifier.name)
             harness.check(m.maxArgCount, 4, "%s should have 4 arguments" % m.identifier.name)
-            harness.ok(not m.isMaplikeOrSetlikeMethod(),
+            harness.ok(not m.isMaplikeOrSetlikeOrIterableMethod(),
                        "%s should not be a maplike/setlike function" % m.identifier.name)
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -187,18 +187,17 @@ static_assert(int(HeadersGuardEnum::None
               int(HeadersGuardEnum::Request_no_cors) == 2 &&
               int(HeadersGuardEnum::Response) == 3 &&
               int(HeadersGuardEnum::Immutable) == 4 &&
               int(HeadersGuardEnum::EndGuard_) == 5,
               "HeadersGuardEnum values are as expected");
 static_assert(int(RequestMode::Same_origin) == 0 &&
               int(RequestMode::No_cors) == 1 &&
               int(RequestMode::Cors) == 2 &&
-              int(RequestMode::Cors_with_forced_preflight) == 3 &&
-              int(RequestMode::EndGuard_) == 4,
+              int(RequestMode::EndGuard_) == 3,
               "RequestMode values are as expected");
 static_assert(int(RequestCredentials::Omit) == 0 &&
               int(RequestCredentials::Same_origin) == 1 &&
               int(RequestCredentials::Include) == 2 &&
               int(RequestCredentials::EndGuard_) == 3,
               "RequestCredentials values are as expected");
 static_assert(int(RequestCache::Default) == 0 &&
               int(RequestCache::No_store) == 1 &&
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -66,16 +66,21 @@ public:
 
     // -------------------------------------------------------------------------
     // Framebuffer objects - WebGL2ContextFramebuffers.cpp
 
     void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                          GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                          GLbitfield mask, GLenum filter);
     void FramebufferTextureLayer(GLenum target, GLenum attachment, WebGLTexture* texture, GLint level, GLint layer);
+
+    virtual JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
+                                                        GLenum attachment, GLenum pname,
+                                                        ErrorResult& rv) override;
+
     void InvalidateFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
                                ErrorResult& rv);
     void InvalidateSubFramebuffer (GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
                                    GLsizei width, GLsizei height, ErrorResult& rv);
     void ReadBuffer(GLenum mode);
 
 
     // -------------------------------------------------------------------------
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -3,20 +3,27 @@
  * 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 "WebGL2Context.h"
 
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "WebGLContextUtils.h"
+#include "WebGLFormats.h"
 #include "WebGLFramebuffer.h"
 
 namespace mozilla {
 
+using gl::GLContext;
+using gl::GLFormats;
+using webgl::EffectiveFormat;
+using webgl::FormatInfo;
+using webgl::ComponentType;
+
 // Returns one of FLOAT, INT, UNSIGNED_INT.
 // Fixed-points (normalized ints) are considered FLOAT.
 static GLenum
 ValueTypeForFormat(GLenum internalFormat)
 {
     switch (internalFormat) {
     // Fixed-point
     case LOCAL_GL_R8:
@@ -413,16 +420,113 @@ WebGL2Context::FramebufferTextureLayer(G
     if (!fb) {
         return ErrorInvalidOperation("framebufferTextureLayer: cannot modify"
                                      " framebuffer 0.");
     }
 
     fb->FramebufferTextureLayer(attachment, texture, level, layer);
 }
 
+JS::Value
+WebGL2Context::GetFramebufferAttachmentParameter(JSContext* cx,
+                                                 GLenum target,
+                                                 GLenum attachment,
+                                                 GLenum pname,
+                                                 ErrorResult& rv)
+{
+    if (IsContextLost())
+        return JS::NullValue();
+
+    // OpenGL ES 3.0.4 (August 27, 2014) 6.1. QUERYING GL STATE 240
+    // "getFramebufferAttachmentParamter returns information about attachments of a bound
+    // framebuffer object. target must be DRAW_FRAMEBUFFER, READ_FRAMEBUFFER, or
+    // FRAMEBUFFER."
+
+    if (!ValidateFramebufferTarget(target, "getFramebufferAttachmentParameter"))
+        return JS::NullValue();
+
+    // FRAMEBUFFER is equivalent to DRAW_FRAMEBUFFER.
+    if (target == LOCAL_GL_FRAMEBUFFER)
+        target = LOCAL_GL_DRAW_FRAMEBUFFER;
+
+    WebGLFramebuffer* boundFB = nullptr;
+    switch (target) {
+    case LOCAL_GL_DRAW_FRAMEBUFFER: boundFB = mBoundDrawFramebuffer; break;
+    case LOCAL_GL_READ_FRAMEBUFFER: boundFB = mBoundReadFramebuffer; break;
+    }
+
+    if (boundFB) {
+        return boundFB->GetAttachmentParameter(cx, attachment, pname, rv);
+    }
+
+    // Handle default FB
+    const gl::GLFormats& formats = gl->GetGLFormats();
+    GLenum internalFormat = LOCAL_GL_NONE;
+
+    /* If the default framebuffer is bound to target, then attachment must be BACK,
+       identifying the color buffer; DEPTH, identifying the depth buffer; or STENCIL,
+       identifying the stencil buffer. */
+    switch (attachment) {
+    case LOCAL_GL_BACK:
+        internalFormat = formats.color_texInternalFormat;
+        break;
+
+    case LOCAL_GL_DEPTH:
+        internalFormat = formats.depth;
+        break;
+
+    case LOCAL_GL_STENCIL:
+        internalFormat = formats.stencil;
+        break;
+
+    default:
+        ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only query "
+                         "attachment BACK, DEPTH, or STENCIL from default "
+                         "framebuffer");
+        return JS::NullValue();
+    }
+
+    const FormatInfo* info = webgl::GetInfoBySizedFormat(internalFormat);
+    MOZ_RELEASE_ASSERT(info);
+    EffectiveFormat effectiveFormat = info->effectiveFormat;
+
+    switch (pname) {
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+        return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+        return JS::Int32Value(webgl::GetComponentSize(effectiveFormat, pname));
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
+        if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT &&
+            pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)
+        {
+            ErrorInvalidOperation("getFramebufferAttachmentParameter: Querying "
+                                  "FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE against "
+                                  "DEPTH_STENCIL_ATTACHMENT is an error.");
+            return JS::NullValue();
+        }
+
+        return JS::Int32Value(webgl::GetComponentType(effectiveFormat));
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+        return JS::Int32Value(webgl::GetColorEncoding(effectiveFormat));
+    }
+
+    /* Any combinations of framebuffer type and pname not described above will generate an
+       INVALID_ENUM error. */
+    ErrorInvalidEnum("getFramebufferAttachmentParameter: Invalid combination of ");
+    return JS::NullValue();
+}
+
 // Map attachments intended for the default buffer, to attachments for a non-
 // default buffer.
 static bool
 TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
 {
     for (size_t i = 0; i < in.Length(); i++) {
         switch (in[i]) {
             case LOCAL_GL_COLOR:
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -453,19 +453,19 @@ public:
 
     void GetBufferParameter(JSContext*, GLenum target, GLenum pname,
                             JS::MutableHandle<JS::Value> retval)
     {
         retval.set(GetBufferParameter(target, pname));
     }
 
     GLenum GetError();
-    JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
-                                                GLenum attachment, GLenum pname,
-                                                ErrorResult& rv);
+    virtual JS::Value GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
+                                                        GLenum attachment, GLenum pname,
+                                                        ErrorResult& rv);
 
     void GetFramebufferAttachmentParameter(JSContext* cx, GLenum target,
                                            GLenum attachment, GLenum pname,
                                            JS::MutableHandle<JS::Value> retval,
                                            ErrorResult& rv)
     {
         retval.set(GetFramebufferAttachmentParameter(cx, target, attachment,
                                                      pname, rv));
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -804,10 +804,324 @@ FormatUsageAuthority::AddUnpackOption(GL
 
     MOZ_RELEASE_ASSERT(usage->asTexture);
 
     auto res = usage->validUnpacks.insert(unpack);
     bool didInsert = res.second;
     MOZ_ALWAYS_TRUE(didInsert);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
+struct ComponentSizes
+{
+    GLubyte redSize;
+    GLubyte greenSize;
+    GLubyte blueSize;
+    GLubyte alphaSize;
+    GLubyte depthSize;
+    GLubyte stencilSize;
+};
+
+static ComponentSizes kComponentSizes[] = {
+    // GLES 3.0.4, p128-129, "Required Texture Formats"
+    // "Texture and renderbuffer color formats"
+    { 32, 32, 32, 32,  0,  0 }, // RGBA32I,
+    { 32, 32, 32, 32,  0,  0 }, // RGBA32UI,
+    { 16, 16, 16, 16,  0,  0 }, // RGBA16I,
+    { 16, 16, 16, 16,  0,  0 }, // RGBA16UI,
+    {  8,  8,  8,  8,  0,  0 }, // RGBA8,
+    {  8,  8,  8,  8,  0,  0 }, // RGBA8I,
+    {  8,  8,  8,  8,  0,  0 }, // RGBA8UI,
+    {  8,  8,  8,  8,  0,  0 }, // SRGB8_ALPHA8,
+    { 10, 10, 10,  2,  0,  0 }, // RGB10_A2,
+    { 10, 10, 10,  2,  0,  0 }, // RGB10_A2UI,
+    {  4,  4,  4,  4,  0,  0 }, // RGBA4,
+    {  5,  5,  5,  1,  0,  0 }, // RGB5_A1,
+
+    {  8,  8,  8,  0,  0,  0 }, // RGB8,
+    {  8,  8,  8,  0,  0,  0 }, // RGB565,
+
+    { 32, 32,  0,  0,  0,  0 }, // RG32I,
+    { 32, 32,  0,  0,  0,  0 }, // RG32UI,
+    { 16, 16,  0,  0,  0,  0 }, // RG16I,
+    { 16, 16,  0,  0,  0,  0 }, // RG16UI,
+    {  8,  8,  0,  0,  0,  0 }, // RG8,
+    {  8,  8,  0,  0,  0,  0 }, // RG8I,
+    {  8,  8,  0,  0,  0,  0 }, // RG8UI,
+
+    { 32,  0,  0,  0,  0,  0 }, // R32I,
+    { 32,  0,  0,  0,  0,  0 }, // R32UI,
+    { 16,  0,  0,  0,  0,  0 }, // R16I,
+    { 16,  0,  0,  0,  0,  0 }, // R16UI,
+    {  8,  0,  0,  0,  0,  0 }, // R8,
+    {  8,  0,  0,  0,  0,  0 }, // R8I,
+    {  8,  0,  0,  0,  0,  0 }, // R8UI,
+
+    // "Texture-only color formats"
+    { 32, 32, 32, 32,  0,  0 }, // RGBA32F,
+    { 16, 16, 16, 16,  0,  0 }, // RGBA16F,
+    {  8,  8,  8,  8,  0,  0 }, // RGBA8_SNORM,
+
+    { 32, 32, 32,  0,  0,  0 }, // RGB32F,
+    { 32, 32, 32,  0,  0,  0 }, // RGB32I,
+    { 32, 32, 32,  0,  0,  0 }, // RGB32UI,
+
+    { 16, 16, 16,  0,  0,  0 }, // RGB16F,
+    { 16, 16, 16,  0,  0,  0 }, // RGB16I,
+    { 16, 16, 16,  0,  0,  0 }, // RGB16UI,
+
+    {  8,  8,  8,  0,  0,  0 }, // RGB8_SNORM,
+    {  8,  8,  8,  0,  0,  0 }, // RGB8I,
+    {  8,  8,  8,  0,  0,  0 }, // RGB8UI,
+    {  8,  8,  8,  0,  0,  0 }, // SRGB8,
+
+    { 11, 11, 11,  0,  0,  0 }, // R11F_G11F_B10F,
+    {  9,  9,  9,  0,  0,  0 }, // RGB9_E5,
+
+    { 32, 32,  0,  0,  0,  0 }, // RG32F,
+    { 16, 16,  0,  0,  0,  0 }, // RG16F,
+    {  8,  8,  0,  0,  0,  0 }, // RG8_SNORM,
+
+    { 32,  0,  0,  0,  0,  0 }, // R32F,
+    { 16,  0,  0,  0,  0,  0 }, // R16F,
+    {  8,  0,  0,  0,  0,  0 }, // R8_SNORM,
+
+    // "Depth formats"
+    {  0,  0,  0,  0, 32,  0 }, // DEPTH_COMPONENT32F,
+    {  0,  0,  0,  0, 24,  0 }, // DEPTH_COMPONENT24,
+    {  0,  0,  0,  0, 16,  0 }, // DEPTH_COMPONENT16,
+
+    // "Combined depth+stencil formats"
+    {  0,  0,  0,  0, 32,  8 }, // DEPTH32F_STENCIL0,
+    {  0,  0,  0,  0, 24,  8 }, // DEPTH24_STENCIL8,
+
+    // GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
+    {  0,  0,  0,  0,  0,  8 }, // STENCIL_INDEX8,
+
+    // GLES 3.0.4, p128, table 3.12.
+    {  8,  8,  8,  8,  0,  0 }, // Luminance8Alpha8,
+    {  8,  8,  8,  0,  0,  0 }, // Luminance8,
+    {  0,  0,  0,  8,  0,  0 }, // Alpha8,
+
+    // GLES 3.0.4, p147, table 3.19
+    // GLES 3.0.4, p286+, $C.1 "ETC Compressed Texture Image Formats"
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_R11_EAC,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_SIGNED_R11_EAC,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RG11_EAC,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_SIGNED_RG11_EAC,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGB8_ETC2,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_SRGB8_ETC2,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA8_ETC2_EAC,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
+
+    // AMD_compressed_ATC_texture
+    {  8,  8,  8,  0,  0,  0 }, // ATC_RGB_AMD,
+    {  8,  8,  8,  8,  0,  0 }, // ATC_RGBA_EXPLICIT_ALPHA_AMD,
+    {  8,  8,  8,  8,  0,  0 }, // ATC_RGBA_INTERPOLATED_ALPHA_AMD,
+
+    // EXT_texture_compression_s3tc
+    {  8,  8,  8,  0,  0,  0 }, // COMPRESSED_RGB_S3TC_DXT1,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA_S3TC_DXT1,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA_S3TC_DXT3,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA_S3TC_DXT5,
+
+    // IMG_texture_compression_pvrtc
+    {  8,  8,  8,  0,  0,  0 }, // COMPRESSED_RGB_PVRTC_4BPPV1,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA_PVRTC_4BPPV1,
+    {  8,  8,  8,  0,  0,  0 }, // COMPRESSED_RGB_PVRTC_2BPPV1,
+    {  8,  8,  8,  8,  0,  0 }, // COMPRESSED_RGBA_PVRTC_2BPPV1,
+
+    // OES_compressed_ETC1_RGB8_texture
+    {  8,  8,  8,  0,  0,  0 }, // ETC1_RGB8,
+
+    // OES_texture_float
+    { 32, 32, 32, 32,  0,  0 }, // Luminance32FAlpha32F,
+    { 32, 32, 32,  0,  0,  0 }, // Luminance32F,
+    {  0,  0,  0, 32,  0,  0 }, // Alpha32F,
+
+    // OES_texture_half_float
+    { 16, 16, 16, 16, 0, 0 }, // Luminance16FAlpha16F,
+    { 16, 16, 16,  0, 0, 0 }, // Luminance16F,
+    {  0,  0,  0, 16, 0, 0 }, // Alpha16F,
+
+    {  0, } // MAX
+};
+
+GLint
+GetComponentSize(EffectiveFormat format, GLenum component)
+{
+    ComponentSizes compSize = kComponentSizes[(int) format];
+    switch (component) {
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
+    case LOCAL_GL_RENDERBUFFER_RED_SIZE:
+    case LOCAL_GL_RED_BITS:
+        return compSize.redSize;
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
+    case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
+    case LOCAL_GL_GREEN_BITS:
+        return compSize.greenSize;
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
+    case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
+    case LOCAL_GL_BLUE_BITS:
+        return compSize.blueSize;
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
+    case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
+    case LOCAL_GL_ALPHA_BITS:
+        return compSize.alphaSize;
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
+    case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
+    case LOCAL_GL_DEPTH_BITS:
+        return compSize.depthSize;
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+    case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
+    case LOCAL_GL_STENCIL_BITS:
+        return compSize.stencilSize;
+    }
+
+    return 0;
+}
+
+static GLenum kComponentTypes[] = {
+    // GLES 3.0.4, p128-129, "Required Texture Formats"
+    // "Texture and renderbuffer color formats"
+    LOCAL_GL_INT,                       // RGBA32I,
+    LOCAL_GL_UNSIGNED_INT,              // RGBA32UI,
+    LOCAL_GL_INT,                       // RGBA16I,
+    LOCAL_GL_UNSIGNED_INT,              // RGBA16UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGBA8,
+    LOCAL_GL_INT,                       // RGBA8I,
+    LOCAL_GL_UNSIGNED_INT,              // RGBA8UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // SRGB8_ALPHA8,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGB10_A2,
+    LOCAL_GL_UNSIGNED_INT,              // RGB10_A2UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGBA4,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGB5_A1,
+
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGB8,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RGB565,
+
+    LOCAL_GL_INT,                       // RG32I,
+    LOCAL_GL_UNSIGNED_INT,              // RG32UI,
+    LOCAL_GL_INT,                       // RG16I,
+    LOCAL_GL_UNSIGNED_INT,              // RG16UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // RG8,
+    LOCAL_GL_INT,                       // RG8I,
+    LOCAL_GL_UNSIGNED_INT,              // RG8UI,
+
+    LOCAL_GL_INT,                       // R32I,
+    LOCAL_GL_UNSIGNED_INT,              // R32UI,
+    LOCAL_GL_INT,                       // R16I,
+    LOCAL_GL_UNSIGNED_INT,              // R16UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // R8,
+    LOCAL_GL_INT,                       // R8I,
+    LOCAL_GL_UNSIGNED_INT,              // R8UI,
+
+    // "Texture-only color formats"
+    LOCAL_GL_FLOAT,                     // RGBA32F,
+    LOCAL_GL_FLOAT,                     // RGBA16F,
+    LOCAL_GL_SIGNED_NORMALIZED,         // RGBA8_SNORM,
+
+    LOCAL_GL_FLOAT,                     // RGB32F,
+    LOCAL_GL_INT,                       // RGB32I,
+    LOCAL_GL_UNSIGNED_INT,              // RGB32UI,
+
+    LOCAL_GL_FLOAT,                     // RGB16F,
+    LOCAL_GL_INT,                       // RGB16I,
+    LOCAL_GL_UNSIGNED_INT,              // RGB16UI,
+
+    LOCAL_GL_SIGNED_NORMALIZED,         // RGB8_SNORM,
+    LOCAL_GL_INT,                       // RGB8I,
+    LOCAL_GL_UNSIGNED_INT,              // RGB8UI,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // SRGB8,
+
+    LOCAL_GL_FLOAT,                     // R11F_G11F_B10F,
+    LOCAL_GL_FLOAT,                     // RGB9_E5,
+
+    LOCAL_GL_FLOAT,                     // RG32F,
+    LOCAL_GL_FLOAT,                     // RG16F,
+    LOCAL_GL_SIGNED_NORMALIZED,         // RG8_SNORM,
+
+    LOCAL_GL_FLOAT,                     // R32F,
+    LOCAL_GL_FLOAT,                     // R16F,
+    LOCAL_GL_SIGNED_NORMALIZED,         // R8_SNORM,
+
+    // "Depth formats"
+    LOCAL_GL_FLOAT,                     // DEPTH_COMPONENT32F,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // DEPTH_COMPONENT24,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // DEPTH_COMPONENT16,
+
+    // "Combined depth+stencil formats"
+    LOCAL_GL_FLOAT,                     // DEPTH32F_STENCIL8,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // DEPTH24_STENCIL8,
+
+    // GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // STENCIL_INDEX8,
+
+    // GLES 3.0.4, p128, table 3.12.
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // Luminance8Alpha8,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // Luminance8,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // Alpha8,
+
+    // GLES 3.0.4, p147, table 3.19
+    // GLES 3.0.4, p286+, $C.1 "ETC Compressed Texture Image Formats"
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_R11_EAC,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_SIGNED_R11_EAC,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RG11_EAC,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_SIGNED_RG11_EAC,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGB8_ETC2,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_SRGB8_ETC2,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA8_ETC2_EAC,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
+
+    // AMD_compressed_ATC_texture
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // ATC_RGB_AMD,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // ATC_RGBA_EXPLICIT_ALPHA_AMD,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // ATC_RGBA_INTERPOLATED_ALPHA_AMD,
+
+    // EXT_texture_compression_s3tc
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGB_S3TC_DXT1,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA_S3TC_DXT1,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA_S3TC_DXT3,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA_S3TC_DXT5,
+
+    // IMG_texture_compression_pvrtc
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGB_PVRTC_4BPPV1,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA_PVRTC_4BPPV1,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGB_PVRTC_2BPPV1,
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // COMPRESSED_RGBA_PVRTC_2BPPV1,
+
+    // OES_compressed_ETC1_RGB8_texture
+    LOCAL_GL_UNSIGNED_NORMALIZED,       // ETC1_RGB8,
+
+    // OES_texture_float
+    LOCAL_GL_FLOAT,                     // Luminance32FAlpha32F,
+    LOCAL_GL_FLOAT,                     // Luminance32F,
+    LOCAL_GL_FLOAT,                     // Alpha32F,
+
+    // OES_texture_half_float
+    LOCAL_GL_FLOAT,                     // Luminance16FAlpha16F,
+    LOCAL_GL_FLOAT,                     // Luminance16F,
+    LOCAL_GL_FLOAT,                     // Alpha16F,
+
+    LOCAL_GL_NONE // MAX
+};
+
+GLenum
+GetComponentType(EffectiveFormat format)
+{
+    return kComponentTypes[(int) format];
+}
+
+GLenum
+GetColorEncoding(EffectiveFormat format)
+{
+    const bool isSRGB = (GetFormatInfo(format)->colorComponentType ==
+                         ComponentType::NormUIntSRGB);
+    return (isSRGB) ? LOCAL_GL_SRGB : LOCAL_GL_LINEAR;
+}
+
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/WebGLFormats.h
+++ b/dom/canvas/WebGLFormats.h
@@ -247,19 +247,27 @@ private:
 public:
     void AddFormat(EffectiveFormat format, bool asRenderbuffer, bool isRenderable,
                    bool asTexture, bool isFilterable);
 
     void AddUnpackOption(GLenum unpackFormat, GLenum unpackType,
                          EffectiveFormat effectiveFormat);
 
     FormatUsageInfo* GetUsage(EffectiveFormat format);
-
     FormatUsageInfo* GetUsage(const FormatInfo* format)
     {
+        if (!format)
+            return nullptr;
+
         return GetUsage(format->effectiveFormat);
     }
 };
 
+////////////////////////////////////////////////////////////////////////////////
+
+GLint GetComponentSize(EffectiveFormat format, GLenum component);
+GLenum GetComponentType(EffectiveFormat format);
+GLenum GetColorEncoding(EffectiveFormat format);
+
 } // namespace webgl
 } // namespace mozilla
 
 #endif // WEBGL_FORMATS_H_
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -428,16 +428,76 @@ WebGLFBAttachPoint::FinalizeAttachment(g
     if (Renderbuffer()) {
         Renderbuffer()->FramebufferRenderbuffer(attachmentLoc);
         return;
     }
 
     MOZ_CRASH();
 }
 
+JS::Value
+WebGLFBAttachPoint::GetParameter(WebGLContext* context, GLenum pname)
+{
+    // TODO: WebGLTexture and WebGLRenderbuffer should store FormatInfo instead of doing
+    // this dance every time.
+    const GLenum internalFormat = EffectiveInternalFormat().get();
+    const webgl::FormatInfo* info = webgl::GetInfoBySizedFormat(internalFormat);
+    MOZ_ASSERT(info);
+
+    WebGLTexture* tex = Texture();
+
+    switch (pname) {
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+        return JS::Int32Value(webgl::GetComponentSize(info->effectiveFormat, pname));
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
+        return JS::Int32Value(webgl::GetComponentType(info->effectiveFormat));
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+        return JS::Int32Value(webgl::GetColorEncoding(info->effectiveFormat));
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
+        if (tex) {
+            return JS::Int32Value(MipLevel());
+        }
+        break;
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
+        if (tex) {
+            int32_t face = 0;
+            if (tex->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) {
+                face = ImageTarget().get();
+            }
+            return JS::Int32Value(face);
+        }
+        break;
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER:
+        if (tex) {
+            int32_t layer = 0;
+            if (tex->Target() == LOCAL_GL_TEXTURE_2D_ARRAY ||
+                tex->Target() == LOCAL_GL_TEXTURE_3D)
+            {
+                layer = Layer();
+            }
+            return JS::Int32Value(layer);
+        }
+        break;
+    }
+
+    context->ErrorInvalidEnum("getFramebufferParameter: Invalid combination of "
+                              "attachment and pname.");
+    return JS::NullValue();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLFramebuffer
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLContextBoundObject(webgl)
     , mGLName(fbo)
     , mStatus(0)
     , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
@@ -982,16 +1042,120 @@ WebGLFramebuffer::ValidateForRead(const 
         mContext->ErrorInvalidOperation("readPixels: ");
         return false;
     }
 
     *out_format = attachPoint.EffectiveInternalFormat();
     return true;
 }
 
+static bool
+AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
+{
+    if (a.Texture()) {
+        return (a.Texture() != b.Texture());
+    }
+
+    if (a.Renderbuffer()) {
+        return (a.Renderbuffer() != b.Renderbuffer());
+    }
+
+    return false;
+}
+
+JS::Value
+WebGLFramebuffer::GetAttachmentParameter(JSContext* cx,
+                                         GLenum attachment,
+                                         GLenum pname,
+                                         ErrorResult& rv)
+{
+    // "If a framebuffer object is bound to target, then attachment must be one of the
+    // attachment points of the framebuffer listed in table 4.6."
+    switch (attachment) {
+    case LOCAL_GL_DEPTH_ATTACHMENT:
+    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
+        break;
+
+    case LOCAL_GL_STENCIL_ATTACHMENT:
+        // "If attachment is DEPTH_STENCIL_ATTACHMENT, and different objects are bound to
+        //  the depth and stencil attachment points of target, the query will fail and
+        //  generate an INVALID_OPERATION error. If the same object is bound to both
+        //  attachment points, information about that object will be returned."
+
+        // Does this mean it has to be the same level or layer? Because the queries are
+        // independent of level or layer.
+        if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
+            mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: "
+                                            "DEPTH_ATTACHMENT and STENCIL_ATTACHMENT "
+                                            "have different objects bound.");
+            return JS::NullValue();
+        }
+        break;
+
+    default:
+        if (attachment < LOCAL_GL_COLOR_ATTACHMENT0 ||
+            attachment > mContext->LastColorAttachment())
+        {
+            mContext->ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only "
+                                       "query COLOR_ATTACHMENTi, DEPTH_ATTACHMENT, "
+                                       "DEPTH_STENCIL_ATTACHMENT, or STENCIL_ATTACHMENT "
+                                       "on framebuffer.");
+            return JS::NullValue();
+        }
+    }
+
+    if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT &&
+        pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)
+    {
+        mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: Querying "
+                                        "FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE against "
+                                        "DEPTH_STENCIL_ATTACHMENT is an error.");
+        return JS::NullValue();
+    }
+
+    GLenum objectType = LOCAL_GL_NONE;
+    auto& fba = GetAttachPoint(attachment);
+    if (fba.Texture()) {
+        objectType = LOCAL_GL_TEXTURE;
+    } else if (fba.Renderbuffer()) {
+        objectType = LOCAL_GL_RENDERBUFFER;
+    }
+
+    switch (pname) {
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+        return JS::Int32Value(objectType);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
+        if (objectType == LOCAL_GL_NONE) {
+            return JS::NullValue();
+        }
+
+        if (objectType == LOCAL_GL_RENDERBUFFER) {
+            const WebGLRenderbuffer* rb = fba.Renderbuffer();
+            return mContext->WebGLObjectAsJSValue(cx, rb, rv);
+        }
+
+        /* If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is TEXTURE, then */
+        if (objectType == LOCAL_GL_TEXTURE) {
+            const WebGLTexture* tex = fba.Texture();
+            return mContext->WebGLObjectAsJSValue(cx, tex, rv);
+        }
+        break;
+    }
+
+    if (objectType == LOCAL_GL_NONE) {
+        mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: No "
+                                        "attachment at %s",
+                                        mContext->EnumName(attachment));
+        return JS::NullValue();
+    }
+
+    return fba.GetParameter(mContext, pname);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLFramebufferBinding::Wrap(cx, this, givenProto);
 }
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -57,17 +57,17 @@ public:
     void Clear() {
         SetRenderbuffer(nullptr);
     }
 
     void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level);
     void SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, GLint level,
                           GLint layer);
     void SetRenderbuffer(WebGLRenderbuffer* rb);
-    
+
     const WebGLTexture* Texture() const {
         return mTexturePtr;
     }
     WebGLTexture* Texture() {
         return mTexturePtr;
     }
     const WebGLRenderbuffer* Renderbuffer() const {
         return mRenderbufferPtr;
@@ -90,16 +90,18 @@ public:
 
     const WebGLRectangleObject& RectangleObject() const;
 
     bool HasImage() const;
     bool IsComplete() const;
 
     void FinalizeAttachment(gl::GLContext* gl,
                             FBAttachment attachmentLoc) const;
+
+    JS::Value GetParameter(WebGLContext* context, GLenum pname);
 };
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
     , public SupportsWeakPtr<WebGLFramebuffer>
@@ -220,13 +222,16 @@ public:
 
     void EnsureColorAttachPoints(size_t colorAttachmentId);
 
     void InvalidateFramebufferStatus() const {
         mStatus = 0;
     }
 
     bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
+
+    JS::Value GetAttachmentParameter(JSContext* cx, GLenum attachment, GLenum pname,
+                                     ErrorResult& rv);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -14,18 +14,16 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
 #include "WebGLObjectModel.h"
 
 
-template<class> class nsRefPtr;
-
 namespace mozilla {
 class ErrorResult;
 class WebGLActiveInfo;
 class WebGLProgram;
 class WebGLShader;
 class WebGLUniformLocation;
 
 namespace dom {
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -26,16 +26,17 @@
 #include "nsVariant.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DataTransferBinding.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/OSFileSystem.h"
+#include "mozilla/dom/Promise.h"
 
 namespace mozilla {
 namespace dom {
 
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                             TransferItem& aField,
                             const char* aName,
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -15,32 +15,32 @@
 #include "nsIDOMElement.h"
 #include "nsIDragService.h"
 #include "nsCycleCollectionParticipant.h"
 
 #include "nsAutoPtr.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/File.h"
-#include "mozilla/dom/Promise.h"
 
 class nsINode;
 class nsITransferable;
 class nsISupportsArray;
 class nsILoadContext;
 
 namespace mozilla {
 
 class EventStateManager;
 
 namespace dom {
 
 class DOMStringList;
 class Element;
 class FileList;
+class Promise;
 template<typename T> class Optional;
 
 /**
  * TransferItem is used to hold data for a particular format. Each piece of
  * data has a principal set from the caller which added it. This allows a
  * caller that wishes to retrieve the data to only be able to access the data
  * it is allowed to, yet still allow a chrome caller to retrieve any of the
  * data.
--- a/dom/events/Touch.cpp
+++ b/dom/events/Touch.cpp
@@ -62,16 +62,33 @@ Touch::Touch(int32_t aIdentifier,
   mRotationAngle = aRotationAngle;
   mForce = aForce;
 
   mChanged = false;
   mMessage = 0;
   nsJSContext::LikelyShortLivingObjectCreated();
 }
 
+Touch::Touch(const Touch& aOther)
+  : mTarget(aOther.mTarget)
+  , mRefPoint(aOther.mRefPoint)
+  , mChanged(aOther.mChanged)
+  , mMessage(aOther.mMessage)
+  , mIdentifier(aOther.mIdentifier)
+  , mPagePoint(aOther.mPagePoint)
+  , mClientPoint(aOther.mClientPoint)
+  , mScreenPoint(aOther.mScreenPoint)
+  , mRadius(aOther.mRadius)
+  , mRotationAngle(aOther.mRotationAngle)
+  , mForce(aOther.mForce)
+  , mPointsInitialized(aOther.mPointsInitialized)
+{
+  nsJSContext::LikelyShortLivingObjectCreated();
+}
+
 Touch::~Touch()
 {
 }
 
 // static
 bool
 Touch::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
 {
--- a/dom/events/Touch.h
+++ b/dom/events/Touch.h
@@ -40,16 +40,17 @@ public:
         int32_t aRadiusY,
         float aRotationAngle,
         float aForce);
   Touch(int32_t aIdentifier,
         LayoutDeviceIntPoint aPoint,
         nsIntPoint aRadius,
         float aRotationAngle,
         float aForce);
+  Touch(const Touch& aOther);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Touch)
 
   void InitializePoints(nsPresContext* aPresContext, WidgetEvent* aEvent);
 
   void SetTarget(EventTarget* aTarget);
 
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -181,20 +181,20 @@ TouchEvent::ChangedTouches()
 // static
 bool
 TouchEvent::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
 {
   bool prefValue = false;
   int32_t flag = 0;
   if (NS_SUCCEEDED(Preferences::GetInt("dom.w3c_touch_events.enabled", &flag))) {
     if (flag == 2) {
-#ifdef XP_WIN
+#if defined(XP_WIN) || MOZ_WIDGET_GTK == 3
       static bool sDidCheckTouchDeviceSupport = false;
       static bool sIsTouchDeviceSupportPresent = false;
-      // On Windows we auto-detect based on device support.
+      // On Windows and GTK3 we auto-detect based on device support.
       if (!sDidCheckTouchDeviceSupport) {
         sDidCheckTouchDeviceSupport = true;
         sIsTouchDeviceSupportPresent = WidgetUtils::IsTouchDeviceSupportPresent();
       }
       prefValue = sIsTouchDeviceSupportPresent;
 #else
       NS_WARNING("dom.w3c_touch_events.enabled=2 not implemented!");
       prefValue = false;
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -73,16 +73,17 @@ UNIFIED_SOURCES += [
     'AnimationEvent.cpp',
     'AsyncEventDispatcher.cpp',
     'BeforeAfterKeyboardEvent.cpp',
     'BeforeUnloadEvent.cpp',
     'ClipboardEvent.cpp',
     'CommandEvent.cpp',
     'CompositionEvent.cpp',
     'ContentEventHandler.cpp',
+    'CustomEvent.cpp',
     'DataContainerEvent.cpp',
     'DataTransfer.cpp',
     'DeviceMotionEvent.cpp',
     'DOMEventTargetHelper.cpp',
     'DragEvent.cpp',
     'Event.cpp',
     'EventDispatcher.cpp',
     'EventListenerManager.cpp',
@@ -112,17 +113,16 @@ UNIFIED_SOURCES += [
     'UIEvent.cpp',
     'WheelEvent.cpp',
     'WheelHandlingHelper.cpp',
     'XULCommandEvent.cpp',
 ]
 
 # nsEventStateManager.cpp should be built separately because of Mac OS X headers.
 SOURCES += [
-    'CustomEvent.cpp',
     'EventStateManager.cpp',
 ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     UNIFIED_SOURCES += ['SpeechRecognitionError.cpp']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -45,363 +45,144 @@ NS_IMPL_ISUPPORTS(FetchDriver,
                   nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
                   nsIThreadRetargetableStreamListener)
 
 FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
                          nsILoadGroup* aLoadGroup)
   : mPrincipal(aPrincipal)
   , mLoadGroup(aLoadGroup)
   , mRequest(aRequest)
-  , mFetchRecursionCount(0)
-  , mCORSFlagEverSet(false)
+  , mHasBeenCrossSite(false)
   , mResponseAvailableCalled(false)
+  , mFetchCalled(false)
 {
 }
 
 FetchDriver::~FetchDriver()
 {
   // We assert this since even on failures, we should call
   // FailWithNetworkError().
   MOZ_ASSERT(mResponseAvailableCalled);
 }
 
 nsresult
 FetchDriver::Fetch(FetchDriverObserver* aObserver)
 {
   workers::AssertIsOnMainThread();
+  MOZ_ASSERT(!mFetchCalled);
+  mFetchCalled = true;
+
   mObserver = aObserver;
 
   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REQUEST_PASSTHROUGH,
                         mRequest->WasCreatedByFetchEvent());
 
-  return Fetch(false /* CORS flag */);
+  // FIXME(nsm): Deal with HSTS.
+
+  MOZ_RELEASE_ASSERT(!mRequest->IsSynchronous(),
+                     "Synchronous fetch not supported");
+
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(this, &FetchDriver::ContinueFetch);
+  return NS_DispatchToCurrentThread(r);
 }
 
 nsresult
-FetchDriver::Fetch(bool aCORSFlag)
-{
-  // We do not currently implement parts of the spec that lead to recursion.
-  MOZ_ASSERT(mFetchRecursionCount == 0);
-  mFetchRecursionCount++;
-
-  // FIXME(nsm): Deal with HSTS.
-
-  if (!mRequest->IsSynchronous() && mFetchRecursionCount <= 1) {
-    nsCOMPtr<nsIRunnable> r =
-      NS_NewRunnableMethodWithArg<bool>(this, &FetchDriver::ContinueFetch, aCORSFlag);
-    nsresult rv = NS_DispatchToCurrentThread(r);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      FailWithNetworkError();
-    }
-    return rv;
-  }
-
-  MOZ_CRASH("Synchronous fetch not supported");
-}
-
-FetchDriver::MainFetchOp
-FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag)
+FetchDriver::SetTainting()
 {
   workers::AssertIsOnMainThread();
 
+  // If we've already been cross-site then we should be fully updated
+  if (mHasBeenCrossSite) {
+    return NS_OK;
+  }
+
   nsAutoCString url;
   mRequest->GetURL(url);
   nsCOMPtr<nsIURI> requestURI;
   nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
                           nullptr, nullptr);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return MainFetchOp(NETWORK_ERROR);
-  }
-
-  // CSP/mixed content checks.
-  int16_t shouldLoad;
-  rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(),
-                                 requestURI,
-                                 mPrincipal,
-                                 mDocument,
-                                 // FIXME(nsm): Should MIME be extracted from
-                                 // Content-Type header?
-                                 EmptyCString(), /* mime guess */
-                                 nullptr, /* extra */
-                                 &shouldLoad,
-                                 nsContentUtils::GetContentPolicy(),
-                                 nsContentUtils::GetSecurityManager());
-  if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) {
-    // Disallowed by content policy.
-    return MainFetchOp(NETWORK_ERROR);
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Begin Step 8 of the Main Fetch algorithm
   // https://fetch.spec.whatwg.org/#fetching
 
-  nsAutoCString scheme;
-  rv = requestURI->GetScheme(scheme);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return MainFetchOp(NETWORK_ERROR);
-  }
-
   // request's current url's origin is request's origin and the CORS flag is unset
   // request's current url's scheme is "data" and request's same-origin data-URL flag is set
   // request's current url's scheme is "about"
-  rv = mPrincipal->CheckMayLoad(requestURI, false /* report */,
-                                false /* allowIfInheritsPrincipal */);
-  if ((!aCORSFlag && NS_SUCCEEDED(rv)) ||
-      (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) ||
-      scheme.EqualsLiteral("about")) {
-    return MainFetchOp(BASIC_FETCH);
+
+  // We have to manually check about:blank here since it's not treated as
+  // an inheriting URL by CheckMayLoad.
+  if (NS_IsAboutBlank(requestURI) ||
+       NS_SUCCEEDED(mPrincipal->CheckMayLoad(requestURI, false /* report */,
+                                             true /*allowIfInheritsPrincipal*/))) {
+    // What the spec calls "basic fetch" is handled within our necko channel
+    // code.  Therefore everything goes through HTTP Fetch
+    return NS_OK;
   }
 
+  mHasBeenCrossSite = true;
+
   // request's mode is "same-origin"
   if (mRequest->Mode() == RequestMode::Same_origin) {
-    return MainFetchOp(NETWORK_ERROR);
+    return NS_ERROR_DOM_BAD_URI;
   }
 
   // request's mode is "no-cors"
   if (mRequest->Mode() == RequestMode::No_cors) {
     mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
-    return MainFetchOp(BASIC_FETCH);
-  }
-
-  // request's current url's scheme is not one of "http" and "https"
-  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
-    return MainFetchOp(NETWORK_ERROR);
-  }
-
-  // request's mode is "cors-with-forced-preflight"
-  // request's unsafe-request flag is set and either request's method is not
-  // a simple method or a header in request's header list is not a simple header
-  if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
-      (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() ||
-                                     !mRequest->Headers()->HasOnlySimpleHeaders()))) {
-    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
-    mRequest->SetRedirectMode(RequestRedirect::Error);
-
-    // Note, the following text from Main Fetch step 8 is handled in
-    // nsCORSListenerProxy when CheckRequestApproved() fails:
-    //
-    //  The result of performing an HTTP fetch using request with the CORS
-    //  flag and CORS-preflight flag set. If the result is a network error,
-    //  clear cache entries using request.
-
-    return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */);
+    // What the spec calls "basic fetch" is handled within our necko channel
+    // code.  Therefore everything goes through HTTP Fetch
+    return NS_OK;
   }
 
   // Otherwise
   mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
-  return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */);
+
+  return NS_OK;
 }
 
 nsresult
-FetchDriver::ContinueFetch(bool aCORSFlag)
+FetchDriver::ContinueFetch()
 {
   workers::AssertIsOnMainThread();
 
-  MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag);
-
-  if (nextOp.mType == NETWORK_ERROR) {
-    return FailWithNetworkError();
-  }
-
-  if (nextOp.mType == BASIC_FETCH) {
-    return BasicFetch();
-  }
-
-  if (nextOp.mType == HTTP_FETCH) {
-    return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag);
-  }
-
-  MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!");
-  return FailWithNetworkError();
- }
-
-nsresult
-FetchDriver::BasicFetch()
-{
-  nsAutoCString url;
-  mRequest->GetURL(url);
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri),
-                 url,
-                 nullptr,
-                 nullptr);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  nsresult rv = HttpFetch();
+  if (NS_FAILED(rv)) {
     FailWithNetworkError();
-    return rv;
-  }
-
-  nsAutoCString scheme;
-  rv = uri->GetScheme(scheme);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailWithNetworkError();
-    return rv;
-  }
-
-  if (scheme.LowerCaseEqualsLiteral("about")) {
-    if (url.EqualsLiteral("about:blank")) {
-      RefPtr<InternalResponse> response =
-        new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
-      ErrorResult result;
-      response->Headers()->Append(NS_LITERAL_CSTRING("content-type"),
-                                  NS_LITERAL_CSTRING("text/html;charset=utf-8"),
-                                  result);
-      MOZ_ASSERT(!result.Failed());
-      nsCOMPtr<nsIInputStream> body;
-      rv = NS_NewCStringInputStream(getter_AddRefs(body), EmptyCString());
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        FailWithNetworkError();
-        return rv;
-      }
-
-      response->SetBody(body);
-      BeginResponse(response);
-      return SucceedWithResponse();
-    }
-    return FailWithNetworkError();
   }
-
-  if (scheme.LowerCaseEqualsLiteral("blob")) {
-    RefPtr<BlobImpl> blobImpl;
-    rv = NS_GetBlobForBlobURI(uri, getter_AddRefs(blobImpl));
-    BlobImpl* blob = static_cast<BlobImpl*>(blobImpl.get());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      FailWithNetworkError();
-      return rv;
-    }
-
-    RefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
-    ErrorResult result;
-    uint64_t size = blob->GetSize(result);
-    if (NS_WARN_IF(result.Failed())) {
-      FailWithNetworkError();
-      return result.StealNSResult();
-    }
-
-    nsAutoString sizeStr;
-    sizeStr.AppendInt(size);
-    response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), NS_ConvertUTF16toUTF8(sizeStr), result);
-    if (NS_WARN_IF(result.Failed())) {
-      FailWithNetworkError();
-      return result.StealNSResult();
-    }
-
-    nsAutoString type;
-    blob->GetType(type);
-    response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), NS_ConvertUTF16toUTF8(type), result);
-    if (NS_WARN_IF(result.Failed())) {
-      FailWithNetworkError();
-      return result.StealNSResult();
-    }
-
-    nsCOMPtr<nsIInputStream> stream;
-    blob->GetInternalStream(getter_AddRefs(stream), result);
-    if (NS_WARN_IF(result.Failed())) {
-      FailWithNetworkError();
-      return result.StealNSResult();
-    }
-
-    response->SetBody(stream);
-    BeginResponse(response);
-    return SucceedWithResponse();
-  }
-
-  if (scheme.LowerCaseEqualsLiteral("data")) {
-    nsAutoCString method;
-    mRequest->GetMethod(method);
-    if (method.LowerCaseEqualsASCII("get")) {
-      nsresult rv;
-      nsCOMPtr<nsIProtocolHandler> dataHandler =
-        do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", &rv);
-
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
-
-      nsCOMPtr<nsIChannel> channel;
-      rv = dataHandler->NewChannel(uri, getter_AddRefs(channel));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
-
-      nsCOMPtr<nsIInputStream> stream;
-      rv = channel->Open(getter_AddRefs(stream));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
-
-      // nsDataChannel will parse the data URI when it is Open()ed and set the
-      // correct content type and charset.
-      nsAutoCString contentType;
-      if (NS_SUCCEEDED(channel->GetContentType(contentType))) {
-        nsAutoCString charset;
-        if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty()) {
-          contentType.AppendLiteral(";charset=");
-          contentType.Append(charset);
-        }
-      } else {
-        NS_WARNING("Could not get content type from data channel");
-      }
-
-      RefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
-      ErrorResult result;
-      response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result);
-      if (NS_WARN_IF(result.Failed())) {
-        FailWithNetworkError();
-        return result.StealNSResult();
-      }
-
-      response->SetBody(stream);
-      BeginResponse(response);
-      return SucceedWithResponse();
-    }
-
-    return FailWithNetworkError();
-  }
-
-  if (scheme.LowerCaseEqualsLiteral("http") ||
-      scheme.LowerCaseEqualsLiteral("https") ||
-      scheme.LowerCaseEqualsLiteral("app")) {
-    return HttpFetch();
-  }
-
-  return 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(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag)
+FetchDriver::HttpFetch()
 {
   // Step 1. "Let response be null."
   mResponse = nullptr;
   nsresult rv;
 
-  // We need to track the CORS flag through redirects.  Since there is no way
-  // for us to go from CORS mode to non-CORS mode, we just need to remember
-  // if it has ever been set.
-  mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag;
-
   nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailWithNetworkError();
-    return rv;
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString url;
   mRequest->GetURL(url);
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri),
                           url,
                           nullptr,
                           nullptr,
                           ios);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailWithNetworkError();
-    return rv;
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = SetTainting();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Step 2 deals with letting ServiceWorkers intercept requests. This is
   // handled by Necko after the channel is opened.
   // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be
   // set based on the Request's flag.
 
   // Step 3.1 "If the CORS preflight flag is set and one of these conditions is
   // true..." is handled by the CORS proxy.
@@ -414,54 +195,85 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
   // if it succeeds, so we need to have everything setup for the original
   // request too.
 
   // Step 3.3 "Let credentials flag be set if one of
   //  - request's credentials mode is "include"
   //  - request's credentials mode is "same-origin" and either the CORS flag
   //    is unset or response tainting is "opaque"
   // is true, and unset otherwise."
-  bool useCredentials = false;
-  if (mRequest->GetCredentialsMode() == RequestCredentials::Include ||
-      (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && !aCORSFlag &&
-       mRequest->GetResponseTainting() != InternalRequest::RESPONSETAINT_OPAQUE)) {
-    useCredentials = true;
-  }
 
   // This is effectivetly the opposite of the use credentials flag in "HTTP
   // network or cache fetch" in the spec and decides whether to transmit
   // cookies and other identifying information. LOAD_ANONYMOUS also prevents
   // new cookies sent by the server from being stored.  This value will
   // propagate across redirects, which is what we want.
-  const nsLoadFlags credentialsFlag = useCredentials ? 0 : nsIRequest::LOAD_ANONYMOUS;
+  const nsLoadFlags credentialsFlag =
+    (mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
+    (mHasBeenCrossSite &&
+     mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
+     mRequest->Mode() == RequestMode::No_cors)) ?
+    nsIRequest::LOAD_ANONYMOUS : 0;
 
   // Set skip serviceworker flag.
   // While the spec also gates on the client being a ServiceWorker, we can't
   // infer that here. Instead we rely on callers to set the flag correctly.
   const nsLoadFlags bypassFlag = mRequest->SkipServiceWorker() ?
                                  nsIChannel::LOAD_BYPASS_SERVICE_WORKER : 0;
 
+  nsSecurityFlags secFlags;
+  if (mRequest->Mode() == RequestMode::Cors &&
+      mRequest->GetCredentialsMode() == RequestCredentials::Include) {
+    secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS |
+               nsILoadInfo::SEC_REQUIRE_CORS_WITH_CREDENTIALS;
+  } else if (mRequest->Mode() == RequestMode::Cors) {
+    secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+  } else if (mRequest->Mode() == RequestMode::Same_origin) {
+    secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+  } else if (mRequest->Mode() == RequestMode::No_cors) {
+    secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+  } else {
+    MOZ_ASSERT_UNREACHABLE("Unexpected request mode!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
   // From here on we create a channel and set its properties with the
   // information from the InternalRequest. This is an implementation detail.
   MOZ_ASSERT(mLoadGroup);
   nsCOMPtr<nsIChannel> chan;
-  rv = NS_NewChannel(getter_AddRefs(chan),
-                     uri,
-                     mPrincipal,
-                     nsILoadInfo::SEC_NORMAL,
-                     mRequest->ContentPolicyType(),
-                     mLoadGroup,
-                     nullptr, /* aCallbacks */
-                     nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
-                     ios);
+
+  // For dedicated workers mDocument refers to the parent document of the
+  // worker (why do we do that?). In that case we don't want to use the
+  // document here since that is not the correct principal.
+  if (mDocument && mDocument->NodePrincipal() == mPrincipal) {
+    rv = NS_NewChannel(getter_AddRefs(chan),
+                       uri,
+                       mDocument,
+                       secFlags |
+                         nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
+                       mRequest->ContentPolicyType(),
+                       mLoadGroup,
+                       nullptr, /* aCallbacks */
+                       nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
+                       ios);
+  } else {
+    rv = NS_NewChannel(getter_AddRefs(chan),
+                       uri,
+                       mPrincipal,
+                       secFlags |
+                         nsILoadInfo::SEC_ABOUT_BLANK_INHERITS,
+                       mRequest->ContentPolicyType(),
+                       mLoadGroup,
+                       nullptr, /* aCallbacks */
+                       nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag,
+                       ios);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mLoadGroup = nullptr;
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailWithNetworkError();
-    return rv;
-  }
 
   // Insert ourselves into the notification callbacks chain so we can handle
   // cross-origin redirects.
 #ifdef DEBUG
   {
     nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
     chan->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
     MOZ_ASSERT(!notificationCallbacks);
@@ -480,20 +292,17 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
   // ---------------------------
   // Step 1 "Let HTTPRequest..." The channel is the HTTPRequest.
   nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
   if (httpChan) {
     // Copy the method.
     nsAutoCString method;
     mRequest->GetMethod(method);
     rv = httpChan->SetRequestMethod(method);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      FailWithNetworkError();
-      return rv;
-    }
+    NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the same headers.
     nsAutoTArray<InternalHeaders::Entry, 5> headers;
     mRequest->Headers()->GetEntries(headers);
     for (uint32_t i = 0; i < headers.Length(); ++i) {
       if (headers[i].mValue.IsEmpty()) {
         httpChan->SetEmptyRequestHeader(headers[i].mName);
       } else {
@@ -503,55 +312,46 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
 
     // Step 2. Set the referrer.
     nsAutoString referrer;
     mRequest->GetReferrer(referrer);
     if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
       rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
                                                          mDocument,
                                                          httpChan);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
     } else if (referrer.IsEmpty()) {
       rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
     } else {
       // From "Determine request's Referrer" step 3
       // "If request's referrer is a URL, let referrerSource be request's
       // referrer."
       //
       // XXXnsm - We never actually hit this from a fetch() call since both
       // fetch and Request() create a new internal request whose referrer is
       // always set to about:client. Should we just crash here instead until
       // someone tries to use FetchDriver for non-fetch() APIs?
       nsCOMPtr<nsIURI> referrerURI;
       rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
 
       rv =
         httpChan->SetReferrerWithPolicy(referrerURI,
                                         mDocument ? mDocument->GetReferrerPolicy() :
                                                     net::RP_Default);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Step 3 "If HTTPRequest's force Origin header flag is set..."
     if (mRequest->ForceOriginHeader()) {
       nsAutoString origin;
       rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
+
       httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"),
                                  NS_ConvertUTF16toUTF8(origin),
                                  false /* merge */);
     }
     // Bug 1120722 - Authorization will be handled later.
     // Auth may require prompting, we don't support it yet.
     // The next patch in this same bug prevents this from aborting the request.
     // Credentials checks for CORS are handled by nsCORSListenerProxy,
@@ -572,96 +372,68 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
   nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan);
   if (uploadChan) {
     nsAutoCString contentType;
     ErrorResult result;
     mRequest->Headers()->Get(NS_LITERAL_CSTRING("content-type"), contentType, result);
     // This is an error because the Request constructor explicitly extracts and
     // sets a content-type per spec.
     if (result.Failed()) {
-      return FailWithNetworkError();
+      return result.StealNSResult();
     }
 
     nsCOMPtr<nsIInputStream> bodyStream;
     mRequest->GetBody(getter_AddRefs(bodyStream));
     if (bodyStream) {
       nsAutoCString method;
       mRequest->GetMethod(method);
       rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType, -1, method, false /* aStreamHasHeaders */);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return FailWithNetworkError();
-      }
+      NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
-  nsCOMPtr<nsIStreamListener> listener = this;
-
-  MOZ_ASSERT_IF(aCORSFlag, mRequest->Mode() == RequestMode::Cors);
-
-  // Only use nsCORSListenerProxy if we are in CORS mode.  Otherwise it
-  // will overwrite the CorsMode flag unconditionally to "cors" or
-  // "cors-with-forced-preflight".
-  if (mRequest->Mode() == RequestMode::Cors) {
-    // Passing false for the credentials flag to nsCORSListenerProxy is semantically
-    // the same as the "same-origin" RequestCredentials value.  We implement further
-    // blocking of credentials for "omit" by setting LOAD_ANONYMOUS manually above.
-    bool corsCredentials =
-      mRequest->GetCredentialsMode() == RequestCredentials::Include;
-
-    // Set up a CORS proxy that will handle the various requirements of the CORS
-    // protocol. It handles the preflight cache and CORS response headers.
-    // If the request is allowed, it will start our original request
-    // and our observer will be notified. On failure, our observer is notified
-    // directly.
-    RefPtr<nsCORSListenerProxy> corsListener =
-      new nsCORSListenerProxy(this, mPrincipal, corsCredentials);
-    rv = corsListener->Init(chan, DataURIHandling::Allow);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FailWithNetworkError();
-    }
-    listener = corsListener.forget();
-  }
-
   // If preflight is required, start a "CORS preflight fetch"
   // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the
-  // implementation is handled by NS_StartCORSPreflight, we just set up the
-  // unsafeHeaders so they can be verified against the response's
-  // "Access-Control-Allow-Headers" header.
-  if (aCORSPreflightFlag) {
-    MOZ_ASSERT(mRequest->Mode() != RequestMode::No_cors,
-               "FetchDriver::ContinueFetch() should ensure that the request is not no-cors");
-    MOZ_ASSERT(httpChan, "CORS preflight can only be used with HTTP channels");
+  // implementation is handled by the http channel calling into
+  // nsCORSListenerProxy. We just inform it which unsafe headers are included
+  // in the request.
+  if (IsUnsafeRequest()) {
+    if (mRequest->Mode() == RequestMode::No_cors) {
+      return NS_ERROR_DOM_BAD_URI;
+    }
+
+    mRequest->SetRedirectMode(RequestRedirect::Error);
+
     nsAutoTArray<nsCString, 5> unsafeHeaders;
     mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
 
     nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
-    rv = internalChan->SetCorsPreflightParameters(unsafeHeaders, useCredentials, mPrincipal);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FailWithNetworkError();
-    }
+    NS_ENSURE_TRUE(internalChan, NS_ERROR_DOM_BAD_URI);
+
+    rv = internalChan->SetCorsPreflightParameters(
+      unsafeHeaders,
+      mRequest->GetCredentialsMode() == RequestCredentials::Include,
+      mPrincipal);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  rv = chan->AsyncOpen(listener, nullptr);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return FailWithNetworkError();
-  }
+  rv = chan->AsyncOpen2(this);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
   return NS_OK;
 }
 
-nsresult
-FetchDriver::ContinueHttpFetchAfterNetworkFetch()
+bool
+FetchDriver::IsUnsafeRequest()
 {
-  workers::AssertIsOnMainThread();
-  MOZ_ASSERT(mResponse);
-  MOZ_ASSERT(!mResponse->IsError());
-
-  return SucceedWithResponse();
+  return mHasBeenCrossSite &&
+         (mRequest->UnsafeRequest() &&
+          (!mRequest->HasSimpleMethod() ||
+           !mRequest->Headers()->HasOnlySimpleHeaders()));
 }
 
 already_AddRefed<InternalResponse>
 FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI)
 {
   MOZ_ASSERT(aResponse);
   nsAutoCString reqURL;
   if (aFinalURI) {
@@ -694,34 +466,16 @@ FetchDriver::BeginAndGetFilteredResponse
 
   MOZ_ASSERT(filteredResponse);
   MOZ_ASSERT(mObserver);
   mObserver->OnResponseAvailable(filteredResponse);
   mResponseAvailableCalled = true;
   return filteredResponse.forget();
 }
 
-void
-FetchDriver::BeginResponse(InternalResponse* aResponse)
-{
-  RefPtr<InternalResponse> r = BeginAndGetFilteredResponse(aResponse, nullptr);
-  // Release the ref.
-}
-
-nsresult
-FetchDriver::SucceedWithResponse()
-{
-  workers::AssertIsOnMainThread();
-  if (mObserver) {
-    mObserver->OnResponseEnd();
-    mObserver = nullptr;
-  }
-  return NS_OK;
-}
-
 nsresult
 FetchDriver::FailWithNetworkError()
 {
   workers::AssertIsOnMainThread();
   RefPtr<InternalResponse> error = InternalResponse::NetworkError();
   if (mObserver) {
     mObserver->OnResponseAvailable(error);
     mResponseAvailableCalled = true;
@@ -780,47 +534,75 @@ FetchDriver::OnStartRequest(nsIRequest* 
     return rv;
   }
 
   // We should only get to the following code once.
   MOZ_ASSERT(!mPipeOutputStream);
   MOZ_ASSERT(mObserver);
 
   RefPtr<InternalResponse> response;
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+  nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest);
+
   if (httpChannel) {
     uint32_t responseStatus;
     httpChannel->GetResponseStatus(&responseStatus);
 
     nsAutoCString statusText;
     httpChannel->GetResponseStatusText(statusText);
 
     response = new InternalResponse(responseStatus, statusText);
 
     RefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response);
     rv = httpChannel->VisitResponseHeaders(visitor);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       NS_WARNING("Failed to visit all headers.");
     }
-  } else {
-    nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest);
-    // If it is not an http channel, it has to be a jar one.
-    MOZ_ASSERT(jarChannel);
-
+  } else if (jarChannel) {
     // We simulate the http protocol for jar/app requests
     uint32_t responseStatus = 200;
     nsAutoCString statusText;
     response = new InternalResponse(responseStatus, NS_LITERAL_CSTRING("OK"));
     ErrorResult result;
     nsAutoCString contentType;
     jarChannel->GetContentType(contentType);
     response->Headers()->Append(NS_LITERAL_CSTRING("content-type"),
                                 contentType,
                                 result);
     MOZ_ASSERT(!result.Failed());
+  } else {
+    response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+
+    ErrorResult result;
+    nsAutoCString contentType;
+    rv = channel->GetContentType(contentType);
+    if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) {
+      nsAutoCString contentCharset;
+      channel->GetContentCharset(contentCharset);
+      if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) {
+        contentType += NS_LITERAL_CSTRING(";charset=") + contentCharset;
+      }
+
+      response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
+                                  contentType,
+                                  result);
+      MOZ_ASSERT(!result.Failed());
+    }
+
+    int64_t contentLength;
+    rv = channel->GetContentLength(&contentLength);
+    if (NS_SUCCEEDED(rv) && contentLength) {
+      nsAutoCString contentLenStr;
+      contentLenStr.AppendInt(contentLength);
+      response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"),
+                                  contentLenStr,
+                                  result);
+      MOZ_ASSERT(!result.Failed());
+    }
   }
 
   // We open a pipe so that we can immediately set the pipe's read end as the
   // response's body. Setting the segment size to UINT32_MAX means that the
   // pipe has infinite space. The nsIChannel will continue to buffer data in
   // xpcom events even if we block on a fixed size pipe.  It might be possible
   // to suspend the channel and then resume when there is space available, but
   // for now use an infinite pipe to avoid blocking.
@@ -833,17 +615,16 @@ FetchDriver::OnStartRequest(nsIRequest* 
                   false /* blocking output, since the pipe is 'in'finite */ );
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
   }
   response->SetBody(pipeInputStream);
 
-  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   response->InitChannelInfo(channel);
 
   nsCOMPtr<nsIURI> channelURI;
   rv = channel->GetURI(getter_AddRefs(channelURI));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailWithNetworkError();
     // Cancel request.
     return rv;
@@ -894,57 +675,67 @@ FetchDriver::OnStopRequest(nsIRequest* a
                            nsresult aStatusCode)
 {
   workers::AssertIsOnMainThread();
   if (NS_FAILED(aStatusCode)) {
     nsCOMPtr<nsIAsyncOutputStream> outputStream = do_QueryInterface(mPipeOutputStream);
     if (outputStream) {
       outputStream->CloseWithStatus(NS_BINDING_FAILED);
     }
+
     // We proceed as usual here, since we've already created a successful response
     // from OnStartRequest.
-    SucceedWithResponse();
-    return aStatusCode;
+  } else {
+    MOZ_ASSERT(mResponse);
+    MOZ_ASSERT(!mResponse->IsError());
+
+    if (mPipeOutputStream) {
+      mPipeOutputStream->Close();
+    }
   }
 
-  if (mPipeOutputStream) {
-    mPipeOutputStream->Close();
+  if (mObserver) {
+    mObserver->OnResponseEnd();
+    mObserver = nullptr;
   }
 
-  ContinueHttpFetchAfterNetworkFetch();
   return NS_OK;
 }
 
 // This is called when the channel is redirected.
 NS_IMETHODIMP
 FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                     nsIChannel* aNewChannel,
                                     uint32_t aFlags,
                                     nsIAsyncVerifyRedirectCallback *aCallback)
 {
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
+  if (NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags) ||
+      NS_IsHSTSUpgradeRedirect(aOldChannel, aNewChannel, aFlags)) {
+    aCallback->OnRedirectVerifyCallback(NS_OK);
+    return NS_OK;
+  }
+
   // HTTP Fetch step 5, "redirect status", step 1
   if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) {
     aOldChannel->Cancel(NS_BINDING_FAILED);
     return NS_BINDING_FAILED;
   }
 
   // HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically
   // handled by necko before calling AsyncOnChannelRedirect() with the new
   // nsIChannel.
 
   // HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect
   // count are done by Necko.  The pref used is "network.http.redirection-limit"
   // which is set to 20 by default.
 
-  // HTTP Fetch Step 9, "redirect status". We only unset this for spec
-  // compatibility. Any actions we take on mRequest here do not affect what the
-  //channel does.
-  mRequest->UnsetSameOriginDataURL();
+  // HTTP Fetch Step 9, "redirect status". This is enforced by the
+  // nsCORSListenerProxy. It forbids redirecting to data:
 
   // HTTP Fetch step 5, "redirect status", step 10 requires us to halt the
   // redirect, but successfully return an opaqueredirect Response to the
   // initiating Fetch.
   if (mRequest->GetRedirectMode() == RequestRedirect::Manual) {
     // Ideally we would simply not cancel the old channel and allow it to
     // be processed as normal.  Unfortunately this is quite fragile and
     // other redirect handlers can easily break it for certain use cases.
@@ -989,112 +780,90 @@ FetchDriver::AsyncOnChannelRedirect(nsIC
   }
 
   // We need to update our request's URL.
   nsAutoCString newUrl;
   newURI->GetSpec(newUrl);
   mRequest->SetURL(newUrl);
 
   // Implement Main Fetch step 8 again on redirect.
-  MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet);
-
-  if (nextOp.mType == NETWORK_ERROR) {
-    // Cancel the channel if Main Fetch blocks the redirect from continuing.
-    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-  // Otherwise, we rely on necko and the CORS proxy to do the right thing
-  // as the redirect is followed.  In general this means basic or http
-  // fetch.  If we've ever been CORS, we need to stay CORS.
-  MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH);
-  MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH);
-  MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag);
-
-  // Examine and possibly set the LOAD_ANONYMOUS flag on the channel.
-  nsLoadFlags flags;
-  rv = aNewChannel->GetLoadFlags(&flags);
+  rv = SetTainting();
   if (NS_FAILED(rv)) {
     aOldChannel->Cancel(rv);
     return rv;
   }
 
-  if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
-      mRequest->GetResponseTainting() == InternalRequest::RESPONSETAINT_OPAQUE) {
+  // Requests that require preflight are not permitted to redirect.
+  // Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual
+  // redirect flag to decide whether to execute step 4.10 or not. We do not
+  // represent it in our implementation.
+  // The only thing we do is to check if the request requires a preflight (part
+  // of step 4.9), in which case we abort. This part cannot be done by
+  // nsCORSListenerProxy since it does not have access to mRequest.
+  // which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all
+  // the other steps are handled by nsCORSListenerProxy.
+
+  if (IsUnsafeRequest()) {
+    // We can't handle redirects that require preflight yet.
+    // This is especially true for no-cors requests, which much always be
+    // blocked if they require preflight.
+
+    // Simply fire an error here.
+    aOldChannel->Cancel(NS_BINDING_FAILED);
+    return NS_BINDING_FAILED;
+  }
+
+  // Otherwise, we rely on necko and the CORS proxy to do the right thing
+  // as the redirect is followed.  In general this means http
+  // fetch.  If we've ever been CORS, we need to stay CORS.
+
+  // Possibly set the LOAD_ANONYMOUS flag on the channel.
+  if (mHasBeenCrossSite &&
+      mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
+      mRequest->Mode() == RequestMode::No_cors) {
     // In the case of a "no-cors" mode request with "same-origin" credentials,
     // we have to set LOAD_ANONYMOUS manually here in order to avoid sending
     // credentials on a cross-origin redirect.
-    flags |= nsIRequest::LOAD_ANONYMOUS;
-    rv = aNewChannel->SetLoadFlags(flags);
+    nsLoadFlags flags;
+    rv = aNewChannel->GetLoadFlags(&flags);
+    if (NS_SUCCEEDED(rv)) {
+      flags |= nsIRequest::LOAD_ANONYMOUS;
+      rv = aNewChannel->SetLoadFlags(flags);
+    }
     if (NS_FAILED(rv)) {
       aOldChannel->Cancel(rv);
       return rv;
     }
-
-  } else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) {
+  }
+#ifdef DEBUG
+  {
     // Make sure nothing in the redirect chain screws up our credentials
-    // settings.  LOAD_ANONYMOUS must be set if we RequestCredentials is "omit".
-    MOZ_ASSERT(flags & nsIRequest::LOAD_ANONYMOUS);
-
-  } else if (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin &&
-             nextOp.mCORSFlag) {
-    // We also want to verify the LOAD_ANONYMOUS flag is set when we are in
-    // "same-origin" credentials mode and the CORS flag is set.  We can't
-    // unconditionally assert here, however, because the nsCORSListenerProxy
-    // will set the flag later in the redirect callback chain.  Instead,
-    // perform a weaker assertion here by checking if CORS flag was set
-    // before this redirect.  In that case LOAD_ANONYMOUS must still be set.
-    MOZ_ASSERT_IF(mCORSFlagEverSet, flags & nsIRequest::LOAD_ANONYMOUS);
-
-  } else {
-    // Otherwise, we should be sending credentials
-    MOZ_ASSERT(!(flags & nsIRequest::LOAD_ANONYMOUS));
+    // settings. LOAD_ANONYMOUS must be set if we RequestCredentials is "omit"
+    // or "same-origin".
+    nsLoadFlags flags;
+    aNewChannel->GetLoadFlags(&flags);
+    bool shouldBeAnon =
+      mRequest->GetCredentialsMode() == RequestCredentials::Omit ||
+      (mHasBeenCrossSite &&
+       mRequest->GetCredentialsMode() == RequestCredentials::Same_origin);
+    MOZ_ASSERT(!!(flags & nsIRequest::LOAD_ANONYMOUS) == shouldBeAnon);
   }
-
-  // Track the CORSFlag through redirects.
-  mCORSFlagEverSet = mCORSFlagEverSet || nextOp.mCORSFlag;
+#endif
 
   aCallback->OnRedirectVerifyCallback(NS_OK);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FetchDriver::CheckListenerChain()
 {
   return NS_OK;
 }
 
-// Returns NS_OK if no preflight is required, error otherwise.
-nsresult
-FetchDriver::DoesNotRequirePreflight(nsIChannel* aChannel)
-{
-  // If this is a same-origin request or the channel's URI inherits
-  // its principal, it's allowed.
-  if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel, true)) {
-    return NS_OK;
-  }
-
-  // Check if we need to do a preflight request.
-  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
-  NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
-
-  nsAutoCString method;
-  httpChannel->GetRequestMethod(method);
-  if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
-      !mRequest->Headers()->HasOnlySimpleHeaders() ||
-      (!method.LowerCaseEqualsLiteral("get") &&
-       !method.LowerCaseEqualsLiteral("post") &&
-       !method.LowerCaseEqualsLiteral("head"))) {
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 FetchDriver::GetInterface(const nsIID& aIID, void **aResult)
 {
   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
     *aResult = static_cast<nsIChannelEventSink*>(this);
     NS_ADDREF_THIS();
     return NS_OK;
   }
@@ -1112,14 +881,14 @@ FetchDriver::GetInterface(const nsIID& a
 
   return QueryInterface(aIID, aResult);
 }
 
 void
 FetchDriver::SetDocument(nsIDocument* aDocument)
 {
   // Cannot set document after Fetch() has been called.
-  MOZ_ASSERT(mFetchRecursionCount == 0);
+  MOZ_ASSERT(!mFetchCalled);
   mDocument = aDocument;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -77,61 +77,35 @@ public:
 private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
   RefPtr<InternalRequest> mRequest;
   RefPtr<InternalResponse> mResponse;
   nsCOMPtr<nsIOutputStream> mPipeOutputStream;
   RefPtr<FetchDriverObserver> mObserver;
   nsCOMPtr<nsIDocument> mDocument;
-  uint32_t mFetchRecursionCount;
-  bool mCORSFlagEverSet;
+  bool mHasBeenCrossSite;
 
   DebugOnly<bool> mResponseAvailableCalled;
+  DebugOnly<bool> mFetchCalled;
 
   FetchDriver() = delete;
   FetchDriver(const FetchDriver&) = delete;
   FetchDriver& operator=(const FetchDriver&) = delete;
   ~FetchDriver();
 
-  enum MainFetchOpType
-  {
-    NETWORK_ERROR,
-    BASIC_FETCH,
-    HTTP_FETCH,
-    NUM_MAIN_FETCH_OPS
-  };
-
-  struct MainFetchOp
-  {
-    explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false,
-                         bool aCORSPreflightFlag = false)
-      : mType(aType), mCORSFlag(aCORSFlag),
-        mCORSPreflightFlag(aCORSPreflightFlag)
-    { }
-
-    MainFetchOpType mType;
-    bool mCORSFlag;
-    bool mCORSPreflightFlag;
-  };
-
-  nsresult Fetch(bool aCORSFlag);
-  MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag);
-  nsresult ContinueFetch(bool aCORSFlag);
-  nsresult BasicFetch();
-  nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false);
-  nsresult ContinueHttpFetchAfterNetworkFetch();
+  nsresult SetTainting();
+  nsresult ContinueFetch();
+  nsresult HttpFetch();
+  bool IsUnsafeRequest();
   // 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);
   // Utility since not all cases need to do any post processing of the filtered
   // response.
-  void BeginResponse(InternalResponse* aResponse);
   nsresult FailWithNetworkError();
-  nsresult SucceedWithResponse();
-  nsresult DoesNotRequirePreflight(nsIChannel* aChannel);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FetchDriver_h
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -255,26 +255,16 @@ Request::Constructor(const GlobalObject&
     }
 
     request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
     fallbackMode = RequestMode::Cors;
     fallbackCredentials = RequestCredentials::Omit;
     fallbackCache = RequestCache::Default;
   }
 
-  // CORS-with-forced-preflight is not publicly exposed and should not be
-  // considered a valid value.
-  if (aInit.mMode.WasPassed() &&
-      aInit.mMode.Value() == RequestMode::Cors_with_forced_preflight) {
-    NS_NAMED_LITERAL_STRING(sourceDescription, "'mode' member of RequestInit");
-    NS_NAMED_LITERAL_STRING(value, "cors-with-forced-preflight");
-    NS_NAMED_LITERAL_STRING(type, "RequestMode");
-    aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>(&sourceDescription, &value, &type);
-    return nullptr;
-  }
   RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
   RequestCredentials credentials =
     aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()
                                    : fallbackCredentials;
 
   if (mode != RequestMode::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetMode(mode);
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -53,19 +53,16 @@ public:
   GetMethod(nsCString& aMethod) const
   {
     aMethod = mRequest->mMethod;
   }
 
   RequestMode
   Mode() const
   {
-    if (mRequest->mMode == RequestMode::Cors_with_forced_preflight) {
-      return RequestMode::Cors;
-    }
     return mRequest->mMode;
   }
 
   RequestCredentials
   Credentials() const
   {
     return mRequest->mCredentialsMode;
   }
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2217,31 +2217,40 @@ HTMLMediaElement::ResetConnectionState()
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
   ChangeDelayLoadStatus(false);
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
 }
 
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
+  nsresult rv = PlayInternal(nsContentUtils::IsCallerChrome());
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+nsresult
+HTMLMediaElement::PlayInternal(bool aCallerIsChrome)
+{
   // Prevent media element from being auto-started by a script when
   // media.autoplay.enabled=false
   if (!mHasUserInteraction
       && !IsAutoplayEnabled()
       && !EventStateManager::IsHandlingUserInput()
-      && !nsContentUtils::IsCallerChrome()) {
+      && !aCallerIsChrome) {
     LOG(LogLevel::Debug, ("%p Blocked attempt to autoplay media.", this));
 #if defined(MOZ_WIDGET_ANDROID)
     nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
                                          static_cast<nsIContent*>(this),
                                          NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
                                          false,
                                          false);
 #endif
-    return;
+    return NS_OK;
   }
 
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
@@ -2252,29 +2261,29 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     ResumeLoad(PRELOAD_ENOUGH);
   }
 
   if (Preferences::GetBool("media.block-play-until-visible", false) &&
       !nsContentUtils::IsCallerChrome() &&
       OwnerDoc()->Hidden()) {
     LOG(LogLevel::Debug, ("%p Blocked playback because owner hidden.", this));
     mPlayBlockedBecauseHidden = true;
-    return;
+    return NS_OK;
   }
 
   // Even if we just did Load() or ResumeLoad(), we could already have a decoder
   // here if we managed to clone an existing decoder.
   if (mDecoder) {
     if (mDecoder->IsEndedOrShutdown()) {
       SetCurrentTime(0);
     }
     if (!mPausedForInactiveDocumentOrChannel) {
-      aRv = mDecoder->Play();
-      if (aRv.Failed()) {
-        return;
+      nsresult rv = mDecoder->Play();
+      if (NS_FAILED(rv)) {
+        return rv;
       }
     }
   }
 
   if (mCurrentPlayRangeStart == -1.0) {
     mCurrentPlayRangeStart = CurrentTime();
   }
 
@@ -2300,23 +2309,23 @@ HTMLMediaElement::Play(ErrorResult& aRv)
 
   mPaused = false;
   mAutoplaying = false;
   // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
   // and our preload status.
   AddRemoveSelfReference();
   UpdatePreloadAction();
   UpdateSrcMediaStreamPlaying();
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP HTMLMediaElement::Play()
 {
-  ErrorResult rv;
-  Play(rv);
-  return rv.StealNSResult();
+  return PlayInternal(/* aCallerIsChrome = */ true);
 }
 
 HTMLMediaElement::WakeLockBoolWrapper&
 HTMLMediaElement::WakeLockBoolWrapper::operator=(bool val)
 {
   if (mValue == val) {
     return *this;
   }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -706,16 +706,18 @@ protected:
     void UpdateWakeLock();
 
     bool mValue;
     bool mCanPlay;
     HTMLMediaElement* mOuter;
     nsCOMPtr<nsITimer> mTimer;
   };
 
+  nsresult PlayInternal(bool aCallerIsChrome);
+
   /** Use this method to change the mReadyState member, so required
    * events can be fired.
    */
   void ChangeReadyState(nsMediaReadyState aState);
 
   /**
    * Use this method to change the mNetworkState member, so required
    * actions will be taken during the transition.
--- a/dom/imptests/failures/html/dom/test_historical.html.json
+++ b/dom/imptests/failures/html/dom/test_historical.html.json
@@ -1,11 +1,10 @@
 {
   "Historical DOM features must be removed: CDATASection": true,
   "Historical DOM features must be removed: createCDATASection": true,
   "Historical DOM features must be removed: createAttribute": true,
   "Historical DOM features must be removed: createAttributeNS": true,
   "Historical DOM features must be removed: getAttributeNode": true,
   "Historical DOM features must be removed: getAttributeNodeNS": true,
   "Historical DOM features must be removed: setAttributeNode": true,
-  "Historical DOM features must be removed: removeAttributeNode": true,
-  "DocumentType member must be nuked: internalSubset": true
+  "Historical DOM features must be removed: removeAttributeNode": true
 }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -481,28 +481,28 @@ TabParent::ShouldSwitchProcess(nsIChanne
   nsCOMPtr<nsIPrincipal> resultPrincipal;
   nsContentUtils::GetSecurityManager()->
     GetChannelResultPrincipal(aChannel, getter_AddRefs(resultPrincipal));
 
   // Log the debug info which is used to decide the need of proces switch.
   nsCOMPtr<nsIURI> uri;
   aChannel->GetURI(getter_AddRefs(uri));
   LogChannelRelevantInfo(uri, loadingPrincipal, resultPrincipal,
-                         loadInfo->GetContentPolicyType());
+                         loadInfo->InternalContentPolicyType());
 
   // Check if the signed package is loaded from the same origin.
   bool sameOrigin = false;
   loadingPrincipal->Equals(resultPrincipal, &sameOrigin);
   if (sameOrigin) {
     LOG("Loading singed package from the same origin. Don't switch process.\n");
     return false;
   }
 
   // If this is not a top level document, there's no need to switch process.
-  if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->GetContentPolicyType()) {
+  if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->InternalContentPolicyType()) {
     LOG("Subresource of a document. No need to switch process.\n");
     return false;
   }
 
   // If this is a brand new process created to load the signed package
   // (triggered by previous OnStartSignedPackageRequest), the loading origin
   // will be "moz-safe-about:blank". In that case, we don't need to switch process
   // again. We compare with "moz-safe-about:blank" without appId/isBrowserElement/etc
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -136,18 +136,17 @@ NS_IMPL_ISUPPORTS(MediaMemoryTracker, ns
 
 NS_IMPL_ISUPPORTS0(MediaDecoder)
 
 void
 MediaDecoder::NotifyOwnerActivityChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mOwner) {
-    NS_WARNING("MediaDecoder without a decoder owner, can't update dormant");
+  if (mShuttingDown) {
     return;
   }
 
   UpdateDormantState(false /* aDormantTimeout */, false /* aActivity */);
   // Start dormant timer if necessary
   StartDormantTimer();
 }
 
@@ -165,17 +164,18 @@ MediaDecoder::IsHeuristicDormantSupporte
     mIsHeuristicDormantSupported;
 }
 
 void
 MediaDecoder::UpdateDormantState(bool aDormantTimeout, bool aActivity)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mDecoderStateMachine ||
+  if (mShuttingDown ||
+      !mDecoderStateMachine ||
       mPlayState == PLAY_STATE_SHUTDOWN ||
       !mOwner->GetVideoFrameContainer() ||
       (mOwner->GetMediaElement() && mOwner->GetMediaElement()->IsBeingDestroyed()) ||
       !mDormantSupported)
   {
     return;
   }
 
@@ -251,17 +251,16 @@ MediaDecoder::StartDormantTimer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!IsHeuristicDormantSupported()) {
     return;
   }
 
   if (mIsHeuristicDormant ||
       mShuttingDown ||
-      !mOwner ||
       !mOwner->IsHidden() ||
       (mPlayState != PLAY_STATE_PAUSED &&
        !IsEnded()))
   {
     return;
   }
 
   if (!mDormantTimer) {
@@ -473,18 +472,16 @@ MediaDecoder::Shutdown()
   if (mResource) {
     mResource->Close();
   }
 
   CancelDormantTimer();
 
   ChangeState(PLAY_STATE_SHUTDOWN);
 
-  mOwner = nullptr;
-
   MediaShutdownManager::Instance().Unregister(this);
 }
 
 MediaDecoder::~MediaDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::RemoveMediaDecoder(this);
   UnpinForSeek();
@@ -594,22 +591,18 @@ MediaDecoder::Seek(double aTime, SeekTar
   mLogicalPosition = aTime;
   mWasEndedWhenEnteredDormant = false;
 
   mLogicallySeeking = true;
   SeekTarget target = SeekTarget(timeUsecs, aSeekType);
   CallSeek(target);
 
   if (mPlayState == PLAY_STATE_ENDED) {
-    bool paused = false;
-    if (mOwner) {
-      paused = mOwner->GetPaused();
-    }
     PinForSeek();
-    ChangeState(paused ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING);
+    ChangeState(mOwner->GetPaused() ? PLAY_STATE_PAUSED : PLAY_STATE_PLAYING);
   }
   return NS_OK;
 }
 
 void
 MediaDecoder::CallSeek(const SeekTarget& aTarget)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -659,24 +652,22 @@ MediaDecoder::MetadataLoaded(nsAutoPtr<M
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo());
 
   mInfo = aInfo.forget();
   ConstructMediaTracks();
 
-  if (mOwner) {
-    // Make sure the element and the frame (if any) are told about
-    // our new size.
-    Invalidate();
-    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-      mFiredMetadataLoaded = true;
-      mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
-    }
+  // Make sure the element and the frame (if any) are told about
+  // our new size.
+  Invalidate();
+  if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+    mFiredMetadataLoaded = true;
+    mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
   }
 }
 
 const char*
 MediaDecoder::PlayStateStr()
 {
   MOZ_ASSERT(NS_IsMainThread());
   switch (mPlayState) {
@@ -701,21 +692,19 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr
   }
 
   DECODER_LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d mPlayState=%s mIsDormant=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo(), PlayStateStr(), mIsDormant);
 
   mInfo = aInfo.forget();
 
-  if (mOwner) {
-    Invalidate();
-    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-      mOwner->FirstFrameLoaded();
-    }
+  Invalidate();
+  if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+    mOwner->FirstFrameLoaded();
   }
 
   // This can run cache callbacks.
   mResource->EnsureCacheUpToDate();
 
   // The element can run javascript via events
   // before reaching here, so only change the
   // state if we're still set to the original
@@ -731,50 +720,44 @@ MediaDecoder::FirstFrameLoaded(nsAutoPtr
 
 void
 MediaDecoder::ResetConnectionState()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
-  if (mOwner) {
-    // Notify the media element that connection gets lost.
-    mOwner->ResetConnectionState();
-  }
+  // Notify the media element that connection gets lost.
+  mOwner->ResetConnectionState();
 
   // Since we have notified the media element the connection
   // lost event, the decoder will be reloaded when user tries
   // to play the Rtsp streaming next time.
   Shutdown();
 }
 
 void
 MediaDecoder::NetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
-  if (mOwner)
-    mOwner->NetworkError();
-
+  mOwner->NetworkError();
   Shutdown();
 }
 
 void
 MediaDecoder::DecodeError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
-  if (mOwner)
-    mOwner->DecodeError();
-
+  mOwner->DecodeError();
   Shutdown();
 }
 
 void
 MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSameOriginMedia = aSameOrigin;
@@ -810,20 +793,17 @@ MediaDecoder::PlaybackEnded()
   if (mShuttingDown ||
       mLogicallySeeking ||
       mPlayState == PLAY_STATE_LOADING) {
     return;
   }
 
   ChangeState(PLAY_STATE_ENDED);
   InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE);
-
-  if (mOwner)  {
-    mOwner->PlaybackEnded();
-  }
+  mOwner->PlaybackEnded();
 
   // This must be called after |mOwner->PlaybackEnded()| call above, in order
   // to fire the required durationchange.
   if (IsInfinite()) {
     SetInfinite(false);
   }
 }
 
@@ -882,44 +862,50 @@ MediaDecoder::UpdatePlaybackRate()
 
   mResource->SetPlaybackRate(rate);
 }
 
 void
 MediaDecoder::NotifySuspendedStatusChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (mResource && mOwner) {
+  if (mShuttingDown) {
+    return;
+  }
+  if (mResource) {
     bool suspended = mResource->IsSuspendedByCache();
     mOwner->NotifySuspendedByCache(suspended);
   }
 }
 
 void
 MediaDecoder::NotifyBytesDownloaded()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  if (mShuttingDown) {
+    return;
+  }
   UpdatePlaybackRate();
-  if (mOwner) {
-    mOwner->DownloadProgressed();
-  }
+  mOwner->DownloadProgressed();
 }
 
 void
 MediaDecoder::NotifyDownloadEnded(nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mShuttingDown) {
+    return;
+  }
+
   DECODER_LOG("NotifyDownloadEnded, status=%x", aStatus);
 
   if (aStatus == NS_BINDING_ABORTED) {
     // Download has been cancelled by user.
-    if (mOwner) {
-      mOwner->LoadAborted();
-    }
+    mOwner->LoadAborted();
     return;
   }
 
   UpdatePlaybackRate();
 
   if (NS_SUCCEEDED(aStatus)) {
     // A final progress event will be fired by the MediaResource calling
     // DownloadSuspended on the element.
@@ -929,19 +915,20 @@ MediaDecoder::NotifyDownloadEnded(nsresu
     NetworkError();
   }
 }
 
 void
 MediaDecoder::NotifyPrincipalChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (mOwner) {
-    mOwner->NotifyDecoderPrincipalChanged();
+  if (mShuttingDown) {
+    return;
   }
+  mOwner->NotifyDecoderPrincipalChanged();
 }
 
 void
 MediaDecoder::NotifyBytesConsumed(int64_t aBytes, int64_t aOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mShuttingDown || mIgnoreProgressData) {
@@ -973,37 +960,33 @@ MediaDecoder::OnSeekResolved(SeekResolve
     if (aVal.mAtEnd) {
       ChangeState(PLAY_STATE_ENDED);
     }
     mLogicallySeeking = false;
   }
 
   UpdateLogicalPosition(aVal.mEventVisibility);
 
-  if (mOwner) {
-    if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-      mOwner->SeekCompleted();
-      if (fireEnded) {
-        mOwner->PlaybackEnded();
-      }
+  if (aVal.mEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+    mOwner->SeekCompleted();
+    if (fireEnded) {
+      mOwner->PlaybackEnded();
     }
   }
 }
 
 void
 MediaDecoder::SeekingStarted(MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
-  if (mOwner) {
-    if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
-      mOwner->SeekStarted();
-    }
+  if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
+    mOwner->SeekStarted();
   }
 }
 
 void
 MediaDecoder::ChangeState(PlayState aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1047,27 +1030,31 @@ MediaDecoder::UpdateLogicalPosition(Medi
   mLogicalPosition = currentPosition;
 
   // Invalidate the frame so any video data is displayed.
   // Do this before the timeupdate event so that if that
   // event runs JavaScript that queries the media size, the
   // frame has reflowed and the size updated beforehand.
   Invalidate();
 
-  if (mOwner && logicalPositionChanged &&
+  if (logicalPositionChanged &&
       aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     FireTimeUpdate();
   }
 }
 
 void
 MediaDecoder::DurationChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mShuttingDown) {
+    return;
+  }
+
   double oldDuration = mDuration;
   if (IsInfinite()) {
     mDuration = std::numeric_limits<double>::infinity();
   } else if (mExplicitDuration.Ref().isSome()) {
     mDuration = mExplicitDuration.Ref().ref();
   } else if (mStateMachineDuration.Ref().isSome()) {
     mDuration = mStateMachineDuration.Ref().ref().ToSeconds();
   }
@@ -1078,17 +1065,17 @@ MediaDecoder::DurationChanged()
 
   DECODER_LOG("Duration changed to %f", mDuration);
 
   // Duration has changed so we should recompute playback rate
   UpdatePlaybackRate();
 
   // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=28822 for a discussion
   // of whether we should fire durationchange on explicit infinity.
-  if (mOwner && mFiredMetadataLoaded &&
+  if (mFiredMetadataLoaded &&
       (!mozilla::IsInfinite<double>(mDuration) || mExplicitDuration.Ref().isSome())) {
     mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   }
 
   if (CurrentPosition() > TimeUnit::FromSeconds(mDuration).ToMicroseconds()) {
     Seek(mDuration, SeekTarget::Accurate);
   }
 }
@@ -1210,17 +1197,17 @@ MediaDecoder::SetPlaybackRate(double aPl
   if (mPlaybackRate == 0.0) {
     mPausedForPlaybackRateNull = true;
     Pause();
   } else if (mPausedForPlaybackRateNull) {
     // Play() uses mPausedForPlaybackRateNull value, so must reset it first
     mPausedForPlaybackRateNull = false;
     // If the playbackRate is no longer null, restart the playback, iff the
     // media was playing.
-    if (mOwner && !mOwner->GetPaused()) {
+    if (!mOwner->GetPaused()) {
       Play();
     }
   }
 }
 
 void
 MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
 {
@@ -1341,18 +1328,19 @@ MediaDecoder::GetMediaOwner() const
 {
   return mOwner;
 }
 
 void
 MediaDecoder::FireTimeUpdate()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!mOwner)
+  if (mShuttingDown) {
     return;
+  }
   mOwner->FireTimeUpdate(true);
 }
 
 void
 MediaDecoder::PinForSeek()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaResource* resource = GetResource();
@@ -1566,21 +1554,17 @@ MediaDecoder::GetOwner()
   return mOwner;
 }
 
 void
 MediaDecoder::ConstructMediaTracks()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mMediaTracksConstructed) {
-    return;
-  }
-
-  if (!mOwner || !mInfo) {
+  if (mShuttingDown || mMediaTracksConstructed || !mInfo) {
     return;
   }
 
   HTMLMediaElement* element = mOwner->GetMediaElement();
   if (!element) {
     return;
   }
 
@@ -1606,17 +1590,17 @@ MediaDecoder::ConstructMediaTracks()
   }
 }
 
 void
 MediaDecoder::RemoveMediaTracks()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mOwner) {
+  if (mShuttingDown) {
     return;
   }
 
   HTMLMediaElement* element = mOwner->GetMediaElement();
   if (!element) {
     return;
   }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -745,17 +745,18 @@ private:
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) override
   {
     GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded, aDropped);
   }
 
   void UpdateReadyState()
   {
-    if (mOwner) {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!mShuttingDown) {
       mOwner->UpdateReadyState();
     }
   }
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() { return mNextFrameStatus; }
 
 protected:
   virtual ~MediaDecoder();
@@ -863,17 +864,17 @@ protected:
 
   const char* PlayStateStr();
 
   void OnMetadataUpdate(TimedMetadata&& aMetadata);
 
   // This should only ever be accessed from the main thread.
   // It is set in Init and cleared in Shutdown when the element goes away.
   // The decoder does not add a reference the element.
-  MediaDecoderOwner* mOwner;
+  MediaDecoderOwner* const mOwner;
 
   // Counters related to decode and presentation of frames.
   FrameStatistics mFrameStats;
 
   RefPtr<VideoFrameContainer> mVideoFrameContainer;
 
   // Data needed to estimate playback data rate. The timeline used for
   // this estimate is "decode time" (where the "current time" is the
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -99,17 +99,16 @@ MediaDecoderReader::InitializationTask()
 
   // Initialize watchers.
   mWatchManager.Watch(mDuration, &MediaDecoderReader::UpdateBuffered);
 }
 
 MediaDecoderReader::~MediaDecoderReader()
 {
   MOZ_ASSERT(mShutdown);
-  ResetDecode();
   MOZ_COUNT_DTOR(MediaDecoderReader);
 }
 
 size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const
 {
   VideoQueueMemoryFunctor functor;
   mVideoQueue.LockedForEach(functor);
   return functor.mSize;
@@ -245,22 +244,22 @@ MediaDecoderReader::GetBuffered()
   if (!mDuration.Ref().isSome()) {
     return TimeIntervals();
   }
 
   return GetEstimatedBufferedTimeRanges(stream, mDuration.Ref().ref().ToMicroseconds());
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-MediaDecoderReader::AsyncReadMetadata()
+MediaDecoderReader::AsyncReadMetadataInternal()
 {
   typedef ReadMetadataFailureReason Reason;
 
   MOZ_ASSERT(OnTaskQueue());
-  DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
+  DECODER_LOG("MediaDecoderReader::AsyncReadMetadataInternal");
 
   // Attempt to read the metadata.
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
   nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags));
 
   // We're not waiting for anything. If we didn't get the metadata, that's an
   // error.
   if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) {
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -89,16 +89,24 @@ public:
   // between the two (even though in practice, Init will always run first right
   // now thanks to the tail dispatcher).
   void InitializationTask();
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init() { return NS_OK; }
 
+  RefPtr<MetadataPromise> AsyncReadMetadata()
+  {
+    return OnTaskQueue() ?
+           AsyncReadMetadataInternal() :
+           InvokeAsync(OwnerThread(), this, __func__,
+                       &MediaDecoderReader::AsyncReadMetadataInternal);
+  }
+
   // Release media resources they should be released in dormant state
   // The reader can be made usable again by calling ReadMetadata().
   void ReleaseMediaResources()
   {
     if (OnTaskQueue()) {
       ReleaseMediaResourcesInternal();
       return;
     }
@@ -172,28 +180,16 @@ public:
   // in buffering mode. Some readers support a promise-based mechanism by which
   // they notify the state machine when the data arrives.
   virtual bool IsWaitForDataSupported() { return false; }
   virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
-  // The default implementation of AsyncReadMetadata is implemented in terms of
-  // synchronous ReadMetadata() calls. Implementations may also
-  // override AsyncReadMetadata to create a more proper async implementation.
-  virtual RefPtr<MetadataPromise> AsyncReadMetadata();
-
-  // Read header data for all bitstreams in the file. Fills aInfo with
-  // the data required to present the media, and optionally fills *aTags
-  // with tag metadata from the file.
-  // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) { MOZ_CRASH(); }
-
   // Fills aInfo with the latest cached data required to present the media,
   // ReadUpdatedMetadata will always be called once ReadMetadata has succeeded.
   virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { };
 
   // Moves the decode head to aTime microseconds. aEndTime denotes the end
   // time of the media in usecs. This is only needed for OggReader, and should
   // probably be removed somehow.
   virtual RefPtr<SeekPromise>
@@ -263,16 +259,28 @@ public:
 
   virtual size_t SizeOfVideoQueueInFrames();
   virtual size_t SizeOfAudioQueueInFrames();
 
 private:
   virtual void ReleaseMediaResourcesInternal() {}
   virtual void DisableHardwareAccelerationInternal() {}
 
+  // Read header data for all bitstreams in the file. Fills aInfo with
+  // the data required to present the media, and optionally fills *aTags
+  // with tag metadata from the file.
+  // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
+  virtual nsresult ReadMetadata(MediaInfo*, MetadataTags**) { MOZ_CRASH(); }
+
+  // The default implementation of AsyncReadMetadataInternal is implemented in
+  // terms of synchronous ReadMetadata() calls. Implementations may also
+  // override AsyncReadMetadataInternal to create a more proper async
+  // implementation.
+  virtual RefPtr<MetadataPromise> AsyncReadMetadataInternal();
+
 protected:
   friend class TrackBuffer;
   virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) { }
 
   void NotifyDataArrived(const media::Interval<int64_t>& aInfo)
   {
     MOZ_ASSERT(OnTaskQueue());
     NS_ENSURE_TRUE_VOID(!mShutdown);
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -12,16 +12,17 @@
 
 #include <algorithm>
 #include <stdint.h>
 
 #include "gfx2DGlue.h"
 
 #include "mediasink/DecodedAudioDataSink.h"
 #include "mediasink/AudioSinkWrapper.h"
+#include "mediasink/VideoSink.h"
 #include "mediasink/DecodedStream.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Logging.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/TaskQueue.h"
@@ -197,27 +198,25 @@ static void InitVideoQueuePrefs() {
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
   mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
                            /* aSupportsTailDispatch = */ true)),
   mWatchManager(this, mTaskQueue),
-  mProducerID(ImageContainer::AllocateProducerID()),
   mRealTime(aRealTime),
   mDispatchedStateMachine(false),
   mDelayedScheduler(mTaskQueue),
   mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
   mCurrentFrameID(0),
   mObservedDuration(TimeUnit(), "MediaDecoderStateMachine::mObservedDuration"),
   mFragmentEndTime(-1),
   mReader(aReader),
   mDecodedAudioEndTime(-1),
-  mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
   mPlaybackRate(1.0),
   mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS),
   mAmpleAudioThresholdUsecs(detail::AMPLE_AUDIO_USECS),
   mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS),
   mIsAudioPrerolling(false),
   mIsVideoPrerolling(false),
   mAudioCaptured(false, "MediaDecoderStateMachine::mAudioCaptured"),
@@ -300,17 +299,17 @@ MediaDecoderStateMachine::MediaDecoderSt
 
   mAudioQueueListener = AudioQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
   mVideoQueueListener = VideoQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
 
   mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
 
-  mMediaSink = CreateAudioSink();
+  mMediaSink = CreateMediaSink(mAudioCaptured);
 
 #ifdef MOZ_EME
   mCDMProxyPromise.Begin(mDecoder->RequestCDMProxy()->Then(
     OwnerThread(), __func__, this,
     &MediaDecoderStateMachine::OnCDMProxyReady,
     &MediaDecoderStateMachine::OnCDMProxyNotReady));
 #endif
 }
@@ -376,16 +375,33 @@ MediaDecoderStateMachine::CreateAudioSin
     MOZ_ASSERT(self->OnTaskQueue());
     return new DecodedAudioDataSink(
       self->mAudioQueue, self->GetMediaTime(),
       self->mInfo.mAudio, self->mDecoder->GetAudioChannel());
   };
   return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
 }
 
+already_AddRefed<media::MediaSink>
+MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured)
+{
+  // TODO: We can't really create a new DecodedStream until OutputStreamManager
+  //       is extracted. It is tricky that the implementation of DecodedStream
+  //       happens to allow reuse after shutdown without creating a new one.
+  RefPtr<media::MediaSink> audioSink = aAudioCaptured ?
+    mStreamSink : CreateAudioSink();
+
+  RefPtr<media::MediaSink> mediaSink =
+    new VideoSink(mTaskQueue, audioSink, mVideoQueue,
+                  mDecoder->GetVideoFrameContainer(), mRealTime,
+                  mDecoder->GetFrameStatistics(), AUDIO_DURATION_USECS,
+                  sVideoQueueSendToCompositorSize);
+  return mediaSink.forget();
+}
+
 bool MediaDecoderStateMachine::HasFutureAudio()
 {
   MOZ_ASSERT(OnTaskQueue());
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
   // We've got audio ready to play if:
   // 1. We've not completed playback of audio, and
   // 2. we either have more than the threshold of decoded audio available, or
   //    we've completely decoded all audio (but not finished playing it yet
@@ -1045,17 +1061,16 @@ nsresult MediaDecoderStateMachine::Init(
 void MediaDecoderStateMachine::StopPlayback()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("StopPlayback()");
 
   mDecoder->DispatchPlaybackStopped();
 
   if (IsPlaying()) {
-    RenderVideoFrames(1);
     mMediaSink->SetPlaying(false);
     MOZ_ASSERT(!IsPlaying());
   }
 
   DispatchDecodeTasksIfNeeded();
 }
 
 void MediaDecoderStateMachine::MaybeStartPlayback()
@@ -1484,17 +1499,18 @@ MediaDecoderStateMachine::InvokeSeek(See
 }
 
 void MediaDecoderStateMachine::StopMediaSink()
 {
   MOZ_ASSERT(OnTaskQueue());
   if (mMediaSink->IsStarted()) {
     DECODER_LOG("Stop MediaSink");
     mMediaSink->Stop();
-    mMediaSinkPromise.DisconnectIfExists();
+    mMediaSinkAudioPromise.DisconnectIfExists();
+    mMediaSinkVideoPromise.DisconnectIfExists();
   }
 }
 
 void
 MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
 {
   MOZ_ASSERT(OnTaskQueue());
 
@@ -1759,22 +1775,30 @@ MediaDecoderStateMachine::RequestVideoDa
 void
 MediaDecoderStateMachine::StartMediaSink()
 {
   MOZ_ASSERT(OnTaskQueue());
   if (!mMediaSink->IsStarted()) {
     mAudioCompleted = false;
     mMediaSink->Start(GetMediaTime(), mInfo);
 
-    auto promise = mMediaSink->OnEnded(TrackInfo::kAudioTrack);
-    if (promise) {
-      mMediaSinkPromise.Begin(promise->Then(
+    auto videoPromise = mMediaSink->OnEnded(TrackInfo::kVideoTrack);
+    auto audioPromise = mMediaSink->OnEnded(TrackInfo::kAudioTrack);
+
+    if (audioPromise) {
+      mMediaSinkAudioPromise.Begin(audioPromise->Then(
         OwnerThread(), __func__, this,
-        &MediaDecoderStateMachine::OnMediaSinkComplete,
-        &MediaDecoderStateMachine::OnMediaSinkError));
+        &MediaDecoderStateMachine::OnMediaSinkAudioComplete,
+        &MediaDecoderStateMachine::OnMediaSinkAudioError));
+    }
+    if (videoPromise) {
+      mMediaSinkVideoPromise.Begin(videoPromise->Then(
+        OwnerThread(), __func__, this,
+        &MediaDecoderStateMachine::OnMediaSinkVideoComplete,
+        &MediaDecoderStateMachine::OnMediaSinkVideoError));
     }
   }
 }
 
 int64_t MediaDecoderStateMachine::AudioDecodedUsecs()
 {
   MOZ_ASSERT(OnTaskQueue());
   NS_ASSERTION(HasAudio(),
@@ -1827,17 +1851,17 @@ bool MediaDecoderStateMachine::HasLowUnd
   }
 
   if (mBuffered.Ref().IsInvalid()) {
     return false;
   }
 
   int64_t endOfDecodedVideoData = INT64_MAX;
   if (HasVideo() && !VideoQueue().AtEndOfStream()) {
-    endOfDecodedVideoData = VideoQueue().Peek() ? VideoQueue().Peek()->GetEndTime() : mVideoFrameEndTime;
+    endOfDecodedVideoData = VideoQueue().Peek() ? VideoQueue().Peek()->GetEndTime() : VideoEndTime();
   }
   int64_t endOfDecodedAudioData = INT64_MAX;
   if (HasAudio() && !AudioQueue().AtEndOfStream()) {
     // mDecodedAudioEndTime could be -1 when no audio samples are decoded.
     // But that is fine since we consider ourself as low in decoded data when
     // we don't have any decoded audio samples at all.
     endOfDecodedAudioData = mDecodedAudioEndTime;
   }
@@ -2027,17 +2051,17 @@ MediaDecoderStateMachine::AdjustAudioThr
 
 void
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("FinishDecodeFirstFrame");
 
   if (!IsRealTime() && !mSentFirstFrameLoadedEvent) {
-    RenderVideoFrames(1);
+    mMediaSink->Redraw();
   }
 
   // If we don't know the duration by this point, we assume infinity, per spec.
   if (mDuration.Ref().isNothing()) {
     mDuration = Some(TimeUnit::FromInfinity());
   }
 
   DECODER_LOG("Media duration %lld, "
@@ -2124,17 +2148,17 @@ MediaDecoderStateMachine::SeekCompleted(
   // seek while quick-buffering, we won't bypass quick buffering mode
   // if we need to buffer after the seek.
   mQuickBuffering = false;
 
   mCurrentSeek.Resolve(mState == DECODER_STATE_COMPLETED, __func__);
   ScheduleStateMachine();
 
   if (video) {
-    RenderVideoFrames(1);
+    mMediaSink->Redraw();
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
     AbstractThread::MainThread()->Dispatch(event.forget());
   }
 }
 
 class DecoderDisposer
 {
@@ -2251,21 +2275,20 @@ nsresult MediaDecoderStateMachine::RunSt
     case DECODER_STATE_DECODING_NONE: {
       SetState(DECODER_STATE_DECODING_METADATA);
       ScheduleStateMachine();
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       if (!mMetadataRequest.Exists()) {
-        DECODER_LOG("Dispatching AsyncReadMetadata");
+        DECODER_LOG("Calling AsyncReadMetadata");
         // Set mode to METADATA since we are about to read metadata.
         mResource->SetReadMode(MediaCacheStream::MODE_METADATA);
-        mMetadataRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
-                                           &MediaDecoderReader::AsyncReadMetadata)
+        mMetadataRequest.Begin(mReader->AsyncReadMetadata()
           ->Then(OwnerThread(), __func__, this,
                  &MediaDecoderStateMachine::OnMetadataRead,
                  &MediaDecoderStateMachine::OnMetadataNotRead));
 
       }
       return NS_OK;
     }
 
@@ -2280,17 +2303,17 @@ nsresult MediaDecoderStateMachine::RunSt
         // We're playing, but the element/decoder is in paused state. Stop
         // playing!
         StopPlayback();
       }
 
       // Start playback if necessary so that the clock can be properly queried.
       MaybeStartPlayback();
 
-      UpdateRenderedVideoFrames();
+      UpdatePlaybackPositionPeriodically();
       NS_ASSERTION(!IsPlaying() ||
                    mLogicallySeeking ||
                    IsStateMachineScheduled(),
                    "Must have timer scheduled");
       return NS_OK;
     }
 
     case DECODER_STATE_BUFFERING: {
@@ -2351,17 +2374,17 @@ nsresult MediaDecoderStateMachine::RunSt
       // once to ensure the current playback position is advanced to the
       // end of the media, and so that we update the readyState.
       if (VideoQueue().GetSize() > 1 ||
           (HasAudio() && !mAudioCompleted) ||
           (mAudioCaptured && !mStreamSink->IsFinished()))
       {
         // Start playback if necessary to play the remaining media.
         MaybeStartPlayback();
-        UpdateRenderedVideoFrames();
+        UpdatePlaybackPositionPeriodically();
         NS_ASSERTION(!IsPlaying() ||
                      mLogicallySeeking ||
                      IsStateMachineScheduled(),
                      "Must have timer scheduled");
         return NS_OK;
       }
 
       // StopPlayback in order to reset the IsPlaying() state so audio
@@ -2373,17 +2396,17 @@ nsresult MediaDecoderStateMachine::RunSt
         // our state should have scheduled another state machine run.
         NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
         return NS_OK;
       }
 
       if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
           !mSentPlaybackEndedEvent)
       {
-        int64_t clockTime = std::max(AudioEndTime(), mVideoFrameEndTime);
+        int64_t clockTime = std::max(AudioEndTime(), VideoEndTime());
         clockTime = std::max(int64_t(0), std::max(clockTime, Duration().ToMicroseconds()));
         UpdatePlaybackPosition(clockTime);
 
         nsCOMPtr<nsIRunnable> event =
           NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
         AbstractThread::MainThread()->Dispatch(event.forget());
 
         mSentPlaybackEndedEvent = true;
@@ -2414,17 +2437,16 @@ MediaDecoderStateMachine::Reset()
              mState == DECODER_STATE_DORMANT ||
              mState == DECODER_STATE_DECODING_NONE);
 
   // Stop the audio thread. Otherwise, MediaSink might be accessing AudioQueue
   // outside of the decoder monitor while we are clearing the queue and causes
   // crash for no samples to be popped.
   StopMediaSink();
 
-  mVideoFrameEndTime = -1;
   mDecodedVideoEndTime = -1;
   mDecodedAudioEndTime = -1;
   mAudioCompleted = false;
   AudioQueue().Reset();
   VideoQueue().Reset();
   mFirstVideoFrameAfterSeek = nullptr;
   mDropAudioUntilNextDiscontinuity = true;
   mDropVideoUntilNextDiscontinuity = true;
@@ -2464,153 +2486,63 @@ MediaDecoderStateMachine::CheckFrameVali
         mCorruptFrames.clear();
       gfxCriticalNote << "Too many dropped/corrupted frames, disabling DXVA";
     }
   } else {
     mCorruptFrames.insert(0);
   }
 }
 
-void MediaDecoderStateMachine::RenderVideoFrames(int32_t aMaxFrames,
-                                                 int64_t aClockTime,
-                                                 const TimeStamp& aClockTimeStamp)
-{
-  MOZ_ASSERT(OnTaskQueue());
-
-  VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
-  nsAutoTArray<RefPtr<MediaData>,16> frames;
-  VideoQueue().GetFirstElements(aMaxFrames, &frames);
-  if (frames.IsEmpty() || !container) {
-    return;
-  }
-
-  nsAutoTArray<ImageContainer::NonOwningImage,16> images;
-  TimeStamp lastFrameTime;
-  for (uint32_t i = 0; i < frames.Length(); ++i) {
-    VideoData* frame = frames[i]->As<VideoData>();
-
-    bool valid = !frame->mImage || frame->mImage->IsValid();
-    frame->mSentToCompositor = true;
-
-    if (!valid) {
-      continue;
-    }
-
-    int64_t frameTime = frame->mTime;
-    if (frameTime < 0) {
-      // Frame times before the start time are invalid; drop such frames
-      continue;
-    }
-
-
-    TimeStamp t;
-    if (aMaxFrames > 1) {
-      MOZ_ASSERT(!aClockTimeStamp.IsNull());
-      int64_t delta = frame->mTime - aClockTime;
-      t = aClockTimeStamp +
-          TimeDuration::FromMicroseconds(delta / mPlaybackRate);
-      if (!lastFrameTime.IsNull() && t <= lastFrameTime) {
-        // Timestamps out of order; drop the new frame. In theory we should
-        // probably replace the previous frame with the new frame if the
-        // timestamps are equal, but this is a corrupt video file already so
-        // never mind.
-        continue;
-      }
-      lastFrameTime = t;
-    }
-
-    ImageContainer::NonOwningImage* img = images.AppendElement();
-    img->mTimeStamp = t;
-    img->mImage = frame->mImage;
-    img->mFrameID = frame->mFrameID;
-    img->mProducerID = mProducerID;
-
-    VERBOSE_LOG("playing video frame %lld (id=%x) (queued=%i, state-machine=%i, decoder-queued=%i)",
-                frame->mTime, frame->mFrameID,
-                VideoQueue().GetSize() + mReader->SizeOfVideoQueueInFrames(),
-                VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames());
-  }
-
-  container->SetCurrentFrames(frames[0]->As<VideoData>()->mDisplay, images);
-}
-
 int64_t
 MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
 {
   MOZ_ASSERT(OnTaskQueue());
   int64_t clockTime = mMediaSink->GetPosition(aTimeStamp);
   NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
   return clockTime;
 }
 
-void MediaDecoderStateMachine::UpdateRenderedVideoFrames()
+void
+MediaDecoderStateMachine::UpdatePlaybackPositionPeriodically()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (!IsPlaying() || mLogicallySeeking) {
     return;
   }
 
   if (mAudioCaptured) {
     DiscardStreamData();
   }
 
-  TimeStamp nowTime;
-  const int64_t clockTime = GetClock(&nowTime);
-  // Skip frames up to the frame at the playback position, and figure out
-  // the time remaining until it's time to display the next frame and drop
-  // the current frame.
-  NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
-  int64_t remainingTime = AUDIO_DURATION_USECS;
-  if (VideoQueue().GetSize() > 0) {
-    RefPtr<MediaData> currentFrame = VideoQueue().PopFront();
-    int32_t framesRemoved = 0;
-    while (VideoQueue().GetSize() > 0) {
-      MediaData* nextFrame = VideoQueue().PeekFront();
-      if (!IsRealTime() && nextFrame->mTime > clockTime) {
-        remainingTime = nextFrame->mTime - clockTime;
-        break;
-      }
-      ++framesRemoved;
-      if (!currentFrame->As<VideoData>()->mSentToCompositor) {
-        mDecoder->NotifyDecodedFrames(0, 0, 1);
-        VERBOSE_LOG("discarding video frame mTime=%lld clock_time=%lld",
-                    currentFrame->mTime, clockTime);
-      }
-      currentFrame = VideoQueue().PopFront();
-
-    }
-    VideoQueue().PushFront(currentFrame);
-    if (framesRemoved > 0) {
-      mVideoFrameEndTime = currentFrame->GetEndTime();
-      FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
-      frameStats.NotifyPresentedFrame();
-    }
-  }
-
-  RenderVideoFrames(sVideoQueueSendToCompositorSize, clockTime, nowTime);
-
   // Cap the current time to the larger of the audio and video end time.
   // This ensures that if we're running off the system clock, we don't
   // advance the clock to after the media end time.
-  if (mVideoFrameEndTime != -1 || AudioEndTime() != -1) {
+  if (VideoEndTime() != -1 || AudioEndTime() != -1) {
+
+    const int64_t clockTime = GetClock();
+    // Skip frames up to the frame at the playback position, and figure out
+    // the time remaining until it's time to display the next frame and drop
+    // the current frame.
+    NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
+
     // These will be non -1 if we've displayed a video frame, or played an audio frame.
-    int64_t t = std::min(clockTime, std::max(mVideoFrameEndTime, AudioEndTime()));
+    int64_t t = std::min(clockTime, std::max(VideoEndTime(), AudioEndTime()));
     // FIXME: Bug 1091422 - chained ogg files hit this assertion.
     //MOZ_ASSERT(t >= GetMediaTime());
     if (t > GetMediaTime()) {
       UpdatePlaybackPosition(t);
     }
   }
   // Note we have to update playback position before releasing the monitor.
   // Otherwise, MediaDecoder::AddOutputStream could kick in when we are outside
   // the monitor and get a staled value from GetCurrentTimeUs() which hits the
   // assertion in GetClock().
 
-  int64_t delay = std::max<int64_t>(1, remainingTime / mPlaybackRate);
+  int64_t delay = std::max<int64_t>(1, AUDIO_DURATION_USECS / mPlaybackRate);
   ScheduleStateMachineIn(delay);
 }
 
 nsresult
 MediaDecoderStateMachine::DropVideoUpToSeekTarget(MediaData* aSample)
 {
   MOZ_ASSERT(OnTaskQueue());
   RefPtr<VideoData> video(aSample->As<VideoData>());
@@ -2903,32 +2835,67 @@ MediaDecoderStateMachine::AudioEndTime()
   MOZ_ASSERT(OnTaskQueue());
   if (mMediaSink->IsStarted()) {
     return mMediaSink->GetEndTime(TrackInfo::kAudioTrack);
   }
   MOZ_ASSERT(!HasAudio());
   return -1;
 }
 
-void MediaDecoderStateMachine::OnMediaSinkComplete()
+int64_t
+MediaDecoderStateMachine::VideoEndTime() const
+{
+  MOZ_ASSERT(OnTaskQueue());
+  if (mMediaSink->IsStarted()) {
+    return mMediaSink->GetEndTime(TrackInfo::kVideoTrack);
+  }
+  return -1;
+}
+
+void
+MediaDecoderStateMachine::OnMediaSinkVideoComplete()
 {
   MOZ_ASSERT(OnTaskQueue());
-
-  mMediaSinkPromise.Complete();
+  VERBOSE_LOG("[%s]", __func__);
+
+  mMediaSinkVideoPromise.Complete();
+  ScheduleStateMachine();
+}
+
+void
+MediaDecoderStateMachine::OnMediaSinkVideoError()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  VERBOSE_LOG("[%s]", __func__);
+
+  mMediaSinkVideoPromise.Complete();
+  if (HasAudio()) {
+    return;
+  }
+  DecodeError();
+}
+
+void MediaDecoderStateMachine::OnMediaSinkAudioComplete()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  VERBOSE_LOG("[%s]", __func__);
+
+  mMediaSinkAudioPromise.Complete();
   // Set true only when we have audio.
   mAudioCompleted = mInfo.HasAudio();
   // To notify PlaybackEnded as soon as possible.
   ScheduleStateMachine();
 }
 
-void MediaDecoderStateMachine::OnMediaSinkError()
+void MediaDecoderStateMachine::OnMediaSinkAudioError()
 {
   MOZ_ASSERT(OnTaskQueue());
-
-  mMediaSinkPromise.Complete();
+  VERBOSE_LOG("[%s]", __func__);
+
+  mMediaSinkAudioPromise.Complete();
   // Set true only when we have audio.
   mAudioCompleted = mInfo.HasAudio();
 
   // Make the best effort to continue playback when there is video.
   if (HasVideo()) {
     return;
   }
 
@@ -2970,20 +2937,17 @@ MediaDecoderStateMachine::SetAudioCaptur
   // Backup current playback parameters.
   MediaSink::PlaybackParams params = mMediaSink->GetPlaybackParams();
 
   // Stop and shut down the existing sink.
   StopMediaSink();
   mMediaSink->Shutdown();
 
   // Create a new sink according to whether audio is captured.
-  // TODO: We can't really create a new DecodedStream until OutputStreamManager
-  //       is extracted. It is tricky that the implementation of DecodedStream
-  //       happens to allow reuse after shutdown without creating a new one.
-  mMediaSink = aCaptured ? mStreamSink : CreateAudioSink();
+  mMediaSink = CreateMediaSink(aCaptured);
 
   // Restore playback parameters.
   mMediaSink->SetPlaybackParams(params);
 
   // We don't need to call StartMediaSink() here because IsPlaying() is now
   // always in sync with the playing state of MediaSink. It will be started in
   // MaybeStartPlayback() in the next cycle if necessary.
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -123,17 +123,16 @@ extern PRLogModuleInfo* gMediaSampleLog;
 */
 class MediaDecoderStateMachine
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine)
 public:
   typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
   typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
   typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus;
-  typedef mozilla::layers::ImageContainer::ProducerID ProducerID;
   typedef mozilla::layers::ImageContainer::FrameID FrameID;
   MediaDecoderStateMachine(MediaDecoder* aDecoder,
                            MediaDecoderReader* aReader,
                            bool aRealTime = false);
 
   nsresult Init();
 
   // Enumeration for the valid decoding states
@@ -460,35 +459,25 @@ protected:
   // media element -- use UpdatePlaybackPosition for that.  Called on the state
   // machine thread, caller must hold the decoder lock.
   void UpdatePlaybackPositionInternal(int64_t aTime);
 
   // Decode monitor must be held. To determine if MDSM needs to turn off HW
   // acceleration.
   void CheckFrameValidity(VideoData* aData);
 
-  // Sets VideoQueue images into the VideoFrameContainer. Called on the shared
-  // state machine thread. Decode monitor must be held. The first aMaxFrames
-  // (at most) are set.
-  // aClockTime and aClockTimeStamp are used as the baseline for deriving
-  // timestamps for the frames; when omitted, aMaxFrames must be 1 and
-  // a null timestamp is passed to the VideoFrameContainer.
-  // If the VideoQueue is empty, this does nothing.
-  void RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime = 0,
-                         const TimeStamp& aClickTimeStamp = TimeStamp());
-
-  // If we have video, display a video frame if it's time for display has
-  // arrived, otherwise sleep until it's time for the next frame. Update the
-  // current frame time as appropriate, and trigger ready state update.  The
-  // decoder monitor must be held with exactly one lock count. Called on the
-  // state machine thread.
-  void UpdateRenderedVideoFrames();
+  // Update playback position and trigger next update by default time period.
+  // Called on the state machine thread.
+  void UpdatePlaybackPositionPeriodically();
 
   media::MediaSink* CreateAudioSink();
 
+  // Always create mediasink which contains an AudioSink or StreamSink inside.
+  already_AddRefed<media::MediaSink> CreateMediaSink(bool aAudioCaptured);
+
   // Stops the media sink and shut it down.
   // The decoder monitor must be held with exactly one lock count.
   // Called on the state machine thread.
   void StopMediaSink();
 
   // Create and start the media sink.
   // The decoder monitor must be held with exactly one lock count.
   // Called on the state machine thread.
@@ -633,22 +622,24 @@ protected:
   bool IsPausedAndDecoderWaiting();
 
   // These return true if the respective stream's decode has not yet reached
   // the end of stream.
   bool IsAudioDecoding();
   bool IsVideoDecoding();
 
 private:
-  // Resolved by the MediaSink to signal that all outstanding work is complete
-  // and the sink is shutting down.
-  void OnMediaSinkComplete();
+  // Resolved by the MediaSink to signal that all audio/video outstanding
+  // work is complete and identify which part(a/v) of the sink is shutting down.
+  void OnMediaSinkAudioComplete();
+  void OnMediaSinkVideoComplete();
 
-  // Rejected by the MediaSink to signal errors.
-  void OnMediaSinkError();
+  // Rejected by the MediaSink to signal errors for audio/video.
+  void OnMediaSinkAudioError();
+  void OnMediaSinkVideoError();
 
   // Return true if the video decoder's decode speed can not catch up the
   // play time.
   bool NeedToSkipToNextKeyframe();
 
   void AdjustAudioThresholds();
 
   // The decoder object that created this state machine. The state machine
@@ -663,20 +654,16 @@ private:
   RefPtr<MediaDecoder> mDecoder;
 
   // Task queue for running the state machine.
   RefPtr<TaskQueue> mTaskQueue;
 
   // State-watching manager.
   WatchManager<MediaDecoderStateMachine> mWatchManager;
 
-  // Producer ID to help ImageContainer distinguish different streams of
-  // FrameIDs. A unique and immutable value per MDSM.
-  const ProducerID mProducerID;
-
   // True is we are decoding a realtime stream, like a camera stream.
   const bool mRealTime;
 
   // True if we've dispatched a task to run the state machine but the task has
   // yet to run.
   bool mDispatchedStateMachine;
 
   // Used to dispatch another round schedule with specific target time.
@@ -917,24 +904,24 @@ private:
   // This is created in the state machine's constructor.
   RefPtr<MediaDecoderReader> mReader;
 
   // The end time of the last audio frame that's been pushed onto the media sink
   // in microseconds. This will approximately be the end time
   // of the audio stream, unless another frame is pushed to the hardware.
   int64_t AudioEndTime() const;
 
+  // The end time of the last rendered video frame that's been sent to
+  // compositor.
+  int64_t VideoEndTime() const;
+
   // The end time of the last decoded audio frame. This signifies the end of
   // decoded audio data. Used to check if we are low in decoded data.
   int64_t mDecodedAudioEndTime;
 
-  // The presentation end time of the last video frame which has been displayed
-  // in microseconds. Accessed from the state machine thread.
-  int64_t mVideoFrameEndTime;
-
   // The end time of the last decoded video frame. Used to check if we are low
   // on decoded video data.
   int64_t mDecodedVideoEndTime;
 
   // Playback rate. 1.0 : normal speed, 0.5 : two times slower.
   double mPlaybackRate;
 
   // Time at which we started decoding. Synchronised via decoder monitor.
@@ -1187,17 +1174,19 @@ private:
   // Only written on the main thread while holding the monitor. Therefore it
   // can be read on any thread while holding the monitor, or on the main thread
   // without holding the monitor.
   RefPtr<DecodedStream> mStreamSink;
 
   // Media data resource from the decoder.
   RefPtr<MediaResource> mResource;
 
-  MozPromiseRequestHolder<GenericPromise> mMediaSinkPromise;
+  // Track the complete & error for audio/video separately
+  MozPromiseRequestHolder<GenericPromise> mMediaSinkAudioPromise;
+  MozPromiseRequestHolder<GenericPromise> mMediaSinkVideoPromise;
 
   MediaEventListener mAudioQueueListener;
   MediaEventListener mVideoQueueListener;
 
   // True if audio is offloading.
   // Playback will not start when audio is offloading.
   bool mAudioOffloading;
 
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -238,17 +238,17 @@ MediaFormatReader::IsWaitingOnCDMResourc
 #ifdef MOZ_EME
   return IsEncrypted() && !mCDMProxy;
 #else
   return false;
 #endif
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-MediaFormatReader::AsyncReadMetadata()
+MediaFormatReader::AsyncReadMetadataInternal()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   RefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
 
   if (mInitDone) {
     // We are returning from dormant.
     if (!EnsureDecodersCreated()) {
@@ -1042,27 +1042,31 @@ MediaFormatReader::DrainDecoder(TrackTyp
   LOG("Requesting %s decoder to drain", TrackTypeToStr(aTrack));
 }
 
 void
 MediaFormatReader::Update(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  if (mShutdown || !mInitDone) {
+  if (mShutdown) {
     return;
   }
 
   LOGV("Processing update for %s", TrackTypeToStr(aTrack));
 
   bool needInput = false;
   bool needOutput = false;
   auto& decoder = GetDecoderData(aTrack);
   decoder.mUpdateScheduled = false;
 
+  if (!mInitDone) {
+    return;
+  }
+
   if (UpdateReceivedNewData(aTrack)) {
     LOGV("Nothing more to do");
     return;
   }
 
   if (!decoder.HasPromise() && decoder.mWaitingForData) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data.");
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -47,18 +47,16 @@ public:
     return mVideo.mTrackDemuxer;
   }
 
   bool HasAudio() override
   {
     return mAudio.mTrackDemuxer;
   }
 
-  RefPtr<MetadataPromise> AsyncReadMetadata() override;
-
   void ReadUpdatedMetadata(MediaInfo* aInfo) override;
 
   RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aUnused) override;
 
   bool IsMediaSeekable() override
   {
     return mSeekable;
@@ -437,13 +435,14 @@ private:
 #if defined(READER_DORMANT_HEURISTIC)
   const bool mDormantEnabled;
 #endif
 
 private:
   // For Media Resource Management
   void ReleaseMediaResourcesInternal() override;
   void DisableHardwareAccelerationInternal() override;
+  RefPtr<MetadataPromise> AsyncReadMetadataInternal() override;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -30,18 +30,18 @@ AndroidMediaReader::AndroidMediaReader(A
   mPlugin(nullptr),
   mHasAudio(false),
   mHasVideo(false),
   mVideoSeekTimeUs(-1),
   mAudioSeekTimeUs(-1)
 {
 }
 
-nsresult AndroidMediaReader::ReadMetadata(MediaInfo* aInfo,
-                                          MetadataTags** aTags)
+nsresult
+AndroidMediaReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (!mPlugin) {
     mPlugin = GetAndroidMediaPluginHost()->CreateDecoder(mDecoder->GetResource(), mType);
     if (!mPlugin) {
       return NS_ERROR_FAILURE;
     }
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -59,18 +59,16 @@ public:
   }
 
   virtual bool IsMediaSeekable()
   {
     // not used
     return true;
   }
 
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags);
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual RefPtr<ShutdownPromise> Shutdown() override;
 
   class ImageBufferCallback : public MPAPI::BufferCallback {
     typedef mozilla::layers::Image Image;
 
@@ -82,13 +80,15 @@ public:
 
   private:
     uint8_t *CreateI420Image(size_t aWidth, size_t aHeight);
 
     mozilla::layers::ImageContainer *mImageContainer;
     RefPtr<Image> mImage;
   };
 
+private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/apple/AppleMP3Reader.cpp
+++ b/dom/media/apple/AppleMP3Reader.cpp
@@ -346,18 +346,17 @@ GetProperty(AudioFileStreamID aAudioFile
   rv = AudioFileStreamGetProperty(aAudioFileStream, aPropertyID,
                                   &size, aData);
 
   return NS_OK;
 }
 
 
 nsresult
-AppleMP3Reader::ReadMetadata(MediaInfo* aInfo,
-                             MetadataTags** aTags)
+AppleMP3Reader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   *aTags = nullptr;
 
   /*
    * Feed bytes into the parser until we have all the metadata we need to
    * set up the decoder. When the parser has enough data, it will
--- a/dom/media/apple/AppleMP3Reader.h
+++ b/dom/media/apple/AppleMP3Reader.h
@@ -26,39 +26,37 @@ public:
 
   virtual bool DecodeAudioData() override;
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold) override;
 
   virtual bool HasAudio() override;
   virtual bool HasVideo() override;
 
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) override;
-
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   void AudioSampleCallback(UInt32 aNumBytes,
                            UInt32 aNumPackets,
                            const void *aData,
                            AudioStreamPacketDescription *aPackets);
 
   void AudioMetadataCallback(AudioFileStreamID aFileStream,
                              AudioFileStreamPropertyID aPropertyID,
                              UInt32 *aFlags);
 
 protected:
   virtual void NotifyDataArrivedInternal(uint32_t aLength,
                                          int64_t aOffset) override;
 public:
-
   virtual bool IsMediaSeekable() override;
 
 private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
+
   void SetupDecoder();
   nsresult Read(uint32_t *aNumBytes, char *aData);
 
   static OSStatus PassthroughInputDataCallback(AudioConverterRef aAudioConverter,
                                                UInt32 *aNumDataPackets,
                                                AudioBufferList *aData,
                                                AudioStreamPacketDescription **aPacketDesc,
                                                void *aUserData);
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -85,18 +85,17 @@ ParseMP3Headers(MP3FrameParser *aParser,
 }
 
 // Windows XP's MP3 decoder filter. This is available on XP only, on Vista
 // and later we can use the DMO Wrapper filter and MP3 decoder DMO.
 static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER =
 { 0x38BE3000, 0xDBF4, 0x11D0, {0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D} };
 
 nsresult
-DirectShowReader::ReadMetadata(MediaInfo* aInfo,
-                               MetadataTags** aTags)
+DirectShowReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
   HRESULT hr;
   nsresult rv;
 
   // Create the filter graph, reference it by the GraphBuilder interface,
   // to make graph building more convenient.
   hr = CoCreateInstance(CLSID_FilterGraph,
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -45,30 +45,28 @@ public:
 
   bool DecodeAudioData() override;
   bool DecodeVideoFrame(bool &aKeyframeSkip,
                         int64_t aTimeThreshold) override;
 
   bool HasAudio() override;
   bool HasVideo() override;
 
-  nsresult ReadMetadata(MediaInfo* aInfo,
-                        MetadataTags** aTags) override;
-
   RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
 protected:
   void NotifyDataArrivedInternal(uint32_t aLength,
                                  int64_t aOffset) override;
 public:
 
   bool IsMediaSeekable() override;
 
 private:
+  nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
 
   // Notifies the filter graph that playback is complete. aStatus is
   // the code to send to the filter graph. Always returns false, so
   // that we can just "return Finish()" from DecodeAudioData().
   bool Finish(HRESULT aStatus);
 
   nsresult SeekInternal(int64_t aTime);
 
--- a/dom/media/gstreamer/GStreamerReader.cpp
+++ b/dom/media/gstreamer/GStreamerReader.cpp
@@ -355,18 +355,18 @@ GStreamerReader::GetDataLength()
 
   if (streamLen < 0) {
     return streamLen;
   }
 
   return streamLen - mDataOffset;
 }
 
-nsresult GStreamerReader::ReadMetadata(MediaInfo* aInfo,
-                                       MetadataTags** aTags)
+nsresult
+GStreamerReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
   nsresult ret = NS_OK;
 
   /*
    * Parse MP3 headers before we kick off the GStreamer pipeline otherwise there
    * might be concurrent stream operations happening on both decoding and gstreamer
    * threads which will screw the GStreamer state machine.
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -41,18 +41,16 @@ public:
   virtual ~GStreamerReader();
 
   virtual nsresult Init() override;
   virtual RefPtr<ShutdownPromise> Shutdown() override;
   virtual nsresult ResetDecode() override;
   virtual bool DecodeAudioData() override;
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold) override;
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
   virtual media::TimeIntervals GetBuffered() override;
 
 protected:
   virtual void NotifyDataArrivedInternal(uint32_t aLength,
                                          int64_t aOffset) override;
 public:
@@ -65,16 +63,17 @@ public:
     return mInfo.HasVideo();
   }
 
   layers::ImageContainer* GetImageContainer() { return mDecoder->GetImageContainer(); }
 
   virtual bool IsMediaSeekable() override;
 
 private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
 
   void ReadAndPushData(guint aLength);
   RefPtr<layers::PlanarYCbCrImage> GetImageFromBuffer(GstBuffer* aBuffer);
   void CopyIntoImageBuffer(GstBuffer *aBuffer, GstBuffer** aOutBuffer, RefPtr<layers::PlanarYCbCrImage> &image);
   GstCaps* BuildAudioSinkCaps();
   void InstallPadCallbacks();
 
 #if GST_VERSION_MAJOR >= 1
--- a/dom/media/mediasink/MediaSink.h
+++ b/dom/media/mediasink/MediaSink.h
@@ -88,16 +88,21 @@ public:
   // Whether to preserve pitch of the audio track.
   // Do nothing if this sink has no audio track.
   // Can be called in any state.
   virtual void SetPreservesPitch(bool aPreservesPitch) {}
 
   // Pause/resume the playback. Only work after playback starts.
   virtual void SetPlaying(bool aPlaying) = 0;
 
+  // Single frame rendering operation may need to be done before playback
+  // started (1st frame) or right after seek completed or playback stopped.
+  // Do nothing if this sink has no video track. Can be called in any state.
+  virtual void Redraw() {};
+
   // Begin a playback session with the provided start time and media info.
   // Must be called when playback is stopped.
   virtual void Start(int64_t aStartTime, const MediaInfo& aInfo) = 0;
 
   // Finish a playback session.
   // Must be called after playback starts.
   virtual void Stop() = 0;
 
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasink/VideoSink.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "VideoSink.h"
+
+namespace mozilla {
+
+extern PRLogModuleInfo* gMediaDecoderLog;
+#define VSINK_LOG(msg, ...) \
+  MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \
+    ("VideoSink=%p " msg, this, ##__VA_ARGS__))
+#define VSINK_LOG_V(msg, ...) \
+  MOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, \
+  ("VideoSink=%p " msg, this, ##__VA_ARGS__))
+
+using namespace mozilla::layers;
+
+namespace media {
+
+VideoSink::VideoSink(AbstractThread* aThread,
+                     MediaSink* aAudioSink,
+                     MediaQueue<MediaData>& aVideoQueue,
+                     VideoFrameContainer* aContainer,
+                     bool aRealTime,
+                     FrameStatistics& aFrameStats,
+                     int aDelayDuration,
+                     uint32_t aVQueueSentToCompositerSize)
+  : mOwnerThread(aThread)
+  , mAudioSink(aAudioSink)
+  , mVideoQueue(aVideoQueue)
+  , mContainer(aContainer)
+  , mProducerID(ImageContainer::AllocateProducerID())
+  , mRealTime(aRealTime)
+  , mFrameStats(aFrameStats)
+  , mVideoFrameEndTime(-1)
+  , mHasVideo(false)
+  , mUpdateScheduler(aThread)
+  , mDelayDuration(aDelayDuration)
+  , mVideoQueueSendToCompositorSize(aVQueueSentToCompositerSize)
+{
+  MOZ_ASSERT(mAudioSink, "AudioSink should exist.");
+}
+
+VideoSink::~VideoSink()
+{
+}
+
+const MediaSink::PlaybackParams&
+VideoSink::GetPlaybackParams() const
+{
+  AssertOwnerThread();
+
+  return mAudioSink->GetPlaybackParams();
+}
+
+void
+VideoSink::SetPlaybackParams(const PlaybackParams& aParams)
+{
+  AssertOwnerThread();
+
+  mAudioSink->SetPlaybackParams(aParams);
+}
+
+RefPtr<GenericPromise>
+VideoSink::OnEnded(TrackType aType)
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts.");
+
+  if (aType == TrackInfo::kAudioTrack) {
+    return mAudioSink->OnEnded(aType);
+  } else if (aType == TrackInfo::kVideoTrack) {
+    return mEndPromise;
+  }
+  return nullptr;
+}
+
+int64_t
+VideoSink::GetEndTime(TrackType aType) const
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(mAudioSink->IsStarted(), "Must be called after playback starts.");
+
+  if (aType == TrackInfo::kVideoTrack) {
+    return mVideoFrameEndTime;
+  } else if (aType == TrackInfo::kAudioTrack) {
+    return mAudioSink->GetEndTime(aType);
+  }
+  return -1;
+}
+
+int64_t
+VideoSink::GetPosition(TimeStamp* aTimeStamp) const
+{
+  AssertOwnerThread();
+
+  return mAudioSink->GetPosition(aTimeStamp);
+}
+
+bool
+VideoSink::HasUnplayedFrames(TrackType aType) const
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(aType == TrackInfo::kAudioTrack, "Not implemented for non audio tracks.");
+
+  return mAudioSink->HasUnplayedFrames(aType);
+}
+
+void
+VideoSink::SetPlaybackRate(double aPlaybackRate)
+{
+  AssertOwnerThread();
+
+  mAudioSink->SetPlaybackRate(aPlaybackRate);
+}
+
+void
+VideoSink::SetVolume(double aVolume)
+{
+  AssertOwnerThread();
+
+  mAudioSink->SetVolume(aVolume);
+}
+
+void
+VideoSink::SetPreservesPitch(bool aPreservesPitch)
+{
+  AssertOwnerThread();
+
+  mAudioSink->SetPreservesPitch(aPreservesPitch);
+}
+
+void
+VideoSink::SetPlaying(bool aPlaying)
+{
+  AssertOwnerThread();
+  VSINK_LOG_V(" playing (%d) -> (%d)", mAudioSink->IsPlaying(), aPlaying);
+
+  if (!aPlaying) {
+    // Reset any update timer if paused.
+    mUpdateScheduler.Reset();
+    // Since playback is paused, tell compositor to render only current frame.
+    RenderVideoFrames(1);
+  }
+
+  mAudioSink->SetPlaying(aPlaying);
+
+  if (mHasVideo && aPlaying) {
+    // There's no thread in VideoSink for pulling video frames, need to trigger
+    // rendering while becoming playing status. because the VideoQueue may be
+    // full already.
+    TryUpdateRenderedVideoFrames();
+  }
+}
+
+void
+VideoSink::Start(int64_t aStartTime, const MediaInfo& aInfo)
+{
+  AssertOwnerThread();
+  VSINK_LOG("[%s]", __func__);
+
+  mAudioSink->Start(aStartTime, aInfo);
+
+  mHasVideo = aInfo.HasVideo();
+
+  if (mHasVideo) {
+    mEndPromise = mEndPromiseHolder.Ensure(__func__);
+    ConnectListener();
+    TryUpdateRenderedVideoFrames();
+  }
+}
+
+void
+VideoSink::Stop()
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(mAudioSink->IsStarted(), "playback not started.");
+  VSINK_LOG("[%s]", __func__);
+
+  mAudioSink->Stop();
+
+  mUpdateScheduler.Reset();
+  if (mHasVideo) {
+    DisconnectListener();
+    mEndPromiseHolder.Resolve(true, __func__);
+    mEndPromise = nullptr;
+  }
+  mVideoFrameEndTime = -1;
+}
+
+bool
+VideoSink::IsStarted() const
+{
+  AssertOwnerThread();
+
+  return mAudioSink->IsStarted();
+}
+
+bool
+VideoSink::IsPlaying() const
+{
+  AssertOwnerThread();
+
+  return mAudioSink->IsPlaying();
+}
+
+void
+VideoSink::Shutdown()
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(!mAudioSink->IsStarted(), "must be called after playback stops.");
+  VSINK_LOG("[%s]", __func__);
+
+  mAudioSink->Shutdown();
+}
+
+void
+VideoSink::OnVideoQueueEvent()
+{
+  AssertOwnerThread();
+  // Listen to push event, VideoSink should try rendering ASAP if first frame
+  // arrives but update scheduler is not triggered yet.
+  TryUpdateRenderedVideoFrames();
+}
+
+void
+VideoSink::Redraw()
+{
+  AssertOwnerThread();
+  RenderVideoFrames(1);
+}
+
+void
+VideoSink::TryUpdateRenderedVideoFrames()
+{
+  AssertOwnerThread();
+  if (!mUpdateScheduler.IsScheduled() && VideoQueue().GetSize() >= 1 &&
+      mAudioSink->IsPlaying()) {
+    UpdateRenderedVideoFrames();
+  }
+}
+
+void
+VideoSink::UpdateRenderedVideoFramesByTimer()
+{
+  AssertOwnerThread();
+  mUpdateScheduler.CompleteRequest();
+  UpdateRenderedVideoFrames();
+}
+
+void
+VideoSink::ConnectListener()
+{
+  AssertOwnerThread();
+  mPushListener = VideoQueue().PushEvent().Connect(
+    mOwnerThread, this, &VideoSink::OnVideoQueueEvent);
+}
+
+void
+VideoSink::DisconnectListener()
+{
+  AssertOwnerThread();
+  mPushListener.Disconnect();
+}
+
+void
+VideoSink::RenderVideoFrames(int32_t aMaxFrames,
+                             int64_t aClockTime,
+                             const TimeStamp& aClockTimeStamp)
+{
+  AssertOwnerThread();
+
+  nsAutoTArray<RefPtr<MediaData>,16> frames;
+  VideoQueue().GetFirstElements(aMaxFrames, &frames);
+  if (frames.IsEmpty() || !mContainer) {
+    return;
+  }
+
+  nsAutoTArray<ImageContainer::NonOwningImage,16> images;
+  TimeStamp lastFrameTime;
+  MediaSink::PlaybackParams params = mAudioSink->GetPlaybackParams();
+  for (uint32_t i = 0; i < frames.Length(); ++i) {
+    VideoData* frame = frames[i]->As<VideoData>();
+
+    bool valid = !frame->mImage || frame->mImage->IsValid();
+    frame->mSentToCompositor = true;
+
+    if (!valid) {
+      continue;
+    }
+
+    int64_t frameTime = frame->mTime;
+    if (frameTime < 0) {
+      // Frame times before the start time are invalid; drop such frames
+      continue;
+    }
+
+    TimeStamp t;
+    if (aMaxFrames > 1) {
+      MOZ_ASSERT(!aClockTimeStamp.IsNull());
+      int64_t delta = frame->mTime - aClockTime;
+      t = aClockTimeStamp +
+          TimeDuration::FromMicroseconds(delta / params.mPlaybackRate);
+      if (!lastFrameTime.IsNull() && t <= lastFrameTime) {
+        // Timestamps out of order; drop the new frame. In theory we should
+        // probably replace the previous frame with the new frame if the
+        // timestamps are equal, but this is a corrupt video file already so
+        // never mind.
+        continue;
+      }
+      lastFrameTime = t;
+    }
+
+    ImageContainer::NonOwningImage* img = images.AppendElement();
+    img->mTimeStamp = t;
+    img->mImage = frame->mImage;
+    img->mFrameID = frame->mFrameID;
+    img->mProducerID = mProducerID;
+
+    VSINK_LOG_V("playing video frame %lld (id=%x) (vq-queued=%i)",
+                frame->mTime, frame->mFrameID, VideoQueue().GetSize());
+  }
+  mContainer->SetCurrentFrames(frames[0]->As<VideoData>()->mDisplay, images);
+}
+
+void
+VideoSink::UpdateRenderedVideoFrames()
+{
+  AssertOwnerThread();
+  MOZ_ASSERT(mAudioSink->IsPlaying(), "should be called while playing.");
+
+  TimeStamp nowTime;
+  const int64_t clockTime = mAudioSink->GetPosition(&nowTime);
+  // Skip frames up to the frame at the playback position, and figure out
+  // the time remaining until it's time to display the next frame and drop
+  // the current frame.
+  NS_ASSERTION(clockTime >= 0, "Should have positive clock time.");
+
+  int64_t remainingTime = mDelayDuration;
+  if (VideoQueue().GetSize() > 0) {
+    RefPtr<MediaData> currentFrame = VideoQueue().PopFront();
+    int32_t framesRemoved = 0;
+    while (VideoQueue().GetSize() > 0) {
+      MediaData* nextFrame = VideoQueue().PeekFront();
+      if (!mRealTime && nextFrame->mTime > clockTime) {
+        remainingTime = nextFrame->mTime - clockTime;
+        break;
+      }
+      ++framesRemoved;
+      if (!currentFrame->As<VideoData>()->mSentToCompositor) {
+        mFrameStats.NotifyDecodedFrames(0, 0, 1);
+        VSINK_LOG_V("discarding video frame mTime=%lld clock_time=%lld",
+                    currentFrame->mTime, clockTime);
+      }
+      currentFrame = VideoQueue().PopFront();
+    }
+    VideoQueue().PushFront(currentFrame);
+    if (framesRemoved > 0) {
+      mVideoFrameEndTime = currentFrame->GetEndTime();
+      mFrameStats.NotifyPresentedFrame();
+    }
+  }
+
+  RenderVideoFrames(mVideoQueueSendToCompositorSize, clockTime, nowTime);
+
+  TimeStamp target = nowTime + TimeDuration::FromMicroseconds(remainingTime);
+
+  RefPtr<VideoSink> self = this;
+  mUpdateScheduler.Ensure(target, [self] () {
+    self->UpdateRenderedVideoFramesByTimer();
+  }, [self] () {
+    self->UpdateRenderedVideoFramesByTimer();
+  });
+}
+
+} // namespace media
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasink/VideoSink.h
@@ -0,0 +1,154 @@
+/* -*- 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 VideoSink_h_
+#define VideoSink_h_
+
+#include "FrameStatistics.h"
+#include "ImageContainer.h"
+#include "MediaEventSource.h"
+#include "MediaSink.h"
+#include "MediaTimer.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "VideoFrameContainer.h"
+
+namespace mozilla {
+
+class VideoFrameContainer;
+template <class T> class MediaQueue;
+
+namespace media {
+
+class VideoSink : public MediaSink
+{
+  typedef mozilla::layers::ImageContainer::ProducerID ProducerID;
+public:
+  VideoSink(AbstractThread* aThread,
+            MediaSink* aAudioSink,
+            MediaQueue<MediaData>& aVideoQueue,
+            VideoFrameContainer* aContainer,
+            bool aRealTime,
+            FrameStatistics& aFrameStats,
+            int aDelayDuration,
+            uint32_t aVQueueSentToCompositerSize);
+
+  const PlaybackParams& GetPlaybackParams() const override;
+
+  void SetPlaybackParams(const PlaybackParams& aParams) override;
+
+  RefPtr<GenericPromise> OnEnded(TrackType aType) override;
+
+  int64_t GetEndTime(TrackType aType) const override;
+
+  int64_t GetPosition(TimeStamp* aTimeStamp = nullptr) const override;
+
+  bool HasUnplayedFrames(TrackType aType) const override;
+
+  void SetPlaybackRate(double aPlaybackRate) override;
+
+  void SetVolume(double aVolume) override;
+
+  void SetPreservesPitch(bool aPreservesPitch) override;
+
+  void SetPlaying(bool aPlaying) override;
+
+  void Redraw() override;
+
+  void Start(int64_t aStartTime, const MediaInfo& aInfo) override;
+
+  void Stop() override;
+
+  bool IsStarted() const override;
+
+  bool IsPlaying() const override;
+
+  void Shutdown() override;
+
+private:
+  virtual ~VideoSink();
+
+  // VideoQueue listener related.
+  void OnVideoQueueEvent();
+  void ConnectListener();
+  void DisconnectListener();
+
+  // Sets VideoQueue images into the VideoFrameContainer. Called on the shared
+  // state machine thread. The first aMaxFrames (at most) are set.
+  // aClockTime and aClockTimeStamp are used as the baseline for deriving
+  // timestamps for the frames; when omitted, aMaxFrames must be 1 and
+  // a null timestamp is passed to the VideoFrameContainer.
+  // If the VideoQueue is empty, this does nothing.
+  void RenderVideoFrames(int32_t aMaxFrames, int64_t aClockTime = 0,
+                         const TimeStamp& aClickTimeStamp = TimeStamp());
+
+  // Triggered while videosink is started, videosink becomes "playing" status,
+  // or VideoQueue event arrived.
+  void TryUpdateRenderedVideoFrames();
+
+  // If we have video, display a video frame if it's time for display has
+  // arrived, otherwise sleep until it's time for the next frame. Update the
+  // current frame time as appropriate, and trigger ready state update.
+  // Called on the shared state machine thread.
+  void UpdateRenderedVideoFrames();
+  void UpdateRenderedVideoFramesByTimer();
+
+  void AssertOwnerThread() const
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  }
+
+  MediaQueue<MediaData>& VideoQueue() const {
+    return mVideoQueue;
+  }
+
+  const RefPtr<AbstractThread> mOwnerThread;
+  RefPtr<MediaSink> mAudioSink;
+  MediaQueue<MediaData>& mVideoQueue;
+  VideoFrameContainer* mContainer;
+
+  // Producer ID to help ImageContainer distinguish different streams of
+  // FrameIDs. A unique and immutable value per VideoSink.
+  const ProducerID mProducerID;
+
+  // True if we are decoding a real-time stream.
+  const bool mRealTime;
+
+  // Used to notify MediaDecoder's frame statistics
+  FrameStatistics& mFrameStats;
+
+  RefPtr<GenericPromise> mEndPromise;
+  MozPromiseHolder<GenericPromise> mEndPromiseHolder;
+  MozPromiseRequestHolder<GenericPromise> mVideoSinkEndRequest;
+
+  // The presentation end time of the last video frame which has been displayed
+  // in microseconds.
+  int64_t mVideoFrameEndTime;
+
+  // Event listeners for VideoQueue
+  MediaEventListener mPushListener;
+
+  // True if this sink is going to handle video track.
+  bool mHasVideo;
+
+  // Used to trigger another update of rendered frames in next round.
+  DelayedScheduler mUpdateScheduler;
+
+  // A delay duration to trigger next time UpdateRenderedVideoFrames().
+  // Based on the default value in MDSM.
+  const int mDelayDuration;
+
+  // Max frame number sent to compositor at a time.
+  // Based on the pref value obtained in MDSM.
+  const uint32_t mVideoQueueSendToCompositorSize;
+};
+
+} // namespace media
+} // namespace mozilla
+
+#endif
--- a/dom/media/mediasink/moz.build
+++ b/dom/media/mediasink/moz.build
@@ -3,11 +3,12 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'AudioSinkWrapper.cpp',
     'DecodedAudioDataSink.cpp',
     'DecodedStream.cpp',
+    'VideoSink.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -366,18 +366,18 @@ void OggReader::SetupMediaTracksInfo(con
       }
 
       mInfo.mAudio.mRate = opusState->mRate;
       mInfo.mAudio.mChannels = opusState->mChannels;
     }
   }
 }
 
-nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
-                                 MetadataTags** aTags)
+nsresult
+OggReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // We read packets until all bitstreams have read all their header packets.
   // We record the offset of the first non-header page so that we know
   // what page to seek to when seeking to the media start.
 
   NS_ASSERTION(aTags, "Called with null MetadataTags**.");
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -64,25 +64,25 @@ public:
     return (mVorbisState != 0 && mVorbisState->mActive) ||
            (mOpusState != 0 && mOpusState->mActive);
   }
 
   virtual bool HasVideo() override {
     return mTheoraState != 0 && mTheoraState->mActive;
   }
 
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
 private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
+
   // TODO: DEPRECATED. This uses synchronous decoding.
   // Stores the presentation time of the first frame we'd be able to play if
   // we started playback at the current position. Returns the first video
   // frame, if we have video.
   VideoData* FindStartTime(int64_t& aOutStartTime);
   AudioData* SyncDecodeToFirstAudioData();
   VideoData* SyncDecodeToFirstVideoData();
 
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -655,17 +655,17 @@ MediaCodecReader::ParseDataSegment(const
     MOZ_ASSERT(mDecoder);
     mDecoder->DispatchUpdateEstimatedMediaDuration(duration);
   }
 
   return true;
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-MediaCodecReader::AsyncReadMetadata()
+MediaCodecReader::AsyncReadMetadataInternal()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (!ReallocateExtractorResources()) {
     return MediaDecoderReader::MetadataPromise::CreateAndReject(
              ReadMetadataFailureReason::METADATA_ERROR, __func__);
   }
 
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -65,34 +65,35 @@ public:
   // irreversible, whereas ReleaseMediaResources() is reversible.
   virtual RefPtr<ShutdownPromise> Shutdown();
 
 protected:
   // Used to retrieve some special information that can only be retrieved after
   // all contents have been continuously parsed. (ex. total duration of some
   // variable-bit-rate MP3 files.)
   virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
+
+  virtual RefPtr<MediaDecoderReader::MetadataPromise>
+  AsyncReadMetadataInternal() override;
+
 public:
-
   // Flush the TaskQueue, flush MediaCodec and raise the mDiscontinuity.
   virtual nsresult ResetDecode() override;
 
   // Disptach a DecodeVideoFrameTask to decode video data.
   virtual RefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe,
                    int64_t aTimeThreshold) override;
 
   // Disptach a DecodeAduioDataTask to decode video data.
   virtual RefPtr<AudioDataPromise> RequestAudioData() override;
 
   virtual bool HasAudio();
   virtual bool HasVideo();
 
-  virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
-
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual bool IsMediaSeekable() override;
 
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -207,17 +207,17 @@ nsresult MediaOmxReader::InitOmxDecoder(
       return NS_ERROR_FAILURE;
     }
     mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
   }
   return NS_OK;
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-MediaOmxReader::AsyncReadMetadata()
+MediaOmxReader::AsyncReadMetadataInternal()
 {
   MOZ_ASSERT(OnTaskQueue());
   EnsureActive();
 
   // Initialize the internal OMX Decoder.
   nsresult rv = InitOmxDecoder();
   if (NS_FAILED(rv)) {
     return MediaDecoderReader::MetadataPromise::CreateAndReject(
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -67,16 +67,20 @@ protected:
   virtual void HandleResourceAllocated();
 
 public:
   MediaOmxReader(AbstractMediaDecoder* aDecoder);
   ~MediaOmxReader();
 
 protected:
   virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
+
+  virtual RefPtr<MediaDecoderReader::MetadataPromise>
+  AsyncReadMetadataInternal() override;
+
 public:
 
   virtual nsresult ResetDecode()
   {
     mSeekRequest.DisconnectIfExists();
     mSeekPromise.RejectIfExists(NS_OK, __func__);
     return MediaDecoderReader::ResetDecode();
   }
@@ -90,18 +94,16 @@ public:
     return mHasAudio;
   }
 
   virtual bool HasVideo()
   {
     return mHasVideo;
   }
 
-  virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
-
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual bool IsMediaSeekable() override;
 
   virtual void SetIdle() override;
 
   virtual RefPtr<ShutdownPromise> Shutdown() override;
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -84,23 +84,23 @@ RefPtr<MediaDecoderReader::VideoDataProm
 RtspMediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
                                        int64_t aTimeThreshold)
 {
   EnsureActive();
   return MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-RtspMediaCodecReader::AsyncReadMetadata()
+RtspMediaCodecReader::AsyncReadMetadataInternal()
 {
   mRtspResource->DisablePlayoutDelay();
   EnsureActive();
 
   RefPtr<MediaDecoderReader::MetadataPromise> p =
-    MediaCodecReader::AsyncReadMetadata();
+    MediaCodecReader::AsyncReadMetadataInternal();
 
   // Send a PAUSE to the RTSP server because the underlying media resource is
   // not ready.
   SetIdle();
 
   return p;
 }
 
--- a/dom/media/omx/RtspMediaCodecReader.h
+++ b/dom/media/omx/RtspMediaCodecReader.h
@@ -53,22 +53,22 @@ public:
   // Disptach a DecodeVideoFrameTask to decode video data.
   virtual RefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe,
                    int64_t aTimeThreshold) override;
 
   // Disptach a DecodeAudioDataTask to decode audio data.
   virtual RefPtr<AudioDataPromise> RequestAudioData() override;
 
-  virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata()
-    override;
-
   virtual void HandleResourceAllocated() override;
 
 private:
+  virtual RefPtr<MediaDecoderReader::MetadataPromise>
+  AsyncReadMetadataInternal() override;
+
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspMediaCodecReader.
   RtspMediaResource* mRtspResource;
 };
 
 } // namespace mozilla
--- a/dom/media/omx/RtspOmxReader.cpp
+++ b/dom/media/omx/RtspOmxReader.cpp
@@ -82,24 +82,24 @@ void RtspOmxReader::EnsureActive() {
     mRtspResource->SetSuspend(false);
   }
 
   // Call parent class to set OMXCodec active.
   MediaOmxReader::EnsureActive();
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-RtspOmxReader::AsyncReadMetadata()
+RtspOmxReader::AsyncReadMetadataInternal()
 {
   // Send a PLAY command to the RTSP server before reading metadata.
   // Because we might need some decoded samples to ensure we have configuration.
   mRtspResource->DisablePlayoutDelay();
 
   RefPtr<MediaDecoderReader::MetadataPromise> p =
-    MediaOmxReader::AsyncReadMetadata();
+    MediaOmxReader::AsyncReadMetadataInternal();
 
   // Send a PAUSE to the RTSP server because the underlying media resource is
   // not ready.
   SetIdle();
 
   return p;
 }
 
--- a/dom/media/omx/RtspOmxReader.h
+++ b/dom/media/omx/RtspOmxReader.h
@@ -61,22 +61,22 @@ public:
   // ChannelMediaResource, it has a "cache" that can store the whole streaming
   // data so the |GetBuffered| function can retrieve useful time ranges.
   virtual media::TimeIntervals GetBuffered() final override {
     return media::TimeIntervals::Invalid();
   }
 
   virtual void SetIdle() override;
 
-  virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata()
-    override;
-
   virtual void HandleResourceAllocated() override;
 
 private:
+  virtual RefPtr<MediaDecoderReader::MetadataPromise>
+  AsyncReadMetadataInternal() override;
+
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspOmxReader.
   RtspMediaResource* mRtspResource;
 
   bool mEnsureActiveFromSeek;
 };
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
+++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
@@ -202,21 +202,23 @@ GonkDecoderManager::ProcessToDo(bool aEn
   while (mWaitOutput.Length() > 0) {
     RefPtr<MediaData> output;
     int64_t offset = mWaitOutput.ElementAt(0);
     rv = Output(offset, output);
     if (rv == NS_OK) {
       mWaitOutput.RemoveElementAt(0);
       mDecodeCallback->Output(output);
     } else if (rv == NS_ERROR_ABORT) {
-      GMDD_LOG("eos output");
-      mWaitOutput.RemoveElementAt(0);
+      // EOS
       MOZ_ASSERT(mQueuedSamples.IsEmpty());
-      MOZ_ASSERT(mWaitOutput.IsEmpty());
-      // EOS
+      mWaitOutput.RemoveElementAt(0);
+      // Sometimes the decoder attaches EOS flag to the final output buffer
+      // instead of emits EOS by itself, hence the 2nd condition.
+      MOZ_ASSERT(mWaitOutput.IsEmpty() ||
+                 (mWaitOutput.Length() == 1 && output.get()));
       if (output) {
         mDecodeCallback->Output(output);
       }
       mDecodeCallback->DrainComplete();
       return;
     } else if (rv == NS_ERROR_NOT_AVAILABLE) {
       break;
     } else {
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -27,18 +27,18 @@ RawReader::~RawReader()
 }
 
 nsresult RawReader::ResetDecode()
 {
   mCurrentFrame = 0;
   return MediaDecoderReader::ResetDecode();
 }
 
-nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
-                                 MetadataTags** aTags)
+nsresult
+RawReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (!ReadFromResource(reinterpret_cast<uint8_t*>(&mMetadata),
                         sizeof(mMetadata)))
     return NS_ERROR_FAILURE;
 
   // Validate the header
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -31,26 +31,25 @@ public:
     return false;
   }
 
   virtual bool HasVideo() override
   {
     return true;
   }
 
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
 private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
   bool ReadFromResource(uint8_t *aBuf, uint32_t aLength);
 
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
   MediaResourceIndex mResource;
--- a/dom/media/wave/WaveReader.cpp
+++ b/dom/media/wave/WaveReader.cpp
@@ -111,18 +111,18 @@ WaveReader::WaveReader(AbstractMediaDeco
   MOZ_COUNT_CTOR(WaveReader);
 }
 
 WaveReader::~WaveReader()
 {
   MOZ_COUNT_DTOR(WaveReader);
 }
 
-nsresult WaveReader::ReadMetadata(MediaInfo* aInfo,
-                                  MetadataTags** aTags)
+nsresult
+WaveReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   bool loaded = LoadRIFFChunk();
   if (!loaded) {
     return NS_ERROR_FAILURE;
   }
 
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -31,26 +31,25 @@ public:
     return true;
   }
 
   virtual bool HasVideo() override
   {
     return false;
   }
 
-  virtual nsresult ReadMetadata(MediaInfo* aInfo,
-                                MetadataTags** aTags) override;
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
 private:
+  virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) override;
   bool ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead = nullptr);
   bool LoadRIFFChunk();
   bool LoadFormatChunk(uint32_t aChunkSize);
   bool FindDataOffset(uint32_t aChunkSize);
   bool LoadListChunk(uint32_t aChunkSize, nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
   bool LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags);
 
   // Returns the number of seconds that aBytes represents based on the
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -222,18 +222,20 @@ void WebMReader::Cleanup()
 {
   if (mContext) {
     nestegg_destroy(mContext);
     mContext = nullptr;
   }
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
-WebMReader::AsyncReadMetadata()
+WebMReader::AsyncReadMetadataInternal()
 {
+  MOZ_ASSERT(OnTaskQueue());
+
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
 
   if (NS_FAILED(RetrieveWebMMetadata(&metadata->mInfo)) ||
       !metadata->mInfo.HasValidMedia()) {
     return MetadataPromise::CreateAndReject(ReadMetadataFailureReason::METADATA_ERROR,
                                             __func__);
   }
 
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -84,18 +84,16 @@ public:
   }
 
   virtual bool HasVideo() override
   {
     MOZ_ASSERT(OnTaskQueue());
     return mHasVideo;
   }
 
-  virtual RefPtr<MetadataPromise> AsyncReadMetadata() override;
-
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
   // Value passed to NextPacket to determine if we are reading a video or an
@@ -139,16 +137,18 @@ protected:
   virtual nsresult SeekInternal(int64_t aTime);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   bool ShouldSkipVideoFrame(int64_t aTimeThreshold);
 
 private:
+  virtual RefPtr<MetadataPromise> AsyncReadMetadataInternal() override;
+
   nsresult RetrieveWebMMetadata(MediaInfo* aInfo);
 
   // Get the timestamp of keyframe greater than aTimeThreshold.
   int64_t GetNextKeyframeTime(int64_t aTimeThreshold);
   // Push the packets into aOutput which's timestamp is less than aEndTime.
   // Return false if we reach the end of stream or something wrong.
   bool FilterPacketByTime(int64_t aEndTime, WebMPacketQueue& aOutput);
 
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp
@@ -0,0 +1,57 @@
+/* -*- 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/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "OSXSpeechSynthesizerService.h"
+
+using namespace mozilla::dom;
+
+#define OSXSPEECHSYNTHESIZERSERVICE_CID \
+  {0x914e73b4, 0x6337, 0x4bef, {0x97, 0xf3, 0x4d, 0x06, 0x9e, 0x05, 0x3a, 0x12}}
+
+#define OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID "@mozilla.org/synthsystem;1"
+
+// Defines OSXSpeechSynthesizerServiceConstructor
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSXSpeechSynthesizerService,
+                                         OSXSpeechSynthesizerService::GetInstanceForService)
+
+// Defines kSAPISERVICE_CID
+NS_DEFINE_NAMED_CID(OSXSPEECHSYNTHESIZERSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kCIDs[] = {
+  { &kOSXSPEECHSYNTHESIZERSERVICE_CID, true, nullptr, OSXSpeechSynthesizerServiceConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kContracts[] = {
+  { OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID, &kOSXSPEECHSYNTHESIZERSERVICE_CID },
+  { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kCategories[] = {
+  { "profile-after-change", "Sapi Speech Synth", OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID },
+  { nullptr }
+};
+
+static void
+UnloadOSXSpeechSynthesizerModule()
+{
+  OSXSpeechSynthesizerService::Shutdown();
+}
+
+static const mozilla::Module kModule = {
+  mozilla::Module::kVersion,
+  kCIDs,
+  kContracts,
+  kCategories,
+  nullptr,
+  nullptr,
+  UnloadOSXSpeechSynthesizerModule
+};
+
+NSMODULE_DEFN(osxsynth) = &kModule;
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h
@@ -0,0 +1,45 @@
+/* -*- 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_OsxSpeechSynthesizerService_h
+#define mozilla_dom_OsxSpeechSynthesizerService_h
+
+#include "nsAutoPtr.h"
+#include "nsISpeechService.h"
+#include "nsIObserver.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class OSXSpeechSynthesizerService final : public nsISpeechService
+                                        , public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISPEECHSERVICE
+  NS_DECL_NSIOBSERVER
+
+  bool Init();
+
+  static OSXSpeechSynthesizerService* GetInstance();
+  static already_AddRefed<OSXSpeechSynthesizerService> GetInstanceForService();
+  static void Shutdown();
+
+private:
+  OSXSpeechSynthesizerService();
+  virtual ~OSXSpeechSynthesizerService();
+
+  bool RegisterVoices();
+
+  bool mInitialized;
+  static mozilla::StaticRefPtr<OSXSpeechSynthesizerService> sSingleton;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -0,0 +1,384 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.h"
+#include "nsServiceManagerUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/dom/nsSynthVoiceRegistry.h"
+#include "mozilla/dom/nsSpeechTask.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Assertions.h"
+#include "OSXSpeechSynthesizerService.h"
+
+#import <Cocoa/Cocoa.h>
+
+using namespace mozilla;
+
+class SpeechTaskCallback final : public nsISpeechTaskCallback
+{
+public:
+  SpeechTaskCallback(nsISpeechTask* aTask, NSSpeechSynthesizer* aSynth)
+    : mTask(aTask)
+    , mSpeechSynthesizer(aSynth)
+  {
+    mStartingTime = TimeStamp::Now();
+  }
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
+
+  NS_DECL_NSISPEECHTASKCALLBACK
+
+  void OnWillSpeakWord(uint32_t aIndex);
+  void OnError(uint32_t aIndex);
+  void OnDidFinishSpeaking();
+
+private:
+  virtual ~SpeechTaskCallback()
+  {
+    [mSpeechSynthesizer release];
+  }
+
+  float GetTimeDurationFromStart();
+
+  nsCOMPtr<nsISpeechTask> mTask;
+  NSSpeechSynthesizer* mSpeechSynthesizer;
+  TimeStamp mStartingTime;
+  uint32_t mCurrentIndex;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnCancel()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  [mSpeechSynthesizer stopSpeaking];
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnPause()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  [mSpeechSynthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
+  mTask->DispatchPause(GetTimeDurationFromStart(), mCurrentIndex);
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnResume()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  [mSpeechSynthesizer continueSpeaking];
+  mTask->DispatchResume(GetTimeDurationFromStart(), mCurrentIndex);
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnVolumeChanged(float aVolume)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  [mSpeechSynthesizer setObject:[NSNumber numberWithFloat:aVolume]
+                    forProperty:NSSpeechVolumeProperty error:nil];
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+float
+SpeechTaskCallback::GetTimeDurationFromStart()
+{
+  TimeDuration duration = TimeStamp::Now() - mStartingTime;
+  return duration.ToMilliseconds();
+}
+
+void
+SpeechTaskCallback::OnWillSpeakWord(uint32_t aIndex)
+{
+  mCurrentIndex = aIndex;
+  mTask->DispatchBoundary(NS_LITERAL_STRING("word"),
+                          GetTimeDurationFromStart(), mCurrentIndex);
+}
+
+void
+SpeechTaskCallback::OnError(uint32_t aIndex)
+{
+  mTask->DispatchError(GetTimeDurationFromStart(), aIndex);
+}
+
+void
+SpeechTaskCallback::OnDidFinishSpeaking()
+{
+  mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
+  // no longer needed
+  mTask = nullptr;
+}
+
+@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
+{
+@private
+  SpeechTaskCallback* mCallback;
+}
+
+  - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
+@end
+
+@implementation SpeechDelegate
+- (id)initWithCallback:(SpeechTaskCallback*)aCallback
+{
+  [super init];
+  mCallback = aCallback;
+  return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+            willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
+{
+  mCallback->OnWillSpeakWord(aRange.location);
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+        didFinishSpeaking:(BOOL)aFinishedSpeaking
+{
+  mCallback->OnDidFinishSpeaking();
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
+ didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
+                 ofString:(NSString*)aString
+                  message:(NSString*)aMessage
+{
+  mCallback->OnError(aCharacterIndex);
+}
+@end
+
+namespace mozilla {
+namespace dom {
+
+class RegisterVoicesRunnable final : public nsRunnable
+{
+public:
+  explicit RegisterVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService)
+    : mSpeechService(aSpeechService)
+  {
+  }
+
+  NS_IMETHOD Run();
+
+private:
+  ~RegisterVoicesRunnable()
+  {
+  }
+
+  RefPtr<OSXSpeechSynthesizerService> mSpeechService;
+};
+
+NS_IMETHODIMP
+RegisterVoicesRunnable::Run()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  nsresult rv;
+  nsCOMPtr<nsISynthVoiceRegistry> registry =
+    do_GetService(NS_SYNTHVOICEREGISTRY_CONTRACTID, &rv);
+  if (!registry) {
+    return rv;
+  }
+
+  NSArray* voices = [NSSpeechSynthesizer availableVoices];
+  NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
+
+  for (NSString* voice in voices) {
+    NSDictionary* attr = [NSSpeechSynthesizer attributesForVoice:voice];
+
+    nsAutoString identifier;
+    nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceIdentifier],
+                                       identifier);
+
+    nsAutoString name;
+    nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceName], name);
+
+    nsAutoString locale;
+    nsCocoaUtils::GetStringForNSString(
+      [attr objectForKey:NSVoiceLocaleIdentifier], locale);
+    locale.ReplaceChar('_', '-');
+
+    nsAutoString uri;
+    uri.AssignLiteral("urn:moz-tts:osx:");
+    uri.Append(identifier);
+    rv = registry->AddVoice(mSpeechService, uri, name, locale, true, false);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+
+    if ([voice isEqualToString:defaultVoice]) {
+      registry->SetDefaultVoice(uri, true);
+    }
+  }
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+StaticRefPtr<OSXSpeechSynthesizerService> OSXSpeechSynthesizerService::sSingleton;
+
+NS_INTERFACE_MAP_BEGIN(OSXSpeechSynthesizerService)
+  NS_INTERFACE_MAP_ENTRY(nsISpeechService)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(OSXSpeechSynthesizerService)
+NS_IMPL_RELEASE(OSXSpeechSynthesizerService)
+
+OSXSpeechSynthesizerService::OSXSpeechSynthesizerService()
+  : mInitialized(false)
+{
+}
+
+OSXSpeechSynthesizerService::~OSXSpeechSynthesizerService()
+{
+}
+
+bool
+OSXSpeechSynthesizerService::Init()
+{
+  if (Preferences::GetBool("media.webspeech.synth.test") ||
+      !Preferences::GetBool("media.webspeech.synth.enabled")) {
+    // When test is enabled, we shouldn't add OS backend (Bug 1160844)
+    return false;
+  }
+
+  // Get all the voices and register in the SynthVoiceRegistry
+  nsCOMPtr<nsIRunnable> runnable = new RegisterVoicesRunnable(this);
+  NS_DispatchToMainThread(runnable);
+
+  mInitialized = true;
+  return true;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::Speak(const nsAString& aText,
+                                   const nsAString& aUri,
+                                   float aVolume,
+                                   float aRate,
+                                   float aPitch,
+                                   nsISpeechTask* aTask)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  MOZ_ASSERT(StringBeginsWith(aUri, NS_LITERAL_STRING("urn:moz-tts:osx:")),
+             "OSXSpeechSynthesizerService doesn't allow this voice URI");
+
+  NSSpeechSynthesizer* synth = [[NSSpeechSynthesizer alloc] init];
+  // strlen("urn:moz-tts:osx:") == 16
+  NSString* identifier = nsCocoaUtils::ToNSString(Substring(aUri, 16));
+  [synth setVoice:identifier];
+
+  // default rate is 180-220
+  [synth setObject:[NSNumber numberWithInt:aRate * 200]
+         forProperty:NSSpeechRateProperty error:nil];
+  // volume allows 0.0-1.0
+  [synth setObject:[NSNumber numberWithFloat:aVolume]
+         forProperty:NSSpeechVolumeProperty error:nil];
+  // Use default pitch value to calculate this
+  NSNumber* defaultPitch =
+    [synth objectForProperty:NSSpeechPitchBaseProperty error:nil];
+  if (defaultPitch) {
+    int newPitch = [defaultPitch intValue] * (aPitch / 2 + 0.5);
+    [synth setObject:[NSNumber numberWithInt:newPitch]
+           forProperty:NSSpeechPitchBaseProperty error:nil];
+  }
+
+  RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth);
+  nsresult rv = aTask->Setup(callback, 0, 0, 0);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback];
+  [synth setDelegate:delegate];
+  [delegate release ];
+
+  NSString* text = nsCocoaUtils::ToNSString(aText);
+  BOOL success = [synth startSpeakingString:text];
+  NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+  aTask->DispatchStart();
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::GetServiceType(SpeechServiceType* aServiceType)
+{
+  *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::Observe(nsISupports* aSubject, const char* aTopic,
+                                     const char16_t* aData)
+{
+  if (!strcmp(aTopic, "profile-after-change")) {
+    Init();
+  }
+  return NS_OK;
+}
+
+OSXSpeechSynthesizerService*
+OSXSpeechSynthesizerService::GetInstance()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return nullptr;
+  }
+
+  if (!sSingleton) {
+    sSingleton = new OSXSpeechSynthesizerService();
+  }
+  return sSingleton;
+}
+
+already_AddRefed<OSXSpeechSynthesizerService>
+OSXSpeechSynthesizerService::GetInstanceForService()
+{
+  RefPtr<OSXSpeechSynthesizerService> speechService = GetInstance();
+  return speechService.forget();
+}
+
+void
+OSXSpeechSynthesizerService::Shutdown()
+{
+  if (!sSingleton) {
+    return;
+  }
+  sSingleton = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+SOURCES += [
+  'OSXSpeechSynthesizerModule.cpp',
+  'OSXSpeechSynthesizerService.mm'
+]
+
+FINAL_LIBRARY = 'xul'
--- a/dom/media/webspeech/synth/moz.build
+++ b/dom/media/webspeech/synth/moz.build
@@ -41,16 +41,19 @@ if CONFIG['MOZ_WEBSPEECH']:
             'test/nsFakeSynthServices.cpp'
         ]
 
     DIRS = []
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
         DIRS += ['windows']
 
+    if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+        DIRS += ['cocoa']
+
     if CONFIG['MOZ_SYNTH_SPEECHD']:
         DIRS += ['speechd']
 
     if CONFIG['MOZ_SYNTH_PICO']:
         DIRS += ['pico']
 
 IPDL_SOURCES += [
     'ipc/PSpeechSynthesis.ipdl',
--- a/dom/mobilemessage/android/SmsManager.cpp
+++ b/dom/mobilemessage/android/SmsManager.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/mobilemessage/SmsParent.h"
 #include "mozilla/dom/mobilemessage/SmsTypes.h"
 #include "mozilla/dom/mobilemessage/Types.h"
 #include "mozilla/dom/MobileMessageThread.h"
 #include "mozilla/dom/SmsMessage.h"
 #include "mozilla/Services.h"
 #include "nsIMobileMessageDatabaseService.h"
 #include "nsIObserverService.h"
+#include "nsThreadUtils.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::dom::mobilemessage;
 
 namespace mozilla {
 
 /*static*/
 void
--- a/dom/plugins/test/mochitest/browser.ini
+++ b/dom/plugins/test/mochitest/browser.ini
@@ -1,12 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
   plugin_test.html
+  plugin_subframe_test.html
+  plugin_no_scroll_div.html
 
 [browser_bug1163570.js]
 skip-if = (!e10s || os != "win")
 [browser_bug1196539.js]
 skip-if = (!e10s || os != "win")
 [browser_tabswitchbetweenplugins.js]
 skip-if = (!e10s || os != "win")
 [browser_pluginscroll.js]
--- a/dom/plugins/test/mochitest/browser_pluginscroll.js
+++ b/dom/plugins/test/mochitest/browser_pluginscroll.js
@@ -35,17 +35,17 @@ add_task(function*() {
                ["general.smoothScroll.other.durationMinMS", 1999],
                ["general.smoothScroll.mouseWheel.durationMaxMS", 2000],
                ["general.smoothScroll.mouseWheel.durationMinMS", 1999],
              ]}, resolve);
   });
 });
 
 /*
- * test plugin visibility when scrolling with scroll wheel and apz.
+ * test plugin visibility when scrolling with scroll wheel and apz in a top level document.
  */
 
 add_task(function* () {
   let result;
 
   if (!apzEnabled) {
     ok(true, "nothing to test, need apz");
     return;
@@ -100,20 +100,84 @@ add_task(function* () {
     return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
   });
   is(result, true, "plugin is visible");
 
   gBrowser.removeTab(pluginTab);
 });
 
 /*
- * test visibility when scrolling with keyboard shortcuts. This circumvents apz
- * and relies on dom scroll, which is what we want to target for this test. Note
- * this test should only run with e10s since we do not hide plugin windows when
- * scrolling in single process mode.
+ * test plugin visibility when scrolling with scroll wheel and apz in a sub document.
+ */
+
+add_task(function* () {
+  let result;
+
+  if (!apzEnabled) {
+    ok(true, "nothing to test, need apz");
+    return;
+  }
+
+  if (!pluginHideEnabled) {
+    ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll");
+    return;
+  }
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+  let testTab = gBrowser.selectedTab;
+  let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_subframe_test.html");
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return !!plugin;
+  });
+  is(result, true, "plugin is loaded");
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, true, "plugin is visible");
+
+  let nativeId = nativeVerticalWheelEventMsg();
+  let utils = SpecialPowers.getDOMWindowUtils(window);
+  let screenCoords = coordinatesRelativeToWindow(10, 10,
+                                                 gBrowser.selectedBrowser);
+  utils.sendNativeMouseScrollEvent(screenCoords.x, screenCoords.y,
+                                   nativeId, 0, -50, 0, 0, 0,
+                                   gBrowser.selectedBrowser);
+
+  yield waitScrollStart(gBrowser.selectedBrowser);
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, false, "plugin is hidden");
+
+  yield waitScrollFinish(gBrowser.selectedBrowser);
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, true, "plugin is visible");
+
+  gBrowser.removeTab(pluginTab);
+});
+
+/*
+ * test visibility when scrolling with keyboard shortcuts for a top level document.
+ * This circumvents apz and relies on dom scroll, which is what we want to target
+ * for this test.
  */
 
 add_task(function* () {
   let result;
 
   if (!pluginHideEnabled) {
     ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll");
     return;
@@ -166,8 +230,73 @@ add_task(function* () {
     let doc = content.document;
     let plugin = doc.getElementById("testplugin");
     return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
   });
   is(result, true, "plugin is visible");
 
   gBrowser.removeTab(pluginTab);
 });
+
+/*
+ * test visibility when scrolling with keyboard shortcuts for a sub document.
+ */
+
+add_task(function* () {
+  let result;
+
+  if (!pluginHideEnabled) {
+    ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll");
+    return;
+  }
+
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+  let testTab = gBrowser.selectedTab;
+  let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_subframe_test.html");
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return !!plugin;
+  });
+  is(result, true, "plugin is loaded");
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, true, "plugin is visible");
+
+  EventUtils.synthesizeKey("VK_END", {});
+
+  yield waitScrollStart(gBrowser.selectedBrowser);
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, false, "plugin is hidden");
+
+  yield waitScrollFinish(gBrowser.selectedBrowser);
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, false, "plugin is hidden");
+
+  EventUtils.synthesizeKey("VK_HOME", {});
+
+  yield waitScrollFinish(gBrowser.selectedBrowser);
+
+  result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() {
+    let doc = content.document.getElementById("subframe").contentDocument;
+    let plugin = doc.getElementById("testplugin");
+    return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible();
+  });
+  is(result, true, "plugin is visible");
+
+  gBrowser.removeTab(pluginTab);
+});
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/plugin_no_scroll_div.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+  <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window"
+         style="position:absolute; top:5px; left:5px; width:500px; height:250px">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/plugin_subframe_test.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+  <iframe id="subframe" style="width:510px; height:260px;" src="plugin_no_scroll_div.html"></iframe>
+  <div style="display:block; height:3000px;"></div>
+</body>
+</html>
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -193,23 +193,24 @@ CSPService::ShouldLoad(uint32_t aContent
     // to be reported, and thus fallback to the slow path.
     if (*aDecision == nsIContentPolicy::ACCEPT) {
       return NS_OK;
     }
   }
 
   // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
 
-  // find the principal of the document that initiated this request and see
-  // if it has a CSP policy object
+  // query the principal of the document; if no document is passed, then
+  // fall back to using the requestPrincipal (e.g. service workers do not
+  // pass a document).
   nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
-  nsCOMPtr<nsIPrincipal> principal;
-  nsCOMPtr<nsIContentSecurityPolicy> csp;
-  if (node) {
-    principal = node->NodePrincipal();
+  nsCOMPtr<nsIPrincipal> principal = node ? node->NodePrincipal()
+                                          : aRequestPrincipal;
+  if (principal) {
+    nsCOMPtr<nsIContentSecurityPolicy> csp;
     principal->GetCsp(getter_AddRefs(csp));
 
     if (csp) {
       if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
         uint32_t numPolicies = 0;
         nsresult rv = csp->GetPolicyCount(&numPolicies);
         if (NS_SUCCEEDED(rv)) {
           for (uint32_t i=0; i<numPolicies; i++) {
@@ -231,17 +232,17 @@ CSPService::ShouldLoad(uint32_t aContent
                       nullptr,
                       aDecision);
     }
   }
   else if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
     nsAutoCString uriSpec;
     aContentLocation->GetSpec(uriSpec);
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
-           ("COULD NOT get nsINode for location: %s", uriSpec.get()));
+           ("COULD NOT get nsIPrincipal for location: %s", uriSpec.get()));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CSPService::ShouldProcess(uint32_t         aContentType,
                           nsIURI           *aContentLocation,
@@ -308,18 +309,17 @@ CSPService::AsyncOnChannelRedirect(nsICh
    * information set in the LoadInfo when channels are created.
    *
    * We check if the CSP permits this host for this type of load, if not,
    * we cancel the load now.
    */
   nsCOMPtr<nsIURI> originalUri;
   rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
   NS_ENSURE_SUCCESS(rv, rv);
-  nsContentPolicyType policyType =
-    nsContentUtils::InternalContentPolicyTypeToExternal(loadInfo->GetContentPolicyType());
+  nsContentPolicyType policyType = loadInfo->GetExternalContentPolicyType();
 
   int16_t aDecision = nsIContentPolicy::ACCEPT;
   csp->ShouldLoad(policyType,     // load type per nsIContentPolicy (uint32_t)
                   newUri,         // nsIURI
                   nullptr,        // nsIURI
                   nullptr,        // nsISupports
                   EmptyCString(), // ACString - MIME guess
                   originalUri,    // aMimeTypeGuess
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -79,16 +79,23 @@ DoSOPChecks(nsIURI* aURI, nsILoadInfo* a
   if (aLoadInfo->GetAllowChrome() && SchemeIs(aURI, "chrome")) {
     // Enforce same-origin policy, except to chrome.
     return DoCheckLoadURIChecks(aURI, aLoadInfo);
   }
 
   nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
   bool sameOriginDataInherits =
     aLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+
+  if (sameOriginDataInherits &&
+      aLoadInfo->GetAboutBlankInherits() &&
+      NS_IsAboutBlank(aURI)) {
+    return NS_OK;
+  }
+
   return loadingPrincipal->CheckMayLoad(aURI,
                                         true, // report to console
                                         sameOriginDataInherits);
 }
 
 nsresult
 DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo,
              nsCOMPtr<nsIStreamListener>& aInAndOutListener)
@@ -106,30 +113,36 @@ DoCORSChecks(nsIChannel* aChannel, nsILo
   NS_ENSURE_SUCCESS(rv, rv);
   aInAndOutListener = corsListener;
   return NS_OK;
 }
 
 nsresult
 DoContentSecurityChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
 {
-  nsContentPolicyType contentPolicyType = aLoadInfo->GetContentPolicyType();
+  nsContentPolicyType contentPolicyType =
+    aLoadInfo->GetExternalContentPolicyType();
+  nsContentPolicyType internalContentPolicyType =
+    aLoadInfo->InternalContentPolicyType();
   nsCString mimeTypeGuess;
   nsCOMPtr<nsINode> requestingContext = nullptr;
-  nsContentPolicyType internalContentPolicyType =
-    aLoadInfo->InternalContentPolicyType();
 
   switch(contentPolicyType) {
     case nsIContentPolicy::TYPE_OTHER: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
-    case nsIContentPolicy::TYPE_SCRIPT:
+    case nsIContentPolicy::TYPE_SCRIPT: {
+      mimeTypeGuess = NS_LITERAL_CSTRING("application/javascript");
+      requestingContext = aLoadInfo->LoadingNode();
+      break;
+    }
+
     case nsIContentPolicy::TYPE_IMAGE:
     case nsIContentPolicy::TYPE_STYLESHEET:
     case nsIContentPolicy::TYPE_OBJECT:
     case nsIContentPolicy::TYPE_DOCUMENT: {
       MOZ_ASSERT(false, "contentPolicyType not supported yet");
       break;
     }
 
@@ -161,19 +174,23 @@ DoContentSecurityChecks(nsIURI* aURI, ns
 
     case nsIContentPolicy::TYPE_XMLHTTPREQUEST: {
       // alias nsIContentPolicy::TYPE_DATAREQUEST:
       requestingContext = aLoadInfo->LoadingNode();
       MOZ_ASSERT(!requestingContext ||
                  requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
                  "type_xml requires requestingContext of type Document");
 
+      // We're checking for the external TYPE_XMLHTTPREQUEST here in case
+      // an addon creates a request with that type.
       if (internalContentPolicyType ==
-            nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST) {
-        mimeTypeGuess = NS_LITERAL_CSTRING("application/xml");
+            nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST ||
+          internalContentPolicyType ==
+            nsIContentPolicy::TYPE_XMLHTTPREQUEST) {
+        mimeTypeGuess = EmptyCString();
       }
       else {
         MOZ_ASSERT(internalContentPolicyType ==
                    nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
                    "can not set mime type guess for unexpected internal type");
         mimeTypeGuess = NS_LITERAL_CSTRING(TEXT_EVENT_STREAM);
       }
       break;
@@ -227,29 +244,34 @@ DoContentSecurityChecks(nsIURI* aURI, ns
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       MOZ_ASSERT(!requestingContext ||
                  requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
                  "type_beacon requires requestingContext of type Document");
       break;
     }
 
-    case nsIContentPolicy::TYPE_FETCH:
+    case nsIContentPolicy::TYPE_FETCH: {
+      mimeTypeGuess = EmptyCString();
+      requestingContext = aLoadInfo->LoadingNode();
+      break;
+    }
+
     case nsIContentPolicy::TYPE_IMAGESET: {
       MOZ_ASSERT(false, "contentPolicyType not supported yet");
       break;
     }
 
     default:
       // nsIContentPolicy::TYPE_INVALID
       MOZ_ASSERT(false, "can not perform security check without a valid contentType");
   }
 
   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-  nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
+  nsresult rv = NS_CheckContentLoadPolicy(internalContentPolicyType,
                                           aURI,
                                           aLoadInfo->LoadingPrincipal(),
                                           requestingContext,
                                           mimeTypeGuess,
                                           nullptr,        //extra,
                                           &shouldLoad,
                                           nsContentUtils::GetContentPolicy(),
                                           nsContentUtils::GetSecurityManager());
--- a/dom/security/test/cors/file_CrossSiteXHR_server.sjs
+++ b/dom/security/test/cors/file_CrossSiteXHR_server.sjs
@@ -140,17 +140,17 @@ function handleRequest(request, response
         response.setHeader(responseHeader, responseHeaders[responseHeader]);
       }
     }
 
     if (query.exposeHeaders)
       response.setHeader("Access-Control-Expose-Headers", query.exposeHeaders);
   }
 
-  if (query.hop && query.hop < hops.length) {
+  if (!isPreflight && query.hop && query.hop < hops.length) {
     newURL = hops[query.hop].server +
              "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?" +
              "hop=" + (query.hop + 1) + "&hops=" + escape(query.hops);
     response.setStatusLine(null, 307, "redirect");
     response.setHeader("Location", newURL);
 
     return;
   }
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_service_worker.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1208559 - ServiceWorker registration not governed by CSP</title>
+</head>
+<body>
+<script>
+  function finish(status) {
+    window.parent.postMessage({result: status}, "*");
+  }
+
+  navigator.serviceWorker.ready.then(finish.bind(null, 'allowed'),
+                                     finish.bind(null, 'blocked'));
+  navigator.serviceWorker
+           .register("file_service_worker.js", {scope: "."})
+           .then(null, finish.bind(null, 'blocked'));
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_service_worker.js
@@ -0,0 +1,1 @@
+dump("service workers: hello world");
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -125,16 +125,18 @@ support-files =
   file_upgrade_insecure_reporting_server.sjs
   file_upgrade_insecure_referrer.html
   file_upgrade_insecure_referrer_server.sjs
   file_upgrade_insecure_cors.html
   file_upgrade_insecure_cors_server.sjs
   file_report_for_import.css
   file_report_for_import.html
   file_report_for_import_server.sjs
+  file_service_worker.html
+  file_service_worker.js
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
 [test_allow_https_schemes.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_bug663567.html]
@@ -192,8 +194,10 @@ skip-if = buildapp == 'b2g' || buildapp 
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android'
 [test_upgrade_insecure_referrer.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android'
 [test_upgrade_insecure_cors.html]
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android'
 [test_report_for_import.html]
 [test_blocked_uri_in_reports.html]
 skip-if = e10s || buildapp == 'b2g' # http-on-opening-request observer not supported in child process (bug 1009632)
+[test_service_worker.html]
+skip-if = buildapp == 'b2g' #no ssl support
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_service_worker.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1208559 - ServiceWorker registration not governed by CSP</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Spawning a worker from https://example.com but script-src is 'test1.example.com'
+ * CSP is not consulted
+ */
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+  {
+    policy: "default-src 'self'; script-src test1.example.com 'unsafe-inline'",
+    expected: "blocked"
+  },
+];
+
+var counter = 0;
+var curTest;
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+  is(event.data.result, curTest.expected, "Should be (" + curTest.expected + ") in Test " + counter + "!");
+  loadNextTest();
+}
+
+onload = function() {
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true],
+    ["dom.caches.enabled", true]
+  ]}, loadNextTest);
+}
+
+function loadNextTest() {
+  if (counter == tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+  curTest = tests[counter++];
+  var src = "https://example.com/tests/dom/security/test/csp/file_testserver.sjs";
+  // append the file that should be served
+  src += "?file=" + escape("tests/dom/security/test/csp/file_service_worker.html");
+  // append the CSP that should be used to serve the file
+  src += "&csp=" + escape(curTest.policy);
+  document.getElementById("testframe").src = src;
+}
+
+</script>
+</body>
+</html>
--- a/dom/security/test/mixedcontentblocker/file_main.html
+++ b/dom/security/test/mixedcontentblocker/file_main.html
@@ -150,39 +150,30 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
   iframe.onerror = function() {
     parent.postMessage({"test": "iframe", "msg": "insecure iframe blocked"}, "http://mochi.test:8888");
   };
   testContent.appendChild(iframe);
 
 
   // Test 1e: insecure xhr
-  var xhrsuccess = true;
   var xhr = new XMLHttpRequest;
   try {
     xhr.open("GET", baseUrl + "?type=xhr", true);
-  } catch(ex) {
-     xhrsuccess = false;
-     parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
-  }
-
-  if(xhrsuccess) {
-    xhr.onreadystatechange = function (oEvent) {
-      var result = false;
-      if (xhr.readyState == 4) {
-        if (xhr.status == 200) {
-          parent.postMessage({"test": "xhr", "msg": "insecure xhr loaded"}, "http://mochi.test:8888");
-        }
-        else {
-          parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
-        }
+    xhr.send();
+    xhr.onloadend = function (oEvent) {
+      if (xhr.status == 200) {
+        parent.postMessage({"test": "xhr", "msg": "insecure xhr loaded"}, "http://mochi.test:8888");
+      }
+      else {
+        parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
       }
     }
-
-    xhr.send(null);
+  } catch(ex) {
+     parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
   }
 
   /* Part 2: Mixed Display tests */
 
   // Shorthand for all image test variants
   function imgHandlers(img, test) {
     img.onload = function () {
       parent.postMessage({"test": test, "msg": "insecure image loaded"}, "http://mochi.test:8888");
--- a/dom/svg/SVGDocument.cpp
+++ b/dom/svg/SVGDocument.cpp
@@ -97,16 +97,24 @@ SVGDocument::Clone(mozilla::dom::NodeInf
 
 void
 SVGDocument::EnsureNonSVGUserAgentStyleSheetsLoaded()
 {
   if (mHasLoadedNonSVGUserAgentStyleSheets) {
     return;
   }
 
+  if (IsStaticDocument()) {
+    // If we're a static clone of a document, then
+    // nsIDocument::CreateStaticClone will handle cloning the original
+    // document's sheets, including the on-demand non-SVG UA sheets,
+    // for us.
+    return;
+  }
+
   mHasLoadedNonSVGUserAgentStyleSheets = true;
 
   BeginUpdate(UPDATE_STYLE);
 
   if (IsBeingUsedAsImage()) {
     // nsDocumentViewer::CreateStyleSet skipped loading all user-agent/user
     // style sheets in this case, but we'll need B2G/Fennec's
     // content.css. We could load all the sheets registered with the
--- a/dom/tests/mochitest/dom-level2-core/exclusions.js
+++ b/dom/tests/mochitest/dom-level2-core/exclusions.js
@@ -8,17 +8,17 @@ dtdTests = ["attrgetownerelement01", "do
             "documentimportnode04", "documentimportnode19",
             "documentimportnode20", "documentimportnode21",
             "documentimportnode22",
             "elementgetattributenodens03", "elementgetattributens02",
             "elementhasattribute02", "getAttributeNS01", "getElementById01",
             "getNamedItemNS03", "getNamedItemNS04", "hasAttribute02",
             "hasAttributeNS04", "importNode07", "importNode09",
             "importNode10", "importNode11", "importNode12", "importNode13",
-            "internalSubset01", "localName02", "namednodemapgetnameditemns01",
+            "localName02", "namednodemapgetnameditemns01",
             "namednodemapremovenameditemns02",
             "namednodemapremovenameditemns05", "namednodemapsetnameditemns05",
             "namednodemapsetnameditemns09", "namednodemapsetnameditemns10",
             "namednodemapsetnameditemns11", "namespaceURI01", 
             "nodeissupported04", "nodenormalize01", "nodesetprefix04",
             "prefix08", "removeAttributeNS01", "removeAttributeNS02",
             "removeNamedItemNS03", "setAttributeNodeNS02", "setAttributeNS03",
             "setNamedItemNS04"];
--- a/dom/tests/mochitest/dom-level2-core/mochitest.ini
+++ b/dom/tests/mochitest/dom-level2-core/mochitest.ini
@@ -88,17 +88,16 @@ support-files =
 [test_documentimportnode14.html]
 [test_documentimportnode15.html]
 [test_documentimportnode17.html]
 [test_documentimportnode18.html]
 [test_documentimportnode19.html]
 [test_documentimportnode20.html]
 [test_documentimportnode21.html]
 [test_documentimportnode22.html]
-[test_documenttypeinternalSubset01.html]
 [test_documenttypepublicid01.html]
 [test_documenttypesystemid01.html]
 [test_domimplementationcreatedocument03.html]
 [test_domimplementationcreatedocument04.html]
 [test_domimplementationcreatedocument05.html]
 [test_domimplementationcreatedocument07.html]
 [test_domimplementationcreatedocumenttype01.html]
 [test_domimplementationcreatedocumenttype02.html]
@@ -187,17 +186,16 @@ support-files =
 [test_importNode10.html]
 [test_importNode11.html]
 [test_importNode12.html]
 [test_importNode13.html]
 [test_importNode14.html]
 [test_importNode15.html]
 [test_importNode16.html]
 [test_importNode17.html]
-[test_internalSubset01.html]
 [test_localName01.html]
 [test_localName02.html]
 [test_localName03.html]
 [test_localName04.html]
 [test_namednodemapgetnameditemns01.html]
 [test_namednodemapgetnameditemns02.html]
 [test_namednodemapgetnameditemns03.html]
 [test_namednodemapgetnameditemns04.html]
deleted file mode 100644
--- a/dom/tests/mochitest/dom-level2-core/test_documenttypeinternalSubset01.html
+++ /dev/null
@@ -1,123 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
-<html>
-<head>
-<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<title>http://www.w3.org/2001/DOM-Test-Suite/level2/core/documenttypeinternalSubset01</title>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script type="text/javascript" src="DOMTestCase.js"></script>
-<script type="text/javascript" src="exclusions.js"></script>
-<script type="text/javascript">
-// expose test function names
-function exposeTestFunctionNames()
-{
-return ['documenttypeinternalSubset01'];
-}
-
-var docsLoaded = -1000000;
-var builder = null;
-
-//
-//   This function is called by the testing framework before
-//      running the test suite.
-//
-//   If there are no configuration exceptions, asynchronous
-//        document loading is started.  Otherwise, the status
-//        is set to complete and the exception is immediately
-//        raised when entering the body of the test.
-//
-function setUpPage() {
-   setUpPageStatus = 'running';
-   try {
-     //
-     //   creates test document builder, may throw exception
-     //
-     builder = createConfiguredBuilder();
-
-      docsLoaded = 0;
-      
-      var docRef = null;
-      if (typeof(this.doc) != 'undefined') {
-        docRef = this.doc;
-      }
-      docsLoaded += preload(docRef, "doc", "staffNS");
-        
-       if (docsLoaded == 1) {
-          setUpPage = 'complete';
-       }
-    } catch(ex) {
-    	catchInitializationError(builder, ex);
-        setUpPage = 'complete';
-    }
-}
-
-//
-//   This method is called on the completion of 
-//      each asychronous load started in setUpTests.
-//
-//   When every synchronous loaded document has completed,
-//      the page status is changed which allows the
-//      body of the test to be executed.
-function loadComplete() {
-  if (++docsLoaded == 1) {
-    setUpPageStatus = 'complete';
-    runJSUnitTests();
-    markTodos();
-    SimpleTest.finish();
-  }
-}
-
-var docName = 'documenttypeinternalSubset01';
-
-
-/**
-* 
-    The method getInternalSubset() returns the internal subset as a string. 
-  
-    Create a new DocumentType node with null values for publicId and systemId.
-    Verify that its internal subset is null.
-
-* @author IBM
-* @author Neil Delima
-* @see http://www.w3.org/TR/DOM-Level-2-Core/core#ID-Core-DocType-internalSubset
-* @see http://www.w3.org/Bugs/Public/show_bug.cgi?id=259
-*/
-function documenttypeinternalSubset01() {
-   var success;
-    if(checkInitialization(builder, "documenttypeinternalSubset01") != null) return;
-    var doc;
-      var docType;
-      var domImpl;
-      var internal;
-      var nullNS = null;
-
-      
-      var docRef = null;
-      if (typeof(this.doc) != 'undefined') {
-        docRef = this.doc;
-      }
-      doc = load(docRef, "doc", "staffNS");
-      domImpl = doc.implementation;
-docType = domImpl.createDocumentType("l2:root",nullNS,nullNS);
-      internal = docType.internalSubset;
-
-      assertNull("internalSubsetNull",internal);
-    
-}
-
-</script>
-</head>
-<body>
-<h2>Test http://www.w3.org/2001/DOM-Test-Suite/level2/core/documenttypeinternalSubset01</h2>
-<p></p>
-<p>
-Copyright (c) 2001-2004 World Wide Web Consortium, 
-(Massachusetts Institute of Technology, European Research Consortium 
-for Informatics and Mathematics, Keio University). All 
-Rights Reserved. This work is distributed under the <a href="http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231">W3C(r) Software License</a> in the 
-hope that it will be useful, but WITHOUT ANY WARRANTY; without even 
-the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
-</p>
-</body>
-</html>
deleted file mode 100644
--- a/dom/tests/mochitest/dom-level2-core/test_internalSubset01.html
+++ /dev/null
@@ -1,122 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
-<html>
-<head>
-<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<title>http://www.w3.org/2001/DOM-Test-Suite/level2/core/internalSubset01</title>
-<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script type="text/javascript" src="DOMTestCase.js"></script>
-<script type="text/javascript" src="exclusions.js"></script>
-<script type="text/javascript">
-// expose test function names
-function exposeTestFunctionNames()
-{
-return ['internalSubset01'];
-}
-
-var docsLoaded = -1000000;
-var builder = null;
-
-//
-//   This function is called by the testing framework before
-//      running the test suite.
-//
-//   If there are no configuration exceptions, asynchronous
-//        document loading is started.  Otherwise, the status
-//        is set to complete and the exception is immediately
-//        raised when entering the body of the test.
-//
-function setUpPage() {
-   setUpPageStatus = 'running';
-   try {
-     //
-     //   creates test document builder, may throw exception
-     //
-     builder = createConfiguredBuilder();
-
-      docsLoaded = 0;
-      
-      var docRef = null;
-      if (typeof(this.doc) != 'undefined') {
-        docRef = this.doc;
-      }
-      docsLoaded += preload(docRef, "doc", "staff2");
-        
-       if (docsLoaded == 1) {
-          setUpPage = 'complete';
-       }
-    } catch(ex) {
-    	catchInitializationError(builder, ex);
-        setUpPage = 'complete';
-    }
-}
-
-//
-//   This method is called on the completion of 
-//      each asychronous load started in setUpTests.
-//
-//   When every synchronous loaded document has completed,
-//      the page status is changed which allows the
-//      body of the test to be executed.
-function loadComplete() {
-  if (++docsLoaded == 1) {
-    setUpPageStatus = 'complete';
-    runJSUnitTests();
-    markTodos();
-    SimpleTest.finish();
-  }
-}
-
-var docName = 'internalSubset01';
-
-
-/**
-* 
-    The "getInternalSubset()" method returns 
-   the internal subset as a string or null if there is none.
-   This does not contain the delimiting brackets.
-   
-   Retrieve the documenttype.
-   Apply the "getInternalSubset()" method.  Null is returned since there 
-   is not an internal subset.
-
-* @author NIST
-* @author Mary Brady
-* @see http://www.w3.org/TR/DOM-Level-2-Core/core#ID-Core-DocType-internalSubset
-*/
-function internalSubset01() {
-   var success;
-    if(checkInitialization(builder, "internalSubset01") != null) return;
-    var doc;
-      var docType;
-      var internal;
-      
-      var docRef = null;
-      if (typeof(this.doc) != 'undefined') {
-        docRef = this.doc;
-      }
-      doc = load(docRef, "doc", "staff2");
-      docType = doc.doctype;
-
-      internal = docType.internalSubset;
-
-      assertNull("internalSubsetNull",internal);
-    
-}
-
-</script>
-</head>
-<body>
-<h2>Test http://www.w3.org/2001/DOM-Test-Suite/level2/core/internalSubset01</h2>
-<p></p>
-<p>
-Copyright (c) 2001-2004 World Wide Web Consortium, 
-(Massachusetts Institute of Technology, European Research Consortium 
-for Informatics and Mathematics, Keio University). All 
-Rights Reserved. This work is distributed under the <a href="http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231">W3C(r) Software License</a> in the 
-hope that it will be useful, but WITHOUT ANY WARRANTY; without even 
-the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
-</p>
-</body>
-</html>
--- a/dom/tests/mochitest/fetch/test_fetch_basic.js
+++ b/dom/tests/mochitest/fetch/test_fetch_basic.js
@@ -1,13 +1,15 @@
 function testAboutURL() {
   var p1 = fetch('about:blank').then(function(res) {
     is(res.status, 200, "about:blank should load a valid Response");
     is(res.headers.get('content-type'), 'text/html;charset=utf-8',
        "about:blank content-type should be text/html;charset=utf-8");
+    is(res.headers.has('content-length'), false,
+       "about:blank should not have a content-length header");
     return res.text().then(function(v) {
       is(v, "", "about:blank body should be empty");
     });
   });
 
   var p2 = fetch('about:config').then(function(res) {
     ok(false, "about:config should fail");
   }, function(e) {
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1244,17 +1244,17 @@ function testRedirects() {
              headers: { "Content-Type": "text/plain" },
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
                       allowOrigin: origin,
                     },
                     ],
            },
-           { pass: 1,
+           { pass: 0,
              method: "POST",
              body: "hi there",
              headers: { "Content-Type": "text/plain",
                         "my-header": "myValue",
                       },
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
@@ -1276,17 +1276,17 @@ function testRedirects() {
                       allowHeaders: "my-header",
                     },
                     { server: "http://test2.example.com",
                       allowOrigin: origin,
                       allowHeaders: "my-header",
                     }
                     ],
            },
-           { pass: 1,
+           { pass: 0,
              method: "DELETE",
              hops: [{ server: "http://mochi.test:8888",
                     },
                     { server: "http://example.com",
                       allowOrigin: origin,
                     },
                     ],
            },
--- a/dom/tests/mochitest/pointerlock/file_removedFromDOM.html
+++ b/dom/tests/mochitest/pointerlock/file_removedFromDOM.html
@@ -1,68 +1,95 @@
 <!DOCTYPE HTML>
 <html>
   <!--
   https://bugzilla.mozilla.org/show_bug.cgi?id=633602
 
   Test DOM tree in full screen
   -->
-  <head>
-    <title>Bug 633602 - file_DOMtree.html</title>
-    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
-    </script>
-    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
-    </script>
-    <script type="application/javascript" src="pointerlock_utils.js"></script>
-    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-    <style>
-    </style>
-  </head>
-  <body>
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
-      Mozilla Bug 633602
-    </a>
-    <div id="div"></div>
-    <pre id="test">
-      <script type="text/javascript">
-        /*
-         * Test for Bug 633602
-         * Checks if pointer is unlocked when element is removed from
-         * the DOM Tree
-         */
+<head>
+<title>Bug 633602 - file_DOMtree.html</title>
+<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript" src="pointerlock_utils.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=633602">
+  Mozilla Bug 633602
+</a>
+<div id="div"></div>
+<div id="outer"><div id="inner"></div></div>
+<pre id="test">
+<script type="text/javascript">
+/*
+  * Test for Bug 633602
+  * Checks if pointer is unlocked when element is removed from
+  * the DOM Tree
+  */
 
-        SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForExplicitFinish();
+
+var div = document.getElementById("div");
+var outer = document.getElementById("outer");
+var inner = document.getElementById("inner");
 
-        var div = document.getElementById("div")
-          , removedDOM = false;
+function listenOneDocEvent(type, handler) {
+  function callback(event) {
+    document.removeEventListener(type, callback);
+    SimpleTest.executeSoon(() => handler(event));
+  }
+  document.addEventListener(type, callback);
+}
 
-        function runTests() {
-          ok(removedDOM, "Pointer should be unlocked when " +
-            "an element is removed the DOM Tree");
-        }
+function checkPointerLockElement(elem) {
+  if (elem) {
+    is(document.mozPointerLockElement, elem,
+       `#${elem.id} should have locked the pointer`);
+  } else {
+    ok(!document.mozPointerLockElement, "Pointer should have been unlocked");
+  }
+}
 
-        document.addEventListener("mozpointerlockchange", function (e) {
-          if (document.mozPointerLockElement === div) {
-            document.body.removeChild(div);
-            removedDOM = !document.mozPointerLockElement;
-            document.mozCancelFullScreen();
-          }
+function start() {
+  listenOneDocEvent("mozfullscreenchange", enteredFullscreen);
+  document.documentElement.mozRequestFullScreen();
+}
 
-        }, false);
+function enteredFullscreen() {
+  is(document.mozFullScreenElement, document.documentElement,
+     "Root element should have entered fullscreen");
+  listenOneDocEvent("mozpointerlockchange", lockedPointerOnDiv);
+  div.mozRequestPointerLock();
+}
 
-        document.addEventListener("mozfullscreenchange", function (e) {
-          if (document.mozFullScreenElement === div) {
-              div.mozRequestPointerLock();
-          }
-          else {
-            runTests();
-            SimpleTest.finish();
-          }
-        }, false);
+function lockedPointerOnDiv() {
+  checkPointerLockElement(div);
+  listenOneDocEvent("mozpointerlockchange", unlockedPointerFromDiv);
+  document.body.removeChild(div);
+}
+
+function unlockedPointerFromDiv() {
+  checkPointerLockElement(null);
+  listenOneDocEvent("mozpointerlockchange", lockedPointerOnInner);
+  inner.mozRequestPointerLock();
+}
 
-        function start() {
-          div.mozRequestFullScreen();
-        }
-      </script>
-    </pre>
-  </body>
+function lockedPointerOnInner() {
+  checkPointerLockElement(inner);
+  listenOneDocEvent("mozpointerlockchange", unlockedPointerFromInner);
+  document.body.removeChild(outer);
+}
+
+function unlockedPointerFromInner() {
+  checkPointerLockElement(null);
+  listenOneDocEvent("mozfullscreenchange", exitedFullscreen);
+  document.mozCancelFullScreen();
+}
+
+function exitedFullscreen() {
+  SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
 </html>
--- a/dom/webidl/DocumentType.webidl
+++ b/dom/webidl/DocumentType.webidl
@@ -9,14 +9,11 @@
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 interface DocumentType : Node {
   readonly attribute DOMString name;
   readonly attribute DOMString publicId;
   readonly attribute DOMString systemId;
-
-  // Mozilla extension
-  readonly attribute DOMString? internalSubset;
 };
 
 DocumentType implements ChildNode;
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -54,16 +54,18 @@ interface Element : Node {
   [Pure]
   boolean hasAttributes();
 
   [Throws, Pure]
   Element? closest(DOMString selector);
 
   [Throws, Pure]
   boolean matches(DOMString selector);
+  [Throws, Pure, BinaryName="matches"]
+  boolean webkitMatchesSelector(DOMString selector);
 
   [Pure]
   HTMLCollection getElementsByTagName(DOMString localName);
   [Throws, Pure]
   HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
   [Pure]
   HTMLCollection getElementsByClassName(DOMString classNames);
 
@@ -87,17 +89,17 @@ interface Element : Node {
 
   // Selectors API
   /**
    * Returns whether this element would be selected by the given selector
    * string.
    *
    * See <http://dev.w3.org/2006/webapi/selectors-api2/#matchesselector>
    */
-  [Throws, Pure]
+  [Throws, Pure, BinaryName="matches"]
   boolean mozMatchesSelector(DOMString selector);
 
   // Pointer events methods.
   [Throws, Pref="dom.w3c_pointer_events.enabled", UnsafeInPrerendering]
   void setPointerCapture(long pointerId);
 
   [Throws, Pref="dom.w3c_pointer_events.enabled"]
   void releasePointerCapture(long pointerId);
--- a/dom/webidl/IterableIterator.webidl
+++ b/dom/webidl/IterableIterator.webidl
@@ -1,22 +1,14 @@
 /* -*- 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/.
  */
 
-[NoInterfaceObject,
- Exposed=(Window,Worker,System)]
-interface IterableIterator
-{
-  [Throws]
-  object next();
-};
-
 dictionary IterableKeyOrValueResult {
   any value;
   boolean done = false;
 };
 
 dictionary IterableKeyAndValueResult {
   sequence<any> value = [];
   boolean done = false;
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -49,18 +49,12 @@ dictionary RequestInit {
 enum RequestContext {
   "audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch",
   "font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import",
   "internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script",
   "sharedworker", "subresource", "style", "track", "video", "worker", "xmlhttprequest",
   "xslt"
 };
 
-// cors-with-forced-preflight is internal to the Fetch spec, but adding it here
-// allows us to use the various conversion conveniences offered by the WebIDL
-// codegen. The Request constructor has explicit checks to prevent it being
-// passed as a valid value, while Request.mode never returns it. Since enums
-// are only exposed as strings to client JS, this has the same effect as not
-// exposing it at all.
-enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" };
+enum RequestMode { "same-origin", "no-cors", "cors" };
 enum RequestCredentials { "omit", "same-origin", "include" };
 enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
 enum RequestRedirect { "follow", "error", "manual" };
--- a/dom/webidl/URLSearchParams.webidl
+++ b/dom/webidl/URLSearchParams.webidl
@@ -18,11 +18,11 @@
  Exposed=(Window,Worker,System)]
 interface URLSearchParams {
   void append(USVString name, USVString value);
   void delete(USVString name);
   USVString? get(USVString name);
   sequence<USVString> getAll(USVString name);
   boolean has(USVString name);
   void set(USVString name, USVString value);
-  // iterable<USVString, USVString>; - Bug 1085284
+  iterable<USVString, USVString>;
   stringifier;
 };
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -44,16 +44,17 @@
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/RootedDictionary.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/unused.h"
 #include "mozilla/EnumSet.h"
 
+#include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsNetUtil.h"
 #include "nsIURL.h"
 #include "nsProxyRelease.h"
 #include "nsQueryObject.h"
 #include "nsTArray.h"
 
@@ -92,18 +93,16 @@ BEGIN_WORKERS_NAMESPACE
 #define CLEAR_ORIGIN_DATA "clear-origin-data"
 
 static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
-static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight),
-              "RequestMode enumeration value should match Necko CORS mode value.");
 
 static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow),
               "RequestRedirect enumeration value should make Necko Redirect mode value.");
 static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error),
               "RequestRedirect enumeration value should make Necko Redirect mode value.");
 static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual),
               "RequestRedirect enumeration value should make Necko Redirect mode value.");
 static_assert(3 == static_cast<uint32_t>(RequestRedirect::EndGuard_),
@@ -1432,16 +1431,31 @@ ServiceWorkerManager::Register(nsIDOMWin
   nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal();
 
   nsresult rv = documentPrincipal->CheckMayLoad(aScriptURI, true /* report */,
                                                 false /* allowIfInheritsPrincipal */);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
+  // Check content policy.
+  int16_t decision = nsIContentPolicy::ACCEPT;
+  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
+                                 aScriptURI,
+                                 documentPrincipal,
+                                 doc,
+                                 EmptyCString(),
+                                 nullptr,
+                                 &decision);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
+    return NS_ERROR_CONTENT_BLOCKED;
+  }
+
+
   rv = documentPrincipal->CheckMayLoad(aScopeURI, true /* report */,
                                        false /* allowIfInheritsPrinciple */);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsCString cleanedScope;
   rv = aScopeURI->GetSpecIgnoringRef(cleanedScope);
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -110,18 +110,18 @@ public:
       return rv;
     }
 
     // Note that because there is no "serviceworker" RequestContext type, we can
     // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
     // worker.
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        uri, aPrincipal,
-                       nsILoadInfo::SEC_NORMAL,
-                       nsIContentPolicy::TYPE_INTERNAL_SCRIPT,
+                       nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+                       nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
                        loadGroup,
                        nullptr, // aCallbacks
                        nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsLoadFlags flags;
@@ -143,17 +143,17 @@ public:
     }
 
     nsCOMPtr<nsIStreamLoader> loader;
     rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = mChannel->AsyncOpen(loader, nullptr);
+    rv = mChannel->AsyncOpen2(loader);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     return NS_OK;
   }
 
   void
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/embedder.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+  window.onmessage = function(e) {
+    window.parent.postMessage(e.data, "*");
+  };
+</script>
+<iframe src="http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/index.html"></iframe>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/hsts_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function(event) {
+  if (event.request.url.indexOf("index.html") >= 0) {
+    event.respondWith(fetch("realindex.html"));
+  } else if (event.request.url.indexOf("image-20px.png") >= 0) {
+    if (event.request.url.indexOf("https://") == 0) {
+      event.respondWith(fetch("image-40px.png"));
+    } else {
+      event.respondWith(Response.error());
+    }
+  }
+});
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae6a8a6b88403959c75efce931b0bf4293efc956
GIT binary patch
literal 87
zc%17D@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=oTrOph(&MmkMjpU%%3$Q@ydZf
kW_Mm0(}F7pCT1xxd^;`67yW*X5Ktw9r>mdKI;Vst0D!m{_W%F@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fe391dc8a2d797360651fe8cf77161a3fc891194
GIT binary patch
literal 123
zc%17D@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEPM$7~ArY-_&utWBP~c%$*z@jL
z8rOSWjTbX5i}z1sXJLKaupmKJKx7SbQ&Xu!zy>}Ju4{~r2dxw|BEXUllDQ?<M0xZq
S5i$mv#^CAd=d#Wzp$PyI1tgjP
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload=function(){
+  var img = new Image();
+  img.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/image-20px.png";
+  img.onload = function() {
+    window.parent.postMessage({status: "image", data: img.width}, "*");
+  };
+  img.onerror = function() {
+    window.parent.postMessage({status: "image", data: "error"}, "*");
+  };
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/realindex.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+  window.parent.postMessage({status: "protocol", data: location.protocol}, "*");
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+  function ok(v, msg) {
+    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+  }
+
+  function done(reg) {
+    ok(reg.active, "The active worker should be available.");
+    window.parent.postMessage({status: "registrationdone"}, "*");
+  }
+
+  navigator.serviceWorker.ready.then(done);
+  navigator.serviceWorker.register("hsts_test.js", {scope: "."});
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/register.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Strict-Transport-Security: max-age=60
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/hsts/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.getRegistration(".").then(function(registration) {
+    registration.unregister().then(function(success) {
+      if (success) {
+        window.parent.postMessage({status: "unregistrationdone"}, "*");
+      }
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script>
+  window.onmessage = function(e) {
+    window.parent.postMessage(e.data, "*");
+    if (e.data.status == "protocol") {
+      document.querySelector("iframe").src = "image.html";
+    }
+  };
+</script>
+<iframe src="http://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/index.html"></iframe>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html^headers^
@@ -0,0 +1,1 @@
+Content-Security-Policy: upgrade-insecure-requests
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae6a8a6b88403959c75efce931b0bf4293efc956
GIT binary patch
literal 87
zc%17D@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=oTrOph(&MmkMjpU%%3$Q@ydZf
kW_Mm0(}F7pCT1xxd^;`67yW*X5Ktw9r>mdKI;Vst0D!m{_W%F@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fe391dc8a2d797360651fe8cf77161a3fc891194
GIT binary patch
literal 123
zc%17D@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEPM$7~ArY-_&utWBP~c%$*z@jL
z8rOSWjTbX5i}z1sXJLKaupmKJKx7SbQ&Xu!zy>}Ju4{~r2dxw|BEXUllDQ?<M0xZq
S5i$mv#^CAd=d#Wzp$PyI1tgjP
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+onload=function(){
+  var img = new Image();
+  img.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/image-20px.png";
+  img.onload = function() {
+    window.parent.postMessage({status: "image", data: img.width}, "*");
+  };
+  img.onerror = function() {
+    window.parent.postMessage({status: "image", data: "error"}, "*");
+  };
+};
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/realindex.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+  window.parent.postMessage({status: "protocol", data: location.protocol}, "*");
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script>
+  function ok(v, msg) {
+    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
+  }
+
+  function done(reg) {
+    ok(reg.active, "The active worker should be available.");
+    window.parent.postMessage({status: "registrationdone"}, "*");
+  }
+
+  navigator.serviceWorker.ready.then(done);
+  navigator.serviceWorker.register("upgrade-insecure_test.js", {scope: "."});
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.getRegistration(".").then(function(registration) {
+    registration.unregister().then(function(success) {
+      if (success) {
+        window.parent.postMessage({status: "unregistrationdone"}, "*");
+      }
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/upgrade-insecure/upgrade-insecure_test.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", function(event) {
+  if (event.request.url.indexOf("index.html") >= 0) {
+    event.respondWith(fetch("realindex.html"));
+  } else if (event.request.url.indexOf("image-20px.png") >= 0) {
+    if (event.request.url.indexOf("https://") == 0) {
+      event.respondWith(fetch("image-40px.png"));
+    } else {
+      event.respondWith(Response.error());
+    }
+  }
+});
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -37,16 +37,25 @@ support-files =
   fetch/context/beacon.sjs
   fetch/context/csp-violate.sjs
   fetch/context/ping.html
   fetch/context/worker.js
   fetch/context/parentworker.js
   fetch/context/sharedworker.js
   fetch/context/parentsharedworker.js
   fetch/context/xml.xml
+  fetch/hsts/hsts_test.js
+  fetch/hsts/embedder.html
+  fetch/hsts/image.html
+  fetch/hsts/image-20px.png
+  fetch/hsts/image-40px.png
+  fetch/hsts/realindex.html
+  fetch/hsts/register.html
+  fetch/hsts/register.html^headers^
+  fetch/hsts/unregister.html
   fetch/https/index.html
   fetch/https/register.html
   fetch/https/unregister.html
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
@@ -71,16 +80,25 @@ support-files =
   fetch/requesturl/requesturl_test.js
   fetch/requesturl/secret.html
   fetch/requesturl/unregister.html
   fetch/sandbox/index.html
   fetch/sandbox/intercepted_index.html
   fetch/sandbox/register.html
   fetch/sandbox/unregister.html
   fetch/sandbox/sandbox_test.js
+  fetch/upgrade-insecure/upgrade-insecure_test.js
+  fetch/upgrade-insecure/embedder.html
+  fetch/upgrade-insecure/embedder.html^headers^
+  fetch/upgrade-insecure/image.html
+  fetch/upgrade-insecure/image-20px.png
+  fetch/upgrade-insecure/image-40px.png
+  fetch/upgrade-insecure/realindex.html
+  fetch/upgrade-insecure/register.html
+  fetch/upgrade-insecure/unregister.html
   match_all_properties_worker.js
   match_all_clients/match_all_controlled.html
   test_serviceworker_interfaces.js
   serviceworker_wrapper.js
   message_receiver.html
   close_test.js
   serviceworker_not_sharedworker.js
   match_all_client/match_all_client_id.html
@@ -254,8 +272,12 @@ skip-if = toolkit == "android" || toolki
 [test_opaque_intercept.html]
 [test_fetch_event_client_postmessage.html]
 [test_xslt.html]
 [test_escapedSlashes.html]
 [test_eventsource_intercept.html]
 [test_not_intercept_plugin.html]
 [test_file_blob_upload.html]
 [test_unresolved_fetch_interception.html]
+[test_hsts_upgrade_intercept.html]
+skip-if = e10s # Bug 1214305
+[test_csp_upgrade-insecure_intercept.html]
+skip-if = e10s # Bug 1214305
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_csp_upgrade-insecure_intercept.html
@@ -0,0 +1,56 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that a CSP upgraded request can be intercepted by a service worker</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var iframe;
+  function runTest() {
+    iframe = document.querySelector("iframe");
+    iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/register.html";
+    window.onmessage = function(e) {
+      if (e.data.status == "ok") {
+        ok(e.data.result, e.data.message);
+      } else if (e.data.status == "registrationdone") {
+        iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/embedder.html";
+      } else if (e.data.status == "protocol") {
+        is(e.data.data, "https:", "Correct protocol expected");
+      } else if (e.data.status == "image") {
+        is(e.data.data, 40, "The image request was upgraded before interception");
+        iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/upgrade-insecure/unregister.html";
+      } else if (e.data.status == "unregistrationdone") {
+        window.onmessage = null;
+        SimpleTest.finish();
+      }
+    };
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.serviceWorkers.interception.enabled", true],
+      // This is needed so that we can test upgrading a non-secure load inside an https iframe.
+      ["security.mixed_content.block_active_content", false],
+      ["security.mixed_content.block_display_content", false],
+    ]}, runTest);
+  };
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_hsts_upgrade_intercept.html
@@ -0,0 +1,66 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that an HSTS upgraded request can be intercepted by a service worker</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var iframe;
+  var framesLoaded = 0;
+  function runTest() {
+    iframe = document.querySelector("iframe");
+    iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/register.html";
+    window.onmessage = function(e) {
+      if (e.data.status == "ok") {
+        ok(e.data.result, e.data.message);
+      } else if (e.data.status == "registrationdone") {
+        iframe.src = "http://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/index.html";
+      } else if (e.data.status == "protocol") {
+        is(e.data.data, "https:", "Correct protocol expected");
+        switch (++framesLoaded) {
+        case 1:
+          iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/embedder.html";
+          break;
+        case 2:
+          iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/image.html";
+          break;
+        }
+      } else if (e.data.status == "image") {
+        is(e.data.data, 40, "The image request was upgraded before interception");
+        iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/hsts/unregister.html";
+      } else if (e.data.status == "unregistrationdone") {
+        window.onmessage = null;
+        SpecialPowers.cleanUpSTSData("http://example.com");
+        SimpleTest.finish();
+      }
+    };
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.serviceWorkers.interception.enabled", true],
+      // This is needed so that we can test upgrading a non-secure load inside an https iframe.
+      ["security.mixed_content.block_active_content", false],
+      ["security.mixed_content.block_display_content", false],
+    ]}, runTest);
+  };
+</script>
+</pre>
+</body>
+</html>
--- a/dom/workers/test/test_xhr_responseURL.html
+++ b/dom/workers/test/test_xhr_responseURL.html
@@ -40,26 +40,16 @@ worker.addEventListener("message", funct
     worker.postMessage("pong");
     return;
   }
   if (data.type == "info") {
     SimpleTest.info(data.message);
     worker.postMessage("pong");
     return;
   }
-  if (data.type === "todo") {
-    SimpleTest.todo(data.bool, data.message);
-    worker.postMessage("pong");
-    return;
-  }
-  if (data.type === "todo_is") {
-    SimpleTest.todo_is(data.actual, data.expected, data.message);
-    worker.postMessage("pong");
-    return;
-  }
   if (data.type === "redirect_test") {
     if (data.status === "start") {
       SpecialPowers.addObserver(requestObserver, "specialpowers-http-notify-request", false);
       return;
     }
     if (data.status === "end") {
       SpecialPowers.removeObserver(requestObserver, "specialpowers-http-notify-request");
       return;
--- a/dom/xbl/nsBindingManager.h
+++ b/dom/xbl/nsBindingManager.h
@@ -22,17 +22,16 @@ struct ElementDependentRuleProcessorData
 class nsIXPConnectWrappedJS;
 class nsIAtom;
 class nsIDOMNodeList;
 class nsIDocument;
 class nsIURI;
 class nsXBLDocumentInfo;
 class nsIStreamListener;
 class nsXBLBinding;
-template<class E> class nsRefPtr;
 typedef nsTArray<RefPtr<nsXBLBinding> > nsBindingList;
 class nsIPrincipal;
 class nsITimer;
 
 namespace mozilla {
 class CSSStyleSheet;
 } // namespace mozilla
 
--- a/dom/xbl/nsXBLPrototypeResources.cpp
+++ b/dom/xbl/nsXBLPrototypeResources.cpp
@@ -138,17 +138,17 @@ nsXBLPrototypeResources::ClearLoader()
 {
   mLoader = nullptr;
 }
 
 void
 nsXBLPrototypeResources::GatherRuleProcessor()
 {
   mRuleProcessor = new nsCSSRuleProcessor(mStyleSheetList,
-                                          nsStyleSet::eDocSheet,
+                                          SheetType::Doc,
                                           nullptr,
                                           mRuleProcessor);
 }
 
 void
 nsXBLPrototypeResources::AppendStyleSheet(CSSStyleSheet* aSheet)
 {
   mStyleSheetList.AppendElement(aSheet);
--- a/extensions/spellcheck/hunspell/src/moz.build
+++ b/extensions/spellcheck/hunspell/src/moz.build
@@ -23,12 +23,13 @@ SOURCES += [
 DEFINES['HUNSPELL_STATIC'] = True
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../glue',
 ]
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/gfx/angle/moz.build
+++ b/gfx/angle/moz.build
@@ -155,11 +155,12 @@ LOCAL_INCLUDES += [ 'include', 'src' ]
 if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
     NO_VISIBILITY_FLAGS = True
 
 # This tells ANGLE to build the translator with declspec(dllexport) on Windows
 # which we need to get these symbols exported from gkmedias
 DEFINES['COMPONENT_BUILD'] = True
 DEFINES['ANGLE_TRANSLATOR_IMPLEMENTATION'] = True
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 FINAL_LIBRARY = 'gkmedias'
--- a/gfx/angle/src/libANGLE/moz.build
+++ b/gfx/angle/src/libANGLE/moz.build
@@ -306,10 +306,11 @@ else:
 Library('libANGLE')
 
 
 SOURCES['renderer/d3d/HLSLCompiler.cpp'].flags += ['-DANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES=\'{ TEXT("d3dcompiler_47.dll"), TEXT("d3dcompiler_46.dll"), TEXT("d3dcompiler_43.dll") }\'']
 
 if CONFIG['MOZ_HAS_WINSDK_WITH_D3D']:
     SOURCES['renderer/d3d/d3d11/SwapChain11.cpp'].flags += ['-DANGLE_RESOURCE_SHARE_TYPE=D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX']
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
--- a/gfx/cairo/cairo/src/moz.build
+++ b/gfx/cairo/cairo/src/moz.build
@@ -180,16 +180,17 @@ UNIFIED_SOURCES += [
     'cairo-traps.c',
     'cairo-unicode.c',
     'cairo-user-font.c',
     'cairo-version.c',
     'cairo-wideint.c',
     'cairo.c',
 ]
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
 
 for var in ('CAIRO_HAS_PTHREAD', '_GNU_SOURCE'):
--- a/gfx/cairo/libpixman/src/moz.build
+++ b/gfx/cairo/libpixman/src/moz.build
@@ -53,16 +53,17 @@ SOURCES += [
     'pixman-region32.c',
     'pixman-solid-fill.c',
     'pixman-trap.c',
     'pixman-utils.c',
     'pixman-x86.c',
     'pixman.c',
 ]
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 FINAL_LIBRARY = 'gkmedias'
 LOCAL_INCLUDES += [
     '../../cairo/src',
 ]
 
 if CONFIG['MOZ_USE_PTHREADS']:
--- a/gfx/gl/GLTextureImage.cpp
+++ b/gfx/gl/GLTextureImage.cpp
@@ -286,26 +286,26 @@ BasicTextureImage::BasicTextureImage(GLu
   , mTextureState(Created)
   , mGLContext(aContext)
   , mUpdateOffset(0, 0)
 {}
 
 static bool
 WantsSmallTiles(GLContext* gl)
 {
-    // We must use small tiles for good performance if we can't use
-    // glTexSubImage2D() for some reason.
-    if (!CanUploadSubTextures(gl))
-        return true;
-
     // We can't use small tiles on the SGX 540, because of races in texture upload.
     if (gl->WorkAroundDriverBugs() &&
         gl->Renderer() == GLRenderer::SGX540)
         return false;
 
+    // We should use small tiles for good performance if we can't use
+    // glTexSubImage2D() for some reason.
+    if (!CanUploadSubTextures(gl))
+        return true;
+
     // Don't use small tiles otherwise. (If we implement incremental texture upload,
     // then we will want to revisit this.)
     return false;
 }
 
 TiledTextureImage::TiledTextureImage(GLContext* aGL,
                                      gfx::IntSize aSize,
                                      TextureImage::ContentType aContentType,
--- a/gfx/graphite2/src/moz.build
+++ b/gfx/graphite2/src/moz.build
@@ -62,16 +62,17 @@ UNIFIED_SOURCES += [
 if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
     NO_VISIBILITY_FLAGS = True
     DEFINES['GRAPHITE2_EXPORTING'] = True
 else:
     # tell graphite2 not to export symbols, we'll be linking it directly with
     # thebes
     DEFINES['GRAPHITE2_STATIC'] = True
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
 
 # disable features we don't need in the graphite2 code, to reduce code size
--- a/gfx/harfbuzz/src/moz.build
+++ b/gfx/harfbuzz/src/moz.build
@@ -54,16 +54,17 @@ UNIFIED_SOURCES += [
     'hb-ot-tag.cc',
     'hb-set.cc',
     'hb-shape.cc',
     'hb-shaper.cc',
     'hb-unicode.cc',
     'hb-warning.cc',
 ]
 
+# We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
 FINAL_LIBRARY = 'gkmedias'
 
 DEFINES['PACKAGE_VERSION'] = '"moz"'
 DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"'
 DEFINES['HAVE_OT'] = 1
 DEFINES['HB_NO_MT'] = True
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -13,17 +13,16 @@
 #include "nsIDOMWindowUtils.h"
 
 class nsIContent;
 class nsIDocument;
 class nsIPresShell;
 class nsIWidget;
 template<class T> struct already_AddRefed;
 template<class T> class nsCOMPtr;
-template<class T> class nsRefPtr;
 
 namespace mozilla {
 namespace layers {
 
 typedef Function<void(uint64_t, const nsTArray<TouchBehaviorFlags>&)>
         SetAllowedTouchBehaviorCallback;
 
 /* This class contains some helper methods that facilitate implementing the
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -293,16 +293,20 @@ LayerManagerComposite::EndTransaction(co
     mInvalidRegion.Or(mInvalidRegion, mRenderBounds);
   }
 
   if (mInvalidRegion.IsEmpty() && !mTarget) {
     // Composition requested, but nothing has changed. Don't do any work.
     return;
   }
 
+  // We don't want our debug overlay to cause more frames to happen
+  // so we will invalidate after we've decided if something changed.
+  InvalidateDebugOverlay(mRenderBounds);
+
  if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) {
     MOZ_ASSERT(!aTimeStamp.IsNull());
     // The results of our drawing always go directly into a pixel buffer,
     // so we don't need to pass any global transform here.
     mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
 
     nsIntRegion opaque;
     ApplyOcclusionCulling(mRoot, opaque);
@@ -388,16 +392,36 @@ LayerManagerComposite::RootLayer() const
 #ifdef MOZ_PROFILING
 // Only build the QR feature when profiling to avoid bloating
 // our data section.
 // This table was generated using qrencode and is a binary
 // encoding of the qrcodes 0-255.
 #include "qrcode_table.h"
 #endif
 
+void
+LayerManagerComposite::InvalidateDebugOverlay(const IntRect& aBounds)
+{
+  bool drawFps = gfxPrefs::LayersDrawFPS();
+  bool drawFrameCounter = gfxPrefs::DrawFrameCounter();
+  bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
+
+  if (drawFps || drawFrameCounter) {
+    AddInvalidRegion(nsIntRect(0, 0, 256, 256));
+  }
+  if (drawFrameColorBars) {
+    AddInvalidRegion(nsIntRect(0, 0, 10, aBounds.height));
+  }
+
+  if (drawFrameColorBars) {
+    AddInvalidRegion(nsIntRect(0, 0, 10, aBounds.height));
+  }
+
+}
+
 static uint16_t sFrameCount = 0;
 void
 LayerManagerComposite::RenderDebugOverlay(const Rect& aBounds)
 {
   bool drawFps = gfxPrefs::LayersDrawFPS();
   bool drawFrameCounter = gfxPrefs::DrawFrameCounter();
   bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
 
@@ -456,34 +480,31 @@ LayerManagerComposite::RenderDebugOverla
       mCompositor->DrawQuad(gfx::Rect(aBounds.width - 20, 0, aBounds.width, 20),
                             aBounds, effects, alpha, gfx::Matrix4x4());
 
       mUnusedApzTransformWarning = false;
       SetDebugOverlayWantsNextFrame(true);
     }
 
     // Each frame is invalidate by the previous frame for simplicity
-    AddInvalidRegion(nsIntRect(0, 0, 256, 256));
   } else {
     mFPS = nullptr;
   }
 
   if (drawFrameColorBars) {
     gfx::Rect sideRect(0, 0, 10, aBounds.height);
 
     EffectChain effects;
     effects.mPrimaryEffect = new EffectSolidColor(gfxUtils::GetColorForFrameNumber(sFrameCount));
     mCompositor->DrawQuad(sideRect,
                           sideRect,
                           effects,
                           1.0,
                           gfx::Matrix4x4());
 
-    // Each frame is invalidate by the previous frame for simplicity
-    AddInvalidRegion(nsIntRect(0, 0, sideRect.width, sideRect.height));
   }
 
 #ifdef MOZ_PROFILING
   if (drawFrameCounter) {
     profiler_set_frame_number(sFrameCount);
     const char* qr = sQRCodeTable[sFrameCount%256];
 
     int size = 21;
@@ -517,18 +538,16 @@ LayerManagerComposite::RenderDebugOverla
                                 clip,
                                 effects,
                                 opacity,
                                 gfx::Matrix4x4());
         }
       }
     }
 
-    // Each frame is invalidate by the previous frame for simplicity
-    AddInvalidRegion(nsIntRect(0, 0, 256, 256));
   }
 #endif
 
   if (drawFrameColorBars || drawFrameCounter) {
     // We intentionally overflow at 2^16.
     sFrameCount++;
   }
 }
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -301,16 +301,21 @@ private:
    * Render the current layer tree to the active target.
    */
   void Render();
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   void RenderToPresentationSurface();
 #endif
 
   /**
+   * We need to know our invalid region before we're ready to render.
+   */
+  void InvalidateDebugOverlay(const gfx::IntRect& aBounds);
+
+  /**
    * Render debug overlays such as the FPS/FrameCounter above the frame.