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 "bindings/core/v8/ScriptStreamer.h" |
| 7 |
| 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/fetch/ScriptResource.h" |
| 14 #include "core/frame/Settings.h" |
| 15 #include "platform/SharedBuffer.h" |
| 16 #include "wtf/MainThread.h" |
| 17 #include "wtf/text/TextEncodingRegistry.h" |
| 18 |
| 19 namespace blink { |
| 20 |
| 21 // SourceStream implements the streaming interface towards V8. The main |
| 22 // functionality is preparing the data to give to V8 on main thread, and |
| 23 // actually giving the data (via GetMoreData which is called on a background |
| 24 // thread). |
| 25 class SourceStream : public v8::ScriptCompiler::ExternalSourceStream { |
| 26 WTF_MAKE_NONCOPYABLE(SourceStream); |
| 27 public: |
| 28 SourceStream(ScriptStreamer* streamer) |
| 29 : v8::ScriptCompiler::ExternalSourceStream() |
| 30 , m_streamer(streamer) |
| 31 , m_backgroundThreadWaitingForData(false) |
| 32 , m_dataPointer(0) |
| 33 , m_dataLength(0) |
| 34 , m_dataPosition(0) |
| 35 , m_loadingFinished(false) |
| 36 , m_cancelled(false) |
| 37 , m_allDataReturned(false) { } |
| 38 |
| 39 virtual ~SourceStream() { } |
| 40 |
| 41 // Called by V8 on a background thread. Should block until we can return |
| 42 // some data. |
| 43 virtual size_t GetMoreData(const uint8_t** src) OVERRIDE |
| 44 { |
| 45 ASSERT(!isMainThread()); |
| 46 MutexLocker locker(m_mutex); |
| 47 if (m_cancelled || m_allDataReturned) |
| 48 return 0; |
| 49 // The main thread might be modifying the data buffer of the Resource, |
| 50 // so we cannot read it here. Post a task to the main thread and wait |
| 51 // for it to complete. |
| 52 m_backgroundThreadWaitingForData = true; |
| 53 m_dataPointer = src; |
| 54 m_dataLength = 0; |
| 55 callOnMainThread(WTF::bind(&SourceStream::prepareDataOnMainThread, this)
); |
| 56 m_condition.wait(m_mutex); |
| 57 if (m_cancelled || m_allDataReturned) |
| 58 return 0; |
| 59 return m_dataLength; |
| 60 } |
| 61 |
| 62 void didFinishLoading() |
| 63 { |
| 64 ASSERT(isMainThread()); |
| 65 if (m_streamer->resource()->errorOccurred()) { |
| 66 // The background thread might be waiting for data. Wake it up. V8 |
| 67 // will perceive this as EOS and call the completion callback. At |
| 68 // that point we will resume the normal "script loaded" code path |
| 69 // (see HTMLScriptRunner::notifyFinished which doesn't differentiate |
| 70 // between scripts which loaded successfully and scripts which |
| 71 // failed to load). |
| 72 cancel(); |
| 73 return; |
| 74 } |
| 75 { |
| 76 MutexLocker locker(m_mutex); |
| 77 m_loadingFinished = true; |
| 78 if (!m_backgroundThreadWaitingForData) |
| 79 return; |
| 80 } |
| 81 prepareDataOnMainThread(); |
| 82 } |
| 83 |
| 84 void didReceiveData() |
| 85 { |
| 86 ASSERT(isMainThread()); |
| 87 prepareDataOnMainThread(); |
| 88 } |
| 89 |
| 90 void cancel() |
| 91 { |
| 92 ASSERT(isMainThread()); |
| 93 // The script is no longer needed by the upper layers. Stop streaming |
| 94 // it. The next time GetMoreData is called (or woken up), it will return |
| 95 // 0, which will be interpreted as EOS by V8 and the parsing will |
| 96 // fail. ScriptStreamer::streamingComplete will be called, and at that |
| 97 // point we will release the references to SourceStream. |
| 98 MutexLocker locker(m_mutex); |
| 99 m_cancelled = true; |
| 100 m_backgroundThreadWaitingForData = false; |
| 101 m_condition.signal(); |
| 102 } |
| 103 |
| 104 private: |
| 105 void prepareDataOnMainThread() |
| 106 { |
| 107 ASSERT(isMainThread()); |
| 108 MutexLocker locker(m_mutex); |
| 109 if (!m_backgroundThreadWaitingForData) { |
| 110 // It's possible that the background thread is not waiting. This |
| 111 // happens when 1) data arrives but V8 hasn't requested data yet, 2) |
| 112 // we have previously scheduled prepareDataOnMainThread, but some |
| 113 // other event (load cancelled or finished) already retrieved the |
| 114 // data and set m_backgroundThreadWaitingForData to false. |
| 115 return; |
| 116 } |
| 117 // The Resource must still be alive; otherwise we should've cancelled |
| 118 // the streaming (if we have cancelled, the background thread is not |
| 119 // waiting). |
| 120 ASSERT(m_streamer->resource()); |
| 121 |
| 122 if (m_streamer->resource()->cachedMetadata(V8ScriptRunner::tagForCodeCac
he())) { |
| 123 // The resource has a code cache, so it's unnecessary to stream and |
| 124 // parse the code. Cancel the streaming and resume the non-streaming |
| 125 // code path. |
| 126 m_streamer->suppressStreaming(); |
| 127 m_cancelled = true; |
| 128 m_backgroundThreadWaitingForData = false; |
| 129 m_condition.signal(); |
| 130 return; |
| 131 } |
| 132 |
| 133 if (!m_resourceBuffer) { |
| 134 // We don't have a buffer yet. Try to get it from the resource. |
| 135 SharedBuffer* buffer = m_streamer->resource()->resourceBuffer(); |
| 136 if (!buffer) { |
| 137 // The Resource hasn't created its SharedBuffer yet. At some |
| 138 // point (at latest when the script loading finishes) this |
| 139 // function will be called again and we will have data. |
| 140 if (m_loadingFinished) { |
| 141 // This happens only in special cases (e.g., involving empty |
| 142 // scripts). |
| 143 m_allDataReturned = true; |
| 144 m_backgroundThreadWaitingForData = false; |
| 145 m_condition.signal(); |
| 146 } |
| 147 return; |
| 148 } |
| 149 m_resourceBuffer = RefPtr<SharedBuffer>(buffer); |
| 150 } |
| 151 |
| 152 // Get as much data from the ResourceBuffer as we can. |
| 153 const char* data = 0; |
| 154 Vector<const char*> chunks; |
| 155 Vector<unsigned> chunkLengths; |
| 156 while (unsigned length = m_resourceBuffer->getSomeData(data, m_dataPosit
ion)) { |
| 157 // FIXME: Here we can limit based on the total length, if it turns |
| 158 // out that we don't want to give all the data we have (memory |
| 159 // vs. speed). |
| 160 chunks.append(data); |
| 161 chunkLengths.append(length); |
| 162 m_dataLength += length; |
| 163 m_dataPosition += length; |
| 164 } |
| 165 // Copy the data chunks into a new buffer, since we're going to give the |
| 166 // data to a background thread. |
| 167 if (m_dataLength > 0) { |
| 168 uint8_t* copiedData = new uint8_t[m_dataLength]; |
| 169 unsigned offset = 0; |
| 170 for (size_t i = 0; i < chunks.size(); ++i) { |
| 171 memcpy(copiedData + offset, chunks[i], chunkLengths[i]); |
| 172 offset += chunkLengths[i]; |
| 173 } |
| 174 *m_dataPointer = copiedData; |
| 175 m_backgroundThreadWaitingForData = false; |
| 176 m_condition.signal(); |
| 177 } else if (m_loadingFinished) { |
| 178 // We need to return data length 0 to V8 to signal that the data |
| 179 // ends. |
| 180 m_allDataReturned = true; |
| 181 m_backgroundThreadWaitingForData = false; |
| 182 m_condition.signal(); |
| 183 } |
| 184 // Otherwise, we had scheduled prepareDataOnMainThread but we didn't |
| 185 // have any data to return. It will be scheduled again when the data |
| 186 // arrives. |
| 187 } |
| 188 |
| 189 ScriptStreamer* m_streamer; |
| 190 |
| 191 // For coordinating between the main thread and background thread tasks. |
| 192 // Guarded by m_mutex. |
| 193 bool m_backgroundThreadWaitingForData; |
| 194 const uint8_t** m_dataPointer; |
| 195 unsigned m_dataLength; |
| 196 unsigned m_dataPosition; |
| 197 bool m_loadingFinished; |
| 198 bool m_cancelled; |
| 199 bool m_allDataReturned; |
| 200 RefPtr<SharedBuffer> m_resourceBuffer; // Only used by the main thread. |
| 201 |
| 202 ThreadCondition m_condition; |
| 203 Mutex m_mutex; |
| 204 }; |
| 205 |
| 206 |
| 207 bool ScriptStreamer::startStreaming(PendingScript& script, Settings* settings, S
criptState* scriptState) |
| 208 { |
| 209 ASSERT(isMainThread()); |
| 210 if (!settings || !settings->v8ScriptStreamingEnabled()) |
| 211 return false; |
| 212 if (ScriptStreamerThread::shared()->isRunningTask()) { |
| 213 // At the moment we only have one thread for running the tasks. A new |
| 214 // task shouldn't be queued before the running task completes, because |
| 215 // the running task can block and wait for data from the network. At the |
| 216 // moment we are only streaming parser blocking scripts, but this code |
| 217 // can still be hit when multiple frames are loading simultaneously. |
| 218 return false; |
| 219 } |
| 220 ScriptResource* resource = script.resource(); |
| 221 if (!resource->url().protocolIsInHTTPFamily()) |
| 222 return false; |
| 223 if (resource->resourceToRevalidate()) { |
| 224 // This happens e.g., during reloads. We're actually not going to load |
| 225 // the current Resource of the PendingScript but switch to another |
| 226 // Resource -> don't stream. |
| 227 return false; |
| 228 } |
| 229 // We cannot filter out short scripts, even if we wait for the HTTP headers |
| 230 // to arrive. In general, the web servers don't seem to send the |
| 231 // Content-Length HTTP header for scripts. |
| 232 |
| 233 WTF::TextEncoding textEncoding(resource->encoding()); |
| 234 const char* encodingName = textEncoding.name(); |
| 235 |
| 236 // Here's a list of encodings we can use for streaming. These are |
| 237 // the canonical names. |
| 238 v8::ScriptCompiler::StreamedSource::Encoding encoding; |
| 239 if (strcmp(encodingName, "windows-1252") == 0 |
| 240 || strcmp(encodingName, "ISO-8859-1") == 0 |
| 241 || strcmp(encodingName, "US-ASCII") == 0) { |
| 242 encoding = v8::ScriptCompiler::StreamedSource::ONE_BYTE; |
| 243 } else if (strcmp(encodingName, "UTF-8") == 0) { |
| 244 encoding = v8::ScriptCompiler::StreamedSource::UTF8; |
| 245 } else { |
| 246 // We don't stream other encodings; especially we don't stream two byte |
| 247 // scripts to avoid the handling of byte order marks. Most scripts are |
| 248 // Latin1 or UTF-8 anyway, so this should be enough for most real world |
| 249 // purposes. |
| 250 return false; |
| 251 } |
| 252 |
| 253 if (scriptState->contextIsValid()) |
| 254 return false; |
| 255 ScriptState::Scope scope(scriptState); |
| 256 |
| 257 // The Resource might go out of scope if the script is no longer needed. We |
| 258 // will soon call PendingScript::setStreamer, which makes the PendingScript |
| 259 // notify the ScriptStreamer when it is destroyed. |
| 260 RefPtr<ScriptStreamer> streamer = adoptRef(new ScriptStreamer(resource, enco
ding)); |
| 261 |
| 262 // Decide what kind of cached data we should produce while streaming. By |
| 263 // default, we generate the parser cache for streamed scripts, to emulate |
| 264 // the non-streaming behavior (seeV8ScriptRunner::compileScript). |
| 265 v8::ScriptCompiler::CompileOptions compileOption = v8::ScriptCompiler::kProd
uceParserCache; |
| 266 if (settings->v8CacheOptions() == V8CacheOptionsCode) |
| 267 compileOption = v8::ScriptCompiler::kProduceCodeCache; |
| 268 v8::ScriptCompiler::ScriptStreamingTask* scriptStreamingTask = v8::ScriptCom
piler::StartStreamingScript(scriptState->isolate(), &(streamer->m_source), compi
leOption); |
| 269 if (scriptStreamingTask) { |
| 270 ScriptStreamingTask* task = new ScriptStreamingTask(scriptStreamingTask,
streamer.get()); |
| 271 // ScriptStreamer needs to stay alive as long as the background task is |
| 272 // running. This is taken care of with a manual ref() & deref() pair; |
| 273 // the corresponding deref() is in streamingComplete. |
| 274 streamer->ref(); |
| 275 script.setStreamer(streamer.release()); |
| 276 ScriptStreamerThread::shared()->postTask(task); |
| 277 return true; |
| 278 } |
| 279 // Otherwise, V8 cannot stream the script. |
| 280 return false; |
| 281 } |
| 282 |
| 283 void ScriptStreamer::streamingComplete() |
| 284 { |
| 285 ASSERT(isMainThread()); |
| 286 // It's possible that the corresponding Resource was deleted before V8 |
| 287 // finished streaming. In that case, the data or the notification is not |
| 288 // needed. |
| 289 if (m_detached) { |
| 290 deref(); |
| 291 return; |
| 292 } |
| 293 |
| 294 // We have now streamed the whole script to V8 and it has parsed the |
| 295 // script. We're ready for the next step: compiling and executing the |
| 296 // script. |
| 297 m_parsingFinished = true; |
| 298 |
| 299 notifyFinishedToClient(); |
| 300 |
| 301 // The background thread no longer holds an implicit reference. |
| 302 deref(); |
| 303 } |
| 304 |
| 305 void ScriptStreamer::cancel() |
| 306 { |
| 307 ASSERT(isMainThread()); |
| 308 // The upper layer doesn't need the script any more, but streaming might |
| 309 // still be ongoing. Tell SourceStream to try to cancel it whenever it gets |
| 310 // the control the next time. It can also be that V8 has already completed |
| 311 // its operations and streamingComplete will be called soon. |
| 312 m_detached = true; |
| 313 m_resource = 0; |
| 314 m_stream->cancel(); |
| 315 } |
| 316 |
| 317 void ScriptStreamer::notifyAppendData(ScriptResource* resource) |
| 318 { |
| 319 ASSERT(isMainThread()); |
| 320 ASSERT(m_resource == resource); |
| 321 m_stream->didReceiveData(); |
| 322 } |
| 323 |
| 324 void ScriptStreamer::notifyFinished(Resource* resource) |
| 325 { |
| 326 ASSERT(isMainThread()); |
| 327 ASSERT(m_resource == resource); |
| 328 m_stream->didFinishLoading(); |
| 329 m_loadingFinished = true; |
| 330 notifyFinishedToClient(); |
| 331 } |
| 332 |
| 333 ScriptStreamer::ScriptStreamer(ScriptResource* resource, v8::ScriptCompiler::Str
eamedSource::Encoding encoding) |
| 334 : m_resource(resource) |
| 335 , m_detached(false) |
| 336 , m_stream(new SourceStream(this)) |
| 337 , m_source(m_stream, encoding) // m_source takes ownership of m_stream. |
| 338 , m_client(0) |
| 339 , m_loadingFinished(false) |
| 340 , m_parsingFinished(false) |
| 341 , m_streamingSuppressed(false) |
| 342 { |
| 343 } |
| 344 |
| 345 void ScriptStreamer::notifyFinishedToClient() |
| 346 { |
| 347 ASSERT(isMainThread()); |
| 348 // Usually, the loading will be finished first, and V8 will still need some |
| 349 // time to catch up. But the other way is possible too: if V8 detects a |
| 350 // parse error, the V8 side can complete before loading has finished. Send |
| 351 // the notification after both loading and V8 side operations have |
| 352 // completed. Here we also check that we have a client: it can happen that a |
| 353 // function calling notifyFinishedToClient was already scheduled in the task |
| 354 // queue and the upper layer decided that it's not interested in the script |
| 355 // and called removeClient. |
| 356 if (m_loadingFinished && m_parsingFinished && m_client) |
| 357 m_client->notifyFinished(m_resource); |
| 358 } |
| 359 |
| 360 } // namespace blink |
OLD | NEW |