OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 "config.h" | |
6 #include "modules/serviceworkers/Body.h" | |
7 | |
8 #include "bindings/core/v8/ExceptionState.h" | |
9 #include "bindings/core/v8/ScriptPromiseResolver.h" | |
10 #include "bindings/core/v8/ScriptState.h" | |
11 #include "bindings/core/v8/V8ArrayBuffer.h" | |
12 #include "bindings/core/v8/V8ThrowException.h" | |
13 #include "core/dom/DOMArrayBuffer.h" | |
14 #include "core/fileapi/Blob.h" | |
15 #include "core/fileapi/FileReaderLoader.h" | |
16 #include "core/fileapi/FileReaderLoaderClient.h" | |
17 #include "core/streams/UnderlyingSource.h" | |
18 #include "modules/serviceworkers/BodyStreamBuffer.h" | |
19 | |
20 namespace blink { | |
21 | |
22 class Body::BlobHandleReceiver final : public BodyStreamBuffer::BlobHandleCreato
rClient { | |
23 public: | |
24 explicit BlobHandleReceiver(Body* body) | |
25 : m_body(body) | |
26 { | |
27 } | |
28 void didCreateBlobHandle(PassRefPtr<BlobDataHandle> handle) override | |
29 { | |
30 ASSERT(m_body); | |
31 m_body->readAsyncFromBlob(handle); | |
32 m_body = nullptr; | |
33 } | |
34 void didFail(PassRefPtrWillBeRawPtr<DOMException> exception) override | |
35 { | |
36 ASSERT(m_body); | |
37 m_body->didBlobHandleReceiveError(exception); | |
38 m_body = nullptr; | |
39 } | |
40 void trace(Visitor* visitor) override | |
41 { | |
42 BodyStreamBuffer::BlobHandleCreatorClient::trace(visitor); | |
43 visitor->trace(m_body); | |
44 } | |
45 private: | |
46 Member<Body> m_body; | |
47 }; | |
48 | |
49 class Body::ReadableStreamSource : public GarbageCollectedFinalized<ReadableStre
amSource>, public UnderlyingSource { | |
50 USING_GARBAGE_COLLECTED_MIXIN(ReadableStreamSource); | |
51 public: | |
52 ReadableStreamSource(Body* body) : m_body(body) { } | |
53 ~ReadableStreamSource() override { } | |
54 void pullSource() override { m_body->pullSource(); } | |
55 | |
56 ScriptPromise cancelSource(ScriptState* scriptState, ScriptValue reason) ove
rride | |
57 { | |
58 return ScriptPromise(); | |
59 } | |
60 | |
61 void trace(Visitor* visitor) override | |
62 { | |
63 visitor->trace(m_body); | |
64 UnderlyingSource::trace(visitor); | |
65 } | |
66 | |
67 private: | |
68 Member<Body> m_body; | |
69 }; | |
70 | |
71 void Body::pullSource() | |
72 { | |
73 if (!m_streamAccessed) { | |
74 // We do not download data unless the user explicitly uses the | |
75 // ReadableStream object in order to avoid performance regression, | |
76 // because currently Chrome cannot handle Streams efficiently | |
77 // especially with ServiceWorker or Blob. | |
78 return; | |
79 } | |
80 if (m_bodyUsed) { | |
81 m_stream->error(DOMException::create(InvalidStateError, "The stream is l
ocked.")); | |
82 return; | |
83 } | |
84 ASSERT(!m_loader); | |
85 if (buffer()) { | |
86 // If the body has a body buffer, we read all data from the buffer and | |
87 // create a blob and then put the data from the blob to |m_stream|. | |
88 // FIXME: Put the data directry from the buffer. | |
89 buffer()->readAllAndCreateBlobHandle(contentTypeForBuffer(), new BlobHan
dleReceiver(this)); | |
90 return; | |
91 } | |
92 RefPtr<BlobDataHandle> blobHandle = blobDataHandle(); | |
93 if (!blobHandle.get()) { | |
94 blobHandle = BlobDataHandle::create(BlobData::create(), 0); | |
95 } | |
96 readAsyncFromBlob(blobHandle); | |
97 } | |
98 | |
99 ScriptPromise Body::readAsync(ScriptState* scriptState, ResponseType type) | |
100 { | |
101 if (m_bodyUsed) | |
102 return ScriptPromise::reject(scriptState, V8ThrowException::createTypeEr
ror(scriptState->isolate(), "Already read")); | |
103 | |
104 // When the main thread sends a V8::TerminateExecution() signal to a worker | |
105 // thread, any V8 API on the worker thread starts returning an empty | |
106 // handle. This can happen in Body::readAsync. To avoid the situation, we | |
107 // first check the ExecutionContext and return immediately if it's already | |
108 // gone (which means that the V8::TerminateExecution() signal has been sent | |
109 // to this worker thread). | |
110 ExecutionContext* executionContext = scriptState->executionContext(); | |
111 if (!executionContext) | |
112 return ScriptPromise(); | |
113 | |
114 m_bodyUsed = true; | |
115 m_responseType = type; | |
116 | |
117 ASSERT(!m_resolver); | |
118 m_resolver = ScriptPromiseResolver::create(scriptState); | |
119 ScriptPromise promise = m_resolver->promise(); | |
120 | |
121 if (m_streamAccessed) { | |
122 // 'body' attribute was accessed and the stream source started pulling. | |
123 switch (m_stream->state()) { | |
124 case ReadableStream::Readable: | |
125 readAllFromStream(scriptState); | |
126 return promise; | |
127 case ReadableStream::Waiting: | |
128 // m_loader is working and m_resolver will be resolved when it | |
129 // ends. | |
130 return promise; | |
131 case ReadableStream::Closed: | |
132 case ReadableStream::Errored: | |
133 m_resolver->resolve(m_stream->closed(scriptState).v8Value()); | |
134 return promise; | |
135 break; | |
136 } | |
137 ASSERT_NOT_REACHED(); | |
138 return promise; | |
139 } | |
140 | |
141 if (buffer()) { | |
142 buffer()->readAllAndCreateBlobHandle(contentTypeForBuffer(), new BlobHan
dleReceiver(this)); | |
143 return promise; | |
144 } | |
145 readAsyncFromBlob(blobDataHandle()); | |
146 return promise; | |
147 } | |
148 | |
149 void Body::readAsyncFromBlob(PassRefPtr<BlobDataHandle> handle) | |
150 { | |
151 if (m_streamAccessed) { | |
152 FileReaderLoader::ReadType readType = FileReaderLoader::ReadAsArrayBuffe
r; | |
153 m_loader = adoptPtr(new FileReaderLoader(readType, this)); | |
154 m_loader->start(executionContext(), handle); | |
155 return; | |
156 } | |
157 FileReaderLoader::ReadType readType = FileReaderLoader::ReadAsText; | |
158 RefPtr<BlobDataHandle> blobHandle = handle; | |
159 if (!blobHandle.get()) { | |
160 blobHandle = BlobDataHandle::create(BlobData::create(), 0); | |
161 } | |
162 switch (m_responseType) { | |
163 case ResponseAsArrayBuffer: | |
164 readType = FileReaderLoader::ReadAsArrayBuffer; | |
165 break; | |
166 case ResponseAsBlob: | |
167 if (blobHandle->size() != kuint64max) { | |
168 // If the size of |blobHandle| is set correctly, creates Blob from | |
169 // it. | |
170 m_resolver->resolve(Blob::create(blobHandle)); | |
171 m_resolver.clear(); | |
172 return; | |
173 } | |
174 // If the size is not set, read as ArrayBuffer and create a new blob to | |
175 // get the size. | |
176 // FIXME: This workaround is not good for performance. | |
177 // When we will stop using Blob as a base system of Body to support | |
178 // stream, this problem should be solved. | |
179 readType = FileReaderLoader::ReadAsArrayBuffer; | |
180 break; | |
181 case ResponseAsFormData: | |
182 // FIXME: Implement this. | |
183 ASSERT_NOT_REACHED(); | |
184 break; | |
185 case ResponseAsJSON: | |
186 case ResponseAsText: | |
187 break; | |
188 default: | |
189 ASSERT_NOT_REACHED(); | |
190 } | |
191 | |
192 m_loader = adoptPtr(new FileReaderLoader(readType, this)); | |
193 m_loader->start(m_resolver->scriptState()->executionContext(), blobHandle); | |
194 | |
195 return; | |
196 } | |
197 | |
198 void Body::readAllFromStream(ScriptState* scriptState) | |
199 { | |
200 // With the current loading mechanism, the data is loaded atomically. | |
201 ASSERT(m_stream->isDraining()); | |
202 TrackExceptionState es; | |
203 // FIXME: Implement and use another |read| method that doesn't | |
204 // need an exception state and V8ArrayBuffer. | |
205 ScriptValue value = m_stream->read(scriptState, es); | |
206 ASSERT(!es.hadException()); | |
207 ASSERT(m_stream->state() == ReadableStream::Closed); | |
208 ASSERT(!value.isEmpty() && V8ArrayBuffer::hasInstance(value.v8Value(), scrip
tState->isolate())); | |
209 DOMArrayBuffer* buffer = V8ArrayBuffer::toImpl(value.v8Value().As<v8::Object
>()); | |
210 didFinishLoadingViaStream(buffer); | |
211 m_resolver.clear(); | |
212 m_stream->close(); | |
213 } | |
214 | |
215 ScriptPromise Body::arrayBuffer(ScriptState* scriptState) | |
216 { | |
217 return readAsync(scriptState, ResponseAsArrayBuffer); | |
218 } | |
219 | |
220 ScriptPromise Body::blob(ScriptState* scriptState) | |
221 { | |
222 return readAsync(scriptState, ResponseAsBlob); | |
223 } | |
224 | |
225 ScriptPromise Body::formData(ScriptState* scriptState) | |
226 { | |
227 return readAsync(scriptState, ResponseAsFormData); | |
228 } | |
229 | |
230 ScriptPromise Body::json(ScriptState* scriptState) | |
231 { | |
232 return readAsync(scriptState, ResponseAsJSON); | |
233 } | |
234 | |
235 ScriptPromise Body::text(ScriptState* scriptState) | |
236 { | |
237 return readAsync(scriptState, ResponseAsText); | |
238 } | |
239 | |
240 ReadableStream* Body::body() | |
241 { | |
242 if (!m_streamAccessed) { | |
243 m_streamAccessed = true; | |
244 if (m_stream->isPulling()) { | |
245 // The stream has been pulling, but the source ignored the | |
246 // instruction because it didn't know the user wanted to use the | |
247 // ReadableStream interface. Now it knows the user does, so have | |
248 // the source start pulling. | |
249 m_streamSource->pullSource(); | |
250 } | |
251 } | |
252 return m_stream; | |
253 } | |
254 | |
255 bool Body::bodyUsed() const | |
256 { | |
257 return m_bodyUsed; | |
258 } | |
259 | |
260 void Body::setBodyUsed() | |
261 { | |
262 m_bodyUsed = true; | |
263 } | |
264 | |
265 bool Body::streamAccessed() const | |
266 { | |
267 return m_streamAccessed; | |
268 } | |
269 | |
270 void Body::stop() | |
271 { | |
272 // Canceling the load will call didFail which will remove the resolver. | |
273 if (m_loader) | |
274 m_loader->cancel(); | |
275 } | |
276 | |
277 bool Body::hasPendingActivity() const | |
278 { | |
279 if (m_resolver) | |
280 return true; | |
281 if (m_streamAccessed && (m_stream->state() == ReadableStream::Readable || m_
stream->state() == ReadableStream::Waiting)) | |
282 return true; | |
283 return false; | |
284 } | |
285 | |
286 void Body::trace(Visitor* visitor) | |
287 { | |
288 visitor->trace(m_stream); | |
289 visitor->trace(m_streamSource); | |
290 } | |
291 | |
292 Body::Body(ExecutionContext* context) | |
293 : ActiveDOMObject(context) | |
294 , m_bodyUsed(false) | |
295 , m_streamAccessed(false) | |
296 , m_responseType(ResponseType::ResponseUnknown) | |
297 , m_streamSource(new ReadableStreamSource(this)) | |
298 , m_stream(new ReadableStreamImpl<ReadableStreamChunkTypeTraits<DOMArrayBuff
er>>(context, m_streamSource)) | |
299 { | |
300 m_stream->didSourceStart(); | |
301 } | |
302 | |
303 Body::Body(const Body& copy_from) | |
304 : ActiveDOMObject(copy_from.lifecycleContext()) | |
305 , m_bodyUsed(copy_from.bodyUsed()) | |
306 , m_responseType(ResponseType::ResponseUnknown) | |
307 , m_streamSource(new ReadableStreamSource(this)) | |
308 , m_stream(new ReadableStreamImpl<ReadableStreamChunkTypeTraits<DOMArrayBuff
er>>(copy_from.executionContext(), m_streamSource)) | |
309 { | |
310 m_stream->didSourceStart(); | |
311 } | |
312 | |
313 void Body::resolveJSON(const String& string) | |
314 { | |
315 ASSERT(m_responseType == ResponseAsJSON); | |
316 ScriptState::Scope scope(m_resolver->scriptState()); | |
317 v8::Isolate* isolate = m_resolver->scriptState()->isolate(); | |
318 v8::Local<v8::String> inputString = v8String(isolate, string); | |
319 v8::TryCatch trycatch; | |
320 v8::Local<v8::Value> parsed = v8::JSON::Parse(inputString); | |
321 if (parsed.IsEmpty()) { | |
322 if (trycatch.HasCaught()) | |
323 m_resolver->reject(trycatch.Exception()); | |
324 else | |
325 m_resolver->reject(v8::Exception::Error(v8::String::NewFromUtf8(isol
ate, "JSON parse error"))); | |
326 return; | |
327 } | |
328 m_resolver->resolve(parsed); | |
329 } | |
330 | |
331 // FileReaderLoaderClient functions. | |
332 void Body::didStartLoading() { } | |
333 void Body::didReceiveData() { } | |
334 void Body::didFinishLoading() | |
335 { | |
336 if (!executionContext() || executionContext()->activeDOMObjectsAreStopped()) | |
337 return; | |
338 | |
339 if (m_streamAccessed) { | |
340 didFinishLoadingViaStream(m_loader->arrayBufferResult().get()); | |
341 m_resolver.clear(); | |
342 m_stream->close(); | |
343 return; | |
344 } | |
345 | |
346 switch (m_responseType) { | |
347 case ResponseAsArrayBuffer: | |
348 m_resolver->resolve(m_loader->arrayBufferResult()); | |
349 break; | |
350 case ResponseAsBlob: { | |
351 ASSERT(blobDataHandle()->size() == kuint64max); | |
352 OwnPtr<BlobData> blobData = BlobData::create(); | |
353 RefPtr<DOMArrayBuffer> buffer = m_loader->arrayBufferResult(); | |
354 blobData->appendBytes(buffer->data(), buffer->byteLength()); | |
355 const size_t length = blobData->length(); | |
356 m_resolver->resolve(Blob::create(BlobDataHandle::create(blobData.release
(), length))); | |
357 break; | |
358 } | |
359 case ResponseAsFormData: | |
360 ASSERT_NOT_REACHED(); | |
361 break; | |
362 case ResponseAsJSON: | |
363 resolveJSON(m_loader->stringResult()); | |
364 break; | |
365 case ResponseAsText: | |
366 m_resolver->resolve(m_loader->stringResult()); | |
367 break; | |
368 default: | |
369 ASSERT_NOT_REACHED(); | |
370 } | |
371 m_resolver.clear(); | |
372 m_stream->close(); | |
373 } | |
374 | |
375 void Body::didFinishLoadingViaStream(DOMArrayBuffer* buffer) | |
376 { | |
377 if (!m_bodyUsed) { | |
378 // |m_stream| is pulling. | |
379 ASSERT(m_streamAccessed); | |
380 m_stream->enqueue(buffer); | |
381 return; | |
382 } | |
383 | |
384 switch (m_responseType) { | |
385 case ResponseAsArrayBuffer: | |
386 m_resolver->resolve(buffer); | |
387 break; | |
388 case ResponseAsBlob: { | |
389 OwnPtr<BlobData> blobData = BlobData::create(); | |
390 blobData->appendBytes(buffer->data(), buffer->byteLength()); | |
391 m_resolver->resolve(Blob::create(BlobDataHandle::create(blobData.release
(), blobData->length()))); | |
392 break; | |
393 } | |
394 case ResponseAsFormData: | |
395 ASSERT_NOT_REACHED(); | |
396 break; | |
397 case ResponseAsJSON: { | |
398 String s = String::fromUTF8(static_cast<const char*>(buffer->data()), bu
ffer->byteLength()); | |
399 if (s.isNull()) | |
400 m_resolver->reject(DOMException::create(NetworkError, "Invalid utf-8
string")); | |
401 else | |
402 resolveJSON(s); | |
403 break; | |
404 } | |
405 case ResponseAsText: { | |
406 String s = String::fromUTF8(static_cast<const char*>(buffer->data()), bu
ffer->byteLength()); | |
407 if (s.isNull()) | |
408 m_resolver->reject(DOMException::create(NetworkError, "Invalid utf-8
string")); | |
409 else | |
410 m_resolver->resolve(s); | |
411 break; | |
412 } | |
413 default: | |
414 ASSERT_NOT_REACHED(); | |
415 } | |
416 } | |
417 | |
418 void Body::didFail(FileError::ErrorCode code) | |
419 { | |
420 if (!executionContext() || executionContext()->activeDOMObjectsAreStopped()) | |
421 return; | |
422 | |
423 if (m_resolver) { | |
424 // FIXME: We should reject the promise. | |
425 m_resolver->resolve(""); | |
426 m_resolver.clear(); | |
427 } | |
428 m_stream->error(DOMException::create(NetworkError, "network error")); | |
429 } | |
430 | |
431 void Body::didBlobHandleReceiveError(PassRefPtrWillBeRawPtr<DOMException> except
ion) | |
432 { | |
433 if (!m_resolver) | |
434 return; | |
435 m_resolver->reject(exception); | |
436 m_resolver.clear(); | |
437 } | |
438 | |
439 } // namespace blink | |
OLD | NEW |