Home | History | Annotate | Download | only in v8
      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