Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "bindings/core/v8/ScriptStreamer.h" | 5 #include "bindings/core/v8/ScriptStreamer.h" |
| 6 | 6 |
| 7 #include "bindings/core/v8/ScriptStreamerThread.h" | 7 #include "bindings/core/v8/ScriptStreamerThread.h" |
| 8 #include "bindings/core/v8/V8ScriptRunner.h" | 8 #include "bindings/core/v8/V8ScriptRunner.h" |
| 9 #include "core/dom/Document.h" | 9 #include "core/dom/Document.h" |
| 10 #include "core/dom/Element.h" | 10 #include "core/dom/Element.h" |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 39 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.StartedStreaming", 2)); | 39 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.StartedStreaming", 2)); |
| 40 deferredHistogram.count(reason); | 40 deferredHistogram.count(reason); |
| 41 break; | 41 break; |
| 42 } | 42 } |
| 43 case ScriptStreamer::Async: { | 43 case ScriptStreamer::Async: { |
| 44 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.StartedStreaming", 2)); | 44 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.StartedStreaming", 2)); |
| 45 asyncHistogram.count(reason); | 45 asyncHistogram.count(reason); |
| 46 break; | 46 break; |
| 47 } | 47 } |
| 48 default: | 48 default: |
| 49 ASSERT_NOT_REACHED(); | 49 NOTREACHED(); |
| 50 break; | 50 break; |
| 51 } | 51 } |
| 52 } | 52 } |
| 53 | 53 |
| 54 // For tracking why some scripts are not streamed. Not streaming is part of | 54 // For tracking why some scripts are not streamed. Not streaming is part of |
| 55 // normal operation (e.g., script already loaded, script too small) and doesn't | 55 // normal operation (e.g., script already loaded, script too small) and doesn't |
| 56 // necessarily indicate a failure. | 56 // necessarily indicate a failure. |
| 57 enum NotStreamingReason { | 57 enum NotStreamingReason { |
| 58 AlreadyLoaded, | 58 AlreadyLoaded, |
| 59 NotHTTP, | 59 NotHTTP, |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 78 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.NotStreamingReason", NotStreamingReasonEnd)); | 78 DEFINE_STATIC_LOCAL(EnumerationHistogram, deferredHistogram, ("WebCore.S cripts.Deferred.NotStreamingReason", NotStreamingReasonEnd)); |
| 79 deferredHistogram.count(reason); | 79 deferredHistogram.count(reason); |
| 80 break; | 80 break; |
| 81 } | 81 } |
| 82 case ScriptStreamer::Async: { | 82 case ScriptStreamer::Async: { |
| 83 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.NotStreamingReason", NotStreamingReasonEnd)); | 83 DEFINE_STATIC_LOCAL(EnumerationHistogram, asyncHistogram, ("WebCore.Scri pts.Async.NotStreamingReason", NotStreamingReasonEnd)); |
| 84 asyncHistogram.count(reason); | 84 asyncHistogram.count(reason); |
| 85 break; | 85 break; |
| 86 } | 86 } |
| 87 default: | 87 default: |
| 88 ASSERT_NOT_REACHED(); | 88 NOTREACHED(); |
| 89 break; | 89 break; |
| 90 } | 90 } |
| 91 } | 91 } |
| 92 | 92 |
| 93 } // namespace | 93 } // namespace |
| 94 | 94 |
| 95 // For passing data between the main thread (producer) and the streamer thread | 95 // For passing data between the main thread (producer) and the streamer thread |
| 96 // (consumer). The main thread prepares the data (copies it from Resource) and | 96 // (consumer). The main thread prepares the data (copies it from Resource) and |
| 97 // the streamer thread feeds it to V8. | 97 // the streamer thread feeds it to V8. |
| 98 class SourceStreamDataQueue { | 98 class SourceStreamDataQueue { |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 109 void clear() | 109 void clear() |
| 110 { | 110 { |
| 111 MutexLocker locker(m_mutex); | 111 MutexLocker locker(m_mutex); |
| 112 m_finished = false; | 112 m_finished = false; |
| 113 discardQueuedData(); | 113 discardQueuedData(); |
| 114 } | 114 } |
| 115 | 115 |
| 116 void produce(const uint8_t* data, size_t length) | 116 void produce(const uint8_t* data, size_t length) |
| 117 { | 117 { |
| 118 MutexLocker locker(m_mutex); | 118 MutexLocker locker(m_mutex); |
| 119 ASSERT(!m_finished); | 119 DCHECK(!m_finished); |
| 120 m_data.append(std::make_pair(data, length)); | 120 m_data.append(std::make_pair(data, length)); |
| 121 m_haveData.signal(); | 121 m_haveData.signal(); |
| 122 } | 122 } |
| 123 | 123 |
| 124 void finish() | 124 void finish() |
| 125 { | 125 { |
| 126 MutexLocker locker(m_mutex); | 126 MutexLocker locker(m_mutex); |
| 127 m_finished = true; | 127 m_finished = true; |
| 128 m_haveData.signal(); | 128 m_haveData.signal(); |
| 129 } | 129 } |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 173 // thread). | 173 // thread). |
| 174 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { | 174 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { |
| 175 WTF_MAKE_NONCOPYABLE(SourceStream); | 175 WTF_MAKE_NONCOPYABLE(SourceStream); |
| 176 public: | 176 public: |
| 177 explicit SourceStream(WebTaskRunner* loadingTaskRunner) | 177 explicit SourceStream(WebTaskRunner* loadingTaskRunner) |
| 178 : v8::ScriptCompiler::ExternalSourceStream() | 178 : v8::ScriptCompiler::ExternalSourceStream() |
| 179 , m_cancelled(false) | 179 , m_cancelled(false) |
| 180 , m_finished(false) | 180 , m_finished(false) |
| 181 , m_queueLeadPosition(0) | 181 , m_queueLeadPosition(0) |
| 182 , m_queueTailPosition(0) | 182 , m_queueTailPosition(0) |
| 183 , m_bookmarkPosition(0) | |
| 184 , m_lengthOfBOM(0) | |
| 185 , m_loadingTaskRunner(loadingTaskRunner->clone()) | 183 , m_loadingTaskRunner(loadingTaskRunner->clone()) |
| 186 { | 184 { |
| 187 } | 185 } |
| 188 | 186 |
| 189 virtual ~SourceStream() override { } | 187 virtual ~SourceStream() override { } |
| 190 | 188 |
| 191 // Called by V8 on a background thread. Should block until we can return | 189 // Called by V8 on a background thread. Should block until we can return |
| 192 // some data. | 190 // some data. |
| 193 size_t GetMoreData(const uint8_t** src) override | 191 size_t GetMoreData(const uint8_t** src) override |
| 194 { | 192 { |
| 195 ASSERT(!isMainThread()); | 193 DCHECK(!isMainThread()); |
| 196 { | 194 { |
| 197 MutexLocker locker(m_mutex); | 195 MutexLocker locker(m_mutex); |
| 198 if (m_cancelled) | 196 if (m_cancelled) |
| 199 return 0; | 197 return 0; |
| 200 } | 198 } |
| 201 size_t length = 0; | 199 size_t length = 0; |
| 202 // This will wait until there is data. | 200 // This will wait until there is data. |
| 203 m_dataQueue.consume(src, &length); | 201 m_dataQueue.consume(src, &length); |
| 204 { | 202 { |
| 205 MutexLocker locker(m_mutex); | 203 MutexLocker locker(m_mutex); |
| 206 if (m_cancelled) | 204 if (m_cancelled) |
| 207 return 0; | 205 return 0; |
| 208 } | 206 } |
| 209 m_queueLeadPosition += length; | 207 m_queueLeadPosition += length; |
| 210 return length; | 208 return length; |
| 211 } | 209 } |
| 212 | 210 |
| 213 // Called by V8 on background thread. | |
| 214 bool SetBookmark() override | |
| 215 { | |
| 216 ASSERT(!isMainThread()); | |
| 217 m_bookmarkPosition = m_queueLeadPosition; | |
| 218 return true; | |
| 219 } | |
| 220 | |
| 221 // Called by V8 on background thread. | |
| 222 void ResetToBookmark() override | |
| 223 { | |
| 224 ASSERT(!isMainThread()); | |
| 225 { | |
| 226 MutexLocker locker(m_mutex); | |
| 227 m_queueLeadPosition = m_bookmarkPosition; | |
| 228 // See comments at m_lengthOfBOM declaration below for why | |
| 229 // we need this here. | |
| 230 m_queueTailPosition = m_bookmarkPosition + m_lengthOfBOM; | |
| 231 m_dataQueue.clear(); | |
| 232 } | |
| 233 | |
| 234 // Inform main thread to re-queue the data. | |
| 235 m_loadingTaskRunner->postTask( | |
| 236 BLINK_FROM_HERE, crossThreadBind(&SourceStream::fetchDataFromResourc eBuffer, crossThreadUnretained(this), 0)); | |
| 237 } | |
| 238 | |
| 239 void didFinishLoading() | 211 void didFinishLoading() |
| 240 { | 212 { |
| 241 ASSERT(isMainThread()); | 213 DCHECK(isMainThread()); |
| 242 | |
| 243 // ResetToBookmark may reset the data queue's 'finished' status, | |
| 244 // so we may need to re-finish after a ResetToBookmark happened. | |
| 245 // We do this by remembering m_finished, and always checking for it | |
| 246 // at the end ot fetchDataFromResourceBuffer. | |
| 247 m_finished = true; | 214 m_finished = true; |
| 248 fetchDataFromResourceBuffer(0); | 215 m_dataQueue.finish(); |
| 249 } | 216 } |
| 250 | 217 |
| 251 void didReceiveData(ScriptStreamer* streamer, size_t lengthOfBOM) | 218 void didReceiveData(ScriptStreamer* streamer) |
| 252 { | 219 { |
| 253 ASSERT(isMainThread()); | 220 DCHECK(isMainThread()); |
| 254 prepareDataOnMainThread(streamer, lengthOfBOM); | 221 prepareDataOnMainThread(streamer); |
| 255 } | 222 } |
| 256 | 223 |
| 257 void cancel() | 224 void cancel() |
| 258 { | 225 { |
| 259 ASSERT(isMainThread()); | 226 DCHECK(isMainThread()); |
| 260 // The script is no longer needed by the upper layers. Stop streaming | 227 // The script is no longer needed by the upper layers. Stop streaming |
| 261 // it. The next time GetMoreData is called (or woken up), it will return | 228 // it. The next time GetMoreData is called (or woken up), it will return |
| 262 // 0, which will be interpreted as EOS by V8 and the parsing will | 229 // 0, which will be interpreted as EOS by V8 and the parsing will |
| 263 // fail. ScriptStreamer::streamingComplete will be called, and at that | 230 // fail. ScriptStreamer::streamingComplete will be called, and at that |
| 264 // point we will release the references to SourceStream. | 231 // point we will release the references to SourceStream. |
| 265 { | 232 { |
| 266 MutexLocker locker(m_mutex); | 233 MutexLocker locker(m_mutex); |
| 267 m_cancelled = true; | 234 m_cancelled = true; |
| 268 } | 235 } |
| 269 m_dataQueue.finish(); | 236 m_dataQueue.finish(); |
| 270 } | 237 } |
| 271 | 238 |
| 272 private: | 239 private: |
| 273 void prepareDataOnMainThread(ScriptStreamer* streamer, size_t lengthOfBOM) | 240 void prepareDataOnMainThread(ScriptStreamer* streamer) |
| 274 { | 241 { |
| 275 ASSERT(isMainThread()); | 242 DCHECK(isMainThread()); |
| 276 | 243 |
| 277 // The Resource must still be alive; otherwise we should've cancelled | 244 // The Resource must still be alive; otherwise we should've cancelled |
| 278 // the streaming (if we have cancelled, the background thread is not | 245 // the streaming (if we have cancelled, the background thread is not |
| 279 // waiting). | 246 // waiting). |
| 280 ASSERT(streamer->resource()); | 247 DCHECK(streamer->resource()); |
| 281 | |
| 282 // BOM can only occur at the beginning of the data. | |
| 283 ASSERT(lengthOfBOM == 0 || m_queueTailPosition == 0); | |
| 284 | 248 |
| 285 if (!streamer->resource()->response().cacheStorageCacheName().isNull()) { | 249 if (!streamer->resource()->response().cacheStorageCacheName().isNull()) { |
| 286 streamer->suppressStreaming(); | 250 streamer->suppressStreaming(); |
| 287 cancel(); | 251 cancel(); |
| 288 return; | 252 return; |
| 289 } | 253 } |
| 290 | 254 |
| 291 CachedMetadataHandler* cacheHandler = streamer->resource()->cacheHandler (); | 255 CachedMetadataHandler* cacheHandler = streamer->resource()->cacheHandler (); |
| 292 RefPtr<CachedMetadata> codeCache(cacheHandler ? cacheHandler->cachedMeta data(V8ScriptRunner::tagForCodeCache(cacheHandler)) : nullptr); | 256 RefPtr<CachedMetadata> codeCache(cacheHandler ? cacheHandler->cachedMeta data(V8ScriptRunner::tagForCodeCache(cacheHandler)) : nullptr); |
| 293 if (codeCache.get()) { | 257 if (codeCache.get()) { |
| 294 // The resource has a code cache, so it's unnecessary to stream and | 258 // The resource has a code cache, so it's unnecessary to stream and |
| 295 // parse the code. Cancel the streaming and resume the non-streaming | 259 // parse the code. Cancel the streaming and resume the non-streaming |
| 296 // code path. | 260 // code path. |
| 297 streamer->suppressStreaming(); | 261 streamer->suppressStreaming(); |
| 298 cancel(); | 262 cancel(); |
| 299 return; | 263 return; |
| 300 } | 264 } |
| 301 | 265 |
| 302 if (!m_resourceBuffer) { | 266 if (!m_resourceBuffer) { |
| 303 // We don't have a buffer yet. Try to get it from the resource. | 267 // We don't have a buffer yet. Try to get it from the resource. |
| 304 m_resourceBuffer = streamer->resource()->resourceBuffer(); | 268 m_resourceBuffer = streamer->resource()->resourceBuffer(); |
| 305 } | 269 } |
| 306 | 270 |
| 307 fetchDataFromResourceBuffer(lengthOfBOM); | 271 fetchDataFromResourceBuffer(); |
| 308 } | 272 } |
| 309 | 273 |
| 310 void fetchDataFromResourceBuffer(size_t lengthOfBOM) | 274 void fetchDataFromResourceBuffer() |
| 311 { | 275 { |
| 312 ASSERT(isMainThread()); | 276 DCHECK(isMainThread()); |
| 313 MutexLocker locker(m_mutex); // For m_cancelled + m_queueTailPosition. | 277 MutexLocker locker(m_mutex); |
| 314 | 278 |
| 315 if (lengthOfBOM > 0) { | 279 DCHECK(!m_finished); |
| 316 ASSERT(!m_lengthOfBOM); // There should be only one BOM. | |
| 317 m_lengthOfBOM = lengthOfBOM; | |
| 318 } | |
| 319 | |
| 320 if (m_cancelled) { | 280 if (m_cancelled) { |
| 321 m_dataQueue.finish(); | 281 m_dataQueue.finish(); |
| 322 return; | 282 return; |
| 323 } | 283 } |
| 324 | 284 |
| 325 // Get as much data from the ResourceBuffer as we can. | 285 // Get as much data from the ResourceBuffer as we can. |
| 326 const char* data = nullptr; | 286 const char* data = nullptr; |
| 327 Vector<const char*> chunks; | |
| 328 Vector<size_t> chunkLengths; | |
| 329 size_t bufferLength = 0; | |
| 330 while (size_t length = m_resourceBuffer->getSomeData(data, m_queueTailPo sition)) { | 287 while (size_t length = m_resourceBuffer->getSomeData(data, m_queueTailPo sition)) { |
| 331 // FIXME: Here we can limit based on the total length, if it turns | 288 // Copy the data chunks into a new buffer, since we're going to |
| 332 // out that we don't want to give all the data we have (memory | 289 // give the data to a background thread. |
| 333 // vs. speed). | 290 uint8_t* copiedData = new uint8_t[length]; |
| 334 chunks.append(data); | 291 memcpy(copiedData, data, length); |
| 335 chunkLengths.append(length); | 292 m_dataQueue.produce(copiedData, length); |
| 336 bufferLength += length; | 293 |
| 337 m_queueTailPosition += length; | 294 m_queueTailPosition += length; |
| 338 } | 295 } |
| 339 | |
| 340 // Copy the data chunks into a new buffer, since we're going to give the | |
| 341 // data to a background thread. | |
| 342 if (bufferLength > lengthOfBOM) { | |
| 343 size_t totalLength = bufferLength - lengthOfBOM; | |
| 344 uint8_t* copiedData = new uint8_t[totalLength]; | |
| 345 size_t offset = 0; | |
| 346 size_t offsetInChunk = lengthOfBOM; | |
| 347 for (size_t i = 0; i < chunks.size(); ++i) { | |
| 348 if (offsetInChunk >= chunkLengths[i]) { | |
| 349 offsetInChunk -= chunkLengths[i]; | |
| 350 continue; | |
| 351 } | |
| 352 | |
| 353 size_t dataLength = chunkLengths[i] - offsetInChunk; | |
| 354 memcpy(copiedData + offset, chunks[i] + offsetInChunk, dataLengt h); | |
| 355 offset += dataLength; | |
| 356 // BOM is in the beginning of the buffer. | |
| 357 offsetInChunk = 0; | |
| 358 } | |
| 359 m_dataQueue.produce(copiedData, totalLength); | |
| 360 } | |
| 361 | |
| 362 if (m_finished) | |
| 363 m_dataQueue.finish(); | |
| 364 } | 296 } |
| 365 | 297 |
| 366 // For coordinating between the main thread and background thread tasks. | 298 // For coordinating between the main thread and background thread tasks. |
| 367 // Guards m_cancelled and m_queueTailPosition. | 299 // Guards m_cancelled and m_queueTailPosition. |
| 368 Mutex m_mutex; | 300 Mutex m_mutex; |
| 369 | 301 |
| 370 // The shared buffer containing the resource data + state variables. | 302 // The shared buffer containing the resource data + state variables. |
| 371 // Used by both threads, guarded by m_mutex. | 303 // Used by both threads, guarded by m_mutex. |
| 372 bool m_cancelled; | 304 bool m_cancelled; |
| 373 bool m_finished; | 305 bool m_finished; |
| 374 | 306 |
| 375 RefPtr<const SharedBuffer> m_resourceBuffer; // Only used by the main thread . | 307 RefPtr<const SharedBuffer> m_resourceBuffer; // Only used by the main thread . |
| 376 | 308 |
| 377 // The queue contains the data to be passed to the V8 thread. | 309 // The queue contains the data to be passed to the V8 thread. |
| 378 // queueLeadPosition: data we have handed off to the V8 thread. | 310 // queueLeadPosition: data we have handed off to the V8 thread. |
| 379 // queueTailPosition: end of data we have enqued in the queue. | 311 // queueTailPosition: end of data we have enqued in the queue. |
| 380 // bookmarkPosition: position of the bookmark. | 312 // bookmarkPosition: position of the bookmark. |
| 381 SourceStreamDataQueue m_dataQueue; // Thread safe. | 313 SourceStreamDataQueue m_dataQueue; // Thread safe. |
| 382 size_t m_queueLeadPosition; // Only used by v8 thread. | 314 size_t m_queueLeadPosition; // Only used by v8 thread. |
| 383 size_t m_queueTailPosition; // Used by both threads; guarded by m_mutex. | 315 size_t m_queueTailPosition; // Used by both threads; guarded by m_mutex. |
| 384 size_t m_bookmarkPosition; // Only used by v8 thread. | |
| 385 | |
| 386 // BOM (Unicode Byte Order Mark) handling: | |
| 387 // This class is responsible for stripping out the BOM, since Chrome | |
| 388 // delivers the input stream potentially with BOM, but V8 doesn't want | |
| 389 // to see the BOM. This is mostly easy to do, except for a funky edge | |
| 390 // condition with bookmarking: | |
| 391 // - m_queueLeadPosition counts the bytes that V8 has received | |
| 392 // (i.e., without BOM) | |
| 393 // - m_queueTailPosition counts the bytes that Chrome has sent | |
| 394 // (i.e., with BOM) | |
| 395 // So when resetting the bookmark, we have to adjust the lead position | |
| 396 // to account for the BOM (which happens implicitly in the regular | |
| 397 // streaming case). | |
| 398 // We store this separately, to avoid having to guard all | |
| 399 // m_queueLeadPosition references with a mutex. | |
| 400 size_t m_lengthOfBOM; // Used by both threads; guarded by m_mutex. | |
| 401 | 316 |
| 402 std::unique_ptr<WebTaskRunner> m_loadingTaskRunner; | 317 std::unique_ptr<WebTaskRunner> m_loadingTaskRunner; |
| 403 }; | 318 }; |
| 404 | 319 |
| 405 size_t ScriptStreamer::s_smallScriptThreshold = 30 * 1024; | 320 size_t ScriptStreamer::s_smallScriptThreshold = 30 * 1024; |
| 406 | 321 |
| 407 void ScriptStreamer::startStreaming(PendingScript* script, Type scriptType, Sett ings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunner) | 322 void ScriptStreamer::startStreaming(PendingScript* script, Type scriptType, Sett ings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunner) |
| 408 { | 323 { |
| 409 // We don't yet know whether the script will really be streamed. E.g., | 324 // We don't yet know whether the script will really be streamed. E.g., |
| 410 // suppressing streaming for short scripts is done later. Record only the | 325 // suppressing streaming for short scripts is done later. Record only the |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 436 } | 351 } |
| 437 | 352 |
| 438 bool ScriptStreamer::isFinished() const | 353 bool ScriptStreamer::isFinished() const |
| 439 { | 354 { |
| 440 MutexLocker locker(m_mutex); | 355 MutexLocker locker(m_mutex); |
| 441 return m_loadingFinished && (m_parsingFinished || m_streamingSuppressed); | 356 return m_loadingFinished && (m_parsingFinished || m_streamingSuppressed); |
| 442 } | 357 } |
| 443 | 358 |
| 444 void ScriptStreamer::streamingCompleteOnBackgroundThread() | 359 void ScriptStreamer::streamingCompleteOnBackgroundThread() |
| 445 { | 360 { |
| 446 ASSERT(!isMainThread()); | 361 DCHECK(!isMainThread()); |
| 447 { | 362 { |
| 448 MutexLocker locker(m_mutex); | 363 MutexLocker locker(m_mutex); |
| 449 m_parsingFinished = true; | 364 m_parsingFinished = true; |
| 450 } | 365 } |
| 451 | 366 |
| 452 // notifyFinished might already be called, or it might be called in the | 367 // notifyFinished might already be called, or it might be called in the |
| 453 // future (if the parsing finishes earlier because of a parse error). | 368 // future (if the parsing finishes earlier because of a parse error). |
| 454 m_loadingTaskRunner->postTask(BLINK_FROM_HERE, crossThreadBind(&ScriptStream er::streamingComplete, wrapCrossThreadPersistent(this))); | 369 m_loadingTaskRunner->postTask(BLINK_FROM_HERE, crossThreadBind(&ScriptStream er::streamingComplete, wrapCrossThreadPersistent(this))); |
| 455 | 370 |
| 456 // The task might delete ScriptStreamer, so it's not safe to do anything | 371 // The task might delete ScriptStreamer, so it's not safe to do anything |
| 457 // after posting it. Note that there's no way to guarantee that this | 372 // after posting it. Note that there's no way to guarantee that this |
| 458 // function has returned before the task is ran - however, we should not | 373 // function has returned before the task is ran - however, we should not |
| 459 // access the "this" object after posting the task. (Especially, we should | 374 // access the "this" object after posting the task. (Especially, we should |
| 460 // not be holding the mutex at this point.) | 375 // not be holding the mutex at this point.) |
| 461 } | 376 } |
| 462 | 377 |
| 463 void ScriptStreamer::cancel() | 378 void ScriptStreamer::cancel() |
| 464 { | 379 { |
| 465 ASSERT(isMainThread()); | 380 DCHECK(isMainThread()); |
| 466 // The upper layer doesn't need the script any more, but streaming might | 381 // The upper layer doesn't need the script any more, but streaming might |
| 467 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets | 382 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets |
| 468 // the control the next time. It can also be that V8 has already completed | 383 // the control the next time. It can also be that V8 has already completed |
| 469 // its operations and streamingComplete will be called soon. | 384 // its operations and streamingComplete will be called soon. |
| 470 m_detached = true; | 385 m_detached = true; |
| 471 m_resource = 0; | 386 m_resource = 0; |
| 472 if (m_stream) | 387 if (m_stream) |
| 473 m_stream->cancel(); | 388 m_stream->cancel(); |
| 474 } | 389 } |
| 475 | 390 |
| 476 void ScriptStreamer::suppressStreaming() | 391 void ScriptStreamer::suppressStreaming() |
| 477 { | 392 { |
| 478 MutexLocker locker(m_mutex); | 393 MutexLocker locker(m_mutex); |
| 479 ASSERT(!m_loadingFinished); | 394 DCHECK(!m_loadingFinished); |
| 480 // It can be that the parsing task has already finished (e.g., if there was | 395 // It can be that the parsing task has already finished (e.g., if there was |
| 481 // a parse error). | 396 // a parse error). |
| 482 m_streamingSuppressed = true; | 397 m_streamingSuppressed = true; |
| 483 } | 398 } |
| 484 | 399 |
| 485 void ScriptStreamer::notifyAppendData(ScriptResource* resource) | 400 void ScriptStreamer::notifyAppendData(ScriptResource* resource) |
| 486 { | 401 { |
| 487 ASSERT(isMainThread()); | 402 DCHECK(isMainThread()); |
| 488 ASSERT(m_resource == resource); | 403 DCHECK_EQ(m_resource, resource); |
| 489 { | 404 { |
| 490 MutexLocker locker(m_mutex); | 405 MutexLocker locker(m_mutex); |
| 491 if (m_streamingSuppressed) | 406 if (m_streamingSuppressed) |
| 492 return; | 407 return; |
| 493 } | 408 } |
| 494 size_t lengthOfBOM = 0; | |
| 495 if (!m_haveEnoughDataForStreaming) { | 409 if (!m_haveEnoughDataForStreaming) { |
| 496 // Even if the first data chunk is small, the script can still be big | 410 // Even if the first data chunk is small, the script can still be big |
| 497 // enough - wait until the next data chunk comes before deciding whether | 411 // enough - wait until the next data chunk comes before deciding whether |
| 498 // to start the streaming. | 412 // to start the streaming. |
| 499 ASSERT(resource->resourceBuffer()); | 413 DCHECK(resource->resourceBuffer()); |
| 500 if (resource->resourceBuffer()->size() < s_smallScriptThreshold) | 414 if (resource->resourceBuffer()->size() < s_smallScriptThreshold) |
| 501 return; | 415 return; |
| 502 m_haveEnoughDataForStreaming = true; | 416 m_haveEnoughDataForStreaming = true; |
| 503 | 417 |
| 504 // Encoding should be detected only when we have some data. It's | 418 { |
| 505 // possible that resource->encoding() returns a different encoding | 419 // Check for BOM (byte order marks), because that might change our |
| 506 // before the loading has started and after we got some data. In | 420 // understanding of the data encoding. |
| 507 // addition, check for byte order marks. Note that checking the byte | 421 constexpr size_t maximumLengthOfBOM = 4; |
| 508 // order mark might change the encoding. We cannot decode the full text | 422 char maybeBOM[maximumLengthOfBOM] = {}; |
| 509 // here, because it might contain incomplete UTF-8 characters. Also note | 423 if (!resource->resourceBuffer()->getPartAsBytes(maybeBOM, static_cas t<size_t>(0), maximumLengthOfBOM)) { |
| 510 // that have at least s_smallScriptThreshold worth of data, which is mor e | 424 NOTREACHED(); |
| 511 // than enough for detecting a BOM. | 425 return; |
| 512 constexpr size_t maximumLengthOfBOM = 4; | 426 } |
| 513 char maybeBOM[maximumLengthOfBOM] = {}; | 427 |
| 514 if (!resource->resourceBuffer()->getPartAsBytes(maybeBOM, static_cast<si ze_t>(0), maximumLengthOfBOM)) { | 428 std::unique_ptr<TextResourceDecoder> decoder(TextResourceDecoder::cr eate("application/javascript", resource->encoding())); |
| 515 NOTREACHED(); | 429 decoder->checkForBOM(maybeBOM, maximumLengthOfBOM); |
| 516 return; | 430 |
| 431 // The encoding may change when we see the BOM. Check for BOM now | |
| 432 // and update the encoding from the decoder when necessary. Abort | |
| 433 // streaming if the encoding is different from the one we started | |
|
marja
2016/09/20 19:03:55
This comment is not correct: we don't abort stream
vogelheim
2016/09/22 08:15:14
Within Chromium: Yes, I think so.
This is a bit s
vogelheim
2016/09/22 08:15:14
Done.
| |
| 434 // with. | |
| 435 // | |
| 436 // Also note that have at least s_smallScriptThreshold worth of | |
| 437 // data, which is more than enough for detecting a BOM. | |
| 438 if (!convertEncoding(decoder->encoding().name(), &m_encoding)) { | |
| 439 suppressStreaming(); | |
| 440 recordNotStreamingReasonHistogram(m_scriptType, EncodingNotSuppo rted); | |
| 441 recordStartedStreamingHistogram(m_scriptType, 0); | |
| 442 return; | |
| 443 } | |
| 517 } | 444 } |
| 518 | 445 |
| 519 std::unique_ptr<TextResourceDecoder> decoder(TextResourceDecoder::create ("application/javascript", resource->encoding())); | |
| 520 lengthOfBOM = decoder->checkForBOM(maybeBOM, maximumLengthOfBOM); | |
| 521 | |
| 522 // Maybe the encoding changed because we saw the BOM; get the encoding | |
| 523 // from the decoder. | |
| 524 if (!convertEncoding(decoder->encoding().name(), &m_encoding)) { | |
| 525 suppressStreaming(); | |
| 526 recordNotStreamingReasonHistogram(m_scriptType, EncodingNotSupported ); | |
| 527 recordStartedStreamingHistogram(m_scriptType, 0); | |
| 528 return; | |
| 529 } | |
| 530 if (ScriptStreamerThread::shared()->isRunningTask()) { | 446 if (ScriptStreamerThread::shared()->isRunningTask()) { |
| 531 // At the moment we only have one thread for running the tasks. A | 447 // At the moment we only have one thread for running the tasks. A |
| 532 // new task shouldn't be queued before the running task completes, | 448 // new task shouldn't be queued before the running task completes, |
| 533 // because the running task can block and wait for data from the | 449 // because the running task can block and wait for data from the |
| 534 // network. | 450 // network. |
| 535 suppressStreaming(); | 451 suppressStreaming(); |
| 536 recordNotStreamingReasonHistogram(m_scriptType, ThreadBusy); | 452 recordNotStreamingReasonHistogram(m_scriptType, ThreadBusy); |
| 537 recordStartedStreamingHistogram(m_scriptType, 0); | 453 recordStartedStreamingHistogram(m_scriptType, 0); |
| 538 return; | 454 return; |
| 539 } | 455 } |
| 540 | 456 |
| 541 if (!m_scriptState->contextIsValid()) { | 457 if (!m_scriptState->contextIsValid()) { |
| 542 suppressStreaming(); | 458 suppressStreaming(); |
| 543 recordNotStreamingReasonHistogram(m_scriptType, ContextNotValid); | 459 recordNotStreamingReasonHistogram(m_scriptType, ContextNotValid); |
| 544 recordStartedStreamingHistogram(m_scriptType, 0); | 460 recordStartedStreamingHistogram(m_scriptType, 0); |
| 545 return; | 461 return; |
| 546 } | 462 } |
| 547 | 463 |
| 548 ASSERT(!m_stream); | 464 DCHECK(!m_stream); |
| 549 ASSERT(!m_source); | 465 DCHECK(!m_source); |
| 550 m_stream = new SourceStream(m_loadingTaskRunner.get()); | 466 m_stream = new SourceStream(m_loadingTaskRunner.get()); |
| 551 // m_source takes ownership of m_stream. | 467 // m_source takes ownership of m_stream. |
| 552 m_source = wrapUnique(new v8::ScriptCompiler::StreamedSource(m_stream, m _encoding)); | 468 m_source = wrapUnique(new v8::ScriptCompiler::StreamedSource(m_stream, m _encoding)); |
| 553 | 469 |
| 554 ScriptState::Scope scope(m_scriptState.get()); | 470 ScriptState::Scope scope(m_scriptState.get()); |
| 555 std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> scriptStreaming Task(wrapUnique(v8::ScriptCompiler::StartStreamingScript(m_scriptState->isolate( ), m_source.get(), m_compileOptions))); | 471 std::unique_ptr<v8::ScriptCompiler::ScriptStreamingTask> scriptStreaming Task(wrapUnique(v8::ScriptCompiler::StartStreamingScript(m_scriptState->isolate( ), m_source.get(), m_compileOptions))); |
| 556 if (!scriptStreamingTask) { | 472 if (!scriptStreamingTask) { |
| 557 // V8 cannot stream the script. | 473 // V8 cannot stream the script. |
| 558 suppressStreaming(); | 474 suppressStreaming(); |
| 559 m_stream = 0; | 475 m_stream = 0; |
| 560 m_source.reset(); | 476 m_source.reset(); |
| 561 recordNotStreamingReasonHistogram(m_scriptType, V8CannotStream); | 477 recordNotStreamingReasonHistogram(m_scriptType, V8CannotStream); |
| 562 recordStartedStreamingHistogram(m_scriptType, 0); | 478 recordStartedStreamingHistogram(m_scriptType, 0); |
| 563 return; | 479 return; |
| 564 } | 480 } |
| 565 | 481 |
| 566 ScriptStreamerThread::shared()->postTask(crossThreadBind(&ScriptStreamer Thread::runScriptStreamingTask, passed(std::move(scriptStreamingTask)), wrapCros sThreadPersistent(this))); | 482 ScriptStreamerThread::shared()->postTask(crossThreadBind(&ScriptStreamer Thread::runScriptStreamingTask, passed(std::move(scriptStreamingTask)), wrapCros sThreadPersistent(this))); |
| 567 recordStartedStreamingHistogram(m_scriptType, 1); | 483 recordStartedStreamingHistogram(m_scriptType, 1); |
| 568 } | 484 } |
| 569 if (m_stream) | 485 if (m_stream) |
| 570 m_stream->didReceiveData(this, lengthOfBOM); | 486 m_stream->didReceiveData(this); |
| 571 } | 487 } |
| 572 | 488 |
| 573 void ScriptStreamer::notifyFinished(Resource* resource) | 489 void ScriptStreamer::notifyFinished(Resource* resource) |
| 574 { | 490 { |
| 575 ASSERT(isMainThread()); | 491 DCHECK(isMainThread()); |
| 576 ASSERT(m_resource == resource); | 492 DCHECK_EQ(m_resource, resource); |
| 577 // A special case: empty and small scripts. We didn't receive enough data to | 493 // A special case: empty and small scripts. We didn't receive enough data to |
| 578 // start the streaming before this notification. In that case, there won't | 494 // start the streaming before this notification. In that case, there won't |
| 579 // be a "parsing complete" notification either, and we should not wait for | 495 // be a "parsing complete" notification either, and we should not wait for |
| 580 // it. | 496 // it. |
| 581 if (!m_haveEnoughDataForStreaming) { | 497 if (!m_haveEnoughDataForStreaming) { |
| 582 recordNotStreamingReasonHistogram(m_scriptType, ScriptTooSmall); | 498 recordNotStreamingReasonHistogram(m_scriptType, ScriptTooSmall); |
| 583 recordStartedStreamingHistogram(m_scriptType, 0); | 499 recordStartedStreamingHistogram(m_scriptType, 0); |
| 584 suppressStreaming(); | 500 suppressStreaming(); |
| 585 } | 501 } |
| 586 if (m_stream) | 502 if (m_stream) |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 616 DEFINE_TRACE(ScriptStreamer) | 532 DEFINE_TRACE(ScriptStreamer) |
| 617 { | 533 { |
| 618 visitor->trace(m_pendingScript); | 534 visitor->trace(m_pendingScript); |
| 619 visitor->trace(m_resource); | 535 visitor->trace(m_resource); |
| 620 } | 536 } |
| 621 | 537 |
| 622 void ScriptStreamer::streamingComplete() | 538 void ScriptStreamer::streamingComplete() |
| 623 { | 539 { |
| 624 // The background task is completed; do the necessary ramp-down in the main | 540 // The background task is completed; do the necessary ramp-down in the main |
| 625 // thread. | 541 // thread. |
| 626 ASSERT(isMainThread()); | 542 DCHECK(isMainThread()); |
| 627 | 543 |
| 628 // It's possible that the corresponding Resource was deleted before V8 | 544 // It's possible that the corresponding Resource was deleted before V8 |
| 629 // finished streaming. In that case, the data or the notification is not | 545 // finished streaming. In that case, the data or the notification is not |
| 630 // needed. In addition, if the streaming is suppressed, the non-streaming | 546 // needed. In addition, if the streaming is suppressed, the non-streaming |
| 631 // code path will resume after the resource has loaded, before the | 547 // code path will resume after the resource has loaded, before the |
| 632 // background task finishes. | 548 // background task finishes. |
| 633 if (m_detached || m_streamingSuppressed) | 549 if (m_detached || m_streamingSuppressed) |
| 634 return; | 550 return; |
| 635 | 551 |
| 636 // We have now streamed the whole script to V8 and it has parsed the | 552 // We have now streamed the whole script to V8 and it has parsed the |
| 637 // script. We're ready for the next step: compiling and executing the | 553 // script. We're ready for the next step: compiling and executing the |
| 638 // script. | 554 // script. |
| 639 notifyFinishedToClient(); | 555 notifyFinishedToClient(); |
| 640 } | 556 } |
| 641 | 557 |
| 642 void ScriptStreamer::notifyFinishedToClient() | 558 void ScriptStreamer::notifyFinishedToClient() |
| 643 { | 559 { |
| 644 ASSERT(isMainThread()); | 560 DCHECK(isMainThread()); |
| 645 // Usually, the loading will be finished first, and V8 will still need some | 561 // Usually, the loading will be finished first, and V8 will still need some |
| 646 // time to catch up. But the other way is possible too: if V8 detects a | 562 // time to catch up. But the other way is possible too: if V8 detects a |
| 647 // parse error, the V8 side can complete before loading has finished. Send | 563 // parse error, the V8 side can complete before loading has finished. Send |
| 648 // the notification after both loading and V8 side operations have | 564 // the notification after both loading and V8 side operations have |
| 649 // completed. Here we also check that we have a client: it can happen that a | 565 // completed. Here we also check that we have a client: it can happen that a |
| 650 // function calling notifyFinishedToClient was already scheduled in the task | 566 // function calling notifyFinishedToClient was already scheduled in the task |
| 651 // queue and the upper layer decided that it's not interested in the script | 567 // queue and the upper layer decided that it's not interested in the script |
| 652 // and called removeClient. | 568 // and called removeClient. |
| 653 if (!isFinished()) | 569 if (!isFinished()) |
| 654 return; | 570 return; |
| 655 | 571 |
| 656 m_pendingScript->streamingFinished(); | 572 m_pendingScript->streamingFinished(); |
| 657 } | 573 } |
| 658 | 574 |
| 659 bool ScriptStreamer::startStreamingInternal(PendingScript* script, Type scriptTy pe, Settings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunn er) | 575 bool ScriptStreamer::startStreamingInternal(PendingScript* script, Type scriptTy pe, Settings* settings, ScriptState* scriptState, WebTaskRunner* loadingTaskRunn er) |
| 660 { | 576 { |
| 661 ASSERT(isMainThread()); | 577 DCHECK(isMainThread()); |
| 662 ASSERT(scriptState->contextIsValid()); | 578 DCHECK(scriptState->contextIsValid()); |
| 663 ScriptResource* resource = script->resource(); | 579 ScriptResource* resource = script->resource(); |
| 664 if (resource->isLoaded()) { | 580 if (resource->isLoaded()) { |
| 665 recordNotStreamingReasonHistogram(scriptType, AlreadyLoaded); | 581 recordNotStreamingReasonHistogram(scriptType, AlreadyLoaded); |
| 666 return false; | 582 return false; |
| 667 } | 583 } |
| 668 if (!resource->url().protocolIsInHTTPFamily()) { | 584 if (!resource->url().protocolIsInHTTPFamily()) { |
| 669 recordNotStreamingReasonHistogram(scriptType, NotHTTP); | 585 recordNotStreamingReasonHistogram(scriptType, NotHTTP); |
| 670 return false; | 586 return false; |
| 671 } | 587 } |
| 672 if (resource->isCacheValidator()) { | 588 if (resource->isCacheValidator()) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 688 | 604 |
| 689 // The Resource might go out of scope if the script is no longer | 605 // The Resource might go out of scope if the script is no longer |
| 690 // needed. This makes PendingScript notify the ScriptStreamer when it is | 606 // needed. This makes PendingScript notify the ScriptStreamer when it is |
| 691 // destroyed. | 607 // destroyed. |
| 692 script->setStreamer(ScriptStreamer::create(script, scriptType, scriptState, compileOption, loadingTaskRunner)); | 608 script->setStreamer(ScriptStreamer::create(script, scriptType, scriptState, compileOption, loadingTaskRunner)); |
| 693 | 609 |
| 694 return true; | 610 return true; |
| 695 } | 611 } |
| 696 | 612 |
| 697 } // namespace blink | 613 } // namespace blink |
| OLD | NEW |