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.
7 #include "bindings/core/v8/ScriptStreamer.h"
9 #include "bindings/core/v8/ScriptSourceCode.h"
10 #include "bindings/core/v8/ScriptStreamerThread.h"
11 #include "bindings/core/v8/ScriptStreamingMode.h"
12 #include "bindings/core/v8/V8Binding.h"
13 #include "bindings/core/v8/V8ScriptRunner.h"
14 #include "core/dom/PendingScript.h"
15 #include "core/frame/Settings.h"
16 #include "platform/Task.h"
17 #include "platform/heap/Handle.h"
18 #include "public/platform/Platform.h"
20 #include <gtest/gtest.h>
27 // For the benefit of Oilpan, put the part object PendingScript inside
28 // a wrapper that's on the Oilpan heap and hold a reference to that wrapper
29 // from ScriptStreamingTest.
30 class PendingScriptWrapper : public NoBaseWillBeGarbageCollectedFinalized<PendingScriptWrapper> {
32 static PassOwnPtrWillBeRawPtr<PendingScriptWrapper> create()
34 return adoptPtrWillBeNoop(new PendingScriptWrapper());
37 static PassOwnPtrWillBeRawPtr<PendingScriptWrapper> create(Element* element, ScriptResource* resource)
39 return adoptPtrWillBeNoop(new PendingScriptWrapper(element, resource));
42 PendingScript& get() { return m_pendingScript; }
44 void trace(Visitor* visitor)
46 visitor->trace(m_pendingScript);
50 PendingScriptWrapper()
54 PendingScriptWrapper(Element* element, ScriptResource* resource)
55 : m_pendingScript(PendingScript(element, resource))
59 PendingScript m_pendingScript;
62 // The bool param for ScriptStreamingTest controls whether to make the main
63 // thread block and wait for parsing.
64 class ScriptStreamingTest : public testing::TestWithParam<bool> {
67 : m_scope(v8::Isolate::GetCurrent())
68 , m_settings(Settings::create())
69 , m_resourceRequest("http://www.streaming-test.com/")
70 , m_resource(new ScriptResource(m_resourceRequest, "text/utf-8"))
71 , m_pendingScript(PendingScriptWrapper::create(0, m_resource)) // Takes ownership of m_resource.
73 m_settings->setV8ScriptStreamingEnabled(true);
75 m_settings->setV8ScriptStreamingMode(ScriptStreamingModeAllPlusBlockParsingBlocking);
76 m_resource->setLoading(true);
77 ScriptStreamer::setSmallScriptThresholdForTesting(0);
80 ScriptState* scriptState() const { return m_scope.scriptState(); }
81 v8::Isolate* isolate() const { return m_scope.isolate(); }
83 PendingScript& pendingScript() const { return m_pendingScript->get(); }
86 void appendData(const char* data)
88 m_resource->appendData(data, strlen(data));
89 // Yield control to the background thread, so that V8 gets a change to
90 // process the data before the main thread adds more. Note that we
91 // cannot fully control in what kind of chunks the data is passed to V8
92 // (if the V8 is not requesting more data between two appendData calls,
93 // V8 will get both chunks together).
94 Platform::current()->yieldCurrentThread();
99 for (int i = 0; i < 10; ++i) {
100 appendData(" /* this is padding to make the script long enough, so "
101 "that V8's buffer gets filled and it starts processing "
108 m_resource->finish();
109 m_resource->setLoading(false);
112 void processTasksUntilStreamingComplete()
114 WebThread* currentThread = blink::Platform::current()->currentThread();
115 while (ScriptStreamerThread::shared()->isRunningTask()) {
116 currentThread->postTask(new Task(WTF::bind(&WebThread::exitRunLoop, currentThread)));
117 currentThread->enterRunLoop();
119 // Once more, because the "streaming complete" notification might only
120 // now be in the task queue.
121 currentThread->postTask(new Task(WTF::bind(&WebThread::exitRunLoop, currentThread)));
122 currentThread->enterRunLoop();
125 V8TestingScope m_scope;
126 OwnPtr<Settings> m_settings;
127 // The Resource and PendingScript where we stream from. These don't really
128 // fetch any data outside the test; the test controls the data by calling
129 // ScriptResource::appendData.
130 ResourceRequest m_resourceRequest;
131 ScriptResource* m_resource;
132 OwnPtrWillBePersistent<PendingScriptWrapper> m_pendingScript;
135 class TestScriptResourceClient : public ScriptResourceClient {
137 TestScriptResourceClient()
138 : m_finished(false) { }
140 virtual void notifyFinished(Resource*) override { m_finished = true; }
142 bool finished() const { return m_finished; }
148 TEST_P(ScriptStreamingTest, CompilingStreamedScript)
150 // Test that we can successfully compile a streamed script.
151 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
152 TestScriptResourceClient client;
153 pendingScript().watchForLoad(&client);
155 appendData("function foo() {");
157 appendData("return 5; }");
159 appendData("foo();");
160 EXPECT_FALSE(client.finished());
163 // Process tasks on the main thread until the streaming background thread
164 // has completed its tasks.
165 processTasksUntilStreamingComplete();
166 EXPECT_TRUE(client.finished());
167 bool errorOccurred = false;
168 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
169 EXPECT_FALSE(errorOccurred);
170 EXPECT_TRUE(sourceCode.streamer());
171 v8::TryCatch tryCatch;
172 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate());
173 EXPECT_FALSE(script.IsEmpty());
174 EXPECT_FALSE(tryCatch.HasCaught());
177 TEST_P(ScriptStreamingTest, CompilingStreamedScriptWithParseError)
179 // Test that scripts with parse errors are handled properly. In those cases,
180 // the V8 side typically finished before loading finishes: make sure we
181 // handle it gracefully.
182 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
183 TestScriptResourceClient client;
184 pendingScript().watchForLoad(&client);
185 appendData("function foo() {");
186 appendData("this is the part which will be a parse error");
187 // V8 won't realize the parse error until it actually starts parsing the
188 // script, and this happens only when its buffer is filled.
191 EXPECT_FALSE(client.finished());
193 // Force the V8 side to finish before the loading.
194 processTasksUntilStreamingComplete();
195 EXPECT_FALSE(client.finished());
198 EXPECT_TRUE(client.finished());
200 bool errorOccurred = false;
201 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
202 EXPECT_FALSE(errorOccurred);
203 EXPECT_TRUE(sourceCode.streamer());
204 v8::TryCatch tryCatch;
205 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate());
206 EXPECT_TRUE(script.IsEmpty());
207 EXPECT_TRUE(tryCatch.HasCaught());
210 TEST_P(ScriptStreamingTest, CancellingStreaming)
212 // Test that the upper layers (PendingScript and up) can be ramped down
213 // while streaming is ongoing, and ScriptStreamer handles it gracefully.
214 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
215 TestScriptResourceClient client;
216 pendingScript().watchForLoad(&client);
217 appendData("function foo() {");
219 // In general, we cannot control what the background thread is doing
220 // (whether it's parsing or waiting for more data). In this test, we have
221 // given it so little data that it's surely waiting for more.
223 // Simulate cancelling the network load (e.g., because the user navigated
225 EXPECT_FALSE(client.finished());
226 pendingScript().stopWatchingForLoad(&client);
227 pendingScript().releaseElementAndClear();
228 m_pendingScript = PendingScriptWrapper::create(); // This will destroy m_resource.
231 // The V8 side will complete too. This should not crash. We don't receive
232 // any results from the streaming and the client doesn't get notified.
233 processTasksUntilStreamingComplete();
234 EXPECT_FALSE(client.finished());
237 TEST_P(ScriptStreamingTest, SuppressingStreaming)
239 // If we notice during streaming that there is a code cache, streaming
240 // is suppressed (V8 doesn't parse while the script is loading), and the
241 // upper layer (ScriptResourceClient) should get a notification when the
243 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
244 TestScriptResourceClient client;
245 pendingScript().watchForLoad(&client);
246 appendData("function foo() {");
249 m_resource->setCachedMetadata(V8ScriptRunner::tagForCodeCache(), "X", 1, Resource::CacheLocally);
253 processTasksUntilStreamingComplete();
254 EXPECT_TRUE(client.finished());
256 bool errorOccurred = false;
257 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
258 EXPECT_FALSE(errorOccurred);
259 // ScriptSourceCode doesn't refer to the streamer, since we have suppressed
260 // the streaming and resumed the non-streaming code path for script
262 EXPECT_FALSE(sourceCode.streamer());
265 TEST_P(ScriptStreamingTest, EmptyScripts)
267 // Empty scripts should also be streamed properly, that is, the upper layer
268 // (ScriptResourceClient) should be notified when an empty script has been
270 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
271 TestScriptResourceClient client;
272 pendingScript().watchForLoad(&client);
274 // Finish the script without sending any data.
276 // The finished notification should arrive immediately and not be cycled
277 // through a background thread.
278 EXPECT_TRUE(client.finished());
280 bool errorOccurred = false;
281 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
282 EXPECT_FALSE(errorOccurred);
283 EXPECT_FALSE(sourceCode.streamer());
286 TEST_P(ScriptStreamingTest, SmallScripts)
288 // Small scripts shouldn't be streamed.
289 ScriptStreamer::setSmallScriptThresholdForTesting(100);
291 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
292 TestScriptResourceClient client;
293 pendingScript().watchForLoad(&client);
295 appendData("function foo() { }");
299 // The finished notification should arrive immediately and not be cycled
300 // through a background thread.
301 EXPECT_TRUE(client.finished());
303 bool errorOccurred = false;
304 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
305 EXPECT_FALSE(errorOccurred);
306 EXPECT_FALSE(sourceCode.streamer());
309 TEST_P(ScriptStreamingTest, ScriptsWithSmallFirstChunk)
311 // If a script is long enough, if should be streamed, even if the first data
313 ScriptStreamer::setSmallScriptThresholdForTesting(100);
315 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
316 TestScriptResourceClient client;
317 pendingScript().watchForLoad(&client);
319 // This is the first data chunk which is small.
320 appendData("function foo() { }");
327 processTasksUntilStreamingComplete();
328 EXPECT_TRUE(client.finished());
329 bool errorOccurred = false;
330 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
331 EXPECT_FALSE(errorOccurred);
332 EXPECT_TRUE(sourceCode.streamer());
333 v8::TryCatch tryCatch;
334 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate());
335 EXPECT_FALSE(script.IsEmpty());
336 EXPECT_FALSE(tryCatch.HasCaught());
339 TEST_P(ScriptStreamingTest, EncodingChanges)
341 // It's possible that the encoding of the Resource changes after we start
343 m_resource->setEncoding("windows-1252");
345 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking);
346 TestScriptResourceClient client;
347 pendingScript().watchForLoad(&client);
349 m_resource->setEncoding("UTF-8");
350 // \xec\x92\x81 are the raw bytes for \uc481.
351 appendData("function foo() { var foob\xec\x92\x81r = 13; return foob\xec\x92\x81r; } foo();");
355 processTasksUntilStreamingComplete();
356 EXPECT_TRUE(client.finished());
357 bool errorOccurred = false;
358 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred);
359 EXPECT_FALSE(errorOccurred);
360 EXPECT_TRUE(sourceCode.streamer());
361 v8::TryCatch tryCatch;
362 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate());
363 EXPECT_FALSE(script.IsEmpty());
364 EXPECT_FALSE(tryCatch.HasCaught());
367 INSTANTIATE_TEST_CASE_P(ScriptStreamingInstantiation, ScriptStreamingTest, ::testing::Values(false, true));