Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 26 Oct 2015 14:32:00 -0700
changeset 304707 54247e31a9a88654dae3805f9ab01f6ac5e584c4
parent 304642 5c4e5a8a83cfd2568260da92ebdc79dafe5fea98 (current diff)
parent 304706 598c9c0fb79a75fad54c6e633554155d9c4182ca (diff)
child 304708 6c7c983bce46a460c2766fbdd73283f6d2b03a69
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge
testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-event.https.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/synced-state.https.html.ini
--- a/b2g/components/PresentationRequestUIGlue.js
+++ b/b2g/components/PresentationRequestUIGlue.js
@@ -25,37 +25,55 @@ function PresentationRequestUIGlue() {
   //   ...
   // }
   this._resolvers = {};
 
   // Listen to the result for the opened iframe from front-end.
   SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => {
     let detail = aEvent.detail;
 
-    if (detail.type != "presentation-receiver-launched") {
-      return;
-    }
+    switch (detail.type) {
+      case "presentation-receiver-launched": {
+        let sessionId = detail.id;
+        let resolver = this._resolvers[sessionId];
+        if (!resolver) {
+          debug("No correspondent resolver for session ID: " + sessionId);
+          return;
+        }
 
-    let sessionId = detail.id;
-    let resolver = this._resolvers[sessionId];
-    if (!resolver) {
-      debug("No correspondent resolver for session ID: " + sessionId);
-      return;
-    }
+        delete this._resolvers[sessionId];
+        resolver.resolve(detail.frame);
+        break;
+      }
+      case "presentation-receiver-permission-denied": {
+        let sessionId = detail.id;
+        let resolver = this._resolvers[sessionId];
+        if (!resolver) {
+          debug("No correspondent resolver for session ID: " + sessionId);
+          return;
+        }
 
-    delete this._resolvers[sessionId];
-    resolver(detail.frame);
+        delete this._resolvers[sessionId];
+        resolver.reject();
+        break;
+      }
+      default:
+        return;
+      }
   });
 }
 
 PresentationRequestUIGlue.prototype = {
 
   sendRequest: function(aUrl, aSessionId) {
     return new Promise(function(aResolve, aReject) {
-      this._resolvers[aSessionId] = aResolve;
+      this._resolvers[aSessionId] = {
+        resolve: aResolve,
+        reject: aReject,
+      };
 
       SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
                                       { type: "presentation-launch-receiver",
                                         url: aUrl,
                                         id: aSessionId });
     }.bind(this));
   },
 
--- a/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -15,21 +15,18 @@ SystemAppProxy.addEventListener('mozPres
   if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
     return;
   }
   sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
 });
 
 addMessageListener('trigger-ui-glue', function(aData) {
   var promise = glue.sendRequest(aData.url, aData.sessionId);
-  promise.then(function(aFrame){
+  promise.then(function(aFrame) {
     sendAsyncMessage('iframe-resolved', aFrame);
+  }).catch(function() {
+    sendAsyncMessage('iframe-rejected');
   });
 });
 
-addMessageListener('trigger-presentation-content-event', function(aData) {
-  var detail = {
-    type: 'presentation-receiver-launched',
-    id: aData.sessionId,
-    frame: aData.frame
-  };
-  SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail);
+addMessageListener('trigger-presentation-content-event', function(aDetail) {
+  SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', aDetail);
 });
--- a/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
+++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
@@ -1,15 +1,15 @@
 <!DOCTYPE HTML>
 <html>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 <head>
   <meta charset="utf-8">
-  <title>Test for Presentation Device Selection</title>
+  <title>Test for Presentation UI Glue</title>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
 <script type="application/javascript;version=1.8">
 
 'use strict';
@@ -52,24 +52,51 @@ function testReceiverLaunched() {
 
     var iframe = document.createElement('iframe');
     iframe.setAttribute('remote', 'true');
     iframe.setAttribute('mozbrowser', 'true');
     iframe.setAttribute('src', 'http://example.com');
     document.body.appendChild(iframe);
 
     gScript.sendAsyncMessage('trigger-presentation-content-event',
-                             { sessionId : sessionId,
+                             { type: 'presentation-receiver-launched',
+                               id: sessionId,
                                frame: iframe });
   });
 }
 
+function testLaunchError() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+      gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+      ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+      is(aDetail.url, url, "Url should be the same.");
+      is(aDetail.id, sessionId, "Session ID should be the same.");
+
+      gScript.addMessageListener('iframe-rejected', function iframeRejectedHandler() {
+        gScript.removeMessageListener('iframe-rejected', iframeRejectedHandler);
+        ok(true, "The promise should be rejected.");
+        aResolve();
+      });
+
+    gScript.sendAsyncMessage('trigger-presentation-content-event',
+                             { type: 'presentation-receiver-permission-denied',
+                               id: sessionId });
+    });
+
+    gScript.sendAsyncMessage('trigger-ui-glue',
+                             { url: url,
+                               sessionId : sessionId });
+  });
+}
+
 function runTests() {
   testLaunchReceiver()
   .then(testReceiverLaunched)
+  .then(testLaunchError)
   .then(function() {
     info('test finished, teardown');
     gScript.destroy();
     SimpleTest.finish();
   });
 }
 
 window.addEventListener('load', runTests);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -377,16 +377,18 @@
 @RESPATH@/components/RequestSyncManager.js
 @RESPATH@/components/RequestSyncScheduler.js
 @RESPATH@/components/ChromeNotifications.js
 @RESPATH@/components/ChromeNotifications.manifest
 @RESPATH@/components/ConsoleAPI.manifest
 @RESPATH@/components/ConsoleAPIStorage.js
 @RESPATH@/components/BrowserElementParent.manifest
 @RESPATH@/components/BrowserElementParent.js
+@RESPATH@/components/BrowserElementProxy.manifest
+@RESPATH@/components/BrowserElementProxy.js
 @RESPATH@/components/ContactManager.js
 @RESPATH@/components/ContactManager.manifest
 @RESPATH@/components/PhoneNumberService.js
 @RESPATH@/components/PhoneNumberService.manifest
 @RESPATH@/components/NotificationStorage.js
 @RESPATH@/components/NotificationStorage.manifest
 @RESPATH@/components/PermissionSettings.js
 @RESPATH@/components/PermissionSettings.manifest
--- a/browser/components/loop/modules/MozLoopPushHandler.jsm
+++ b/browser/components/loop/modules/MozLoopPushHandler.jsm
@@ -65,17 +65,17 @@ PushSocket.prototype = {
                                    Services.scriptSecurityManager.getSystemPrincipal(),
                                    null, // aTriggeringPrincipal
                                    Ci.nsILoadInfo.SEC_NORMAL,
                                    Ci.nsIContentPolicy.TYPE_WEBSOCKET);
     }
 
     let uri = Services.io.newURI(pushUri, null, null);
     this._websocket.protocol = "push-notification";
-    this._websocket.asyncOpen(uri, pushUri, this, null);
+    this._websocket.asyncOpen(uri, pushUri, 0, this, null);
   },
 
   /**
    * nsIWebSocketListener method, handles the start of the websocket stream.
    *
    * @param {nsISupports} aContext Not used
    */
   onStart: function() {
--- a/browser/components/loop/test/xpcshell/head.js
+++ b/browser/components/loop/test/xpcshell/head.js
@@ -152,21 +152,22 @@ MockWebSocketChannel.prototype = {
                                      JSON.stringify({}));
     return;
   },
 
   /**
    * nsIWebSocketChannel implementations.
    * See nsIWebSocketChannel.idl for API details.
    */
-  asyncOpen: function(aURI, aOrigin, aListener, aContext) {
+  asyncOpen: function(aURI, aOrigin, aWindowId, aListener, aContext) {
     this.uri = aURI;
     this.origin = aOrigin;
     this.listener = aListener;
     this.context = aContext;
+    this.windowId = aWindowId;
 
     this.listener.onStart(this.context);
   },
 
   sendMsg: function(aMsg) {
     var message = JSON.parse(aMsg);
 
     switch (message.messageType) {
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -360,16 +360,18 @@
 
 ; JavaScript components
 @RESPATH@/components/ChromeNotifications.js
 @RESPATH@/components/ChromeNotifications.manifest
 @RESPATH@/components/ConsoleAPI.manifest
 @RESPATH@/components/ConsoleAPIStorage.js
 @RESPATH@/components/BrowserElementParent.manifest
 @RESPATH@/components/BrowserElementParent.js
+@RESPATH@/components/BrowserElementProxy.manifest
+@RESPATH@/components/BrowserElementProxy.js
 @RESPATH@/components/FeedProcessor.manifest
 @RESPATH@/components/FeedProcessor.js
 @RESPATH@/components/PackagedAppUtils.js
 @RESPATH@/components/PackagedAppUtils.manifest
 @RESPATH@/browser/components/BrowserFeeds.manifest
 @RESPATH@/browser/components/FeedConverter.js
 @RESPATH@/browser/components/FeedWriter.js
 @RESPATH@/browser/components/fuelApplication.manifest
--- a/build/macosx/local-mozconfig.common
+++ b/build/macosx/local-mozconfig.common
@@ -12,21 +12,23 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
 fi
 . "$topsrcdir/build/mozconfig.common"
 
 if [ -d "$topsrcdir/clang" ]; then
     # mozilla-central based build
     export CC=$topsrcdir/clang/bin/clang
     export CXX=$topsrcdir/clang/bin/clang++
     export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
+    export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
 elif [ -d "$topsrcdir/../clang" ]; then
     # comm-central based build
     export CC=$topsrcdir/../clang/bin/clang
     export CXX=$topsrcdir/../clang/bin/clang++
     export LLVMCONFIG=$topsrcdir/../clang/bin/llvm-config
+    export DSYMUTIL=$topsrcdir/../clang/bin/llvm-dsymutil
 fi
 
 # If not set use the system default clang
 if [ -z "$CC" ]; then
     export CC=clang
 fi
 
 # If not set use the system default clang++
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -458,41 +458,16 @@ GeneratedLocation.prototype = {
       column: this.generatedColumn,
       lastColumn: this.generatedLastColumn
     };
   }
 };
 
 exports.GeneratedLocation = GeneratedLocation;
 
-// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
-// implemented.
-exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
-  let bestOffsetMapping = null;
-  for (let offsetMapping of aScript.getAllColumnOffsets()) {
-    if (!bestOffsetMapping ||
-        (offsetMapping.offset <= aOffset &&
-         offsetMapping.offset > bestOffsetMapping.offset)) {
-      bestOffsetMapping = offsetMapping;
-    }
-  }
-
-  if (!bestOffsetMapping) {
-    // XXX: Try not to completely break the experience of using the debugger for
-    // the user by assuming column 0. Simultaneously, report the error so that
-    // there is a paper trail if the assumption is bad and the debugging
-    // experience becomes wonky.
-    reportError(new Error("Could not find a column for offset " + aOffset
-                          + " in the script " + aScript));
-    return 0;
-  }
-
-  return bestOffsetMapping.columnNumber;
-}
-
 /**
  * A method decorator that ensures the actor is in the expected state before
  * proceeding. If the actor is not in the expected state, the decorated method
  * returns a rejected promise.
  *
  * The actor's state must be at this.state property.
  *
  * @param String expectedState
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -3628,17 +3628,17 @@ function hackDebugger(Debugger) {
   /**
    * Helper property for quickly getting to the line number a stack frame is
    * currently paused at.
    */
   Object.defineProperty(Debugger.Frame.prototype, "line", {
     configurable: true,
     get: function() {
       if (this.script) {
-        return this.script.getOffsetLine(this.offset);
+        return this.script.getOffsetLocation(this.offset).lineNumber;
       } else {
         return null;
       }
     }
   });
 }
 
 
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const { Ci, Cu } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { dbg_assert, fetch } = DevToolsUtils;
 const EventEmitter = require("devtools/shared/event-emitter");
-const { OriginalLocation, GeneratedLocation, getOffsetColumn } = require("devtools/server/actors/common");
+const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { resolve } = require("promise");
 
 loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
 
 /**
@@ -543,20 +543,22 @@ TabSources.prototype = {
    *        The frame whose location we are getting.
    * @returns Object
    *          Returns an object of the form { source, line, column }
    */
   getFrameLocation: function (aFrame) {
     if (!aFrame || !aFrame.script) {
       return new GeneratedLocation();
     }
+    let {lineNumber, columnNumber} =
+        aFrame.script.getOffsetLocation(aFrame.offset);
     return new GeneratedLocation(
       this.createNonSourceMappedActor(aFrame.script.source),
-      aFrame.script.getOffsetLine(aFrame.offset),
-      getOffsetColumn(aFrame.offset, aFrame.script)
+      lineNumber,
+      columnNumber
     );
   },
 
   /**
    * Returns a promise of the location in the original source if the source is
    * source mapped, otherwise a promise of the same location. This can
    * be called with a source from *any* Debugger instance and we make
    * sure to that it works properly, reusing source maps if already
--- a/devtools/server/tests/unit/test_blackboxing-01.js
+++ b/devtools/server/tests/unit/test_blackboxing-01.js
@@ -55,17 +55,17 @@ const testBlackBox = Task.async(function
   let blackBoxResponse = yield blackBox(sourceClient);
   do_check_true(sourceClient.isBlackBoxed);
 
   // Test that we step through `doStuff` when we are black boxed and its frame
   // doesn't show up.
   yield runTest(
     function onSteppedLocation(aLocation) {
       do_check_eq(aLocation.source.url, SOURCE_URL);
-      do_check_eq(aLocation.line, 3);
+      do_check_eq(aLocation.line, 4);
     },
     function onDebuggerStatementFrames(aFrames) {
       for (let f of aFrames) {
         if (f.where.source.url == BLACK_BOXED_URL) {
           do_check_true(f.where.source.isBlackBoxed);
         } else {
           do_check_true(!f.where.source.isBlackBoxed)
         }
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -126,16 +126,21 @@ this.PermissionsTable =  { geolocation: 
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "browser:universalxss": {
                              app: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
+                           "browser:embedded-system-app": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                            bluetooth: {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            mobileconnection: {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -121,17 +121,18 @@ public:
             const nsAString& aURL,
             nsTArray<nsString>& aProtocolArray,
             const nsACString& aScriptFile,
             uint32_t aScriptLine,
             uint32_t aScriptColumn,
             ErrorResult& aRv,
             bool* aConnectionFailed);
 
-  void AsyncOpen(nsIPrincipal* aPrincipal, ErrorResult& aRv);
+  void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+                 ErrorResult& aRv);
 
   nsresult ParseURL(const nsAString& aURL);
   nsresult InitializeConnection(nsIPrincipal* aPrincipal);
 
   // These methods when called can release the WebSocket object
   void FailConnection(uint16_t reasonCode,
                       const nsACString& aReasonString = EmptyCString());
   nsresult CloseConnection(uint16_t reasonCode,
@@ -1119,39 +1120,52 @@ public:
     MOZ_ASSERT(mWorkerPrivate);
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
 protected:
   virtual bool InitWithWindow(nsPIDOMWindow* aWindow) override
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(aWindow);
 
     nsIDocument* doc = aWindow->GetExtantDoc();
     if (!doc) {
       mRv.Throw(NS_ERROR_FAILURE);
       return true;
     }
 
     nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
     if (!principal) {
       mRv.Throw(NS_ERROR_FAILURE);
       return true;
     }
 
-    mImpl->AsyncOpen(principal, mRv);
+    uint64_t windowID = 0;
+    nsCOMPtr<nsIDOMWindow> topWindow;
+    aWindow->GetScriptableTop(getter_AddRefs(topWindow));
+    nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
+    if (pTopWindow) {
+      pTopWindow = pTopWindow->GetCurrentInnerWindow();
+    }
+
+    if (pTopWindow) {
+      windowID = pTopWindow->WindowID();
+    }
+
+    mImpl->AsyncOpen(principal, windowID, mRv);
     return true;
   }
 
   virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
 
-    mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), mRv);
+    mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv);
     return true;
   }
 
 private:
   // Raw pointer. This worker runs synchronously.
   WebSocketImpl* mImpl;
 
   ErrorResult& mRv;
@@ -1303,17 +1317,30 @@ WebSocket::Constructor(const GlobalObjec
   // main-thread.
   aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   if (NS_IsMainThread()) {
     MOZ_ASSERT(principal);
-    webSocket->mImpl->AsyncOpen(principal, aRv);
+
+    uint64_t windowID = 0;
+    nsCOMPtr<nsIDOMWindow> topWindow;
+    ownerWindow->GetScriptableTop(getter_AddRefs(topWindow));
+    nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
+    if (pTopWindow) {
+      pTopWindow = pTopWindow->GetCurrentInnerWindow();
+    }
+
+    if (pTopWindow) {
+      windowID = pTopWindow->WindowID();
+    }
+
+    webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
   } else {
     RefPtr<AsyncOpenRunnable> runnable =
       new AsyncOpenRunnable(webSocket->mImpl, aRv);
     runnable->Dispatch(aGlobal.Context());
   }
 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
@@ -1599,33 +1626,34 @@ WebSocketImpl::Init(JSContext* aCx,
   if (NS_FAILED(InitializeConnection(aPrincipal))) {
     *aConnectionFailed = true;
   } else {
     *aConnectionFailed = false;
   }
 }
 
 void
-WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, ErrorResult& aRv)
+WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+                         ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
 
   nsCString asciiOrigin;
   aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   ToLowerCase(asciiOrigin);
 
   nsCOMPtr<nsIURI> uri;
   aRv = NS_NewURI(getter_AddRefs(uri), mURI);
   MOZ_ASSERT(!aRv.Failed());
 
-  aRv = mChannel->AsyncOpen(uri, asciiOrigin, this, nullptr);
+  aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
 //-----------------------------------------------------------------------------
 // WebSocketImpl methods:
 //-----------------------------------------------------------------------------
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5352,24 +5352,16 @@ static void ProcessViewportToken(nsIDocu
   else if (key_atom == nsGkAtoms::user_scalable)
     aDocument->SetHeaderData(nsGkAtoms::viewport_user_scalable, value);
 }
 
 #define IS_SEPARATOR(c) ((c == '=') || (c == ',') || (c == ';') || \
                          (c == '\t') || (c == '\n') || (c == '\r'))
 
 /* static */
-nsViewportInfo
-nsContentUtils::GetViewportInfo(nsIDocument *aDocument,
-                                const ScreenIntSize& aDisplaySize)
-{
-  return aDocument->GetViewportInfo(aDisplaySize);
-}
-
-/* static */
 nsresult
 nsContentUtils::ProcessViewportInfo(nsIDocument *aDocument,
                                     const nsAString &viewportInfo) {
 
   /* We never fail. */
   nsresult rv = NS_OK;
 
   aDocument->SetHeaderData(nsGkAtoms::viewport, viewportInfo);
@@ -7025,48 +7017,16 @@ nsContentUtils::GetSelectionInTextContro
     }
   }
 
   // Make sure aOutStartOffset <= aOutEndOffset.
   aOutStartOffset = std::min(anchorOffset, focusOffset);
   aOutEndOffset = std::max(anchorOffset, focusOffset);
 }
 
-/* static */
-nsRect
-nsContentUtils::GetSelectionBoundingRect(Selection* aSel)
-{
-  nsRect res;
-  // Bounding client rect may be empty after calling GetBoundingClientRect
-  // when range is collapsed. So we get caret's rect when range is
-  // collapsed.
-  if (aSel->IsCollapsed()) {
-    nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
-    if (frame) {
-      nsIFrame* relativeTo =
-        nsLayoutUtils::GetContainingBlockForClientRect(frame);
-      res = nsLayoutUtils::TransformFrameRectToAncestor(frame, res, relativeTo);
-    }
-  } else {
-    int32_t rangeCount = aSel->RangeCount();
-    nsLayoutUtils::RectAccumulator accumulator;
-    for (int32_t idx = 0; idx < rangeCount; ++idx) {
-      nsRange* range = aSel->GetRangeAt(idx);
-      nsRange::CollectClientRects(&accumulator, range,
-                                  range->GetStartParent(), range->StartOffset(),
-                                  range->GetEndParent(), range->EndOffset(),
-                                  true, false);
-    }
-    res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
-      accumulator.mResultRect;
-  }
-
-  return res;
-}
-
 
 nsIEditor*
 nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext)
 {
   nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell());
   bool isEditable;
   if (!docShell ||
       NS_FAILED(docShell->GetEditable(&isEditable)) || !isEditable)
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1667,34 +1667,16 @@ public:
    * microtask has finished.  This is not specced at this time.
    * In practice this runs aRunnable once the currently executing task or
    * microtask finishes.  If called multiple times per microtask, all the
    * runnables will be executed, in the order in which RunInMetastableState()
    * was called
    */
   static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
 
-  /**
-   * Retrieve information about the viewport as a data structure.
-   * This will return information in the viewport META data section
-   * of the document. This can be used in lieu of ProcessViewportInfo(),
-   * which places the viewport information in the document header instead
-   * of returning it directly.
-   *
-   * @param aDisplayWidth width of the on-screen display area for this
-   * document, in device pixels.
-   * @param aDisplayHeight height of the on-screen display area for this
-   * document, in device pixels.
-   *
-   * NOTE: If the site is optimized for mobile (via the doctype), this
-   * will return viewport information that specifies default information.
-   */
-  static nsViewportInfo GetViewportInfo(nsIDocument* aDocument,
-                                        const mozilla::ScreenIntSize& aDisplaySize);
-
   // Call EnterMicroTask when you're entering JS execution.
   // Usually the best way to do this is to use nsAutoMicroTask.
   static void EnterMicroTask();
   static void LeaveMicroTask();
 
   static bool IsInMicroTask();
   static uint32_t MicroTaskLevel();
   static void SetMicroTaskLevel(uint32_t aLevel);
@@ -2326,24 +2308,16 @@ public:
    * @param aOutEndOffset   Output end offset
    */
   static void GetSelectionInTextControl(mozilla::dom::Selection* aSelection,
                                         Element* aRoot,
                                         int32_t& aOutStartOffset,
                                         int32_t& aOutEndOffset);
 
   /**
-   * Takes a selection, and return selection's bounding rect which is relative
-   * to root frame.
-   *
-   * @param aSel      Selection to check
-   */
-  static nsRect GetSelectionBoundingRect(mozilla::dom::Selection* aSel);
-
-  /**
    * Takes a frame for anonymous content within a text control (<input> or
    * <textarea>), and returns an offset in the text content, adjusted for a
    * trailing <br> frame.
    *
    * @param aOffsetFrame      Frame for the text content in which the offset
    *                          lies
    * @param aOffset           Offset as calculated by GetContentOffsetsFromPoint
    * @param aOutOffset        Output adjusted offset
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -293,17 +293,17 @@ nsDOMWindowUtils::GetViewportInfo(uint32
                                   double *aDefaultZoom, bool *aAllowZoom,
                                   double *aMinZoom, double *aMaxZoom,
                                   uint32_t *aWidth, uint32_t *aHeight,
                                   bool *aAutoSize)
 {
   nsIDocument* doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
-  nsViewportInfo info = nsContentUtils::GetViewportInfo(doc, ScreenIntSize(aDisplayWidth, aDisplayHeight));
+  nsViewportInfo info = doc->GetViewportInfo(ScreenIntSize(aDisplayWidth, aDisplayHeight));
   *aDefaultZoom = info.GetDefaultZoom().scale;
   *aAllowZoom = info.IsZoomAllowed();
   *aMinZoom = info.GetMinZoom().scale;
   *aMaxZoom = info.GetMaxZoom().scale;
   CSSIntSize size = gfx::RoundedToInt(info.GetSize());
   *aWidth = size.width;
   *aHeight = size.height;
   *aAutoSize = info.IsAutoSizeEnabled();
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -727,16 +727,29 @@ public:
    */
   mozilla::dom::DocumentType* GetDoctype() const;
 
   /**
    * Return the root element for this document.
    */
   Element* GetRootElement() const;
 
+  /**
+   * Retrieve information about the viewport as a data structure.
+   * This will return information in the viewport META data section
+   * of the document. This can be used in lieu of ProcessViewportInfo(),
+   * which places the viewport information in the document header instead
+   * of returning it directly.
+   *
+   * @param aDisplaySize size of the on-screen display area for this
+   * document, in device pixels.
+   *
+   * NOTE: If the site is optimized for mobile (via the doctype), this
+   * will return viewport information that specifies default information.
+   */
   virtual nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize) = 0;
 
   /**
    * True iff this doc will ignore manual character encoding overrides.
    */
   virtual bool WillIgnoreCharsetOverride() {
     return true;
   }
--- a/dom/base/nsViewportInfo.h
+++ b/dom/base/nsViewportInfo.h
@@ -14,17 +14,17 @@
  */
 static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.1f);
 static const mozilla::LayoutDeviceToScreenScale kViewportMaxScale(10.0f);
 static const mozilla::CSSIntSize kViewportMinSize(200, 40);
 static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);
 
 /**
  * Information retrieved from the <meta name="viewport"> tag. See
- * nsContentUtils::GetViewportInfo for more information on this functionality.
+ * nsIDocument::GetViewportInfo for more information on this functionality.
  */
 class MOZ_STACK_CLASS nsViewportInfo
 {
   public:
     nsViewportInfo(const mozilla::ScreenIntSize& aDisplaySize,
                    const mozilla::CSSToScreenScale& aDefaultZoom,
                    bool aAllowZoom) :
       mDefaultZoomValid(true),
@@ -65,17 +65,17 @@ class MOZ_STACK_CLASS nsViewportInfo
 
     bool IsAutoSizeEnabled() const { return mAutoSize; }
     bool IsZoomAllowed() const { return mAllowZoom; }
 
   private:
 
     /**
      * Constrain the viewport calculations from the
-     * nsContentUtils::GetViewportInfo() function in order to always return
+     * nsIDocument::GetViewportInfo() function in order to always return
      * sane minimum/maximum values.
      */
     void ConstrainViewportValues();
 
     // If the default zoom was specified and was between the min and max
     // zoom values.
     bool mDefaultZoomValid;
 
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -21,8 +21,9 @@ support-files =
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
 skip-if = buildapp == 'mulet'
 [test_bug945152.html]
 run-if = os == 'linux'
 [test_bug1008126.html]
 run-if = os == 'linux'
 [test_sandboxed_blob_uri.html]
+[test_websocket_frame.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket_frame.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+  <title>Basic websocket frame interception test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+var frameReceivedCounter = 0;
+var frameSentCounter = 0;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var tests = [
+  { payload: "Hello world!" },
+  { payload: (function() { var buffer = ""; for (var i = 0; i < 120; ++i) buffer += i; return buffer; }()) },
+  { payload: "end" },
+]
+
+var innerId =
+  window.top.QueryInterface(Ci.nsIInterfaceRequestor)
+        .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var service = Cc["@mozilla.org/websocketframe/service;1"]
+                .getService(Ci.nsIWebSocketFrameService);
+ok(!!service, "We have the nsIWebSocketFrameService");
+
+var listener = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebSocketFrameListener]),
+
+  frameReceived: function(aWebSocketSerialID, aFrame) {
+    ok(!!aFrame, "We have received a frame");
+
+    if (tests.length) {
+      ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+      is(aFrame.finBit, true, "Checking finBit");
+      is(aFrame.rsvBit1, true, "Checking rsvBit1");
+      is(aFrame.rsvBit2, false, "Checking rsvBit2");
+      is(aFrame.rsvBit3, false, "Checking rsvBit3");
+      is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
+      is(aFrame.maskBit, false, "Checking maskBit");
+      is(aFrame.mask, 0, "Checking mask");
+      is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
+    } else {
+      ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+      is(aFrame.finBit, true, "Checking finBit");
+      is(aFrame.rsvBit1, false, "Checking rsvBit1");
+      is(aFrame.rsvBit2, false, "Checking rsvBit2");
+      is(aFrame.rsvBit3, false, "Checking rsvBit3");
+      is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
+      is(aFrame.maskBit, false, "Checking maskBit");
+      is(aFrame.mask, 0, "Checking mask");
+    }
+
+    frameReceivedCounter++;
+  },
+
+  frameSent: function(aWebSocketSerialID, aFrame) {
+    ok(!!aFrame, "We have sent a frame");
+
+    if (tests.length) {
+      ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+      is(aFrame.finBit, true, "Checking finBit");
+      is(aFrame.rsvBit1, true, "Checking rsvBit1");
+      is(aFrame.rsvBit2, false, "Checking rsvBit2");
+      is(aFrame.rsvBit3, false, "Checking rsvBit3");
+      is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
+      is(aFrame.maskBit, true, "Checking maskBit");
+      ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
+      is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
+    } else {
+      ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+      is(aFrame.finBit, true, "Checking finBit");
+      is(aFrame.rsvBit1, false, "Checking rsvBit1");
+      is(aFrame.rsvBit2, false, "Checking rsvBit2");
+      is(aFrame.rsvBit3, false, "Checking rsvBit3");
+      is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
+      is(aFrame.maskBit, true, "Checking maskBit");
+      ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
+    }
+
+    frameSentCounter++;
+  }
+};
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+
+function checkListener() {
+  service.removeListener(innerId, listener);
+
+  ok(frameReceivedCounter, "We received some frames!");
+  ok(frameSentCounter, "We sent some frames!");
+  SimpleTest.finish();
+}
+
+var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic", "frame");
+ws.onopen = function(e) {
+  info("onopen");
+
+  ws.send(tests[0].payload);
+}
+
+ws.onclose = function(e) {
+  info("onclose");
+
+  ws.close();
+  checkListener();
+}
+
+ws.onmessage = function(e) {
+  info("onmessage");
+
+  is(e.data, tests[0].payload, "Wrong data");
+  tests.shift();
+  if (tests.length) {
+    ws.send(tests[0].payload);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -80,16 +80,48 @@ const COMMAND_MAP = {
  * nsIMessageManager::LoadFrameScript().
  *
  * Our job here is to listen for events within this frame and bubble them up to
  * the parent process.
  */
 
 var global = this;
 
+function BrowserElementProxyForwarder() {
+}
+
+BrowserElementProxyForwarder.prototype = {
+  init: function() {
+    Services.obs.addObserver(this, "browser-element-api:proxy-call", false);
+    addMessageListener("browser-element-api:proxy", this);
+  },
+
+  uninit: function() {
+    Services.obs.removeObserver(this, "browser-element-api:proxy-call", false);
+    removeMessageListener("browser-element-api:proxy", this);
+  },
+
+  // Observer callback receives messages from BrowserElementProxy.js
+  observe: function(subject, topic, stringifedData) {
+    if (subject !== content) {
+      return;
+    }
+
+    // Forward it to BrowserElementParent.js
+    sendAsyncMessage(topic, JSON.parse(stringifedData));
+  },
+
+  // Message manager callback receives messages from BrowserElementParent.js
+  receiveMessage: function(mmMsg) {
+    // Forward it to BrowserElementProxy.js
+    Services.obs.notifyObservers(
+      content, mmMsg.name, JSON.stringify(mmMsg.json));
+  }
+};
+
 function BrowserElementChild() {
   // Maps outer window id --> weak ref to window.  Used by modal dialog code.
   this._windowIDDict = {};
 
   // _forcedVisible corresponds to the visibility state our owner has set on us
   // (via iframe.setVisible).  ownerVisible corresponds to whether the docShell
   // whose window owns this element is visible.
   //
@@ -99,16 +131,18 @@ function BrowserElementChild() {
   this._ownerVisible = true;
 
   this._nextPaintHandler = null;
 
   this._isContentWindowCreated = false;
   this._pendingSetInputMethodActive = [];
   this._selectionStateChangedTarget = null;
 
+  this.forwarder = new BrowserElementProxyForwarder();
+
   this._init();
 };
 
 BrowserElementChild.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
@@ -283,16 +317,18 @@ BrowserElementChild.prototype = {
                                /* useCapture = */ false);
     els.addSystemEventListener(global, 'scroll',
                                this._scrollEventHandler.bind(this),
                                /* useCapture = */ false);
 
     OBSERVED_EVENTS.forEach((aTopic) => {
       Services.obs.addObserver(this, aTopic, false);
     });
+
+    this.forwarder.init();
   },
 
   observe: function(subject, topic, data) {
     // Ignore notifications not about our document.  (Note that |content| /can/
     // be null; see bug 874900.)
 
     if (topic !== 'activity-done' && topic !== 'audio-playback' &&
         (!content || subject !== content.document)) {
@@ -322,16 +358,19 @@ BrowserElementChild.prototype = {
    * Called when our TabChildGlobal starts to die.  This is not called when the
    * page inside |content| unloads.
    */
   _unloadHandler: function() {
     this._shuttingDown = true;
     OBSERVED_EVENTS.forEach((aTopic) => {
       Services.obs.removeObserver(this, aTopic);
     });
+
+    this.forwarder.uninit();
+    this.forwarder = null;
   },
 
   get _windowUtils() {
     return content.document.defaultView
                   .QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIDOMWindowUtils);
   },
 
@@ -929,17 +968,17 @@ BrowserElementChild.prototype = {
     if (sendSyncMsg('contextmenu', menuData)[0]) {
       e.preventDefault();
     } else {
       this._ctxHandlers = {};
     }
   },
 
   _getSystemCtxMenuData: function(elem) {
-    let documentURI = 
+    let documentURI =
       docShell.QueryInterface(Ci.nsIWebNavigation).currentURI.spec;
     if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
         (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {
       return {uri: elem.href,
               documentURI: documentURI,
               text: elem.textContent.substring(0, kLongestReturnedString)};
     }
     if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) {
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -59,29 +59,202 @@ function defineNoReturnMethod(fn) {
 }
 
 function defineDOMRequestMethod(msgName) {
   return function() {
     return this._sendDOMRequest(msgName);
   };
 }
 
+function BrowserElementParentProxyCallHandler() {
+}
+
+BrowserElementParentProxyCallHandler.prototype = {
+  _frameElement: null,
+  _mm: null,
+
+  MOZBROWSER_EVENT_NAMES: Object.freeze([
+    "loadstart", "loadend", "close", "error", "firstpaint",
+    "documentfirstpaint", "audioplaybackchange",
+    "contextmenu", "securitychange", "locationchange",
+    "iconchange", "scrollareachanged", "titlechange",
+    "opensearch", "manifestchange", "metachange",
+    "resize", "selectionstatechanged", "scrollviewchange",
+    "caretstatechanged", "activitydone", "scroll", "opentab"]),
+
+  init: function(frameElement, mm) {
+    this._frameElement = frameElement;
+    this._mm = mm;
+    this.innerWindowIDSet = new Set();
+
+    mm.addMessageListener("browser-element-api:proxy-call", this);
+  },
+
+  // Message manager callback receives messages from BrowserElementProxy.js
+  receiveMessage: function(mmMsg) {
+    let data = mmMsg.json;
+
+    let mm;
+    try {
+      mm = mmMsg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
+                     .frameLoader.messageManager;
+    } catch(e) {
+      mm = mmMsg.target;
+    }
+    if (!mm.assertPermission("browser:embedded-system-app")) {
+      dump("BrowserElementParent.js: Method call " + data.methodName +
+        " from a content process with no 'browser:embedded-system-app'" +
+        " privileges.\n");
+      return;
+    }
+
+    switch (data.methodName) {
+      case '_proxyInstanceInit':
+        if (!this.innerWindowIDSet.size) {
+          this._attachEventListeners();
+        }
+        this.innerWindowIDSet.add(data.innerWindowID);
+
+        break;
+
+      case '_proxyInstanceUninit':
+        this.innerWindowIDSet.delete(data.innerWindowID);
+        if (!this.innerWindowIDSet.size) {
+          this._detachEventListeners();
+        }
+
+        break;
+
+      // void methods
+      case 'setVisible':
+      case 'setActive':
+      case 'sendMouseEvent':
+      case 'sendTouchEvent':
+      case 'goBack':
+      case 'goForward':
+      case 'reload':
+      case 'stop':
+      case 'zoom':
+      case 'setNFCFocus':
+      case 'findAll':
+      case 'findNext':
+      case 'clearMatch':
+      case 'mute':
+      case 'unmute':
+      case 'setVolume':
+        this._frameElement[data.methodName]
+          .apply(this._frameElement, data.args);
+
+        break;
+
+      // DOMRequest methods
+      case 'getVisible':
+      case 'download':
+      case 'purgeHistory':
+      case 'getCanGoBack':
+      case 'getCanGoForward':
+      case 'getContentDimensions':
+      case 'setInputMethodActive':
+      case 'executeScript':
+      case 'getMuted':
+      case 'getVolume':
+        let req = this._frameElement[data.methodName]
+          .apply(this._frameElement, data.args);
+        req.onsuccess = () => {
+          this._sendToProxy({
+            domRequestId: data.domRequestId,
+            innerWindowID: data.innerWindowID,
+            result: req.result
+          });
+        };
+        req.onerror = () => {
+          this._sendToProxy({
+            domRequestId: data.domRequestId,
+            innerWindowID: data.innerWindowID,
+            err: req.error
+          });
+        };
+
+        break;
+
+      // Not implemented
+      case 'getActive': // Sync ???
+      case 'addNextPaintListener': // Takes a callback
+      case 'removeNextPaintListener': // Takes a callback
+      case 'getScreenshot': // Need to pass a blob back
+        dump("BrowserElementParentProxyCallHandler Error:" +
+          "Attempt to call unimplemented method " + data.methodName + ".\n");
+        break;
+
+      default:
+        dump("BrowserElementParentProxyCallHandler Error:" +
+          "Attempt to call non-exist method " + data.methodName + ".\n");
+        break;
+    }
+  },
+
+  // Receving events from the frame element and forward it.
+  handleEvent: function(evt) {
+    // Ignore the events from nested mozbrowser iframes
+    if (evt.target !== this._frameElement) {
+      return;
+    }
+
+    let detailString;
+    try {
+      detailString = JSON.stringify(evt.detail);
+    } catch (e) {
+      dump("BrowserElementParentProxyCallHandler Error:" +
+        "Event detail of " + evt.type + " can't be stingified.\n");
+      return;
+    }
+
+    this.innerWindowIDSet.forEach((innerWindowID) => {
+      this._sendToProxy({
+        eventName: evt.type,
+        innerWindowID: innerWindowID,
+        eventDetailString: detailString
+      });
+    });
+  },
+
+  _sendToProxy: function(data) {
+    this._mm.sendAsyncMessage("browser-element-api:proxy", data);
+  },
+
+  _attachEventListeners: function() {
+    this.MOZBROWSER_EVENT_NAMES.forEach(function(eventName) {
+      this._frameElement.addEventListener(
+        "mozbrowser" + eventName, this, true);
+    }, this);
+  },
+
+  _detachEventListeners: function() {
+    this.MOZBROWSER_EVENT_NAMES.forEach(function(eventName) {
+      this._frameElement.removeEventListener(
+        "mozbrowser" + eventName, this, true);
+    }, this);
+  }
+};
+
 function BrowserElementParent() {
   debug("Creating new BrowserElementParent object");
   this._domRequestCounter = 0;
   this._domRequestReady = false;
   this._pendingAPICalls = [];
   this._pendingDOMRequests = {};
   this._pendingSetInputMethodActive = [];
   this._nextPaintListeners = [];
   this._pendingDOMFullscreen = false;
 
   Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
+
+  this.proxyCallHandler = new BrowserElementParentProxyCallHandler();
 }
 
 BrowserElementParent.prototype = {
 
   classDescription: "BrowserElementAPI implementation",
   classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
   contractID: "@mozilla.org/dom/browser-element-api;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
@@ -118,16 +291,19 @@ BrowserElementParent.prototype = {
     }
 
     this._window._browserElementParents.set(this, null);
 
     // Insert ourself into the prompt service.
     BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
     this._setupMessageListener();
     this._registerAppManifest();
+
+    this.proxyCallHandler.init(
+      this._frameElement, this._frameLoader.messageManager);
   },
 
   _runPendingAPICall: function() {
     if (!this._pendingAPICalls) {
       return;
     }
     for (let i = 0; i < this._pendingAPICalls.length; i++) {
       try {
@@ -731,17 +907,17 @@ BrowserElementParent.prototype = {
 
   purgeHistory: defineDOMRequestMethod('purge-history'),
 
 
   download: function(_url, _options) {
     if (!this._isAlive()) {
       return null;
     }
-    
+
     let uri = Services.io.newURI(_url, null, null);
     let url = uri.QueryInterface(Ci.nsIURL);
 
     debug('original _options = ' + uneval(_options));
 
     // Ensure we have _options, we always use it to send the filename.
     _options = _options || {};
     if (!_options.filename) {
@@ -821,17 +997,17 @@ BrowserElementParent.prototype = {
                                 aOffset, aCount) {
         this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                          aOffset, aCount);
       },
       QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
                                              Ci.nsIRequestObserver])
     };
 
-    // If we have a URI we'll use it to get the triggering principal to use, 
+    // If we have a URI we'll use it to get the triggering principal to use,
     // if not available a null principal is acceptable.
     let referrer = null;
     let principal = null;
     if (_options.referrer) {
       // newURI can throw on malformed URIs.
       try {
         referrer = Services.io.newURI(_options.referrer, null, null);
       }
@@ -844,19 +1020,19 @@ BrowserElementParent.prototype = {
       // calling newChannelFromURI2.
       principal =
         Services.scriptSecurityManager.createCodebasePrincipal(
           referrer, this._frameLoader.loadContext.originAttributes);
     }
 
     debug('Using principal? ' + !!principal);
 
-    let channel = 
+    let channel =
       Services.io.newChannelFromURI2(url,
-                                     null,       // No document. 
+                                     null,       // No document.
                                      principal,  // Loading principal
                                      principal,  // Triggering principal
                                      Ci.nsILoadInfo.SEC_NORMAL,
                                      Ci.nsIContentPolicy.TYPE_OTHER);
 
     // XXX We would set private browsing information prior to calling this.
     channel.notificationCallbacks = interfaceRequestor;
 
@@ -867,17 +1043,17 @@ BrowserElementParent.prototype = {
     if (channel instanceof Ci.nsICachingChannel) {
       debug('This is a caching channel. Forcing bypass.');
       flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
     }
 
     channel.loadFlags |= flags;
 
     if (channel instanceof Ci.nsIHttpChannel) {
-      debug('Setting HTTP referrer = ' + (referrer && referrer.spec)); 
+      debug('Setting HTTP referrer = ' + (referrer && referrer.spec));
       channel.referrer = referrer;
       if (channel instanceof Ci.nsIHttpChannelInternal) {
         channel.forceAllowThirdPartyCookie = true;
       }
     }
 
     // Set-up complete, let's get things started.
     channel.asyncOpen(new DownloadListener(), null);
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/BrowserElementProxy.js
@@ -0,0 +1,220 @@
+/* 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/. */
+
+'use strict';
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+function defineNoReturnMethod(methodName) {
+  return function noReturnMethod() {
+    let args = Array.slice(arguments);
+    this._sendToParent(methodName, args);
+  };
+}
+
+function defineDOMRequestMethod(methodName) {
+  return function domRequestMethod() {
+    let args = Array.slice(arguments);
+    return this._sendDOMRequest(methodName, args);
+  };
+}
+
+function defineUnimplementedMethod(methodName) {
+  return function unimplementedMethod() {
+    throw Components.Exception(
+      'Unimplemented method: ' + methodName, Cr.NS_ERROR_FAILURE);
+  };
+}
+
+/**
+ * The BrowserElementProxy talks to the Browser IFrameElement instance on
+ * behave of the embedded document. It implements all the methods on
+ * the Browser IFrameElement and the methods will work if they are applicable.
+ *
+ * The message is forwarded to BrowserElementParent.js by creating an
+ * 'browser-element-api:proxy-call' observer message.
+ * BrowserElementChildPreload will get notified and send the message through
+ * to the main process through sendAsyncMessage with message of the same name.
+ *
+ * The return message will follow the same route. The message name on the
+ * return route is 'browser-element-api:proxy'.
+ *
+ * Both BrowserElementProxy and BrowserElementParent must be modified if there
+ * is a new method implemented, or a new event added to the Browser
+ * IFrameElement.
+ *
+ * Other details unmentioned here are checks of message sender and recipients
+ * to identify proxy instance on different innerWindows or ensure the content
+ * process has the right permission.
+ */
+function BrowserElementProxy() {
+  // Pad the 0th element so that DOMRequest ID will always be a truthy value.
+  this._pendingDOMRequests = [ undefined ];
+}
+
+BrowserElementProxy.prototype = {
+  classDescription: 'BrowserElementProxy allowed embedded frame to control ' +
+                    'it\'s own embedding browser element frame instance.',
+  classID:          Components.ID('{7e95d54c-9930-49c8-9a10-44fe40fe8251}'),
+  contractID:       '@mozilla.org/dom/browser-element-proxy;1',
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIDOMGlobalPropertyInitializer,
+    Ci.nsIObserver]),
+
+  _window: null,
+  _innerWindowID: undefined,
+
+  get allowedAudioChannels() {
+    return this._window.navigator.mozAudioChannelManager ?
+      this._window.navigator.mozAudioChannelManager.allowedAudioChannels :
+      null;
+  },
+
+  init: function(win) {
+    this._window = win;
+    this._innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils)
+                          .currentInnerWindowID;
+
+    this._sendToParent('_proxyInstanceInit');
+    Services.obs.addObserver(this, 'browser-element-api:proxy', false);
+  },
+
+  uninit: function(win) {
+    this._sendToParent('_proxyInstanceUninit');
+
+    this._window = null;
+    this._innerWindowID = undefined;
+
+    Services.obs.removeObserver(this, 'browser-element-api:proxy');
+  },
+
+  observe: function(subject, topic, stringifedData) {
+    let data = JSON.parse(stringifedData);
+
+    if (subject !== this._window ||
+        data.innerWindowID !== data.innerWindowID) {
+      return;
+    }
+
+    if (data.eventName) {
+      this._fireEvent(data.eventName, JSON.parse(data.eventDetailString));
+
+      return;
+    }
+
+    if ('domRequestId' in data) {
+      let req = this._pendingDOMRequests[data.domRequestId];
+      this._pendingDOMRequests[data.domRequestId] = undefined;
+
+      if (!req) {
+        dump('BrowserElementProxy Error: ' +
+          'Multiple observer messages for the same DOMRequest result.\n');
+        return;
+      }
+
+      if ('result' in data) {
+        let clientObj = Cu.cloneInto(data.result, this._window);
+        Services.DOMRequest.fireSuccess(req, clientObj);
+      } else {
+        let clientObj = Cu.cloneInto(data.error, this._window);
+        Services.DOMRequest.fireSuccess(req, clientObj);
+      }
+
+      return;
+    }
+
+    dump('BrowserElementProxy Error: ' +
+          'Received unhandled observer messages ' + stringifedData + '.\n');
+  },
+
+  _sendDOMRequest: function(methodName, args) {
+    let id = this._pendingDOMRequests.length;
+    let req = Services.DOMRequest.createRequest(this._window);
+
+    this._pendingDOMRequests.push(req);
+    this._sendToParent(methodName, args, id);
+
+    return req;
+  },
+
+  _sendToParent: function(methodName, args, domRequestId) {
+    let data = {
+      methodName: methodName,
+      args: args,
+      innerWindowID: this._innerWindowID
+    };
+
+    if (domRequestId) {
+      data.domRequestId = domRequestId;
+    }
+
+    Services.obs.notifyObservers(
+      this._window, 'browser-element-api:proxy-call', JSON.stringify(data));
+  },
+
+  _fireEvent: function(name, detail) {
+    let evt = this._createEvent(name, detail,
+                                /* cancelable = */ false);
+    this.__DOM_IMPL__.dispatchEvent(evt);
+  },
+
+  _createEvent: function(evtName, detail, cancelable) {
+    // This will have to change if we ever want to send a CustomEvent with null
+    // detail.  For now, it's OK.
+    if (detail !== undefined && detail !== null) {
+      detail = Cu.cloneInto(detail, this._window);
+      return new this._window.CustomEvent(evtName,
+                                          { bubbles: false,
+                                            cancelable: cancelable,
+                                            detail: detail });
+    }
+
+    return new this._window.Event(evtName,
+                                  { bubbles: false,
+                                    cancelable: cancelable });
+  },
+
+  setVisible: defineNoReturnMethod('setVisible'),
+  setActive: defineNoReturnMethod('setActive'),
+  sendMouseEvent: defineNoReturnMethod('sendMouseEvent'),
+  sendTouchEvent: defineNoReturnMethod('sendTouchEvent'),
+  goBack: defineNoReturnMethod('goBack'),
+  goForward: defineNoReturnMethod('goForward'),
+  reload: defineNoReturnMethod('reload'),
+  stop: defineNoReturnMethod('stop'),
+  zoom: defineNoReturnMethod('zoom'),
+  setNFCFocus: defineNoReturnMethod('setNFCFocus'),
+  findAll: defineNoReturnMethod('findAll'),
+  findNext: defineNoReturnMethod('findNext'),
+  clearMatch: defineNoReturnMethod('clearMatch'),
+  mute: defineNoReturnMethod('mute'),
+  unmute: defineNoReturnMethod('unmute'),
+  setVolume: defineNoReturnMethod('setVolume'),
+
+  getVisible: defineDOMRequestMethod('getVisible'),
+  download: defineDOMRequestMethod('download'),
+  purgeHistory: defineDOMRequestMethod('purgeHistory'),
+  getCanGoBack: defineDOMRequestMethod('getCanGoBack'),
+  getCanGoForward: defineDOMRequestMethod('getCanGoForward'),
+  getContentDimensions: defineDOMRequestMethod('getContentDimensions'),
+  setInputMethodActive: defineDOMRequestMethod('setInputMethodActive'),
+  executeScript: defineDOMRequestMethod('executeScript'),
+  getMuted: defineDOMRequestMethod('getMuted'),
+  getVolume: defineDOMRequestMethod('getVolume'),
+
+  getActive: defineUnimplementedMethod('getActive'),
+  addNextPaintListener: defineUnimplementedMethod('addNextPaintListener'),
+  removeNextPaintListener: defineUnimplementedMethod('removeNextPaintListener'),
+  getScreenshot: defineUnimplementedMethod('getScreenshot')
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementProxy]);
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/BrowserElementProxy.manifest
@@ -0,0 +1,2 @@
+component {7e95d54c-9930-49c8-9a10-44fe40fe8251} BrowserElementProxy.js
+contract @mozilla.org/dom/browser-element-proxy;1 {7e95d54c-9930-49c8-9a10-44fe40fe8251}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_Proxy.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+function runTest() {
+  let frameUrl = SimpleTest.getTestFileURL('/file_empty.html');
+  SpecialPowers.pushPermissions([{
+    type: 'browser:embedded-system-app',
+    allow: true,
+    context: {
+      url: frameUrl,
+      appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+      isInBrowserElement: true
+    }
+  },{
+    type: 'browser',
+    allow: true,
+    context: {
+      url: frameUrl,
+      appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+      isInBrowserElement: true
+    }
+  }], createFrame);
+}
+
+var frame;
+var mm;
+
+function createFrame() {
+  let loadEnd = function() {
+    // The frame script running in the frame where the input is hosted.
+    let appFrameScript = function appFrameScript() {
+      let i = 1;
+
+      addMessageListener('test:next', function() {
+        try {
+          switch (i) {
+            case 1:
+              content.document.addEventListener(
+                'visibilitychange', function fn() {
+                content.document.removeEventListener('visibilitychange', fn);
+                sendAsyncMessage('test:done', {
+                  ok: true,
+                  msg: 'setVisible()'
+                });
+              });
+
+              // Test a no return method
+              content.navigator.mozBrowserElementProxy.setVisible(false);
+
+              break;
+            case 2:
+              // Test a DOMRequest method
+              var req = content.navigator.mozBrowserElementProxy.getVisible();
+              req.onsuccess = function() {
+                sendAsyncMessage('test:done', {
+                  ok: true,
+                  msg: 'getVisible()'
+                });
+              };
+
+              req.onerror = function() {
+                sendAsyncMessage('test:done', {
+                  ok: false,
+                  msg: 'getVisible() DOMRequest return error.'
+                });
+              };
+
+              break;
+            case 3:
+              // Test an unimplemented method
+              try {
+                content.navigator.mozBrowserElementProxy.getActive();
+                sendAsyncMessage('test:done', {
+                  ok: false,
+                  msg: 'getActive() should throw.'
+                });
+
+              } catch (e) {
+                sendAsyncMessage('test:done', {
+                  ok: true,
+                  msg: 'getActive() throws.'
+                });
+              }
+
+              break;
+            case 4:
+              content.navigator.mozBrowserElementProxy
+              .addEventListener(
+                'mozbrowserlocationchange',
+                function() {
+                  content.navigator.mozBrowserElementProxy
+                    .onmozbrowserlocationchange = null;
+
+                  sendAsyncMessage('test:done', {
+                    ok: true,
+                    msg: 'mozbrowserlocationchange'
+                  });
+              });
+
+              // Test event
+              content.location.hash = '#foo';
+
+              break;
+          }
+        } catch (e) {
+          sendAsyncMessage('test:done', {
+            ok: false, msg: 'thrown: ' + e.toString() });
+        }
+
+        i++;
+      });
+
+      sendAsyncMessage('test:done', {});
+    }
+
+    mm = SpecialPowers.getBrowserFrameMessageManager(frame);
+    mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+    mm.addMessageListener("test:done", next);
+  };
+
+  frame = document.createElement('iframe');
+  frame.setAttribute('mozbrowser', 'true');
+  frame.src = 'file_empty.html';
+  document.body.appendChild(frame);
+  frame.addEventListener('mozbrowserloadend', loadEnd);
+}
+
+var i = 0;
+function next(msg) {
+  let wrappedMsg = SpecialPowers.wrap(msg);
+  let isOK = wrappedMsg.data.ok;
+  let msgString = wrappedMsg.data.msg;
+
+  switch (i) {
+    case 0:
+      mm.sendAsyncMessage('test:next');
+      break;
+
+    case 1:
+    case 2:
+    case 3:
+      ok(isOK, msgString);
+      mm.sendAsyncMessage('test:next');
+      break;
+
+    case 4:
+      ok(isOK, msgString);
+      SimpleTest.finish();
+      break;
+  }
+
+  i++;
+}
+
+addEventListener('testready', runTest);
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -71,16 +71,18 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_OpenWindowRejected.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_Opensearch.html]
 [test_browserElement_oop_OpenTab.html]
 skip-if = (toolkit == 'gonk') # Disabled on emulator. See bug 1144015 comment 8
 [test_browserElement_oop_PrivateBrowsing.html]
 [test_browserElement_oop_PromptCheck.html]
 [test_browserElement_oop_PromptConfirm.html]
+[test_browserElement_oop_Proxy.html]
+skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
 [test_browserElement_oop_PurgeHistory.html]
 [test_browserElement_oop_Reload.html]
 [test_browserElement_oop_ReloadPostRequest.html]
 [test_browserElement_oop_RemoveBrowserElement.html]
 [test_browserElement_oop_ScrollEvent.html]
 [test_browserElement_oop_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) #TIMED_OUT, bug 766586
 [test_browserElement_oop_SelectionStateBlur.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -49,16 +49,17 @@ support-files =
   browserElement_OpenWindow.js
   browserElement_OpenWindowDifferentOrigin.js
   browserElement_OpenWindowInFrame.js
   browserElement_OpenWindowRejected.js
   browserElement_Opensearch.js
   browserElement_PrivateBrowsing.js
   browserElement_PromptCheck.js
   browserElement_PromptConfirm.js
+  browserElement_Proxy.js
   browserElement_PurgeHistory.js
   browserElement_Reload.js
   browserElement_ReloadPostRequest.js
   browserElement_RemoveBrowserElement.js
   browserElement_ScrollEvent.js
   browserElement_SecurityChange.js
   browserElement_SendEvent.js
   browserElement_SelectionStateBlur.js
@@ -199,16 +200,18 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenWindowInFrame.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenWindowRejected.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_Opensearch.html]
 [test_browserElement_inproc_PrivateBrowsing.html]
 [test_browserElement_inproc_PromptCheck.html]
 [test_browserElement_inproc_PromptConfirm.html]
+[test_browserElement_inproc_Proxy.html]
+skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
 [test_browserElement_inproc_PurgeHistory.html]
 [test_browserElement_inproc_ReloadPostRequest.html]
 [test_browserElement_inproc_RemoveBrowserElement.html]
 [test_browserElement_inproc_ScrollEvent.html]
 [test_browserElement_inproc_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) # android(TIMED_OUT, bug 766586) androidx86(TIMED_OUT, bug 766586)
 [test_browserElement_inproc_SelectionStateBlur.html]
 skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_Proxy.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1196654
+-->
+<head>
+  <title>Test for Bug 1196654</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.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=1196654">Mozilla Bug 1196654</a>
+
+<script type="application/javascript;version=1.7" src="browserElement_Proxy.js">
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_Proxy.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1196654
+-->
+<head>
+  <title>Test for Bug 1196654</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.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=1196654">Mozilla Bug 1196654</a>
+
+<script type="application/javascript;version=1.7" src="browserElement_Proxy.js">
+</script>
+</body>
+</html>
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -21,16 +21,18 @@ XPIDL_SOURCES += [
     'nsIBrowserElementAPI.idl',
 ]
 
 XPIDL_MODULE = 'browser-element'
 
 EXTRA_COMPONENTS += [
     'BrowserElementParent.js',
     'BrowserElementParent.manifest',
+    'BrowserElementProxy.js',
+    'BrowserElementProxy.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'BrowserElementPromptService.jsm',
 ]
 
 LOCAL_INCLUDES += [
     '../bluetooth',
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -449,405 +449,16 @@ ExtractFromURLSearchParams(const URLSear
                            nsIInputStream** aStream,
                            nsCString& aContentType)
 {
   nsAutoString serialized;
   aParams.Stringify(serialized);
   aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
   return NS_NewStringInputStream(aStream, serialized);
 }
-
-class MOZ_STACK_CLASS FillFormIterator final
-  : public URLSearchParams::ForEachIterator
-{
-public:
-  explicit FillFormIterator(nsFormData* aFormData)
-    : mFormData(aFormData)
-  {
-    MOZ_ASSERT(aFormData);
-  }
-
-  bool URLParamsIterator(const nsString& aName,
-                         const nsString& aValue) override
-  {
-    mFormData->Append(aName, aValue);
-    return true;
-  }
-
-private:
-  nsFormData* mFormData;
-};
-
-/**
- * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
- * This does not respect any encoding specified per entry, using UTF-8
- * throughout. This is as the Fetch spec states in the consume body algorithm.
- * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
- * unlike Necko we do not have to deal with receiving incomplete chunks of data.
- *
- * This parser will fail the entire parse on any invalid entry, so it will
- * never return a partially filled FormData.
- * The content-disposition header is used to figure out the name and filename
- * entries. The inclusion of the filename parameter decides if the entry is
- * inserted into the nsFormData as a string or a File.
- *
- * File blobs are copies of the underlying data string since we cannot adopt
- * char* chunks embedded within the larger body without significant effort.
- * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
- * friends to figure out if Fetch ends up copying big blobs to see if this is
- * worth optimizing.
- */
-class MOZ_STACK_CLASS FormDataParser
-{
-private:
-  RefPtr<nsFormData> mFormData;
-  nsCString mMimeType;
-  nsCString mData;
-
-  // Entry state, reset in START_PART.
-  nsCString mName;
-  nsCString mFilename;
-  nsCString mContentType;
-
-  enum
-  {
-    START_PART,
-    PARSE_HEADER,
-    PARSE_BODY,
-  } mState;
-
-  nsIGlobalObject* mParentObject;
-
-  // Reads over a boundary and sets start to the position after the end of the
-  // boundary. Returns false if no boundary is found immediately.
-  bool
-  PushOverBoundary(const nsACString& aBoundaryString,
-                   nsACString::const_iterator& aStart,
-                   nsACString::const_iterator& aEnd)
-  {
-    // We copy the end iterator to keep the original pointing to the real end
-    // of the string.
-    nsACString::const_iterator end(aEnd);
-    const char* beginning = aStart.get();
-    if (FindInReadable(aBoundaryString, aStart, end)) {
-      // We either should find the body immediately, or after 2 chars with the
-      // 2 chars being '-', everything else is failure.
-      if ((aStart.get() - beginning) == 0) {
-        aStart.advance(aBoundaryString.Length());
-        return true;
-      }
-
-      if ((aStart.get() - beginning) == 2) {
-        if (*(--aStart) == '-' && *(--aStart) == '-') {
-          aStart.advance(aBoundaryString.Length() + 2);
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-  // Reads over a CRLF and positions start after it.
-  bool
-  PushOverLine(nsACString::const_iterator& aStart)
-  {
-    if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
-      ++aStart; // advance to after CRLF
-      return true;
-    }
-
-    return false;
-  }
-
-  bool
-  FindCRLF(nsACString::const_iterator& aStart,
-           nsACString::const_iterator& aEnd)
-  {
-    nsACString::const_iterator end(aEnd);
-    return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
-  }
-
-  bool
-  ParseHeader(nsACString::const_iterator& aStart,
-              nsACString::const_iterator& aEnd,
-              bool* aWasEmptyHeader)
-  {
-    MOZ_ASSERT(aWasEmptyHeader);
-    // Set it to a valid value here so we don't forget later.
-    *aWasEmptyHeader = false;
-
-    const char* beginning = aStart.get();
-    nsACString::const_iterator end(aEnd);
-    if (!FindCRLF(aStart, end)) {
-      return false;
-    }
-
-    if (aStart.get() == beginning) {
-      *aWasEmptyHeader = true;
-      return true;
-    }
-
-    nsAutoCString header(beginning, aStart.get() - beginning);
-
-    nsACString::const_iterator headerStart, headerEnd;
-    header.BeginReading(headerStart);
-    header.EndReading(headerEnd);
-    if (!FindCharInReadable(':', headerStart, headerEnd)) {
-      return false;
-    }
-
-    nsAutoCString headerName(StringHead(header, headerStart.size_backward()));
-    headerName.CompressWhitespace();
-    if (!NS_IsValidHTTPToken(headerName)) {
-      return false;
-    }
-
-    nsAutoCString headerValue(Substring(++headerStart, headerEnd));
-    if (!NS_IsReasonableHTTPHeaderValue(headerValue)) {
-      return false;
-    }
-    headerValue.CompressWhitespace();
-
-    if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
-      nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
-      bool seenFormData = false;
-      while (tokenizer.hasMoreTokens()) {
-        const nsDependentCSubstring& token = tokenizer.nextToken();
-        if (token.IsEmpty()) {
-          continue;
-        }
-
-        if (token.EqualsLiteral("form-data")) {
-          seenFormData = true;
-          continue;
-        }
-
-        if (seenFormData &&
-            StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
-          mName = StringTail(token, token.Length() - 5);
-          mName.Trim(" \"");
-          continue;
-        }
-
-        if (seenFormData &&
-            StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
-          mFilename = StringTail(token, token.Length() - 9);
-          mFilename.Trim(" \"");
-          continue;
-        }
-      }
-
-      if (mName.IsVoid()) {
-        // Could not parse a valid entry name.
-        return false;
-      }
-    } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
-      mContentType = headerValue;
-    }
-
-    return true;
-  }
-
-  // The end of a body is marked by a CRLF followed by the boundary. So the
-  // CRLF is part of the boundary and not the body, but any prior CRLFs are
-  // part of the body. This will position the iterator at the beginning of the
-  // boundary (after the CRLF).
-  bool
-  ParseBody(const nsACString& aBoundaryString,
-            nsACString::const_iterator& aStart,
-            nsACString::const_iterator& aEnd)
-  {
-    const char* beginning = aStart.get();
-
-    // Find the boundary marking the end of the body.
-    nsACString::const_iterator end(aEnd);
-    if (!FindInReadable(aBoundaryString, aStart, end)) {
-      return false;
-    }
-
-    // We found a boundary, strip the just prior CRLF, and consider
-    // everything else the body section.
-    if (aStart.get() - beginning < 2) {
-      // Only the first entry can have a boundary right at the beginning. Even
-      // an empty body will have a CRLF before the boundary. So this is
-      // a failure.
-      return false;
-    }
-
-    // Check that there is a CRLF right before the boundary.
-    aStart.advance(-2);
-
-    // Skip optional hyphens.
-    if (*aStart == '-' && *(aStart.get()+1) == '-') {
-      if (aStart.get() - beginning < 2) {
-        return false;
-      }
-
-      aStart.advance(-2);
-    }
-
-    if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
-      return false;
-    }
-
-    nsAutoCString body(beginning, aStart.get() - beginning);
-
-    // Restore iterator to after the \r\n as we promised.
-    // We do not need to handle the extra hyphens case since our boundary
-    // parser in PushOverBoundary()
-    aStart.advance(2);
-
-    if (!mFormData) {
-      mFormData = new nsFormData();
-    }
-
-    NS_ConvertUTF8toUTF16 name(mName);
-
-    if (mFilename.IsVoid()) {
-      mFormData->Append(name, NS_ConvertUTF8toUTF16(body));
-    } else {
-      // Unfortunately we've to copy the data first since all our strings are
-      // going to free it. We also need fallible alloc, so we can't just use
-      // ToNewCString().
-      char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
-      if (!copy) {
-        NS_WARNING("Failed to copy File entry body.");
-        return false;
-      }
-      nsCString::const_iterator bodyIter, bodyEnd;
-      body.BeginReading(bodyIter);
-      body.EndReading(bodyEnd);
-      char *p = copy;
-      while (bodyIter != bodyEnd) {
-        *p++ = *bodyIter++;
-      }
-      p = nullptr;
-
-      RefPtr<Blob> file =
-        File::CreateMemoryFile(mParentObject,
-                               reinterpret_cast<void *>(copy), body.Length(),
-                               NS_ConvertUTF8toUTF16(mFilename),
-                               NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
-      Optional<nsAString> dummy;
-      mFormData->Append(name, *file, dummy);
-    }
-
-    return true;
-  }
-
-public:
-  FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
-    : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
-  {
-  }
-
-  bool
-  Parse()
-  {
-    // Determine boundary from mimetype.
-    const char* boundaryId = nullptr;
-    boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
-    if (!boundaryId) {
-      return false;
-    }
-
-    boundaryId = strchr(boundaryId, '=');
-    if (!boundaryId) {
-      return false;
-    }
-
-    // Skip over '='.
-    boundaryId++;
-
-    char *attrib = (char *) strchr(boundaryId, ';');
-    if (attrib) *attrib = '\0';
-
-    nsAutoCString boundaryString(boundaryId);
-    if (attrib) *attrib = ';';
-
-    boundaryString.Trim(" \"");
-
-    if (boundaryString.Length() == 0) {
-      return false;
-    }
-
-    nsACString::const_iterator start, end;
-    mData.BeginReading(start);
-    // This should ALWAYS point to the end of data.
-    // Helpers make copies.
-    mData.EndReading(end);
-
-    while (start != end) {
-      switch(mState) {
-        case START_PART:
-          mName.SetIsVoid(true);
-          mFilename.SetIsVoid(true);
-          mContentType = NS_LITERAL_CSTRING("text/plain");
-
-          // MUST start with boundary.
-          if (!PushOverBoundary(boundaryString, start, end)) {
-            return false;
-          }
-
-          if (start != end && *start == '-') {
-            // End of data.
-            if (!mFormData) {
-              mFormData = new nsFormData();
-            }
-            return true;
-          }
-
-          if (!PushOverLine(start)) {
-            return false;
-          }
-          mState = PARSE_HEADER;
-          break;
-
-        case PARSE_HEADER:
-          bool emptyHeader;
-          if (!ParseHeader(start, end, &emptyHeader)) {
-            return false;
-          }
-
-          if (!PushOverLine(start)) {
-            return false;
-          }
-
-          mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
-          break;
-
-        case PARSE_BODY:
-          if (mName.IsVoid()) {
-            NS_WARNING("No content-disposition header with a valid name was "
-                       "found. Failing at body parse.");
-            return false;
-          }
-
-          if (!ParseBody(boundaryString, start, end)) {
-            return false;
-          }
-
-          mState = START_PART;
-          break;
-
-        default:
-          MOZ_CRASH("Invalid case");
-      }
-    }
-
-    NS_NOTREACHED("Should never reach here.");
-    return false;
-  }
-
-  already_AddRefed<nsFormData> FormData()
-  {
-    return mFormData.forget();
-  }
-};
 } // namespace
 
 nsresult
 ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
                           nsIInputStream** aStream,
                           nsCString& aContentType)
 {
   MOZ_ASSERT(aStream);
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -298,24 +298,34 @@ FetchDriver::HttpFetch()
     nsAutoCString method;
     mRequest->GetMethod(method);
     rv = httpChan->SetRequestMethod(method);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Set the same headers.
     nsAutoTArray<InternalHeaders::Entry, 5> headers;
     mRequest->Headers()->GetEntries(headers);
+    bool hasAccept = false;
     for (uint32_t i = 0; i < headers.Length(); ++i) {
+      if (!hasAccept && headers[i].mName.EqualsLiteral("accept")) {
+        hasAccept = true;
+      }
       if (headers[i].mValue.IsEmpty()) {
         httpChan->SetEmptyRequestHeader(headers[i].mName);
       } else {
         httpChan->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */);
       }
     }
 
+    if (!hasAccept) {
+      httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"),
+                                 NS_LITERAL_CSTRING("*/*"),
+                                 false /* merge */);
+    }
+
     // Step 2. Set the referrer.
     nsAutoString referrer;
     mRequest->GetReferrer(referrer);
     if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
       rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
                                                          mDocument,
                                                          httpChan);
       NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -117,16 +117,428 @@ FetchUtil::ConsumeBlob(nsISupports* aPar
 
   if (!blob) {
     aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
     return nullptr;
   }
   return blob.forget();
 }
 
+static bool
+FindCRLF(nsACString::const_iterator& aStart,
+         nsACString::const_iterator& aEnd)
+{
+  nsACString::const_iterator end(aEnd);
+  return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
+}
+
+// Reads over a CRLF and positions start after it.
+static bool
+PushOverLine(nsACString::const_iterator& aStart)
+{
+  if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
+    ++aStart; // advance to after CRLF
+    return true;
+  }
+
+  return false;
+}
+
+// static
+bool
+FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
+                         nsACString::const_iterator& aEnd,
+                         nsCString& aHeaderName,
+                         nsCString& aHeaderValue,
+                         bool* aWasEmptyHeader)
+{
+  MOZ_ASSERT(aWasEmptyHeader);
+  // Set it to a valid value here so we don't forget later.
+  *aWasEmptyHeader = false;
+
+  const char* beginning = aStart.get();
+  nsACString::const_iterator end(aEnd);
+  if (!FindCRLF(aStart, end)) {
+    return false;
+  }
+
+  if (aStart.get() == beginning) {
+    *aWasEmptyHeader = true;
+    return true;
+  }
+
+  nsAutoCString header(beginning, aStart.get() - beginning);
+
+  nsACString::const_iterator headerStart, headerEnd;
+  header.BeginReading(headerStart);
+  header.EndReading(headerEnd);
+  if (!FindCharInReadable(':', headerStart, headerEnd)) {
+    return false;
+  }
+
+  aHeaderName.Assign(StringHead(header, headerStart.size_backward()));
+  aHeaderName.CompressWhitespace();
+  if (!NS_IsValidHTTPToken(aHeaderName)) {
+    return false;
+  }
+
+  aHeaderValue.Assign(Substring(++headerStart, headerEnd));
+  if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
+    return false;
+  }
+  aHeaderValue.CompressWhitespace();
+
+  return PushOverLine(aStart);
+}
+
+namespace {
+class MOZ_STACK_CLASS FillFormIterator final
+  : public URLSearchParams::ForEachIterator
+{
+public:
+  explicit FillFormIterator(nsFormData* aFormData)
+    : mFormData(aFormData)
+  {
+    MOZ_ASSERT(aFormData);
+  }
+
+  bool URLParamsIterator(const nsString& aName,
+                         const nsString& aValue) override
+  {
+    mFormData->Append(aName, aValue);
+    return true;
+  }
+
+private:
+  nsFormData* mFormData;
+};
+
+/**
+ * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
+ * This does not respect any encoding specified per entry, using UTF-8
+ * throughout. This is as the Fetch spec states in the consume body algorithm.
+ * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
+ * unlike Necko we do not have to deal with receiving incomplete chunks of data.
+ *
+ * This parser will fail the entire parse on any invalid entry, so it will
+ * never return a partially filled FormData.
+ * The content-disposition header is used to figure out the name and filename
+ * entries. The inclusion of the filename parameter decides if the entry is
+ * inserted into the nsFormData as a string or a File.
+ *
+ * File blobs are copies of the underlying data string since we cannot adopt
+ * char* chunks embedded within the larger body without significant effort.
+ * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
+ * friends to figure out if Fetch ends up copying big blobs to see if this is
+ * worth optimizing.
+ */
+class MOZ_STACK_CLASS FormDataParser
+{
+private:
+  RefPtr<nsFormData> mFormData;
+  nsCString mMimeType;
+  nsCString mData;
+
+  // Entry state, reset in START_PART.
+  nsCString mName;
+  nsCString mFilename;
+  nsCString mContentType;
+
+  enum
+  {
+    START_PART,
+    PARSE_HEADER,
+    PARSE_BODY,
+  } mState;
+
+  nsIGlobalObject* mParentObject;
+
+  // Reads over a boundary and sets start to the position after the end of the
+  // boundary. Returns false if no boundary is found immediately.
+  bool
+  PushOverBoundary(const nsACString& aBoundaryString,
+                   nsACString::const_iterator& aStart,
+                   nsACString::const_iterator& aEnd)
+  {
+    // We copy the end iterator to keep the original pointing to the real end
+    // of the string.
+    nsACString::const_iterator end(aEnd);
+    const char* beginning = aStart.get();
+    if (FindInReadable(aBoundaryString, aStart, end)) {
+      // We either should find the body immediately, or after 2 chars with the
+      // 2 chars being '-', everything else is failure.
+      if ((aStart.get() - beginning) == 0) {
+        aStart.advance(aBoundaryString.Length());
+        return true;
+      }
+
+      if ((aStart.get() - beginning) == 2) {
+        if (*(--aStart) == '-' && *(--aStart) == '-') {
+          aStart.advance(aBoundaryString.Length() + 2);
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  bool
+  ParseHeader(nsACString::const_iterator& aStart,
+              nsACString::const_iterator& aEnd,
+              bool* aWasEmptyHeader)
+  {
+    nsAutoCString headerName, headerValue;
+    if (!FetchUtil::ExtractHeader(aStart, aEnd,
+                                  headerName, headerValue,
+                                  aWasEmptyHeader)) {
+      return false;
+    }
+    if (*aWasEmptyHeader) {
+      return true;
+    }
+
+    if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
+      nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
+      bool seenFormData = false;
+      while (tokenizer.hasMoreTokens()) {
+        const nsDependentCSubstring& token = tokenizer.nextToken();
+        if (token.IsEmpty()) {
+          continue;
+        }
+
+        if (token.EqualsLiteral("form-data")) {
+          seenFormData = true;
+          continue;
+        }
+
+        if (seenFormData &&
+            StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
+          mName = StringTail(token, token.Length() - 5);
+          mName.Trim(" \"");
+          continue;
+        }
+
+        if (seenFormData &&
+            StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
+          mFilename = StringTail(token, token.Length() - 9);
+          mFilename.Trim(" \"");
+          continue;
+        }
+      }
+
+      if (mName.IsVoid()) {
+        // Could not parse a valid entry name.
+        return false;
+      }
+    } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
+      mContentType = headerValue;
+    }
+
+    return true;
+  }
+
+  // The end of a body is marked by a CRLF followed by the boundary. So the
+  // CRLF is part of the boundary and not the body, but any prior CRLFs are
+  // part of the body. This will position the iterator at the beginning of the
+  // boundary (after the CRLF).
+  bool
+  ParseBody(const nsACString& aBoundaryString,
+            nsACString::const_iterator& aStart,
+            nsACString::const_iterator& aEnd)
+  {
+    const char* beginning = aStart.get();
+
+    // Find the boundary marking the end of the body.
+    nsACString::const_iterator end(aEnd);
+    if (!FindInReadable(aBoundaryString, aStart, end)) {
+      return false;
+    }
+
+    // We found a boundary, strip the just prior CRLF, and consider
+    // everything else the body section.
+    if (aStart.get() - beginning < 2) {
+      // Only the first entry can have a boundary right at the beginning. Even
+      // an empty body will have a CRLF before the boundary. So this is
+      // a failure.
+      return false;
+    }
+
+    // Check that there is a CRLF right before the boundary.
+    aStart.advance(-2);
+
+    // Skip optional hyphens.
+    if (*aStart == '-' && *(aStart.get()+1) == '-') {
+      if (aStart.get() - beginning < 2) {
+        return false;
+      }
+
+      aStart.advance(-2);
+    }
+
+    if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
+      return false;
+    }
+
+    nsAutoCString body(beginning, aStart.get() - beginning);
+
+    // Restore iterator to after the \r\n as we promised.
+    // We do not need to handle the extra hyphens case since our boundary
+    // parser in PushOverBoundary()
+    aStart.advance(2);
+
+    if (!mFormData) {
+      mFormData = new nsFormData();
+    }
+
+    NS_ConvertUTF8toUTF16 name(mName);
+
+    if (mFilename.IsVoid()) {
+      mFormData->Append(name, NS_ConvertUTF8toUTF16(body));
+    } else {
+      // Unfortunately we've to copy the data first since all our strings are
+      // going to free it. We also need fallible alloc, so we can't just use
+      // ToNewCString().
+      char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
+      if (!copy) {
+        NS_WARNING("Failed to copy File entry body.");
+        return false;
+      }
+      nsCString::const_iterator bodyIter, bodyEnd;
+      body.BeginReading(bodyIter);
+      body.EndReading(bodyEnd);
+      char *p = copy;
+      while (bodyIter != bodyEnd) {
+        *p++ = *bodyIter++;
+      }
+      p = nullptr;
+
+      RefPtr<Blob> file =
+        File::CreateMemoryFile(mParentObject,
+                               reinterpret_cast<void *>(copy), body.Length(),
+                               NS_ConvertUTF8toUTF16(mFilename),
+                               NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
+      Optional<nsAString> dummy;
+      mFormData->Append(name, *file, dummy);
+    }
+
+    return true;
+  }
+
+public:
+  FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
+    : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
+  {
+  }
+
+  bool
+  Parse()
+  {
+    // Determine boundary from mimetype.
+    const char* boundaryId = nullptr;
+    boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
+    if (!boundaryId) {
+      return false;
+    }
+
+    boundaryId = strchr(boundaryId, '=');
+    if (!boundaryId) {
+      return false;
+    }
+
+    // Skip over '='.
+    boundaryId++;
+
+    char *attrib = (char *) strchr(boundaryId, ';');
+    if (attrib) *attrib = '\0';
+
+    nsAutoCString boundaryString(boundaryId);
+    if (attrib) *attrib = ';';
+
+    boundaryString.Trim(" \"");
+
+    if (boundaryString.Length() == 0) {
+      return false;
+    }
+
+    nsACString::const_iterator start, end;
+    mData.BeginReading(start);
+    // This should ALWAYS point to the end of data.
+    // Helpers make copies.
+    mData.EndReading(end);
+
+    while (start != end) {
+      switch(mState) {
+        case START_PART:
+          mName.SetIsVoid(true);
+          mFilename.SetIsVoid(true);
+          mContentType = NS_LITERAL_CSTRING("text/plain");
+
+          // MUST start with boundary.
+          if (!PushOverBoundary(boundaryString, start, end)) {
+            return false;
+          }
+
+          if (start != end && *start == '-') {
+            // End of data.
+            if (!mFormData) {
+              mFormData = new nsFormData();
+            }
+            return true;
+          }
+
+          if (!PushOverLine(start)) {
+            return false;
+          }
+          mState = PARSE_HEADER;
+          break;
+
+        case PARSE_HEADER:
+          bool emptyHeader;
+          if (!ParseHeader(start, end, &emptyHeader)) {
+            return false;
+          }
+
+          if (emptyHeader && !PushOverLine(start)) {
+            return false;
+          }
+
+          mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
+          break;
+
+        case PARSE_BODY:
+          if (mName.IsVoid()) {
+            NS_WARNING("No content-disposition header with a valid name was "
+                       "found. Failing at body parse.");
+            return false;
+          }
+
+          if (!ParseBody(boundaryString, start, end)) {
+            return false;
+          }
+
+          mState = START_PART;
+          break;
+
+        default:
+          MOZ_CRASH("Invalid case");
+      }
+    }
+
+    NS_NOTREACHED("Should never reach here.");
+    return false;
+  }
+
+  already_AddRefed<nsFormData> FormData()
+  {
+    return mFormData.forget();
+  }
+};
+}
+
 // static
 already_AddRefed<nsFormData>
 FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
                            const nsCString& aStr, ErrorResult& aRv)
 {
   NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
 
   // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
--- a/dom/fetch/FetchUtil.h
+++ b/dom/fetch/FetchUtil.h
@@ -60,13 +60,23 @@ public:
 
   /**
    * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
    * Sets |aRv| to a syntax error if |aStr| contains invalid data.
    */
   static void
   ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
               const nsString& aStr, ErrorResult& aRv);
+
+  /**
+   * Extracts an HTTP header from a substring range.
+   */
+  static bool
+  ExtractHeader(nsACString::const_iterator& aStart,
+                nsACString::const_iterator& aEnd,
+                nsCString& aHeaderName,
+                nsCString& aHeaderValue,
+                bool* aWasEmptyHeader);
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif
--- a/dom/html/nsFormSubmission.cpp
+++ b/dom/html/nsFormSubmission.cpp
@@ -689,65 +689,40 @@ nsFSTextPlain::GetEncodedSubmission(nsIU
   return rv;
 }
 
 // --------------------------------------------------------------------------
 
 nsEncodingFormSubmission::nsEncodingFormSubmission(const nsACString& aCharset,
                                                    nsIContent* aOriginatingElement)
   : nsFormSubmission(aCharset, aOriginatingElement)
+  , mEncoder(aCharset)
 {
-  nsAutoCString charset(aCharset);
-  // canonical name is passed so that we just have to check against
-  // *our* canonical names listed in charsetaliases.properties
-  if (charset.EqualsLiteral("ISO-8859-1")) {
-    charset.AssignLiteral("windows-1252");
-  }
-
-  if (!(charset.EqualsLiteral("UTF-8") || charset.EqualsLiteral("gb18030"))) {
-    NS_ConvertUTF8toUTF16 charsetUtf16(charset);
+  if (!(aCharset.EqualsLiteral("UTF-8") || aCharset.EqualsLiteral("gb18030"))) {
+    NS_ConvertUTF8toUTF16 charsetUtf16(aCharset);
     const char16_t* charsetPtr = charsetUtf16.get();
     SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument()
                                       : nullptr,
                   "CannotEncodeAllUnicode",
                   &charsetPtr,
                   1);
   }
-
-  mEncoder = do_CreateInstance(NS_SAVEASCHARSET_CONTRACTID);
-  if (mEncoder) {
-    nsresult rv =
-      mEncoder->Init(charset.get(),
-                     (nsISaveAsCharset::attr_EntityAfterCharsetConv + 
-                      nsISaveAsCharset::attr_FallbackDecimalNCR),
-                     0);
-    if (NS_FAILED(rv)) {
-      mEncoder = nullptr;
-    }
-  }
 }
 
 nsEncodingFormSubmission::~nsEncodingFormSubmission()
 {
 }
 
 // i18n helper routines
 nsresult
 nsEncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut,
                                     bool aHeaderEncode)
 {
-  if (mEncoder && !aStr.IsEmpty()) {
-    aOut.Truncate();
-    nsresult rv = mEncoder->Convert(PromiseFlatString(aStr).get(),
-                                    getter_Copies(aOut));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else {
-    // fall back to UTF-8
-    CopyUTF16toUTF8(aStr, aOut);
+  if (!mEncoder.Encode(aStr, aOut)) {
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (aHeaderEncode) {
     aOut.Adopt(nsLinebreakConverter::
                ConvertLineBreaks(aOut.get(),
                                  nsLinebreakConverter::eLinebreakAny,
                                  nsLinebreakConverter::eLinebreakSpace));
     aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""),
--- a/dom/html/nsFormSubmission.h
+++ b/dom/html/nsFormSubmission.h
@@ -5,21 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIFormSubmission_h___
 #define nsIFormSubmission_h___
 
 #include "mozilla/Attributes.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIContent.h"
+#include "nsNCRFallbackEncoderWrapper.h"
 
 class nsIURI;
 class nsIInputStream;
 class nsGenericHTMLElement;
-class nsISaveAsCharset;
 class nsIMultiplexInputStream;
 
 namespace mozilla {
 namespace dom {
 class File;
 } // namespace dom
 } // namespace mozilla
 
@@ -135,17 +135,17 @@ public:
    *                      all quotes
    * @throws an error if UnicodeToNewBytes fails
    */
   nsresult EncodeVal(const nsAString& aStr, nsCString& aResult,
                      bool aHeaderEncode);
 
 private:
   // The encoder that will encode Unicode names and values
-  nsCOMPtr<nsISaveAsCharset> mEncoder;
+  nsNCRFallbackEncoderWrapper mEncoder;
 };
 
 /**
  * Handle multipart/form-data encoding, which does files as well as normal
  * inputs.  This always does POST.
  */
 class nsFSMultipartFormData : public nsEncodingFormSubmission
 {
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -12,23 +12,23 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Endian.h"
 #include "VideoUtils.h"
 #include "TimeUnits.h"
 #include "prenv.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gMP3DemuxerLog;
-#define MP3DEMUXER_LOG(msg, ...) \
+#define MP3LOG(msg, ...) \
   MOZ_LOG(gMP3DemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__))
-#define MP3DEMUXER_LOGV(msg, ...) \
+#define MP3LOGV(msg, ...) \
   MOZ_LOG(gMP3DemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__))
 #else
-#define MP3DEMUXER_LOG(msg, ...)
-#define MP3DEMUXER_LOGV(msg, ...)
+#define MP3LOG(msg, ...)
+#define MP3LOGV(msg, ...)
 #endif
 
 using mozilla::media::TimeUnit;
 using mozilla::media::TimeIntervals;
 using mp4_demuxer::ByteReader;
 
 namespace mozilla {
 namespace mp3 {
@@ -45,23 +45,23 @@ MP3Demuxer::InitInternal() {
     mTrackDemuxer = new MP3TrackDemuxer(mSource);
   }
   return mTrackDemuxer->Init();
 }
 
 RefPtr<MP3Demuxer::InitPromise>
 MP3Demuxer::Init() {
   if (!InitInternal()) {
-    MP3DEMUXER_LOG("MP3Demuxer::Init() failure: waiting for data");
+    MP3LOG("MP3Demuxer::Init() failure: waiting for data");
 
     return InitPromise::CreateAndReject(
       DemuxerFailureReason::DEMUXER_ERROR, __func__);
   }
 
-  MP3DEMUXER_LOG("MP3Demuxer::Init() successful");
+  MP3LOG("MP3Demuxer::Init() successful");
   return InitPromise::CreateAndResolve(NS_OK, __func__);
 }
 
 bool
 MP3Demuxer::HasTrackType(TrackInfo::TrackType aType) const {
   return aType == TrackInfo::kAudioTrack;
 }
 
@@ -82,31 +82,39 @@ bool
 MP3Demuxer::IsSeekable() const {
   return true;
 }
 
 void
 MP3Demuxer::NotifyDataArrived() {
   // TODO: bug 1169485.
   NS_WARNING("Unimplemented function NotifyDataArrived");
-  MP3DEMUXER_LOGV("NotifyDataArrived()");
+  MP3LOGV("NotifyDataArrived()");
 }
 
 void
 MP3Demuxer::NotifyDataRemoved() {
   // TODO: bug 1169485.
   NS_WARNING("Unimplemented function NotifyDataRemoved");
-  MP3DEMUXER_LOGV("NotifyDataRemoved()");
+  MP3LOGV("NotifyDataRemoved()");
 }
 
 
 // MP3TrackDemuxer
 
 MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource)
   : mSource(aSource)
+  , mOffset(0)
+  , mFirstFrameOffset(0)
+  , mNumParsedFrames(0)
+  , mFrameIndex(0)
+  , mTotalFrameLen(0)
+  , mSamplesPerFrame(0)
+  , mSamplesPerSecond(0)
+  , mChannels(0)
 {
   Reset();
 
 #ifdef PR_LOGGING
   if (!gMP3DemuxerLog) {
     gMP3DemuxerLog = PR_NewLogModule("MP3Demuxer");
   }
 #endif
@@ -114,18 +122,18 @@ MP3TrackDemuxer::MP3TrackDemuxer(MediaRe
 
 bool
 MP3TrackDemuxer::Init() {
   Reset();
   FastSeek(TimeUnit());
   // Read the first frame to fetch sample rate and other meta data.
   RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
 
-  MP3DEMUXER_LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
-                 StreamLength(), !!frame);
+  MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
+         StreamLength(), !!frame);
 
   if (!frame) {
     return false;
   }
 
   // Rewind back to the stream begin to avoid dropping the first frame.
   FastSeek(TimeUnit());
 
@@ -134,19 +142,19 @@ MP3TrackDemuxer::Init() {
   }
 
   mInfo->mRate = mSamplesPerSecond;
   mInfo->mChannels = mChannels;
   mInfo->mBitDepth = 16;
   mInfo->mMimeType = "audio/mpeg";
   mInfo->mDuration = Duration().ToMicroseconds();
 
-  MP3DEMUXER_LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}",
-                 mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
-                 mInfo->mDuration);
+  MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}",
+         mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
+         mInfo->mDuration);
 
   return mSamplesPerSecond && mChannels;
 }
 
 #ifdef ENABLE_TESTS
 const FrameParser::Frame&
 MP3TrackDemuxer::LastFrame() const {
   return mParser.PrevFrame();
@@ -175,93 +183,97 @@ MP3TrackDemuxer::VBRInfo() const {
 
 UniquePtr<TrackInfo>
 MP3TrackDemuxer::GetInfo() const {
   return mInfo->Clone();
 }
 
 RefPtr<MP3TrackDemuxer::SeekPromise>
 MP3TrackDemuxer::Seek(TimeUnit aTime) {
+  // Efficiently seek to the position.
+  FastSeek(aTime);
+  // Correct seek position by scanning the next frames.
   const TimeUnit seekTime = ScanUntil(aTime);
 
   return SeekPromise::CreateAndResolve(seekTime, __func__);
 }
 
 TimeUnit
-MP3TrackDemuxer::FastSeek(TimeUnit aTime) {
-  MP3DEMUXER_LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
-                 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
-                 aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
-                 mOffset);
+MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) {
+  MP3LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
+         " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
+         aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
 
+  const auto& vbr = mParser.VBRInfo();
   if (!aTime.ToMicroseconds()) {
     // Quick seek to the beginning of the stream.
     mOffset = mFirstFrameOffset;
-    mFrameIndex = 0;
-    mParser.EndFrameSession();
-    return TimeUnit();
-  }
-
-  if (!mSamplesPerFrame || !mNumParsedFrames) {
-    return TimeUnit::FromMicroseconds(-1);
+  } else if (vbr.IsTOCPresent()) {
+    // Use TOC for more precise seeking.
+    const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) /
+                                                  Duration().ToMicroseconds();
+    mOffset = vbr.Offset(durationFrac);
+  } else if (AverageFrameLength() > 0) {
+    mOffset = mFirstFrameOffset + FrameIndexFromTime(aTime) *
+              AverageFrameLength();
   }
 
-  const int64_t numFrames = aTime.ToSeconds() *
-                            mSamplesPerSecond / mSamplesPerFrame;
-  mOffset = mFirstFrameOffset + numFrames * AverageFrameLength();
-  mFrameIndex = numFrames;
+  if (mOffset > mFirstFrameOffset && StreamLength() > 0) {
+    mOffset = std::min(StreamLength() - 1, mOffset);
+  }
 
-  MP3DEMUXER_LOG("FastSeek mSamplesPerSecond=%d mSamplesPerFrame=%d "
-                 "numFrames=%" PRId64,
-                 mSamplesPerSecond, mSamplesPerFrame, numFrames);
+  mFrameIndex = FrameIndexFromOffset(mOffset);
+  mParser.EndFrameSession();
 
-  mParser.EndFrameSession();
+  MP3LOG("FastSeek End TOC=%d avgFrameLen=%f mNumParsedFrames=%" PRIu64
+         " mFrameIndex=%" PRId64 " mFirstFrameOffset=%llu mOffset=%" PRIu64
+         " SL=%llu NumBytes=%u",
+         vbr.IsTOCPresent(), AverageFrameLength(), mNumParsedFrames, mFrameIndex,
+         mFirstFrameOffset, mOffset, StreamLength(), vbr.NumBytes().valueOr(0));
 
   return Duration(mFrameIndex);
 }
 
 TimeUnit
-MP3TrackDemuxer::ScanUntil(TimeUnit aTime) {
-  MP3DEMUXER_LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
-                 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
-                 aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
-                 mOffset);
+MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) {
+  MP3LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
+         " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
+         aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
 
   if (!aTime.ToMicroseconds()) {
     return FastSeek(aTime);
   }
 
   if (Duration(mFrameIndex) > aTime) {
     FastSeek(aTime);
   }
 
   MediaByteRange nextRange = FindNextFrame();
   while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
     nextRange = FindNextFrame();
-    MP3DEMUXER_LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64
-                    " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64,
-                    aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
-                    mOffset, Duration(mFrameIndex + 1));
+    MP3LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64
+            " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64,
+            aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
+            mOffset, Duration(mFrameIndex + 1));
   }
 
-  MP3DEMUXER_LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64
-                 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
-                 aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex,
-                 mOffset);
+  MP3LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64
+         " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
+         aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
 
   return Duration(mFrameIndex);
 }
 
 RefPtr<MP3TrackDemuxer::SamplesPromise>
 MP3TrackDemuxer::GetSamples(int32_t aNumSamples) {
-  MP3DEMUXER_LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
-                  " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d "
-                  "mSamplesPerSecond=%d mChannels=%d",
-                  aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
-                  mSamplesPerFrame, mSamplesPerSecond, mChannels);
+  MP3LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
+          " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d "
+          "mSamplesPerSecond=%d mChannels=%d",
+          aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
+          mSamplesPerFrame, mSamplesPerSecond, mChannels);
 
   if (!aNumSamples) {
     return SamplesPromise::CreateAndReject(
         DemuxerFailureReason::DEMUXER_ERROR, __func__);
   }
 
   RefPtr<SamplesHolder> frames = new SamplesHolder();
 
@@ -269,43 +281,36 @@ MP3TrackDemuxer::GetSamples(int32_t aNum
     RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
     if (!frame) {
       break;
     }
 
     frames->mSamples.AppendElement(frame);
   }
 
-  MP3DEMUXER_LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64
-                  " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64
-                  " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
-                  "mChannels=%d",
-                  frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames,
-                  mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
+  MP3LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64
+          " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64
+          " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
+          "mChannels=%d",
+          frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames,
+          mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond,
+          mChannels);
 
   if (frames->mSamples.IsEmpty()) {
     return SamplesPromise::CreateAndReject(
         DemuxerFailureReason::END_OF_STREAM, __func__);
   }
   return SamplesPromise::CreateAndResolve(frames, __func__);
 }
 
 void
 MP3TrackDemuxer::Reset() {
-  MP3DEMUXER_LOG("Reset()");
+  MP3LOG("Reset()");
 
-  mOffset = 0;
-  mFirstFrameOffset = 0;
-  mNumParsedFrames = 0;
-  mFrameIndex = 0;
-  mTotalFrameLen = 0;
-  mSamplesPerFrame = 0;
-  mSamplesPerSecond = 0;
-  mChannels = 0;
-
+  FastSeek(TimeUnit());
   mParser.Reset();
 }
 
 RefPtr<MP3TrackDemuxer::SkipAccessPointPromise>
 MP3TrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) {
   // Will not be called for audio-only resources.
   return SkipAccessPointPromise::CreateAndReject(
     SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__);
@@ -333,20 +338,23 @@ MP3TrackDemuxer::StreamLength() const {
 }
 
 TimeUnit
 MP3TrackDemuxer::Duration() const {
   if (!mNumParsedFrames) {
     return TimeUnit::FromMicroseconds(-1);
   }
 
-  const int64_t streamLen = StreamLength();
-  // Assume we know the exact number of frames from the VBR header.
-  int64_t numFrames = mParser.VBRInfo().NumFrames();
-  if (numFrames < 0) {
+  int64_t numFrames = 0;
+  const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
+  if (numAudioFrames) {
+    // VBR headers don't include the VBR header frame.
+    numFrames = numAudioFrames.value() + 1;
+  } else {
+    const int64_t streamLen = StreamLength();
     if (streamLen < 0) {
       // Unknown length, we can't estimate duration.
       return TimeUnit::FromMicroseconds(-1);
     }
     numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength();
   }
   return Duration(numFrames);
 }
@@ -361,33 +369,34 @@ MP3TrackDemuxer::Duration(int64_t aNumFr
   return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
 }
 
 MediaByteRange
 MP3TrackDemuxer::FindNextFrame() {
   static const int BUFFER_SIZE = 4096;
   static const int MAX_SKIPPED_BYTES = 10 * BUFFER_SIZE;
 
-  MP3DEMUXER_LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
-                  " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
-                  " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
-                  mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
-                  mSamplesPerSecond, mChannels);
+  MP3LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
+          " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
+          " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
+          mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
+          mSamplesPerFrame, mSamplesPerSecond, mChannels);
 
   uint8_t buffer[BUFFER_SIZE];
   int32_t read = 0;
 
   bool foundFrame = false;
   int64_t frameHeaderOffset = 0;
 
   // Check whether we've found a valid MPEG frame.
   while (!foundFrame) {
     if ((!mParser.FirstFrame().Length() &&
          mOffset - mParser.ID3Header().Size() > MAX_SKIPPED_BYTES) ||
         (read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) {
+      MP3LOG("FindNext() EOS or exceeded MAX_SKIPPED_BYTES without a frame");
       // This is not a valid MPEG audio stream or we've reached EOS, give up.
       break;
     }
 
     ByteReader reader(buffer, read);
     uint32_t bytesToSkip = 0;
     foundFrame = mParser.Parse(&reader, &bytesToSkip);
     frameHeaderOffset = mOffset + reader.Offset() - FrameParser::FrameHeader::SIZE;
@@ -399,71 +408,71 @@ MP3TrackDemuxer::FindNextFrame() {
 
     // Advance mOffset by the amount of bytes read and if necessary,
     // skip an ID3v2 tag which stretches beyond the current buffer.
     NS_ENSURE_TRUE(mOffset + read + bytesToSkip > mOffset, MediaByteRange(0, 0));
     mOffset += read + bytesToSkip;
   }
 
   if (!foundFrame || !mParser.CurrentFrame().Length()) {
-    MP3DEMUXER_LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
-                   foundFrame, mParser.CurrentFrame().Length());
+    MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
+           foundFrame, mParser.CurrentFrame().Length());
     return { 0, 0 };
   }
 
-  MP3DEMUXER_LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
-                  " mFrameIndex=%" PRId64 " frameHeaderOffset=%d"
-                  " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
-                  "mChannels=%d",
-                  mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
-                  mTotalFrameLen, mSamplesPerFrame,
-                  mSamplesPerSecond, mChannels);
+  MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
+          " mFrameIndex=%" PRId64 " frameHeaderOffset=%d"
+          " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d"
+          " mChannels=%d",
+          mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
+          mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
 
   return { frameHeaderOffset, frameHeaderOffset + mParser.CurrentFrame().Length() };
 }
 
 bool
 MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) {
   if (!mNumParsedFrames || !aRange.Length()) {
     // We can't skip the first frame, since it could contain VBR headers.
     RefPtr<MediaRawData> frame(GetNextFrame(aRange));
     return frame;
   }
 
   UpdateState(aRange);
 
-  MP3DEMUXER_LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
-                  " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
-                  " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
-                  mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
-                  mSamplesPerSecond, mChannels);
+  MP3LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
+          " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
+          " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
+          mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
+          mSamplesPerFrame, mSamplesPerSecond, mChannels);
 
   return true;
 }
 
 already_AddRefed<MediaRawData>
 MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
-  MP3DEMUXER_LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})");
+  MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})",
+         aRange.mStart, aRange.Length());
   if (!aRange.Length()) {
     return nullptr;
   }
 
   RefPtr<MediaRawData> frame = new MediaRawData();
   frame->mOffset = aRange.mStart;
 
   nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
   if (!frameWriter->SetSize(aRange.Length())) {
-    MP3DEMUXER_LOG("GetNext() Exit failed to allocated media buffer");
+    MP3LOG("GetNext() Exit failed to allocated media buffer");
     return nullptr;
   }
 
   const uint32_t read = Read(frameWriter->Data(), frame->mOffset, frame->Size());
 
   if (read != aRange.Length()) {
-    MP3DEMUXER_LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size());
+    MP3LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size());
     return nullptr;
   }
 
   UpdateState(aRange);
 
   frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds();
   frame->mDuration = Duration(1).ToMicroseconds();
   frame->mTimecode = frame->mTime;
@@ -476,74 +485,111 @@ MP3TrackDemuxer::GetNextFrame(const Medi
     // First frame parsed, let's read VBR info if available.
     // TODO: read info that helps with seeking (bug 1163667).
     ByteReader reader(frame->Data(), frame->Size());
     mParser.ParseVBRHeader(&reader);
     reader.DiscardRemaining();
     mFirstFrameOffset = frame->mOffset;
   }
 
-  MP3DEMUXER_LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
-                  " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
-                  " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
-                  mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame,
-                  mSamplesPerSecond, mChannels);
+  MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
+          " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
+          " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
+          mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
+          mSamplesPerFrame, mSamplesPerSecond, mChannels);
 
   return frame.forget();
 }
 
+int64_t
+MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
+  int64_t frameIndex = 0;
+  const auto& vbr = mParser.VBRInfo();
+
+  if (vbr.NumBytes() && vbr.NumAudioFrames()) {
+    frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
+                 vbr.NumBytes().value() * vbr.NumAudioFrames().value();
+    frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
+  } else if (AverageFrameLength() > 0) {
+    frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength();
+  }
+
+  MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
+  return std::max<int64_t>(0, frameIndex);
+}
+
+int64_t
+MP3TrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const {
+  int64_t frameIndex = 0;
+  if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {
+    frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1;
+  }
+
+  MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(), frameIndex);
+  return std::max<int64_t>(0, frameIndex);
+}
+
 void
 MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
   // Prevent overflow.
   if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) {
     // These variables have a linear dependency and are only used to derive the
     // average frame length.
     mTotalFrameLen /= 2;
     mNumParsedFrames /= 2;
   }
 
   // Full frame parsed, move offset to its end.
   mOffset = aRange.mEnd;
 
   mTotalFrameLen += aRange.Length();
-  mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
-  mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
-  mChannels = mParser.CurrentFrame().Header().Channels();
+
+  if (!mSamplesPerFrame) {
+    mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
+    mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
+    mChannels = mParser.CurrentFrame().Header().Channels();
+  }
+
   ++mNumParsedFrames;
   ++mFrameIndex;
   MOZ_ASSERT(mFrameIndex > 0);
 
   // Prepare the parser for the next frame parsing session.
   mParser.EndFrameSession();
 }
 
 int32_t
 MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) {
-  MP3DEMUXER_LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
+  MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
 
   const int64_t streamLen = StreamLength();
   if (mInfo && streamLen > 0) {
     // Prevent blocking reads after successful initialization.
     aSize = std::min<int64_t>(aSize, streamLen - aOffset);
   }
 
   uint32_t read = 0;
-  MP3DEMUXER_LOGV("MP3TrackDemuxer::Read        -> ReadAt(%d)", aSize);
+  MP3LOGV("MP3TrackDemuxer::Read        -> ReadAt(%d)", aSize);
   const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast<char*>(aBuffer),
                                      static_cast<uint32_t>(aSize), &read);
   NS_ENSURE_SUCCESS(rv, 0);
   return static_cast<int32_t>(read);
 }
 
 double
 MP3TrackDemuxer::AverageFrameLength() const {
-  if (!mNumParsedFrames) {
-    return 0.0;
+  if (mNumParsedFrames) {
+    return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
   }
-  return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
+  const auto& vbr = mParser.VBRInfo();
+  if (vbr.NumBytes() && vbr.NumAudioFrames()) {
+    return static_cast<double>(vbr.NumBytes().value()) /
+           (vbr.NumAudioFrames().value() + 1);
+  }
+  return 0.0;
 }
 
 // FrameParser
 
 namespace frame_header {
 // FrameHeader mRaw byte offsets.
 static const int SYNC1 = 0;
 static const int SYNC2_VERSION_LAYER_PROTECTION = 1;
@@ -553,17 +599,16 @@ static const int CHANNELMODE_MODEEXT_COP
 
 FrameParser::FrameParser()
 {
 }
 
 void
 FrameParser::Reset() {
   mID3Parser.Reset();
-  mFirstFrame.Reset();
   mFrame.Reset();
 }
 
 void
 FrameParser::EndFrameSession() {
   if (!mID3Parser.Header().IsValid()) {
     // Reset ID3 tags only if we have not parsed a valid ID3 header yet.
     mID3Parser.Reset();
@@ -614,23 +659,23 @@ FrameParser::Parse(ByteReader* aReader, 
     const uint32_t tagSize = mID3Parser.Parse(aReader);
     if (tagSize) {
       // ID3 tag found, skip past it.
       const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE;
 
       if (skipSize > aReader->Remaining()) {
         // Skipping across the ID3v2 tag would take us past the end of the buffer, therefore we
         // return immediately and let the calling function handle skipping the rest of the tag.
-        MP3DEMUXER_LOGV("ID3v2 tag detected, size=%d, "
-                        "needing to skip %d bytes past the current buffer",
-                        tagSize, skipSize - aReader->Remaining());
+        MP3LOGV("ID3v2 tag detected, size=%d,"
+                " needing to skip %d bytes past the current buffer",
+                tagSize, skipSize - aReader->Remaining());
         *aBytesToSkip = skipSize - aReader->Remaining();
         return false;
       }
-      MP3DEMUXER_LOGV("ID3v2 tag detected, size=%d", tagSize);
+      MP3LOGV("ID3v2 tag detected, size=%d", tagSize);
       aReader->Read(skipSize);
     } else {
       // No ID3v2 tag found, rewinding reader in order to search for a MPEG frame header.
       aReader->Seek(prevReaderOffset);
     }
   }
 
   while (aReader->CanRead8() && !mFrame.ParseNext(aReader->ReadU8())) { }
@@ -831,68 +876,126 @@ FrameParser::FrameHeader::Update(uint8_t
   if (mPos < SIZE) {
     mRaw[mPos] = c;
   }
   return IsValid(mPos++);
 }
 
 // FrameParser::VBRHeader
 
+namespace vbr_header {
+static const char* TYPE_STR[3] = {"NONE", "XING", "VBRI"};
+static const uint32_t TOC_SIZE = 100;
+} // namespace vbr_header
+
 FrameParser::VBRHeader::VBRHeader()
-  : mNumFrames(-1),
-    mType(NONE)
+  : mType(NONE)
 {
 }
 
 FrameParser::VBRHeader::VBRHeaderType
 FrameParser::VBRHeader::Type() const {
   return mType;
 }
 
+const Maybe<uint32_t>&
+FrameParser::VBRHeader::NumAudioFrames() const {
+  return mNumAudioFrames;
+}
+
+const Maybe<uint32_t>&
+FrameParser::VBRHeader::NumBytes() const {
+  return mNumBytes;
+}
+
+const Maybe<uint32_t>&
+FrameParser::VBRHeader::Scale() const {
+  return mScale;
+}
+
+bool
+FrameParser::VBRHeader::IsTOCPresent() const {
+  return mTOC.size() == vbr_header::TOC_SIZE;
+}
+
 int64_t
-FrameParser::VBRHeader::NumFrames() const {
-  return mNumFrames;
+FrameParser::VBRHeader::Offset(float aDurationFac) const {
+  if (!IsTOCPresent()) {
+    return -1;
+  }
+
+  // Constrain the duration percentage to [0, 99].
+  const float durationPer = 100.0f * std::min(0.99f, std::max(0.0f, aDurationFac));
+  const size_t fullPer = durationPer;
+  const float rest = durationPer - fullPer;
+
+  MOZ_ASSERT(fullPer < mTOC.size());
+  int64_t offset = mTOC.at(fullPer);
+
+  if (rest > 0.0 && fullPer + 1 < mTOC.size()) {
+    offset += rest * (mTOC.at(fullPer + 1) - offset);
+  }
+
+  return offset;
 }
 
 bool
 FrameParser::VBRHeader::ParseXing(ByteReader* aReader) {
-  static const uint32_t TAG = BigEndian::readUint32("Xing");
-  static const uint32_t TAG2 = BigEndian::readUint32("Info");
-  static const uint32_t FRAME_COUNT_OFFSET = 8;
-  static const uint32_t FRAME_COUNT_SIZE = 4;
+  static const uint32_t XING_TAG = BigEndian::readUint32("Xing");
+  static const uint32_t INFO_TAG = BigEndian::readUint32("Info");
 
   enum Flags {
     NUM_FRAMES = 0x01,
     NUM_BYTES = 0x02,
     TOC = 0x04,
     VBR_SCALE = 0x08
   };
 
   MOZ_ASSERT(aReader);
   const size_t prevReaderOffset = aReader->Offset();
 
   // We have to search for the Xing header as its position can change.
-  while (aReader->Remaining() >= FRAME_COUNT_OFFSET + FRAME_COUNT_SIZE) {
-    if (aReader->PeekU32() != TAG && aReader->PeekU32() != TAG2) {
-      aReader->Read(1);
-      continue;
-    }
-    // Skip across the VBR header ID tag.
-    aReader->Read(sizeof(TAG));
+  while (aReader->CanRead32() &&
+         aReader->PeekU32() != XING_TAG && aReader->PeekU32() != INFO_TAG) {
+    aReader->Read(1);
+  }
 
-    const uint32_t flags = aReader->ReadU32();
-    if (flags & NUM_FRAMES) {
-      mNumFrames = aReader->ReadU32();
-    }
+  if (aReader->CanRead32()) {
+    // Skip across the VBR header ID tag.
+    aReader->ReadU32();
     mType = XING;
-    aReader->Seek(prevReaderOffset);
-    return true;
+  }
+  uint32_t flags = 0;
+  if (aReader->CanRead32()) {
+    flags = aReader->ReadU32();
+  }
+  if (flags & NUM_FRAMES && aReader->CanRead32()) {
+    mNumAudioFrames = Some(aReader->ReadU32());
+  }
+  if (flags & NUM_BYTES && aReader->CanRead32()) {
+    mNumBytes = Some(aReader->ReadU32());
   }
+  if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) {
+    if (!mNumBytes) {
+      // We don't have the stream size to calculate offsets, skip the TOC.
+      aReader->Read(vbr_header::TOC_SIZE);
+    } else {
+      mTOC.clear();
+      mTOC.reserve(vbr_header::TOC_SIZE);
+      for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) {
+        mTOC.push_back(1.0f / 256.0f * aReader->ReadU8() * mNumBytes.value());
+      }
+    }
+  }
+  if (flags & VBR_SCALE && aReader->CanRead32()) {
+    mScale = Some(aReader->ReadU32());
+  }
+
   aReader->Seek(prevReaderOffset);
-  return false;
+  return mType == XING;
 }
 
 bool
 FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) {
   static const uint32_t TAG = BigEndian::readUint32("VBRI");
   static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE;
   static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14;
   static const uint32_t MIN_FRAME_SIZE = OFFSET + 26;
@@ -903,29 +1006,36 @@ FrameParser::VBRHeader::ParseVBRI(ByteRe
   MOZ_ASSERT(aReader->PeekU16() & 0xFFE0);
   const size_t prevReaderOffset = aReader->Offset();
 
   // VBRI have a fixed relative position, so let's check for it there.
   if (aReader->Remaining() > MIN_FRAME_SIZE) {
     aReader->Seek(prevReaderOffset + OFFSET);
     if (aReader->ReadU32() == TAG) {
       aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET);
-      mNumFrames = aReader->ReadU32();
+      mNumAudioFrames = Some(aReader->ReadU32());
       mType = VBRI;
       aReader->Seek(prevReaderOffset);
       return true;
     }
   }
   aReader->Seek(prevReaderOffset);
   return false;
 }
 
 bool
 FrameParser::VBRHeader::Parse(ByteReader* aReader) {
-  return ParseVBRI(aReader) || ParseXing(aReader);
+  const bool rv = ParseVBRI(aReader) || ParseXing(aReader);
+  if (rv) {
+    MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s"
+           " NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%u",
+           vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0),
+           NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size());
+  }
+  return rv;
 }
 
 // FrameParser::Frame
 
 void
 FrameParser::Frame::Reset() {
   mHeader.Reset();
 }
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MP3_DEMUXER_H_
 #define MP3_DEMUXER_H_
 
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
 #include "MediaDataDemuxer.h"
 #include "MediaResource.h"
 #include "mp4_demuxer/ByteReader.h"
 
 namespace mozilla {
 namespace mp3 {
 
 class MP3TrackDemuxer;
@@ -200,30 +201,45 @@ public:
     // incremented via Update.
     int mPos;
   };
 
   // VBR frames may contain Xing or VBRI headers for additional info, we use
   // this class to parse them and access this info.
   class VBRHeader {
   public:
+    // Synchronize with vbr_header TYPE_STR on change.
     enum VBRHeaderType {
-      NONE,
+      NONE = 0,
       XING,
       VBRI
     };
 
     // Constructor.
     VBRHeader();
 
     // Returns the parsed VBR header type, or NONE if no valid header found.
     VBRHeaderType Type() const;
 
-    // Returns the total number of frames expected in the stream/file.
-    int64_t NumFrames() const;
+    // Returns the total number of audio frames (excluding the VBR header frame)
+    // expected in the stream/file.
+    const Maybe<uint32_t>& NumAudioFrames() const;
+
+    // Returns the expected size of the stream.
+    const Maybe<uint32_t>& NumBytes() const;
+
+    // Returns the VBR scale factor (0: best quality, 100: lowest quality).
+    const Maybe<uint32_t>& Scale() const;
+
+    // Returns true iff Xing/Info TOC (table of contents) is present.
+    bool IsTOCPresent() const;
+
+    // Returns the byte offset for the given duration percentage as a factor
+    // (0: begin, 1.0: end).
+    int64_t Offset(float aDurationFac) const;
 
     // Parses contents of given ByteReader for a valid VBR header.
     // The offset of the passed ByteReader needs to point to an MPEG frame begin,
     // as a VBRI-style header is searched at a fixed offset relative to frame begin.
     // Returns whether a valid VBR header was found in the range.
     bool Parse(mp4_demuxer::ByteReader* aReader);
 
   private:
@@ -235,17 +251,26 @@ public:
     // Parses contents of given ByteReader for a valid VBRI header.
     // The initial ByteReader offset will be preserved. It also needs to point
     // to the beginning of a valid MPEG frame, as VBRI headers are searched
     // at a fixed offset relative to frame begin.
     // Returns whether a valid VBRI header was found in the range.
     bool ParseVBRI(mp4_demuxer::ByteReader* aReader);
 
     // The total number of frames expected as parsed from a VBR header.
-    int64_t mNumFrames;
+    Maybe<uint32_t> mNumAudioFrames;
+
+    // The total number of bytes expected in the stream.
+    Maybe<uint32_t> mNumBytes;
+
+    // The VBR scale factor.
+    Maybe<uint32_t> mScale;
+
+    // The TOC table mapping duration percentage to byte offset.
+    std::vector<int64_t> mTOC;
 
     // The detected VBR header type.
     VBRHeaderType mType;
   };
 
   // Frame meta container used to parse and hold a frame header and side info.
   class Frame {
   public:
@@ -364,33 +389,39 @@ public:
   int64_t GetResourceOffset() const override;
   media::TimeIntervals GetBuffered() override;
 
 private:
   // Destructor.
   ~MP3TrackDemuxer() {}
 
   // Fast approximate seeking to given time.
-  media::TimeUnit FastSeek(media::TimeUnit aTime);
+  media::TimeUnit FastSeek(const media::TimeUnit& aTime);
 
   // Seeks by scanning the stream up to the given time for more accurate results.
-  media::TimeUnit ScanUntil(media::TimeUnit aTime);
+  media::TimeUnit ScanUntil(const media::TimeUnit& aTime);
 
   // Finds the next valid frame and returns its byte range.
   MediaByteRange FindNextFrame();
 
   // Skips the next frame given the provided byte range.
   bool SkipNextFrame(const MediaByteRange& aRange);
 
   // Returns the next MPEG frame, if available.
   already_AddRefed<MediaRawData> GetNextFrame(const MediaByteRange& aRange);
 
   // Updates post-read meta data.
   void UpdateState(const MediaByteRange& aRange);
 
+  // Returns the frame index for the given offset.
+  int64_t FrameIndexFromOffset(int64_t aOffset) const;
+
+  // Returns the frame index for the given time.
+  int64_t FrameIndexFromTime(const media::TimeUnit& aTime) const;
+
   // Reads aSize bytes into aBuffer from the source starting at aOffset.
   // Returns the actual size read.
   int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);
 
   // Returns the average frame length derived from the previously parsed frames.
   double AverageFrameLength() const;
 
   // The (hopefully) MPEG resource.
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -54,54 +54,57 @@ enum class ReadMetadataFailureReason : i
 
 // Encapsulates the decoding and reading of media data. Reading can either
 // synchronous and done on the calling "decode" thread, or asynchronous and
 // performed on a background thread, with the result being returned by
 // callback. Never hold the decoder monitor when calling into this class.
 // Unless otherwise specified, methods and fields of this class can only
 // be accessed on the decode task queue.
 class MediaDecoderReader {
+  friend class ReRequestVideoWithSkipTask;
+  friend class ReRequestAudioTask;
+
+  static const bool IsExclusive = true;
+
 public:
   enum NotDecodedReason {
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
-  typedef MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, /* IsExclusive = */ true> MetadataPromise;
-  typedef MozPromise<RefPtr<MediaData>, NotDecodedReason, /* IsExclusive = */ true> AudioDataPromise;
-  typedef MozPromise<RefPtr<MediaData>, NotDecodedReason, /* IsExclusive = */ true> VideoDataPromise;
-  typedef MozPromise<int64_t, nsresult, /* IsExclusive = */ true> SeekPromise;
+  using MetadataPromise =
+    MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>;
+  using AudioDataPromise =
+    MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
+  using VideoDataPromise =
+    MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
+  using SeekPromise = MozPromise<int64_t, nsresult, IsExclusive>;
 
   // Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
   // But in the current architecture it's only ever used exclusively (by MDSM),
   // so we mark it that way to verify our assumptions. If you have a use-case
   // for multiple WaitForData consumers, feel free to flip the exclusivity here.
-  typedef MozPromise<MediaData::Type, WaitForDataRejectValue, /* IsExclusive = */ true> WaitForDataPromise;
+  using WaitForDataPromise =
+    MozPromise<MediaData::Type, WaitForDataRejectValue, IsExclusive>;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   // The caller must ensure that Shutdown() is called before aDecoder is
   // destroyed.
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
 
-  // Does any spinup that needs to happen on this task queue. This runs on a
-  // different thread than Init, and there should not be ordering dependencies
-  // 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; }
 
   // Release media resources they should be released in dormant state
   // The reader can be made usable again by calling ReadMetadata().
-  virtual void ReleaseMediaResources() {};
+  virtual void ReleaseMediaResources() {}
   // Breaks reference-counted cycles. Called during shutdown.
   // WARNING: If you override this, you must call the base implementation
   // in your override.
   virtual void BreakCycles();
 
   // Destroys the decoding state. The reader cannot be made usable again.
   // This is different from ReleaseMediaResources() as it is irreversable,
   // whereas ReleaseMediaResources() is.  Must be called on the decode
@@ -139,103 +142,72 @@ public:
   //
   // Don't hold the decoder monitor while calling this, as the implementation
   // may try to wait on something that needs the monitor and deadlock.
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
   virtual RefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
-  friend class ReRequestVideoWithSkipTask;
-  friend class ReRequestAudioTask;
-
   // By default, the state machine polls the reader once per second when it's
   // 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 RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType)
+  {
+    MOZ_CRASH();
+  }
 
   // By default, the reader return the decoded data. Some readers support
   // retuning demuxed data.
   virtual bool IsDemuxOnlySupported() const { return false; }
 
   // Configure the reader to return demuxed or decoded data
   // upon calls to Request{Audio,Video}Data.
   virtual void SetDemuxOnly(bool /*aDemuxedOnly*/) {}
 
-  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) { };
+  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>
-  Seek(int64_t aTime, int64_t aEndTime) = 0;
+  virtual RefPtr<SeekPromise> Seek(int64_t aTime, int64_t aEndTime) = 0;
 
   // Called to move the reader into idle state. When the reader is
   // created it is assumed to be active (i.e. not idle). When the media
   // element is paused and we don't need to decode any more data, the state
   // machine calls SetIdle() to inform the reader that its decoder won't be
   // needed for a while. The reader can use these notifications to enter
   // a low power state when the decoder isn't needed, if desired.
   // This is most useful on mobile.
   // Note: DecodeVideoFrame, DecodeAudioData, ReadMetadata and Seek should
   // activate the decoder if necessary. The state machine only needs to know
   // when to call SetIdle().
-  virtual void SetIdle() { }
+  virtual void SetIdle() {}
 
 #ifdef MOZ_EME
   virtual void SetCDMProxy(CDMProxy* aProxy) {}
 #endif
 
   // Tell the reader that the data decoded are not for direct playback, so it
   // can accept more files, in particular those which have more channels than
   // available in the audio output.
   void SetIgnoreAudioOutputFormat()
   {
     mIgnoreAudioOutputFormat = true;
   }
 
-  // Populates aBuffered with the time ranges which are buffered. This may only
-  // be called on the decode task queue, and should only be used internally by
-  // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
-  // else.
-  //
-  // This base implementation in MediaDecoderReader estimates the time ranges
-  // buffered by interpolating the cached byte ranges with the duration
-  // of the media. Reader subclasses should override this method if they
-  // can quickly calculate the buffered ranges more accurately.
-  //
-  // The primary advantage of this implementation in the reader base class
-  // is that it's a fast approximation, which does not perform any I/O.
-  //
-  // The OggReader relies on this base implementation not performing I/O,
-  // since in FirefoxOS we can't do I/O on the main thread, where this is
-  // called.
-  virtual media::TimeIntervals GetBuffered();
-
-  // Recomputes mBuffered.
-  virtual void UpdateBuffered();
-
   // MediaSourceReader opts out of the start-time-guessing mechanism.
   virtual bool ForceZeroStartTime() const { return false; }
 
   // The MediaDecoderStateMachine uses various heuristics that assume that
   // raw media data is arriving sequentially from a network channel. This
   // makes sense in the <video src="foo"> case, but not for more advanced use
   // cases like MSE.
   virtual bool UseBufferingHeuristics() { return true; }
@@ -246,63 +218,53 @@ public:
 
   // Returns the number of bytes of memory allocated by structures/frames in
   // the audio queue.
   size_t SizeOfAudioQueueInBytes() const;
 
   virtual size_t SizeOfVideoQueueInFrames();
   virtual size_t SizeOfAudioQueueInFrames();
 
-protected:
-  friend class TrackBuffer;
-  virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) { }
+  // In situations where these notifications come from stochastic network
+  // activity, we can save significant recomputation by throttling the delivery
+  // of these updates to the reader implementation. We don't want to do this
+  // throttling when the update comes from MSE code, since that code needs the
+  // updates to be observable immediately, and is generally less
+  // trigger-happy with notifications anyway.
+  void DispatchNotifyDataArrived(uint32_t aLength,
+                                 int64_t aOffset,
+                                 bool aThrottleUpdates)
+  {
+    typedef media::Interval<int64_t> Interval;
+    RefPtr<nsRunnable> r = NS_NewRunnableMethodWithArg<Interval>(
+      this,
+      aThrottleUpdates ? &MediaDecoderReader::ThrottledNotifyDataArrived :
+                         &MediaDecoderReader::NotifyDataArrived,
+      Interval(aOffset, aOffset + aLength));
+
+    OwnerThread()->Dispatch(
+      r.forget(), AbstractThread::DontAssertDispatchSuccess);
+  }
 
   void NotifyDataArrived(const media::Interval<int64_t>& aInfo)
   {
     MOZ_ASSERT(OnTaskQueue());
     NS_ENSURE_TRUE_VOID(!mShutdown);
     NotifyDataArrivedInternal(aInfo.Length(), aInfo.mStart);
     UpdateBuffered();
   }
 
-  // Invokes NotifyDataArrived while throttling the calls to occur at most every mThrottleDuration ms.
-  void ThrottledNotifyDataArrived(const media::Interval<int64_t>& aInterval);
-  void DoThrottledNotify();
-
-public:
-  // In situations where these notifications come from stochastic network
-  // activity, we can save significant recomputation by throttling the delivery
-  // of these updates to the reader implementation. We don't want to do this
-  // throttling when the update comes from MSE code, since that code needs the
-  // updates to be observable immediately, and is generally less
-  // trigger-happy with notifications anyway.
-  void DispatchNotifyDataArrived(uint32_t aLength, int64_t aOffset, bool aThrottleUpdates)
-  {
-    RefPtr<nsRunnable> r =
-      NS_NewRunnableMethodWithArg<media::Interval<int64_t>>(this, aThrottleUpdates ? &MediaDecoderReader::ThrottledNotifyDataArrived
-                                                                                   : &MediaDecoderReader::NotifyDataArrived,
-                                                            media::Interval<int64_t>(aOffset, aOffset + aLength));
-    OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
-  }
-
-  // Notify the reader that data from the resource was evicted (MediaSource only)
-  virtual void NotifyDataRemoved() {}
-
   virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   virtual MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
-  // Returns a pointer to the decoder.
-  AbstractMediaDecoder* GetDecoder() {
-    return mDecoder;
+  AbstractCanonical<media::TimeIntervals>* CanonicalBuffered()
+  {
+    return &mBuffered;
   }
 
-  RefPtr<VideoDataPromise> DecodeToFirstVideoData();
-
-  MediaInfo GetMediaInfo() { return mInfo; }
-
   // Indicates if the media is seekable.
   // ReadMetada should be called before calling this method.
   virtual bool IsMediaSeekable() = 0;
 
   void DispatchSetStartTime(int64_t aStartTime)
   {
     RefPtr<MediaDecoderReader> self = this;
     nsCOMPtr<nsIRunnable> r =
@@ -311,56 +273,68 @@ public:
       MOZ_ASSERT(self->OnTaskQueue());
       MOZ_ASSERT(!self->HaveStartTime());
       self->mStartTime.emplace(aStartTime);
       self->UpdateBuffered();
     });
     OwnerThread()->Dispatch(r.forget());
   }
 
-  TaskQueue* OwnerThread() const {
+  TaskQueue* OwnerThread() const
+  {
     return mTaskQueue;
   }
 
   // Returns true if the reader implements RequestAudioData()
   // and RequestVideoData() asynchronously, rather than using the
   // implementation in this class to adapt the old synchronous to
   // the newer async model.
   virtual bool IsAsync() const { return false; }
 
   // Returns true if this decoder reader uses hardware accelerated video
   // decoding.
   virtual bool VideoIsHardwareAccelerated() const { return false; }
 
   virtual void DisableHardwareAcceleration() {}
 
-  TimedMetadataEventSource& TimedMetadataEvent() {
+  TimedMetadataEventSource& TimedMetadataEvent()
+  {
     return mTimedMetadataEvent;
   }
 
 protected:
   virtual ~MediaDecoderReader();
 
-  // Overrides of this function should decodes an unspecified amount of
-  // audio data, enqueuing the audio data in mAudioQueue. Returns true
-  // when there's more audio to decode, false if the audio is finished,
-  // end of file has been reached, or an un-recoverable read error has
-  // occured. This function blocks until the decode is complete.
-  virtual bool DecodeAudioData() {
-    return false;
+  // Populates aBuffered with the time ranges which are buffered. This may only
+  // be called on the decode task queue, and should only be used internally by
+  // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
+  // else.
+  //
+  // This base implementation in MediaDecoderReader estimates the time ranges
+  // buffered by interpolating the cached byte ranges with the duration
+  // of the media. Reader subclasses should override this method if they
+  // can quickly calculate the buffered ranges more accurately.
+  //
+  // The primary advantage of this implementation in the reader base class
+  // is that it's a fast approximation, which does not perform any I/O.
+  //
+  // The OggReader relies on this base implementation not performing I/O,
+  // since in FirefoxOS we can't do I/O on the main thread, where this is
+  // called.
+  virtual media::TimeIntervals GetBuffered();
+
+  RefPtr<VideoDataPromise> DecodeToFirstVideoData();
+
+  bool HaveStartTime()
+  {
+    MOZ_ASSERT(OnTaskQueue());
+    return mStartTime.isSome();
   }
 
-  // Overrides of this function should read and decodes one video frame.
-  // Packets with a timestamp less than aTimeThreshold will be decoded
-  // (unless they're not keyframes and aKeyframeSkip is true), but will
-  // not be added to the queue. This function blocks until the decode
-  // is complete.
-  virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) {
-    return false;
-  }
+  int64_t StartTime() { MOZ_ASSERT(HaveStartTime()); return mStartTime.ref(); }
 
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
   MediaQueue<AudioData> mAudioQueue;
 
   // Queue of video frames. This queue is threadsafe, and is accessed from
   // the decoder, state machine, and main threads.
   MediaQueue<VideoData> mVideoQueue;
@@ -380,19 +354,16 @@ protected:
   // State-watching manager.
   WatchManager<MediaDecoderReader> mWatchManager;
 
   // MediaTimer.
   RefPtr<MediaTimer> mTimer;
 
   // Buffered range.
   Canonical<media::TimeIntervals> mBuffered;
-public:
-  AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() { return &mBuffered; }
-protected:
 
   // Stores presentation info required for playback.
   MediaInfo mInfo;
 
   // Duration, mirrored from the state machine task queue.
   Mirror<media::NullableTimeUnit> mDuration;
 
   // State for ThrottledNotifyDataArrived.
@@ -412,30 +383,73 @@ protected:
   // after which point it never changes (though SetStartTime may be called
   // multiple times with the same value).
   //
   // This is an ugly breach of abstractions - it's currently necessary for the
   // readers to return the correct value of GetBuffered. We should refactor
   // things such that all GetBuffered calls go through the MDSM, which would
   // offset the range accordingly.
   Maybe<int64_t> mStartTime;
-  bool HaveStartTime() { MOZ_ASSERT(OnTaskQueue()); return mStartTime.isSome(); }
-  int64_t StartTime() { MOZ_ASSERT(HaveStartTime()); return mStartTime.ref(); }
 
   // This is a quick-and-dirty way for DecodeAudioData implementations to
   // communicate the presence of a decoding error to RequestAudioData. We should
   // replace this with a promise-y mechanism as we make this stuff properly
   // async.
   bool mHitAudioDecodeError;
   bool mShutdown;
 
   // Used to send TimedMetadata to the listener.
   TimedMetadataEventProducer mTimedMetadataEvent;
 
 private:
+  // Does any spinup that needs to happen on this task queue. This runs on a
+  // different thread than Init, and there should not be ordering dependencies
+  // between the two (even though in practice, Init will always run first right
+  // now thanks to the tail dispatcher).
+  void InitializationTask();
+
+  // 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();
+  }
+
+  // Recomputes mBuffered.
+  virtual void UpdateBuffered();
+
+  virtual void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) {}
+
+  // Invokes NotifyDataArrived while throttling the calls to occur
+  // at most every mThrottleDuration ms.
+  void ThrottledNotifyDataArrived(const media::Interval<int64_t>& aInterval);
+  void DoThrottledNotify();
+
+  // Overrides of this function should decodes an unspecified amount of
+  // audio data, enqueuing the audio data in mAudioQueue. Returns true
+  // when there's more audio to decode, false if the audio is finished,
+  // end of file has been reached, or an un-recoverable read error has
+  // occured. This function blocks until the decode is complete.
+  virtual bool DecodeAudioData()
+  {
+    return false;
+  }
+
+  // Overrides of this function should read and decodes one video frame.
+  // Packets with a timestamp less than aTimeThreshold will be decoded
+  // (unless they're not keyframes and aKeyframeSkip is true), but will
+  // not be added to the queue. This function blocks until the decode
+  // is complete.
+  virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
+  {
+    return false;
+  }
+
   // Promises used only for the base-class (sync->async adapter) implementation
   // of Request{Audio,Video}Data.
   MozPromiseHolder<AudioDataPromise> mBaseAudioPromise;
   MozPromiseHolder<VideoDataPromise> mBaseVideoPromise;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1539,21 +1539,18 @@ MediaFormatReader::NotifyDemuxer(uint32_
   MOZ_ASSERT(OnTaskQueue());
 
   LOGV("aLength=%u, aOffset=%lld", aLength, aOffset);
   if (mShutdown || !mDemuxer ||
       (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
     return;
   }
 
-  if (aLength || aOffset) {
-    mDemuxer->NotifyDataArrived();
-  } else {
-    mDemuxer->NotifyDataRemoved();
-  }
+  mDemuxer->NotifyDataArrived();
+
   if (!mInitDone) {
     return;
   }
   if (HasVideo()) {
     mVideo.mReceivedNewData = true;
     ScheduleUpdate(TrackType::kVideoTrack);
   }
   if (HasAudio()) {
@@ -1566,24 +1563,16 @@ void
 MediaFormatReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(aLength);
 
   NotifyDemuxer(aLength, aOffset);
 }
 
-void
-MediaFormatReader::NotifyDataRemoved()
-{
-  MOZ_ASSERT(OnTaskQueue());
-
-  NotifyDemuxer(0, 0);
-}
-
 bool
 MediaFormatReader::ForceZeroStartTime() const
 {
   return !mDemuxer->ShouldComputeStartTime();
 }
 
 layers::ImageContainer*
 MediaFormatReader::GetImageContainer()
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -37,43 +37,32 @@ public:
   size_t SizeOfVideoQueueInFrames() override;
   size_t SizeOfAudioQueueInFrames() override;
 
   RefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) override;
 
   RefPtr<AudioDataPromise> RequestAudioData() override;
 
-  bool HasVideo() override
-  {
-    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;
   }
 
 protected:
   void NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) override;
+
 public:
-  void NotifyDataRemoved() override;
-
   media::TimeIntervals GetBuffered() override;
 
   virtual bool ForceZeroStartTime() const override;
 
   // For Media Resource Management
   void ReleaseMediaResources() override;
 
   nsresult ResetDecode() override;
@@ -108,16 +97,19 @@ public:
     return mTrackDemuxersMayBlock;
   }
 
 #ifdef MOZ_EME
   void SetCDMProxy(CDMProxy* aProxy) override;
 #endif
 
 private:
+  bool HasVideo() { return mVideo.mTrackDemuxer; }
+  bool HasAudio() { return mAudio.mTrackDemuxer; }
+
   bool IsWaitingOnCDMResource();
 
   bool InitDemuxer();
   // Notify the demuxer that new data has been received.
   // The next queued task calling GetBuffered() is guaranteed to have up to date
   // buffered ranges.
   void NotifyDemuxer(uint32_t aLength, int64_t aOffset);
   void ReturnOutput(MediaData* aData, TrackType aTrack);
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -43,26 +43,16 @@ public:
                      const nsACString& aContentType);
 
   virtual nsresult ResetDecode();
 
   virtual bool DecodeAudioData();
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold);
 
-  virtual bool HasAudio()
-  {
-    return mHasAudio;
-  }
-
-  virtual bool HasVideo()
-  {
-    return mHasVideo;
-  }
-
   virtual bool IsMediaSeekable()
   {
     // not used
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
--- a/dom/media/apple/AppleMP3Reader.cpp
+++ b/dom/media/apple/AppleMP3Reader.cpp
@@ -298,31 +298,16 @@ AppleMP3Reader::DecodeAudioData()
 bool
 AppleMP3Reader::DecodeVideoFrame(bool &aKeyframeSkip,
                                  int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   return false;
 }
 
-
-bool
-AppleMP3Reader::HasAudio()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  return mStreamReady;
-}
-
-bool
-AppleMP3Reader::HasVideo()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  return false;
-}
-
 bool
 AppleMP3Reader::IsMediaSeekable()
 {
   // not used
   return true;
 }
 
 /*
--- a/dom/media/apple/AppleMP3Reader.h
+++ b/dom/media/apple/AppleMP3Reader.h
@@ -23,19 +23,16 @@ public:
   virtual nsresult Init() override;
 
   nsresult PushDataToDemuxer();
 
   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,
--- a/dom/media/directshow/DirectShowReader.cpp
+++ b/dom/media/directshow/DirectShowReader.cpp
@@ -336,30 +336,16 @@ DirectShowReader::DecodeAudioData()
 bool
 DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip,
                                    int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   return false;
 }
 
-bool
-DirectShowReader::HasAudio()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  return true;
-}
-
-bool
-DirectShowReader::HasVideo()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  return false;
-}
-
 RefPtr<MediaDecoderReader::SeekPromise>
 DirectShowReader::Seek(int64_t aTargetUs, int64_t aEndTime)
 {
   nsresult res = SeekInternal(aTargetUs);
   if (NS_FAILED(res)) {
     return SeekPromise::CreateAndReject(res, __func__);
   } else {
     return SeekPromise::CreateAndResolve(aTargetUs, __func__);
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -42,19 +42,16 @@ public:
   DirectShowReader(AbstractMediaDecoder* aDecoder);
 
   virtual ~DirectShowReader();
 
   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,
--- a/dom/media/gstreamer/GStreamerReader.h
+++ b/dom/media/gstreamer/GStreamerReader.h
@@ -51,31 +51,23 @@ public:
   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:
-
-  virtual bool HasAudio() override {
-    return mInfo.HasAudio();
-  }
-
-  virtual bool HasVideo() override {
-    return mInfo.HasVideo();
-  }
-
   layers::ImageContainer* GetImageContainer() { return mDecoder->GetImageContainer(); }
 
   virtual bool IsMediaSeekable() override;
 
 private:
-
+  bool HasAudio() { return mInfo.HasAudio(); }
+  bool HasVideo() { return mInfo.HasVideo(); }
   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
   void ImageDataFromVideoFrame(GstVideoFrame *aFrame, layers::PlanarYCbCrImage::Data *aData);
--- a/dom/media/gtest/TestMP3Demuxer.cpp
+++ b/dom/media/gtest/TestMP3Demuxer.cpp
@@ -219,20 +219,20 @@ TEST_F(MP3DemuxerTest, VBRHeader) {
     RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frame);
 
     const auto& vbr = target.mDemuxer->VBRInfo();
 
     if (target.mIsVBR) {
       EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
       // TODO: find reference number which accounts for trailing headers.
-      // EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, vbr.NumFrames());
+      // EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, vbr.NumAudioFrames().value());
     } else {
       EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
-      EXPECT_EQ(-1, vbr.NumFrames());
+      EXPECT_FALSE(vbr.NumAudioFrames());
     }
   }
 }
 
 TEST_F(MP3DemuxerTest, FrameParsing) {
   for (const auto& target: mTargets) {
     RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frameData);
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -55,34 +55,34 @@ public:
   virtual bool DecodeAudioData() override;
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold) override;
 
-  virtual bool HasAudio() override {
-    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:
+  bool HasAudio() {
+    return (mVorbisState != 0 && mVorbisState->mActive) ||
+           (mOpusState != 0 && mOpusState->mActive);
+  }
+
+  bool HasVideo() {
+    return mTheoraState != 0 && mTheoraState->mActive;
+  }
+
   // 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.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -81,19 +81,16 @@ public:
   // 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;
 
@@ -176,16 +173,18 @@ protected:
   android::sp<android::MediaExtractor> mExtractor;
 
   MozPromiseHolder<MediaDecoderReader::MetadataPromise> mMetadataPromise;
   // XXX Remove after bug 1168008 land.
   MozPromiseRequestHolder<MediaResourcePromise> mMediaResourceRequest;
   MozPromiseHolder<MediaResourcePromise> mMediaResourcePromise;
 
 private:
+  virtual bool HasAudio() override;
+  virtual bool HasVideo() override;
 
   // An intermediary class that can be managed by android::sp<T>.
   // Redirect codecReserved() and codecCanceled() to MediaCodecReader.
   class VideoResourceListener : public android::MediaCodecProxy::CodecResourceListener
   {
   public:
     VideoResourceListener(MediaCodecReader* aReader);
     ~VideoResourceListener();
--- a/dom/media/omx/MediaOmxCommonReader.h
+++ b/dom/media/omx/MediaOmxCommonReader.h
@@ -44,13 +44,17 @@ public:
 
 protected:
   dom::AudioChannel mAudioChannel;
   // Weak reference to the MediaStreamSource that will be created by either
   // MediaOmxReader or MediaCodecReader.
   android::MediaStreamSource* mStreamSource;
   // Get value from the preferece, if true, we stop the audio offload.
   bool IsMonoAudioEnabled();
+
+private:
+  virtual bool HasAudio() = 0;
+  virtual bool HasVideo() = 0;
 };
 
 } // namespace mozilla
 
 #endif // MEDIA_OMX_COMMON_READER_H
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -80,26 +80,16 @@ public:
     mSeekPromise.RejectIfExists(NS_OK, __func__);
     return MediaDecoderReader::ResetDecode();
   }
 
   virtual bool DecodeAudioData();
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold);
 
-  virtual bool HasAudio()
-  {
-    return mHasAudio;
-  }
-
-  virtual bool HasVideo()
-  {
-    return mHasVideo;
-  }
-
   virtual void ReleaseMediaResources() override;
 
   virtual RefPtr<MediaDecoderReader::MetadataPromise> AsyncReadMetadata() override;
 
   virtual RefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aEndTime) override;
 
   virtual bool IsMediaSeekable() override;
@@ -113,16 +103,19 @@ public:
   // This method is intended only for private use but public only for
   // MozPromise::InvokeCallbackMethod().
   void ReleaseDecoder();
 
 private:
   class ProcessCachedDataTask;
   class NotifyDataArrivedRunnable;
 
+  virtual bool HasAudio() override { return mHasAudio; }
+  virtual bool HasVideo() override { return mHasVideo; }
+
   bool IsShutdown() {
     MutexAutoLock lock(mShutdownMutex);
     return mIsShutdown;
   }
 
   int64_t ProcessCachedData(int64_t aOffset);
 
   already_AddRefed<AbstractMediaDecoder> SafeGetDecoder();
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -21,17 +21,17 @@
 #include <jni.h>
 
 using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::widget::sdk;
 
 namespace mozilla {
 
-#define ENVOKE_CALLBACK(Func, ...) \
+#define INVOKE_CALLBACK(Func, ...) \
   if (mCallback) { \
     mCallback->Func(__VA_ARGS__); \
   } else { \
     NS_WARNING("callback not set"); \
   }
 
 static const char* TranslateMimeType(const nsACString& aMimeType)
 {
@@ -187,17 +187,17 @@ public:
                                  presentationTimeUs,
                                  aDuration.ToMicroseconds(),
                                  img,
                                  isSync,
                                  presentationTimeUs,
                                  gfx::IntRect(0, 0,
                                               mConfig.mDisplay.width,
                                               mConfig.mDisplay.height));
-    ENVOKE_CALLBACK(Output, v);
+    INVOKE_CALLBACK(Output, v);
     return NS_OK;
   }
 
 protected:
   bool EnsureGLContext() {
     if (mGLContext) {
       return true;
     }
@@ -258,17 +258,17 @@ public:
     NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
 
     RefPtr<AudioData> data = new AudioData(offset, presentationTimeUs,
                                              aDuration.ToMicroseconds(),
                                              numFrames,
                                              audio,
                                              numChannels,
                                              sampleRate);
-    ENVOKE_CALLBACK(Output, data);
+    INVOKE_CALLBACK(Output, data);
     return NS_OK;
   }
 };
 
 
 bool AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType)
 {
   if (!AndroidBridge::Bridge() || (AndroidBridge::Bridge()->GetAPIVersion() < 16)) {
@@ -377,17 +377,17 @@ RefPtr<MediaDataDecoder::InitPromise> Me
            InitPromise::CreateAndResolve(type, __func__) :
            InitPromise::CreateAndReject(MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
 }
 
 nsresult MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
 {
   mDecoder = CreateDecoder(mMimeType);
   if (!mDecoder) {
-    ENVOKE_CALLBACK(Error);
+    INVOKE_CALLBACK(Error);
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
   NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, nullptr, 0), rv);
   NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv);
 
   NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv);
@@ -401,20 +401,20 @@ nsresult MediaCodecDataDecoder::InitDeco
 
 // This is in usec, so that's 10ms
 #define DECODER_TIMEOUT 10000
 
 #define HANDLE_DECODER_ERROR() \
   if (NS_FAILED(res)) { \
     NS_WARNING("exiting decoder loop due to exception"); \
     if (mDraining) { \
-      ENVOKE_CALLBACK(DrainComplete); \
+      INVOKE_CALLBACK(DrainComplete); \
       mDraining = false; \
     } \
-    ENVOKE_CALLBACK(Error); \
+    INVOKE_CALLBACK(Error); \
     break; \
   }
 
 nsresult MediaCodecDataDecoder::GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer)
 {
   bool retried = false;
   while (!*buffer) {
     *buffer = jni::Object::LocalRef::Adopt(env->GetObjectArrayElement(mInputBuffers.Get(), index));
@@ -450,17 +450,17 @@ void MediaCodecDataDecoder::DecoderLoop(
   nsresult res;
 
   for (;;) {
     {
       MonitorAutoLock lock(mMonitor);
       while (!mStopping && !mDraining && !mFlushing && mQueue.empty()) {
         if (mQueue.empty()) {
           // We could be waiting here forever if we don't signal that we need more input
-          ENVOKE_CALLBACK(InputExhausted);
+          INVOKE_CALLBACK(InputExhausted);
         }
         lock.Wait();
       }
 
       if (mStopping) {
         // Get out of the loop. This is the only exit point.
         break;
       }
@@ -468,24 +468,24 @@ void MediaCodecDataDecoder::DecoderLoop(
       if (mFlushing) {
         mDecoder->Flush();
         ClearQueue();
         mFlushing =  false;
         lock.Notify();
         continue;
       }
 
-      if (mDraining && !sample && !waitingEOF) {
-        draining = true;
-      }
-
       // We're not stopping or draining, so try to get a sample
       if (!mQueue.empty()) {
         sample = mQueue.front();
       }
+
+      if (mDraining && !sample && !waitingEOF) {
+        draining = true;
+      }
     }
 
     if (draining && !waitingEOF) {
       MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame");
 
       int32_t inputIndex;
       res = mDecoder->DequeueInputBuffer(DECODER_TIMEOUT, &inputIndex);
       HANDLE_DECODER_ERROR();
@@ -547,17 +547,17 @@ void MediaCodecDataDecoder::DecoderLoop(
       } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
         res = ResetOutputBuffers();
         HANDLE_DECODER_ERROR();
       } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
         res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
         HANDLE_DECODER_ERROR();
       } else if (outputStatus < 0) {
         NS_WARNING("unknown error from decoder!");
-        ENVOKE_CALLBACK(Error);
+        INVOKE_CALLBACK(Error);
 
         // Don't break here just in case it's recoverable. If it's not, others stuff will fail later and
         // we'll bail out.
       } else {
         int32_t flags;
         res = bufferInfo->Flags(&flags);
         HANDLE_DECODER_ERROR();
 
@@ -567,17 +567,17 @@ void MediaCodecDataDecoder::DecoderLoop(
             draining = false;
             waitingEOF = false;
 
             mMonitor.Lock();
             mDraining = false;
             mMonitor.Notify();
             mMonitor.Unlock();
 
-            ENVOKE_CALLBACK(DrainComplete);
+            INVOKE_CALLBACK(DrainComplete);
           }
 
           mDecoder->ReleaseOutputBuffer(outputStatus, false);
           outputDone = true;
 
           // We only queue empty EOF frames, so we're done for now
           continue;
         }
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -21,26 +21,16 @@ protected:
 
 public:
   virtual nsresult ResetDecode() override;
   virtual bool DecodeAudioData() override;
 
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                   int64_t aTimeThreshold) override;
 
-  virtual bool HasAudio() override
-  {
-    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;
--- a/dom/media/wave/WaveReader.h
+++ b/dom/media/wave/WaveReader.h
@@ -21,26 +21,16 @@ public:
 protected:
   ~WaveReader();
 
 public:
   virtual bool DecodeAudioData() override;
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                   int64_t aTimeThreshold) override;
 
-  virtual bool HasAudio() override
-  {
-    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;
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -64,36 +64,32 @@ class WebMReader : public MediaDecoderRe
 {
 public:
   explicit WebMReader(AbstractMediaDecoder* aDecoder);
 
 protected:
   ~WebMReader();
 
 public:
+  // Returns a pointer to the decoder.
+  AbstractMediaDecoder* GetDecoder()
+  {
+    return mDecoder;
+  }
+
+  MediaInfo GetMediaInfo() { return mInfo; }
+
   virtual RefPtr<ShutdownPromise> Shutdown() override;
   virtual nsresult Init() override;
   virtual nsresult ResetDecode() override;
   virtual bool DecodeAudioData() override;
 
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold) override;
 
-  virtual bool HasAudio() override
-  {
-    MOZ_ASSERT(OnTaskQueue());
-    return mHasAudio;
-  }
-
-  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;
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/crashtests/843086.xhtml
@@ -0,0 +1,1 @@
+<applet xmlns="http://www.w3.org/1999/xhtml" />
--- a/dom/plugins/test/crashtests/crashtests.list
+++ b/dom/plugins/test/crashtests/crashtests.list
@@ -8,8 +8,9 @@ load 570884.html
 # the X server, which is a bad assumption for <browser remote>.
 # Plugin arch is going to change anyway with OOP content so skipping
 # this test for now is OK.
 skip-if(browserIsRemote||!haveTestPlugin||http.platform!="X11") load 598862.html
 
 # SkiaGL is causing a compositor hang here, disable temporarily while that gets resolved in bug 908363
 skip-if(Android) load 626602-1.html
 load 752340.html
+load 843086.xhtml
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -541,16 +541,23 @@ PresentationControllingInfo::GetAddress(
       this,
       &PresentationControllingInfo::OnGetAddress,
       EmptyCString()));
 #endif
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
 nsresult
 PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Prepare and send the offer.
   int32_t port;
   nsresult rv = mServerSocket->GetPort(&port);
@@ -602,17 +609,17 @@ NS_IMETHODIMP
 PresentationControllingInfo::NotifyClosed(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
-  if (NS_WARN_IF(NS_FAILED(aReason))) {
+  if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
     // The presentation session instance may already exist.
     // Change the state to TERMINATED since it never succeeds.
     SetState(nsIPresentationSessionListener::STATE_TERMINATED);
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
@@ -847,16 +854,23 @@ PresentationPresentingInfo::OnOffer(nsIP
 NS_IMETHODIMP
 PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
   MOZ_ASSERT(false, "Receiver side should not receive answer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 PresentationPresentingInfo::NotifyOpened()
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::NotifyClosed(nsresult aReason)
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -27,74 +27,82 @@ interface nsIPresentationChannelDescript
   // SDP for Data Channel.
   // Should only be used while type == TYPE_DATACHANNEL.
   readonly attribute DOMString dataChannelSDP;
 };
 
 /*
  * The callbacks for events on control channel.
  */
-[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)]
+[scriptable, uuid(96dd548f-7d0f-43c1-b1ad-28e666cf1e82)]
 interface nsIPresentationControlChannelListener: nsISupports
 {
   /*
    * Callback for receiving offer from remote endpoint.
    * @param offer The received offer.
    */
   void onOffer(in nsIPresentationChannelDescription offer);
 
   /*
    * Callback for receiving answer from remote endpoint.
    * @param answer The received answer.
    */
   void onAnswer(in nsIPresentationChannelDescription answer);
 
   /*
+   * Callback for receiving ICE candidate from remote endpoint.
+   * @param answer The received answer.
+   */
+  void onIceCandidate(in DOMString candidate);
+
+  /*
    * The callback for notifying channel opened.
    */
   void notifyOpened();
 
   /*
    * The callback for notifying channel closed.
    * @param reason The reason of channel close, NS_OK represents normal close.
    */
   void notifyClosed(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface.
  */
-[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
+[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
 interface nsIPresentationControlChannel: nsISupports
 {
   // The listener for handling events of this control channel.
   // All the events should be pending until listener is assigned.
   attribute nsIPresentationControlChannelListener listener;
 
   /*
-   * Send offer to remote endpiont. |onOffer| should be invoked
-   * on remote endpoint.
+   * Send offer to remote endpoint. |onOffer| should be invoked on remote
+   * endpoint.
    * @param offer The offer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendOffer(in nsIPresentationChannelDescription offer);
 
   /*
-   * Send answer to remote endpiont. |onAnswer| should
-   * be invoked on remote endpoint.
+   * Send answer to remote endpoint. |onAnswer| should be invoked on remote
+   * endpoint.
    * @param answer The answer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendAnswer(in nsIPresentationChannelDescription answer);
 
   /*
-   * Notify the app-to-app connection is fully established. (Only used at the
-   * receiver side.)
+   * Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked
+   * on remote endpoint.
+   * @param candidate The candidate to send
+   * @throws NS_ERROR_FAILURE on failure
    */
-  void sendReceiverReady();
+  void sendIceCandidate(in DOMString candidate);
 
   /*
    * Close the transport channel.
    * @param reason The reason of channel close; NS_OK represents normal.
    */
   void close(in nsresult reason);
 };
--- a/dom/presentation/provider/TCPPresentationServer.js
+++ b/dom/presentation/provider/TCPPresentationServer.js
@@ -369,16 +369,24 @@ TCPControlChannel.prototype = {
     let msg = {
       type: "requestSession:Answer",
       presentationId: this.presentationId,
       answer: discriptionAsJson(aAnswer),
     };
     this._sendMessage("answer", msg);
   },
 
+  sendIceCandidate: function(aCandidate) {
+    let msg = {
+      type: "requestSession:IceCandidate",
+      presentationId: this.presentationId,
+      iceCandidate: aCandidate,
+    };
+    this._sendMessage("iceCandidate", msg);
+  },
   // may throw an exception
   _send: function(aMsg) {
     DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2));
 
     /**
      * XXX In TCP streaming, it is possible that more than one message in one
      * TCP packet. We use line delimited JSON to identify where one JSON encoded
      * object ends and the next begins. Therefore, we do not allow newline
@@ -433,17 +441,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - onStartRequest with role: "
                  + this._direction);
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStopRequest: function(aRequest, aContext, aStatus) {
     DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
                  + " with role: " + this._direction);
-    this.close();
+    this.close(Cr.NS_OK);
     this._notifyClosed(aStatus);
   },
 
   // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     let data = NetUtil.readInputStreamToString(aInputStream,
                                                aInputStream.available());
     DEBUG && log("TCPControlChannel - onDataAvailable: " + data);
@@ -491,16 +499,24 @@ TCPControlChannel.prototype = {
       case "requestSession:Offer": {
         this._onOffer(aMsg.offer);
         break;
       }
       case "requestSession:Answer": {
         this._onAnswer(aMsg.answer);
         break;
       }
+      case "requestSession:IceCandidate": {
+        this._listener.onIceCandidate(aMsg.iceCandidate);
+        break;
+      }
+      case "requestSession:CloseReason": {
+        this._pendingCloseReason = aMsg.reason;
+        break;
+      }
     }
   },
 
   get listener() {
     return this._listener;
   },
 
   set listener(aListener) {
@@ -568,17 +584,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - notify answer: "
                  + JSON.stringify(aAnswer));
     this._listener.onAnswer(new ChannelDescription(aAnswer));
   },
 
   _notifyOpened: function() {
     this._connected = true;
     this._pendingClose = false;
-    this._pendingCloseReason = null;
+    this._pendingCloseReason = Cr.NS_OK;
 
     if (!this._listener) {
       this._pendingOpen = true;
       return;
     }
 
     DEBUG && log("TCPControlChannel - notify opened with role: "
                  + this._direction);
@@ -586,30 +602,47 @@ TCPControlChannel.prototype = {
   },
 
   _notifyClosed: function(aReason) {
     this._connected = false;
     this._pendingOpen = false;
     this._pendingOffer = null;
     this._pendingAnswer = null;
 
+    // Remote endpoint closes the control channel with abnormal reason.
+    if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
+      aReason = this._pendingCloseReason;
+    }
+
     if (!this._listener) {
-     this._pendingClose = true;
-     this._pendingCloseReason = aReason;
-     return;
+      this._pendingClose = true;
+      this._pendingCloseReason = aReason;
+      return;
     }
 
     DEBUG && log("TCPControlChannel - notify closed with role: "
                  + this._direction);
     this._listener.notifyClosed(aReason);
   },
 
-  close: function() {
-    DEBUG && log("TCPControlChannel - close");
+  close: function(aReason) {
+    DEBUG && log("TCPControlChannel - close with reason: " + aReason);
+
     if (this._connected) {
+      // default reason is NS_OK
+      if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
+        let msg = {
+          type: "requestSession:CloseReason",
+          presentationId: this.presentationId,
+          reason: aReason,
+        };
+        this._sendMessage("close", msg);
+        this._pendingCloseReason = aReason;
+      }
+
       this._transport.setEventSink(null, null);
       this._pump = null;
 
       this._input.close();
       this._output.close();
       this._presentationServer.releaseControlChannel(this);
 
       this._connected = false;
--- a/dom/presentation/tests/mochitest/test_presentation_sender_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_establish_connection_error.html
@@ -110,16 +110,59 @@ function testStartConnectionUnexpectedCo
       function(aError) {
         is(aError.name, "OperationError", "OperationError is expected when a connection error happens during establishing a connection.");
         aResolve();
       }
     );
   });
 }
 
+function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+      gScript.sendAsyncMessage('trigger-control-channel-open');
+    });
+
+    gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+      info("The control channel is opened.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      ok(aIsValid, "A valid offer is sent out.");
+      gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
+    });
+
+    request.start().then(
+      function(aConnection) {
+        ok(false, "|start| shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
+        aResolve();
+      }
+    );
+  });
+}
+
 function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
       gScript.sendAsyncMessage('trigger-device-prompt-select');
     });
 
@@ -164,16 +207,70 @@ function testStartConnectionUnexpectedCo
       function(aError) {
         is(aError.name, "OperationError", "OperationError is expected when a connection error happens during establishing a connection.");
         aResolve();
       }
     );
   });
 }
 
+function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+      gScript.sendAsyncMessage('trigger-control-channel-open');
+    });
+
+    gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+      info("The control channel is opened.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      ok(aIsValid, "A valid offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+      info("Data transport channel is initialized.");
+      gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    request.start().then(
+      function(aConnection) {
+        ok(false, "|start| shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
+        aResolve();
+      }
+    );
+  });
+}
+
 function testStartConnectionUnexpectedDataTransportClose() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
       gScript.sendAsyncMessage('trigger-device-prompt-select');
     });
 
@@ -235,17 +332,19 @@ function teardown() {
 
 function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
 
   testCreateRequestWithEmptyURL().
   then(setup).
   then(testStartConnectionCancelPrompt).
   then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
+  then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit).
   then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady).
+  then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady).
   then(testStartConnectionUnexpectedDataTransportClose).
   then(teardown);
 }
 
 SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -39,16 +39,19 @@ function TestDescription(aType, aTcpAddr
 
 TestDescription.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
 }
 
 const CONTROLLER_CONTROL_CHANNEL_PORT = 36777;
 const PRESENTER_CONTROL_CHANNEL_PORT = 36888;
 
+var CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_OK;
+var candidate;
+
 // presenter's presentation channel description
 const OFFER_ADDRESS = '192.168.123.123';
 const OFFER_PORT = 123;
 
 // controller's presentation channel description
 const ANSWER_ADDRESS = '192.168.321.321';
 const ANSWER_PORT = 321;
 
@@ -93,23 +96,33 @@ function testPresentationServer() {
             controllerControlChannel.sendAnswer(answer);
           } catch (e) {
             Assert.ok(false, 'sending answer fails' + e);
           }
         },
         onAnswer: function(aAnswer) {
           Assert.ok(false, 'get answer');
         },
+        onIceCandidate: function(aCandidate) {
+          Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel');
+          let recvCandidate = JSON.parse(aCandidate);
+          for (let key in recvCandidate) {
+            if (typeof(recvCandidate[key]) !== "function") {
+              Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match.");
+            }
+          }
+          controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON);
+        },
         notifyOpened: function() {
           Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
           this.status = 'opened';
         },
         notifyClosed: function(aReason) {
-          Assert.equal(this.status, 'onOffer', '3. controllerControlChannel: closed');
-          Assert.equal(aReason, Cr.NS_OK, 'presenterControlChannel notify closed NS_OK');
+          Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
+          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
           this.status = 'closed';
           yayFuncs.controllerControlChannelClose();
         },
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
       };
     },
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
@@ -127,40 +140,47 @@ function testPresentationServer() {
                                                    'testPresentationId');
 
   presenterControlChannel.listener = {
     status: 'created',
     onOffer: function(offer) {
       Assert.ok(false, 'get offer');
     },
     onAnswer: function(aAnswer) {
-      Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, close channel');
+      Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate');
 
       let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription);
       Assert.strictEqual(answer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data,
                          ANSWER_ADDRESS,
                          'expected answer address array');
       Assert.equal(answer.tcpPort, ANSWER_PORT, 'expected answer port');
-
-      presenterControlChannel.close(Cr.NS_OK);
+      candidate = {
+        candidate: "1 1 UDP 1 127.0.0.1 34567 type host",
+        sdpMid: "helloworld",
+        sdpMLineIndex: 1
+      };
+      presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
+    },
+    onIceCandidate: function(aCandidate) {
+      Assert.ok(false, 'get ICE candidate');
     },
     notifyOpened: function() {
       Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
       this.status = 'opened';
       try {
         let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
         let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT)
         presenterControlChannel.sendOffer(offer);
       } catch (e) {
         Assert.ok(false, 'sending offer fails:' + e);
       }
     },
     notifyClosed: function(aReason) {
       this.status = 'closed';
-      Assert.equal(aReason, Cr.NS_OK, '3. presenterControlChannel notify closed NS_OK');
+      Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
       yayFuncs.presenterControlChannelClose();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
   let expectedReason;
@@ -193,18 +213,25 @@ function shutdown()
     onClose: function(aReason) {
       Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success');
       run_next_test();
     },
   }
   tps.close();
 }
 
+// Test manually close control channel with NS_ERROR_FAILURE
+function changeCloseReason() {
+  CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE;
+  run_next_test();
+}
+
 add_test(loopOfferAnser);
 add_test(setOffline);
+add_test(changeCloseReason);
 add_test(oneMoreLoop);
 add_test(shutdown);
 
 function run_test() {
   Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true);
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("dom.presentation.tcp_server.debug");
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -633,17 +633,17 @@ this.PushServiceWebSocket = {
 
     debug("serverURL: " + uri.spec);
     this._wsListener = new PushWebSocketListener(this);
     this._ws.protocol = "push-notification";
 
     try {
       // Grab a wakelock before we open the socket to ensure we don't go to
       // sleep before connection the is opened.
-      this._ws.asyncOpen(uri, uri.spec, this._wsListener, null);
+      this._ws.asyncOpen(uri, uri.spec, 0, this._wsListener, null);
       this._acquireWakeLock();
       this._currentState = STATE_WAITING_FOR_WS_START;
     } catch(e) {
       debug("Error opening websocket. asyncOpen failed!");
       this._reconnect();
     }
   },
 
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -274,17 +274,17 @@ MockWebSocket.prototype = {
     Ci.nsISupports,
     Ci.nsIWebSocketChannel
   ]),
 
   get originalURI() {
     return this._originalURI;
   },
 
-  asyncOpen(uri, origin, listener, context) {
+  asyncOpen(uri, origin, windowId, listener, context) {
     this._listener = listener;
     this._context = context;
     waterfall(() => this._listener.onStart(this._context));
   },
 
   _handleMessage(msg) {
     let messageType, request;
     if (msg == '{}') {
--- a/dom/simplepush/PushService.jsm
+++ b/dom/simplepush/PushService.jsm
@@ -842,17 +842,17 @@ this.PushService = {
 
     debug("serverURL: " + uri.spec);
     this._wsListener = new PushWebSocketListener(this);
     this._ws.protocol = "push-notification";
 
     try {
       // Grab a wakelock before we open the socket to ensure we don't go to sleep
       // before connection the is opened.
-      this._ws.asyncOpen(uri, serverURL, this._wsListener, null);
+      this._ws.asyncOpen(uri, serverURL, 0, this._wsListener, null);
       this._acquireWakeLock();
       this._currentState = STATE_WAITING_FOR_WS_START;
     } catch(e) {
       debug("Error opening websocket. asyncOpen failed!");
       this._shutdownWS();
       this._reconnectAfterBackoff();
     }
   },
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BrowserElementProxy.webidl
@@ -0,0 +1,15 @@
+/* -*- 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/.
+ */
+
+[Constructor,
+ JSImplementation="@mozilla.org/dom/browser-element-proxy;1",
+ NavigatorProperty="mozBrowserElementProxy",
+ Pref="dom.mozBrowserFramesEnabled",
+ CheckAnyPermissions="browser:embedded-system-app"]
+interface BrowserElementProxy : EventTarget {
+};
+BrowserElementProxy implements BrowserElementCommon;
+BrowserElementProxy implements BrowserElementPrivileged;
--- a/dom/webidl/IDBIndex.webidl
+++ b/dom/webidl/IDBIndex.webidl
@@ -51,21 +51,19 @@ interface IDBIndex {
     IDBRequest getKey (any key);
 
     [Throws]
     IDBRequest count (optional any key);
 };
 
 partial interface IDBIndex {
     [Throws]
-    IDBRequest mozGetAll (optional any key, optional unsigned long limit);
+    IDBRequest mozGetAll (optional any key, [EnforceRange] optional unsigned long limit);
+
+    [Throws]
+    IDBRequest mozGetAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
 
     [Throws]
-    IDBRequest mozGetAllKeys (optional any key, optional unsigned long limit);
+    IDBRequest getAll (optional any key, [EnforceRange] optional unsigned long limit);
 
-    [Throws,
-     Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
-    IDBRequest getAll (optional any key, optional unsigned long limit);
-
-    [Throws,
-     Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
-    IDBRequest getAllKeys (optional any key, optional unsigned long limit);
+    [Throws]
+    IDBRequest getAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
 };
--- a/dom/webidl/IDBObjectStore.webidl
+++ b/dom/webidl/IDBObjectStore.webidl
@@ -58,22 +58,19 @@ interface IDBObjectStore {
 
     [Throws]
     IDBRequest count (optional any key);
 };
 
 partial interface IDBObjectStore {
     // Success fires IDBTransactionEvent, result == array of values for given keys
     [Throws]
-    IDBRequest mozGetAll (optional any key, optional unsigned long limit);
+    IDBRequest mozGetAll (optional any key, [EnforceRange] optional unsigned long limit);
 
-    [Throws,
-     Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
-    IDBRequest getAll (optional any key, optional unsigned long limit);
+    [Throws]
+    IDBRequest getAll (optional any key, [EnforceRange] optional unsigned long limit);
 
-    [Throws,
-     Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
-    IDBRequest getAllKeys (optional any key, optional unsigned long limit);
+    [Throws]
+    IDBRequest getAllKeys (optional any key, [EnforceRange] optional unsigned long limit);
 
-    [Throws,
-     Func="mozilla::dom::indexedDB::IndexedDatabaseManager::ExperimentalFeaturesEnabled"]
+    [Throws]
     IDBRequest openKeyCursor (optional any range, optional IDBCursorDirection direction = "next");
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -54,16 +54,17 @@ WEBIDL_FILES = [
     'BeforeUnloadEvent.webidl',
     'BiquadFilterNode.webidl',
     'Blob.webidl',
     'BoxObject.webidl',
     'BroadcastChannel.webidl',
     'BrowserElement.webidl',
     'BrowserElementAudioChannel.webidl',
     'BrowserElementDictionaries.webidl',
+    'BrowserElementProxy.webidl',
     'Cache.webidl',
     'CacheStorage.webidl',
     'CallsList.webidl',
     'CameraCapabilities.webidl',
     'CameraControl.webidl',
     'CameraManager.webidl',
     'CameraUtil.webidl',
     'CanvasCaptureMediaStream.webidl',
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -971,17 +971,23 @@ private:
 
     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
     if (!principal) {
       WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
       MOZ_ASSERT(parentWorker, "Must have a parent!");
       principal = parentWorker->GetPrincipal();
     }
 
-    aLoadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(channelPrincipal));
+    // We don't mute the main worker script becase we've already done
+    // same-origin checks on them so we should be able to see their errors.
+    // Note that for data: url, where we allow it through the same-origin check
+    // but then give it a different origin.
+    aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript()
+                                        ? false 
+                                        : !principal->Subsumes(channelPrincipal));
 
     // Make sure we're not seeing the result of a 404 or something by checking
     // the 'requestSucceeded' attribute on the http channel.
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
     if (httpChannel) {
       bool requestSucceeded;
       rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
       NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -96,21 +96,11 @@ ServiceWorker::PostMessage(JSContext* aC
     return;
   }
 
   UniquePtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(window->GetExtantDoc()));
   ServiceWorkerPrivate* workerPrivate = mInfo->WorkerPrivate();
   aRv = workerPrivate->SendMessageEvent(aCx, aMessage, aTransferable, Move(clientInfo));
 }
 
-void
-ServiceWorker::QueueStateChangeEvent(ServiceWorkerState aState)
-{
-  nsCOMPtr<nsIRunnable> r =
-    NS_NewRunnableMethodWithArg<ServiceWorkerState>(this,
-                                                    &ServiceWorker::DispatchStateChange,
-                                                    aState);
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
-}
-
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/ServiceWorker.h
+++ b/dom/workers/ServiceWorker.h
@@ -50,23 +50,19 @@ public:
   }
 
   void
   GetScriptURL(nsString& aURL) const;
 
   void
   DispatchStateChange(ServiceWorkerState aState)
   {
-    SetState(aState);
     DOMEventTargetHelper::DispatchTrustedEvent(NS_LITERAL_STRING("statechange"));
   }
 
-  void
-  QueueStateChangeEvent(ServiceWorkerState aState);
-
 #ifdef XP_WIN
 #undef PostMessage
 #endif
 
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
               const Optional<Sequence<JS::Value>>& aTransferable,
               ErrorResult& aRv);
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -437,19 +437,25 @@ RespondWithHandler::CancelRequest(nsresu
 void
 FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
 {
   if (EventPhase() == nsIDOMEvent::NONE || mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  if (!mPromise) {
-    mPromise = &aArg;
+  // 4.5.3.2 If the respond-with entered flag is set, then:
+  // Throw an "InvalidStateError" exception.
+  // Here we use |mPromise != nullptr| as respond-with enter flag
+  if (mPromise) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
   }
+  mPromise = &aArg;
+
   RefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
   StopImmediatePropagation();
   mWaitToRespond = true;
   RefPtr<RespondWithHandler> handler =
     new RespondWithHandler(mChannel, mRequest->Mode(), ir->IsClientRequest(),
                            ir->IsNavigationRequest(), mScriptSpec);
   aArg.AppendNativeHandler(handler);
 }
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -367,16 +367,17 @@ ServiceWorkerRegistrationInfo::Clear()
                                                  WhichServiceWorker::ACTIVE_WORKER);
 }
 
 ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                                              nsIPrincipal* aPrincipal)
   : mControlledDocumentsCounter(0)
   , mScope(aScope)
   , mPrincipal(aPrincipal)
+  , mLastUpdateCheckTime(0)
   , mPendingUninstall(false)
 { }
 
 ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
 {
   if (IsControllingDocuments()) {
     NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
   }
@@ -1177,17 +1178,17 @@ private:
     // 9.2.20 If newestWorker is not null, and newestWorker's script url is
     // equal to registration's registering script url and response is a
     // byte-for-byte match with the script resource of newestWorker...
     if (workerInfo && workerInfo->ScriptSpec().Equals(mRegistration->mScriptSpec)) {
       cacheName = workerInfo->CacheName();
     }
 
     nsresult rv =
-      serviceWorkerScriptCache::Compare(mRegistration->mPrincipal, cacheName,
+      serviceWorkerScriptCache::Compare(mRegistration, mRegistration->mPrincipal, cacheName,
                                         NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec),
                                         this, mLoadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return Fail(rv);
     }
   }
 
   void
@@ -1900,25 +1901,28 @@ ServiceWorkerManager::SendPushEvent(cons
     return NS_ERROR_INVALID_ARG;
   }
 
   ServiceWorkerInfo* serviceWorker = GetActiveWorkerInfoForScope(attrs, aScope);
   if (NS_WARN_IF(!serviceWorker)) {
     return NS_ERROR_FAILURE;
   }
 
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    GetRegistration(serviceWorker->GetPrincipal(), aScope);
+
   if (optional_argc == 2) {
     nsTArray<uint8_t> data;
     if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data));
+    return serviceWorker->WorkerPrivate()->SendPushEvent(Some(data), registration);
   } else {
     MOZ_ASSERT(optional_argc == 0);
-    return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing());
+    return serviceWorker->WorkerPrivate()->SendPushEvent(Nothing(), registration);
   }
 #endif // MOZ_SIMPLEPUSH
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
                                                       const nsACString& aScope)
 {
@@ -2378,16 +2382,43 @@ ServiceWorkerRegistrationInfo::FinishAct
 
   // Activation never fails, so aSuccess is ignored.
   mActiveWorker->UpdateState(ServiceWorkerState::Activated);
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   swm->StoreRegistration(mPrincipal, this);
 }
 
 void
+ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
+}
+
+bool
+ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // For testing.
+  if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
+    return true;
+  }
+
+  const uint64_t kSecondsPerDay = 86400;
+  const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
+
+  if ((mLastUpdateCheckTime != 0) &&
+      (now - mLastUpdateCheckTime > kSecondsPerDay)) {
+    return true;
+  }
+  return false;
+}
+
+void
 ServiceWorkerManager::LoadRegistration(
                              const ServiceWorkerRegistrationData& aRegistration)
 {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(aRegistration.principal());
   if (!principal) {
@@ -4176,35 +4207,70 @@ ServiceWorkerInfo::RemoveWorker(ServiceW
   aWorker->GetScriptURL(workerURL);
   MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
 #endif
   MOZ_ASSERT(mInstances.Contains(aWorker));
 
   mInstances.RemoveElement(aWorker);
 }
 
+namespace {
+
+class ChangeStateUpdater final : public nsRunnable
+{
+public:
+  ChangeStateUpdater(const nsTArray<ServiceWorker*>& aInstances,
+                     ServiceWorkerState aState)
+    : mState(aState)
+  {
+    for (size_t i = 0; i < aInstances.Length(); ++i) {
+      mInstances.AppendElement(aInstances[i]);
+    }
+  }
+
+  NS_IMETHODIMP Run()
+  {
+    // We need to update the state of all instances atomically before notifying
+    // them to make sure that the observed state for all instances inside
+    // statechange event handlers is correct.
+    for (size_t i = 0; i < mInstances.Length(); ++i) {
+      mInstances[i]->SetState(mState);
+    }
+    for (size_t i = 0; i < mInstances.Length(); ++i) {
+      mInstances[i]->DispatchStateChange(mState);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  nsAutoTArray<RefPtr<ServiceWorker>, 1> mInstances;
+  ServiceWorkerState mState;
+};
+
+}
+
 void
 ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
 {
 #ifdef DEBUG
   // Any state can directly transition to redundant, but everything else is
   // ordered.
   if (aState != ServiceWorkerState::Redundant) {
     MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
   }
   // Activated can only go to redundant.
   MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
 #endif
   mState = aState;
-  for (uint32_t i = 0; i < mInstances.Length(); ++i) {
-    mInstances[i]->QueueStateChangeEvent(mState);
-  }
+  nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r.forget())));
 }
 
 ServiceWorkerInfo::ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
                                      const nsACString& aScriptSpec,
                                      const nsAString& aCacheName)
   : mRegistration(aReg)
   , mScriptSpec(aScriptSpec)
   , mCacheName(aCacheName)
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -68,16 +68,18 @@ public:
   nsCString mScriptSpec;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   RefPtr<ServiceWorkerInfo> mActiveWorker;
   RefPtr<ServiceWorkerInfo> mWaitingWorker;
   RefPtr<ServiceWorkerInfo> mInstallingWorker;
 
+  uint64_t mLastUpdateCheckTime;
+
   // When unregister() is called on a registration, it is not immediately
   // removed since documents may be controlled. It is marked as
   // pendingUninstall and when all controlling documents go away, removed.
   bool mPendingUninstall;
 
   ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal);
 
@@ -127,16 +129,21 @@ public:
   TryToActivate();
 
   void
   Activate();
 
   void
   FinishActivate(bool aSuccess);
 
+  void
+  RefreshLastUpdateCheckTime();
+
+  bool
+  IsLastUpdateCheckTimeOverOneDay() const;
 };
 
 class ServiceWorkerUpdateFinishCallback
 {
 protected:
   virtual ~ServiceWorkerUpdateFinishCallback()
   { }
 
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1,17 +1,19 @@
 /* -*- 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 "ServiceWorkerPrivate.h"
-
 #include "ServiceWorkerManager.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "mozilla/dom/FetchUtil.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerPrivate)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerPrivate)
@@ -186,16 +188,61 @@ public:
     MOZ_ASSERT(workerPrivate);
     workerPrivate->AssertIsOnWorkerThread();
 #endif
   }
 };
 
 NS_IMPL_ISUPPORTS0(KeepAliveHandler)
 
+class SoftUpdateRequest : public nsRunnable
+{
+protected:
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+public:
+  explicit SoftUpdateRequest(nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(aRegistration);
+  }
+
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    OriginAttributes attrs =
+      mozilla::BasePrincipal::Cast(mRegistration->mPrincipal)->OriginAttributesRef();
+
+    swm->PropagateSoftUpdate(attrs,
+                             NS_ConvertUTF8toUTF16(mRegistration->mScope));
+    return NS_OK;
+  }
+};
+
+class CheckLastUpdateTimeRequest final : public SoftUpdateRequest
+{
+public:
+  explicit CheckLastUpdateTimeRequest(
+    nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : SoftUpdateRequest(aRegistration)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    AssertIsOnMainThread();
+    if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
+      SoftUpdateRequest::Run();
+    }
+    return NS_OK;
+  }
+};
+
 class ExtendableEventWorkerRunnable : public WorkerRunnable
 {
 protected:
   nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
 
 public:
   ExtendableEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                 KeepAliveToken* aKeepAliveToken)
@@ -243,16 +290,43 @@ public:
     waitUntilPromise->AppendNativeHandler(keepAliveHandler);
 
     if (aWaitUntilPromise) {
       waitUntilPromise.forget(aWaitUntilPromise);
     }
   }
 };
 
+// Handle functional event
+// 9.9.7 If the time difference in seconds calculated by the current time minus
+// registration's last update check time is greater than 86400, invoke Soft Update
+// algorithm.
+class ExtendableFunctionalEventWorkerRunnable : public ExtendableEventWorkerRunnable
+{
+protected:
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
+public:
+  ExtendableFunctionalEventWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+                                          KeepAliveToken* aKeepAliveToken,
+                                          nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
+    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+    , mRegistration(aRegistration)
+  {
+    MOZ_ASSERT(aRegistration);
+  }
+
+  void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
+  {
+    nsCOMPtr<nsIRunnable> runnable = new CheckLastUpdateTimeRequest(mRegistration);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
+  }
+};
+
 /*
  * Fires 'install' event on the ServiceWorkerGlobalScope. Modifies busy count
  * since it fires the event. This is ok since there can't be nested
  * ServiceWorkers, so the parent thread -> worker thread requirement for
  * runnables is satisfied.
  */
 class LifecycleEventWorkerRunnable : public ExtendableEventWorkerRunnable
 {
@@ -338,17 +412,17 @@ public:
     js::ErrorReport report(aCx);
     if (NS_WARN_IF(!report.init(aCx, aValue))) {
       JS_ClearPendingException(aCx);
       return;
     }
 
     RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
     xpcReport->Init(report.report(), report.message(),
-                    /* aIsChrome = */ false, /* aWindowID = */ 0);
+                    /* aIsChrome = */ false, workerPrivate->WindowID());
 
     RefPtr<AsyncErrorReporter> aer = new AsyncErrorReporter(xpcReport);
     NS_DispatchToMainThread(aer);
   }
 };
 
 NS_IMPL_ISUPPORTS0(LifecycleEventPromiseHandler)
 
@@ -410,25 +484,27 @@ ServiceWorkerPrivate::SendLifeCycleEvent
   }
 
   return NS_OK;
 }
 
 #ifndef MOZ_SIMPLEPUSH
 namespace {
 
-class SendPushEventRunnable final : public ExtendableEventWorkerRunnable
+class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
 {
   Maybe<nsTArray<uint8_t>> mData;
 
 public:
   SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                         KeepAliveToken* aKeepAliveToken,
-                        const Maybe<nsTArray<uint8_t>>& aData)
-      : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+                        const Maybe<nsTArray<uint8_t>>& aData,
+                        nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
+      : ExtendableFunctionalEventWorkerRunnable(
+          aWorkerPrivate, aKeepAliveToken, aRegistration)
       , mData(aData)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
   }
 
   bool
@@ -499,28 +575,34 @@ public:
     return true;
   }
 };
 
 } // anonymous namespace
 #endif // !MOZ_SIMPLEPUSH
 
 nsresult
-ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData)
+ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+                                    ServiceWorkerRegistrationInfo* aRegistration)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(mKeepAliveToken);
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
+
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
                                                          mKeepAliveToken,
-                                                         aData);
+                                                         aData,
+                                                         regInfo);
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif // MOZ_SIMPLEPUSH
@@ -822,17 +904,17 @@ ServiceWorkerPrivate::SendNotificationCl
 
   return NS_OK;
 }
 
 namespace {
 
 // Inheriting ExtendableEventWorkerRunnable so that the worker is not terminated
 // while handling the fetch event, though that's very unlikely.
-class FetchEventRunnable : public ExtendableEventWorkerRunnable
+class FetchEventRunnable : public ExtendableFunctionalEventWorkerRunnable
                          , public nsIHttpHeaderVisitor {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   const nsCString mScriptSpec;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
   UniquePtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
@@ -846,19 +928,21 @@ class FetchEventRunnable : public Extend
   nsCString mReferrer;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      KeepAliveToken* aKeepAliveToken,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      // CSP checks might require the worker script spec
                      // later on.
                      const nsACString& aScriptSpec,
+                     nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration,
                      UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                      bool aIsReload)
-    : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
+    : ExtendableFunctionalEventWorkerRunnable(
+        aWorkerPrivate, aKeepAliveToken, aRegistration)
     , mInterceptedChannel(aChannel)
     , mScriptSpec(aScriptSpec)
     , mClientInfo(Move(aClientInfo))
     , mIsReload(aIsReload)
     , mIsHttpChannel(false)
     , mRequestMode(RequestMode::No_cors)
     , mRequestRedirect(RequestRedirect::Follow)
     // By default we set it to same-origin since normal HTTP fetches always
@@ -942,18 +1026,27 @@ public:
       }
 
       rv = httpChannel->VisitNonDefaultRequestHeaders(this);
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
       if (uploadChannel) {
         MOZ_ASSERT(!mUploadStream);
-        rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
+        bool bodyHasHeaders = false;
+        rv = uploadChannel->GetUploadStreamHasHeaders(&bodyHasHeaders);
+        NS_ENSURE_SUCCESS(rv, rv);
+        nsCOMPtr<nsIInputStream> uploadStream;
+        rv = uploadChannel->CloneUploadStream(getter_AddRefs(uploadStream));
         NS_ENSURE_SUCCESS(rv, rv);
+        if (bodyHasHeaders) {
+          HandleBodyWithHeaders(uploadStream);
+        } else {
+          mUploadStream = uploadStream;
+        }
       }
     } else {
       nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
       // If it is not an HTTP channel it must be a JAR one.
       NS_ENSURE_TRUE(jarChannel, NS_ERROR_NOT_AVAILABLE);
 
       mMethod = "GET";
 
@@ -1085,18 +1178,71 @@ private:
     }
 
     RefPtr<Promise> respondWithPromise = event->GetPromise();
     if (respondWithPromise) {
       RefPtr<KeepAliveHandler> keepAliveHandler =
         new KeepAliveHandler(mKeepAliveToken);
       respondWithPromise->AppendNativeHandler(keepAliveHandler);
     }
+
+    // 9.8.22 If request is a non-subresource request, then: Invoke Soft Update algorithm
+    if (internalReq->IsNavigationRequest()) {
+      nsCOMPtr<nsIRunnable> runnable= new SoftUpdateRequest(mRegistration);
+
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
+    }
     return true;
   }
+
+  nsresult
+  HandleBodyWithHeaders(nsIInputStream* aUploadStream)
+  {
+    // We are dealing with an nsMIMEInputStream which uses string input streams
+    // under the hood, so all of the data is available synchronously.
+    bool nonBlocking = false;
+    nsresult rv = aUploadStream->IsNonBlocking(&nonBlocking);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(!nonBlocking)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    nsAutoCString body;
+    rv = NS_ConsumeStream(aUploadStream, UINT32_MAX, body);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Extract the headers in the beginning of the buffer
+    nsAutoCString::const_iterator begin, end;
+    body.BeginReading(begin);
+    body.EndReading(end);
+    const nsAutoCString::const_iterator body_end = end;
+    nsAutoCString headerName, headerValue;
+    bool emptyHeader = false;
+    while (FetchUtil::ExtractHeader(begin, end, headerName,
+                                    headerValue, &emptyHeader) &&
+           !emptyHeader) {
+      mHeaderNames.AppendElement(headerName);
+      mHeaderValues.AppendElement(headerValue);
+      headerName.Truncate();
+      headerValue.Truncate();
+    }
+
+    // Replace the upload stream with one only containing the body text.
+    nsCOMPtr<nsIStringInputStream> strStream =
+      do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    // Skip past the "\r\n" that separates the headers and the body.
+    ++begin;
+    ++begin;
+    body.Assign(Substring(begin, body_end));
+    rv = strStream->SetData(body.BeginReading(), body.Length());
+    NS_ENSURE_SUCCESS(rv, rv);
+    mUploadStream = strStream;
+
+    return NS_OK;
+  }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
 
 } // anonymous namespace
 
 nsresult
 ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
@@ -1114,19 +1260,29 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
 
   nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
     new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
 
   if (NS_WARN_IF(!mInfo)) {
     return NS_ERROR_FAILURE;
   }
 
+  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  MOZ_ASSERT(swm);
+
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    swm->GetRegistration(mInfo->GetPrincipal(), mInfo->Scope());
+
+  nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
+    new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(registration, false));
+
   RefPtr<FetchEventRunnable> r =
     new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle,
-                           mInfo->ScriptSpec(), Move(aClientInfo), aIsReload);
+                           mInfo->ScriptSpec(), regInfo,
+                           Move(aClientInfo), aIsReload);
   rv = r->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -79,17 +79,18 @@ public:
   ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback);
 
   nsresult
   SendLifeCycleEvent(const nsAString& aEventType,
                      LifeCycleEventCallback* aCallback,
                      nsIRunnable* aLoadFailure);
 
   nsresult
-  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData);
+  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+                ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
   SendPushSubscriptionChangeEvent();
 
   nsresult
   SendNotificationClickEvent(const nsAString& aID,
                              const nsAString& aTitle,
                              const nsAString& aDir,
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/unused.h"
 #include "mozilla/dom/CacheBinding.h"
 #include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/cache/Cache.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsICacheInfoChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIStreamLoader.h"
 #include "nsIThreadRetargetableRequest.h"
 
 #include "nsIPrincipal.h"
 #include "nsNetUtil.h"
 #include "nsScriptLoader.h"
 #include "Workers.h"
@@ -88,82 +89,17 @@ public:
   explicit CompareNetwork(CompareManager* aManager)
     : mManager(aManager)
   {
     MOZ_ASSERT(aManager);
     AssertIsOnMainThread();
   }
 
   nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
-  {
-    MOZ_ASSERT(aPrincipal);
-    AssertIsOnMainThread();
-
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCOMPtr<nsILoadGroup> loadGroup;
-    rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      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_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;
-    rv = mChannel->GetLoadFlags(&flags);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    flags |= nsIRequest::LOAD_BYPASS_CACHE;
-    rv = mChannel->SetLoadFlags(flags);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
-    if (httpChannel) {
-      // Spec says no redirects allowed for SW scripts.
-      httpChannel->SetRedirectionLimit(0);
-
-      httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
-                                    NS_LITERAL_CSTRING("script"),
-                                    /* merge */ false);
-    }
-
-    nsCOMPtr<nsIStreamLoader> loader;
-    rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = mChannel->AsyncOpen2(loader);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    return NS_OK;
-  }
+  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup);
 
   void
   Abort()
   {
     AssertIsOnMainThread();
 
     MOZ_ASSERT(mChannel);
     mChannel->Cancel(NS_BINDING_ABORTED);
@@ -291,24 +227,27 @@ private:
 
 NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
 
 class CompareManager final : public PromiseNativeHandler
 {
 public:
   NS_DECL_ISUPPORTS
 
-  explicit CompareManager(CompareCallback* aCallback)
-    : mCallback(aCallback)
+  explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
+                          CompareCallback* aCallback)
+    : mRegistration(aRegistration)
+    , mCallback(aCallback)
     , mState(WaitingForOpen)
     , mNetworkFinished(false)
     , mCacheFinished(false)
     , mInCache(false)
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(aRegistration);
   }
 
   nsresult
   Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
              const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aPrincipal);
@@ -354,16 +293,23 @@ public:
 
   void
   SetMaxScope(const nsACString& aMaxScope)
   {
     MOZ_ASSERT(!mNetworkFinished);
     mMaxScope = aMaxScope;
   }
 
+  already_AddRefed<ServiceWorkerRegistrationInfo>
+  GetRegistration()
+  {
+    RefPtr<ServiceWorkerRegistrationInfo> copy = mRegistration.get();
+    return copy.forget();
+  }
+
   void
   NetworkFinished(nsresult aStatus)
   {
     AssertIsOnMainThread();
 
     mNetworkFinished = true;
 
     if (NS_WARN_IF(NS_FAILED(aStatus))) {
@@ -616,16 +562,17 @@ private:
       Fail(result.StealNSResult());
       return;
     }
 
     mState = WaitingForPut;
     cachePromise->AppendNativeHandler(this);
   }
 
+  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
   RefPtr<CompareCallback> mCallback;
   JS::PersistentRooted<JSObject*> mSandbox;
   RefPtr<CacheStorage> mCacheStorage;
 
   RefPtr<CompareNetwork> mCN;
   RefPtr<CompareCache> mCC;
 
   nsString mURL;
@@ -645,16 +592,79 @@ private:
 
   bool mNetworkFinished;
   bool mCacheFinished;
   bool mInCache;
 };
 
 NS_IMPL_ISUPPORTS0(CompareManager)
 
+nsresult
+CompareNetwork::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
+{
+  MOZ_ASSERT(aPrincipal);
+  AssertIsOnMainThread();
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsILoadGroup> loadGroup;
+  rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsLoadFlags flags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    mManager->GetRegistration();
+  if (registration->IsLastUpdateCheckTimeOverOneDay()) {
+    flags |= nsIRequest::LOAD_BYPASS_CACHE;
+  }
+
+  // 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_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+                     nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
+                     loadGroup,
+                     nullptr, // aCallbacks
+                     flags);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+  if (httpChannel) {
+    // Spec says no redirects allowed for SW scripts.
+    httpChannel->SetRedirectionLimit(0);
+
+    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Service-Worker"),
+                                  NS_LITERAL_CSTRING("script"),
+                                  /* merge */ false);
+  }
+
+  nsCOMPtr<nsIStreamLoader> loader;
+  rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mChannel->AsyncOpen2(loader);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   AssertIsOnMainThread();
 
   // If no channel, Abort() has been called.
   if (!mChannel) {
     return NS_OK;
@@ -727,16 +737,30 @@ CompareNetwork::OnStreamComplete(nsIStre
     nsAutoCString maxScope;
     // Note: we explicitly don't check for the return value here, because the
     // absence of the header is not an error condition.
     unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Service-Worker-Allowed"),
                                              maxScope);
 
     mManager->SetMaxScope(maxScope);
 
+    bool isFromCache = false;
+    nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
+    if (cacheChannel) {
+      cacheChannel->IsFromCache(&isFromCache);
+    }
+
+    // [9.2 Update]4.13, If response's cache state is not "local",
+    // set registration's last update check time to the current time
+    if (!isFromCache) {
+      RefPtr<ServiceWorkerRegistrationInfo> registration =
+        mManager->GetRegistration();
+      registration->RefreshLastUpdateCheckTime();
+    }
+
     nsAutoCString mimeType;
     rv = httpChannel->GetContentType(mimeType);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
       return rv;
     }
 
     if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
@@ -767,17 +791,17 @@ CompareNetwork::OnStreamComplete(nsIStre
     rv = uri->GetScheme(scheme);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mManager->NetworkFinished(rv);
       return NS_OK;
     }
 
     if (NS_WARN_IF(!scheme.LowerCaseEqualsLiteral("app"))) {
       mManager->NetworkFinished(NS_ERROR_FAILURE);
-      return NS_OK;      
+      return NS_OK;
     }
   }
 
   char16_t* buffer = nullptr;
   size_t len = 0;
 
   rv = nsScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
                                       NS_LITERAL_STRING("UTF-8"), nullptr,
@@ -1013,26 +1037,28 @@ GenerateCacheName(nsAString& aName)
   char chars[NSID_LENGTH];
   id.ToProvidedString(chars);
   aName.AssignASCII(chars, NSID_LENGTH);
 
   return NS_OK;
 }
 
 nsresult
-Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+        nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback,
         nsILoadGroup* aLoadGroup)
 {
   AssertIsOnMainThread();
+  MOZ_ASSERT(aRegistration);
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(!aURL.IsEmpty());
   MOZ_ASSERT(aCallback);
 
-  RefPtr<CompareManager> cm = new CompareManager(aCallback);
+  RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
 
   nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
--- a/dom/workers/ServiceWorkerScriptCache.h
+++ b/dom/workers/ServiceWorkerScriptCache.h
@@ -39,17 +39,18 @@ public:
                    const nsAString& aNewCacheName,
                    const nsACString& aMaxScope) = 0;
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
   NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
 };
 
 nsresult
-Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
+Compare(ServiceWorkerRegistrationInfo* aRegistration,
+        nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
 
 } // namespace serviceWorkerScriptCache
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -215,18 +215,16 @@ skip-if = release_build
 [test_match_all_advanced.html]
 [test_match_all_client_id.html]
 [test_match_all_client_properties.html]
 [test_navigator.html]
 [test_origin_after_redirect.html]
 [test_origin_after_redirect_cached.html]
 [test_origin_after_redirect_to_https.html]
 [test_origin_after_redirect_to_https_cached.html]
-[test_periodic_https_update.html]
-[test_periodic_update.html]
 [test_post_message.html]
 [test_post_message_advanced.html]
 [test_post_message_source.html]
 [test_register_base.html]
 [test_register_https_in_http.html]
 [test_request_context_audio.html]
 [test_request_context_beacon.html]
 [test_request_context_cache.html]
--- a/editor/libeditor/nsPlaintextEditor.cpp
+++ b/editor/libeditor/nsPlaintextEditor.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsPlaintextEditor.h"
 
+#include "gfxFontUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/mozalloc.h"
 #include "nsAString.h"
@@ -595,35 +596,38 @@ nsPlaintextEditor::ExtendSelectionForDel
         *aAction = eNone;
         break;
       case eNext:
         result = selCont->CharacterExtendForDelete();
         // Don't set aAction to eNone (see Bug 502259)
         break;
       case ePrevious: {
         // Only extend the selection where the selection is after a UTF-16
-        // surrogate pair.  For other cases we don't want to do that, in order
+        // surrogate pair or a variation selector.
+        // For other cases we don't want to do that, in order
         // to make sure that pressing backspace will only delete the last
         // typed character.
         nsCOMPtr<nsIDOMNode> node;
         int32_t offset;
         result = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
         NS_ENSURE_SUCCESS(result, result);
         NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
 
         if (IsTextNode(node)) {
           nsCOMPtr<nsIDOMCharacterData> charData = do_QueryInterface(node);
           if (charData) {
             nsAutoString data;
             result = charData->GetData(data);
             NS_ENSURE_SUCCESS(result, result);
 
-            if (offset > 1 &&
-                NS_IS_LOW_SURROGATE(data[offset - 1]) &&
-                NS_IS_HIGH_SURROGATE(data[offset - 2])) {
+            if ((offset > 1 &&
+                 NS_IS_LOW_SURROGATE(data[offset - 1]) &&
+                 NS_IS_HIGH_SURROGATE(data[offset - 2])) ||
+                (offset > 0 &&
+                 gfxFontUtils::IsVarSelector(data[offset - 1]))) {
               result = selCont->CharacterExtendForBackspace();
             }
           }
         }
         break;
       }
       case eToBeginningOfLine:
         selCont->IntraLineMove(true, false);          // try to move to end
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -163,8 +163,9 @@ skip-if = e10s
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_bug1068979.html]
 [test_bug1109465.html]
 [test_bug1162952.html]
 [test_bug1186799.html]
 [test_bug1181130-1.html]
 [test_bug1181130-2.html]
+[test_backspace_vs.html]
copy from editor/libeditor/tests/test_bug332636.html
copy to editor/libeditor/tests/test_backspace_vs.html
--- a/editor/libeditor/tests/test_bug332636.html
+++ b/editor/libeditor/tests/test_backspace_vs.html
@@ -1,75 +1,130 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=332636
+https://bugzilla.mozilla.org/show_bug.cgi?id=1216427
 -->
 <head>
-  <title>Test for Bug 332636</title>
+  <title>Test for Bug 1216427</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>  
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332636">Mozilla Bug 332636</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1216427">Mozilla Bug 1216427</a>
 <p id="display"></p>
 <div id="content">
-  <div id="edit0" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
-  <div id="edit1" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
-  <div id="edit2" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
-  <div id="edit3" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
+  <div id="edit1" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
+  <div id="edit2" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
+  <div id="edit3" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
+  <div id="edit4" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
+  <div id="edit5" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
+  <div id="edit6" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
+  <!-- The Regional Indicator combinations here were supported by Apple Color Emoji
+       even prior to the major extension of coverage in the 10.10.5 timeframe. -->
+  <div id="edit7" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
+  <div id="edit8" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
+  <div id="edit9" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
+  <div id="edit10" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
+  <div id="edit11" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
 
-  <div id="edit0b" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
-  <div id="edit1b" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
-  <div id="edit2b" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
-  <div id="edit3b" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
+  <div id="edit1b" contenteditable="true">a&#x263a;&#xfe0f;b</div><!-- BMP symbol with VS16 -->
+  <div id="edit2b" contenteditable="true">a&#x1f310;&#xfe0e;b</div><!-- plane 1 symbol with VS15 -->
+  <div id="edit3b" contenteditable="true">a&#x3402;&#xE0100;b</div><!-- BMP ideograph with VS17 -->
+  <div id="edit4b" contenteditable="true">a&#x20000;&#xE0101;b</div><!-- SMP ideograph with VS18 -->
+  <div id="edit5b" contenteditable="true">a&#x263a;&#xfe01;&#xfe02;&#xfe03;b</div><!-- BMP symbol with extra VSes -->
+  <div id="edit6b" contenteditable="true">a&#x20000;&#xE0100;&#xE0101;&#xE0102;b</div><!-- SMP symbol with extra VSes -->
+  <div id="edit7b" contenteditable="true">a&#x1F1E8;&#x1F1F3;b</div><!-- Regional Indicator flag: CN -->
+  <div id="edit8b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;b</div><!-- two RI flags: CN, DE -->
+  <div id="edit9b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;b</div><!-- three RI flags: CN, DE, ES -->
+  <div id="edit10b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;b</div><!-- four RI flags: CN, DE, ES, FR -->
+  <div id="edit11b" contenteditable="true">a&#x1F1E8;&#x1F1F3;&#x1F1E9;&#x1F1EA;&#x1F1EA;&#x1F1F8;&#x1F1EB;&#x1F1F7;&#x1F1EC;&#x1F1E7;b</div><!-- five RI flags: CN, DE, ES, FR, GB -->
 </div>
 <pre id="test">
 <script type="application/javascript">
 
-/** Test for Bug 332636 **/
+/** Test for Bug 1216427 **/
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(runTest);
 
-function test(edit) {
+function test(edit, bsCount) {
   edit.focus();
   var sel = window.getSelection();
   sel.collapse(edit.childNodes[0], edit.textContent.length - 1);
-  synthesizeKey("VK_BACK_SPACE", {});
-  is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
+  for (i = 0; i < bsCount; ++i) {
+    synthesizeKey("VK_BACK_SPACE", {});
+  }
+  is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
 }
 
-function testWithMove(edit, offset) {
+function testWithMove(edit, offset, bsCount) {
   edit.focus();
   var sel = window.getSelection();
   sel.collapse(edit.childNodes[0], 0);
   var i;
   for (i = 0; i < offset; ++i) {
     synthesizeKey("VK_RIGHT", {});
     synthesizeKey("VK_LEFT", {});
     synthesizeKey("VK_RIGHT", {});
   }
-  synthesizeKey("VK_BACK_SPACE", {});
-  is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
+  for (i = 0; i < bsCount; ++i) {
+    synthesizeKey("VK_BACK_SPACE", {});
+  }
+  is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
 }
 
 function runTest() {
-  /* test backspace-deletion of the middle character */
-  test(document.getElementById("edit0"));
-  test(document.getElementById("edit1"));
-  test(document.getElementById("edit2"));
-  test(document.getElementById("edit3"));
+  /* test backspace-deletion of the middle character(s) */
+  test(document.getElementById("edit1"), 1);
+  test(document.getElementById("edit2"), 1);
+  test(document.getElementById("edit3"), 1);
+  test(document.getElementById("edit4"), 1);
+  test(document.getElementById("edit5"), 1);
+  test(document.getElementById("edit6"), 1);
+
+  /*
+   * Tests with Regional Indicator flags: these behave differently depending
+   * whether an emoji font is present, as ligated flags are edited as single
+   * characters whereas non-ligated RI characters act individually.
+   *
+   * For now, only rely on such an emoji font on OS X 10.7+. (Note that the
+   * Segoe UI Emoji font on Win8.1 and Win10 does not implement Regional
+   * Indicator flags.)
+   *
+   * Once the Firefox Emoji font is ready, we can load that via @font-face
+   * and expect these tests to work across all platforms.
+   */
+  hasEmojiFont =
+    (navigator.platform.indexOf("Mac") == 0 &&
+     /10\.([7-9]|[1-9][0-9])/.test(navigator.oscpu));
+
+  if (hasEmojiFont) {
+    test(document.getElementById("edit7"), 1);
+    test(document.getElementById("edit8"), 2);
+    test(document.getElementById("edit9"), 3);
+    test(document.getElementById("edit10"), 4);
+    test(document.getElementById("edit11"), 5);
+  }
 
   /* extra tests with the use of RIGHT and LEFT to get to the right place */
-  testWithMove(document.getElementById("edit0b"), 2);
-  testWithMove(document.getElementById("edit1b"), 1);
-  testWithMove(document.getElementById("edit2b"), 2);
-  testWithMove(document.getElementById("edit3b"), 1);
+  testWithMove(document.getElementById("edit1b"), 2, 1);
+  testWithMove(document.getElementById("edit2b"), 2, 1);
+  testWithMove(document.getElementById("edit3b"), 2, 1);
+  testWithMove(document.getElementById("edit4b"), 2, 1);
+  testWithMove(document.getElementById("edit5b"), 2, 1);
+  testWithMove(document.getElementById("edit6b"), 2, 1);
+  if (hasEmojiFont) {
+    testWithMove(document.getElementById("edit7b"), 2, 1);
+    testWithMove(document.getElementById("edit8b"), 3, 2);
+    testWithMove(document.getElementById("edit9b"), 4, 3);
+    testWithMove(document.getElementById("edit10b"), 5, 4);
+    testWithMove(document.getElementById("edit11b"), 6, 5);
+  }
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/harfbuzz/src/hb-ft.cc
+++ b/gfx/harfbuzz/src/hb-ft.cc
@@ -79,17 +79,17 @@ static hb_ft_font_t *
   hb_ft_font_t *ft_font = (hb_ft_font_t *) calloc (1, sizeof (hb_ft_font_t));
 
   if (unlikely (!ft_font))
     return NULL;
 
   ft_font->ft_face = ft_face;
   ft_font->unref = unref;
 
-  ft_font->load_flags = FT_LOAD_DEFAULT;
+  ft_font->load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
 
   return ft_font;
 }
 
 static void
 _hb_ft_font_destroy (hb_ft_font_t *ft_font)
 {
   if (ft_font->unref)
--- a/gfx/harfbuzz/src/hb-ot-font.cc
+++ b/gfx/harfbuzz/src/hb-ot-font.cc
@@ -54,17 +54,17 @@ struct hb_ot_face_metrics_accelerator_t
 
     hb_blob_t *_hea_blob = OT::Sanitizer<OT::_hea>::sanitize (face->reference_table (_hea_tag));
     const OT::_hea *_hea = OT::Sanitizer<OT::_hea>::lock_instance (_hea_blob);
     this->num_advances = _hea->numberOfLongMetrics;
     hb_blob_destroy (_hea_blob);
 
     this->blob = OT::Sanitizer<OT::_mtx>::sanitize (face->reference_table (_mtx_tag));
     if (unlikely (!this->num_advances ||
-		  2 * (this->num_advances + this->num_metrics) < hb_blob_get_length (this->blob)))
+		  2 * (this->num_advances + this->num_metrics) > hb_blob_get_length (this->blob)))
     {
       this->num_metrics = this->num_advances = 0;
       hb_blob_destroy (this->blob);
       this->blob = hb_blob_get_empty ();
     }
     this->table = OT::Sanitizer<OT::_mtx>::lock_instance (this->blob);
   }
 
--- a/gfx/harfbuzz/src/hb-version.h
+++ b/gfx/harfbuzz/src/hb-version.h
@@ -33,19 +33,19 @@
 
 #include "hb-common.h"
 
 HB_BEGIN_DECLS
 
 
 #define HB_VERSION_MAJOR 1
 #define HB_VERSION_MINOR 0
-#define HB_VERSION_MICRO 5
+#define HB_VERSION_MICRO 6
 
-#define HB_VERSION_STRING "1.0.5"
+#define HB_VERSION_STRING "1.0.6"
 
 #define HB_VERSION_ATLEAST(major,minor,micro) \
 	((major)*10000+(minor)*100+(micro) <= \
 	 HB_VERSION_MAJOR*10000+HB_VERSION_MINOR*100+HB_VERSION_MICRO)
 
 
 void
 hb_version (unsigned int *major,
--- a/gfx/layers/AtomicRefCountedWithFinalize.h
+++ b/gfx/layers/AtomicRefCountedWithFinalize.h
@@ -57,19 +57,16 @@ protected:
 
     static void DestroyToBeCalledOnMainThread(T* ptr) {
       MOZ_ASSERT(NS_IsMainThread());
       delete ptr;
     }
 
 public:
     // Mark user classes that are considered flawless.
-    template<typename U>
-    friend class RefPtr;
-
     template<class U>
     friend class ::mozilla::StaticRefPtr;
 
     template<class U>
     friend class ::RefPtr;
 
     template<class U>
     friend struct ::RunnableMethodTraits;
--- a/gfx/layers/basic/BasicCompositor.h
+++ b/gfx/layers/basic/BasicCompositor.h
@@ -87,18 +87,19 @@ public:
   virtual void BeginFrame(const nsIntRegion& aInvalidRegion,
                           const gfx::Rect *aClipRectIn,
                           const gfx::Rect& aRenderBounds,
                           gfx::Rect *aClipRectOut = nullptr,
                           gfx::Rect *aRenderBoundsOut = nullptr) override;
   virtual void EndFrame() override;
   virtual void EndFrameForExternalComposition(const gfx::Matrix& aTransform) override
   {
-    // XXX See Bug 1215364
-    NS_WARNING("BasicCOmpositor::EndFrameForExternalComposition - not implemented!");
+      MOZ_ASSERT(!mTarget);
+      MOZ_ASSERT(!mDrawTarget);
+      MOZ_ASSERT(!mRenderTarget);
   }
 
   virtual bool SupportsPartialTextureUpdate() override { return true; }
   virtual bool CanUseCanvasLayerForSize(const gfx::IntSize &aSize) override { return true; }
   virtual int32_t GetMaxTextureSize() const override;
   virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) override { }
   
   virtual void SetScreenRenderOffset(const ScreenPoint& aOffset) override {
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -1500,22 +1500,17 @@ CompositorOGL::GetReleaseFence()
 {
   return FenceHandle();
 }
 #endif
 
 void
 CompositorOGL::EndFrameForExternalComposition(const gfx::Matrix& aTransform)
 {
-  // This lets us reftest and screenshot content rendered externally
-  if (mTarget) {
-    MakeCurrent();
-    CopyToTarget(mTarget, mTargetBounds.TopLeft(), aTransform);
-    mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
-  }
+  MOZ_ASSERT(!mTarget);
   if (mTexturePool) {
     mTexturePool->EndFrame();
   }
 }
 
 void
 CompositorOGL::SetDestinationSurfaceSize(const IntSize& aSize)
 {
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -568,19 +568,16 @@ gfxShapedText::SetupClusterBoundaries(ui
         // advance iter to the next cluster-start (or end of text)
         iter.Next();
         // step past the first char of the cluster
         aString++;
         glyphs++;
         // mark all the rest as cluster-continuations
         while (aString < iter) {
             *glyphs = extendCluster;
-            if (NS_IS_LOW_SURROGATE(*aString)) {
-                glyphs->SetIsLowSurrogate();
-            }
             glyphs++;
             aString++;
         }
     }
 }
 
 void
 gfxShapedText::SetupClusterBoundaries(uint32_t       aOffset,
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -736,18 +736,17 @@ public:
             // the mGlyphID is actually the UTF16 character code. The bit is
             // inverted so we can memset the array to zero to indicate all missing.
             FLAG_NOT_MISSING              = 0x01,
             FLAG_NOT_CLUSTER_START        = 0x02,
             FLAG_NOT_LIGATURE_GROUP_START = 0x04,
 
             FLAG_CHAR_IS_TAB              = 0x08,
             FLAG_CHAR_IS_NEWLINE          = 0x10,
-            FLAG_CHAR_IS_LOW_SURROGATE    = 0x20,
-            CHAR_IDENTITY_FLAGS_MASK      = 0x38,
+            CHAR_IDENTITY_FLAGS_MASK      = 0x18,
 
             GLYPH_COUNT_MASK = 0x00FFFF00U,
             GLYPH_COUNT_SHIFT = 8
         };
 
         // "Simple glyphs" have a simple glyph ID, simple advance and their
         // x and y offsets are zero. Also the glyph extents do not overflow
         // the font-box defined by the font ascent, descent and glyph advance width.
@@ -788,19 +787,16 @@ public:
         }
 
         bool CharIsTab() const {
             return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB) != 0;
         }
         bool CharIsNewline() const {
             return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
         }
-        bool CharIsLowSurrogate() const {
-            return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0;
-        }
 
         uint32_t CharIdentityFlags() const {
             return IsSimpleGlyph() ? 0 : (mValue & CHAR_IDENTITY_FLAGS_MASK);
         }
 
         void SetClusterStart(bool aIsClusterStart) {
             NS_ASSERTION(!IsSimpleGlyph(),
                          "can't call SetClusterStart on simple glyphs");
@@ -865,20 +861,16 @@ public:
         void SetIsTab() {
             NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
             mValue |= FLAG_CHAR_IS_TAB;
         }
         void SetIsNewline() {
             NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
             mValue |= FLAG_CHAR_IS_NEWLINE;
         }
-        void SetIsLowSurrogate() {
-            NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
-            mValue |= FLAG_CHAR_IS_LOW_SURROGATE;
-        }
 
     private:
         uint32_t mValue;
     };
 
     // Accessor for the array of CompressedGlyph records, which will be in
     // a different place in gfxShapedWord vs gfxTextRun
     virtual CompressedGlyph *GetCharacterGlyphs() = 0;
@@ -903,21 +895,16 @@ public:
                    const DetailedGlyph *aGlyphs);
 
     void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont);
 
     void SetIsSpace(uint32_t aIndex) {
         GetCharacterGlyphs()[aIndex].SetIsSpace();
     }
 
-    void SetIsLowSurrogate(uint32_t aIndex) {
-        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
-        GetCharacterGlyphs()[aIndex].SetIsLowSurrogate();
-    }
-
     bool HasDetailedGlyphs() const {
         return mDetailedGlyphs != nullptr;
     }
 
     bool IsLigatureGroupStart(uint32_t aPos) {
         NS_ASSERTION(aPos < GetLength(), "aPos out of range");
         return GetCharacterGlyphs()[aPos].IsLigatureGroupStart();
     }
--- a/gfx/thebes/gfxFontUtils.h
+++ b/gfx/thebes/gfxFontUtils.h
@@ -898,16 +898,26 @@ public:
         kUnicodeVS256 = 0xE01EF
     };
 
     static inline bool IsVarSelector(uint32_t ch) {
         return (ch >= kUnicodeVS1 && ch <= kUnicodeVS16) ||
                (ch >= kUnicodeVS17 && ch <= kUnicodeVS256);
     }
 
+    enum {
+        kUnicodeRegionalIndicatorA = 0x1F1E6,
+        kUnicodeRegionalIndicatorZ = 0x1F1FF
+    };
+
+    static inline bool IsRegionalIndicator(uint32_t aCh) {
+        return aCh >= kUnicodeRegionalIndicatorA &&
+               aCh <= kUnicodeRegionalIndicatorZ;
+    }
+
     static inline bool IsInvalid(uint32_t ch) {
         return (ch == 0xFFFD);
     }
 
     // Font code may want to know if there is the potential for bidi behavior
     // to be triggered by any of the characters in a text run; this can be
     // used to test that possibility.
     enum {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1055,19 +1055,21 @@ gfxPlatform::ComputeTileSize()
   }
 
   int32_t w = gfxPrefs::LayersTileWidth();
   int32_t h = gfxPrefs::LayersTileHeight();
 
   if (gfxPrefs::LayersTilesAdjust()) {
     gfx::IntSize screenSize = GetScreenSize();
     if (screenSize.width > 0) {
-      // FIXME: we should probably make sure this is within the max texture size,
-      // but I think everything should at least support 1024
-      w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256);
+      // For the time being tiles larger than 512 probably do not make much
+      // sense. This is due to e.g. increased rasterisation time outweighing
+      // the decreased composition time, or large increases in memory usage
+      // for screens slightly wider than a higher power of two.
+      w = h = screenSize.width >= 512 ? 512 : 256;
     }
 
 #ifdef MOZ_WIDGET_GONK
     android::sp<android::GraphicBuffer> alloc =
           new android::GraphicBuffer(w, h, android::PIXEL_FORMAT_RGBA_8888,
                                      android::GraphicBuffer::USAGE_SW_READ_OFTEN |
                                      android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
                                      android::GraphicBuffer::USAGE_HW_TEXTURE);
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -123,20 +123,16 @@ public:
     bool CharIsTab(uint32_t aPos) const {
         NS_ASSERTION(aPos < GetLength(), "aPos out of range");
         return mCharacterGlyphs[aPos].CharIsTab();
     }
     bool CharIsNewline(uint32_t aPos) const {
         NS_ASSERTION(aPos < GetLength(), "aPos out of range");
         return mCharacterGlyphs[aPos].CharIsNewline();
     }
-    bool CharIsLowSurrogate(uint32_t aPos) const {
-        NS_ASSERTION(aPos < GetLength(), "aPos out of range");
-        return mCharacterGlyphs[aPos].CharIsLowSurrogate();
-    }
 
     // All uint32_t aStart, uint32_t aLength ranges below are restricted to
     // grapheme cluster boundaries! All offsets are in terms of the string
     // passed into MakeTextRun.
     
     // All coordinates are in layout/app units
 
     /**
@@ -530,20 +526,16 @@ public:
             DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
             details->mGlyphID = g->GetSimpleGlyph();
             details->mAdvance = g->GetSimpleAdvance();
             details->mXOffset = details->mYOffset = 0;
             SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1), details);
         }
         g->SetIsNewline();
     }
-    void SetIsLowSurrogate(uint32_t aIndex) {
-        SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
-        mCharacterGlyphs[aIndex].SetIsLowSurrogate();
-    }
 
     /**
      * Prefetch all the glyph extents needed to ensure that Measure calls
      * on this textrun not requesting tight boundingBoxes will succeed. Note
      * that some glyph extents might not be fetched due to OOM or other
      * errors.
      */
     void FetchGlyphExtents(gfxContext *aRefContext);
--- a/image/ClippedImage.cpp
+++ b/image/ClippedImage.cpp
@@ -291,19 +291,19 @@ ClippedImage::GetFrameInternal(const nsI
     gfxUtils::DrawPixelSnapped(ctx, drawable, aSize,
                                ImageRegion::Create(aSize),
                                SurfaceFormat::B8G8R8A8,
                                Filter::LINEAR,
                                imgIContainer::FLAG_CLAMP);
 
     // Cache the resulting surface.
     mCachedSurface =
-      new ClippedImageCachedSurface(target->Snapshot(), aSize, aSVGContext,
-                                    frameToDraw, aFlags,
-                                    drawTileCallback->GetDrawResult());
+      MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext,
+                                            frameToDraw, aFlags,
+                                            drawTileCallback->GetDrawResult());
   }
 
   MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
   RefPtr<SourceSurface> surface = mCachedSurface->Surface();
   return MakePair(mCachedSurface->GetDrawResult(), Move(surface));
 }
 
 NS_IMETHODIMP_(bool)
--- a/image/ClippedImage.h
+++ b/image/ClippedImage.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_image_ClippedImage_h
 #define mozilla_image_ClippedImage_h
 
 #include "ImageWrapper.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace image {
 
 class ClippedImageCachedSurface;
 class DrawSingleTileCallback;
 
 /**
@@ -78,17 +79,17 @@ private:
                             const nsIntSize& aSize,
                             const ImageRegion& aRegion,
                             uint32_t aWhichFrame,
                             gfx::Filter aFilter,
                             const Maybe<SVGImageContext>& aSVGContext,
                             uint32_t aFlags);
 
   // If we are forced to draw a temporary surface, we cache it here.
-  nsAutoPtr<ClippedImageCachedSurface> mCachedSurface;
+  UniquePtr<ClippedImageCachedSurface> mCachedSurface;
 
   nsIntRect   mClip;              // The region to clip to.
   Maybe<bool> mShouldClip;        // Memoized ShouldClip() if present.
 
   friend class DrawSingleTileCallback;
   friend class ImageOps;
 };
 
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DecodePool.h"
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Monitor.h"
-#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -9,17 +9,16 @@
 
 #include "RasterImage.h"
 
 #include "base/histogram.h"
 #include "gfxPlatform.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "Decoder.h"
-#include "nsAutoPtr.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
 #include "LookupResult.h"
 #include "nsIConsoleService.h"
 #include "nsIInputStream.h"
--- a/image/ShutdownTracker.cpp
+++ b/image/ShutdownTracker.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ShutdownTracker.h"
 
 #include "mozilla/Services.h"
-#include "nsAutoPtr.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 
 namespace mozilla {
 namespace image {
 
 class ShutdownTrackerImpl;
 
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -23,17 +23,16 @@
 #include "nsIMemoryReporter.h"
 #include "gfx2DGlue.h"
 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "imgFrame.h"
 #include "Image.h"
 #include "LookupResult.h"
-#include "nsAutoPtr.h"
 #include "nsExpirationTracker.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsSize.h"
 #include "nsTArray.h"
 #include "prsystem.h"
 #include "ShutdownTracker.h"
 #include "SVGImageContext.h"
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -115,16 +115,51 @@ struct RLE {
     DELTA_LENGTH = 2
   };
 };
 
 } // namespace bmp
 
 using namespace bmp;
 
+/// Sets the pixel data in aDecoded to the given values.
+/// @param aDecoded pointer to pixel to be set, will be incremented to point to
+/// the next pixel.
+static void
+SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen,
+         uint8_t aBlue, uint8_t aAlpha = 0xFF)
+{
+  *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue);
+}
+
+static void
+SetPixel(uint32_t*& aDecoded, uint8_t idx, bmp::ColorTableEntry* aColors)
+{
+  SetPixel(aDecoded,
+           aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue);
+}
+
+/// Sets two (or one if aCount = 1) pixels
+/// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes
+/// depending on whether one or two pixels are written.
+/// @param aData The values for the two pixels
+/// @param aCount Current count. Is decremented by one or two.
+static void
+Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount,
+             bmp::ColorTableEntry* aColors)
+{
+  uint8_t idx = aData >> 4;
+  SetPixel(aDecoded, idx, aColors);
+  if (--aCount > 0) {
+    idx = aData & 0xF;
+    SetPixel(aDecoded, idx, aColors);
+    --aCount;
+  }
+}
+
 static PRLogModuleInfo*
 GetBMPLog()
 {
   static PRLogModuleInfo* sBMPLog;
   if (!sBMPLog) {
     sBMPLog = PR_NewLogModule("BMPDecoder");
   }
   return sBMPLog;
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsBMPDecoder_h
 #define mozilla_image_decoders_nsBMPDecoder_h
 
 #include "BMPFileHeaders.h"
 #include "Decoder.h"
 #include "gfxColor.h"
-#include "nsAutoPtr.h"
 #include "StreamingLexer.h"
 
 namespace mozilla {
 namespace image {
 
 namespace bmp {
 
 /// An entry in the color table.
@@ -181,47 +180,12 @@ private:
   //   http://en.wikipedia.org/wiki/ICO_(file_format)#cite_note-9
   // Bitmaps where the alpha bytes are all 0 should be fully visible.
   bool mUseAlphaData;
 
   // Whether the 4th byte alpha data was found to be non zero and hence used.
   bool mHaveAlphaData;
 };
 
-/// Sets the pixel data in aDecoded to the given values.
-/// @param aDecoded pointer to pixel to be set, will be incremented to point to
-/// the next pixel.
-static inline void
-SetPixel(uint32_t*& aDecoded, uint8_t aRed, uint8_t aGreen,
-         uint8_t aBlue, uint8_t aAlpha = 0xFF)
-{
-  *aDecoded++ = gfxPackedPixel(aAlpha, aRed, aGreen, aBlue);
-}
-
-static inline void
-SetPixel(uint32_t*& aDecoded, uint8_t idx, bmp::ColorTableEntry* aColors)
-{
-  SetPixel(aDecoded,
-           aColors[idx].mRed, aColors[idx].mGreen, aColors[idx].mBlue);
-}
-
-/// Sets two (or one if aCount = 1) pixels
-/// @param aDecoded where the data is stored. Will be moved 4 resp 8 bytes
-/// depending on whether one or two pixels are written.
-/// @param aData The values for the two pixels
-/// @param aCount Current count. Is decremented by one or two.
-inline void
-Set4BitPixel(uint32_t*& aDecoded, uint8_t aData, uint32_t& aCount,
-             bmp::ColorTableEntry* aColors)
-{
-  uint8_t idx = aData >> 4;
-  SetPixel(aDecoded, idx, aColors);
-  if (--aCount > 0) {
-    idx = aData & 0xF;
-    SetPixel(aDecoded, idx, aColors);
-    --aCount;
-  }
-}
-
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_decoders_nsBMPDecoder_h
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #ifndef mozilla_image_decoders_nsICODecoder_h
 #define mozilla_image_decoders_nsICODecoder_h
 
-#include "nsAutoPtr.h"
 #include "StreamingLexer.h"
 #include "Decoder.h"
 #include "imgFrame.h"
 #include "nsBMPDecoder.h"
 #include "nsPNGDecoder.h"
 #include "ICOFileHeaders.h"
 
 namespace mozilla {
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -10,18 +10,16 @@
 #include "RasterImage.h"
 // On Windows systems, RasterImage.h brings in 'windows.h', which defines INT32.
 // But the jpeg decoder has its own definition of INT32. To avoid build issues,
 // we need to undefine the version from 'windows.h'.
 #undef INT32
 
 #include "Decoder.h"
 
-#include "nsAutoPtr.h"
-
 #include "nsIInputStream.h"
 #include "nsIPipe.h"
 #include "qcms.h"
 
 extern "C" {
 #include "jpeglib.h"
 }
 
--- a/image/encoders/bmp/nsBMPEncoder.cpp
+++ b/image/encoders/bmp/nsBMPEncoder.cpp
@@ -1,20 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsCRT.h"
 #include "mozilla/Endian.h"
 #include "nsBMPEncoder.h"
 #include "prprf.h"
 #include "nsString.h"
 #include "nsStreamUtils.h"
 #include "nsTArray.h"
-#include "nsAutoPtr.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
 using namespace mozilla::image::bmp;
 
 NS_IMPL_ISUPPORTS(nsBMPEncoder, imgIEncoder, nsIInputStream,
                   nsIAsyncInputStream)
 
@@ -182,42 +182,42 @@ nsBMPEncoder::AddImageFrame(const uint8_
 
   // validate input format
   if (aInputFormat != INPUT_FORMAT_RGB &&
       aInputFormat != INPUT_FORMAT_RGBA &&
       aInputFormat != INPUT_FORMAT_HOSTARGB) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  nsAutoArrayPtr<uint8_t> row(new (fallible)
-                              uint8_t[mBMPInfoHeader.width *
-                              BytesPerPixel(mBMPInfoHeader.bpp)]);
+  UniquePtr<uint8_t[]> row(new (fallible)
+                           uint8_t[mBMPInfoHeader.width *
+                                   BytesPerPixel(mBMPInfoHeader.bpp)]);
   if (!row) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // write each row: if we add more input formats, we may want to
   // generalize the conversions
   if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
     // BMP requires RGBA with post-multiplied alpha, so we need to convert
     for (int32_t y = mBMPInfoHeader.height - 1; y >= 0 ; y --) {
       ConvertHostARGBRow(&aData[y * aStride], row, mBMPInfoHeader.width);
       if(mBMPInfoHeader.bpp == 24) {
-        EncodeImageDataRow24(row);
+        EncodeImageDataRow24(row.get());
       } else {
-        EncodeImageDataRow32(row);
+        EncodeImageDataRow32(row.get());
       }
     }
   } else if (aInputFormat == INPUT_FORMAT_RGBA) {
     // simple RGBA, no conversion needed
     for (int32_t y = 0; y < mBMPInfoHeader.height; y++) {
       if (mBMPInfoHeader.bpp == 24) {
-        EncodeImageDataRow24(row);
+        EncodeImageDataRow24(row.get());
       } else {
-        EncodeImageDataRow32(row);
+        EncodeImageDataRow32(row.get());
       }
     }
   } else if (aInputFormat == INPUT_FORMAT_RGB) {
     // simple RGB, no conversion needed
     for (int32_t y = 0; y < mBMPInfoHeader.height; y++) {
       if (mBMPInfoHeader.bpp == 24) {
         EncodeImageDataRow24(&aData[y * aStride]);
       } else {
@@ -420,17 +420,18 @@ nsBMPEncoder::CloseWithStatus(nsresult a
 }
 
 // nsBMPEncoder::ConvertHostARGBRow
 //
 //    Our colors are stored with premultiplied alphas, but we need
 //    an output with no alpha in machine-independent byte order.
 //
 void
-nsBMPEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
+nsBMPEncoder::ConvertHostARGBRow(const uint8_t* aSrc,
+                                 const UniquePtr<uint8_t[]>& aDest,
                                  uint32_t aPixelWidth)
 {
   int bytes = BytesPerPixel(mBMPInfoHeader.bpp);
 
   if (mBMPInfoHeader.bpp == 32) {
     for (uint32_t x = 0; x < aPixelWidth; x++) {
       const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
       uint8_t* pixelOut = &aDest[x * bytes];
--- a/image/encoders/bmp/nsBMPEncoder.h
+++ b/image/encoders/bmp/nsBMPEncoder.h
@@ -1,17 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_encoders_bmp_nsBMPEncoder_h
 #define mozilla_image_encoders_bmp_nsBMPEncoder_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
+#include "mozilla/UniquePtr.h"
 
 #include "imgIEncoder.h"
 #include "BMPFileHeaders.h"
 
 #include "nsCOMPtr.h"
 
 #define NS_BMPENCODER_CID \
 { /* 13a5320c-4c91-4FA4-bd16-b081a3ba8c0b */         \
@@ -43,17 +45,18 @@ protected:
       VERSION_3 = 3,
       VERSION_5 = 5
   };
 
   // See InitData in the cpp for valid parse options
   nsresult ParseOptions(const nsAString& aOptions, Version* version,
                         uint32_t* bpp);
   // Obtains data with no alpha in machine-independent byte order
-  void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
+  void ConvertHostARGBRow(const uint8_t* aSrc,
+                          const mozilla::UniquePtr<uint8_t[]>& aDest,
                           uint32_t aPixelWidth);
   // Thread safe notify listener
   void NotifyListener();
 
   // Initializes the bitmap file header member mBMPFileHeader
   void InitFileHeader(Version aVersion, uint32_t aBPP, uint32_t aWidth,
                       uint32_t aHeight);
   // Initializes the bitmap info header member mBMPInfoHeader
--- a/image/encoders/ico/nsICOEncoder.h
+++ b/image/encoders/ico/nsICOEncoder.h
@@ -5,17 +5,16 @@
 #ifndef mozilla_image_encoders_ico_nsICOEncoder_h
 #define mozilla_image_encoders_ico_nsICOEncoder_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 
 #include "imgIEncoder.h"
 
-#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "ICOFileHeaders.h"
 
 #define NS_ICOENCODER_CID \
 { /*92AE3AB2-8968-41B1-8709-B6123BCEAF21 */          \
      0x92ae3ab2,                                     \
      0x8968,                                         \
      0x41b1,                                         \
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -1185,17 +1185,17 @@ imgLoader::VerifyCacheSizes()
   if (!mCacheTracker) {
     return;
   }
 
   uint32_t cachesize = mCache.Count() + mChromeCache.Count();
   uint32_t queuesize =
     mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
   uint32_t trackersize = 0;
-  for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker);
+  for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
        it.Next(); ){
     trackersize++;
   }
   MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
   MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
 #endif
 }
 
@@ -1248,17 +1248,17 @@ imgLoader::InitCache()
   os->AddObserver(this, "memory-pressure", false);
   os->AddObserver(this, "app-theme-changed", false);
   os->AddObserver(this, "chrome-flush-skin-caches", false);
   os->AddObserver(this, "chrome-flush-caches", false);
   os->AddObserver(this, "last-pb-context-exited", false);
   os->AddObserver(this, "profile-before-change", false);
   os->AddObserver(this, "xpcom-shutdown", false);
 
-  mCacheTracker = new imgCacheExpirationTracker();
+  mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
 
   return NS_OK;
 }
 
 nsresult
 imgLoader::Init()
 {
   InitCache();
--- a/image/imgLoader.h
+++ b/image/imgLoader.h
@@ -4,24 +4,24 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_imgLoader_h
 #define mozilla_image_imgLoader_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
 
 #include "imgILoader.h"
 #include "imgICache.h"
 #include "nsWeakReference.h"
 #include "nsIContentSniffer.h"
 #include "nsRefPtrHashtable.h"
 #include "nsExpirationTracker.h"
-#include "nsAutoPtr.h"
 #include "ImageCacheKey.h"
 #include "imgRequest.h"
 #include "nsIProgressEventSink.h"
 #include "nsIChannel.h"
 #include "nsIThreadRetargetableStreamListener.h"
 #include "imgIRequest.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
@@ -435,17 +435,17 @@ private: // data
   Mutex mUncachedImagesMutex;
 
   static double sCacheTimeWeight;
   static uint32_t sCacheMaxSize;
   static imgMemoryReporter* sMemReporter;
 
   nsCString mAcceptHeader;
 
-  nsAutoPtr<imgCacheExpirationTracker> mCacheTracker;
+  mozilla::UniquePtr<imgCacheExpirationTracker> mCacheTracker;
   bool mRespectPrivacy;
 };
 
 
 
 /**
  * proxy stream listener class used to handle multipart/x-mixed-replace
  */
--- a/image/imgRequest.h
+++ b/image/imgRequest.h
@@ -8,17 +8,16 @@
 #define mozilla_image_imgRequest_h
 
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIStreamListener.h"
 #include "nsIThreadRetargetableStreamListener.h"
 #include "nsIPrincipal.h"
 
-#include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsProxyRelease.h"
 #include "nsStringGlue.h"
 #include "nsError.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "ImageCacheKey.h"
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -1085,17 +1085,17 @@ private:
   // the job of the underlying request.
   RefPtr<mozilla::image::Image> mImage;
 };
 
 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
                                              nsIPrincipal* aPrincipal)
 : mPrincipal(aPrincipal)
 {
-  mBehaviour = new StaticBehaviour(aImage);
+  mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
 }
 
 NS_IMETHODIMP
 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal)
 {
   if (!mPrincipal) {
     return NS_ERROR_FAILURE;
   }
--- a/image/imgRequestProxy.h
+++ b/image/imgRequestProxy.h
@@ -9,19 +9,19 @@
 
 #include "imgIRequest.h"
 #include "nsISecurityInfoProvider.h"
 
 #include "nsILoadGroup.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "nsCOMPtr.h"
-#include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/Rect.h"
 
 #include "imgRequest.h"
 #include "IProgressObserver.h"
 
 #define NS_IMGREQUESTPROXY_CID \
 { /* 20557898-1dd2-11b2-8f65-9c462ee2bc95 */         \
      0x20557898,                                     \
@@ -186,17 +186,17 @@ protected:
   nsresult PerformClone(imgINotificationObserver* aObserver,
                         imgRequestProxy* (aAllocFn)(imgRequestProxy*),
                         imgRequestProxy** aClone);
 
 public:
   NS_FORWARD_SAFE_NSITIMEDCHANNEL(TimedChannel())
 
 protected:
-  nsAutoPtr<ProxyBehaviour> mBehaviour;
+  mozilla::UniquePtr<ProxyBehaviour> mBehaviour;
 
 private:
   friend class imgCacheValidator;
   friend imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis);
 
   // The URI of our request.
   RefPtr<ImageURL> mURI;
 
--- a/intl/uconv/moz.build
+++ b/intl/uconv/moz.build
@@ -14,31 +14,33 @@ XPIDL_SOURCES += [
 ]
 
 XPIDL_MODULE = 'uconv'
 
 EXPORTS += [
     'nsEncoderDecoderUtils.h',
     'nsIUnicodeDecoder.h',
     'nsIUnicodeEncoder.h',
+    'nsNCRFallbackEncoderWrapper.h',
     'nsUConvCID.h',
     'nsUCSupport.h',
     'uconvutil.h',
     'ucvcn/nsUCvCnCID.h',
     'ucvja/nsUCVJA2CID.h',
     'ucvja/nsUCVJACID.h',
     'ucvko/nsUCvKOCID.h',
     'ucvlatin/nsUCvLatinCID.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsConverterInputStream.cpp',
     'nsConverterOutputStream.cpp',
     'nsCP1252ToUnicode.cpp',
     'nsMacRomanToUnicode.cpp',
+    'nsNCRFallbackEncoderWrapper.cpp',
     'nsReplacementToUnicode.cpp',
     'nsScriptableUConv.cpp',
     'nsTextToSubURI.cpp',
     'nsUConvModule.cpp',
     'nsUnicodeToCP1252.cpp',
     'nsUnicodeToISO88591.cpp',
     'nsUnicodeToMacRoman.cpp',
     'nsUnicodeToUTF8.cpp',
--- a/intl/uconv/nsIUnicodeEncoder.h
+++ b/intl/uconv/nsIUnicodeEncoder.h
@@ -97,29 +97,34 @@ public:
    *                    NS_ERROR_UENC_NOMAPPING if character without mapping
    *                    was encountered and the behavior was set to "signal".
    *                    In the case of an unmappable BMP character, aDestLength
    *                    must indicate that the unmappable character was
    *                    consumed by the encoder (unlike in the decode API!).
    *                    In the case of an unmappable astral character,
    *                    aDestLength must indicate that the high surrogate was
    *                    consumed by the encoder but the low surrogate was not.
+   *                    NS_OK otherwise.
    */
   NS_IMETHOD Convert(const char16_t * aSrc, int32_t * aSrcLength, 
       char * aDest, int32_t * aDestLength) = 0;
 
   /**
    * Finishes the conversion. The converter has the possibility to write some 
    * extra data and flush its final state.
    *
    * @param aDest       [OUT] the destination data buffer
    * @param aDestLength [IN/OUT] the length of destination data buffer; after
    *                    conversion it will contain the number of bytes written
    * @return            NS_OK_UENC_MOREOUTPUT if only  a partial conversion
-   *                    was done; more output space is needed to continue
+   *                    was done; more output space is needed to continue.
+   *                    NS_ERROR_UENC_NOMAPPING if input ended with an unpaired
+   *                    high surrogate, the behavior was "signal" and the
+   *                    encoding can't represent U+FFFD.
+   *                    NS_OK otherwise.
    */
   NS_IMETHOD Finish(char * aDest, int32_t * aDestLength) = 0;
 
   /**
    * Returns a quick estimation of the size of the buffer needed to hold the
    * converted data. Remember: this estimation is >= with the actual size of 
    * the buffer needed. It will be computed for the "worst case"
    *
new file mode 100644
--- /dev/null
+++ b/intl/uconv/nsNCRFallbackEncoderWrapper.cpp
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNCRFallbackEncoderWrapper.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+nsNCRFallbackEncoderWrapper::nsNCRFallbackEncoderWrapper(const nsACString& aEncoding)
+ : mEncoder(mozilla::dom::EncodingUtils::EncoderForEncoding(aEncoding))
+{
+}
+
+nsNCRFallbackEncoderWrapper::~nsNCRFallbackEncoderWrapper()
+{
+}
+
+bool
+nsNCRFallbackEncoderWrapper::WriteNCR(nsACString& aBytes,
+                                      uint32_t& aDstWritten,
+                                      int32_t aUnmappable)
+{
+  // To avoid potentially shrinking aBytes and then growing it back, use
+  // another string for number formatting.
+  nsAutoCString ncr("&#");
+  ncr.AppendInt(aUnmappable);
+  ncr.Append(';');
+  uint32_t ncrLen = ncr.Length();
+  uint32_t needed = aDstWritten + ncrLen;
+  if (needed > INT32_MAX) {
+    return false;
+  }
+  if (needed > aBytes.Length() && !aBytes.SetLength(needed,
+                                                    mozilla::fallible_t())) {
+    return false;
+  }
+  memcpy(aBytes.BeginWriting() + aDstWritten,
+         ncr.BeginReading(),
+         ncrLen);
+  aDstWritten += ncrLen;
+  return true;
+}
+
+bool
+nsNCRFallbackEncoderWrapper::Encode(const nsAString& aUtf16,
+                                    nsACString& aBytes)
+{
+  // nsIUnicodeEncoder uses int32_t for sizes :-(
+  if (aUtf16.Length() > INT32_MAX) {
+    return false;
+  }
+  const char16_t* src = aUtf16.BeginReading();
+  const char16_t* srcEnd = aUtf16.EndReading();
+  uint32_t dstWritten = 0;
+  for (;;) {
+    int32_t srcLen = srcEnd - src;
+    int32_t dstLen = 0;
+    nsresult rv = mEncoder->GetMaxLength(src, srcLen, &dstLen);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+    uint32_t needed = dstWritten + dstLen;
+    if (needed > INT32_MAX) {
+      return false;
+    }
+    // Behind the scenes SetLength() makes the underlying allocation not have
+    // slop, so we don't need to round up here.
+    if (needed > aBytes.Length() && !aBytes.SetLength(needed,
+                                                      mozilla::fallible_t())) {
+      return false;
+    }
+    // We need to re-obtain the destination pointer on every iteration, because
+    // SetLength() invalidates it.
+    char* dst = aBytes.BeginWriting() + dstWritten;
+    dstLen = aBytes.Length() - dstWritten;
+    mEncoder->Reset();
+    rv = mEncoder->Convert(src, &srcLen, dst, &dstLen);
+    // Update state tracking
+    src += srcLen;
+    dstWritten += dstLen;
+    if (rv == NS_OK_UENC_MOREOUTPUT) {
+      MOZ_ASSERT_UNREACHABLE("GetMaxLength must have returned a bogus length.");
+      return false;
+    }
+    if (rv == NS_ERROR_UENC_NOMAPPING) {
+      int32_t unmappable;
+      // The unmappable code unit or the first half of an unmappable surrogate
+      // pair is consumed by the encoder.
+      MOZ_ASSERT(srcLen > 0, "Encoder should have consumed some input.");
+      char16_t codeUnit = src[-1];
+      // Let's see if it is a surrogate
+      size_t highBits = (codeUnit & 0xFC00);
+      if (highBits == 0xD800) {
+        // high surrogate
+        // Let's see if we actually have a surrogate pair.
+        char16_t next;
+        if (src < srcEnd && NS_IS_LOW_SURROGATE((next = *src))) {
+          src++; // consume the low surrogate
+          unmappable = SURROGATE_TO_UCS4(codeUnit, next);
+        } else {
+          // unpaired surrogate.
+          unmappable = 0xFFFD;
+        }
+      } else if (highBits == 0xDC00) {
+        // low surrogate
+        // This must be an unpaired surrogate.
+        unmappable = 0xFFFD;
+      } else {
+        // not a surrogate
+        unmappable = codeUnit;
+      }
+      // If we are encoding to ISO-2022-JP, we need to let the encoder to
+      // generate a transition to the ASCII state if not already there.
+      dst = aBytes.BeginWriting() + dstWritten;
+      dstLen = aBytes.Length() - dstWritten;
+      rv = mEncoder->Finish(dst, &dstLen);
+      dstWritten += dstLen;
+      if (rv != NS_OK) {
+        // Failures should be impossible if GetMaxLength works. Big5 is the
+        // only case where Finish() may return NS_ERROR_UENC_NOMAPPING but
+        // that should never happen right after Convert() has returned it.
+        MOZ_ASSERT_UNREACHABLE("Broken encoder.");
+        return false;
+      }
+      if (!WriteNCR(aBytes, dstWritten, unmappable)) {
+        return false;
+      }
+      continue;
+    }
+    if (!(rv == NS_OK || rv == NS_OK_UENC_MOREINPUT)) {
+      return false;
+    }
+    MOZ_ASSERT(src == srcEnd, "Converter did not consume all input.");
+    dst = aBytes.BeginWriting() + dstWritten;
+    dstLen = aBytes.Length() - dstWritten;
+    rv = mEncoder->Finish(dst, &dstLen);
+    dstWritten += dstLen;
+    if (rv == NS_OK_UENC_MOREOUTPUT) {
+      MOZ_ASSERT_UNREACHABLE("GetMaxLength must have returned a bogus length.");
+      return false;
+    }
+    if (rv == NS_ERROR_UENC_NOMAPPING) {
+      // Big5
+      if (!WriteNCR(aBytes, dstWritten, 0xFFFD)) {
+        return false;
+      }
+    }
+    return aBytes.SetLength(dstWritten, mozilla::fallible_t());
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/intl/uconv/nsNCRFallbackEncoderWrapper.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNCRFallbackEncoderWrapper_h_
+#define nsNCRFallbackEncoderWrapper_h_
+
+#include "nsIUnicodeEncoder.h"
+
+class nsNCRFallbackEncoderWrapper
+{
+public:
+  explicit nsNCRFallbackEncoderWrapper(const nsACString& aEncoding);
+  ~nsNCRFallbackEncoderWrapper();
+
+  /**
+   * Convert text to bytes with decimal numeric character reference replacement
+   * for unmappables.
+   *
+   * @param aUtf16 UTF-16 input
+   * @param aBytes conversion output
+   * @return true on success and false on failure (OOM)
+   */
+  bool Encode(const nsAString& aUtf16,
+              nsACString& aBytes);
+
+private:
+  bool WriteNCR(nsACString& aBytes, uint32_t& aDstWritten, int32_t aUnmappable);
+
+  nsCOMPtr<nsIUnicodeEncoder> mEncoder;
+};
+
+#endif /* nsNCRFallbackEncoderWrapper_h_ */
--- a/intl/uconv/tests/mochitest.ini
+++ b/intl/uconv/tests/mochitest.ini
@@ -8,8 +8,9 @@ skip-if = buildapp == 'b2g'
 [test_long_doc.html]
 skip-if = toolkit == 'android' #bug 775227
 [test_singlebyte_overconsumption.html]
 [test_unicode_noncharacterescapes.html]
 [test_unicode_noncharacters_gb18030.html]
 [test_unicode_noncharacters_utf8.html]
 [test_utf8_overconsumption.html]
 [test_big5_encoder.html]
+[test_ncr_fallback.html]
copy from intl/uconv/tests/test_big5_encoder.html
copy to intl/uconv/tests/test_ncr_fallback.html
--- a/intl/uconv/tests/test_big5_encoder.html
+++ b/intl/uconv/tests/test_ncr_fallback.html
@@ -1,43 +1,74 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=912470
+https://bugzilla.mozilla.org/show_bug.cgi?id=1202366
 -->
 <head>
   <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
-  <title>Test for Unicode non-characters</title>
+  <title>Test for unpaired surrogates</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="test()">
+<body onload="step()">
 <pre id="test">
 <script class="testbody" type="text/javascript">
 /* NOTE:
  * When we make our data: URL origin work as in Blink, this test will fail.
- * Hopefully, by that time are URL parser has become spec-compliant, so that
- * we'll pass the Web Platform Test for the big5 encoder
- * (testing/web-platform/tests/encoding/big5-encoder.html) and this test can
- * simply be removed.
+ * Don't let this test block alignment of data: URL origin with Blink.
  */
 SimpleTest.waitForExplicitFinish();
 
-function test() {
-  var f = document.getElementsByTagName("iframe")[0];
+var expectations = [
+  "%26%2365533%3B",
+  "a%26%2365533%3B",
+  "%26%2365533%3Ba",
+  "a%26%2365533%3Ba",
+  "%26%2365533%3B",
+  "a%26%2365533%3B",
+  "%26%2365533%3Ba",
+  "a%26%2365533%3Ba",
+  "%26%23128169%3B",
+  "%26%23128169%3B",
+  "%1B%24B%22%29%1B%28B",
+  "%1B%24B%22%29%1B%28B%26%23128169%3B",
+];
+
+var i = 0;
+
+function step() {
+  var f = document.getElementsByTagName("iframe")[i];
   f.onload = function() {
     var href = f.contentWindow.location.href;
     var index = href.indexOf("?foo=");
     var actual = href.substring(index + 5);
-    var expected = "h%26%2340614%3Bi%26%23156267%3Bj%A1%40k%A3%E1l%A4%40m%C8%A4n%C8%CDo%FE%FEp%26%238365%3Bq%FDjr%F9%F9s%26%23128169%3Bt";
+    var expected = expectations[i];
     is(actual, expected, "Should have gotten the expected encode.");
-    SimpleTest.finish();
+    i++
+    if (i == document.getElementsByTagName("iframe").length) {
+      SimpleTest.finish();
+    } else {
+      step();
+    }    
   }
   f.contentDocument.forms[0].submit();
 }
 </script>
 </pre>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912470">Mozilla Bug 912470</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1202366">Mozilla Bug 1202366</a>
 <p id="display"></p>
-<div id="content" style="display: none"><iframe src="data:text/html;charset=big5,<form><input name=foo value=h&amp;%23x9EA6;i&amp;%23x2626B;j&amp;%23x3000;k&amp;%23x20AC;l&amp;%23x4E00;m&amp;%23x27607;n&amp;%23xFFE2;o&amp;%23x79D4;p&amp;%23x20AD;q&amp;%23x203B5;r&amp;%23x2550;s&amp;%23x1F4A9;t></form>">
+<div id="content" style="display: none">
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=\uD83D></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=a\uD83D></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=\uD83Da></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=a\uD83Da></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=\uDCA9></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=a\uDCA9></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=\uDCA9a></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=a\uDCA9a></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=big5,<script>document.write('<form><input name=foo value=\uD83D\uDCA9></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=iso-2022-jp,<script>document.write('<form><input name=foo value=\uD83D\uDCA9></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=iso-2022-jp,<script>document.write('<form><input name=foo value=\u3012></form>');</script>"></iframe>
+<iframe src="data:text/html;charset=iso-2022-jp,<script>document.write('<form><input name=foo value=\u3012\uD83D\uDCA9></form>');</script>"></iframe>
 </div>
 </body>
 </html>
--- a/intl/uconv/ucvtw/nsUnicodeToBIG5.cpp
+++ b/intl/uconv/ucvtw/nsUnicodeToBIG5.cpp
@@ -39,17 +39,17 @@ nsUnicodeToBIG5::Convert(const char16_t*
     }
     *out++ = mPendingTrail;
     mPendingTrail = 0;
   }
   for (;;) {
     if (in == inEnd) {
       *aSrcLength = in - aSrc;
       *aDestLength = out - reinterpret_cast<uint8_t*>(aDest);
-      return NS_OK_UENC_MOREINPUT;
+      return mUtf16Lead ? NS_OK_UENC_MOREINPUT : NS_OK;
     }
     if (out == outEnd) {
       *aSrcLength = in - aSrc;
       *aDestLength = out - reinterpret_cast<uint8_t*>(aDest);
       return NS_OK_UENC_MOREOUTPUT;
     }
     bool isAstral; // true means Plane 2, false means BMP
     char16_t lowBits; // The low 16 bits of the code point
@@ -188,20 +188,21 @@ nsUnicodeToBIG5::Finish(char* aDest,
     *aDestLength = 1;
     return NS_OK;
   }
   if (mUtf16Lead) {
     if (*aDestLength < 1) {
       *aDestLength = 0;
       return NS_OK_UENC_MOREOUTPUT;
     }
-    // The API doesn't support signaling an error. It pretends that malformed
-    // input doesn't exist. The UTF-8 encoder outputs the replacement character
-    // unconditionally.
     mUtf16Lead = 0;
+    if (mSignal) {
+      *aDestLength = 0;
+      return NS_ERROR_UENC_NOMAPPING;
+    }
     *out = '?';
     *aDestLength = 1;
     return NS_OK;
   }
   *aDestLength = 0;
   return NS_OK;
 }
 
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -9,16 +9,17 @@
 
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/net/WebSocketFrame.h"
 #include "mozilla/TimeStamp.h"
 #ifdef XP_WIN
 #include "mozilla/TimeStamp_windows.h"
 #endif
 #include "mozilla/TypeTraits.h"
 #include "mozilla/IntegerTypeTraits.h"
 
 #include <stdint.h>
@@ -714,16 +715,32 @@ struct ParamTraits<mozilla::dom::ipc::St
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     LogParam(aParam.DataLength(), aLog);
   }
 };
 
 template <>
+struct ParamTraits<mozilla::net::WebSocketFrameData>
+{
+  typedef mozilla::net::WebSocketFrameData paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    aParam.WriteIPCParams(aMsg);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return aResult->ReadIPCParams(aMsg, aIter);
+  }
+};
+
+template <>
 struct ParamTraits<mozilla::SerializedStructuredCloneBuffer>
 {
   typedef mozilla::SerializedStructuredCloneBuffer paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.dataLength);
     if (aParam.dataLength) {
--- a/js/examples/jorendb.js
+++ b/js/examples/jorendb.js
@@ -57,17 +57,17 @@ Debugger.Frame.prototype.frameDescriptio
         return ((this.callee.name || '<anonymous>') +
                 "(" + this.arguments.map(dvToString).join(", ") + ")");
     else
         return this.type + " code";
 }
 
 Debugger.Frame.prototype.positionDescription = function positionDescription() {
     if (this.script) {
-        var line = this.script.getOffsetLine(this.offset);
+        var line = this.script.getOffsetLocation(this.offset).lineNumber;
         if (this.script.url)
             return this.script.url + ":" + line;
         return "line " + line;
     }
     return null;
 }
 
 Debugger.Frame.prototype.fullDescription = function fullDescription() {
@@ -78,17 +78,17 @@ Debugger.Frame.prototype.fullDescription
     return fr;
 }
 
 Object.defineProperty(Debugger.Frame.prototype, "line", {
         configurable: true,
         enumerable: false,
         get: function() {
             if (this.script)
-                return this.script.getOffsetLine(this.offset);
+                return this.script.getOffsetLocation(this.offset).lineNumber;
             else
                 return null;
         }
     });
 
 function callDescription(f) {
     return ((f.callee.name || '<anonymous>') +
             "(" + f.arguments.map(dvToString).join(", ") + ")");
--- a/js/src/doc/Debugger/Debugger.Script.md
+++ b/js/src/doc/Debugger/Debugger.Script.md
@@ -204,19 +204,24 @@ methods of other kinds of objects.
      { lineNumber: 3, columnNumber: 4, offset: 10 }]
     ```
 
 <code>getLineOffsets(<i>line</i>)</code>
 :   Return an array of bytecode instruction offsets representing the entry
     points to source line <i>line</i>. If the script contains no executable
     code at that line, the array returned is empty.
 
-<code>getOffsetLine(<i>offset</i>)</code>
-:   Return the source code line responsible for the bytecode at
-    <i>offset</i> in this script.
+<code>getOffsetLocation(<i>offset</i>)</code>
+:   Return an object describing the source code location responsible for the
+    bytecode at <i>offset</i> in this script.  The object has the
+    following properties:
+
+    * lineNumber: the line number for which offset is an entry point
+
+    * columnNumber: the column number for which offset is an entry point
 
 `getOffsetsCoverage()`:
 :   Return `null` or an array which contains informations about the coverage of
     all opcodes. The elements of the array are objects, each of which describes
     a single opcode, and contains the following properties:
 
     * lineNumber: the line number of the current opcode.
 
--- a/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
+++ b/js/src/jit-test/tests/debug/Frame-onStep-lines-01.js
@@ -8,21 +8,21 @@ var dbg = new Debugger(g);
 // single-stepping the current frame; for each source line we hit, delete
 // the line's entry in offsets. Thus, at the end, offsets is an array with
 // an element for each line we did not reach.
 var doSingleStep = true;
 var offsets;
 dbg.onDebuggerStatement = function (frame) {
     var script = frame.script;
     offsets = script.getAllOffsets();
-    print("debugger line: " + script.getOffsetLine(frame.offset));
+    print("debugger line: " + script.getOffsetLocation(frame.offset).lineNumber);
     print("original lines: " + uneval(Object.keys(offsets)));
     if (doSingleStep) {
 	frame.onStep = function onStepHandler() {
-	    var line = script.getOffsetLine(this.offset);
+	    var line = script.getOffsetLocation(this.offset).lineNumber;
 	    delete offsets[line];
 	};
     }
 };
 
 g.eval(
        'function t(a, b, c) {                \n' +
        '    debugger;                        \n' +
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-02.js
@@ -7,17 +7,17 @@ dbg.onDebuggerStatement = function (fram
         return {hit: function (frame) { g.log += "" + line; }};
     }
 
     var s = frame.eval("f").return.script;
     for (var line = 2; line <= 6; line++) {
         var offs = s.getLineOffsets(g.line0 + line);
         var h = handler(line);
         for (var i = 0; i < offs.length; i++) {
-            assertEq(s.getOffsetLine(offs[i]), g.line0 + line);
+            assertEq(s.getOffsetLocation(offs[i]).lineNumber, g.line0 + line);
             s.setBreakpoint(offs[i], h);
         }
     }
 };
 
 g.log = '';
 g.eval("var line0 = Error().lineNumber;\n" +
        "function f(n) {\n" +        // line0 + 1
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-03.js
@@ -5,17 +5,17 @@ var g = newGlobal();
 g.line0 = null;
 var dbg = Debugger(g);
 var log;
 dbg.onDebuggerStatement = function (frame) {
     var s = frame.script;
     var lineno = g.line0 + 2;
     var offs = s.getLineOffsets(lineno);
     for (var i = 0; i < offs.length; i++) {
-        assertEq(s.getOffsetLine(offs[i]), lineno);
+        assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
         s.setBreakpoint(offs[i], {hit: function () { log += 'B'; }});
     }
     log += 'A';
 };
 
 function test(s) {
     log = '';
     g.eval("line0 = Error().lineNumber;\n" +
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-04.js
@@ -4,17 +4,17 @@ var g = newGlobal();
 g.line0 = null;
 var dbg = Debugger(g);
 var where;
 dbg.onDebuggerStatement = function (frame) {
     var s = frame.eval("f").return.script;
     var lineno = g.line0 + where;
     var offs = s.getLineOffsets(lineno);
     for (var i = 0; i < offs.length; i++) {
-        assertEq(s.getOffsetLine(offs[i]), lineno);
+        assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
         s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }});
     }
     g.log += 'A';
 };
 
 function test(s) {
     var count = (s.split(/\n/).length - 1); // number of newlines in s
     g.log = '';
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-05.js
@@ -5,24 +5,24 @@ g.line0 = null;
 var dbg = Debugger(g);
 var where;
 dbg.onDebuggerStatement = function (frame) {
     var s = frame.script, lineno, offs;
 
     lineno = g.line0 + where;
     offs = s.getLineOffsets(lineno);
     for (var i = 0; i < offs.length; i++) {
-        assertEq(s.getOffsetLine(offs[i]), lineno);
+        assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
         s.setBreakpoint(offs[i], {hit: function () { g.log += 'B'; }});
     }
 
     lineno++;
     offs = s.getLineOffsets(lineno);
     for (var i = 0; i < offs.length; i++) {
-        assertEq(s.getOffsetLine(offs[i]), lineno);
+        assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
         s.setBreakpoint(offs[i], {hit: function () { g.log += 'C'; }});
     }
 
     g.log += 'A';
 };
 
 function test(s) {
     assertEq(s.charAt(s.length - 1), '\n');
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js
+++ b/js/src/jit-test/tests/debug/Script-getLineOffsets-06.js
@@ -5,17 +5,17 @@ g.line0 = null;
 var dbg = Debugger(g);
 var where;
 dbg.onDebuggerStatement = function (frame) {
     var s = frame.script;
     var offs;
     var lineno = g.line0 + where;
     offs = s.getLineOffsets(lineno);
     for (var i = 0; i < offs.length; i++) {
-        assertEq(s.getOffsetLine(offs[i]), lineno);
+        assertEq(s.getOffsetLocation(offs[i]).lineNumber, lineno);
         s.setBreakpoint(offs[i], {hit: function (frame) { g.log += 'B'; }});
     }
     g.log += 'A';
 };
 
 function test(s) {
     assertEq(s.charAt(s.length - 1) !== '\n', true);
     var count = s.split(/\n/).length;  // number of lines in s
--- a/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-01.js
@@ -1,16 +1,16 @@
-// Basic getOffsetLine test, using Error.lineNumber as the gold standard.
+// Basic getOffsetLocation test, using Error.lineNumber as the gold standard.
 
 var g = newGlobal();
 var dbg = Debugger(g);
 var hits;
 dbg.onDebuggerStatement = function (frame) {
     var knownLine = frame.eval("line").return;
-    assertEq(frame.script.getOffsetLine(frame.offset), knownLine);
+    assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, knownLine);
     hits++;
 };
 
 hits = 0;
 g.eval("var line = new Error().lineNumber; debugger;");
 assertEq(hits, 1);
 
 hits = 0;
--- a/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLine-02.js
@@ -1,17 +1,17 @@
-// Frame.script/offset and Script.getOffsetLine work in non-top frames.
+// Frame.script/offset and Script.getOffsetLocation work in non-top frames.
 
 var g = newGlobal();
 var dbg = Debugger(g);
 var hits = 0;
 dbg.onDebuggerStatement = function (frame) {
     var a = [];
     for (; frame.type == "call"; frame = frame.older)
-        a.push(frame.script.getOffsetLine(frame.offset) - g.line0);
+        a.push(frame.script.getOffsetLocation(frame.offset).lineNumber - g.line0);
     assertEq(a.join(","), "1,2,3,4");
     hits++;
 };
 g.eval("var line0 = Error().lineNumber;\n" +
        "function f0() { debugger; }\n" +
        "function f1() { f0(); }\n" +
        "function f2() { f1(); }\n" +
        "function f3() { f2(); }\n" +
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-getOffsetLocation.js
@@ -0,0 +1,27 @@
+// getOffsetLocation agrees with getAllColumnOffsets
+
+var global = newGlobal();
+Debugger(global).onDebuggerStatement = function (frame) {
+    var script = frame.eval("f").return.script;
+    script.getAllColumnOffsets().forEach(function (entry) {
+        var {lineNumber, columnNumber, offset} = entry;
+        var location = script.getOffsetLocation(offset);
+        assertEq(location.lineNumber, lineNumber);
+        assertEq(location.columnNumber, columnNumber);
+    });
+};
+
+function test(body) {
+  print("Test: " + body);
+  global.eval(`function f(n) { ${body} } debugger;`);
+  global.f(3);
+}
+
+test("for (var i = 0; i < n; ++i) ;");
+test("var w0,x1=3,y2=4,z3=9");
+test("print(n),print(n),print(n)");
+test("var o={a:1,b:2,c:3}");
+test("var a=[1,2,n]");
+
+global.eval("function ppppp() { return 1; }");
+test("1 && ppppp(ppppp()) && new Error()");
--- a/js/src/jit-test/tests/debug/Source-introductionScript-01.js
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-01.js
@@ -27,17 +27,17 @@ dbg.onDebuggerStatement = function (fram
     log += 'de2';
     introduced = frame.script.source;
   };
 };
 log = '';
 g.evaluate('debugger; eval("\\n\\ndebugger;");', { lineNumber: 1812 });
 assertEq(log, 'de1de2');
 assertEq(introduced.introductionScript, introducer);
-assertEq(introducer.getOffsetLine(introduced.introductionOffset), 1812);
+assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1812);
 
 // Indirect eval, while the frame is live.
 dbg.onDebuggerStatement = function (frame) {
   log += 'd';
   var source = frame.script.source;
   var introducer = frame.older;
   assertEq(source.introductionScript, introducer.script);
   assertEq(source.introductionOffset, introducer.offset);
@@ -55,29 +55,29 @@ dbg.onDebuggerStatement = function (fram
     log += 'de2';
     introduced = frame.script.source;
   };
 };
 log = '';
 g.evaluate('debugger; (0,eval)("\\n\\ndebugger;");', { lineNumber: 1066 });
 assertEq(log, 'de1de2');
 assertEq(introduced.introductionScript, introducer);
-assertEq(introducer.getOffsetLine(introduced.introductionOffset), 1066);
+assertEq(introducer.getOffsetLocation(introduced.introductionOffset).lineNumber, 1066);
 
 // Function constructor.
 dbg.onDebuggerStatement = function (frame) {
   log += 'o';
   var outerScript = frame.script;
   var outerOffset = frame.offset;
   dbg.onDebuggerStatement = function (frame) {
     log += 'i';
     var source = frame.script.source;
     assertEq(source.introductionScript, outerScript);
-    assertEq(outerScript.getOffsetLine(source.introductionOffset),
-             outerScript.getOffsetLine(outerOffset));
+    assertEq(outerScript.getOffsetLocation(source.introductionOffset).lineNumber,
+             outerScript.getOffsetLocation(outerOffset).lineNumber);
   };
 };
 log = '';
 g.eval('\n\n\ndebugger; Function("debugger;")()');
 assertEq(log, 'oi');
 
 // Function constructor, after the the introduction call's frame has been
 // popped.
@@ -86,17 +86,17 @@ dbg.onDebuggerStatement = function (fram
   log += 'F2';
   introducer = frame.script;
 };
 log = '';
 var fDO = gDO.executeInGlobal('debugger; Function("origami;")', { lineNumber: 1685 }).return;
 var source = fDO.script.source;
 assertEq(log, 'F2');
 assertEq(source.introductionScript, introducer);
-assertEq(introducer.getOffsetLine(source.introductionOffset), 1685);
+assertEq(introducer.getOffsetLocation(source.introductionOffset).lineNumber, 1685);
 
 // If the introduction script is in a different global from the script it
 // introduced, we don't record it.
 dbg.onDebuggerStatement = function (frame) {
   log += 'x';
   var source = frame.script.source;
   assertEq(source.introductionScript, undefined);
   assertEq(source.introductionOffset, undefined);
--- a/js/src/jit-test/tests/debug/Source-introductionScript-02.js
+++ b/js/src/jit-test/tests/debug/Source-introductionScript-02.js
@@ -10,17 +10,17 @@ function outerHandler(frame) {
   log += 'o';
   var outerScript = frame.script;
 
   dbg.onDebuggerStatement = function (frame) {
     log += 'i';
     var source = frame.script.source;
     var introScript = source.introductionScript;
     assertEq(introScript, outerScript);
-    assertEq(introScript.getOffsetLine(source.introductionOffset), 1234);
+    assertEq(introScript.getOffsetLocation(source.introductionOffset).lineNumber, 1234);
   };
 };
 
 log = '';
 dbg.onDebuggerStatement = outerHandler;
 g.evaluate('debugger; ["debugger;"].map(eval)', { lineNumber: 1234 });
 assertEq(log, 'oi');
 
--- a/js/src/jit-test/tests/debug/breakpoint-01.js
+++ b/js/src/jit-test/tests/debug/breakpoint-01.js
@@ -6,17 +6,17 @@ var handler = {
     hit: function (frame) {
         assertEq(this, handler);
         g.s += '1';
     }
 };
 var dbg = Debugger(g);
 dbg.onDebuggerStatement = function (frame) {
     g.s += '0';
-    var line0 = frame.script.getOffsetLine(frame.offset);
+    var line0 = frame.script.getOffsetLocation(frame.offset).lineNumber;
     var offs = frame.script.getLineOffsets(line0 + 2);
     for (var i = 0; i < offs.length; i++)
         frame.script.setBreakpoint(offs[i], handler);
 };
 g.eval("debugger;\n" +
        "s += 'a';\n" +  // line0 + 1
        "s += 'b';\n");  // line0 + 2
 assertEq(g.s, "0a1b");
--- a/js/src/jit-test/tests/debug/surfaces-offsets.js
+++ b/js/src/jit-test/tests/debug/surfaces-offsets.js
@@ -1,37 +1,37 @@
 // Invalid offsets result in exceptions, not bogus results.
 
 load(libdir + "asserts.js");
 
 var g = newGlobal();
 var dbg = Debugger(g);
 var hits;
 dbg.onDebuggerStatement = function (frame) {
-    assertEq(frame.script.getOffsetLine(frame.offset), g.line);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(String(frame.offset)); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(Object(frame.offset)); }, Error);
+    assertEq(frame.script.getOffsetLocation(frame.offset).lineNumber, g.line);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(String(frame.offset)).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(Object(frame.offset)).lineNumber; }, Error);
 
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(-1); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(1000000); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(0.25); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(+Infinity); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(-Infinity); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(NaN); }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-1).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(1000000).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(0.25).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(+Infinity).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(-Infinity).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(NaN).lineNumber; }, Error);
 
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(false); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(true); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(undefined); }, Error);
-    assertThrowsInstanceOf(function () { frame.script.getOffsetLine(); }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(false).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(true).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation(undefined).lineNumber; }, Error);
+    assertThrowsInstanceOf(function () { frame.script.getOffsetLocation().lineNumber; }, Error);
 
     // We assume that at least one whole number between 0 and frame.offset is invalid.
     assertThrowsInstanceOf(
         function () {
             for (var i = 0; i < frame.offset; i++)
-                frame.script.getOffsetLine(i);
+                frame.script.getOffsetLocation(i).lineNumber;
         },
         Error);
 
     hits++;
 };
 
 hits = 0;
 g.eval("var line = new Error().lineNumber; debugger;");
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8475,33 +8475,23 @@ CodeGenerator::addGetPropertyCache(LInst
 {
     GetPropertyIC cache(liveRegs, objReg, id, output, monitoredResult, allowDoubleResult);
     cache.setProfilerLeavePC(profilerLeavePc);
     addCache(ins, allocateCache(cache));
 }
 
 void
 CodeGenerator::addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
-                                   Register tempReg, PropertyName* name, ConstantOrRegister value,
-                                   bool strict, bool needsTypeBarrier, jsbytecode* profilerLeavePc)
-{
-    SetPropertyIC cache(liveRegs, objReg, tempReg, name, value, strict, needsTypeBarrier);
-    cache.setProfilerLeavePC(profilerLeavePc);
-    addCache(ins, allocateCache(cache));
-}
-
-void
-CodeGenerator::addSetElementCache(LInstruction* ins, Register obj, Register unboxIndex,
-                                  Register temp, FloatRegister tempDouble,
-                                  FloatRegister tempFloat32, ValueOperand index,
-                                  ConstantOrRegister value, bool strict, bool guardHoles,
-                                  jsbytecode* profilerLeavePc)
-{
-    SetElementIC cache(obj, unboxIndex, temp, tempDouble, tempFloat32, index, value, strict,
-                       guardHoles);
+                                   Register temp, Register tempUnbox, FloatRegister tempDouble,
+                                   FloatRegister tempF32, ConstantOrRegister id, ConstantOrRegister value,
+                                   bool strict, bool needsTypeBarrier, bool guardHoles,
+                                   jsbytecode* profilerLeavePc)
+{
+    SetPropertyIC cache(liveRegs, objReg, temp, tempUnbox, tempDouble, tempF32, id, value, strict,
+                        needsTypeBarrier, guardHoles);
     cache.setProfilerLeavePC(profilerLeavePc);
     addCache(ins, allocateCache(cache));
 }
 
 ConstantOrRegister
 CodeGenerator::toConstantOrRegister(LInstruction* lir, size_t n, MIRType type)
 {
     if (type == MIRType_Value)
@@ -8565,74 +8555,16 @@ CodeGenerator::visitGetPropertyIC(OutOfL
     callVM(GetPropertyIC::UpdateInfo, lir);
     StoreValueTo(ic->output()).generate(this);
     restoreLiveIgnore(lir, StoreValueTo(ic->output()).clobbered());
 
     masm.jump(ool->rejoin());
 }
 
 void
-CodeGenerator::visitSetElementCacheV(LSetElementCacheV* ins)
-{
-    Register obj = ToRegister(ins->object());
-    Register unboxIndex = ToTempUnboxRegister(ins->tempToUnboxIndex());
-    Register temp = ToRegister(ins->temp());
-    FloatRegister tempDouble = ToFloatRegister(ins->tempDouble());
-    FloatRegister tempFloat32 = ToFloatRegister(ins->tempFloat32());
-    ValueOperand index = ToValue(ins, LSetElementCacheV::Index);
-    ConstantOrRegister value = TypedOrValueRegister(ToValue(ins, LSetElementCacheV::Value));
-
-    addSetElementCache(ins, obj, unboxIndex, temp, tempDouble, tempFloat32, index, value,
-                       ins->mir()->strict(), ins->mir()->guardHoles(),
-                       ins->mir()->profilerLeavePc());
-}
-
-void
-CodeGenerator::visitSetElementCacheT(LSetElementCacheT* ins)
-{
-    Register obj = ToRegister(ins->object());
-    Register unboxIndex = ToTempUnboxRegister(ins->tempToUnboxIndex());
-    Register temp = ToRegister(ins->temp());
-    FloatRegister tempDouble = ToFloatRegister(ins->tempDouble());
-    FloatRegister tempFloat32 = ToFloatRegister(ins->tempFloat32());
-    ValueOperand index = ToValue(ins, LSetElementCacheT::Index);
-    ConstantOrRegister value;
-    const LAllocation* tmp = ins->value();
-    if (tmp->isConstant())
-        value = *tmp->toConstant();
-    else
-        value = TypedOrValueRegister(ins->mir()->value()->type(), ToAnyRegister(tmp));
-
-    addSetElementCache(ins, obj, unboxIndex, temp, tempDouble, tempFloat32, index, value,
-                       ins->mir()->strict(), ins->mir()->guardHoles(),
-                       ins->mir()->profilerLeavePc());
-}
-
-typedef bool (*SetElementICFn)(JSContext*, HandleScript, size_t, HandleObject, HandleValue,
-                               HandleValue);
-const VMFunction SetElementIC::UpdateInfo = FunctionInfo<SetElementICFn>(SetElementIC::update);
-
-void
-CodeGenerator::visitSetElementIC(OutOfLineUpdateCache* ool, DataPtr<SetElementIC>& ic)
-{
-    LInstruction* lir = ool->lir();
-    saveLive(lir);
-
-    pushArg(ic->value());
-    pushArg(ic->index());
-    pushArg(ic->object());
-    pushArg(Imm32(ool->getCacheIndex()));
-    pushArg(ImmGCPtr(gen->info().script()));
-    callVM(SetElementIC::UpdateInfo, lir);
-    restoreLive(lir);
-
-    masm.jump(ool->rejoin());
-}
-
-void
 CodeGenerator::visitBindNameCache(LBindNameCache* ins)
 {
     Register scopeChain = ToRegister(ins->scopeChain());
     Register output = ToRegister(ins->output());
     BindNameIC cache(scopeChain, ins->mir()->name(), output);
     cache.setProfilerLeavePC(ins->mir()->profilerLeavePc());
 
     addCache(ins, allocateCache(cache));
@@ -8714,35 +8646,43 @@ CodeGenerator::visitCallDeleteElement(LC
         callVM(DeleteElementNonStrictInfo, lir);
 }
 
 void
 CodeGenerator::visitSetPropertyCache(LSetPropertyCache* ins)
 {
     LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
     Register objReg = ToRegister(ins->getOperand(0));
-    Register tempReg = ToRegister(ins->getTemp(0));
+    Register temp = ToRegister(ins->temp());
+    Register tempUnbox = ToTempUnboxRegister(ins->tempToUnboxIndex());
+    FloatRegister tempDouble = ToTempFloatRegisterOrInvalid(ins->tempDouble());
+    FloatRegister tempF32 = ToTempFloatRegisterOrInvalid(ins->tempFloat32());
+
+    ConstantOrRegister id =
+        toConstantOrRegister(ins, LSetPropertyCache::Id, ins->mir()->idval()->type());
     ConstantOrRegister value =
         toConstantOrRegister(ins, LSetPropertyCache::Value, ins->mir()->value()->type());
 
-    addSetPropertyCache(ins, liveRegs, objReg, tempReg, ins->mir()->name(), value,
-                        ins->mir()->strict(), ins->mir()->needsTypeBarrier(),
-                        ins->mir()->profilerLeavePc());
-}
-
-typedef bool (*SetPropertyICFn)(JSContext*, HandleScript, size_t, HandleObject, HandleValue);
+    addSetPropertyCache(ins, liveRegs, objReg, temp, tempUnbox, tempDouble, tempF32,
+                        id, value, ins->mir()->strict(), ins->mir()->needsTypeBarrier(),
+                        ins->mir()->guardHoles(), ins->mir()->profilerLeavePc());
+}
+
+typedef bool (*SetPropertyICFn)(JSContext*, HandleScript, size_t, HandleObject, HandleValue,
+                                HandleValue);
 const VMFunction SetPropertyIC::UpdateInfo = FunctionInfo<SetPropertyICFn>(SetPropertyIC::update);
 
 void
 CodeGenerator::visitSetPropertyIC(OutOfLineUpdateCache* ool, DataPtr<SetPropertyIC>& ic)
 {
     LInstruction* lir = ool->lir();
     saveLive(lir);
 
     pushArg(ic->value());
+    pushArg(ic->id());
     pushArg(ic->object());
     pushArg(Imm32(ool->getCacheIndex()));
     pushArg(ImmGCPtr(gen->info().script()));
     callVM(SetPropertyIC::UpdateInfo, lir);
     restoreLive(lir);
 
     masm.jump(ool->rejoin());
 }
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -352,26 +352,23 @@ class CodeGenerator : public CodeGenerat
     void loadJSScriptForBlock(MBasicBlock* block, Register reg);
     void loadOutermostJSScript(Register reg);
 
     // Inline caches visitors.
     void visitOutOfLineCache(OutOfLineUpdateCache* ool);
 
     void visitGetPropertyCacheV(LGetPropertyCacheV* ins);
     void visitGetPropertyCacheT(LGetPropertyCacheT* ins);
-    void visitSetElementCacheV(LSetElementCacheV* ins);
-    void visitSetElementCacheT(LSetElementCacheT* ins);
     void visitBindNameCache(LBindNameCache* ins);
     void visitCallSetProperty(LInstruction* ins);
     void visitSetPropertyCache(LSetPropertyCache* ins);
     void visitGetNameCache(LGetNameCache* ins);
 
     void visitGetPropertyIC(OutOfLineUpdateCache* ool, DataPtr<GetPropertyIC>& ic);
     void visitSetPropertyIC(OutOfLineUpdateCache* ool, DataPtr<SetPropertyIC>& ic);
-    void visitSetElementIC(OutOfLineUpdateCache* ool, DataPtr<SetElementIC>& ic);
     void visitBindNameIC(OutOfLineUpdateCache* ool, DataPtr<BindNameIC>& ic);
     void visitNameIC(OutOfLineUpdateCache* ool, DataPtr<NameIC>& ic);
 
     void visitAssertRangeI(LAssertRangeI* ins);
     void visitAssertRangeD(LAssertRangeD* ins);
     void visitAssertRangeF(LAssertRangeF* ins);
     void visitAssertRangeV(LAssertRangeV* ins);
 
@@ -394,22 +391,20 @@ class CodeGenerator : public CodeGenerat
     }
 
   private:
     void addGetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
                              ConstantOrRegister id, TypedOrValueRegister output,
                              bool monitoredResult, bool allowDoubleResult,
                              jsbytecode* profilerLeavePc);
     void addSetPropertyCache(LInstruction* ins, LiveRegisterSet liveRegs, Register objReg,
-                             Register tempReg, PropertyName* name, ConstantOrRegister value,
-                             bool strict, bool needsTypeBarrier, jsbytecode* profilerLeavePc);
-    void addSetElementCache(LInstruction* ins, Register obj, Register unboxIndex, Register temp,
-                            FloatRegister tempDouble, FloatRegister tempFloat32,
-                            ValueOperand index, ConstantOrRegister value,
-                            bool strict, bool guardHoles, jsbytecode* profilerLeavePc);
+                             Register temp, Register tempUnbox, FloatRegister tempDouble,
+                             FloatRegister tempF32, ConstantOrRegister id, ConstantOrRegister value,
+                             bool strict, bool needsTypeBarrier, bool guardHoles,
+                             jsbytecode* profilerLeavePc);
 
     bool generateBranchV(const ValueOperand& value, Label* ifTrue, Label* ifFalse, FloatRegister fr);
 
     void emitLambdaInit(Register resultReg, Register scopeChainReg,
                         const LambdaFunctionInfo& info);
 
     void emitFilterArgumentsOrEval(LInstruction* lir, Register string, Register temp1,
                                    Register temp2);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -9808,30 +9808,29 @@ IonBuilder::setElemTryCache(bool* emitte
     if (!index->mightBeType(MIRType_Int32) &&
         !index->mightBeType(MIRType_String) &&
         !index->mightBeType(MIRType_Symbol))
     {
         trackOptimizationOutcome(TrackedOutcome::IndexType);
         return true;
     }
 
-    // TODO: Bug 876650: remove this check:
-    // Temporary disable the cache if non dense native,
-    // until the cache supports more ics
-    SetElemICInspector icInspect(inspector->setElemICInspector(pc));
-    if (!icInspect.sawDenseWrite() && !icInspect.sawTypedArrayWrite()) {
-        trackOptimizationOutcome(TrackedOutcome::SetElemNonDenseNonTANotCached);
-        return true;
-    }
-
-    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
-                                      &object, nullptr, &value, /* canModify = */ true))
-    {
-        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
-        return true;
+    bool barrier = true;
+
+    if (index->mightBeType(MIRType_Int32)) {
+        // Bail if we might have a barriered write to a dense element, as the
+        // dense element stub doesn't support this yet.
+        if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
+                                          &object, nullptr, &value, /* canModify = */ true))
+        {
+            trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
+            return true;
+        }
+        if (index->type() == MIRType_Int32)
+            barrier = false;
     }
 
     // We can avoid worrying about holes in the IC if we know a priori we are safe
     // from them. If TI can guard that there are no indexed properties on the prototype
     // chain, we know that we anen't missing any setters by overwriting the hole with
     // another value.
     bool guardHoles = ElementAccessHasExtraIndexedProperty(this, object);
 
@@ -9840,17 +9839,18 @@ IonBuilder::setElemTryCache(bool* emitte
     bool checkNative = !clasp || !clasp->isNative();
     object = addMaybeCopyElementsForWrite(object, checkNative);
 
     if (NeedsPostBarrier(info(), value))
         current->add(MPostWriteBarrier::New(alloc(), object, value));
 
     // Emit SetElementCache.
     bool strict = JSOp(*pc) == JSOP_STRICTSETELEM;
-    MInstruction* ins = MSetElementCache::New(alloc(), object, index, value, strict, guardHoles);
+    MSetPropertyCache* ins =
+        MSetPropertyCache::New(alloc(), object, index, value, strict, barrier, guardHoles);
     current->add(ins);
     current->push(value);
 
     if (!resumeAfter(ins))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
@@ -12469,17 +12469,19 @@ bool
 IonBuilder::setPropTryCache(bool* emitted, MDefinition* obj,
                             PropertyName* name, MDefinition* value,
                             bool barrier, TemporaryTypeSet* objTypes)
 {
     MOZ_ASSERT(*emitted == false);
 
     bool strict = IsStrictSetPC(pc);
 
-    MSetPropertyCache* ins = MSetPropertyCache::New(alloc(), obj, value, name, strict, barrier);
+    MConstant* id = constant(StringValue(name));
+    MSetPropertyCache* ins = MSetPropertyCache::New(alloc(), obj, id, value, strict, barrier,
+                                                    /* guardHoles = */ false);
     current->add(ins);
     current->push(value);
 
     if (!resumeAfter(ins))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -1348,92 +1348,105 @@ EqualStringsHelper(JSString* str1, JSStr
 
     JSLinearString* str2Linear = str2->ensureLinear(nullptr);
     if (!str2Linear)
         return false;
 
     return EqualChars(&str1->asLinear(), str2Linear);
 }
 
-void
-GetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
+static void
+EmitIdGuard(MacroAssembler& masm, jsid id, TypedOrValueRegister idReg, Register objReg,
+            Register scratchReg, Label* failures)
 {
     MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
 
-    if (this->id().constant())
-        return;
-
-    TypedOrValueRegister idReg = this->id().reg();
     MOZ_ASSERT(idReg.type() == MIRType_String ||
                idReg.type() == MIRType_Symbol ||
                idReg.type() == MIRType_Value);
 
-    Register scratch = output().valueReg().scratchReg();
-
     Register payloadReg;
     if (idReg.type() == MIRType_Value) {
         ValueOperand val = idReg.valueReg();
         if (JSID_IS_SYMBOL(id)) {
-            masm.branchTestSymbol(Assembler::NotEqual, val, fail);
+            masm.branchTestSymbol(Assembler::NotEqual, val, failures);
         } else {
             MOZ_ASSERT(JSID_IS_STRING(id));
-            masm.branchTestString(Assembler::NotEqual, val, fail);
+            masm.branchTestString(Assembler::NotEqual, val, failures);
         }
-        masm.unboxNonDouble(val, scratch);
-        payloadReg = scratch;
+        masm.unboxNonDouble(val, scratchReg);
+        payloadReg = scratchReg;
     } else {
         payloadReg = idReg.typedReg().gpr();
     }
 
     if (JSID_IS_SYMBOL(id)) {
         // For symbols, we can just do a pointer comparison.
-        masm.branchPtr(Assembler::NotEqual, payloadReg, ImmGCPtr(JSID_TO_SYMBOL(id)), fail);
+        masm.branchPtr(Assembler::NotEqual, payloadReg, ImmGCPtr(JSID_TO_SYMBOL(id)), failures);
     } else {
         PropertyName* name = JSID_TO_ATOM(id)->asPropertyName();
 
         Label equal;
         masm.branchPtr(Assembler::Equal, payloadReg, ImmGCPtr(name), &equal);
 
         // The pointers are not equal, so if the input string is also an atom it
         // must be a different string.
         masm.branchTest32(Assembler::NonZero, Address(payloadReg, JSString::offsetOfFlags()),
-                          Imm32(JSString::ATOM_BIT), fail);
+                          Imm32(JSString::ATOM_BIT), failures);
 
         // Check the length.
         masm.branch32(Assembler::NotEqual, Address(payloadReg, JSString::offsetOfLength()),
-                      Imm32(name->length()), fail);
+                      Imm32(name->length()), failures);
 
         // We have a non-atomized string with the same length. For now call a helper
         // function to do the comparison.
         LiveRegisterSet volatileRegs(RegisterSet::Volatile());
         masm.PushRegsInMask(volatileRegs);
 
-        Register objReg = object();
         if (!volatileRegs.has(objReg))
             masm.push(objReg);
 
         masm.setupUnalignedABICall(objReg);
         masm.movePtr(ImmGCPtr(name), objReg);
         masm.passABIArg(objReg);
         masm.passABIArg(payloadReg);
         masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, EqualStringsHelper));
-        masm.mov(ReturnReg, scratch);
+        masm.mov(ReturnReg, scratchReg);
 
         if (!volatileRegs.has(objReg))
             masm.pop(objReg);
 
         LiveRegisterSet ignore;
-        ignore.add(scratch);
+        ignore.add(scratchReg);
         masm.PopRegsInMaskIgnore(volatileRegs, ignore);
 
-        masm.branchIfFalseBool(scratch, fail);
+        masm.branchIfFalseBool(scratchReg, failures);
         masm.bind(&equal);
     }
 }
 
+void
+GetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
+{
+    if (this->id().constant())
+        return;
+
+    Register scratch = output().valueReg().scratchReg();
+    EmitIdGuard(masm, id, this->id().reg(), object(), scratch, fail);
+}
+
+void
+SetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
+{
+    if (this->id().constant())
+        return;
+
+    EmitIdGuard(masm, id, this->id().reg(), object(), temp(), fail);
+}
+
 bool
 GetPropertyIC::allowArrayLength(JSContext* cx) const
 {
     if (!idempotent())
         return true;
 
     uint32_t locationIndex, numLocations;
     getLocationInfo(&locationIndex, &numLocations);
@@ -2037,37 +2050,53 @@ GetPropertyIC::tryAttachArgumentsLength(
     }
 
     MOZ_ASSERT(!hasMappedArgumentsLengthStub_);
     hasMappedArgumentsLengthStub_ = true;
     return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (mapped)",
                                  JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
 }
 
+static bool
+ValueToNameOrSymbolId(JSContext* cx, HandleValue idval, MutableHandleId id, bool* nameOrSymbol)
+{
+    *nameOrSymbol = false;
+
+    if (!idval.isString() && !idval.isSymbol())
+        return true;
+
+    if (!ValueToId<CanGC>(cx, idval, id))
+        return false;
+
+    if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id))
+        return true;
+
+    uint32_t dummy;
+    if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy))
+        return true;
+
+    *nameOrSymbol = true;
+    return true;
+}
+
 bool
 GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
                              HandleObject obj, HandleValue idval, bool* emitted)
 {
     MOZ_ASSERT(!*emitted);
 
     if (!canAttachStub())
         return true;
 
-    if (idval.isString() || idval.isSymbol()) {
-        RootedId id(cx);
-        if (!ValueToId<CanGC>(cx, idval, &id))
-            return false;
-
-        if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id))
-            return true;
-
-        uint32_t dummy;
-        if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy))
-            return true;
-
+    RootedId id(cx);
+    bool nameOrSymbol;
+    if (!ValueToNameOrSymbolId(cx, idval, &id, &nameOrSymbol))
+        return false;
+
+    if (nameOrSymbol) {
         if (!*emitted && !tryAttachArgumentsLength(cx, outerScript, ion, obj, id, emitted))
             return false;
 
         void* returnAddr = GetReturnAddressToIonCode(cx);
 
         if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, returnAddr, emitted))
             return false;
 
@@ -2254,29 +2283,28 @@ CheckTypeSetForWrite(MacroAssembler& mas
     TypeSet::readBarrier(propTypes);
 
     masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch, failure);
 }
 
 static void
 GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                 JSObject* obj, Shape* shape, Register object, Register tempReg,
-                ConstantOrRegister value, bool needsTypeBarrier, bool checkTypeset)
+                ConstantOrRegister value, bool needsTypeBarrier, bool checkTypeset,
+                Label* failures)
 {
-    Label failures;
-
-    TestMatchingReceiver(masm, attacher, object, obj, &failures, needsTypeBarrier);
+    TestMatchingReceiver(masm, attacher, object, obj, failures, needsTypeBarrier);
 
     // Guard that the incoming value is in the type set for the property
     // if a type barrier is required.
     if (needsTypeBarrier) {
         // We can't do anything that would change the HeapTypeSet, so
         // just guard that it's already there.
         if (checkTypeset)
-            CheckTypeSetForWrite(masm, obj, shape->propid(), tempReg, value, &failures);
+            CheckTypeSetForWrite(masm, obj, shape->propid(), tempReg, value, failures);
     }
 
     NativeObject::slotsSizeMustNotOverflow();
 
     if (obj->is<UnboxedPlainObject>()) {
         obj = obj->as<UnboxedPlainObject>().maybeExpando();
         masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
         object = tempReg;
@@ -2297,28 +2325,33 @@ GenerateSetSlot(JSContext* cx, MacroAsse
         if (cx->zone()->needsIncrementalBarrier())
             masm.callPreBarrier(addr, MIRType_Value);
 
         masm.storeConstantOrRegister(value, addr);
     }
 
     attacher.jumpRejoin(masm);
 
-    masm.bind(&failures);
+    masm.bind(failures);
     attacher.jumpNextStub(masm);
 }
 
 bool
 SetPropertyIC::attachSetSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                              HandleObject obj, HandleShape shape, bool checkTypeset)
 {
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
+
+    Label failures;
+    emitIdGuard(masm, shape->propid(), &failures);
+
     GenerateSetSlot(cx, masm, attacher, obj, shape, object(), temp(), value(), needsTypeBarrier(),
-                    checkTypeset);
+                    checkTypeset, &failures);
+
     return linkAndAttachStub(cx, masm, attacher, ion, "setting",
                              JS::TrackedOutcome::ICSetPropStub_Slot);
 }
 
 static bool
 IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape)
 {
     if (!shape || !IsCacheableProtoChainForIon(obj, holder))
@@ -2493,35 +2526,35 @@ EmitCallProxySet(JSContext* cx, MacroAss
     masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
 
     masm.icRestoreLive(liveRegs, aic);
     return true;
 }
 
 bool
 SetPropertyIC::attachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                                  void* returnAddr)
+                                  HandleId id, void* returnAddr)
 {
     MOZ_ASSERT(!hasGenericProxyStub());
 
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
 
     Label failures;
+    emitIdGuard(masm, id, &failures);
     {
         masm.branchTestObjectIsProxy(false, object(), temp(), &failures);
 
         // Remove the DOM proxies. They'll take care of themselves so this stub doesn't
         // catch too much. The failure case is actually Equal. Fall through to the failure code.
         masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), temp(),
                                           GetDOMProxyHandlerFamily(), &failures);
     }
 
-    RootedId propId(cx, AtomToId(name()));
-    if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(), value(),
+    if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(), value(),
                           returnAddr, strict()))
     {
         return false;
     }
 
     attacher.jumpRejoin(masm);
 
     masm.bind(&failures);
@@ -2531,35 +2564,36 @@ SetPropertyIC::attachGenericProxy(JSCont
     hasGenericProxyStub_ = true;
 
     return linkAndAttachStub(cx, masm, attacher, ion, "generic proxy set",
                              JS::TrackedOutcome::ICSetPropStub_GenericProxy);
 }
 
 bool
 SetPropertyIC::attachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                                      HandleObject obj, void* returnAddr)
+                                      HandleObject obj, HandleId id, void* returnAddr)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
 
+    emitIdGuard(masm, id, &failures);
+
     // Guard on the shape of the object.
     masm.branchPtr(Assembler::NotEqual,
                    Address(object(), JSObject::offsetOfShape()),
                    ImmGCPtr(obj->maybeShape()), &failures);
 
     // No need for more guards: we know this is a DOM proxy, since the shape
     // guard enforces a given JSClass, so just go ahead and emit the call to
     // ProxySet.
 
-    RootedId propId(cx, AtomToId(name()));
-    if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(),
+    if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
                           value(), returnAddr, strict()))
     {
         return false;
     }
 
     // Success.
     attacher.jumpRejoin(masm);
 
@@ -2781,68 +2815,68 @@ GenerateCallSetter(JSContext* cx, IonScr
         masm.freeStack(masm.framePushed() - framePushedBefore);
     }
 
     masm.icRestoreLive(liveRegs, aic);
     return true;
 }
 
 static bool
-IsCacheableDOMProxyUnshadowedSetterCall(JSContext* cx, HandleObject obj, HandlePropertyName name,
+IsCacheableDOMProxyUnshadowedSetterCall(JSContext* cx, HandleObject obj, HandleId id,
                                         MutableHandleObject holder, MutableHandleShape shape)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
     RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull());
     if (!checkObj)
         return false;
 
-    if (!LookupPropertyPure(cx, obj, NameToId(name), holder.address(), shape.address()))
+    if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
         return false;
 
     if (!holder)
         return false;
 
     return IsCacheableSetPropCallNative(checkObj, holder, shape) ||
            IsCacheableSetPropCallPropertyOp(checkObj, holder, shape) ||
            IsCacheableSetPropCallScripted(checkObj, holder, shape);
 }
 
 bool
 SetPropertyIC::attachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                                        HandleObject obj, void* returnAddr)
+                                        HandleObject obj, HandleId id, void* returnAddr)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
 
+    emitIdGuard(masm, id, &failures);
+
     // Guard on the shape of the object.
     masm.branchPtr(Assembler::NotEqual,
                    Address(object(), JSObject::offsetOfShape()),
                    ImmGCPtr(obj->maybeShape()), &failures);
 
     // Guard that our expando object hasn't started shadowing this property.
-    CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, NameToId(name()), object(), &failures);
-
-    RootedPropertyName propName(cx, name());
+    CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);
+
     RootedObject holder(cx);
     RootedShape shape(cx);
-    if (IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, propName, &holder, &shape)) {
+    if (IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, id, &holder, &shape)) {
         if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
                                 object(), temp(), value(), &failures, liveRegs_, returnAddr))
         {
             return false;
         }
     } else {
         // Either there was no proto, or the property wasn't appropriately found on it.
         // Drop back to just a call to Proxy::set().
-        RootedId propId(cx, AtomToId(name()));
-        if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(),
+        if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
                             value(), returnAddr, strict()))
         {
             return false;
         }
     }
 
     // Success.
     attacher.jumpRejoin(masm);
@@ -2859,16 +2893,17 @@ bool
 SetPropertyIC::attachCallSetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                 HandleObject obj, HandleObject holder, HandleShape shape,
                                 void* returnAddr)
 {
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
 
     Label failure;
+    emitIdGuard(masm, shape->propid(), &failure);
     TestMatchingReceiver(masm, attacher, object(), obj, &failure);
 
     if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
                             object(), temp(), value(), &failure, liveRegs_, returnAddr))
     {
         return false;
     }
 
@@ -2882,56 +2917,54 @@ SetPropertyIC::attachCallSetter(JSContex
     return linkAndAttachStub(cx, masm, attacher, ion, "setter call",
                              JS::TrackedOutcome::ICSetPropStub_CallSetter);
 }
 
 static void
 GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                 JSObject* obj, Shape* oldShape, ObjectGroup* oldGroup,
                 Register object, Register tempReg, ConstantOrRegister value,
-                bool checkTypeset)
+                bool checkTypeset, Label* failures)
 {
-    Label failures;
-
     // Use a modified version of TestMatchingReceiver that uses the old shape and group.
-    masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, &failures);
+    masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
     if (obj->maybeShape()) {
-        masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures);
+        masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
     } else {
         MOZ_ASSERT(obj->is<UnboxedPlainObject>());
 
         Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
-        masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), &failures);
+        masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);
 
         masm.loadPtr(expandoAddress, tempReg);
-        masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, &failures);
+        masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
     }
 
     Shape* newShape = obj->maybeShape();
     if (!newShape)
         newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
 
     // Guard that the incoming value is in the type set for the property
     // if a type barrier is required.
     if (checkTypeset)
-        CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, &failures);
+        CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
 
     // Guard shapes along prototype chain.
     JSObject* proto = obj->getProto();
     Register protoReg = tempReg;
     bool first = true;
     while (proto) {
         Shape* protoShape = proto->as<NativeObject>().lastProperty();
 
         // Load next prototype.
         masm.loadObjProto(first ? object : protoReg, protoReg);
         first = false;
 
         // Ensure that its shape matches.
-        masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, &failures);
+        masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, failures);
 
         proto = proto->getProto();
     }
 
     // Call a stub to (re)allocate dynamic slots, if necessary.
     uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
                                   ? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
                                   : obj->as<NativeObject>().numDynamicSlots();
@@ -2965,17 +2998,17 @@ GenerateAddSlot(JSContext* cx, MacroAsse
         Label allocFailed, allocDone;
         masm.branchIfFalseBool(ReturnReg, &allocFailed);
         masm.jump(&allocDone);
 
         masm.bind(&allocFailed);
         if (obj->is<UnboxedPlainObject>())
             masm.Pop(object);
         masm.PopRegsInMask(save);
-        masm.jump(&failures);
+        masm.jump(failures);
 
         masm.bind(&allocDone);
         masm.setFramePushed(framePushedAfterCall);
         if (obj->is<UnboxedPlainObject>())
             masm.Pop(object);
         masm.PopRegsInMask(save);
     }
 
@@ -3031,32 +3064,36 @@ GenerateAddSlot(JSContext* cx, MacroAsse
 
     if (popObject)
         masm.pop(object);
 
     // Success.
     attacher.jumpRejoin(masm);
 
     // Failure.
-    masm.bind(&failures);
+    masm.bind(failures);
 
     attacher.jumpNextStub(masm);
 }
 
 bool
 SetPropertyIC::attachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                             HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup,
-                             bool checkTypeset)
+                             HandleObject obj, HandleId id, HandleShape oldShape,
+                             HandleObjectGroup oldGroup, bool checkTypeset)
 {
     MOZ_ASSERT_IF(!needsTypeBarrier(), !checkTypeset);
 
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
+
+    Label failures;
+    emitIdGuard(masm, id, &failures);
+
     GenerateAddSlot(cx, masm, attacher, obj, oldShape, oldGroup, object(), temp(), value(),
-                    checkTypeset);
+                    checkTypeset, &failures);
     return linkAndAttachStub(cx, masm, attacher, ion, "adding",
                              JS::TrackedOutcome::ICSetPropStub_AddSlot);
 }
 
 static bool
 CanInlineSetPropTypeCheck(JSObject* obj, jsid id, ConstantOrRegister val, bool* checkTypeset)
 {
     bool shouldCheck = false;
@@ -3230,44 +3267,43 @@ CanAttachNativeSetProp(JSContext* cx, Ha
     }
 
     return SetPropertyIC::CanAttachNone;
 }
 
 static void
 GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                    JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
-                   Register object, Register tempReg, ConstantOrRegister value, bool checkTypeset)
+                   Register object, Register tempReg, ConstantOrRegister value, bool checkTypeset,
+                   Label* failures)
 {
-    Label failure;
-
     // Guard on the type of the object.
     masm.branchPtr(Assembler::NotEqual,
                    Address(object, JSObject::offsetOfGroup()),
-                   ImmGCPtr(obj->group()), &failure);
+                   ImmGCPtr(obj->group()), failures);
 
     if (checkTypeset)
-        CheckTypeSetForWrite(masm, obj, id, tempReg, value, &failure);
+        CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);
 
     Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
 
     if (cx->zone()->needsIncrementalBarrier()) {
         if (unboxedType == JSVAL_TYPE_OBJECT)
             masm.callPreBarrier(address, MIRType_Object);
         else if (unboxedType == JSVAL_TYPE_STRING)
             masm.callPreBarrier(address, MIRType_String);
         else
             MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
     }
 
-    masm.storeUnboxedProperty(address, unboxedType, value, &failure);
+    masm.storeUnboxedProperty(address, unboxedType, value, failures);
 
     attacher.jumpRejoin(masm);
 
-    masm.bind(&failure);
+    masm.bind(failures);
     attacher.jumpNextStub(masm);
 }
 
 static bool
 CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
                     bool needsTypeBarrier, bool* checkTypeset,
                     uint32_t* unboxedOffset, JSValueType* unboxedType)
 {
@@ -3349,18 +3385,22 @@ SetPropertyIC::tryAttachUnboxed(JSContex
     {
         return true;
     }
 
     *emitted = true;
 
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
+
+    Label failures;
+    emitIdGuard(masm, id, &failures);
+
     GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
-                       object(), temp(), value(), checkTypeset);
+                       object(), temp(), value(), checkTypeset, &failures);
     return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
                              JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
 }
 
 bool
 SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
                               HandleObject obj, HandleId id, bool* emitted)
 {
@@ -3371,35 +3411,35 @@ SetPropertyIC::tryAttachProxy(JSContext*
 
     void* returnAddr = GetReturnAddressToIonCode(cx);
     if (IsCacheableDOMProxy(obj)) {
         DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
         if (shadows == ShadowCheckFailed)
             return false;
 
         if (DOMProxyIsShadowing(shadows)) {
-            if (!attachDOMProxyShadowed(cx, outerScript, ion, obj, returnAddr))
+            if (!attachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr))
                 return false;
             *emitted = true;
             return true;
         }
 
         MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
         if (shadows == DoesntShadowUnique)
             reset(Reprotect);
-        if (!attachDOMProxyUnshadowed(cx, outerScript, ion, obj, returnAddr))
+        if (!attachDOMProxyUnshadowed(cx, outerScript, ion, obj, id, returnAddr))
             return false;
         *emitted = true;
         return true;
     }
 
     if (hasGenericProxyStub())
         return true;
 
-    if (!attachGenericProxy(cx, outerScript, ion, returnAddr))
+    if (!attachGenericProxy(cx, outerScript, ion, id, returnAddr))
         return false;
     *emitted = true;
     return true;
 }
 
 bool
 SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                HandleObject obj, HandleId id, bool* emitted, bool* tryNativeAddSlot)
@@ -3457,35 +3497,52 @@ SetPropertyIC::tryAttachUnboxedExpando(J
     if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
         return false;
     *emitted = true;
     return true;
 }
 
 bool
 SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                             HandleObject obj, HandleId id, bool* emitted, bool* tryNativeAddSlot)
+                             HandleObject obj, HandleValue idval, HandleValue value,
+                             MutableHandleId id, bool* emitted, bool* tryNativeAddSlot)
 {
     MOZ_ASSERT(!*emitted);
     MOZ_ASSERT(!*tryNativeAddSlot);
 
     if (!canAttachStub() || obj->watched())
         return true;
 
-    if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, emitted))
-        return false;
-
-    if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
+    bool nameOrSymbol;
+    if (!ValueToNameOrSymbolId(cx, idval, id, &nameOrSymbol))
         return false;
 
-    if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
-        return false;
-
-    if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
-        return false;
+    if (nameOrSymbol) {
+        if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, emitted))
+            return false;
+
+        if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
+            return false;
+
+        if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
+            return false;
+
+        if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
+            return false;
+    }
+
+    if (idval.isInt32()) {
+        if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
+            return false;
+        if (!*emitted &&
+            !tryAttachTypedArrayElement(cx, outerScript, ion, obj, idval, value, emitted))
+        {
+            return false;
+        }
+    }
 
     return true;
 }
 
 bool
 SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
                                 HandleObject obj, HandleId id, HandleObjectGroup oldGroup,
                                 HandleShape oldShape, bool tryNativeAddSlot, bool* emitted)
@@ -3503,43 +3560,41 @@ SetPropertyIC::tryAttachAddSlot(JSContex
         return true;
 
     // The property did not exist before, now we can try to inline the property add.
     bool checkTypeset = false;
     if (tryNativeAddSlot &&
         IsPropertyAddInlineable(cx, &obj->as<NativeObject>(), id, value(), oldShape,
                                 needsTypeBarrier(), &checkTypeset))
     {
-        if (!attachAddSlot(cx, outerScript, ion, obj, oldShape, oldGroup, checkTypeset))
+        if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
             return false;
         *emitted = true;
         return true;
     }
 
     checkTypeset = false;
     if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
                                    &checkTypeset))
     {
-        if (!attachAddSlot(cx, outerScript, ion, obj, oldShape, oldGroup, checkTypeset))
+        if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
             return false;
         *emitted = true;
         return true;
     }
 
     return true;
 }
 
 bool
 SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj,
-                      HandleValue value)
+                      HandleValue idval, HandleValue value)
 {
     IonScript* ion = outerScript->ionScript();
     SetPropertyIC& cache = ion->getCache(cacheIndex).toSetProperty();
-    RootedPropertyName name(cx, cache.name());
-    RootedId id(cx, AtomToId(name));
 
     // Remember the old group and shape if we may attach an add-property stub.
     // Also, some code under tryAttachStub depends on obj having a non-lazy
     // group, see for instance CanInlineSetPropTypeCheck.
     RootedObjectGroup oldGroup(cx);
     RootedShape oldShape(cx);
     if (cache.canAttachStub()) {
         oldGroup = obj->getGroup(cx);
@@ -3549,29 +3604,37 @@ SetPropertyIC::update(JSContext* cx, Han
         oldShape = obj->maybeShape();
         if (obj->is<UnboxedPlainObject>()) {
             MOZ_ASSERT(!oldShape);
             if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
                 oldShape = expando->lastProperty();
         }
     }
 
+    RootedId id(cx);
     bool emitted = false;
     bool tryNativeAddSlot = false;
-    if (!cache.tryAttachStub(cx, outerScript, ion, obj, id, &emitted, &tryNativeAddSlot))
+    if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, value, &id, &emitted,
+                             &tryNativeAddSlot))
+    {
         return false;
+    }
 
     // Set/Add the property on the object, the inlined cache are setup for the next execution.
     if (JSOp(*cache.pc()) == JSOP_INITGLEXICAL) {
         RootedScript script(cx);
         jsbytecode* pc;
         cache.getScriptedLocation(&script, &pc);
         MOZ_ASSERT(!script->hasNonSyntacticScope());
         InitGlobalLexicalOperation(cx, &cx->global()->lexicalScope(), script, pc, value);
+    } else if (*cache.pc() == JSOP_SETELEM || *cache.pc() == JSOP_STRICTSETELEM) {
+        if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
+            return false;
     } else {
+        RootedPropertyName name(cx, idval.toString()->asAtom().asPropertyName());
         if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
             return false;
     }
 
     if (!emitted &&
         !cache.tryAttachAddSlot(cx, outerScript, ion, obj, id, oldGroup, oldShape,
                                 tryNativeAddSlot, &emitted))
     {
@@ -3584,16 +3647,17 @@ SetPropertyIC::update(JSContext* cx, Han
     return true;
 }
 
 void
 SetPropertyIC::reset(ReprotectCode reprotect)
 {
     IonCache::reset(reprotect);
     hasGenericProxyStub_ = false;
+    hasDenseStub_ = false;
 }
 
 static bool
 GenerateDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                      JSObject* obj, const Value& idval, Register object,
                      TypedOrValueRegister index, TypedOrValueRegister output)
 {
     Label failures;
@@ -4227,160 +4291,176 @@ StoreDenseElement(MacroAssembler& masm, 
     }
 
     masm.bind(&done);
 }
 
 static bool
 GenerateSetDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
                         JSObject* obj, const Value& idval, bool guardHoles, Register object,
-                        ValueOperand indexVal, ConstantOrRegister value, Register tempToUnboxIndex,
+                        TypedOrValueRegister index, ConstantOrRegister value, Register tempToUnboxIndex,
                         Register temp)
 {
     MOZ_ASSERT(obj->isNative());
     MOZ_ASSERT(idval.isInt32());
 
     Label failures;
-    Label outOfBounds; // index represents a known hole, or an illegal append
-
-    Label markElem, storeElement; // used if TI protects us from worrying about holes.
 
     // Guard object is a dense array.
     Shape* shape = obj->as<NativeObject>().lastProperty();
     if (!shape)
         return false;
     masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
 
     // Ensure the index is an int32 value.
-    masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures);
-
-    // Unbox the index.
-    Register index = masm.extractInt32(indexVal, tempToUnboxIndex);
+    Register indexReg;
+    if (index.hasValue()) {
+        ValueOperand val = index.valueReg();
+        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+        indexReg = masm.extractInt32(val, tempToUnboxIndex);
+    } else {
+        MOZ_ASSERT(!index.typedReg().isFloat());
+        indexReg = index.typedReg().gpr();
+    }
 
     {
         // Load obj->elements.
         Register elements = temp;
         masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elements);
 
         // Compute the location of the element.
-        BaseObjectElementIndex target(elements, index);
+        BaseObjectElementIndex target(elements, indexReg);
+
+        Label storeElement;
 
         // If TI cannot help us deal with HOLES by preventing indexed properties
         // on the prototype chain, we have to be very careful to check for ourselves
         // to avoid stomping on what should be a setter call. Start by only allowing things
         // within the initialized length.
         if (guardHoles) {
             Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-            masm.branch32(Assembler::BelowOrEqual, initLength, index, &outOfBounds);
+            masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &failures);
         } else {
             // Guard that we can increase the initialized length.
             Address capacity(elements, ObjectElements::offsetOfCapacity());
-            masm.branch32(Assembler::BelowOrEqual, capacity, index, &outOfBounds);
+            masm.branch32(Assembler::BelowOrEqual, capacity, indexReg, &failures);
 
             // Guard on the initialized length.
             Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-            masm.branch32(Assembler::Below, initLength, index, &outOfBounds);
+            masm.branch32(Assembler::Below, initLength, indexReg, &failures);
 
             // if (initLength == index)
-            masm.branch32(Assembler::NotEqual, initLength, index, &markElem);
+            Label inBounds;
+            masm.branch32(Assembler::NotEqual, initLength, indexReg, &inBounds);
             {
                 // Increase initialize length.
-                Int32Key newLength(index);
+                Int32Key newLength(indexReg);
                 masm.bumpKey(&newLength, 1);
                 masm.storeKey(newLength, initLength);
 
                 // Increase length if needed.
                 Label bumpedLength;
                 Address length(elements, ObjectElements::offsetOfLength());
-                masm.branch32(Assembler::AboveOrEqual, length, index, &bumpedLength);
+                masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength);
                 masm.storeKey(newLength, length);
                 masm.bind(&bumpedLength);
 
                 // Restore the index.
                 masm.bumpKey(&newLength, -1);
                 masm.jump(&storeElement);
             }
             // else
-            masm.bind(&markElem);
+            masm.bind(&inBounds);
         }
 
         if (cx->zone()->needsIncrementalBarrier())
             masm.callPreBarrier(target, MIRType_Value);
 
         // Store the value.
         if (guardHoles)
             masm.branchTestMagic(Assembler::Equal, target, &failures);
         else
             masm.bind(&storeElement);
         StoreDenseElement(masm, value, elements, target);
     }
     attacher.jumpRejoin(masm);
 
-    // All failures flow to here.
-    masm.bind(&outOfBounds);
     masm.bind(&failures);
     attacher.jumpNextStub(masm);
 
     return true;
 }
 
 bool
-SetElementIC::attachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                                 HandleObject obj, const Value& idval)
+SetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+                                     HandleObject obj, const Value& idval, bool* emitted)
 {
+    MOZ_ASSERT(!*emitted);
+    MOZ_ASSERT(canAttachStub());
+
+    if (hasDenseStub() || !IsDenseElementSetInlineable(obj, idval))
+        return true;
+
+    *emitted = true;
+
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
     if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval,
-                                 guardHoles(), object(), index(),
-                                 value(), tempToUnboxIndex(),
-                                 temp()))
+                                 guardHoles(), object(), id().reg(),
+                                 value(), tempToUnboxIndex(), temp()))
     {
         return false;
     }
 
     setHasDenseStub();
-    const char* message = guardHoles()            ?
-                            "dense array (holes)" :
-                            "dense array";
+    const char* message = guardHoles() ?  "dense array (holes)" : "dense array";
     return linkAndAttachStub(cx, masm, attacher, ion, message,
                              JS::TrackedOutcome::ICSetElemStub_Dense);
 }
 
 static bool
 GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
-                             HandleObject tarr, Register object,
-                             ValueOperand indexVal, ConstantOrRegister value,
-                             Register tempUnbox, Register temp, FloatRegister tempDouble,
-                             FloatRegister tempFloat32)
+                             HandleObject tarr, Register object, TypedOrValueRegister index,
+                             ConstantOrRegister value, Register tempUnbox, Register temp,
+                             FloatRegister tempDouble, FloatRegister tempFloat32)
 {
     Label failures, done, popObjectAndFail;
 
     // Guard on the shape.
     Shape* shape = AnyTypedArrayShape(tarr);
     if (!shape)
         return false;
     masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
 
     // Ensure the index is an int32.
-    masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures);
-    Register index = masm.extractInt32(indexVal, tempUnbox);
+    Register indexReg;
+    if (index.hasValue()) {
+        ValueOperand val = index.valueReg();
+        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+        indexReg = masm.extractInt32(val, tempUnbox);
+    } else {
+        MOZ_ASSERT(!index.typedReg().isFloat());
+        indexReg = index.typedReg().gpr();
+    }
 
     // Guard on the length.
     Address length(object, TypedArrayLayout::lengthOffset());
     masm.unboxInt32(length, temp);
-    masm.branch32(Assembler::BelowOrEqual, temp, index, &done);
+    masm.branch32(Assembler::BelowOrEqual, temp, indexReg, &done);
 
     // Load the elements vector.
     Register elements = temp;
     masm.loadPtr(Address(object, TypedArrayLayout::dataOffset()), elements);
 
     // Set the value.
     Scalar::Type arrayType = AnyTypedArrayType(tarr);
     int width = Scalar::byteSize(arrayType);
-    BaseIndex target(elements, index, ScaleFromElemWidth(width));
+    BaseIndex target(elements, indexReg, ScaleFromElemWidth(width));
 
     if (arrayType == Scalar::Float32) {
         MOZ_ASSERT_IF(hasUnaliasedDouble(), tempFloat32 != InvalidFloatReg);
         FloatRegister tempFloat = hasUnaliasedDouble() ? tempFloat32 : tempDouble;
         if (!masm.convertConstantOrRegisterToFloat(cx, value, tempFloat, &failures))
             return false;
         masm.storeToTypedFloatArray(arrayType, tempFloat, target);
     } else if (arrayType == Scalar::Float64) {
@@ -4421,65 +4501,42 @@ GenerateSetTypedArrayElement(JSContext* 
     }
 
     masm.bind(&failures);
     attacher.jumpNextStub(masm);
     return true;
 }
 
 bool
-SetElementIC::attachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
-                                      HandleObject tarr)
+SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+                                          HandleObject obj, HandleValue idval, HandleValue val,
+                                          bool* emitted)
 {
+    MOZ_ASSERT(!*emitted);
+    MOZ_ASSERT(canAttachStub());
+
+    if (!IsTypedArrayElementSetInlineable(obj, idval, val))
+        return true;
+
+    *emitted = true;
+
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
-    if (!GenerateSetTypedArrayElement(cx, masm, attacher, tarr,
-                                      object(), index(), value(),
+    if (!GenerateSetTypedArrayElement(cx, masm, attacher, obj,
+                                      object(), id().reg(), value(),
                                       tempToUnboxIndex(), temp(), tempDouble(), tempFloat32()))
     {
         return false;
     }
 
     return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
                              JS::TrackedOutcome::ICSetElemStub_TypedArray);
 }
 
 bool
-SetElementIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj,
-                     HandleValue idval, HandleValue value)
-{
-    IonScript* ion = outerScript->ionScript();
-    SetElementIC& cache = ion->getCache(cacheIndex).toSetElement();
-
-    bool attachedStub = false;
-    if (cache.canAttachStub()) {
-        if (!cache.hasDenseStub() && IsDenseElementSetInlineable(obj, idval)) {
-            if (!cache.attachDenseElement(cx, outerScript, ion, obj, idval))
-                return false;
-            attachedStub = true;
-        }
-        if (!attachedStub && IsTypedArrayElementSetInlineable(obj, idval, value)) {
-            if (!cache.attachTypedArrayElement(cx, outerScript, ion, obj))
-                return false;
-        }
-    }
-
-    if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
-        return false;
-    return true;
-}
-
-void
-SetElementIC::reset(ReprotectCode reprotect)
-{
-    IonCache::reset(reprotect);
-    hasDenseStub_ = false;
-}
-
-bool
 BindNameIC::attachGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
                          HandleObject scopeChain)
 {
     MOZ_ASSERT(scopeChain->is<GlobalObject>());
 
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     StubAttacher attacher(*this);
 
--- a/js/src/jit/IonCaches.h
+++ b/js/src/jit/IonCaches.h
@@ -24,17 +24,16 @@
 namespace js {
 namespace jit {
 
 class LInstruction;
 
 #define IONCACHE_KIND_LIST(_)                                   \
     _(GetProperty)                                              \
     _(SetProperty)                                              \
-    _(SetElement)                                               \
     _(BindName)                                                 \
     _(Name)
 
 // Forward declarations of Cache kinds.
 #define FORWARD_DECLARE(kind) class kind##IC;
 IONCACHE_KIND_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
 
@@ -566,196 +565,152 @@ class SetPropertyIC : public IonCache
 {
   protected:
     // Registers live after the cache, excluding output registers. The initial
     // value of these registers must be preserved by the cache.
     LiveRegisterSet liveRegs_;
 
     Register object_;
     Register temp_;
-    PropertyName* name_;
+    Register tempToUnboxIndex_;
+    FloatRegister tempDouble_;
+    FloatRegister tempFloat32_;
+    ConstantOrRegister id_;
     ConstantOrRegister value_;
-    bool strict_;
-    bool needsTypeBarrier_;
+    bool strict_ : 1;
+    bool needsTypeBarrier_ : 1;
+    bool guardHoles_ : 1;
 
-    bool hasGenericProxyStub_;
+    bool hasGenericProxyStub_ : 1;
+    bool hasDenseStub_ : 1;
+
+    void emitIdGuard(MacroAssembler& masm, jsid id, Label* fail);
 
   public:
-    SetPropertyIC(LiveRegisterSet liveRegs, Register object, Register temp, PropertyName* name,
-                  ConstantOrRegister value, bool strict, bool needsTypeBarrier)
+    SetPropertyIC(LiveRegisterSet liveRegs, Register object, Register temp, Register tempToUnboxIndex,
+                  FloatRegister tempDouble, FloatRegister tempFloat32, ConstantOrRegister id,
+                  ConstantOrRegister value, bool strict, bool needsTypeBarrier, bool guardHoles)
       : liveRegs_(liveRegs),
         object_(object),
         temp_(temp),
-        name_(name),
+        tempToUnboxIndex_(tempToUnboxIndex),
+        tempDouble_(tempDouble),
+        tempFloat32_(tempFloat32),
+        id_(id),
         value_(value),
         strict_(strict),
         needsTypeBarrier_(needsTypeBarrier),
-        hasGenericProxyStub_(false)
+        guardHoles_(guardHoles),
+        hasGenericProxyStub_(false),
+        hasDenseStub_(false)
     {
     }
 
     CACHE_HEADER(SetProperty)
 
     void reset(ReprotectCode reprotect);
 
     Register object() const {
         return object_;
     }
     Register temp() const {
         return temp_;
     }
-    PropertyName* name() const {
-        return name_;
+    Register tempToUnboxIndex() const {
+        return tempToUnboxIndex_;
+    }
+    FloatRegister tempDouble() const {
+        return tempDouble_;
+    }
+    FloatRegister tempFloat32() const {
+        return tempFloat32_;
+    }
+    ConstantOrRegister id() const {
+        return id_;
     }
     ConstantOrRegister value() const {
         return value_;
     }
     bool strict() const {
         return strict_;
     }
     bool needsTypeBarrier() const {
         return needsTypeBarrier_;
     }