# HG changeset patch # User Yury Delendik # Date 1516654530 21600 # Node ID 53c5a199232afde93f198c07be1d7b59e125a824 # Parent ff25aabee5e098a4acd3b44ece382ec95d7f796d Bug 1431864 - Use Response.url as WebAssembly module URL. r=bkelly,luke MozReview-Commit-ID: 2xAasdmpIKX diff --git a/dom/fetch/FetchUtil.cpp b/dom/fetch/FetchUtil.cpp --- a/dom/fetch/FetchUtil.cpp +++ b/dom/fetch/FetchUtil.cpp @@ -561,16 +561,33 @@ FetchUtil::StreamResponseToJS(JSContext* if (!response->Ok()) { return ThrowException(aCx, JSMSG_BAD_RESPONSE_STATUS); } if (response->BodyUsed()) { return ThrowException(aCx, JSMSG_RESPONSE_ALREADY_CONSUMED); } + switch (aMimeType) { + case JS::MimeType::Wasm: + nsAutoString url; + response->GetUrl(url); + + IgnoredErrorResult result; + nsCString sourceMapUrl; + response->GetInternalHeaders()->Get(NS_LITERAL_CSTRING("SourceMap"), sourceMapUrl, result); + if (NS_WARN_IF(result.Failed())) { + return ThrowException(aCx, JSMSG_ERROR_CONSUMING_RESPONSE); + } + NS_ConvertUTF16toUTF8 urlUTF8(url); + aConsumer->noteResponseURLs(urlUTF8.get(), + sourceMapUrl.IsVoid() ? nullptr : sourceMapUrl.get()); + break; + } + RefPtr ir = response->GetInternalResponse(); if (NS_WARN_IF(!ir)) { return ThrowException(aCx, JSMSG_OUT_OF_MEMORY); } nsCOMPtr body; ir->GetUnfilteredBody(getter_AddRefs(body)); if (!body) { diff --git a/js/src/jit-test/tests/debug/wasm-responseurls.js b/js/src/jit-test/tests/debug/wasm-responseurls.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/debug/wasm-responseurls.js @@ -0,0 +1,37 @@ +// |jit-test| test-also-no-wasm-baseline +// Tests that wasm module can accept URL and sourceMapURL from response +// when instantiateStreaming is used. + +if (!wasmDebuggingIsSupported()) + quit(); + +try { + WebAssembly.compileStreaming(); +} catch (err) { + assertEq(String(err).indexOf("not supported with --no-threads") !== -1, true); + quit(); +} + +load(libdir + "asserts.js"); + +var g = newGlobal(); + +var source = new g.Uint8Array(wasmTextToBinary('(module (func unreachable))')); +source.url = "http://example.org/test.wasm"; +source.sourceMappingURL = "http://example.org/test.wasm.map"; +g.source = source; + +var gotUrl, gotSourceMapURL; +var dbg = new Debugger(g); +dbg.allowWasmBinarySource = true; +dbg.onNewScript = function (s, g) { + gotUrl = s.source.url; + gotSourceMapURL = s.source.sourceMapURL; +}; + +g.eval('WebAssembly.instantiateStreaming(source);'); + +drainJobQueue(); + +assertEq(gotUrl, "http://example.org/test.wasm"); +assertEq(gotSourceMapURL, "http://example.org/test.wasm.map"); diff --git a/js/src/jsapi.h b/js/src/jsapi.h --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4592,16 +4592,21 @@ class JS_PUBLIC_API(StreamConsumer) // 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) = 0; + + // Provides optional stream attributes such as base or source mapping URLs. + // Necessarily called before consumeChunk() or streamClosed(). The caller + // retains ownership of the given 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); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5508,25 +5508,66 @@ BufferStreamMain(BufferStreamJob* job) jobIndex++; job->thread.detach(); // quiet assert in ~Thread() called by erase(). state->jobs.erase(state->jobs.begin() + jobIndex); if (state->jobs.empty()) state.notify_all(/* jobs empty */); } static bool +EnsureLatin1CharsLinearString(JSContext* cx, HandleValue value, JS::MutableHandle result) +{ + if (!value.isString()) { + result.set(nullptr); + return true; + } + RootedString str(cx, value.toString()); + if (!str->isLinear() || !str->hasLatin1Chars()) { + JS_ReportErrorASCII(cx, "only latin1 chars and linear strings are expected"); + return false; + } + result.set(&str->asLinear()); + MOZ_ASSERT(result->hasLatin1Chars()); + return true; +} + +static bool ConsumeBufferSource(JSContext* cx, JS::HandleObject obj, JS::MimeType, JS::StreamConsumer* consumer) { SharedMem dataPointer; size_t byteLength; if (!IsBufferSource(obj, &dataPointer, &byteLength)) { JS_ReportErrorASCII(cx, "shell streaming consumes a buffer source (buffer or view)"); return false; } + { + RootedValue url(cx); + if (!JS_GetProperty(cx, obj, "url", &url)) + return false; + RootedLinearString urlStr(cx); + if (!EnsureLatin1CharsLinearString(cx, url, &urlStr)) + return false; + + RootedValue mapUrl(cx); + if (!JS_GetProperty(cx, obj, "sourceMappingURL", &mapUrl)) + return false; + RootedLinearString mapUrlStr(cx); + if (!EnsureLatin1CharsLinearString(cx, mapUrl, &mapUrlStr)) + return false; + + JS::AutoCheckCannotGC nogc; + consumer->noteResponseURLs(urlStr + ? reinterpret_cast(urlStr->latin1Chars(nogc)) + : nullptr, + mapUrlStr + ? reinterpret_cast(mapUrlStr->latin1Chars(nogc)) + : nullptr); + } + auto job = cx->make_unique(consumer); if (!job || !job->bytes.resize(byteLength)) return false; memcpy(job->bytes.begin(), dataPointer.unwrap(), byteLength); BufferStreamJob* jobPtr = job.get(); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -7127,16 +7127,22 @@ class DebuggerSourceGetURLMatcher MOZ_ASSERT(ss); if (ss->filename()) { JSString* str = NewStringCopyZ(cx_, ss->filename()); return Some(str); } return Nothing(); } ReturnType match(Handle wasmInstance) { + if (wasmInstance->instance().metadata().baseURL) { + JSString* str = NewStringCopyZ(cx_, wasmInstance->instance().metadata().baseURL.get()); + if (!str) + return Nothing(); + return Some(str); + } if (JSString* str = wasmInstance->instance().debug().debugDisplayURL(cx_)) return Some(str); return Nothing(); } }; static bool DebuggerSource_getURL(JSContext* cx, unsigned argc, Value* vp) diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp --- a/js/src/wasm/WasmCode.cpp +++ b/js/src/wasm/WasmCode.cpp @@ -568,62 +568,70 @@ Metadata::serializedSize() const { return sizeof(pod()) + metadata(Tier::Serialized).serializedSize() + SerializedVectorSize(sigIds) + SerializedPodVectorSize(globals) + SerializedPodVectorSize(tables) + SerializedPodVectorSize(funcNames) + SerializedPodVectorSize(customSections) + - filename.serializedSize(); + filename.serializedSize() + + baseURL.serializedSize() + + sourceMapURL.serializedSize(); } size_t Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { size_t sum = 0; for (auto t : tiers()) sum += metadata(t).sizeOfExcludingThis(mallocSizeOf); return sum + SizeOfVectorExcludingThis(sigIds, mallocSizeOf) + globals.sizeOfExcludingThis(mallocSizeOf) + tables.sizeOfExcludingThis(mallocSizeOf) + funcNames.sizeOfExcludingThis(mallocSizeOf) + customSections.sizeOfExcludingThis(mallocSizeOf) + - filename.sizeOfExcludingThis(mallocSizeOf); + filename.sizeOfExcludingThis(mallocSizeOf) + + baseURL.sizeOfExcludingThis(mallocSizeOf) + + sourceMapURL.sizeOfExcludingThis(mallocSizeOf); } uint8_t* Metadata::serialize(uint8_t* cursor) const { MOZ_ASSERT(!debugEnabled && debugFuncArgTypes.empty() && debugFuncReturnTypes.empty()); cursor = WriteBytes(cursor, &pod(), sizeof(pod())); cursor = metadata(Tier::Serialized).serialize(cursor); cursor = SerializeVector(cursor, sigIds); cursor = SerializePodVector(cursor, globals); cursor = SerializePodVector(cursor, tables); cursor = SerializePodVector(cursor, funcNames); cursor = SerializePodVector(cursor, customSections); cursor = filename.serialize(cursor); + cursor = baseURL.serialize(cursor); + cursor = sourceMapURL.serialize(cursor); return cursor; } /* static */ const uint8_t* Metadata::deserialize(const uint8_t* cursor) { (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) && (cursor = metadata(Tier::Serialized).deserialize(cursor)) && (cursor = DeserializeVector(cursor, &sigIds)) && (cursor = DeserializePodVector(cursor, &globals)) && (cursor = DeserializePodVector(cursor, &tables)) && (cursor = DeserializePodVector(cursor, &funcNames)) && (cursor = DeserializePodVector(cursor, &customSections)) && (cursor = filename.deserialize(cursor)); + (cursor = baseURL.deserialize(cursor)); + (cursor = sourceMapURL.deserialize(cursor)); debugEnabled = false; debugFuncArgTypes.clear(); debugFuncReturnTypes.clear(); return cursor; } struct ProjectFuncIndex { diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -404,16 +404,18 @@ class Metadata : public ShareableBase { Assumptions assumptions; ScriptedCaller scriptedCaller; + ResponseURLs responseURLs; bool baselineEnabled; bool debugEnabled; bool ionEnabled; bool sharedMemoryEnabled; bool testTiering; CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller) : assumptions(Move(assumptions)), diff --git a/js/src/wasm/WasmDebug.cpp b/js/src/wasm/WasmDebug.cpp --- a/js/src/wasm/WasmDebug.cpp +++ b/js/src/wasm/WasmDebug.cpp @@ -676,17 +676,27 @@ DebugState::getSourceMappingURL(JSContex if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end()) return true; // ignoring invalid section data UTF8Chars utf8Chars(reinterpret_cast(chars), nchars); JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars); if (!str) return false; result.set(str); - break; + return true; + } + + // Check presence of "SourceMap:" HTTP response header. + char* sourceMapURL = metadata().sourceMapURL.get(); + if (sourceMapURL && strlen(sourceMapURL)) { + UTF8Chars utf8Chars(sourceMapURL, strlen(sourceMapURL)); + JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars); + if (!str) + return false; + result.set(str); } return true; } void DebugState::addSizeOfMisc(MallocSizeOf mallocSizeOf, Metadata::SeenSet* seenMetadata, ShareableBytes::SeenSet* seenBytes, diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -175,16 +175,28 @@ ModuleGenerator::init(Metadata* maybeAsm } if (compileArgs_->scriptedCaller.filename) { metadata_->filename = DuplicateString(compileArgs_->scriptedCaller.filename.get()); if (!metadata_->filename) return false; } + if (compileArgs_->responseURLs.baseURL) { + metadata_->baseURL = DuplicateString(compileArgs_->responseURLs.baseURL.get()); + if (!metadata_->baseURL) + return false; + } + + if (compileArgs_->responseURLs.sourceMapURL) { + metadata_->sourceMapURL = DuplicateString(compileArgs_->responseURLs.sourceMapURL.get()); + if (!metadata_->sourceMapURL) + return false; + } + if (!linkData_.initTier1(tier(), *metadata_)) return false; linkDataTier_ = &linkData_.linkData(tier()); if (!assumptions_.clone(compileArgs_->assumptions)) return false; diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -790,17 +790,17 @@ GetBufferSource(JSContext* cx, JSObject* if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) { ReportOutOfMemory(cx); return false; } return true; } -static SharedCompileArgs +static MutableCompileArgs InitCompileArgs(JSContext* cx) { ScriptedCaller scriptedCaller; if (!DescribeScriptedCaller(cx, &scriptedCaller)) return nullptr; MutableCompileArgs compileArgs = cx->new_(); if (!compileArgs) @@ -2205,17 +2205,17 @@ RejectWithErrorNumber(JSContext* cx, uin } class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer { enum StreamState { Env, Code, Tail, Closed }; typedef ExclusiveWaitableData ExclusiveStreamState; // Immutable: - const SharedCompileArgs compileArgs_; + const MutableCompileArgs compileArgs_; // immutable during streaming const bool instantiate_; const PersistentRootedObject importObj_; // Mutated on a stream thread (consumeChunk() and streamClosed()): ExclusiveStreamState streamState_; Bytes envBytes_; // immutable after Env state SectionRange codeSection_; // immutable after Env state Bytes codeBytes_; // not resized after Env state @@ -2225,16 +2225,25 @@ class CompileStreamTask : public Promise ExclusiveTailBytesPtr exclusiveTailBytes_; Maybe streamError_; Atomic streamFailed_; // Mutated on helper thread (execute()): SharedModule module_; UniqueChars compileError_; + // Called on some thread before consumeChunk() or streamClosed(): + + void noteResponseURLs(const char* url, const char* sourceMapUrl) override { + if (url) + compileArgs_->responseURLs.baseURL = DuplicateString(url); + if (sourceMapUrl) + compileArgs_->responseURLs.sourceMapURL = DuplicateString(sourceMapUrl); + } + // Called on a stream thread: // Until StartOffThreadPromiseHelperTask succeeds, we are responsible for // dispatching ourselves back to the JS thread. // // Warning: After this function returns, 'this' can be deleted at any time, so the // caller must immediately return from the stream callback. void setClosedAndDestroyBeforeHelperThreadStarted() { @@ -2411,17 +2420,17 @@ class CompileStreamTask : public Promise ? Resolve(cx, *module_, *compileArgs_, promise, instantiate_, importObj_) : streamError_ ? RejectWithErrorNumber(cx, *streamError_, promise) : Reject(cx, *compileArgs_, Move(compileError_), promise); } public: CompileStreamTask(JSContext* cx, Handle promise, - const CompileArgs& compileArgs, bool instantiate, + CompileArgs& compileArgs, bool instantiate, HandleObject importObj) : PromiseHelperTask(cx, promise), compileArgs_(&compileArgs), instantiate_(instantiate), importObj_(cx, importObj), streamState_(mutexid::WasmStreamStatus, Env), codeStreamEnd_(nullptr), exclusiveCodeStreamEnd_(mutexid::WasmCodeStreamEnd, nullptr), @@ -2446,37 +2455,37 @@ class ResolveResponseClosure : public Na static void finalize(FreeOp* fop, JSObject* obj) { obj->as().compileArgs().Release(); } public: static const unsigned RESERVED_SLOTS = 4; static const Class class_; - static ResolveResponseClosure* create(JSContext* cx, const CompileArgs& args, + static ResolveResponseClosure* create(JSContext* cx, CompileArgs& args, HandleObject promise, bool instantiate, HandleObject importObj) { MOZ_ASSERT_IF(importObj, instantiate); AutoSetNewObjectMetadata metadata(cx); auto* obj = NewObjectWithGivenProto(cx, nullptr); if (!obj) return nullptr; args.AddRef(); - obj->setReservedSlot(COMPILE_ARGS_SLOT, PrivateValue(const_cast(&args))); + obj->setReservedSlot(COMPILE_ARGS_SLOT, PrivateValue(&args)); obj->setReservedSlot(PROMISE_OBJ_SLOT, ObjectValue(*promise)); obj->setReservedSlot(INSTANTIATE_SLOT, BooleanValue(instantiate)); obj->setReservedSlot(IMPORT_OBJ_SLOT, ObjectOrNullValue(importObj)); return obj; } - const CompileArgs& compileArgs() const { - return *(const CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate(); + CompileArgs& compileArgs() const { + return *(CompileArgs*)getReservedSlot(COMPILE_ARGS_SLOT).toPrivate(); } PromiseObject& promise() const { return getReservedSlot(PROMISE_OBJ_SLOT).toObject().as(); } bool instantiate() const { return getReservedSlot(INSTANTIATE_SLOT).toBoolean(); } JSObject* importObj() const { @@ -2514,17 +2523,17 @@ ToResolveResponseClosure(CallArgs args) static bool ResolveResponse_OnFulfilled(JSContext* cx, unsigned argc, Value* vp) { CallArgs callArgs = CallArgsFromVp(argc, vp); Rooted closure(cx, ToResolveResponseClosure(callArgs)); Rooted promise(cx, &closure->promise()); - const CompileArgs& compileArgs = closure->compileArgs(); + CompileArgs& compileArgs = closure->compileArgs(); bool instantiate = closure->instantiate(); Rooted importObj(cx, closure->importObj()); auto task = cx->make_unique(cx, promise, compileArgs, instantiate, importObj); if (!task || !task->init(cx)) return false; if (!callArgs.get(0).isObject()) @@ -2556,17 +2565,17 @@ ResolveResponse_OnRejected(JSContext* cx } static bool ResolveResponse(JSContext* cx, CallArgs callArgs, Handle promise, bool instantiate = false, HandleObject importObj = nullptr) { MOZ_ASSERT_IF(importObj, instantiate); - SharedCompileArgs compileArgs = InitCompileArgs(cx); + MutableCompileArgs compileArgs = InitCompileArgs(cx); if (!compileArgs) return false; RootedObject closure(cx, ResolveResponseClosure::create(cx, *compileArgs, promise, instantiate, importObj)); if (!closure) return false;