Bug 1431864 - Use Response.url as WebAssembly module URL. r=luke,bkelly
MozReview-Commit-ID: 2xAasdmpIKX
--- 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<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) {
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");
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4590,16 +4590,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);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5921,25 +5921,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<JSLinearString*> 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<uint8_t*> 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<const char*>(urlStr->latin1Chars(nogc))
+ : nullptr,
+ mapUrlStr
+ ? reinterpret_cast<const char*>(mapUrlStr->latin1Chars(nogc))
+ : nullptr);
+ }
+
auto job = cx->make_unique<BufferStreamJob>(consumer);
if (!job || !job->bytes.resize(byteLength))
return false;
memcpy(job->bytes.begin(), dataPointer.unwrap(), byteLength);
BufferStreamJob* jobPtr = job.get();
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -7116,16 +7116,22 @@ class DebuggerSourceGetURLMatcher
MOZ_ASSERT(ss);
if (ss->filename()) {
JSString* str = NewStringCopyZ<CanGC>(cx_, ss->filename());
return Some(str);
}
return Nothing();
}
ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
+ if (wasmInstance->instance().metadata().baseURL) {
+ JSString* str = NewStringCopyZ<CanGC>(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)
--- 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
{
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -404,16 +404,18 @@ class Metadata : public ShareableBase<Me
}
SigWithIdVector sigIds;
GlobalDescVector globals;
TableDescVector tables;
NameInBytecodeVector funcNames;
CustomSectionVector customSections;
CacheableChars filename;
+ CacheableChars baseURL;
+ CacheableChars sourceMapURL;
// Debug-enabled code is not serialized.
bool debugEnabled;
FuncArgTypesVector debugFuncArgTypes;
FuncReturnTypesVector debugFuncReturnTypes;
ModuleHash debugHash;
bool usesMemory() const { return memoryUsage != MemoryUsage::None; }
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -28,22 +28,29 @@ namespace wasm {
struct ScriptedCaller
{
UniqueChars filename;
unsigned line;
unsigned column;
};
+struct ResponseURLs
+{
+ UniqueChars baseURL;
+ UniqueChars sourceMapURL;
+};
+
// Describes all the parameters that control wasm compilation.
struct CompileArgs : ShareableBase<CompileArgs>
{
Assumptions assumptions;
ScriptedCaller scriptedCaller;
+ ResponseURLs responseURLs;
bool baselineEnabled;
bool debugEnabled;
bool ionEnabled;
bool sharedMemoryEnabled;
bool testTiering;
CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
: assumptions(Move(assumptions)),
--- a/js/src/wasm/WasmDebug.cpp
+++ b/js/src/wasm/WasmDebug.cpp
@@ -675,17 +675,27 @@ DebugState::getSourceMappingURL(JSContex
if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end())
return true; // ignoring invalid section data
UTF8Chars utf8Chars(reinterpret_cast<const char*>(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,
--- 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;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -786,17 +786,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_<CompileArgs>();
if (!compileArgs)
@@ -2191,17 +2191,17 @@ RejectWithErrorNumber(JSContext* cx, uin
}
class CompileStreamTask : public PromiseHelperTask, public JS::StreamConsumer
{
enum StreamState { Env, Code, Tail, Closed };
typedef ExclusiveWaitableData<StreamState> 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
@@ -2211,16 +2211,25 @@ class CompileStreamTask : public Promise
ExclusiveTailBytesPtr exclusiveTailBytes_;
Maybe<uint32_t> streamError_;
Atomic<bool> 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() {
@@ -2397,17 +2406,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<PromiseObject*> 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),
@@ -2432,37 +2441,37 @@ class ResolveResponseClosure : public Na
static void finalize(FreeOp* fop, JSObject* obj) {
obj->as<ResolveResponseClosure>().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<ResolveResponseClosure>(cx, nullptr);
if (!obj)
return nullptr;
args.AddRef();
- obj->setReservedSlot(COMPILE_ARGS_SLOT, PrivateValue(const_cast<CompileArgs*>(&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<PromiseObject>();
}
bool instantiate() const {
return getReservedSlot(INSTANTIATE_SLOT).toBoolean();
}
JSObject* importObj() const {
@@ -2498,17 +2507,17 @@ ToResolveResponseClosure(CallArgs args)
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());
- const CompileArgs& compileArgs = closure->compileArgs();
+ CompileArgs& compileArgs = closure->compileArgs();
bool instantiate = closure->instantiate();
Rooted<JSObject*> importObj(cx, closure->importObj());
auto task = cx->make_unique<CompileStreamTask>(cx, promise, compileArgs, instantiate, importObj);
if (!task || !task->init(cx))
return false;
if (!callArgs.get(0).isObject())
@@ -2540,17 +2549,17 @@ ResolveResponse_OnRejected(JSContext* cx
}
static bool
ResolveResponse(JSContext* cx, CallArgs callArgs, Handle<PromiseObject*> 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;