Merge mozilla-central to autoland. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Fri, 16 Nov 2018 23:57:57 +0200
changeset 446880 b9a2dff8c8f5963bd9384f30036731cd65cd3c8d
parent 446879 2c3b27812cbce5ed4514753e529e4fb4d2ca3d4a (current diff)
parent 446777 7154eb3601f3b89ef5b8e559c9f2bae57f6d59f3 (diff)
child 446881 be52a638d6c5f2ee00e472bc8a08c1605535b60a
push id109932
push userapavel@mozilla.com
push dateSat, 17 Nov 2018 11:35:32 +0000
treeherdermozilla-inbound@e4deec61fc8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge
devtools/client/themes/webconsole.css
js/src/jsapi.cpp
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -16,21 +16,16 @@ function handleThreadState(toolbox, even
     return;
   }
 
   // TODO: Bug 1225492, we continue emitting events on the target
   // like we used to, but we should emit these only on the
   // threadClient now.
   toolbox.target.emit("thread-" + event);
 
-  const replayButton = toolbox.doc.getElementById("command-button-stop-replay");
-  if (replayButton) {
-    replayButton.classList.toggle("paused", event === "paused");
-  }
-
   if (event === "paused") {
     toolbox.highlightTool("jsdebugger");
 
     if (packet.why.type === "debuggerStatement" ||
        packet.why.type === "breakpoint" ||
        packet.why.type === "exception") {
       toolbox.raise();
       toolbox.selectTool("jsdebugger", packet.why.type);
@@ -54,16 +49,23 @@ function attachThread(toolbox) {
       if (res.error) {
         reject(new Error("Couldn't attach to thread: " + res.error));
         return;
       }
 
       threadClient.addListener("paused", handleThreadState.bind(null, toolbox));
       threadClient.addListener("resumed", handleThreadState.bind(null, toolbox));
 
+      threadClient.addListener("progress", (_, {recording}) => {
+        const replayButton = toolbox.doc.getElementById("command-button-stop-replay");
+        if (replayButton) {
+          replayButton.classList.toggle("recording", recording);
+        }
+      });
+
       if (!threadClient.paused) {
         reject(new Error("Thread in wrong state when starting up, should be paused"));
       }
 
       // These flags need to be set here because the client sends them
       // with the `resume` request. We make sure to do this before
       // resuming to avoid another interrupt. We can't pass it in with
       // `threadOptions` because the resume request will override them.
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -352,21 +352,21 @@
   background-color: transparent;
 }
 
 #command-button-replay:hover, #command-button-stop-replay:hover {
   background: var(--toolbarbutton-background);
 }
 
 #command-button-stop-replay::before {
-  fill: var(--red-60);
+  fill: currentColor;
 }
 
-#command-button-stop-replay.paused::before{
-  fill: currentColor;
+#command-button-stop-replay.recording::before{
+  fill: var(--red-60);
 }
 
 #command-button-scratchpad::before {
   background-image: var(--command-scratchpad-image);
 }
 
 #command-button-eyedropper::before {
   background-image: var(--command-eyedropper-image);
@@ -511,16 +511,20 @@
 .webreplay-player .btn {
   width: 15px;
   height: 19px;
   background: #6A6A6A;
   mask-size: 15px 19px;
   align-self: center;
 }
 
