Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "modules/wasm/WasmResponseExtensions.h" | |
| 6 | |
| 7 #include "bindings/core/v8/ExceptionState.h" | |
| 8 #include "bindings/core/v8/ScriptPromise.h" | |
| 9 #include "bindings/core/v8/ScriptPromiseResolver.h" | |
| 10 #include "bindings/core/v8/ScriptState.h" | |
| 11 #include "bindings/modules/v8/V8Response.h" | |
| 12 #include "modules/fetch/BodyStreamBuffer.h" | |
| 13 #include "modules/fetch/FetchDataLoader.h" | |
| 14 #include "platform/heap/Handle.h" | |
| 15 #include "wtf/RefPtr.h" | |
| 16 | |
| 17 namespace blink { | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 class FetchDataLoaderAsWasmModule final : public FetchDataLoader, | |
| 22 public BytesConsumer::Client { | |
| 23 USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderAsWasmModule); | |
| 24 | |
| 25 public: | |
| 26 FetchDataLoaderAsWasmModule(ScriptPromiseResolver* resolver, | |
| 27 ScriptState* scriptState) | |
| 28 : m_resolver(resolver), | |
| 29 m_builder(scriptState->isolate()), | |
| 30 m_scriptState(scriptState) {} | |
| 31 | |
| 32 void start(BytesConsumer* consumer, | |
| 33 FetchDataLoader::Client* client) override { | |
| 34 DCHECK(!m_consumer); | |
| 35 DCHECK(!m_client); | |
| 36 m_client = client; | |
| 37 m_consumer = consumer; | |
| 38 m_consumer->setClient(this); | |
| 39 onStateChange(); | |
| 40 } | |
| 41 | |
| 42 void onStateChange() override { | |
| 43 while (true) { | |
| 44 // buffer is allocated by beginRead and de-allocated by endRead. | |
|
haraken
2017/04/07 10:22:40
allocated / deallocated sounds misleading to me. b
Mircea Trofin
2017/04/07 15:33:08
True - rephrased.
| |
| 45 const char* buffer = nullptr; | |
| 46 size_t available = 0; | |
| 47 BytesConsumer::Result result = m_consumer->beginRead(&buffer, &available); | |
| 48 | |
| 49 if (result == BytesConsumer::Result::ShouldWait) | |
| 50 return; | |
| 51 if (result == BytesConsumer::Result::Ok) { | |
| 52 if (available > 0) { | |
| 53 DCHECK_NE(buffer, nullptr); | |
| 54 m_builder.OnBytesReceived(reinterpret_cast<const uint8_t*>(buffer), | |
| 55 available); | |
| 56 } | |
| 57 result = m_consumer->endRead(available); | |
| 58 } | |
| 59 switch (result) { | |
| 60 case BytesConsumer::Result::ShouldWait: | |
| 61 NOTREACHED(); | |
| 62 return; | |
| 63 case BytesConsumer::Result::Ok: { | |
| 64 break; | |
| 65 } | |
| 66 case BytesConsumer::Result::Done: { | |
| 67 v8::Isolate* isolate = m_scriptState->isolate(); | |
| 68 ScriptState::Scope scope(m_scriptState.get()); | |
| 69 v8::TryCatch trycatch(isolate); | |
| 70 v8::Local<v8::WasmCompiledModule> module; | |
| 71 if (m_builder.Finish().ToLocal(&module)) { | |
| 72 DCHECK(!trycatch.HasCaught()); | |
| 73 ScriptValue scriptValue(m_scriptState.get(), module); | |
| 74 m_resolver->resolve(scriptValue); | |
| 75 } else { | |
| 76 DCHECK(trycatch.HasCaught()); | |
| 77 m_resolver->reject(trycatch.Exception()); | |
| 78 trycatch.Reset(); | |
|
haraken
2017/04/07 10:22:40
I don't fully understand why you need to reset it.
Mircea Trofin
2017/04/07 15:33:09
Granted, the destructor of TryCatch clears the exc
| |
| 79 } | |
| 80 m_client->didFetchDataLoadedCustomFormat(); | |
| 81 return; | |
| 82 } | |
| 83 case BytesConsumer::Result::Error: { | |
| 84 // TODO(mtrofin): do we need an abort on the wasm side? | |
| 85 // Something like "m_outStream->abort()" maybe? | |
| 86 return rejectPromise(); | |
| 87 } | |
| 88 } | |
| 89 } | |
| 90 } | |
| 91 | |
| 92 void cancel() override { | |
| 93 m_consumer->cancel(); | |
| 94 return rejectPromise(); | |
| 95 } | |
| 96 | |
| 97 DEFINE_INLINE_TRACE() { | |
| 98 visitor->trace(m_consumer); | |
| 99 visitor->trace(m_resolver); | |
| 100 visitor->trace(m_client); | |
| 101 FetchDataLoader::trace(visitor); | |
| 102 BytesConsumer::Client::trace(visitor); | |
| 103 } | |
| 104 | |
| 105 private: | |
| 106 // TODO(mtrofin): replace with spec-ed error types, once spec clarifies | |
| 107 // what they are. | |
| 108 void rejectPromise() { | |
| 109 m_resolver->reject(V8ThrowException::createTypeError( | |
| 110 m_scriptState->isolate(), "Could not download wasm module")); | |
| 111 } | |
| 112 Member<BytesConsumer> m_consumer; | |
| 113 Member<ScriptPromiseResolver> m_resolver; | |
| 114 Member<FetchDataLoader::Client> m_client; | |
| 115 v8::WasmModuleObjectBuilder m_builder; | |
| 116 const RefPtr<ScriptState> m_scriptState; | |
| 117 }; | |
| 118 | |
| 119 // TODO(mtrofin): WasmDataLoaderClient is necessary so we may provide an | |
| 120 // argument to BodyStreamBuffer::startLoading, however, it fulfills | |
| 121 // a very small role. Consider refactoring to avoid it. | |
| 122 class WasmDataLoaderClient final | |
| 123 : public GarbageCollectedFinalized<WasmDataLoaderClient>, | |
| 124 public FetchDataLoader::Client { | |
| 125 WTF_MAKE_NONCOPYABLE(WasmDataLoaderClient); | |
| 126 USING_GARBAGE_COLLECTED_MIXIN(WasmDataLoaderClient); | |
| 127 | |
| 128 public: | |
| 129 explicit WasmDataLoaderClient() {} | |
| 130 void didFetchDataLoadedCustomFormat() override {} | |
| 131 void didFetchDataLoadFailed() override { NOTREACHED(); } | |
| 132 }; | |
| 133 | |
| 134 // This callback may be entered as a promise is resolved, or directly | |
| 135 // from the overload callback. | |
| 136 // See | |
| 137 // https://github.com/WebAssembly/design/blob/master/Web.md#webassemblycompile | |
| 138 void compileFromResponseCallback( | |
| 139 const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 140 ExceptionState exceptionState(args.GetIsolate(), | |
| 141 ExceptionState::ExecutionContext, "WebAssembly", | |
| 142 "compile"); | |
| 143 ExceptionToRejectPromiseScope rejectPromiseScope(args, exceptionState); | |
| 144 | |
| 145 ScriptState* scriptState = ScriptState::forReceiverObject(args); | |
| 146 if (!scriptState->getExecutionContext()) { | |
| 147 v8SetReturnValue(args, ScriptPromise().v8Value()); | |
| 148 return; | |
| 149 } | |
| 150 | |
| 151 if (args.Length() < 1 || !args[0]->IsObject() || | |
| 152 !V8Response::hasInstance(args[0], args.GetIsolate())) { | |
| 153 v8SetReturnValue( | |
| 154 args, ScriptPromise::reject( | |
| 155 scriptState, V8ThrowException::createTypeError( | |
| 156 scriptState->isolate(), | |
| 157 "Promise argument must be called with a " | |
| 158 "Promise<Response> object")) | |
| 159 .v8Value()); | |
| 160 return; | |
| 161 } | |
| 162 | |
| 163 Response* response = V8Response::toImpl(v8::Local<v8::Object>::Cast(args[0])); | |
| 164 ScriptPromise promise; | |
| 165 if (response->isBodyLocked() || response->bodyUsed()) { | |
| 166 promise = ScriptPromise::reject( | |
| 167 scriptState, | |
| 168 V8ThrowException::createTypeError( | |
| 169 scriptState->isolate(), | |
| 170 "Cannot compile WebAssembly.Module from an already read Response")); | |
| 171 } else { | |
| 172 ScriptPromiseResolver* resolver = | |
| 173 ScriptPromiseResolver::create(scriptState); | |
| 174 if (response->bodyBuffer()) { | |
| 175 promise = resolver->promise(); | |
| 176 response->bodyBuffer()->startLoading( | |
| 177 new FetchDataLoaderAsWasmModule(resolver, scriptState), | |
| 178 new WasmDataLoaderClient()); | |
| 179 } else { | |
| 180 promise = ScriptPromise::reject( | |
| 181 scriptState, | |
| 182 V8ThrowException::createTypeError( | |
| 183 scriptState->isolate(), "Response object has a null body.")); | |
| 184 } | |
| 185 } | |
| 186 v8SetReturnValue(args, promise.v8Value()); | |
| 187 } | |
| 188 | |
| 189 // See https://crbug.com/708238 for tracking avoiding the hand-generated code. | |
| 190 bool wasmCompileOverload(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 191 if (args.Length() < 1 || !args[0]->IsObject()) | |
| 192 return false; | |
| 193 | |
| 194 if (!args[0]->IsPromise() && | |
| 195 !V8Response::hasInstance(args[0], args.GetIsolate())) | |
| 196 return false; | |
| 197 | |
| 198 v8::Isolate* isolate = args.GetIsolate(); | |
| 199 ScriptState* scriptState = ScriptState::forReceiverObject(args); | |
| 200 | |
| 201 v8::Local<v8::Function> compileCallback = | |
| 202 v8::Function::New(isolate, compileFromResponseCallback); | |
| 203 | |
|
yhirano
2017/04/07 07:39:47
V8SetReturnValue(args, ScriptPromise::cast(scriptS
Mircea Trofin
2017/04/07 15:33:08
Nice! Thanks!
| |
| 204 ScriptPromiseResolver* scriptPromiseResolver = | |
| 205 ScriptPromiseResolver::create(scriptState); | |
| 206 // treat either case of parameter as | |
| 207 // Promise.resolve(parameter) | |
| 208 // as per https://www.w3.org/2001/tag/doc/promises-guide#resolve-arguments | |
| 209 | |
| 210 // Ending with: | |
| 211 // return Promise.resolve(parameter).then(compileCallback); | |
| 212 ScriptPromise parameterAsPromise = scriptPromiseResolver->promise(); | |
| 213 v8SetReturnValue(args, parameterAsPromise.then(compileCallback).v8Value()); | |
| 214 | |
| 215 // resolve the first parameter promise. | |
| 216 scriptPromiseResolver->resolve(ScriptValue::from(scriptState, args[0])); | |
| 217 return true; | |
| 218 } | |
| 219 | |
| 220 } // namespace | |
| 221 | |
| 222 void WasmResponseExtensions::initialize(v8::Isolate* isolate) { | |
| 223 isolate->SetWasmCompileCallback(wasmCompileOverload); | |
| 224 } | |
| 225 | |
| 226 } // namespace blink | |
| OLD | NEW |