OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 "core/loader/modulescript/ModuleTreeLinker.h" | |
6 | |
7 #include "bindings/core/v8/ScriptModule.h" | |
8 #include "bindings/core/v8/ScriptState.h" | |
9 #include "bindings/core/v8/V8Binding.h" | |
10 #include "bindings/core/v8/V8BindingForTesting.h" | |
11 #include "bindings/core/v8/V8ThrowException.h" | |
12 #include "core/dom/Modulator.h" | |
13 #include "core/dom/ModuleScript.h" | |
14 #include "core/loader/modulescript/ModuleScriptFetchRequest.h" | |
15 #include "core/loader/modulescript/ModuleTreeLinkerRegistry.h" | |
16 #include "core/testing/DummyModulator.h" | |
17 #include "core/testing/DummyPageHolder.h" | |
18 #include "platform/heap/Handle.h" | |
19 #include "platform/weborigin/KURL.h" | |
20 #include "platform/wtf/text/StringBuilder.h" | |
21 #include "public/platform/Platform.h" | |
22 #include "public/platform/scheduler/renderer/renderer_scheduler.h" | |
23 #include "testing/gtest/include/gtest/gtest.h" | |
24 | |
25 namespace blink { | |
26 | |
27 namespace { | |
28 | |
29 class TestModuleTreeClient final | |
30 : public GarbageCollectedFinalized<TestModuleTreeClient>, | |
31 public ModuleTreeClient { | |
32 USING_GARBAGE_COLLECTED_MIXIN(TestModuleTreeClient); | |
33 | |
34 public: | |
35 TestModuleTreeClient() = default; | |
36 | |
37 DEFINE_INLINE_TRACE() { visitor->Trace(module_script_); } | |
38 | |
39 void NotifyModuleTreeLoadFinished(ModuleScript* module_script) override { | |
40 was_notify_finished_ = true; | |
41 module_script_ = module_script; | |
42 } | |
43 | |
44 bool WasNotifyFinished() const { return was_notify_finished_; } | |
45 ModuleScript* GetModuleScript() { return module_script_; } | |
46 | |
47 private: | |
48 bool was_notify_finished_ = false; | |
49 Member<ModuleScript> module_script_; | |
50 }; | |
51 | |
52 class ModuleTreeLinkerTestModulator final : public DummyModulator { | |
53 public: | |
54 ModuleTreeLinkerTestModulator(RefPtr<ScriptState> script_state) | |
55 : script_state_(std::move(script_state)) {} | |
56 ~ModuleTreeLinkerTestModulator() override {} | |
57 | |
58 DECLARE_TRACE(); | |
59 | |
60 enum class ResolveResult { kFailure, kSuccess }; | |
61 | |
62 // Resolve last |Modulator::FetchSingle()| call. | |
63 ModuleScript* ResolveSingleModuleScriptFetch( | |
64 const KURL& url, | |
65 const Vector<String>& dependency_module_requests) { | |
66 ScriptState::Scope scope(script_state_.Get()); | |
67 | |
68 StringBuilder source_text; | |
69 for (const auto& request : dependency_module_requests) { | |
70 source_text.Append("import '"); | |
71 source_text.Append(request); | |
72 source_text.Append("';\n"); | |
73 } | |
74 source_text.Append("export default 'grapes';"); | |
75 | |
76 ScriptModule script_module = ScriptModule::Compile( | |
77 script_state_->GetIsolate(), source_text.ToString(), url.GetString(), | |
78 kSharableCrossOrigin); | |
79 ModuleScript* module_script = | |
80 ModuleScript::Create(this, script_module, url, "", kParserInserted, | |
81 WebURLRequest::kFetchCredentialsModeOmit); | |
82 auto result_request = dependency_module_requests_map_.insert( | |
83 script_module, dependency_module_requests); | |
84 EXPECT_TRUE(result_request.is_new_entry); | |
85 auto result_map = module_map_.insert(url, module_script); | |
86 EXPECT_TRUE(result_map.is_new_entry); | |
87 | |
88 EXPECT_EQ(url, pending_request_url_); | |
89 EXPECT_TRUE(pending_client_); | |
90 pending_client_->NotifyModuleLoadFinished(module_script); | |
91 pending_client_.Clear(); | |
92 | |
93 return module_script; | |
94 } | |
95 | |
96 // Get AncestorList specified in |Modulator::FetchTreeInternal()| call for | |
97 // request matching |url|. | |
98 AncestorList GetAncestorListForTreeFetch(const KURL& url) const { | |
99 const auto& it = pending_tree_ancestor_list_.Find(url); | |
100 if (it == pending_tree_ancestor_list_.end()) | |
101 return AncestorList(); | |
102 return it->value; | |
103 } | |
104 | |
105 // Resolve |Modulator::FetchTreeInternal()| for given url. | |
106 void ResolveDependentTreeFetch(const KURL& url, ResolveResult result) { | |
107 const auto& it = pending_tree_client_map_.Find(url); | |
108 EXPECT_NE(pending_tree_client_map_.end(), it); | |
109 auto pending_client = it->value; | |
110 EXPECT_TRUE(pending_client); | |
111 pending_tree_client_map_.erase(it); | |
112 | |
113 if (result == ResolveResult::kFailure) { | |
114 pending_client->NotifyModuleTreeLoadFinished(nullptr); | |
115 return; | |
116 } | |
117 EXPECT_EQ(ResolveResult::kSuccess, result); | |
118 | |
119 ScriptState::Scope scope(script_state_.Get()); | |
120 | |
121 ScriptModule script_module = ScriptModule::Compile( | |
122 script_state_->GetIsolate(), "export default 'pineapples';", | |
123 url.GetString(), kSharableCrossOrigin); | |
124 ModuleScript* module_script = | |
125 ModuleScript::Create(this, script_module, url, "", kParserInserted, | |
126 WebURLRequest::kFetchCredentialsModeOmit); | |
127 auto result_map = module_map_.insert(url, module_script); | |
128 EXPECT_TRUE(result_map.is_new_entry); | |
129 | |
130 pending_client->NotifyModuleTreeLoadFinished(module_script); | |
131 } | |
132 | |
133 void SetInstantiateShouldFail(bool b) { instantiate_should_fail_ = b; } | |
134 | |
135 private: | |
136 // Implements Modulator: | |
137 | |
138 void FetchSingle(const ModuleScriptFetchRequest& request, | |
139 ModuleGraphLevel, | |
140 SingleModuleClient* client) override { | |
141 pending_request_url_ = request.Url(); | |
142 EXPECT_FALSE(pending_client_); | |
143 pending_client_ = client; | |
144 } | |
145 | |
146 void FetchTreeInternal(const ModuleScriptFetchRequest& request, | |
147 const AncestorList& list, | |
148 ModuleGraphLevel level, | |
149 ModuleTreeClient* client) override { | |
150 const auto& url = request.Url(); | |
151 | |
152 auto ancestor_result = pending_tree_ancestor_list_.insert(url, list); | |
153 EXPECT_TRUE(ancestor_result.is_new_entry); | |
154 | |
155 EXPECT_EQ(ModuleGraphLevel::kDependentModuleFetch, level); | |
156 | |
157 auto result_map = pending_tree_client_map_.insert(url, client); | |
158 EXPECT_TRUE(result_map.is_new_entry); | |
159 } | |
160 | |
161 ModuleScript* GetFetchedModuleScript(const KURL& url) override { | |
162 const auto& it = module_map_.Find(url); | |
163 if (it == module_map_.end()) | |
164 return nullptr; | |
165 | |
166 return it->value; | |
167 } | |
168 | |
169 ScriptValue InstantiateModule(ScriptModule) override { | |
170 if (instantiate_should_fail_) { | |
171 ScriptState::Scope scope(script_state_.Get()); | |
172 v8::Local<v8::Value> error = V8ThrowException::CreateError( | |
173 script_state_->GetIsolate(), "Instantiation failure."); | |
174 return ScriptValue(script_state_.Get(), error); | |
175 } | |
176 return ScriptValue(); | |
177 } | |
178 | |
179 Vector<String> ModuleRequestsFromScriptModule( | |
180 ScriptModule script_module) override { | |
181 const auto& it = dependency_module_requests_map_.Find(script_module); | |
182 if (it == dependency_module_requests_map_.end()) | |
183 return Vector<String>(); | |
184 | |
185 return it->value; | |
186 } | |
187 | |
188 RefPtr<ScriptState> script_state_; | |
189 KURL pending_request_url_; | |
190 Member<SingleModuleClient> pending_client_; | |
191 HashMap<ScriptModule, Vector<String>> dependency_module_requests_map_; | |
192 HeapHashMap<KURL, Member<ModuleScript>> module_map_; | |
193 HeapHashMap<KURL, Member<ModuleTreeClient>> pending_tree_client_map_; | |
194 HashMap<KURL, AncestorList> pending_tree_ancestor_list_; | |
195 bool instantiate_should_fail_ = false; | |
196 }; | |
197 | |
198 DEFINE_TRACE(ModuleTreeLinkerTestModulator) { | |
199 visitor->Trace(pending_client_); | |
200 visitor->Trace(module_map_); | |
201 visitor->Trace(pending_tree_client_map_); | |
202 DummyModulator::Trace(visitor); | |
203 } | |
204 | |
205 } // namespace | |
206 | |
207 class ModuleTreeLinkerTest : public ::testing::Test { | |
208 DISALLOW_COPY_AND_ASSIGN(ModuleTreeLinkerTest); | |
209 | |
210 public: | |
211 ModuleTreeLinkerTest() = default; | |
212 void SetUp() override; | |
213 | |
214 ModuleTreeLinkerTestModulator* GetModulator() { return modulator_.Get(); } | |
215 | |
216 protected: | |
217 std::unique_ptr<DummyPageHolder> dummy_page_holder_; | |
218 Persistent<ModuleTreeLinkerTestModulator> modulator_; | |
219 }; | |
220 | |
221 void ModuleTreeLinkerTest::SetUp() { | |
222 dummy_page_holder_ = DummyPageHolder::Create(IntSize(500, 500)); | |
223 RefPtr<ScriptState> script_state = | |
224 ToScriptStateForMainWorld(&dummy_page_holder_->GetFrame()); | |
225 modulator_ = new ModuleTreeLinkerTestModulator(script_state); | |
226 } | |
227 | |
228 TEST_F(ModuleTreeLinkerTest, fetchTreeNoDeps) { | |
229 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
230 | |
231 KURL url(kParsedURLString, "http://example.com/root.js"); | |
232 ModuleScriptFetchRequest module_request( | |
233 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
234 TestModuleTreeClient* client = new TestModuleTreeClient; | |
235 registry->Fetch(module_request, AncestorList(), | |
236 ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), | |
237 client); | |
238 | |
239 EXPECT_FALSE(client->WasNotifyFinished()) | |
240 << "ModuleTreeLinker should always finish asynchronously."; | |
241 EXPECT_FALSE(client->GetModuleScript()); | |
242 | |
243 GetModulator()->ResolveSingleModuleScriptFetch(url, {}); | |
244 EXPECT_TRUE(client->WasNotifyFinished()); | |
245 ASSERT_TRUE(client->GetModuleScript()); | |
246 EXPECT_EQ(client->GetModuleScript()->InstantiationState(), | |
247 ModuleInstantiationState::kInstantiated); | |
248 } | |
249 | |
250 TEST_F(ModuleTreeLinkerTest, fetchTreeInstantiationFailure) { | |
251 GetModulator()->SetInstantiateShouldFail(true); | |
252 | |
253 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
254 | |
255 KURL url(kParsedURLString, "http://example.com/root.js"); | |
256 ModuleScriptFetchRequest module_request( | |
257 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
258 TestModuleTreeClient* client = new TestModuleTreeClient; | |
259 registry->Fetch(module_request, AncestorList(), | |
260 ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), | |
261 client); | |
262 | |
263 EXPECT_FALSE(client->WasNotifyFinished()) | |
264 << "ModuleTreeLinker should always finish asynchronously."; | |
265 EXPECT_FALSE(client->GetModuleScript()); | |
266 | |
267 GetModulator()->ResolveSingleModuleScriptFetch(url, {}); | |
268 EXPECT_TRUE(client->WasNotifyFinished()); | |
269 ASSERT_TRUE(client->GetModuleScript()); | |
270 EXPECT_EQ(client->GetModuleScript()->InstantiationState(), | |
271 ModuleInstantiationState::kErrored); | |
272 } | |
273 | |
274 TEST_F(ModuleTreeLinkerTest, fetchTreeWithSingleDependency) { | |
275 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
276 | |
277 KURL url(kParsedURLString, "http://example.com/root.js"); | |
278 ModuleScriptFetchRequest module_request( | |
279 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
280 TestModuleTreeClient* client = new TestModuleTreeClient; | |
281 registry->Fetch(module_request, AncestorList(), | |
282 ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), | |
283 client); | |
284 | |
285 EXPECT_FALSE(client->WasNotifyFinished()) | |
286 << "ModuleTreeLinker should always finish asynchronously."; | |
287 EXPECT_FALSE(client->GetModuleScript()); | |
288 | |
289 GetModulator()->ResolveSingleModuleScriptFetch(url, {"./dep1.js"}); | |
290 EXPECT_FALSE(client->WasNotifyFinished()); | |
291 | |
292 KURL url_dep1(kParsedURLString, "http://example.com/dep1.js"); | |
293 auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep1); | |
294 EXPECT_EQ(1u, ancestor_list.size()); | |
295 EXPECT_TRUE(ancestor_list.Contains( | |
296 KURL(kParsedURLString, "http://example.com/root.js"))); | |
297 | |
298 GetModulator()->ResolveDependentTreeFetch( | |
299 url_dep1, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); | |
300 EXPECT_TRUE(client->WasNotifyFinished()); | |
301 | |
302 ASSERT_TRUE(client->GetModuleScript()); | |
303 EXPECT_EQ(client->GetModuleScript()->InstantiationState(), | |
304 ModuleInstantiationState::kInstantiated); | |
305 } | |
306 | |
307 TEST_F(ModuleTreeLinkerTest, fetchTreeWith3Deps) { | |
308 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
309 | |
310 KURL url(kParsedURLString, "http://example.com/root.js"); | |
311 ModuleScriptFetchRequest module_request( | |
312 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
313 TestModuleTreeClient* client = new TestModuleTreeClient; | |
314 registry->Fetch(module_request, AncestorList(), | |
315 ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), | |
316 client); | |
317 | |
318 EXPECT_FALSE(client->WasNotifyFinished()) | |
319 << "ModuleTreeLinker should always finish asynchronously."; | |
320 EXPECT_FALSE(client->GetModuleScript()); | |
321 | |
322 GetModulator()->ResolveSingleModuleScriptFetch( | |
323 url, {"./dep1.js", "./dep2.js", "./dep3.js"}); | |
324 EXPECT_FALSE(client->WasNotifyFinished()); | |
325 | |
326 Vector<KURL> url_deps; | |
327 for (int i = 1; i <= 3; ++i) { | |
328 StringBuilder url_dep_str; | |
329 url_dep_str.Append("http://example.com/dep"); | |
330 url_dep_str.AppendNumber(i); | |
331 url_dep_str.Append(".js"); | |
332 | |
333 KURL url_dep(kParsedURLString, url_dep_str.ToString()); | |
334 url_deps.push_back(url_dep); | |
335 } | |
336 | |
337 for (const auto& url_dep : url_deps) { | |
338 SCOPED_TRACE(url_dep.GetString()); | |
339 auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep); | |
340 EXPECT_EQ(1u, ancestor_list.size()); | |
341 EXPECT_TRUE(ancestor_list.Contains( | |
342 KURL(kParsedURLString, "http://example.com/root.js"))); | |
343 } | |
344 | |
345 for (const auto& url_dep : url_deps) { | |
yhirano
2017/04/19 05:25:08
EXPECT_FALSE(client->WasNotifyFinished());
kouhei (in TOK)
2017/04/19 05:50:35
Done.
| |
346 GetModulator()->ResolveDependentTreeFetch( | |
347 url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); | |
348 } | |
349 | |
350 EXPECT_TRUE(client->WasNotifyFinished()); | |
351 ASSERT_TRUE(client->GetModuleScript()); | |
352 EXPECT_EQ(client->GetModuleScript()->InstantiationState(), | |
353 ModuleInstantiationState::kInstantiated); | |
354 } | |
355 | |
356 TEST_F(ModuleTreeLinkerTest, fetchTreeWith3Deps1Fail) { | |
357 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
358 | |
359 KURL url(kParsedURLString, "http://example.com/root.js"); | |
360 ModuleScriptFetchRequest module_request( | |
361 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
362 TestModuleTreeClient* client = new TestModuleTreeClient; | |
363 registry->Fetch(module_request, AncestorList(), | |
364 ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), | |
365 client); | |
366 | |
367 EXPECT_FALSE(client->WasNotifyFinished()) | |
368 << "ModuleTreeLinker should always finish asynchronously."; | |
369 EXPECT_FALSE(client->GetModuleScript()); | |
370 | |
371 GetModulator()->ResolveSingleModuleScriptFetch( | |
372 url, {"./dep1.js", "./dep2.js", "./dep3.js"}); | |
373 EXPECT_FALSE(client->WasNotifyFinished()); | |
374 | |
375 Vector<KURL> url_deps; | |
376 for (int i = 1; i <= 3; ++i) { | |
377 StringBuilder url_dep_str; | |
378 url_dep_str.Append("http://example.com/dep"); | |
379 url_dep_str.AppendNumber(i); | |
380 url_dep_str.Append(".js"); | |
381 | |
382 KURL url_dep(kParsedURLString, url_dep_str.ToString()); | |
383 url_deps.push_back(url_dep); | |
384 } | |
385 | |
386 for (const auto& url_dep : url_deps) { | |
387 SCOPED_TRACE(url_dep.GetString()); | |
388 auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep); | |
389 EXPECT_EQ(1u, ancestor_list.size()); | |
390 EXPECT_TRUE(ancestor_list.Contains( | |
391 KURL(kParsedURLString, "http://example.com/root.js"))); | |
392 } | |
393 | |
394 auto url_dep = url_deps.back(); | |
395 url_deps.pop_back(); | |
396 GetModulator()->ResolveDependentTreeFetch( | |
397 url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); | |
398 url_dep = url_deps.back(); | |
399 url_deps.pop_back(); | |
yhirano
2017/04/19 05:25:08
EXPECT_FALSE(client->WasNotifyFinished());
kouhei (in TOK)
2017/04/19 05:50:35
Done.
| |
400 GetModulator()->ResolveDependentTreeFetch( | |
401 url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kFailure); | |
402 | |
403 EXPECT_TRUE(client->WasNotifyFinished()); | |
404 EXPECT_FALSE(client->GetModuleScript()); | |
405 | |
406 // Check below doesn't crash. | |
407 url_dep = url_deps.back(); | |
408 url_deps.pop_back(); | |
409 GetModulator()->ResolveDependentTreeFetch( | |
410 url_dep, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); | |
411 EXPECT_TRUE(url_deps.IsEmpty()); | |
412 } | |
413 | |
414 TEST_F(ModuleTreeLinkerTest, fetchDependencyTree) { | |
415 ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); | |
416 | |
417 KURL url(kParsedURLString, "http://example.com/depth1.js"); | |
418 ModuleScriptFetchRequest module_request( | |
419 url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); | |
420 TestModuleTreeClient* client = new TestModuleTreeClient; | |
421 registry->Fetch( | |
422 module_request, | |
423 AncestorList{KURL(kParsedURLString, "http://example.com/root.js")}, | |
424 ModuleGraphLevel::kDependentModuleFetch, GetModulator(), client); | |
425 | |
426 EXPECT_FALSE(client->WasNotifyFinished()) | |
427 << "ModuleTreeLinker should always finish asynchronously."; | |
428 EXPECT_FALSE(client->GetModuleScript()); | |
429 | |
430 GetModulator()->ResolveSingleModuleScriptFetch(url, {"./depth2.js"}); | |
431 | |
432 KURL url_dep2(kParsedURLString, "http://example.com/depth2.js"); | |
433 auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep2); | |
434 EXPECT_EQ(2u, ancestor_list.size()); | |
435 EXPECT_TRUE(ancestor_list.Contains( | |
436 KURL(kParsedURLString, "http://example.com/root.js"))); | |
437 EXPECT_TRUE(ancestor_list.Contains( | |
438 KURL(kParsedURLString, "http://example.com/depth1.js"))); | |
439 | |
440 GetModulator()->ResolveDependentTreeFetch( | |
441 url_dep2, ModuleTreeLinkerTestModulator::ResolveResult::kSuccess); | |
442 | |
443 EXPECT_TRUE(client->WasNotifyFinished()); | |
444 ASSERT_TRUE(client->GetModuleScript()); | |
445 EXPECT_EQ(client->GetModuleScript()->InstantiationState(), | |
446 ModuleInstantiationState::kInstantiated); | |
447 } | |
448 | |
449 } // namespace blink | |
OLD | NEW |