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 "bindings/modules/v8/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 owned by {m_consumer}. | |
| 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 | |
| 70 { | |
| 71 // The TryCatch destructor will clear the exception. We | |
| 72 // scope the block here to ensure tight control over the | |
| 73 // lifetime of the exception. | |
| 74 v8::TryCatch trycatch(isolate); | |
| 75 v8::Local<v8::WasmCompiledModule> module; | |
| 76 if (m_builder.Finish().ToLocal(&module)) { | |
| 77 DCHECK(!trycatch.HasCaught()); | |
| 78 ScriptValue scriptValue(m_scriptState.get(), module); | |
| 79 m_resolver->resolve(scriptValue); | |
| 80 } else { | |
| 81 DCHECK(trycatch.HasCaught()); | |
| 82 m_resolver->reject(trycatch.Exception()); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 m_client->didFetchDataLoadedCustomFormat(); | |
| 87 return; | |
| 88 } | |
| 89 case BytesConsumer::Result::Error: { | |
| 90 // TODO(mtrofin): do we need an abort on the wasm side? | |
| 91 // Something like "m_outStream->abort()" maybe? | |
| 92 return rejectPromise(); | |
| 93 } | |
| 94 } | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 void cancel() override { | |
| 99 m_consumer->cancel(); | |
| 100 return rejectPromise(); | |
| 101 } | |
| 102 | |
| 103 DEFINE_INLINE_TRACE() { | |
| 104 visitor->trace(m_consumer); | |
| 105 visitor->trace(m_resolver); | |
| 106 visitor->trace(m_client); | |
| 107 FetchDataLoader::trace(visitor); | |
| 108 BytesConsumer::Client::trace(visitor); | |
| 109 } | |
| 110 | |
| 111 private: | |
| 112 // TODO(mtrofin): replace with spec-ed error types, once spec clarifies | |
| 113 // what they are. | |
| 114 void rejectPromise() { | |
| 115 m_resolver->reject(V8ThrowException::createTypeError( | |
| 116 m_scriptState->isolate(), "Could not download wasm module")); | |
| 117 } | |
| 118 Member<BytesConsumer> m_consumer; | |
| 119 Member<ScriptPromiseResolver> m_resolver; | |
| 120 Member<FetchDataLoader::Client> m_client; | |
| 121 v8::WasmModuleObjectBuilder m_builder; | |
| 122 const RefPtr<ScriptState> m_scriptState; | |
| 123 }; | |
| 124 | |
| 125 // TODO(mtrofin): WasmDataLoaderClient is necessary so we may provide an | |
| 126 // argument to BodyStreamBuffer::startLoading, however, it fulfills | |
| 127 // a very small role. Consider refactoring to avoid it. | |
| 128 class WasmDataLoaderClient final | |
| 129 : public GarbageCollectedFinalized<WasmDataLoaderClient>, | |
| 130 public FetchDataLoader::Client { | |
| 131 WTF_MAKE_NONCOPYABLE(WasmDataLoaderClient); | |
| 132 USING_GARBAGE_COLLECTED_MIXIN(WasmDataLoaderClient); | |
| 133 | |
| 134 public: | |
| 135 explicit WasmDataLoaderClient() {} | |
| 136 void didFetchDataLoadedCustomFormat() override {} | |
| 137 void didFetchDataLoadFailed() override { NOTREACHED(); } | |
| 138 }; | |
| 139 | |
| 140 // This callback may be entered as a promise is resolved, or directly | |
| 141 // from the overload callback. | |
| 142 // See | |
| 143 // https://github.com/WebAssembly/design/blob/master/Web.md#webassemblycompile | |
| 144 void compileFromResponseCallback( | |
| 145 const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 146 ExceptionState exceptionState(args.GetIsolate(), | |
| 147 ExceptionState::ExecutionContext, "WebAssembly", | |
| 148 "compile"); | |
| 149 ExceptionToRejectPromiseScope rejectPromiseScope(args, exceptionState); | |
| 150 | |
| 151 ScriptState* scriptState = ScriptState::forReceiverObject(args); | |
| 152 if (!scriptState->getExecutionContext()) { | |
| 153 v8SetReturnValue(args, ScriptPromise().v8Value()); | |
| 154 return; | |
| 155 } | |
| 156 | |
| 157 if (args.Length() < 1 || !args[0]->IsObject() || | |
| 158 !V8Response::hasInstance(args[0], args.GetIsolate())) { | |
| 159 v8SetReturnValue( | |
| 160 args, ScriptPromise::reject( | |
| 161 scriptState, V8ThrowException::createTypeError( | |
| 162 scriptState->isolate(), | |
| 163 "Promise argument must be called with a " | |
| 164 "Promise<Response> object")) | |
| 165 .v8Value()); | |
| 166 return; | |
| 167 } | |
| 168 | |
| 169 Response* response = V8Response::toImpl(v8::Local<v8::Object>::Cast(args[0])); | |
| 170 ScriptPromise promise; | |
| 171 if (response->isBodyLocked() || response->bodyUsed()) { | |
| 172 promise = ScriptPromise::reject( | |
| 173 scriptState, | |
| 174 V8ThrowException::createTypeError( | |
| 175 scriptState->isolate(), | |
| 176 "Cannot compile WebAssembly.Module from an already read Response")); | |
| 177 } else { | |
| 178 ScriptPromiseResolver* resolver = | |
| 179 ScriptPromiseResolver::create(scriptState); | |
| 180 if (response->bodyBuffer()) { | |
| 181 promise = resolver->promise(); | |
| 182 response->bodyBuffer()->startLoading( | |
| 183 new FetchDataLoaderAsWasmModule(resolver, scriptState), | |
| 184 new WasmDataLoaderClient()); | |
| 185 } else { | |
| 186 promise = ScriptPromise::reject( | |
| 187 scriptState, | |
| 188 V8ThrowException::createTypeError( | |
| 189 scriptState->isolate(), "Response object has a null body.")); | |
| 190 } | |
| 191 } | |
| 192 v8SetReturnValue(args, promise.v8Value()); | |
| 193 } | |
| 194 | |
| 195 // See https://crbug.com/708238 for tracking avoiding the hand-generated code. | |
| 196 bool wasmCompileOverload(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
| 197 if (args.Length() < 1 || !args[0]->IsObject()) | |
| 198 return false; | |
| 199 | |
| 200 if (!args[0]->IsPromise() && | |
| 201 !V8Response::hasInstance(args[0], args.GetIsolate())) | |
| 202 return false; | |
| 203 | |
| 204 v8::Isolate* isolate = args.GetIsolate(); | |
| 205 ScriptState* scriptState = ScriptState::forReceiverObject(args); | |
| 206 | |
| 207 v8::Local<v8::Function> compileCallback = | |
| 208 v8::Function::New(isolate, compileFromResponseCallback); | |
| 209 | |
| 210 ScriptPromiseResolver* scriptPromiseResolver = | |
|
yhirano
2017/04/10 04:38:54
You don't need this.
| |
| 211 ScriptPromiseResolver::create(scriptState); | |
| 212 // treat either case of parameter as | |
| 213 // Promise.resolve(parameter) | |
| 214 // as per https://www.w3.org/2001/tag/doc/promises-guide#resolve-arguments | |
| 215 | |
| 216 // Ending with: | |
| 217 // return Promise.resolve(parameter).then(compileCallback); | |
| 218 ScriptPromise parameterAsPromise = scriptPromiseResolver->promise(); | |
|
yhirano
2017/04/10 04:38:54
ditto
| |
| 219 v8SetReturnValue(args, ScriptPromise::cast(scriptState, args[0]) | |
| 220 .then(compileCallback) | |
| 221 .v8Value()); | |
| 222 | |
| 223 // resolve the first parameter promise. | |
| 224 scriptPromiseResolver->resolve(ScriptValue::from(scriptState, args[0])); | |
|
yhirano
2017/04/10 04:38:54
ditto
| |
| 225 return true; | |
| 226 } | |
| 227 | |
| 228 } // namespace | |
| 229 | |
| 230 void WasmResponseExtensions::initialize(v8::Isolate* isolate) { | |
| 231 isolate->SetWasmCompileCallback(wasmCompileOverload); | |
| 232 } | |
| 233 | |
| 234 } // namespace blink | |
| OLD | NEW |