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 6 #include "config.h" 7 #include "bindings/core/v8/ScriptStreamer.h" 8 9 #include "bindings/core/v8/ScriptSourceCode.h" 10 #include "bindings/core/v8/ScriptStreamerThread.h" 11 #include "bindings/core/v8/V8Binding.h" 12 #include "bindings/core/v8/V8ScriptRunner.h" 13 #include "core/dom/PendingScript.h" 14 #include "core/frame/Settings.h" 15 #include "platform/Task.h" 16 #include "platform/heap/Handle.h" 17 #include "public/platform/Platform.h" 18 19 #include <gtest/gtest.h> 20 #include <v8.h> 21 22 namespace blink { 23 24 namespace { 25 26 // For the benefit of Oilpan, put the part object PendingScript inside 27 // a wrapper that's on the Oilpan heap and hold a reference to that wrapper 28 // from ScriptStreamingTest. 29 class PendingScriptWrapper : public NoBaseWillBeGarbageCollectedFinalized<PendingScriptWrapper> { 30 public: 31 static PassOwnPtrWillBeRawPtr<PendingScriptWrapper> create() 32 { 33 return adoptPtrWillBeNoop(new PendingScriptWrapper()); 34 } 35 36 static PassOwnPtrWillBeRawPtr<PendingScriptWrapper> create(Element* element, ScriptResource* resource) 37 { 38 return adoptPtrWillBeNoop(new PendingScriptWrapper(element, resource)); 39 } 40 41 PendingScript& get() { return m_pendingScript; } 42 43 void trace(Visitor* visitor) 44 { 45 visitor->trace(m_pendingScript); 46 } 47 48 private: 49 PendingScriptWrapper() 50 { 51 } 52 53 PendingScriptWrapper(Element* element, ScriptResource* resource) 54 : m_pendingScript(PendingScript(element, resource)) 55 { 56 } 57 58 PendingScript m_pendingScript; 59 }; 60 61 class ScriptStreamingTest : public testing::Test { 62 public: 63 ScriptStreamingTest() 64 : m_scope(v8::Isolate::GetCurrent()) 65 , m_settings(Settings::create()) 66 , m_resourceRequest("http://www.streaming-test.com/") 67 , m_resource(new ScriptResource(m_resourceRequest, "text/utf-8")) 68 , m_pendingScript(PendingScriptWrapper::create(0, m_resource)) // Takes ownership of m_resource. 69 { 70 m_settings->setV8ScriptStreamingEnabled(true); 71 m_resource->setLoading(true); 72 ScriptStreamer::removeSmallScriptThresholdForTesting(); 73 } 74 75 ScriptState* scriptState() const { return m_scope.scriptState(); } 76 v8::Isolate* isolate() const { return m_scope.isolate(); } 77 78 PendingScript& pendingScript() const { return m_pendingScript->get(); } 79 80 protected: 81 void appendData(const char* data) 82 { 83 m_resource->appendData(data, strlen(data)); 84 // Yield control to the background thread, so that V8 gets a change to 85 // process the data before the main thread adds more. Note that we 86 // cannot fully control in what kind of chunks the data is passed to V8 87 // (if the V8 is not requesting more data between two appendData calls, 88 // V8 will get both chunks together). 89 Platform::current()->yieldCurrentThread(); 90 } 91 92 void appendPadding() 93 { 94 for (int i = 0; i < 10; ++i) { 95 appendData(" /* this is padding to make the script long enough, so " 96 "that V8's buffer gets filled and it starts processing " 97 "the data */ "); 98 } 99 } 100 101 void finish() 102 { 103 m_resource->finish(); 104 m_resource->setLoading(false); 105 } 106 107 void processTasksUntilStreamingComplete() 108 { 109 WebThread* currentThread = blink::Platform::current()->currentThread(); 110 while (ScriptStreamerThread::shared()->isRunningTask()) { 111 currentThread->postTask(new Task(WTF::bind(&WebThread::exitRunLoop, currentThread))); 112 currentThread->enterRunLoop(); 113 } 114 // Once more, because the "streaming complete" notification might only 115 // now be in the task queue. 116 currentThread->postTask(new Task(WTF::bind(&WebThread::exitRunLoop, currentThread))); 117 currentThread->enterRunLoop(); 118 } 119 120 V8TestingScope m_scope; 121 OwnPtr<Settings> m_settings; 122 // The Resource and PendingScript where we stream from. These don't really 123 // fetch any data outside the test; the test controls the data by calling 124 // ScriptResource::appendData. 125 ResourceRequest m_resourceRequest; 126 ScriptResource* m_resource; 127 OwnPtrWillBePersistent<PendingScriptWrapper> m_pendingScript; 128 }; 129 130 class TestScriptResourceClient : public ScriptResourceClient { 131 public: 132 TestScriptResourceClient() 133 : m_finished(false) { } 134 135 virtual void notifyFinished(Resource*) OVERRIDE { m_finished = true; } 136 137 bool finished() const { return m_finished; } 138 139 private: 140 bool m_finished; 141 }; 142 143 TEST_F(ScriptStreamingTest, CompilingStreamedScript) 144 { 145 // Test that we can successfully compile a streamed script. 146 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking); 147 TestScriptResourceClient client; 148 pendingScript().watchForLoad(&client); 149 150 appendData("function foo() {"); 151 appendPadding(); 152 appendData("return 5; }"); 153 appendPadding(); 154 appendData("foo();"); 155 EXPECT_FALSE(client.finished()); 156 finish(); 157 158 // Process tasks on the main thread until the streaming background thread 159 // has completed its tasks. 160 processTasksUntilStreamingComplete(); 161 EXPECT_TRUE(client.finished()); 162 bool errorOccurred = false; 163 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred); 164 EXPECT_FALSE(errorOccurred); 165 EXPECT_TRUE(sourceCode.streamer()); 166 v8::TryCatch tryCatch; 167 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate()); 168 EXPECT_FALSE(script.IsEmpty()); 169 EXPECT_FALSE(tryCatch.HasCaught()); 170 } 171 172 TEST_F(ScriptStreamingTest, CompilingStreamedScriptWithParseError) 173 { 174 // Test that scripts with parse errors are handled properly. In those cases, 175 // the V8 side typically finished before loading finishes: make sure we 176 // handle it gracefully. 177 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking); 178 TestScriptResourceClient client; 179 pendingScript().watchForLoad(&client); 180 appendData("function foo() {"); 181 appendData("this is the part which will be a parse error"); 182 // V8 won't realize the parse error until it actually starts parsing the 183 // script, and this happens only when its buffer is filled. 184 appendPadding(); 185 186 EXPECT_FALSE(client.finished()); 187 188 // Force the V8 side to finish before the loading. 189 processTasksUntilStreamingComplete(); 190 EXPECT_FALSE(client.finished()); 191 192 finish(); 193 EXPECT_TRUE(client.finished()); 194 195 bool errorOccurred = false; 196 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred); 197 EXPECT_FALSE(errorOccurred); 198 EXPECT_TRUE(sourceCode.streamer()); 199 v8::TryCatch tryCatch; 200 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(sourceCode, isolate()); 201 EXPECT_TRUE(script.IsEmpty()); 202 EXPECT_TRUE(tryCatch.HasCaught()); 203 } 204 205 TEST_F(ScriptStreamingTest, CancellingStreaming) 206 { 207 // Test that the upper layers (PendingScript and up) can be ramped down 208 // while streaming is ongoing, and ScriptStreamer handles it gracefully. 209 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking); 210 TestScriptResourceClient client; 211 pendingScript().watchForLoad(&client); 212 appendData("function foo() {"); 213 214 // In general, we cannot control what the background thread is doing 215 // (whether it's parsing or waiting for more data). In this test, we have 216 // given it so little data that it's surely waiting for more. 217 218 // Simulate cancelling the network load (e.g., because the user navigated 219 // away). 220 EXPECT_FALSE(client.finished()); 221 pendingScript().stopWatchingForLoad(&client); 222 m_pendingScript = PendingScriptWrapper::create(); // This will destroy m_resource. 223 m_resource = 0; 224 225 // The V8 side will complete too. This should not crash. We don't receive 226 // any results from the streaming and the client doesn't get notified. 227 processTasksUntilStreamingComplete(); 228 EXPECT_FALSE(client.finished()); 229 } 230 231 TEST_F(ScriptStreamingTest, SuppressingStreaming) 232 { 233 // If we notice during streaming that there is a code cache, streaming 234 // is suppressed (V8 doesn't parse while the script is loading), and the 235 // upper layer (ScriptResourceClient) should get a notification when the 236 // script is loaded. 237 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking); 238 TestScriptResourceClient client; 239 pendingScript().watchForLoad(&client); 240 appendData("function foo() {"); 241 appendPadding(); 242 243 m_resource->setCachedMetadata(V8ScriptRunner::tagForCodeCache(), "X", 1, Resource::CacheLocally); 244 245 appendPadding(); 246 finish(); 247 processTasksUntilStreamingComplete(); 248 EXPECT_TRUE(client.finished()); 249 250 bool errorOccurred = false; 251 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred); 252 EXPECT_FALSE(errorOccurred); 253 // ScriptSourceCode doesn't refer to the streamer, since we have suppressed 254 // the streaming and resumed the non-streaming code path for script 255 // compilation. 256 EXPECT_FALSE(sourceCode.streamer()); 257 } 258 259 TEST_F(ScriptStreamingTest, EmptyScripts) 260 { 261 // Empty scripts should also be streamed properly, that is, the upper layer 262 // (ScriptResourceClient) should be notified when an empty script has been 263 // loaded. 264 ScriptStreamer::startStreaming(pendingScript(), m_settings.get(), m_scope.scriptState(), PendingScript::ParsingBlocking); 265 TestScriptResourceClient client; 266 pendingScript().watchForLoad(&client); 267 268 // Finish the script without sending any data. 269 finish(); 270 // The finished notification should arrive immediately and not be cycled 271 // through a background thread. 272 EXPECT_TRUE(client.finished()); 273 274 bool errorOccurred = false; 275 ScriptSourceCode sourceCode = pendingScript().getSource(KURL(), errorOccurred); 276 EXPECT_FALSE(errorOccurred); 277 EXPECT_FALSE(sourceCode.streamer()); 278 } 279 280 } // namespace 281 282 } // namespace blink 283