Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(300)

Side by Side Diff: third_party/WebKit/Source/bindings/core/v8/ScriptStreamer.cpp

Issue 2830533002: Split ScriptStreamer and ScriptStreamerImpl, preparing for unit testing (Closed)
Patch Set: Rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 "bindings/core/v8/ScriptStreamer.h"
6
7 #include <memory>
8 #include "bindings/core/v8/ScriptStreamerThread.h"
9 #include "bindings/core/v8/V8ScriptRunner.h"
10 #include "core/dom/Document.h"
11 #include "core/dom/Element.h"
12 #include "core/dom/PendingScript.h"
13 #include "core/frame/Settings.h"
14 #include "core/html/parser/TextResourceDecoder.h"
15 #include "core/loader/resource/ScriptResource.h"
16 #include "platform/CrossThreadFunctional.h"
17 #include "platform/Histogram.h"
18 #include "platform/SharedBuffer.h"
19 #include "platform/instrumentation/tracing/TraceEvent.h"
20 #include "platform/loader/fetch/CachedMetadata.h"
21 #include "platform/wtf/Deque.h"
22 #include "platform/wtf/PtrUtil.h"
23 #include "platform/wtf/text/TextEncodingRegistry.h"
24 #include "public/platform/WebScheduler.h"
25
26 namespace blink {
27
28 namespace {
29
30 void RecordStartedStreamingHistogram(ScriptStreamer::Type script_type,
31 int reason) {
32 switch (script_type) {
33 case ScriptStreamer::kParsingBlocking: {
34 DEFINE_STATIC_LOCAL(
35 EnumerationHistogram, parse_blocking_histogram,
36 ("WebCore.Scripts.ParsingBlocking.StartedStreaming", 2));
37 parse_blocking_histogram.Count(reason);
38 break;
39 }
40 case ScriptStreamer::kDeferred: {
41 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferred_histogram,
42 ("WebCore.Scripts.Deferred.StartedStreaming", 2));
43 deferred_histogram.Count(reason);
44 break;
45 }
46 case ScriptStreamer::kAsync: {
47 DEFINE_STATIC_LOCAL(EnumerationHistogram, async_histogram,
48 ("WebCore.Scripts.Async.StartedStreaming", 2));
49 async_histogram.Count(reason);
50 break;
51 }
52 default:
53 NOTREACHED();
54 break;
55 }
56 }
57
58 // For tracking why some scripts are not streamed. Not streaming is part of
59 // normal operation (e.g., script already loaded, script too small) and doesn't
60 // necessarily indicate a failure.
61 enum NotStreamingReason {
62 kAlreadyLoaded,
63 kNotHTTP,
64 kReload,
65 kContextNotValid,
66 kEncodingNotSupported,
67 kThreadBusy,
68 kV8CannotStream,
69 kScriptTooSmall,
70 kNotStreamingReasonEnd
71 };
72
73 void RecordNotStreamingReasonHistogram(ScriptStreamer::Type script_type,
74 NotStreamingReason reason) {
75 switch (script_type) {
76 case ScriptStreamer::kParsingBlocking: {
77 DEFINE_STATIC_LOCAL(EnumerationHistogram, parse_blocking_histogram,
78 ("WebCore.Scripts.ParsingBlocking.NotStreamingReason",
79 kNotStreamingReasonEnd));
80 parse_blocking_histogram.Count(reason);
81 break;
82 }
83 case ScriptStreamer::kDeferred: {
84 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferred_histogram,
85 ("WebCore.Scripts.Deferred.NotStreamingReason",
86 kNotStreamingReasonEnd));
87 deferred_histogram.Count(reason);
88 break;
89 }
90 case ScriptStreamer::kAsync: {
91 DEFINE_STATIC_LOCAL(
92 EnumerationHistogram, async_histogram,
93 ("WebCore.Scripts.Async.NotStreamingReason", kNotStreamingReasonEnd));
94 async_histogram.Count(reason);
95 break;
96 }
97 default:
98 NOTREACHED();
99 break;
100 }
101 }
102
103 } // namespace
104
105 // For passing data between the main thread (producer) and the streamer thread
106 // (consumer). The main thread prepares the data (copies it from Resource) and
107 // the streamer thread feeds it to V8.
108 class SourceStreamDataQueue {
109 WTF_MAKE_NONCOPYABLE(SourceStreamDataQueue);
110
111 public:
112 SourceStreamDataQueue() : finished_(false) {}
113
114 ~SourceStreamDataQueue() { DiscardQueuedData(); }
115
116 void Clear() {
117 MutexLocker locker(mutex_);
118 finished_ = false;
119 DiscardQueuedData();
120 }
121
122 void Produce(const uint8_t* data, size_t length) {
123 MutexLocker locker(mutex_);
124 DCHECK(!finished_);
125 data_.push_back(std::make_pair(data, length));
126 have_data_.Signal();
127 }
128
129 void Finish() {
130 MutexLocker locker(mutex_);
131 finished_ = true;
132 have_data_.Signal();
133 }
134
135 void Consume(const uint8_t** data, size_t* length) {
136 MutexLocker locker(mutex_);
137 while (!TryGetData(data, length))
138 have_data_.Wait(mutex_);
139 }
140
141 private:
142 bool TryGetData(const uint8_t** data, size_t* length) {
143 #if DCHECK_IS_ON()
144 DCHECK(mutex_.Locked());
145 #endif
146 if (!data_.IsEmpty()) {
147 std::pair<const uint8_t*, size_t> next_data = data_.TakeFirst();
148 *data = next_data.first;
149 *length = next_data.second;
150 return true;
151 }
152 if (finished_) {
153 *length = 0;
154 return true;
155 }
156 return false;
157 }
158
159 void DiscardQueuedData() {
160 while (!data_.IsEmpty()) {
161 std::pair<const uint8_t*, size_t> next_data = data_.TakeFirst();
162 delete[] next_data.first;
163 }
164 }
165
166 Deque<std::pair<const uint8_t*, size_t>> data_;
167 bool finished_;
168 Mutex mutex_;
169 ThreadCondition have_data_;
170 };
171
172 // SourceStream implements the streaming interface towards V8. The main
173 // functionality is preparing the data to give to V8 on main thread, and
174 // actually giving the data (via GetMoreData which is called on a background
175 // thread).
176 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream {
177 WTF_MAKE_NONCOPYABLE(SourceStream);
178
179 public:
180 explicit SourceStream(RefPtr<WebTaskRunner> loading_task_runner)
181 : v8::ScriptCompiler::ExternalSourceStream(),
182 cancelled_(false),
183 finished_(false),
184 queue_lead_position_(0),
185 queue_tail_position_(0),
186 loading_task_runner_(std::move(loading_task_runner)) {}
187
188 virtual ~SourceStream() override {}
189
190 // Called by V8 on a background thread. Should block until we can return
191 // some data.
192 size_t GetMoreData(const uint8_t** src) override {
193 DCHECK(!IsMainThread());
194 {
195 MutexLocker locker(mutex_);
196 if (cancelled_)
197 return 0;
198 }
199 size_t length = 0;
200 // This will wait until there is data.
201 data_queue_.Consume(src, &length);
202 {
203 MutexLocker locker(mutex_);
204 if (cancelled_)
205 return 0;
206 }
207 queue_lead_position_ += length;
208 return length;
209 }
210
211 void DidFinishLoading() {
212 DCHECK(IsMainThread());
213 finished_ = true;
214 data_queue_.Finish();
215 }
216
217 void DidReceiveData(ScriptStreamer* streamer) {
218 DCHECK(IsMainThread());
219 PrepareDataOnMainThread(streamer);
220 }
221
222 void Cancel() {
223 DCHECK(IsMainThread());
224 // The script is no longer needed by the upper layers. Stop streaming
225 // it. The next time GetMoreData is called (or woken up), it will return
226 // 0, which will be interpreted as EOS by V8 and the parsing will
227 // fail. ScriptStreamer::streamingComplete will be called, and at that
228 // point we will release the references to SourceStream.
229 {
230 MutexLocker locker(mutex_);
231 cancelled_ = true;
232 }
233 data_queue_.Finish();
234 }
235
236 private:
237 void PrepareDataOnMainThread(ScriptStreamer* streamer) {
238 DCHECK(IsMainThread());
239
240 if (cancelled_) {
241 data_queue_.Finish();
242 return;
243 }
244
245 // The Resource must still be alive; otherwise we should've cancelled
246 // the streaming (if we have cancelled, the background thread is not
247 // waiting).
248 DCHECK(streamer->GetResource());
249
250 if (!streamer->GetResource()
251 ->GetResponse()
252 .CacheStorageCacheName()
253 .IsNull()) {
254 streamer->SuppressStreaming();
255 Cancel();
256 return;
257 }
258
259 CachedMetadataHandler* cache_handler =
260 streamer->GetResource()->CacheHandler();
261 RefPtr<CachedMetadata> code_cache(
262 cache_handler ? cache_handler->GetCachedMetadata(
263 V8ScriptRunner::TagForCodeCache(cache_handler))
264 : nullptr);
265 if (code_cache.Get()) {
266 // The resource has a code cache, so it's unnecessary to stream and
267 // parse the code. Cancel the streaming and resume the non-streaming
268 // code path.
269 streamer->SuppressStreaming();
270 Cancel();
271 return;
272 }
273
274 if (!resource_buffer_) {
275 // We don't have a buffer yet. Try to get it from the resource.
276 resource_buffer_ = streamer->GetResource()->ResourceBuffer();
277 }
278
279 FetchDataFromResourceBuffer();
280 }
281
282 void FetchDataFromResourceBuffer() {
283 DCHECK(IsMainThread());
284 MutexLocker locker(mutex_);
285
286 DCHECK(!finished_);
287 if (cancelled_) {
288 data_queue_.Finish();
289 return;
290 }
291
292 // Get as much data from the ResourceBuffer as we can.
293 const char* data = nullptr;
294 while (size_t length =
295 resource_buffer_->GetSomeData(data, queue_tail_position_)) {
296 // Copy the data chunks into a new buffer, since we're going to
297 // give the data to a background thread.
298 uint8_t* copied_data = new uint8_t[length];
299 memcpy(copied_data, data, length);
300 data_queue_.Produce(copied_data, length);
301
302 queue_tail_position_ += length;
303 }
304 }
305
306 // For coordinating between the main thread and background thread tasks.
307 // Guards m_cancelled and m_queueTailPosition.
308 Mutex mutex_;
309
310 // The shared buffer containing the resource data + state variables.
311 // Used by both threads, guarded by m_mutex.
312 bool cancelled_;
313 bool finished_;
314
315 RefPtr<const SharedBuffer> resource_buffer_; // Only used by the main thread.
316
317 // The queue contains the data to be passed to the V8 thread.
318 // queueLeadPosition: data we have handed off to the V8 thread.
319 // queueTailPosition: end of data we have enqued in the queue.
320 // bookmarkPosition: position of the bookmark.
321 SourceStreamDataQueue data_queue_; // Thread safe.
322 size_t queue_lead_position_; // Only used by v8 thread.
323 size_t queue_tail_position_; // Used by both threads; guarded by m_mutex.
324
325 RefPtr<WebTaskRunner> loading_task_runner_;
326 };
327
328 size_t ScriptStreamer::small_script_threshold_ = 30 * 1024;
329
330 void ScriptStreamer::StartStreaming(PendingScript* script,
331 Type script_type,
332 Settings* settings,
333 ScriptState* script_state,
334 RefPtr<WebTaskRunner> loading_task_runner) {
335 // We don't yet know whether the script will really be streamed. E.g.,
336 // suppressing streaming for short scripts is done later. Record only the
337 // sure negative cases here.
338 bool started_streaming =
339 StartStreamingInternal(script, script_type, settings, script_state,
340 std::move(loading_task_runner));
341 if (!started_streaming)
342 RecordStartedStreamingHistogram(script_type, 0);
343 }
344
345 bool ScriptStreamer::ConvertEncoding(
346 const char* encoding_name,
347 v8::ScriptCompiler::StreamedSource::Encoding* encoding) {
348 // Here's a list of encodings we can use for streaming. These are
349 // the canonical names.
350 if (strcmp(encoding_name, "windows-1252") == 0 ||
351 strcmp(encoding_name, "ISO-8859-1") == 0 ||
352 strcmp(encoding_name, "US-ASCII") == 0) {
353 *encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE;
354 return true;
355 }
356 if (strcmp(encoding_name, "UTF-8") == 0) {
357 *encoding = v8::ScriptCompiler::StreamedSource::UTF8;
358 return true;
359 }
360 // We don't stream other encodings; especially we don't stream two
361 // byte scripts to avoid the handling of endianness. Most scripts
362 // are Latin1 or UTF-8 anyway, so this should be enough for most
363 // real world purposes.
364 return false;
365 }
366
367 bool ScriptStreamer::IsFinished() const {
368 DCHECK(IsMainThread());
369 return loading_finished_ && (parsing_finished_ || streaming_suppressed_);
370 }
371
372 void ScriptStreamer::StreamingCompleteOnBackgroundThread() {
373 DCHECK(!IsMainThread());
374
375 // notifyFinished might already be called, or it might be called in the
376 // future (if the parsing finishes earlier because of a parse error).
377 loading_task_runner_->PostTask(
378 BLINK_FROM_HERE, CrossThreadBind(&ScriptStreamer::StreamingComplete,
379 WrapCrossThreadPersistent(this)));
380
381 // The task might delete ScriptStreamer, so it's not safe to do anything
382 // after posting it. Note that there's no way to guarantee that this
383 // function has returned before the task is ran - however, we should not
384 // access the "this" object after posting the task.
385 }
386
387 void ScriptStreamer::Cancel() {
388 DCHECK(IsMainThread());
389 // The upper layer doesn't need the script any more, but streaming might
390 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets
391 // the control the next time. It can also be that V8 has already completed
392 // its operations and streamingComplete will be called soon.
393 detached_ = true;
394 resource_ = 0;
395 if (stream_)
396 stream_->Cancel();
397 }
398
399 void ScriptStreamer::SuppressStreaming() {
400 DCHECK(IsMainThread());
401 DCHECK(!loading_finished_);
402 // It can be that the parsing task has already finished (e.g., if there was
403 // a parse error).
404 streaming_suppressed_ = true;
405 }
406
407 void ScriptStreamer::NotifyAppendData(ScriptResource* resource) {
408 DCHECK(IsMainThread());
409 CHECK_EQ(resource_, resource);
410 if (streaming_suppressed_)
411 return;
412 if (!have_enough_data_for_streaming_) {
413 // Even if the first data chunk is small, the script can still be big
414 // enough - wait until the next data chunk comes before deciding whether
415 // to start the streaming.
416 DCHECK(resource->ResourceBuffer());
417 if (resource->ResourceBuffer()->size() < small_script_threshold_)
418 return;
419 have_enough_data_for_streaming_ = true;
420
421 {
422 // Check for BOM (byte order marks), because that might change our
423 // understanding of the data encoding.
424 constexpr size_t kMaximumLengthOfBOM = 4;
425 char maybe_bom[kMaximumLengthOfBOM] = {};
426 if (!resource->ResourceBuffer()->GetPartAsBytes(
427 maybe_bom, static_cast<size_t>(0), kMaximumLengthOfBOM)) {
428 NOTREACHED();
429 return;
430 }
431
432 std::unique_ptr<TextResourceDecoder> decoder(TextResourceDecoder::Create(
433 "application/javascript", resource->Encoding()));
434 decoder->CheckForBOM(maybe_bom, kMaximumLengthOfBOM);
435
436 // The encoding may change when we see the BOM. Check for BOM now
437 // and update the encoding from the decoder when necessary. Supress
438 // streaming if the encoding is unsupported.
439 //
440 // Also note that have at least s_smallScriptThreshold worth of
441 // data, which is more than enough for detecting a BOM.
442 if (!ConvertEncoding(decoder->Encoding().GetName(), &encoding_)) {
443 SuppressStreaming();
444 RecordNotStreamingReasonHistogram(script_type_, kEncodingNotSupported);
445 RecordStartedStreamingHistogram(script_type_, 0);
446 return;
447 }
448 }
449
450 if (ScriptStreamerThread::Shared()->IsRunningTask()) {
451 // At the moment we only have one thread for running the tasks. A
452 // new task shouldn't be queued before the running task completes,
453 // because the running task can block and wait for data from the
454 // network.
455 SuppressStreaming();
456 RecordNotStreamingReasonHistogram(script_type_, kThreadBusy);
457 RecordStartedStreamingHistogram(script_type_, 0);
458 return;
459 }
460
461 if (!script_state_->ContextIsValid()) {
462 SuppressStreaming();
463 RecordNotStreamingReasonHistogram(script_type_, kContextNotValid);
464 RecordStartedStreamingHistogram(script_type_, 0);
465 return;
466 }
467
468 DCHECK(!stream_);
469 DCHECK(!source_);
470 stream_ = new SourceStream(loading_task_runner_.Get());
471 // m_source takes ownership of m_stream.
472 source_ = WTF::WrapUnique(
473 new v8::ScriptCompiler::StreamedSource(stream_, encoding_));
474
475 ScriptState::Scope scope(script_state_.Get());
476 std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask>
477 script_streaming_task(
478 WTF::WrapUnique(v8::ScriptCompiler::StartStreamingScript(
479 script_state_->GetIsolate(), source_.get(), compile_options_)));
480 if (!script_streaming_task) {
481 // V8 cannot stream the script.
482 SuppressStreaming();
483 stream_ = 0;
484 source_.reset();
485 RecordNotStreamingReasonHistogram(script_type_, kV8CannotStream);
486 RecordStartedStreamingHistogram(script_type_, 0);
487 return;
488 }
489
490 ScriptStreamerThread::Shared()->PostTask(
491 CrossThreadBind(&ScriptStreamerThread::RunScriptStreamingTask,
492 WTF::Passed(std::move(script_streaming_task)),
493 WrapCrossThreadPersistent(this)));
494 RecordStartedStreamingHistogram(script_type_, 1);
495 }
496 if (stream_)
497 stream_->DidReceiveData(this);
498 }
499
500 void ScriptStreamer::NotifyFinished(Resource* resource) {
501 DCHECK(IsMainThread());
502 CHECK_EQ(resource_, resource);
503 // A special case: empty and small scripts. We didn't receive enough data to
504 // start the streaming before this notification. In that case, there won't
505 // be a "parsing complete" notification either, and we should not wait for
506 // it.
507 if (!have_enough_data_for_streaming_) {
508 RecordNotStreamingReasonHistogram(script_type_, kScriptTooSmall);
509 RecordStartedStreamingHistogram(script_type_, 0);
510 SuppressStreaming();
511 }
512 if (stream_)
513 stream_->DidFinishLoading();
514 loading_finished_ = true;
515
516 NotifyFinishedToClient();
517 }
518
519 ScriptStreamer::ScriptStreamer(
520 PendingScript* script,
521 Type script_type,
522 ScriptState* script_state,
523 v8::ScriptCompiler::CompileOptions compile_options,
524 RefPtr<WebTaskRunner> loading_task_runner)
525 : pending_script_(script),
526 resource_(script->GetResource()),
527 detached_(false),
528 stream_(0),
529 loading_finished_(false),
530 parsing_finished_(false),
531 have_enough_data_for_streaming_(false),
532 streaming_suppressed_(false),
533 compile_options_(compile_options),
534 script_state_(script_state),
535 script_type_(script_type),
536 script_url_string_(resource_->Url().Copy().GetString()),
537 script_resource_identifier_(resource_->Identifier()),
538 // Unfortunately there's no dummy encoding value in the enum; let's use
539 // one we don't stream.
540 encoding_(v8::ScriptCompiler::StreamedSource::TWO_BYTE),
541 loading_task_runner_(std::move(loading_task_runner)) {}
542
543 ScriptStreamer::~ScriptStreamer() {}
544
545 DEFINE_TRACE(ScriptStreamer) {
546 visitor->Trace(pending_script_);
547 visitor->Trace(resource_);
548 }
549
550 void ScriptStreamer::StreamingComplete() {
551 // The background task is completed; do the necessary ramp-down in the main
552 // thread.
553 DCHECK(IsMainThread());
554 parsing_finished_ = true;
555
556 // It's possible that the corresponding Resource was deleted before V8
557 // finished streaming. In that case, the data or the notification is not
558 // needed. In addition, if the streaming is suppressed, the non-streaming
559 // code path will resume after the resource has loaded, before the
560 // background task finishes.
561 if (detached_ || streaming_suppressed_)
562 return;
563
564 // We have now streamed the whole script to V8 and it has parsed the
565 // script. We're ready for the next step: compiling and executing the
566 // script.
567 NotifyFinishedToClient();
568 }
569
570 void ScriptStreamer::NotifyFinishedToClient() {
571 DCHECK(IsMainThread());
572 // Usually, the loading will be finished first, and V8 will still need some
573 // time to catch up. But the other way is possible too: if V8 detects a
574 // parse error, the V8 side can complete before loading has finished. Send
575 // the notification after both loading and V8 side operations have
576 // completed.
577 if (!IsFinished())
578 return;
579
580 pending_script_->StreamingFinished();
581 }
582
583 bool ScriptStreamer::StartStreamingInternal(
584 PendingScript* script,
585 Type script_type,
586 Settings* settings,
587 ScriptState* script_state,
588 RefPtr<WebTaskRunner> loading_task_runner) {
589 DCHECK(IsMainThread());
590 DCHECK(script_state->ContextIsValid());
591 ScriptResource* resource = script->GetResource();
592 if (resource->IsLoaded()) {
593 RecordNotStreamingReasonHistogram(script_type, kAlreadyLoaded);
594 return false;
595 }
596 if (!resource->Url().ProtocolIsInHTTPFamily()) {
597 RecordNotStreamingReasonHistogram(script_type, kNotHTTP);
598 return false;
599 }
600 if (resource->IsCacheValidator()) {
601 RecordNotStreamingReasonHistogram(script_type, kReload);
602 // This happens e.g., during reloads. We're actually not going to load
603 // the current Resource of the PendingScript but switch to another
604 // Resource -> don't stream.
605 return false;
606 }
607 // We cannot filter out short scripts, even if we wait for the HTTP headers
608 // to arrive: the Content-Length HTTP header is not sent for chunked
609 // downloads.
610
611 // Decide what kind of cached data we should produce while streaming. Only
612 // produce parser cache if the non-streaming compile takes advantage of it.
613 v8::ScriptCompiler::CompileOptions compile_option =
614 v8::ScriptCompiler::kNoCompileOptions;
615 if (settings->GetV8CacheOptions() == kV8CacheOptionsParse)
616 compile_option = v8::ScriptCompiler::kProduceParserCache;
617
618 // The Resource might go out of scope if the script is no longer
619 // needed. This makes PendingScript notify the ScriptStreamer when it is
620 // destroyed.
621 script->SetStreamer(ScriptStreamer::Create(script, script_type, script_state,
622 compile_option,
623 std::move(loading_task_runner)));
624
625 return true;
626 }
627
628 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698