+.webreplay-player .command-button.active:hover {
+  background: none;
+}
+
 .webreplay-player .play-button {
   mask-image: var(--play-image);
   margin-right: 5px;
   margin-left: 2px;
 }
 
 .webreplay-player .rewind-button {
   mask-image: var(--play-image);
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -110,17 +110,17 @@ a {
   background-color: var(--warning-background-color);
 }
 
 .message.paused::before {
   background: #d8461f;
   opacity: 0.6;
   width: 100vw;
   height: 1px;
-  bottom: 0px;
+  top: 0px;
   left: -3px;
   display: block;
   content: "";
   position: absolute;
 }
 
 .message.paused ~ .message {
   opacity: 0.5;
--- a/devtools/client/webconsole/components/ConsoleOutput.js
+++ b/devtools/client/webconsole/components/ConsoleOutput.js
@@ -21,16 +21,35 @@ const {
 const MessageContainer = createFactory(require("devtools/client/webconsole/components/MessageContainer").MessageContainer);
 const {
   MESSAGE_TYPE,
 } = require("devtools/client/webconsole/constants");
 const {
   getInitialMessageCountForViewport,
 } = require("devtools/client/webconsole/utils/messages.js");
 
+// Finds the message that comes right after the current paused execution point.
+// NOTE: visibleMessages are not guaranteed to be ordered.
+function getPausedMessage(visibleMessages, messages, executionPoint) {
+  if (!executionPoint || !visibleMessages) {
+    return null;
+  }
+
+  let pausedMessage = messages.get(visibleMessages[0]);
+  for (const messageId of visibleMessages) {
+    const message = messages.get(messageId);
+    if (executionPoint.progress >= message.executionPoint.progress &&
+        message.executionPoint.progress > pausedMessage.executionPoint.progress) {
+      pausedMessage = message;
+    }
+  }
+
+  return pausedMessage;
+}
+
 class ConsoleOutput extends Component {
   static get propTypes() {
     return {
       initialized: PropTypes.bool.isRequired,
       messages: PropTypes.object.isRequired,
       messagesUi: PropTypes.array.isRequired,
       serviceContainer: PropTypes.shape({
         attachRefToHud: PropTypes.func.isRequired,
@@ -140,29 +159,33 @@ class ConsoleOutput extends Component {
     if (!initialized) {
       const numberMessagesFitViewport = getInitialMessageCountForViewport(window);
       if (numberMessagesFitViewport < visibleMessages.length) {
         visibleMessages = visibleMessages.slice(
           visibleMessages.length - numberMessagesFitViewport);
       }
     }
 
+    const pausedMessage = getPausedMessage(
+      visibleMessages, messages, pausedExecutionPoint);
+
     const messageNodes = visibleMessages.map((messageId) => MessageContainer({
       dispatch,
       key: messageId,
       messageId,
       serviceContainer,
       open: messagesUi.includes(messageId),
       tableData: messagesTableData.get(messageId),
       timestampsVisible,
       repeat: messagesRepeat[messageId],
       networkMessageUpdate: networkMessagesUpdate[messageId],
       networkMessageActiveTabId,
       pausedExecutionPoint,
       getMessage: () => messages.get(messageId),
+      isPaused: pausedMessage && pausedMessage.id == messageId,
     }));
 
     return (
       dom.div({
         className: "webconsole-output",
         onContextMenu: this.onContextMenu,
         ref: node => {
           this.outputNode = node;
--- a/devtools/client/webconsole/components/MessageContainer.js
+++ b/devtools/client/webconsole/components/MessageContainer.js
@@ -19,34 +19,28 @@ const componentMap = new Map([
   ["ConsoleApiCall", require("./message-types/ConsoleApiCall")],
   ["ConsoleCommand", require("./message-types/ConsoleCommand")],
   ["DefaultRenderer", require("./message-types/DefaultRenderer")],
   ["EvaluationResult", require("./message-types/EvaluationResult")],
   ["NetworkEventMessage", require("./message-types/NetworkEventMessage")],
   ["PageError", require("./message-types/PageError")],
 ]);
 
-function isPaused({ getMessage, pausedExecutionPoint }) {
-  const message = getMessage();
-  return pausedExecutionPoint
-  && message.executionPoint
-    && pausedExecutionPoint.progress === message.executionPoint.progress;
-}
-
 class MessageContainer extends Component {
   static get propTypes() {
     return {
       messageId: PropTypes.string.isRequired,
       open: PropTypes.bool.isRequired,
       serviceContainer: PropTypes.object.isRequired,
       tableData: PropTypes.object,
       timestampsVisible: PropTypes.bool.isRequired,
       repeat: PropTypes.number,
       networkMessageUpdate: PropTypes.object,
       getMessage: PropTypes.func.isRequired,
+      isPaused: PropTypes.bool.isRequired,
     };
   }
 
   static get defaultProps() {
     return {
       open: false,
     };
   }
@@ -54,33 +48,31 @@ class MessageContainer extends Component
   shouldComponentUpdate(nextProps, nextState) {
     const repeatChanged = this.props.repeat !== nextProps.repeat;
     const openChanged = this.props.open !== nextProps.open;
     const tableDataChanged = this.props.tableData !== nextProps.tableData;
     const timestampVisibleChanged =
       this.props.timestampsVisible !== nextProps.timestampsVisible;
     const networkMessageUpdateChanged =
       this.props.networkMessageUpdate !== nextProps.networkMessageUpdate;
-    const pausedChanged = isPaused(this.props) !== isPaused(nextProps);
+    const pausedChanged = this.props.isPaused !== nextProps.isPaused;
 
     return repeatChanged
       || openChanged
       || tableDataChanged
       || timestampVisibleChanged
       || networkMessageUpdateChanged
       || pausedChanged;
   }
 
   render() {
     const message = this.props.getMessage();
 
     const MessageComponent = getMessageComponent(message);
-    return MessageComponent(Object.assign({message}, this.props, {
-      isPaused: isPaused(this.props),
-    }));
+    return MessageComponent(Object.assign({message}, this.props));
   }
 }
 
 function getMessageComponent(message) {
   if (!message) {
     return componentMap.get("DefaultRenderer");
   }
 
--- a/devtools/client/webconsole/webconsole-output-wrapper.js
+++ b/devtools/client/webconsole/webconsole-output-wrapper.js
@@ -170,17 +170,18 @@ WebConsoleOutputWrapper.prototype = {
         menu.once("open", () => this.emit("menu-open"));
         menu.popup(screenX, screenY, { doc: this.owner.chromeWindow.document });
 
         return menu;
       };
 
       if (this.toolbox) {
         this.toolbox.threadClient.addListener("paused", this.dispatchPaused.bind(this));
-        this.toolbox.threadClient.addListener("resumed", this.dispatchResumed.bind(this));
+        this.toolbox.threadClient.addListener(
+          "progress", this.dispatchProgress.bind(this));
 
         Object.assign(serviceContainer, {
           onViewSourceInDebugger: frame => {
             this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() => {
               this.telemetry.recordEvent("jump_to_source", "webconsole",
                                          null, { "session_id": this.toolbox.sessionId }
               );
               this.hud.emit("source-in-debugger-opened");
@@ -360,18 +361,20 @@ WebConsoleOutputWrapper.prototype = {
   },
 
   dispatchPaused: function(_, packet) {
     if (packet.executionPoint) {
       store.dispatch(actions.setPauseExecutionPoint(packet.executionPoint));
     }
   },
 
-  dispatchResumed: function(_, packet) {
-    store.dispatch(actions.setPauseExecutionPoint(null));
+  dispatchProgress: function(_, packet) {
+    const {executionPoint, recording} = packet;
+    const point = recording ? null : executionPoint;
+    store.dispatch(actions.setPauseExecutionPoint(point));
   },
 
   dispatchMessageUpdate: function(message, res) {
     // network-message-updated will emit when all the update message arrives.
     // Since we can't ensure the order of the network update, we check
     // that networkInfo.updates has all we need.
     // Note that 'requestPostData' is sent only for POST requests, so we need
     // to count with that.
--- a/devtools/client/webreplay/components/WebReplayPlayer.js
+++ b/devtools/client/webreplay/components/WebReplayPlayer.js
@@ -16,130 +16,163 @@ class WebReplayPlayer extends Component 
   }
 
   constructor(props) {
     super(props);
     this.state = {
       executionPoint: null,
       recordingEndpoint: null,
       seeking: false,
+      recording: true,
       messages: [],
     };
   }
 
   componentDidMount() {
     this.threadClient.addListener("paused", this.onPaused.bind(this));
     this.threadClient.addListener("resumed", this.onResumed.bind(this));
+    this.threadClient.addListener("progress", this.onProgress.bind(this));
     this.activeConsole._client.addListener(
       "consoleAPICall",
       this.onMessage.bind(this)
     );
   }
 
   get threadClient() {
     return this.props.toolbox.threadClient;
   }
 
   get activeConsole() {
     return this.props.toolbox.target.activeConsole;
   }
 
+  isRecording() {
+    return this.state.recording;
+  }
+
+  isReplaying() {
+    const {recording} = this.state;
+    return !this.isPaused() && !recording;
+  }
+
   isPaused() {
-    const { executionPoint, seeking } = this.state;
-    return !!executionPoint || !!seeking;
+    const { paused } = this.state;
+    return paused;
   }
 
   onPaused(_, packet) {
     if (packet && packet.recordingEndpoint) {
       const { executionPoint, recordingEndpoint } = packet;
-      this.setState({ executionPoint, recordingEndpoint, seeking: false });
+      this.setState({ executionPoint, recordingEndpoint, paused: true });
     }
   }
 
   onResumed(_, packet) {
-    this.setState({ executionPoint: null });
+    this.setState({ paused: false });
+  }
+
+  onProgress(_, packet) {
+    const { recording, executionPoint } = packet;
+    this.setState({ recording, executionPoint });
   }
 
   onMessage(_, packet) {
     this.setState({ messages: this.state.messages.concat(packet.message) });
   }
 
   seek(executionPoint) {
     if (!executionPoint) {
       return null;
     }
 
     // set seeking to the current execution point to avoid a progress bar jump
-    this.setState({ seeking: this.state.executionPoint });
     return this.threadClient.timeWarp(executionPoint);
   }
 
   next(ev) {
+    if (!this.isPaused()) {
+      return null;
+    }
+
     if (!ev.metaKey) {
       return this.threadClient.resume();
     }
 
-    const { messages, executionPoint } = this.state;
-    const seekPoint = messages
+    const { messages, executionPoint, recordingEndpoint } = this.state;
+    let seekPoint = messages
       .map(m => m.executionPoint)
       .filter(point => point.progress > executionPoint.progress)
       .slice(0)[0];
 
+    if (!seekPoint) {
+      seekPoint = recordingEndpoint;
+    }
+
     return this.seek(seekPoint);
   }
 
   previous(ev) {
+    if (!this.isPaused()) {
+      return null;
+    }
+
     if (!ev.metaKey) {
       return this.threadClient.rewind();
     }
 
     const { messages, executionPoint } = this.state;
 
     const seekPoint = messages
       .map(m => m.executionPoint)
       .filter(point => point.progress < executionPoint.progress)
       .slice(-1)[0];
 
     return this.seek(seekPoint);
   }
 
   renderCommands() {
-    if (this.isPaused()) {
+    if (this.isRecording()) {
       return [
         div(
           { className: "command-button" },
           div({
-            className: "rewind-button btn",
-            onClick: ev => this.previous(ev),
-          })
-        ),
-        div(
-          { className: "command-button" },
-          div({
-            className: "play-button btn",
-            onClick: ev => this.next(ev),
+            className: "pause-button btn",
+            onClick: () => this.threadClient.interrupt(),
           })
         ),
       ];
     }
 
+    const isActiveClass = !this.isPaused() ? "active" : "";
+
     return [
       div(
-        { className: "command-button" },
+        { className: `command-button ${isActiveClass}` },
         div({
-          className: "pause-button btn",
-          onClick: () => this.threadClient.interrupt(),
+          className: "rewind-button btn",
+          onClick: ev => this.previous(ev),
+        })
+      ),
+      div(
+        { className: `command-button ${isActiveClass}` },
+        div({
+          className: "play-button btn",
+          onClick: ev => this.next(ev),
         })
       ),
     ];
   }
 
   renderMessages() {
     const messages = this.state.messages;
 
+    if (this.isRecording()) {
+      return [];
+    }
+
     return messages.map((message, index) =>
       dom.div({
         className: "message",
         style: {
           left: `${this.getPercent(message.executionPoint)}%`,
         },
         onClick: () => this.seek(message.executionPoint),
       })
@@ -164,19 +197,17 @@ class WebReplayPlayer extends Component 
         div(
           { className: "overlay-container " },
           div({ className: "commands" }, ...this.renderCommands()),
           div(
             { className: "progressBar" },
             div({
               className: "progress",
               style: {
-                width: `${this.getPercent(
-                  this.state.executionPoint || this.state.seeking
-                )}%`,
+                width: `${this.getPercent(this.state.executionPoint)}%`,
               },
             }),
             ...this.renderMessages()
           )
         )
       )
     );
   }
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -23,16 +23,17 @@ loader.lazyRequireGetter(this, "Breakpoi
 loader.lazyRequireGetter(this, "setBreakpointAtEntryPoints", "devtools/server/actors/breakpoint", true);
 loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true);
 loader.lazyRequireGetter(this, "SourceActorStore", "devtools/server/actors/utils/source-actor-store", true);
 loader.lazyRequireGetter(this, "BreakpointActorMap", "devtools/server/actors/utils/breakpoint-actor-map", true);
 loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors/pause-scoped", true);
 loader.lazyRequireGetter(this, "EventLoopStack", "devtools/server/actors/utils/event-loop", true);
 loader.lazyRequireGetter(this, "FrameActor", "devtools/server/actors/frame", true);
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
+loader.lazyRequireGetter(this, "throttle", "devtools/shared/throttle", true);
 
 /**
  * JSD2 actors.
  */
 
 /**
  * Creates a ThreadActor.
  *
@@ -108,16 +109,18 @@ const ThreadActor = ActorClassWithSpec(t
   get dbg() {
     if (!this._dbg) {
       this._dbg = this._parent.makeDebugger();
       this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook;
       this._dbg.onDebuggerStatement = this.onDebuggerStatement;
       this._dbg.onNewScript = this.onNewScript;
       if (this._dbg.replaying) {
         this._dbg.replayingOnForcedPause = this.replayingOnForcedPause.bind(this);
+        this._dbg.replayingOnPositionChange =
+          throttle(this.replayingOnPositionChange.bind(this), 100);
       }
       // Keep the debugger disabled until a client attaches.
       this._dbg.enabled = this._state != "detached";
     }
     return this._dbg;
   },
 
   get globalDebugObject() {
@@ -1773,16 +1776,27 @@ const ThreadActor = ActorClassWithSpec(t
     return this._pauseAndRespond(frame, { type: "debuggerStatement" });
   },
 
   onSkipBreakpoints: function({ skip }) {
     this.skipBreakpoints = skip;
     return { skip };
   },
 
+  /*
+   * A function that the engine calls when a recording/replaying process has
+   * changed its position: a checkpoint was reached or a switch between a
+   * recording and replaying child process occurred.
+   */
+  replayingOnPositionChange: function() {
+    const recording = this.dbg.replayIsRecording();
+    const executionPoint = this.dbg.replayCurrentExecutionPoint();
+    this.conn.send({ type: "progress", from: this.actorID, recording, executionPoint });
+  },
+
   /**
    * A function that the engine calls when replay has hit a point where it will
    * pause, even if no breakpoint has been set. Such points include hitting the
    * beginning or end of the replay, or reaching the target of a time warp.
    *
    * @param frame Debugger.Frame
    *        The youngest stack frame, or null.
    */
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -749,14 +749,14 @@ ThreadClient.prototype = {
    * @param actors [string]
    *        List of actor ID of the queried objects.
    */
   getPrototypesAndProperties: DebuggerClient.requester({
     type: "prototypesAndProperties",
     actors: arg(0),
   }),
 
-  events: ["newSource"],
+  events: ["newSource", "progress"],
 };
 
 eventSource(ThreadClient.prototype);
 
 module.exports = ThreadClient;
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2697,17 +2697,19 @@ nsJSContext::EnsureStatics()
     AsmJSCacheOpenEntryForRead,
     asmjscache::CloseEntryForRead,
     AsmJSCacheOpenEntryForWrite,
     asmjscache::CloseEntryForWrite
   };
   JS::SetAsmJSCacheOps(jsapi.cx(), &asmJSCacheOps);
 
   JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
-  JS::InitConsumeStreamCallback(jsapi.cx(), ConsumeStream);
+  JS::InitConsumeStreamCallback(jsapi.cx(),
+                                ConsumeStream,
+                                FetchUtil::ReportJSStreamError);
 
   // Set these global xpconnect options...
   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
                                        "javascript.options.mem.high_water_mark",
                                        (void*)JSGC_MAX_MALLOC_BYTES);
 
   Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
                                        "javascript.options.mem.max",
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -479,40 +479,40 @@ public:
 
     uint64_t available = 0;
     rv = aStream->Available(&available);
     if (NS_SUCCEEDED(rv) && available == 0) {
       rv = NS_BASE_STREAM_CLOSED;
     }
 
     if (rv == NS_BASE_STREAM_CLOSED) {
-      mConsumer->streamClosed(JS::StreamConsumer::EndOfFile);
+      mConsumer->streamEnd();
       return NS_OK;
     }
 
     if (NS_FAILED(rv)) {
-      mConsumer->streamClosed(JS::StreamConsumer::Error);
+      mConsumer->streamError(size_t(rv));
       return NS_OK;
     }
 
-    // Check mConsumerAborted before NS_FAILED to avoid calling streamClosed()
+    // Check mConsumerAborted before NS_FAILED to avoid calling streamError()
     // if consumeChunk() returned false per JS API contract.
     uint32_t written = 0;
     rv = aStream->ReadSegments(WriteSegment, this, available, &written);
     if (mConsumerAborted) {
       return NS_OK;
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      mConsumer->streamClosed(JS::StreamConsumer::Error);
+      mConsumer->streamError(size_t(rv));
       return NS_OK;
     }
 
     rv = aStream->AsyncWait(this, 0, 0, nullptr);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      mConsumer->streamClosed(JS::StreamConsumer::Error);
+      mConsumer->streamError(size_t(rv));
       return NS_OK;
     }
 
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS(JSStreamConsumer,
@@ -590,17 +590,17 @@ FetchUtil::StreamResponseToJS(JSContext*
   RefPtr<InternalResponse> ir = response->GetInternalResponse();
   if (NS_WARN_IF(!ir)) {
     return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
   }
 
   nsCOMPtr<nsIInputStream> body;
   ir->GetUnfilteredBody(getter_AddRefs(body));
   if (!body) {
-    aConsumer->streamClosed(JS::StreamConsumer::EndOfFile);
+    aConsumer->streamEnd();
     return true;
   }
 
   IgnoredErrorResult error;
   response->SetBodyUsed(aCx, error);
   if (NS_WARN_IF(error.Failed())) {
     return ThrowException(aCx, JSMSG_ERROR_CONSUMING_RESPONSE);
   }
@@ -609,10 +609,26 @@ FetchUtil::StreamResponseToJS(JSContext*
 
   if (!JSStreamConsumer::Start(body, aConsumer, global, aMaybeWorker)) {
     return ThrowException(aCx, JSMSG_OUT_OF_MEMORY);
   }
 
   return true;
 }
 
+// static
+void
+FetchUtil::ReportJSStreamError(JSContext* aCx, size_t aErrorCode)
+{
+  // For now, convert *all* errors into AbortError.
+
+  RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!GetOrCreateDOMReflector(aCx, e, &value)) {
+    return;
+  }
+
+  JS_SetPendingException(aCx, value);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.h
+++ b/dom/fetch/FetchUtil.h
@@ -62,13 +62,23 @@ public:
    * WorkerPrivate must be given.
    */
   static bool
   StreamResponseToJS(JSContext* aCx,
                      JS::HandleObject aObj,
                      JS::MimeType aMimeType,
                      JS::StreamConsumer* aConsumer,
                      WorkerPrivate* aMaybeWorker);
+
+  /**
+   * Called by JS to report (i.e., throw) an error that was passed to the
+   * JS::StreamConsumer::streamError() method on a random stream thread.
+   * This method is passed by function pointer to the JS engine hence the
+   * untyped 'size_t' instead of Gecko 'nsresult'.
+   */
+  static void
+  ReportJSStreamError(JSContext* aCx,
+                      size_t aErrorCode);
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -891,17 +891,19 @@ InitJSContextForWorker(WorkerPrivate* aW
     asmjscache::CloseEntryForWrite
   };
   JS::SetAsmJSCacheOps(aWorkerCx, &asmJSCacheOps);
 
   // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely
   // store a raw pointer as the callback's closure argument on the JSRuntime.
   JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop, (void*)aWorkerPrivate);
 
-  JS::InitConsumeStreamCallback(aWorkerCx, ConsumeStream);
+  JS::InitConsumeStreamCallback(aWorkerCx,
+                                ConsumeStream,
+                                FetchUtil::ReportJSStreamError);
 
   if (!JS::InitSelfHostedCode(aWorkerCx)) {
     NS_WARNING("Could not init self-hosted code!");
     return false;
   }
 
   JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/bug1507314.js
@@ -0,0 +1,6 @@
+// |jit-test| --no-sse4
+if (!wasmCachingIsSupported())
+    quit(0);
+
+var m = wasmCompileInSeparateProcess(wasmTextToBinary('(module (func (export "run") (result i32) (i32.const 42)))'));
+assertEq(new WebAssembly.Instance(m).exports.run(), 42);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -412,17 +412,16 @@ MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT_GENERALIZED, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"anyfunc\" or \"anyref\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field '{0}' is not an Object")
 MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
 MSG_DEF(JSMSG_WASM_BAD_I64_TYPE,       0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_GLOBAL_TYPE,    0, JSEXN_TYPEERR,     "bad type for a WebAssembly.Global")
 MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_TYPEERR,     "cannot transfer WebAssembly/asm.js ArrayBuffer")
-MSG_DEF(JSMSG_WASM_STREAM_ERROR,       0, JSEXN_TYPEERR,     "stream error during WebAssembly compilation")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM,    0, JSEXN_TYPEERR,     "'shared' is true but maximum is not specified")
 MSG_DEF(JSMSG_WASM_GLOBAL_IMMUTABLE,   0, JSEXN_TYPEERR,     "can't set value of immutable global")
 
 // Proxy
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,   2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
 MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
@@ -663,17 +662,18 @@ MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_N
 MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.")
 MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,    1, JSEXN_TYPEERR, "{0} passed a bad chunk.")
 MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.")
 MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER,   1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
 MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED,  0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
 MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED,     1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
 
 // Other Stream-related
-MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK,             0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.")
+MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK,              0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.")
+MSG_DEF(JSMSG_STREAM_CONSUME_ERROR,                      0, JSEXN_TYPEERR,  "error consuming stream body")
 
 // Response-related
 MSG_DEF(JSMSG_ERROR_CONSUMING_RESPONSE,                  0, JSEXN_TYPEERR,  "there was an error consuming the Response")
 MSG_DEF(JSMSG_BAD_RESPONSE_VALUE,                        0, JSEXN_TYPEERR,  "expected Response or Promise resolving to Response")
 MSG_DEF(JSMSG_BAD_RESPONSE_MIME_TYPE,                    0, JSEXN_TYPEERR,  "Response has unsupported MIME type")
 MSG_DEF(JSMSG_BAD_RESPONSE_CORS_SAME_ORIGIN,             0, JSEXN_TYPEERR,  "Response.type must be 'basic', 'cors' or 'default'")
 MSG_DEF(JSMSG_BAD_RESPONSE_STATUS,                       0, JSEXN_TYPEERR,  "Response does not have ok status")
 MSG_DEF(JSMSG_RESPONSE_ALREADY_CONSUMED,                 0, JSEXN_TYPEERR,  "Response already consumed")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4654,19 +4654,21 @@ JS::ShutdownAsyncTasks(JSContext* cx)
 
 JS_PUBLIC_API(bool)
 JS::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId)
 {
     return wasm::GetOptimizedEncodingBuildId(buildId);
 }
 
 JS_PUBLIC_API(void)
-JS::InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback callback)
-{
-    cx->runtime()->consumeStreamCallback = callback;
+JS::InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback consume,
+                              ReportStreamErrorCallback report)
+{
+    cx->runtime()->consumeStreamCallback = consume;
+    cx->runtime()->reportStreamErrorCallback = report;
 }
 
 JS_PUBLIC_API(void)
 JS_RequestInterruptCallback(JSContext* cx)
 {
     cx->requestInterrupt(InterruptReason::CallbackUrgent);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3582,31 +3582,32 @@ typedef js::Vector<char, 0, js::SystemAl
 
 /**
  * The ConsumeStreamCallback is called from an active JSContext, passing a
  * StreamConsumer that wishes to consume the given host object as a stream of
  * bytes with the given MIME type. On failure, the embedding must report the
  * appropriate error on 'cx'. On success, the embedding must call
  * consumer->consumeChunk() repeatedly on any thread until exactly one of:
  *  - consumeChunk() returns false
- *  - the embedding calls consumer->streamClosed()
+ *  - the embedding calls consumer->streamEnd()
+ *  - the embedding calls consumer->streamError()
  * before JS_DestroyContext(cx) or JS::ShutdownAsyncTasks(cx) is called.
  *
- * Note: consumeChunk() and streamClosed() may be called synchronously by
- * ConsumeStreamCallback.
+ * Note: consumeChunk(), streamEnd() and streamError() may be called
+ * synchronously by ConsumeStreamCallback.
  *
- * When streamClosed() is called, the embedding may optionally pass an
+ * When streamEnd() is called, the embedding may optionally pass an
  * OptimizedEncodingListener*, indicating that there is a cache entry associated
  * with this stream that can store an optimized encoding of the bytes that were
  * just streamed at some point in the future by having SpiderMonkey call
  * storeOptimizedEncoding(). Until the optimized encoding is ready, SpiderMonkey
  * will hold an outstanding refcount to keep the listener alive.
  *
  * After storeOptimizedEncoding() is called, on cache hit, the embedding
- * may call consumeOptimizedEncoding() instead of consumeChunk()/streamClosed().
+ * may call consumeOptimizedEncoding() instead of consumeChunk()/streamEnd().
  * The embedding must ensure that the GetOptimizedEncodingBuildId() at the time
  * when an optimized encoding is created is the same as when it is later
  * consumed.
  */
 
 class OptimizedEncodingListener
 {
   protected:
@@ -3634,41 +3635,49 @@ class JS_PUBLIC_API(StreamConsumer)
     virtual ~StreamConsumer() = default;
 
   public:
     // Called by the embedding as each chunk of bytes becomes available.
     // If this function returns 'false', the stream must drop all pointers to
     // this StreamConsumer.
     virtual bool consumeChunk(const uint8_t* begin, size_t length) = 0;
 
-    // Called by the embedding when the stream is closed according to the
-    // contract described above.
-    enum CloseReason { EndOfFile, Error };
-    virtual void streamClosed(CloseReason reason,
-                              OptimizedEncodingListener* listener = nullptr) = 0;
-
-    // Called by the embedding *instead of* consumeChunk()/streamClosed() if an
+    // Called by the embedding when the stream reaches end-of-file, passing the
+    // listener described above.
+    virtual void streamEnd(OptimizedEncodingListener* listener = nullptr) = 0;
+
+    // Called by the embedding when there is an error during streaming. The
+    // given error code should be passed to the ReportStreamErrorCallback on the
+    // main thread to produce the semantically-correct rejection value.
+    virtual void streamError(size_t errorCode) = 0;
+
+    // Called by the embedding *instead of* consumeChunk()/streamEnd() if an
     // optimized encoding is available from a previous streaming of the same
     // contents with the same optimized build id.
     virtual void consumeOptimizedEncoding(const uint8_t* begin, size_t length) = 0;
 
     // Provides optional stream attributes such as base or source mapping URLs.
-    // Necessarily called before consumeChunk(), streamClosed() or
+    // Necessarily called before consumeChunk(), streamEnd(), streamError() or
     // consumeOptimizedEncoding(). The caller retains ownership of the strings.
     virtual void noteResponseURLs(const char* maybeUrl, const char* maybeSourceMapUrl) = 0;
 };
 
 enum class MimeType { Wasm };
 
 typedef bool
 (*ConsumeStreamCallback)(JSContext* cx, JS::HandleObject obj, MimeType mimeType,
                          StreamConsumer* consumer);
 
+typedef void
+(*ReportStreamErrorCallback)(JSContext* cx, size_t errorCode);
+
 extern JS_PUBLIC_API(void)
-InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback callback);
+InitConsumeStreamCallback(JSContext* cx,
+                          ConsumeStreamCallback consume,
+                          ReportStreamErrorCallback report);
 
 /**
  * When a JSRuntime is destroyed it implicitly cancels all async tasks in
  * progress, releasing any roots held by the task. However, this is not soon
  * enough for cycle collection, which needs to have roots dropped earlier so
  * that the cycle collector can transitively remove roots for a future GC. For
  * these and other cases, the set of pending async tasks can be canceled
  * with this call earlier than JSRuntime destruction.
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6014,72 +6014,79 @@ class AutoPipe
 
     void closeWriter() {
         MOZ_ASSERT(fds_[1] != -1);
         close(fds_[1]);
         fds_[1] = -1;
     }
 };
 
+static const char sWasmCompileAndSerializeFlag[] = "--wasm-compile-and-serialize";
+
 static bool
 CompileAndSerializeInSeparateProcess(JSContext* cx, const uint8_t* bytecode, size_t bytecodeLength,
                                      wasm::Bytes* serialized)
 {
     AutoPipe stdIn, stdOut;
     if (!stdIn.init() || !stdOut.init()) {
         return false;
     }
 
     AutoCStringVector argv(cx);
 
     UniqueChars argv0 = DuplicateString(cx, sArgv[0]);
     if (!argv0 || !argv.append(std::move(argv0))) {
         return false;
     }
 
-    UniqueChars argv1 = DuplicateString("--wasm-compile-and-serialize");
-    if (!argv1 || !argv.append(std::move(argv1))) {
+    // Propagate shell flags first, since they must precede the non-option
+    // file-descriptor args (passed on Windows, below).
+    for (unsigned i = 0; i < sPropagatedFlags.length(); i++) {
+        UniqueChars flags = DuplicateString(cx, sPropagatedFlags[i]);
+        if (!flags || !argv.append(std::move(flags))) {
+            return false;
+        }
+    }
+
+    UniqueChars arg;
+
+    arg = DuplicateString(sWasmCompileAndSerializeFlag);
+    if (!arg || !argv.append(std::move(arg))) {
         return false;
     }
 
 #ifdef XP_WIN
     // The spawned process will have all the stdIn/stdOut file handles open, but
     // without the power of fork, we need some other way to communicate the
     // integer fd values so we encode them in argv and WasmCompileAndSerialize()
     // has a matching #ifdef XP_WIN to parse them out. Communicate both ends of
     // both pipes so the child process can closed the unused ends.
 
-    UniqueChars argv2 = JS_smprintf("%d", stdIn.reader());
-    if (!argv2 || !argv.append(std::move(argv2))) {
-        return false;
-    }
-
-    UniqueChars argv3 = JS_smprintf("%d", stdIn.writer());
-    if (!argv3 || !argv.append(std::move(argv3))) {
-        return false;
-    }
-
-    UniqueChars argv4 = JS_smprintf("%d", stdOut.reader());
-    if (!argv4 || !argv.append(std::move(argv4))) {
-        return false;
-    }
-
-    UniqueChars argv5 = JS_smprintf("%d", stdOut.writer());
-    if (!argv5 || !argv.append(std::move(argv5))) {
-        return false;
-    }
-#endif
-
-    for (unsigned i = 0; i < sPropagatedFlags.length(); i++) {
-        UniqueChars flags = DuplicateString(cx, sPropagatedFlags[i]);
-        if (!flags || !argv.append(std::move(flags))) {
-            return false;
-        }
-    }
-
+    arg = JS_smprintf("%d", stdIn.reader());
+    if (!arg || !argv.append(std::move(arg))) {
+        return false;
+    }
+
+    arg = JS_smprintf("%d", stdIn.writer());
+    if (!arg || !argv.append(std::move(arg))) {
+        return false;
+    }
+
+    arg = JS_smprintf("%d", stdOut.reader());
+    if (!arg || !argv.append(std::move(arg))) {
+        return false;
+    }
+
+    arg = JS_smprintf("%d", stdOut.writer());
+    if (!arg || !argv.append(std::move(arg))) {
+        return false;
+    }
+#endif
+
+    // Required by both _spawnv and exec.
     if (!argv.append(nullptr)) {
         return false;
     }
 
 #ifdef XP_WIN
     if (!EscapeForShell(cx, argv)) {
         return false;
     }
@@ -6153,22 +6160,36 @@ static bool
 WasmCompileAndSerialize(JSContext* cx)
 {
     MOZ_ASSERT(wasm::HasCachingSupport(cx));
 
 #ifdef XP_WIN
     // See CompileAndSerializeInSeparateProcess for why we've had to smuggle
     // these fd values through argv. Closing the writing ends is necessary for
     // the reading ends to hit EOF.
-    MOZ_RELEASE_ASSERT(sArgc >= 6);
-    MOZ_ASSERT(!strcmp(sArgv[1], "--wasm-compile-and-serialize"));
-    int stdIn = atoi(sArgv[2]);   // stdIn.reader()
-    close(atoi(sArgv[3]));        // stdIn.writer()
-    close(atoi(sArgv[4]));        // stdOut.reader()
-    int stdOut = atoi(sArgv[5]);  // stdOut.writer()
+    int flagIndex = 0;
+    for (; flagIndex < sArgc; flagIndex++) {
+        if (!strcmp(sArgv[flagIndex], sWasmCompileAndSerializeFlag)) {
+            break;
+        }
+    }
+    MOZ_RELEASE_ASSERT(flagIndex < sArgc);
+
+    int fdsIndex = flagIndex + 1;
+    MOZ_RELEASE_ASSERT(fdsIndex + 4 == sArgc);
+
+    int stdInReader  = atoi(sArgv[fdsIndex + 0]);
+    int stdInWriter  = atoi(sArgv[fdsIndex + 1]);
+    int stdOutReader = atoi(sArgv[fdsIndex + 2]);
+    int stdOutWriter = atoi(sArgv[fdsIndex + 3]);
+
+    int stdIn = stdInReader;
+    close(stdInWriter);
+    close(stdOutReader);
+    int stdOut = stdOutWriter;
 #else
     int stdIn = STDIN_FILENO;
     int stdOut = STDOUT_FILENO;
 #endif
 
     wasm::MutableBytes bytecode = js_new<wasm::ShareableBytes>();
     if (!ReadAll(stdIn, &bytecode->bytes)) {
         return false;
@@ -7384,32 +7405,32 @@ BufferStreamMain(BufferStreamJob* job)
         byteLength = job->source.as<Uint8Vector>().length();
         listener = nullptr;
     }
 
     size_t byteOffset;
     byteOffset = 0;
     while (true) {
         if (byteOffset == byteLength) {
-            job->consumer->streamClosed(JS::StreamConsumer::EndOfFile, listener);
+            job->consumer->streamEnd(listener);
             break;
         }
 
         bool shutdown;
         size_t delayMillis;
         size_t chunkSize;
         {
             auto state = bufferStreamState->lock();
             shutdown = state->shutdown;
             delayMillis = state->delayMillis;
             chunkSize = state->chunkSize;
         }
 
         if (shutdown) {
-            job->consumer->streamClosed(JS::StreamConsumer::Error);
+            job->consumer->streamError(JSMSG_STREAM_CONSUME_ERROR);
             break;
         }
 
         std::this_thread::sleep_for(std::chrono::milliseconds(delayMillis));
 
         chunkSize = Min(chunkSize, byteLength - byteOffset);
 
         if (!job->consumer->consumeChunk(bytes + byteOffset, chunkSize)) {
@@ -7520,16 +7541,23 @@ ConsumeBufferSource(JSContext* cx, JS::H
         if (!jobPtr->thread.init(BufferStreamMain, jobPtr)) {
             oomUnsafe.crash("ConsumeBufferSource");
         }
     }
 
     return true;
 }
 
+static void
+ReportStreamError(JSContext* cx, size_t errorNumber)
+{
+    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
+}
+
+
 static bool
 SetBufferStreamParams(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.requireAtLeast(cx, "setBufferStreamParams", 2)) {
         return false;
     }
 
@@ -11343,17 +11371,17 @@ main(int argc, char** argv, char** envp)
         js_new<ExclusiveWaitableData<BufferStreamState>>(mutexid::BufferStreamState);
     if (!bufferStreamState) {
         return 1;
     }
     auto shutdownBufferStreams = MakeScopeExit([] {
         ShutdownBufferStreams();
         js_delete(bufferStreamState);
     });
-    JS::InitConsumeStreamCallback(cx, ConsumeBufferSource);
+    JS::InitConsumeStreamCallback(cx, ConsumeBufferSource, ReportStreamError);
 
     JS_SetNativeStackQuota(cx, gMaxStackSize);
 
     JS::dbg::SetDebuggerMallocSizeOf(cx, moz_malloc_size_of);
 
     js::UseInternalJobQueues(cx);
 
     if (const char* opt = op.getStringOption("nursery-strings")) {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -96,16 +96,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
 #ifdef DEBUG
     updateChildRuntimeCount(parentRuntime),
     initialized_(false),
 #endif
     mainContext_(nullptr),
     profilerSampleBufferRangeStart_(0),
     telemetryCallback(nullptr),
     consumeStreamCallback(nullptr),
+    reportStreamErrorCallback(nullptr),
     readableStreamDataRequestCallback(nullptr),
     readableStreamWriteIntoReadRequestCallback(nullptr),
     readableStreamCancelCallback(nullptr),
     readableStreamClosedCallback(nullptr),
     readableStreamErroredCallback(nullptr),
     readableStreamFinalizeCallback(nullptr),
     hadOutOfMemory(false),
     allowRelazificationForTesting(false),
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -334,16 +334,17 @@ struct JSRuntime : public js::MallocProv
     // the passed JSObject belongs to.
     void setUseCounter(JSObject* obj, JSUseCounter counter);
 
     void setUseCounterCallback(JSRuntime* rt, JSSetUseCounterCallback callback);
 
   public:
     js::UnprotectedData<js::OffThreadPromiseRuntimeState> offThreadPromiseState;
     js::UnprotectedData<JS::ConsumeStreamCallback> consumeStreamCallback;
+    js::UnprotectedData<JS::ReportStreamErrorCallback> reportStreamErrorCallback;
 
     js::GlobalObject* getIncumbentGlobal(JSContext* cx);
     bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job, js::HandleObject promise,
                            js::Handle<js::GlobalObject*> incumbentGlobal);
     void addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);
     void removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise);
 
     js::UnprotectedData<JS::RequestReadableStreamDataCallback> readableStreamDataRequestCallback;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -119,17 +119,18 @@ wasm::HasSupport(JSContext* cx)
 bool
 wasm::HasStreamingSupport(JSContext* cx)
 {
     // This should match EnsureStreamSupport().
 
     return HasSupport(cx) &&
            cx->runtime()->offThreadPromiseState.ref().initialized() &&
            CanUseExtraThreads() &&
-           cx->runtime()->consumeStreamCallback;
+           cx->runtime()->consumeStreamCallback &&
+           cx->runtime()->reportStreamErrorCallback;
 }
 
 bool
 wasm::HasCachingSupport(JSContext* cx)
 {
     return HasStreamingSupport(cx) &&
            cx->options().wasmIon() &&
            IonCanCompile();
@@ -3086,52 +3087,60 @@ EnsureStreamSupport(JSContext* cx)
     if (!cx->runtime()->consumeStreamCallback) {
         JS_ReportErrorASCII(cx, "WebAssembly streaming not supported in this runtime");
         return false;
     }
 
     return true;
 }
 
+// This value is chosen and asserted to be disjoint from any host error code.
+static const size_t StreamOOMCode = 0;
+
 static bool
-RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber, Handle<PromiseObject*> promise)
+RejectWithStreamErrorNumber(JSContext* cx, size_t errorCode, Handle<PromiseObject*> promise)
 {
-    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
+    if (errorCode == StreamOOMCode) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    cx->runtime()->reportStreamErrorCallback(cx, errorCode);
     return RejectWithPendingException(cx, promise);
 }
 
 class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
 {
     enum StreamState { Env, Code, Tail, Closed };
     typedef ExclusiveWaitableData<StreamState> ExclusiveStreamState;
 
     // Immutable:
     const MutableCompileArgs     compileArgs_;     // immutable during streaming
     const bool                   instantiate_;
     const PersistentRootedObject importObj_;
 
-    // Mutated on a stream thread (consumeChunk() and streamClosed()):
+    // Mutated on a stream thread (consumeChunk(), streamEnd(), streamError()):
     ExclusiveStreamState         streamState_;
     Bytes                        envBytes_;        // immutable after Env state
     SectionRange                 codeSection_;     // immutable after Env state
     Bytes                        codeBytes_;       // not resized after Env state
     uint8_t*                     codeBytesEnd_;
     ExclusiveBytesPtr            exclusiveCodeBytesEnd_;
     Bytes                        tailBytes_;       // immutable after Tail state
     ExclusiveStreamEndData       exclusiveStreamEnd_;
-    Maybe<uint32_t>              streamError_;
+    Maybe<size_t>                streamError_;
     Atomic<bool>                 streamFailed_;
     Tier2Listener                tier2Listener_;
 
     // Mutated on helper thread (execute()):
     SharedModule                 module_;
     UniqueChars                  compileError_;
     UniqueCharsVector            warnings_;
 
-    // Called on some thread before consumeChunk() or streamClosed():
+    // Called on some thread before consumeChunk(), streamEnd(), streamError()):
 
     void noteResponseURLs(const char* url, const char* sourceMapUrl) override {
         if (url) {
             compileArgs_->scriptedCaller.filename = DuplicateString(url);
             compileArgs_->scriptedCaller.filenameIsURL = true;
         }
         if (sourceMapUrl) {
             compileArgs_->sourceMapURL = DuplicateString(sourceMapUrl);
@@ -3146,77 +3155,77 @@ class CompileStreamTask : public Promise
     // Warning: After this function returns, 'this' can be deleted at any time, so the
     // caller must immediately return from the stream callback.
     void setClosedAndDestroyBeforeHelperThreadStarted() {
         streamState_.lock().get() = Closed;
         dispatchResolveAndDestroy();
     }
 
     // See setClosedAndDestroyBeforeHelperThreadStarted() comment.
-    bool rejectAndDestroyBeforeHelperThreadStarted(unsigned errorNumber) {
+    bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) {
         MOZ_ASSERT(streamState_.lock() == Env);
         MOZ_ASSERT(!streamError_);
         streamError_ = Some(errorNumber);
         setClosedAndDestroyBeforeHelperThreadStarted();
         return false;
     }
 
     // Once StartOffThreadPromiseHelperTask succeeds, the helper thread will
     // dispatchResolveAndDestroy() after execute() returns, but execute()
     // wait()s for state to be Closed.
     //
     // Warning: After this function returns, 'this' can be deleted at any time, so the
     // caller must immediately return from the stream callback.
     void setClosedAndDestroyAfterHelperThreadStarted() {
         auto streamState = streamState_.lock();
+        MOZ_ASSERT(streamState != Closed);
         streamState.get() = Closed;
         streamState.notify_one(/* stream closed */);
     }
 
     // See setClosedAndDestroyAfterHelperThreadStarted() comment.
-    bool rejectAndDestroyAfterHelperThreadStarted(unsigned errorNumber) {
-        MOZ_ASSERT(streamState_.lock() == Code || streamState_.lock() == Tail);
+    bool rejectAndDestroyAfterHelperThreadStarted(size_t errorNumber) {
         MOZ_ASSERT(!streamError_);
         streamError_ = Some(errorNumber);
         streamFailed_ = true;
         exclusiveCodeBytesEnd_.lock().notify_one();
         exclusiveStreamEnd_.lock().notify_one();
         setClosedAndDestroyAfterHelperThreadStarted();
         return false;
     }
 
     bool consumeChunk(const uint8_t* begin, size_t length) override {
         switch (streamState_.lock().get()) {
           case Env: {
             if (!envBytes_.append(begin, length)) {
-                return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
+                return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
             }
 
             if (!StartsCodeSection(envBytes_.begin(), envBytes_.end(), &codeSection_)) {
                 return true;
             }
 
             uint32_t extraBytes = envBytes_.length() - codeSection_.start;
             if (extraBytes) {
                 envBytes_.shrinkTo(codeSection_.start);
             }
 
             if (codeSection_.size > MaxCodeSectionBytes) {
-                return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
+                return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
             }
 
             if (!codeBytes_.resize(codeSection_.size)) {
-                return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
+                return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
             }
 
             codeBytesEnd_ = codeBytes_.begin();
             exclusiveCodeBytesEnd_.lock().get() = codeBytesEnd_;
 
             if (!StartOffThreadPromiseHelperTask(this)) {
-                return rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
+                return rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
             }
 
             // Set the state to Code iff StartOffThreadPromiseHelperTask()
             // succeeds so that the state tells us whether we are before or
             // after the helper thread started.
             streamState_.lock().get() = Code;
 
             if (extraBytes) {
@@ -3245,73 +3254,69 @@ class CompileStreamTask : public Promise
             if (uint32_t extraBytes = length - copyLength) {
                 return consumeChunk(begin + copyLength, extraBytes);
             }
 
             return true;
           }
           case Tail: {
             if (!tailBytes_.append(begin, length)) {
-                return rejectAndDestroyAfterHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
+                return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode);
             }
 
             return true;
           }
           case Closed:
             MOZ_CRASH("consumeChunk() in Closed state");
         }
         MOZ_CRASH("unreachable");
     }
 
-    void streamClosed(JS::StreamConsumer::CloseReason closeReason,
-                      JS::OptimizedEncodingListener* tier2Listener) override {
-        switch (closeReason) {
-          case JS::StreamConsumer::EndOfFile:
-            switch (streamState_.lock().get()) {
-              case Env: {
-                SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
-                if (!bytecode) {
-                    rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
-                    return;
-                }
-                module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
-                setClosedAndDestroyBeforeHelperThreadStarted();
+    void streamEnd(JS::OptimizedEncodingListener* tier2Listener) override {
+        switch (streamState_.lock().get()) {
+          case Env: {
+            SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
+            if (!bytecode) {
+                rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
                 return;
-              }
-              case Code:
-              case Tail:
-                {
-                    auto streamEnd = exclusiveStreamEnd_.lock();
-                    MOZ_ASSERT(!streamEnd->reached);
-                    streamEnd->reached = true;
-                    streamEnd->tailBytes = &tailBytes_;
-                    streamEnd->tier2Listener = tier2Listener;
-                    streamEnd.notify_one();
-                }
-                setClosedAndDestroyAfterHelperThreadStarted();
-                return;
-              case Closed:
-                MOZ_CRASH("streamClosed() in Closed state");
+            }
+            module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
+            setClosedAndDestroyBeforeHelperThreadStarted();
+            return;
+          }
+          case Code:
+          case Tail:
+            {
+                auto streamEnd = exclusiveStreamEnd_.lock();
+                MOZ_ASSERT(!streamEnd->reached);
+                streamEnd->reached = true;
+                streamEnd->tailBytes = &tailBytes_;
+                streamEnd->tier2Listener = tier2Listener;
+                streamEnd.notify_one();
             }
-            break;
-          case JS::StreamConsumer::Error:
-            switch (streamState_.lock().get()) {
-              case Env:
-                rejectAndDestroyBeforeHelperThreadStarted(JSMSG_WASM_STREAM_ERROR);
-                return;
-              case Tail:
-              case Code:
-                rejectAndDestroyAfterHelperThreadStarted(JSMSG_WASM_STREAM_ERROR);
-                return;
-              case Closed:
-                MOZ_CRASH("streamClosed() in Closed state");
-            }
-            break;
+            setClosedAndDestroyAfterHelperThreadStarted();
+            return;
+          case Closed:
+            MOZ_CRASH("streamEnd() in Closed state");
         }
-        MOZ_CRASH("unreachable");
+    }
+
+    void streamError(size_t errorCode) override {
+        MOZ_ASSERT(errorCode != StreamOOMCode);
+        switch (streamState_.lock().get()) {
+          case Env:
+            rejectAndDestroyBeforeHelperThreadStarted(errorCode);
+            return;
+          case Tail:
+          case Code:
+            rejectAndDestroyAfterHelperThreadStarted(errorCode);
+            return;
+          case Closed:
+            MOZ_CRASH("streamError() in Closed state");
+        }
     }
 
     void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
         module_ = Module::deserialize(begin, length);
 
         MOZ_ASSERT(streamState_.lock().get() == Env);
         setClosedAndDestroyBeforeHelperThreadStarted();
     }
@@ -3326,32 +3331,32 @@ class CompileStreamTask : public Promise
                                    exclusiveStreamEnd_,
                                    streamFailed_,
                                    &compileError_,
                                    &warnings_);
 
         // When execute() returns, the CompileStreamTask will be dispatched
         // back to its JS thread to call resolve() and then be destroyed. We
         // can't let this happen until the stream has been closed lest
-        // consumeChunk() or streamClosed() be called on a dead object.
+        // consumeChunk() or streamEnd() be called on a dead object.
         auto streamState = streamState_.lock();
         while (streamState != Closed) {
             streamState.wait(/* stream closed */);
         }
     }
 
     // Called on a JS thread after streaming compilation completes/errors:
 
     bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
         MOZ_ASSERT(streamState_.lock() == Closed);
         MOZ_ASSERT_IF(module_, !streamFailed_ && !streamError_ && !compileError_);
         return module_
                ? Resolve(cx, *module_, promise, instantiate_, importObj_, warnings_)
                : streamError_
-                 ? RejectWithErrorNumber(cx, *streamError_, promise)
+                 ? RejectWithStreamErrorNumber(cx, *streamError_, promise)
                  : Reject(cx, *compileArgs_, promise, compileError_);
     }
 
   public:
     CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise,
                       CompileArgs& compileArgs, bool instantiate,
                       HandleObject importObj)
       : PromiseHelperTask(cx, promise),
@@ -3444,16 +3449,23 @@ const Class ResolveResponseClosure::clas
 
 static ResolveResponseClosure*
 ToResolveResponseClosure(CallArgs args)
 {
     return &args.callee().as<JSFunction>().getExtendedSlot(0).toObject().as<ResolveResponseClosure>();
 }
 
 static bool
+RejectWithErrorNumber(JSContext* cx, uint32_t errorNumber, Handle<PromiseObject*> promise)
+{
+    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
+    return RejectWithPendingException(cx, promise);
+}
+
+static bool
 ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     Rooted<ResolveResponseClosure*> closure(cx, ToResolveResponseClosure(callArgs));
     Rooted<PromiseObject*> promise(cx, &closure->promise());
     CompileArgs& compileArgs = closure->compileArgs();
     bool instantiate = closure->instantiate();
--- a/testing/web-platform/tests/wasm/webapi/abort.any.js
+++ b/testing/web-platform/tests/wasm/webapi/abort.any.js
@@ -16,9 +16,22 @@ for (const method of methods) {
   promise_test(async t => {
     const controller = new AbortController();
     const signal = controller.signal;
     const request = fetch('../incrementer.wasm', { signal });
     const promise = WebAssembly[method](request);
     controller.abort();
     return promise_rejects(t, 'AbortError', promise, `${method} should reject`);
   }, `${method}() synchronously followed by abort should reject with AbortError`);
+
+  promise_test(async t => {
+    const controller = new AbortController();
+    const signal = controller.signal;
+    return fetch('../incrementer.wasm', { signal })
+    .then(response => {
+      Promise.resolve().then(() => controller.abort());
+      return WebAssembly[method](response);
+    })
+    .catch(err => {
+      assert_true(err.name === "AbortError");
+    });
+  }, `${method}() asynchronously racing with abort should succeed or reject with AbortError`);
 }
--- a/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.h
+++ b/toolkit/crashreporter/breakpad-client/windows/common/minidump_callback.h
@@ -44,17 +44,17 @@ typedef struct {
   AppMemoryList::const_iterator iter;
   AppMemoryList::const_iterator end;
 } MinidumpCallbackContext;
 
 static const size_t kExceptionAppMemoryRegions = 16;
 
 #if defined(_M_IX86)
 using RegisterValueType = DWORD;
-#elif defined(_M_AMD64)
+#elif defined(_M_AMD64) || defined(_M_ARM64)
 using RegisterValueType = DWORD64;
 #else
 #error Unsupported platform
 #endif
 
 void IncludeAppMemoryFromExceptionContext(HANDLE aProcess,
                                           DWORD aThreadId,
                                           AppMemoryList& aList,