Bug 1431864 - Use Response.url as WebAssembly module URL. r=luke,bkelly draft
authorYury Delendik <ydelendik@mozilla.com>
Mon, 22 Jan 2018 14:55:30 -0600
changeset 724461 f78c3d77e49a42cdb026b811ba64c230207955e0
parent 724080 0e62eb7804c00c0996a9bdde5350328a384fb7af
child 747151 73e729641b591fc9d174a56f3c5dc3e9f68cbe53
push id96747
push userydelendik@mozilla.com
push dateThu, 25 Jan 2018 00:07:08 +0000
reviewersluke, bkelly
bugs1431864
milestone60.0a1
Bug 1431864 - Use Response.url as WebAssembly module URL. r=luke,bkelly MozReview-Commit-ID: 2xAasdmpIKX
dom/fetch/FetchUtil.cpp
js/src/jit-test/tests/debug/wasm-responseurls.js
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmCompile.h
js/src/wasm/WasmDebug.cpp
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmJS.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<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;