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 |