Merge mozilla-central to autoland. a=merge

--HG--
extra : rebase_source : d2d1fef29703d48d14b7ceac0ffa9ee0e5a0a007
This commit is contained in:
Cosmin Sabou 2018-11-16 23:57:57 +02:00
commit 8b0d7e9861
23 changed files with 328 additions and 157 deletions

View File

@ -21,11 +21,6 @@ function handleThreadState(toolbox, event, packet) {
// 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");
@ -59,6 +54,13 @@ function attachThread(toolbox) {
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"));
}

View File

@ -357,11 +357,11 @@
}
#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 {
@ -516,6 +516,10 @@
align-self: center;
}
.webreplay-player .command-button.active:hover {
background: none;
}
.webreplay-player .play-button {
mask-image: var(--play-image);
margin-right: 5px;

View File

@ -115,7 +115,7 @@ a {
opacity: 0.6;
width: 100vw;
height: 1px;
bottom: 0px;
top: 0px;
left: -3px;
display: block;
content: "";

View File

@ -26,6 +26,25 @@ 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 {
@ -145,6 +164,9 @@ class ConsoleOutput extends Component {
}
}
const pausedMessage = getPausedMessage(
visibleMessages, messages, pausedExecutionPoint);
const messageNodes = visibleMessages.map((messageId) => MessageContainer({
dispatch,
key: messageId,
@ -158,6 +180,7 @@ class ConsoleOutput extends Component {
networkMessageActiveTabId,
pausedExecutionPoint,
getMessage: () => messages.get(messageId),
isPaused: pausedMessage && pausedMessage.id == messageId,
}));
return (

View File

@ -24,13 +24,6 @@ const componentMap = new Map([
["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 {
@ -42,6 +35,7 @@ class MessageContainer extends Component {
repeat: PropTypes.number,
networkMessageUpdate: PropTypes.object,
getMessage: PropTypes.func.isRequired,
isPaused: PropTypes.bool.isRequired,
};
}
@ -59,7 +53,7 @@ class MessageContainer extends Component {
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
@ -73,9 +67,7 @@ class MessageContainer extends Component {
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));
}
}

View File

@ -175,7 +175,8 @@ WebConsoleOutputWrapper.prototype = {
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 => {
@ -365,8 +366,10 @@ WebConsoleOutputWrapper.prototype = {
}
},
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) {

View File

@ -21,6 +21,7 @@ class WebReplayPlayer extends Component {
executionPoint: null,
recordingEndpoint: null,
seeking: false,
recording: true,
messages: [],
};
}
@ -28,6 +29,7 @@ class WebReplayPlayer extends Component {
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)
@ -42,20 +44,34 @@ class WebReplayPlayer extends Component {
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) {
@ -68,25 +84,36 @@ class WebReplayPlayer extends Component {
}
// 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();
}
@ -102,25 +129,7 @@ class WebReplayPlayer extends Component {
}
renderCommands() {
if (this.isPaused()) {
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),
})
),
];
}
if (this.isRecording()) {
return [
div(
{ className: "command-button" },
@ -132,9 +141,33 @@ class WebReplayPlayer extends Component {
];
}
const isActiveClass = !this.isPaused() ? "active" : "";
return [
div(
{ className: `command-button ${isActiveClass}` },
div({
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",
@ -169,9 +202,7 @@ class WebReplayPlayer extends Component {
div({
className: "progress",
style: {
width: `${this.getPercent(
this.state.executionPoint || this.state.seeking
)}%`,
width: `${this.getPercent(this.state.executionPoint)}%`,
},
}),
...this.renderMessages()

View File

@ -28,6 +28,7 @@ loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors
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.
@ -113,6 +114,8 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
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";
@ -1778,6 +1781,17 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
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

View File

@ -754,7 +754,7 @@ ThreadClient.prototype = {
actors: arg(0),
}),
events: ["newSource"],
events: ["newSource", "progress"],
};
eventSource(ThreadClient.prototype);

View File

@ -2702,7 +2702,9 @@ nsJSContext::EnsureStatics()
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,

View File

@ -484,16 +484,16 @@ public:
}
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);
@ -501,13 +501,13 @@ public:
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;
}
@ -595,7 +595,7 @@ FetchUtil::StreamResponseToJS(JSContext* aCx,
nsCOMPtr<nsIInputStream> body;
ir->GetUnfilteredBody(getter_AddRefs(body));
if (!body) {
aConsumer->streamClosed(JS::StreamConsumer::EndOfFile);
aConsumer->streamEnd();
return true;
}
@ -614,5 +614,21 @@ FetchUtil::StreamResponseToJS(JSContext* aCx,
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

View File

@ -67,6 +67,16 @@ public:
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

View File

@ -896,7 +896,9 @@ InitJSContextForWorker(WorkerPrivate* aWorkerPrivate, JSContext* aWorkerCx)
// 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!");

View File

@ -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);

View File

@ -417,7 +417,6 @@ MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE, 0, JSEXN_TYPEERR, "can only assign We
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")
@ -669,6 +668,7 @@ MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "Read
// 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_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")

View File

@ -4659,9 +4659,11 @@ JS::GetOptimizedEncodingBuildId(JS::BuildIdCharVector* buildId)
}
JS_PUBLIC_API(void)
JS::InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback callback)
JS::InitConsumeStreamCallback(JSContext* cx, ConsumeStreamCallback consume,
ReportStreamErrorCallback report)
{
cx->runtime()->consumeStreamCallback = callback;
cx->runtime()->consumeStreamCallback = consume;
cx->runtime()->reportStreamErrorCallback = report;
}
JS_PUBLIC_API(void)

View File

@ -3587,13 +3587,14 @@ typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
* 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
@ -3601,7 +3602,7 @@ typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
* 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.
@ -3639,19 +3640,22 @@ class JS_PUBLIC_API(StreamConsumer)
// 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 when the stream reaches end-of-file, passing the
// listener described above.
virtual void streamEnd(OptimizedEncodingListener* listener = nullptr) = 0;
// Called by the embedding *instead of* consumeChunk()/streamClosed() if an
// 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;
};
@ -3662,8 +3666,13 @@ 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

