Index: third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp |
diff --git a/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7b0672214faab663eebe48f95c777796ab8d369f |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/loader/modulescript/ModuleTreeLinkerTest.cpp |
@@ -0,0 +1,379 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "core/loader/modulescript/ModuleTreeLinker.h" |
+ |
+#include "bindings/core/v8/ScriptModule.h" |
+#include "bindings/core/v8/ScriptState.h" |
+#include "bindings/core/v8/V8Binding.h" |
+#include "bindings/core/v8/V8BindingForTesting.h" |
+#include "bindings/core/v8/V8ThrowException.h" |
+#include "core/dom/Modulator.h" |
+#include "core/dom/ModuleScript.h" |
+#include "core/loader/modulescript/ModuleScriptFetchRequest.h" |
+#include "core/loader/modulescript/ModuleTreeLinkerRegistry.h" |
+#include "core/testing/DummyModulator.h" |
+#include "core/testing/DummyPageHolder.h" |
+#include "platform/heap/Handle.h" |
+#include "platform/weborigin/KURL.h" |
+#include "platform/wtf/text/StringBuilder.h" |
+#include "public/platform/Platform.h" |
+#include "public/platform/scheduler/renderer/renderer_scheduler.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace blink { |
+ |
+namespace { |
+ |
+class TestModuleTreeClient final |
+ : public GarbageCollectedFinalized<TestModuleTreeClient>, |
+ public ModuleTreeClient { |
+ USING_GARBAGE_COLLECTED_MIXIN(TestModuleTreeClient); |
+ |
+ public: |
+ TestModuleTreeClient() = default; |
+ |
+ DEFINE_INLINE_TRACE() { visitor->Trace(module_script_); } |
+ |
+ void NotifyModuleTreeLoadFinished(ModuleScript* module_script) override { |
+ was_notify_finished_ = true; |
+ module_script_ = module_script; |
+ } |
+ |
+ bool WasNotifyFinished() const { return was_notify_finished_; } |
+ ModuleScript* GetModuleScript() { return module_script_; } |
+ |
+ private: |
+ bool was_notify_finished_ = false; |
+ Member<ModuleScript> module_script_; |
+}; |
+ |
+class ModuleTreeLinkerTestModulator final : public DummyModulator { |
+ public: |
+ ModuleTreeLinkerTestModulator(RefPtr<ScriptState> script_state) |
+ : script_state_(std::move(script_state)) {} |
+ ~ModuleTreeLinkerTestModulator() override {} |
+ |
+ DECLARE_TRACE(); |
+ |
+ // Resolve last |Modulator::FetchSingle()| call. |
+ ModuleScript* ResolveSingleModuleScriptFetch( |
+ const KURL& url, |
+ const Vector<String>& dependency_module_requests) { |
+ ScriptState::Scope scope(script_state_.Get()); |
+ |
+ StringBuilder source_text; |
+ for (const auto& request : dependency_module_requests) { |
+ source_text.Append("import '"); |
+ source_text.Append(request); |
+ source_text.Append("';\n"); |
+ } |
+ source_text.Append("export default 'grapes';"); |
+ |
+ ScriptModule script_module = ScriptModule::Compile( |
+ script_state_->GetIsolate(), source_text.ToString(), url.GetString(), |
+ kSharableCrossOrigin); |
+ ModuleScript* module_script = |
+ ModuleScript::Create(this, script_module, url, "", kParserInserted, |
+ WebURLRequest::kFetchCredentialsModeOmit); |
+ auto result_request = dependency_module_requests_map_.insert( |
+ script_module, dependency_module_requests); |
+ EXPECT_TRUE(result_request.is_new_entry); |
+ auto result_map = module_map_.insert(url, module_script); |
+ EXPECT_TRUE(result_map.is_new_entry); |
+ |
+ EXPECT_EQ(url, pending_request_url_); |
+ EXPECT_TRUE(pending_client_); |
+ pending_client_->NotifyModuleLoadFinished(module_script); |
+ pending_client_.Clear(); |
+ |
+ return module_script; |
+ } |
+ |
+ // Get AncestorList specified in |Modulator::FetchTreeInternal()| call for |
+ // request matching |url|. |
+ AncestorList GetAncestorListForTreeFetch(const KURL& url) const { |
+ const auto& it = pending_tree_ancestor_list_.Find(url); |
+ if (it == pending_tree_ancestor_list_.end()) |
+ return AncestorList(); |
+ return it->value; |
+ } |
+ |
+ // Resolve |Modulator::FetchTreeInternal()| for given url. |
+ void ResolveDependentTreeFetch(const KURL& url) { |
+ ScriptState::Scope scope(script_state_.Get()); |
+ |
+ ScriptModule script_module = ScriptModule::Compile( |
+ script_state_->GetIsolate(), "export default 'pineapples';", |
+ url.GetString(), kSharableCrossOrigin); |
+ ModuleScript* module_script = |
+ ModuleScript::Create(this, script_module, url, "", kParserInserted, |
+ WebURLRequest::kFetchCredentialsModeOmit); |
+ auto result_map = module_map_.insert(url, module_script); |
+ EXPECT_TRUE(result_map.is_new_entry); |
+ |
+ const auto& it = pending_tree_client_map_.Find(url); |
+ EXPECT_NE(pending_tree_client_map_.end(), it); |
+ |
+ auto pending_client = it->value; |
+ EXPECT_TRUE(pending_client); |
+ pending_client->NotifyModuleTreeLoadFinished(module_script); |
+ pending_tree_client_map_.erase(it); |
+ } |
+ |
+ void SetInstantiateShouldFail(bool b) { instantiate_should_fail_ = b; } |
+ |
+ private: |
+ // Implements Modulator: |
+ |
+ void FetchSingle(const ModuleScriptFetchRequest& request, |
+ ModuleGraphLevel, |
+ SingleModuleClient* client) override { |
+ pending_request_url_ = request.Url(); |
+ EXPECT_FALSE(pending_client_); |
+ pending_client_ = client; |
+ } |
+ |
+ void FetchTreeInternal(const ModuleScriptFetchRequest& request, |
+ const AncestorList& list, |
+ ModuleGraphLevel level, |
+ ModuleTreeClient* client) override { |
+ const auto& url = request.Url(); |
+ |
+ auto ancestor_result = pending_tree_ancestor_list_.insert(url, list); |
+ EXPECT_TRUE(ancestor_result.is_new_entry); |
+ |
+ EXPECT_EQ(ModuleGraphLevel::kDependentModuleFetch, level); |
+ |
+ auto result_map = pending_tree_client_map_.insert(url, client); |
+ EXPECT_TRUE(result_map.is_new_entry); |
+ } |
+ |
+ ModuleScript* GetFetchedModuleScript(const KURL& url) override { |
+ const auto& it = module_map_.Find(url); |
+ if (it == module_map_.end()) |
+ return nullptr; |
+ |
+ return it->value; |
+ } |
+ |
+ ScriptValue InstantiateModule(ScriptModule) override { |
+ if (instantiate_should_fail_) { |
+ ScriptState::Scope scope(script_state_.Get()); |
+ v8::Local<v8::Value> error = V8ThrowException::CreateError( |
+ script_state_->GetIsolate(), "Instantiation failure."); |
+ return ScriptValue(script_state_.Get(), error); |
+ } |
+ return ScriptValue(); |
+ } |
+ |
+ Vector<String> ModuleRequestsFromScriptModule( |
+ ScriptModule script_module) override { |
+ const auto& it = dependency_module_requests_map_.Find(script_module); |
+ if (it == dependency_module_requests_map_.end()) |
+ return Vector<String>(); |
+ |
+ return it->value; |
+ } |
+ |
+ RefPtr<ScriptState> script_state_; |
+ KURL pending_request_url_; |
+ Member<SingleModuleClient> pending_client_; |
+ HashMap<ScriptModule, Vector<String>> dependency_module_requests_map_; |
+ HeapHashMap<KURL, Member<ModuleScript>> module_map_; |
+ HeapHashMap<KURL, Member<ModuleTreeClient>> pending_tree_client_map_; |
+ HashMap<KURL, AncestorList> pending_tree_ancestor_list_; |
+ bool instantiate_should_fail_ = false; |
+}; |
+ |
+DEFINE_TRACE(ModuleTreeLinkerTestModulator) { |
+ visitor->Trace(pending_client_); |
+ visitor->Trace(module_map_); |
+ visitor->Trace(pending_tree_client_map_); |
+ DummyModulator::Trace(visitor); |
+} |
+ |
+} // namespace |
+ |
+class ModuleTreeLinkerTest : public ::testing::Test { |
+ DISALLOW_COPY_AND_ASSIGN(ModuleTreeLinkerTest); |
+ |
+ public: |
+ ModuleTreeLinkerTest() = default; |
+ void SetUp() override; |
+ |
+ ModuleTreeLinkerTestModulator* GetModulator() { return modulator_.Get(); } |
+ |
+ protected: |
+ std::unique_ptr<DummyPageHolder> dummy_page_holder_; |
+ Persistent<ModuleTreeLinkerTestModulator> modulator_; |
+}; |
+ |
+void ModuleTreeLinkerTest::SetUp() { |
+ dummy_page_holder_ = DummyPageHolder::Create(IntSize(500, 500)); |
+ RefPtr<ScriptState> script_state = |
+ ToScriptStateForMainWorld(&dummy_page_holder_->GetFrame()); |
+ modulator_ = new ModuleTreeLinkerTestModulator(script_state); |
+} |
+ |
+TEST_F(ModuleTreeLinkerTest, fetchTreeNoDeps) { |
+ ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); |
+ |
+ KURL url(kParsedURLString, "http://example.com/root.js"); |
+ ModuleScriptFetchRequest module_request( |
+ url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); |
+ TestModuleTreeClient* client = new TestModuleTreeClient; |
+ registry->Fetch(module_request, AncestorList(), |
+ ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), |
+ client); |
+ |
+ EXPECT_FALSE(client->WasNotifyFinished()) |
+ << "ModuleTreeLinker should always finish asynchronously."; |
+ EXPECT_FALSE(client->GetModuleScript()); |
+ |
+ GetModulator()->ResolveSingleModuleScriptFetch(url, {}); |
+ EXPECT_TRUE(client->WasNotifyFinished()); |
+ ASSERT_TRUE(client->GetModuleScript()); |
+ EXPECT_EQ(client->GetModuleScript()->InstantiationState(), |
+ ModuleInstantiationState::kInstantiated); |
+} |
+ |
+TEST_F(ModuleTreeLinkerTest, fetchTreeInstantiationFailure) { |
+ GetModulator()->SetInstantiateShouldFail(true); |
+ |
+ ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); |
+ |
+ KURL url(kParsedURLString, "http://example.com/root.js"); |
+ ModuleScriptFetchRequest module_request( |
+ url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); |
+ TestModuleTreeClient* client = new TestModuleTreeClient; |
+ registry->Fetch(module_request, AncestorList(), |
+ ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), |
+ client); |
+ |
+ EXPECT_FALSE(client->WasNotifyFinished()) |
+ << "ModuleTreeLinker should always finish asynchronously."; |
+ EXPECT_FALSE(client->GetModuleScript()); |
+ |
+ GetModulator()->ResolveSingleModuleScriptFetch(url, {}); |
+ EXPECT_TRUE(client->WasNotifyFinished()); |
+ ASSERT_TRUE(client->GetModuleScript()); |
+ EXPECT_EQ(client->GetModuleScript()->InstantiationState(), |
+ ModuleInstantiationState::kErrored); |
+} |
+ |
+TEST_F(ModuleTreeLinkerTest, fetchTreeWithSingleDependency) { |
+ ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); |
+ |
+ KURL url(kParsedURLString, "http://example.com/root.js"); |
+ ModuleScriptFetchRequest module_request( |
+ url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); |
+ TestModuleTreeClient* client = new TestModuleTreeClient; |
+ registry->Fetch(module_request, AncestorList(), |
+ ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), |
+ client); |
+ |
+ EXPECT_FALSE(client->WasNotifyFinished()) |
+ << "ModuleTreeLinker should always finish asynchronously."; |
+ EXPECT_FALSE(client->GetModuleScript()); |
+ |
+ GetModulator()->ResolveSingleModuleScriptFetch(url, {"./dep1.js"}); |
+ EXPECT_FALSE(client->WasNotifyFinished()); |
+ |
+ KURL url_dep1(kParsedURLString, "http://example.com/dep1.js"); |
+ auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep1); |
+ EXPECT_EQ(1u, ancestor_list.size()); |
+ EXPECT_TRUE(ancestor_list.Contains( |
+ KURL(kParsedURLString, "http://example.com/root.js"))); |
+ |
+ GetModulator()->ResolveDependentTreeFetch(url_dep1); |
+ EXPECT_TRUE(client->WasNotifyFinished()); |
+ |
+ ASSERT_TRUE(client->GetModuleScript()); |
+ EXPECT_EQ(client->GetModuleScript()->InstantiationState(), |
+ ModuleInstantiationState::kInstantiated); |
+} |
+ |
+TEST_F(ModuleTreeLinkerTest, fetchTreeWith3Deps) { |
+ ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); |
+ |
+ KURL url(kParsedURLString, "http://example.com/root.js"); |
+ ModuleScriptFetchRequest module_request( |
+ url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); |
+ TestModuleTreeClient* client = new TestModuleTreeClient; |
+ registry->Fetch(module_request, AncestorList(), |
+ ModuleGraphLevel::kTopLevelModuleFetch, GetModulator(), |
+ client); |
+ |
+ EXPECT_FALSE(client->WasNotifyFinished()) |
+ << "ModuleTreeLinker should always finish asynchronously."; |
+ EXPECT_FALSE(client->GetModuleScript()); |
+ |
+ GetModulator()->ResolveSingleModuleScriptFetch( |
+ url, {"./dep1.js", "./dep2.js", "./dep3.js"}); |
+ EXPECT_FALSE(client->WasNotifyFinished()); |
+ |
+ Vector<KURL> url_deps; |
+ for (int i = 1; i <= 3; ++i) { |
+ StringBuilder url_dep_str; |
+ url_dep_str.Append("http://example.com/dep"); |
+ url_dep_str.AppendNumber(i); |
+ url_dep_str.Append(".js"); |
+ |
+ KURL url_dep(kParsedURLString, url_dep_str.ToString()); |
+ url_deps.push_back(url_dep); |
+ } |
+ |
+ for (const auto& url_dep : url_deps) { |
+ SCOPED_TRACE(url_dep.GetString()); |
+ auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep); |
+ EXPECT_EQ(1u, ancestor_list.size()); |
+ EXPECT_TRUE(ancestor_list.Contains( |
+ KURL(kParsedURLString, "http://example.com/root.js"))); |
+ } |
+ |
+ for (const auto& url_dep : url_deps) |
+ GetModulator()->ResolveDependentTreeFetch(url_dep); |
+ |
+ EXPECT_TRUE(client->WasNotifyFinished()); |
+ ASSERT_TRUE(client->GetModuleScript()); |
+ EXPECT_EQ(client->GetModuleScript()->InstantiationState(), |
+ ModuleInstantiationState::kInstantiated); |
+} |
+ |
+TEST_F(ModuleTreeLinkerTest, fetchDependencyTree) { |
+ ModuleTreeLinkerRegistry* registry = ModuleTreeLinkerRegistry::Create(); |
+ |
+ KURL url(kParsedURLString, "http://example.com/depth1.js"); |
+ ModuleScriptFetchRequest module_request( |
+ url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit); |
+ TestModuleTreeClient* client = new TestModuleTreeClient; |
+ registry->Fetch( |
+ module_request, |
+ AncestorList{KURL(kParsedURLString, "http://example.com/root.js")}, |
+ ModuleGraphLevel::kDependentModuleFetch, GetModulator(), client); |
+ |
+ EXPECT_FALSE(client->WasNotifyFinished()) |
+ << "ModuleTreeLinker should always finish asynchronously."; |
+ EXPECT_FALSE(client->GetModuleScript()); |
+ |
+ GetModulator()->ResolveSingleModuleScriptFetch(url, {"./depth2.js"}); |
+ |
+ KURL url_dep2(kParsedURLString, "http://example.com/depth2.js"); |
+ auto ancestor_list = GetModulator()->GetAncestorListForTreeFetch(url_dep2); |
+ EXPECT_EQ(2u, ancestor_list.size()); |
+ EXPECT_TRUE(ancestor_list.Contains( |
+ KURL(kParsedURLString, "http://example.com/root.js"))); |
+ EXPECT_TRUE(ancestor_list.Contains( |
+ KURL(kParsedURLString, "http://example.com/depth1.js"))); |
+ |
+ GetModulator()->ResolveDependentTreeFetch(url_dep2); |
+ |
+ EXPECT_TRUE(client->WasNotifyFinished()); |
+ ASSERT_TRUE(client->GetModuleScript()); |
+ EXPECT_EQ(client->GetModuleScript()->InstantiationState(), |
+ ModuleInstantiationState::kInstantiated); |
+} |
+ |
+} // namespace blink |