Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: third_party/WebKit/Source/modules/compositorworker/CompositorWorkerThreadTest.cpp

Issue 1733353004: Introduce WorkerBackingThread (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 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 "modules/compositorworker/CompositorWorkerThread.h" 5 #include "modules/compositorworker/CompositorWorkerThread.h"
6 6
7 #include "bindings/core/v8/ScriptSourceCode.h" 7 #include "bindings/core/v8/ScriptSourceCode.h"
8 #include "bindings/core/v8/V8GCController.h" 8 #include "bindings/core/v8/V8GCController.h"
9 #include "core/inspector/ConsoleMessage.h" 9 #include "core/inspector/ConsoleMessage.h"
10 #include "core/testing/DummyPageHolder.h" 10 #include "core/testing/DummyPageHolder.h"
11 #include "core/workers/WorkerBackingThread.h"
11 #include "core/workers/WorkerLoaderProxy.h" 12 #include "core/workers/WorkerLoaderProxy.h"
12 #include "core/workers/WorkerObjectProxy.h" 13 #include "core/workers/WorkerObjectProxy.h"
13 #include "core/workers/WorkerThreadStartupData.h" 14 #include "core/workers/WorkerThreadStartupData.h"
14 #include "platform/NotImplemented.h" 15 #include "platform/NotImplemented.h"
15 #include "platform/ThreadSafeFunctional.h" 16 #include "platform/ThreadSafeFunctional.h"
16 #include "platform/WaitableEvent.h" 17 #include "platform/WaitableEvent.h"
17 #include "platform/heap/Handle.h" 18 #include "platform/heap/Handle.h"
18 #include "platform/testing/TestingPlatformSupport.h" 19 #include "platform/testing/TestingPlatformSupport.h"
19 #include "platform/testing/UnitTestHelpers.h" 20 #include "platform/testing/UnitTestHelpers.h"
20 #include "public/platform/Platform.h" 21 #include "public/platform/Platform.h"
21 #include "testing/gtest/include/gtest/gtest.h" 22 #include "testing/gtest/include/gtest/gtest.h"
22 23
23 namespace blink { 24 namespace blink {
24 namespace { 25 namespace {
25 26
26 class TestCompositorWorkerThread : public CompositorWorkerThread {
27 public:
28 TestCompositorWorkerThread(WorkerLoaderProxyProvider* loaderProxyProvider, W orkerObjectProxy& objectProxy, double timeOrigin, WaitableEvent* startEvent)
29 : CompositorWorkerThread(WorkerLoaderProxy::create(loaderProxyProvider), objectProxy, timeOrigin)
30 , m_startEvent(startEvent)
31 {
32 }
33
34 ~TestCompositorWorkerThread() override {}
35
36 void setCallbackAfterV8Termination(PassOwnPtr<Function<void()>> callback)
37 {
38 m_v8TerminationCallback = callback;
39 }
40
41 private:
42 // WorkerThread:
43 void didStartWorkerThread() override
44 {
45 m_startEvent->signal();
46 }
47 void terminateV8Execution() override
48 {
49 CompositorWorkerThread::terminateV8Execution();
50 if (m_v8TerminationCallback)
51 (*m_v8TerminationCallback)();
52 }
53
54 void willDestroyIsolate() override
55 {
56 v8::Isolate::GetCurrent()->RequestGarbageCollectionForTesting(v8::Isolat e::kFullGarbageCollection);
57 Heap::collectAllGarbage();
58 CompositorWorkerThread::willDestroyIsolate();
59 }
60
61 WaitableEvent* m_startEvent;
62 OwnPtr<Function<void()>> m_v8TerminationCallback;
63 };
64
65 // A null WorkerObjectProxy, supplied when creating CompositorWorkerThreads. 27 // A null WorkerObjectProxy, supplied when creating CompositorWorkerThreads.
66 class TestCompositorWorkerObjectProxy : public WorkerObjectProxy { 28 class TestCompositorWorkerObjectProxy : public WorkerObjectProxy {
67 public: 29 public:
68 static PassOwnPtr<TestCompositorWorkerObjectProxy> create(ExecutionContext* context) 30 static PassOwnPtr<TestCompositorWorkerObjectProxy> create(ExecutionContext* context)
69 { 31 {
70 return adoptPtr(new TestCompositorWorkerObjectProxy(context)); 32 return adoptPtr(new TestCompositorWorkerObjectProxy(context));
71 } 33 }
72 34
73 // (Empty) WorkerReportingProxy implementation: 35 // (Empty) WorkerReportingProxy implementation:
74 virtual void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, int exceptionId) {} 36 virtual void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, int exceptionId) {}
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
112 OwnPtr<WebThread> m_thread; 74 OwnPtr<WebThread> m_thread;
113 TestingCompositorSupport m_compositorSupport; 75 TestingCompositorSupport m_compositorSupport;
114 }; 76 };
115 77
116 } // namespace 78 } // namespace
117 79
118 class CompositorWorkerThreadTest : public ::testing::Test { 80 class CompositorWorkerThreadTest : public ::testing::Test {
119 public: 81 public:
120 void SetUp() override 82 void SetUp() override
121 { 83 {
84 CompositorWorkerThread::resetSharedBackingThreadForTest();
122 m_page = DummyPageHolder::create(); 85 m_page = DummyPageHolder::create();
123 m_objectProxy = TestCompositorWorkerObjectProxy::create(&m_page->documen t()); 86 m_objectProxy = TestCompositorWorkerObjectProxy::create(&m_page->documen t());
124 m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http:// fake.url/")); 87 m_securityOrigin = SecurityOrigin::create(KURL(ParsedURLString, "http:// fake.url/"));
125 } 88 }
126 89
127 void TearDown() override 90 void TearDown() override
128 { 91 {
129 ASSERT(!hasThread());
130 ASSERT(!hasIsolate());
131 m_page.clear(); 92 m_page.clear();
93 CompositorWorkerThread::resetSharedBackingThreadForTest();
132 } 94 }
133 95
134 PassRefPtr<TestCompositorWorkerThread> createCompositorWorker(WaitableEvent* startEvent) 96 PassRefPtr<CompositorWorkerThread> createCompositorWorker()
135 { 97 {
136 TestCompositorWorkerThread* workerThread = new TestCompositorWorkerThrea d(nullptr, *m_objectProxy, 0, startEvent); 98 RefPtr<CompositorWorkerThread> workerThread = CompositorWorkerThread::cr eate(nullptr, *m_objectProxy, 0);
137 OwnPtrWillBeRawPtr<WorkerClients> clients = nullptr; 99 OwnPtrWillBeRawPtr<WorkerClients> clients = nullptr;
100 workerThread->workerBackingThread().setInitializationEventForTest();
138 workerThread->start(WorkerThreadStartupData::create( 101 workerThread->start(WorkerThreadStartupData::create(
139 KURL(ParsedURLString, "http://fake.url/"), 102 KURL(ParsedURLString, "http://fake.url/"),
140 "fake user agent", 103 "fake user agent",
141 "//fake source code", 104 "//fake source code",
142 nullptr, 105 nullptr,
143 DontPauseWorkerGlobalScopeOnStart, 106 DontPauseWorkerGlobalScopeOnStart,
144 adoptPtr(new Vector<CSPHeaderAndType>()), 107 adoptPtr(new Vector<CSPHeaderAndType>()),
145 m_securityOrigin.get(), 108 m_securityOrigin.get(),
146 clients.release(), 109 clients.release(),
147 V8CacheOptionsDefault)); 110 V8CacheOptionsDefault));
148 return adoptRef(workerThread);
149 }
150 111
151 void createWorkerAdapter(RefPtr<CompositorWorkerThread>* workerThread, Waita bleEvent* creationEvent) 112 return workerThread;
152 {
153 *workerThread = createCompositorWorker(creationEvent);
154 } 113 }
155 114
156 // Attempts to run some simple script for |worker|. 115 // Attempts to run some simple script for |worker|.
157 void checkWorkerCanExecuteScript(WorkerThread* worker) 116 void checkWorkerCanExecuteScript(WorkerThread* worker)
158 { 117 {
159 OwnPtr<WaitableEvent> waitEvent = adoptPtr(new WaitableEvent()); 118 OwnPtr<WaitableEvent> waitEvent = adoptPtr(new WaitableEvent());
160 worker->backingThread().platformThread().taskRunner()->postTask(BLINK_FR OM_HERE, threadSafeBind(&CompositorWorkerThreadTest::executeScriptInWorker, Allo wCrossThreadAccess(this), 119 worker->backingThread().platformThread().taskRunner()->postTask(BLINK_FR OM_HERE, threadSafeBind(&CompositorWorkerThreadTest::executeScriptInWorker, Allo wCrossThreadAccess(this),
161 AllowCrossThreadAccess(worker), AllowCrossThreadAccess(waitEvent.get ()))); 120 AllowCrossThreadAccess(worker), AllowCrossThreadAccess(waitEvent.get ())));
162 waitEvent->wait(); 121 waitEvent->wait();
163 } 122 }
164 123
124 bool hasCurrentIsolateInWorkerThread(WorkerBackingThread* thread)
125 {
126 WaitableEvent event;
127 bool hasIsolate = false;
128 thread->backingThread().platformThread().taskRunner()->postTask(BLINK_FR OM_HERE, threadSafeBind(&hasCurrentIsolate, AllowCrossThreadAccess(&event), Allo wCrossThreadAccess(&hasIsolate)));
129 event.wait();
130 return hasIsolate;
131 }
132
165 void waitForWaitableEventAfterIteratingCurrentLoop(WaitableEvent* waitEvent) 133 void waitForWaitableEventAfterIteratingCurrentLoop(WaitableEvent* waitEvent)
166 { 134 {
167 testing::runPendingTasks(); 135 testing::runPendingTasks();
168 waitEvent->wait(); 136 waitEvent->wait();
169 } 137 }
170 138
171 bool hasThread() const
172 {
173 return CompositorWorkerThread::hasThreadForTest();
174 }
175
176 bool hasIsolate() const
177 {
178 return CompositorWorkerThread::hasIsolateForTest();
179 }
180
181 private: 139 private:
182 void executeScriptInWorker(WorkerThread* worker, WaitableEvent* waitEvent) 140 void executeScriptInWorker(WorkerThread* worker, WaitableEvent* waitEvent)
183 { 141 {
184 WorkerOrWorkletScriptController* scriptController = worker->workerGlobal Scope()->scriptController(); 142 WorkerOrWorkletScriptController* scriptController = worker->workerGlobal Scope()->scriptController();
185 bool evaluateResult = scriptController->evaluate(ScriptSourceCode("var c ounter = 0; ++counter;")); 143 bool evaluateResult = scriptController->evaluate(ScriptSourceCode("var c ounter = 0; ++counter;"));
186 ASSERT_UNUSED(evaluateResult, evaluateResult); 144 ASSERT_UNUSED(evaluateResult, evaluateResult);
187 waitEvent->signal(); 145 waitEvent->signal();
188 } 146 }
189 147
148 static void hasCurrentIsolate(WaitableEvent* event, bool* hasIsolate)
149 {
150 *hasIsolate = !!v8::Isolate::GetCurrent();
151 event->signal();
152 }
153
190 OwnPtr<DummyPageHolder> m_page; 154 OwnPtr<DummyPageHolder> m_page;
191 RefPtr<SecurityOrigin> m_securityOrigin; 155 RefPtr<SecurityOrigin> m_securityOrigin;
192 OwnPtr<WorkerObjectProxy> m_objectProxy; 156 OwnPtr<WorkerObjectProxy> m_objectProxy;
193 CompositorWorkerTestPlatform m_testPlatform; 157 CompositorWorkerTestPlatform m_testPlatform;
194 }; 158 };
195 159
196 TEST_F(CompositorWorkerThreadTest, Basic) 160 TEST_F(CompositorWorkerThreadTest, Basic)
197 { 161 {
198 OwnPtr<WaitableEvent> creationEvent = adoptPtr(new WaitableEvent()); 162 RefPtr<CompositorWorkerThread> compositorWorker = createCompositorWorker();
199 RefPtr<CompositorWorkerThread> compositorWorker = createCompositorWorker(cre ationEvent.get()); 163 waitForWaitableEventAfterIteratingCurrentLoop(compositorWorker->workerBackin gThread().initializationEventForTest());
kinuko 2016/02/29 09:32:54 Would it be possible to just replace this with pos
yhirano 2016/02/29 23:47:32 Done.
200 waitForWaitableEventAfterIteratingCurrentLoop(creationEvent.get());
201 checkWorkerCanExecuteScript(compositorWorker.get()); 164 checkWorkerCanExecuteScript(compositorWorker.get());
202 compositorWorker->terminateAndWait(); 165 compositorWorker->terminateAndWait();
203 } 166 }
204 167
205 // Tests that the same WebThread is used for new workers if the WebThread is sti ll alive. 168 // Tests that the same WebThread is used for new workers if the WebThread is sti ll alive.
206 TEST_F(CompositorWorkerThreadTest, CreateSecondAndTerminateFirst) 169 TEST_F(CompositorWorkerThreadTest, CreateSecondAndTerminateFirst)
207 { 170 {
208 // Create the first worker and wait until it is initialized. 171 // Create the first worker and wait until it is initialized.
209 OwnPtr<WaitableEvent> firstCreationEvent = adoptPtr(new WaitableEvent()); 172 RefPtr<CompositorWorkerThread> firstWorker = createCompositorWorker();
210 RefPtr<CompositorWorkerThread> firstWorker = createCompositorWorker(firstCre ationEvent.get()); 173 WebThreadSupportingGC* firstThread = &firstWorker->backingThread();
211 WebThreadSupportingGC* firstThread = CompositorWorkerThread::sharedBackingTh read();
212 ASSERT(firstThread); 174 ASSERT(firstThread);
213 waitForWaitableEventAfterIteratingCurrentLoop(firstCreationEvent.get()); 175 waitForWaitableEventAfterIteratingCurrentLoop(firstWorker->workerBackingThre ad().initializationEventForTest());
214 v8::Isolate* firstIsolate = firstWorker->isolate(); 176 v8::Isolate* firstIsolate = firstWorker->isolate();
215 ASSERT(firstIsolate); 177 ASSERT(firstIsolate);
216 178
217 // Create the second worker and immediately destroy the first worker. 179 // Create the second worker and immediately destroy the first worker.
218 OwnPtr<WaitableEvent> secondCreationEvent = adoptPtr(new WaitableEvent()); 180 RefPtr<CompositorWorkerThread> secondWorker = createCompositorWorker();
219 RefPtr<CompositorWorkerThread> secondWorker = createCompositorWorker(secondC reationEvent.get());
220 firstWorker->terminateAndWait(); 181 firstWorker->terminateAndWait();
221 182
222 // Wait until the second worker is initialized. Verify that the second worke r is using the same 183 // Wait until the second worker is initialized. Verify that the second worke r is using the same
223 // thread and Isolate as the first worker. 184 // thread and Isolate as the first worker.
224 WebThreadSupportingGC* secondThread = CompositorWorkerThread::sharedBackingT hread(); 185 WebThreadSupportingGC* secondThread = &secondWorker->backingThread();
225 ASSERT(secondThread); 186 ASSERT(secondThread);
226 waitForWaitableEventAfterIteratingCurrentLoop(secondCreationEvent.get()); 187 ASSERT_EQ(firstThread, secondThread);
227 EXPECT_EQ(firstThread, secondThread);
228 188
229 v8::Isolate* secondIsolate = secondWorker->isolate(); 189 v8::Isolate* secondIsolate = secondWorker->isolate();
230 ASSERT(secondIsolate); 190 ASSERT(secondIsolate);
231 EXPECT_EQ(firstIsolate, secondIsolate); 191 EXPECT_EQ(firstIsolate, secondIsolate);
232 192
233 // Verify that the worker can still successfully execute script. 193 // Verify that the worker can still successfully execute script.
234 checkWorkerCanExecuteScript(secondWorker.get()); 194 checkWorkerCanExecuteScript(secondWorker.get());
235 195
236 secondWorker->terminateAndWait(); 196 secondWorker->terminateAndWait();
237 } 197 }
238 198
239 static void checkCurrentIsolate(v8::Isolate* isolate, WaitableEvent* event) 199 static void checkCurrentIsolate(v8::Isolate* isolate, WaitableEvent* event)
240 { 200 {
241 EXPECT_EQ(v8::Isolate::GetCurrent(), isolate); 201 EXPECT_EQ(v8::Isolate::GetCurrent(), isolate);
242 event->signal(); 202 event->signal();
243 } 203 }
244 204
245 // Tests that a new WebThread is created if all existing workers are terminated before a new worker is created. 205 // Tests that a new WebThread is created if all existing workers are terminated before a new worker is created.
246 TEST_F(CompositorWorkerThreadTest, TerminateFirstAndCreateSecond) 206 TEST_F(CompositorWorkerThreadTest, TerminateFirstAndCreateSecond)
247 { 207 {
248 // Create the first worker, wait until it is initialized, and terminate it. 208 // Create the first worker, wait until it is initialized, and terminate it.
249 OwnPtr<WaitableEvent> creationEvent = adoptPtr(new WaitableEvent()); 209 RefPtr<CompositorWorkerThread> compositorWorker = createCompositorWorker();
250 RefPtr<CompositorWorkerThread> compositorWorker = createCompositorWorker(cre ationEvent.get()); 210 RefPtr<WorkerBackingThread> workerBackingThread = &compositorWorker->workerB ackingThread();
251 WebThreadSupportingGC* firstThread = CompositorWorkerThread::sharedBackingTh read(); 211 WebThreadSupportingGC* firstThread = &compositorWorker->backingThread();
252 waitForWaitableEventAfterIteratingCurrentLoop(creationEvent.get()); 212 waitForWaitableEventAfterIteratingCurrentLoop(compositorWorker->workerBackin gThread().initializationEventForTest());
253 ASSERT(compositorWorker->isolate()); 213 v8::Isolate* firstIsolate = compositorWorker->isolate();
214 ASSERT(firstIsolate);
215
216 EXPECT_TRUE(hasCurrentIsolateInWorkerThread(workerBackingThread.get()));
254 compositorWorker->terminateAndWait(); 217 compositorWorker->terminateAndWait();
255 218
256 // Create the second worker. Verify that the second worker lives in a differ ent WebThread since the first 219 // The backing thread is still alive but its isolate is detached.
257 // thread will have been destroyed after destroying the first worker. 220 // TODO(yhirano): Enable this check if / when returning null from
258 creationEvent = adoptPtr(new WaitableEvent()); 221 // v8::Isolate::GetCurrent() is allowed.
259 compositorWorker = createCompositorWorker(creationEvent.get()); 222 // EXPECT_FALSE(hasCurrentIsolateInWorkerThread(workerBackingThread.get()));
260 WebThreadSupportingGC* secondThread = CompositorWorkerThread::sharedBackingT hread(); 223
261 EXPECT_NE(firstThread, secondThread); 224 // Create the second worker. The backing thread is same.
262 waitForWaitableEventAfterIteratingCurrentLoop(creationEvent.get()); 225 compositorWorker = createCompositorWorker();
226 WebThreadSupportingGC* secondThread = &compositorWorker->backingThread();
227 EXPECT_EQ(firstThread, secondThread);
228 waitForWaitableEventAfterIteratingCurrentLoop(compositorWorker->workerBackin gThread().initializationEventForTest());
263 229
264 // Jump over to the worker's thread to verify that the Isolate is set up cor rectly and execute script. 230 // Jump over to the worker's thread to verify that the Isolate is set up cor rectly and execute script.
265 OwnPtr<WaitableEvent> checkEvent = adoptPtr(new WaitableEvent()); 231 OwnPtr<WaitableEvent> checkEvent = adoptPtr(new WaitableEvent());
266 secondThread->platformThread().taskRunner()->postTask(BLINK_FROM_HERE, threa dSafeBind(&checkCurrentIsolate, AllowCrossThreadAccess(compositorWorker->isolate ()), AllowCrossThreadAccess(checkEvent.get()))); 232 secondThread->platformThread().taskRunner()->postTask(BLINK_FROM_HERE, threa dSafeBind(&checkCurrentIsolate, AllowCrossThreadAccess(compositorWorker->isolate ()), AllowCrossThreadAccess(checkEvent.get())));
267 waitForWaitableEventAfterIteratingCurrentLoop(checkEvent.get()); 233 waitForWaitableEventAfterIteratingCurrentLoop(checkEvent.get());
234 EXPECT_TRUE(hasCurrentIsolateInWorkerThread(workerBackingThread.get()));
268 checkWorkerCanExecuteScript(compositorWorker.get()); 235 checkWorkerCanExecuteScript(compositorWorker.get());
269 236
270 compositorWorker->terminateAndWait(); 237 compositorWorker->terminateAndWait();
271 } 238 }
272 239
273 // Tests that v8::Isolate and WebThread are correctly set-up if a worker is crea ted while another is terminating. 240 // Tests that v8::Isolate and WebThread are correctly set-up if a worker is crea ted while another is terminating.
274 TEST_F(CompositorWorkerThreadTest, CreatingSecondDuringTerminationOfFirst) 241 TEST_F(CompositorWorkerThreadTest, CreatingSecondDuringTerminationOfFirst)
275 { 242 {
276 OwnPtr<WaitableEvent> firstCreationEvent = adoptPtr(new WaitableEvent()); 243 RefPtr<CompositorWorkerThread> firstWorker = createCompositorWorker();
277 RefPtr<TestCompositorWorkerThread> firstWorker = createCompositorWorker(firs tCreationEvent.get()); 244 waitForWaitableEventAfterIteratingCurrentLoop(firstWorker->workerBackingThre ad().initializationEventForTest());
278 waitForWaitableEventAfterIteratingCurrentLoop(firstCreationEvent.get());
279 v8::Isolate* firstIsolate = firstWorker->isolate(); 245 v8::Isolate* firstIsolate = firstWorker->isolate();
280 ASSERT(firstIsolate); 246 ASSERT(firstIsolate);
281 247
282 // Request termination of the first worker, and set-up to make sure the seco nd worker is created right as 248 // Request termination of the first worker and create the second worker
283 // the first worker terminates its isolate. 249 // as soon as possible.
284 OwnPtr<WaitableEvent> secondCreationEvent = adoptPtr(new WaitableEvent()); 250 EXPECT_EQ(1u, firstWorker->workerBackingThread().workerScriptCount());
285 RefPtr<CompositorWorkerThread> secondWorker; 251 firstWorker->terminate();
286 firstWorker->setCallbackAfterV8Termination(bind(&CompositorWorkerThreadTest: :createWorkerAdapter, this, &secondWorker, secondCreationEvent.get())); 252 // We don't wait for its termination.
287 firstWorker->terminateAndWait(); 253 // Note: We rely on the assumption that the termination steps don't run
254 // on the worker thread so quickly. This could be a source of flakiness.
255
256 RefPtr<CompositorWorkerThread> secondWorker = createCompositorWorker();
288 ASSERT(secondWorker); 257 ASSERT(secondWorker);
289 258
290 waitForWaitableEventAfterIteratingCurrentLoop(secondCreationEvent.get());
291 v8::Isolate* secondIsolate = secondWorker->isolate(); 259 v8::Isolate* secondIsolate = secondWorker->isolate();
292 ASSERT(secondIsolate); 260 ASSERT(secondIsolate);
293 EXPECT_EQ(firstIsolate, secondIsolate); 261 EXPECT_EQ(firstIsolate, secondIsolate);
294 262
295 // Verify that the isolate can run some scripts correctly in the second work er. 263 // Verify that the isolate can run some scripts correctly in the second work er.
296 checkWorkerCanExecuteScript(secondWorker.get()); 264 checkWorkerCanExecuteScript(secondWorker.get());
297 secondWorker->terminateAndWait(); 265 secondWorker->terminateAndWait();
298 } 266 }
299 267
300 } // namespace blink 268 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698