View File

@ -6019,6 +6019,8 @@ class AutoPipe
}
};
static const char sWasmCompileAndSerializeFlag[] = "--wasm-compile-and-serialize";
static bool
CompileAndSerializeInSeparateProcess(JSContext* cx, const uint8_t* bytecode, size_t bytecodeLength,
wasm::Bytes* serialized)
@ -6035,8 +6037,19 @@ CompileAndSerializeInSeparateProcess(JSContext* cx, const uint8_t* bytecode, siz
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;
}
@ -6047,34 +6060,28 @@ CompileAndSerializeInSeparateProcess(JSContext* cx, const uint8_t* bytecode, siz
// 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))) {
arg = JS_smprintf("%d", stdIn.reader());
if (!arg || !argv.append(std::move(arg))) {
return false;
}
UniqueChars argv3 = JS_smprintf("%d", stdIn.writer());
if (!argv3 || !argv.append(std::move(argv3))) {
arg = JS_smprintf("%d", stdIn.writer());
if (!arg || !argv.append(std::move(arg))) {
return false;
}
UniqueChars argv4 = JS_smprintf("%d", stdOut.reader());
if (!argv4 || !argv.append(std::move(argv4))) {
arg = JS_smprintf("%d", stdOut.reader());
if (!arg || !argv.append(std::move(arg))) {
return false;
}
UniqueChars argv5 = JS_smprintf("%d", stdOut.writer());
if (!argv5 || !argv.append(std::move(argv5))) {
arg = JS_smprintf("%d", stdOut.writer());
if (!arg || !argv.append(std::move(arg))) {
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;
}
}
// Required by both _spawnv and exec.
if (!argv.append(nullptr)) {
return false;
}
@ -6158,12 +6165,26 @@ WasmCompileAndSerialize(JSContext* cx)
// 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;
@ -7389,7 +7410,7 @@ BufferStreamMain(BufferStreamJob* job)
byteOffset = 0;
while (true) {
if (byteOffset == byteLength) {
job->consumer->streamClosed(JS::StreamConsumer::EndOfFile, listener);
job->consumer->streamEnd(listener);
break;
}
@ -7404,7 +7425,7 @@ BufferStreamMain(BufferStreamJob* job)
}
if (shutdown) {
job->consumer->streamClosed(JS::StreamConsumer::Error);
job->consumer->streamError(JSMSG_STREAM_CONSUME_ERROR);
break;
}
@ -7525,6 +7546,13 @@ ConsumeBufferSource(JSContext* cx, JS::HandleObject obj, JS::MimeType, JS::Strea
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)
{
@ -11348,7 +11376,7 @@ main(int argc, char** argv, char** envp)
ShutdownBufferStreams();
js_delete(bufferStreamState);
});
JS::InitConsumeStreamCallback(cx, ConsumeBufferSource);
JS::InitConsumeStreamCallback(cx, ConsumeBufferSource, ReportStreamError);
JS_SetNativeStackQuota(cx, gMaxStackSize);

View File

@ -101,6 +101,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
profilerSampleBufferRangeStart_(0),
telemetryCallback(nullptr),
consumeStreamCallback(nullptr),
reportStreamErrorCallback(nullptr),
readableStreamDataRequestCallback(nullptr),
readableStreamWriteIntoReadRequestCallback(nullptr),
readableStreamCancelCallback(nullptr),

View File

@ -339,6 +339,7 @@ struct JSRuntime : public js::MallocProvider<JSRuntime>
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,

View File

@ -124,7 +124,8 @@ wasm::HasStreamingSupport(JSContext* cx)
return HasSupport(cx) &&
cx->runtime()->offThreadPromiseState.ref().initialized() &&
CanUseExtraThreads() &&
cx->runtime()->consumeStreamCallback;
cx->runtime()->consumeStreamCallback &&
cx->runtime()->reportStreamErrorCallback;
}
bool
@ -3091,10 +3092,18 @@ EnsureStreamSupport(JSContext* cx)
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);
}
@ -3108,7 +3117,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
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
@ -3117,7 +3126,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
ExclusiveBytesPtr exclusiveCodeBytesEnd_;
Bytes tailBytes_; // immutable after Tail state
ExclusiveStreamEndData exclusiveStreamEnd_;
Maybe<uint32_t> streamError_;
Maybe<size_t> streamError_;
Atomic<bool> streamFailed_;
Tier2Listener tier2Listener_;
@ -3126,7 +3135,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
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) {
@ -3151,7 +3160,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
}
// See setClosedAndDestroyBeforeHelperThreadStarted() comment.
bool rejectAndDestroyBeforeHelperThreadStarted(unsigned errorNumber) {
bool rejectAndDestroyBeforeHelperThreadStarted(size_t errorNumber) {
MOZ_ASSERT(streamState_.lock() == Env);
MOZ_ASSERT(!streamError_);
streamError_ = Some(errorNumber);
@ -3167,13 +3176,13 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
// 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;
@ -3187,7 +3196,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
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_)) {
@ -3200,18 +3209,18 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
}
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()
@ -3250,7 +3259,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
}
case Tail: {
if (!tailBytes_.append(begin, length)) {
return rejectAndDestroyAfterHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
return rejectAndDestroyAfterHelperThreadStarted(StreamOOMCode);
}
return true;
@ -3261,15 +3270,12 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
MOZ_CRASH("unreachable");
}
void streamClosed(JS::StreamConsumer::CloseReason closeReason,
JS::OptimizedEncodingListener* tier2Listener) override {
switch (closeReason) {
case JS::StreamConsumer::EndOfFile:
void streamEnd(JS::OptimizedEncodingListener* tier2Listener) override {
switch (streamState_.lock().get()) {
case Env: {
SharedBytes bytecode = js_new<ShareableBytes>(std::move(envBytes_));
if (!bytecode) {
rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
rejectAndDestroyBeforeHelperThreadStarted(StreamOOMCode);
return;
}
module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
@ -3289,24 +3295,23 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
setClosedAndDestroyAfterHelperThreadStarted();
return;
case Closed:
MOZ_CRASH("streamClosed() in Closed state");
MOZ_CRASH("streamEnd() in Closed state");
}
break;
case JS::StreamConsumer::Error:
}
void streamError(size_t errorCode) override {
MOZ_ASSERT(errorCode != StreamOOMCode);
switch (streamState_.lock().get()) {
case Env:
rejectAndDestroyBeforeHelperThreadStarted(JSMSG_WASM_STREAM_ERROR);
rejectAndDestroyBeforeHelperThreadStarted(errorCode);
return;
case Tail:
case Code:
rejectAndDestroyAfterHelperThreadStarted(JSMSG_WASM_STREAM_ERROR);
rejectAndDestroyAfterHelperThreadStarted(errorCode);
return;
case Closed:
MOZ_CRASH("streamClosed() in Closed state");
MOZ_CRASH("streamError() in Closed state");
}
break;
}
MOZ_CRASH("unreachable");
}
void consumeOptimizedEncoding(const uint8_t* begin, size_t length) override {
@ -3331,7 +3336,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
// 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 */);
@ -3346,7 +3351,7 @@ class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
return module_
? Resolve(cx, *module_, promise, instantiate_, importObj_, warnings_)
: streamError_
? RejectWithErrorNumber(cx, *streamError_, promise)
? RejectWithStreamErrorNumber(cx, *streamError_, promise)
: Reject(cx, *compileArgs_, promise, compileError_);
}
@ -3448,6 +3453,13 @@ 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)
{

View File

@ -21,4 +21,17 @@ for (const method of methods) {
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`);
}

View File

@ -49,7 +49,7 @@ 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