| Index: third_party/WebKit/Source/core/dom/ModuleMapTest.cpp | 
| diff --git a/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..cbe3ac11aac3b989a59222f674bc3a22377c9bca | 
| --- /dev/null | 
| +++ b/third_party/WebKit/Source/core/dom/ModuleMapTest.cpp | 
| @@ -0,0 +1,239 @@ | 
| +// 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/dom/ModuleMap.h" | 
| + | 
| +#include "core/dom/Document.h" | 
| +#include "core/dom/Modulator.h" | 
| +#include "core/dom/ModuleScript.h" | 
| +#include "core/dom/ScriptModuleResolver.h" | 
| +#include "core/loader/modulescript/ModuleScriptFetchRequest.h" | 
| +#include "core/loader/modulescript/ModuleScriptLoaderClient.h" | 
| +#include "core/testing/DummyModulator.h" | 
| +#include "core/testing/DummyPageHolder.h" | 
| +#include "platform/heap/Handle.h" | 
| +#include "platform/testing/TestingPlatformSupport.h" | 
| +#include "public/platform/Platform.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| + | 
| +namespace blink { | 
| + | 
| +namespace { | 
| + | 
| +class TestSingleModuleClient | 
| +    : public GarbageCollectedFinalized<TestSingleModuleClient>, | 
| +      public SingleModuleClient { | 
| +  USING_GARBAGE_COLLECTED_MIXIN(TestSingleModuleClient); | 
| + | 
| + public: | 
| +  TestSingleModuleClient() = default; | 
| +  virtual ~TestSingleModuleClient() {} | 
| + | 
| +  DEFINE_INLINE_TRACE() { visitor->trace(m_moduleScript); } | 
| + | 
| +  void notifyModuleLoadFinished(ModuleScript* moduleScript) override { | 
| +    m_wasNotifyFinished = true; | 
| +    m_moduleScript = moduleScript; | 
| +  } | 
| + | 
| +  bool wasNotifyFinished() const { return m_wasNotifyFinished; } | 
| +  ModuleScript* moduleScript() { return m_moduleScript; } | 
| + | 
| + private: | 
| +  bool m_wasNotifyFinished = false; | 
| +  Member<ModuleScript> m_moduleScript; | 
| +}; | 
| + | 
| +class TestScriptModuleResolver final : public ScriptModuleResolver { | 
| + public: | 
| +  TestScriptModuleResolver() {} | 
| + | 
| +  int registerModuleScriptCallCount() const { | 
| +    return m_registerModuleScriptCallCount; | 
| +  } | 
| + | 
| +  void registerModuleScript(ModuleScript*) override { | 
| +    m_registerModuleScriptCallCount++; | 
| +  } | 
| + | 
| +  ScriptModule resolve(const String& specifier, | 
| +                       ScriptModuleIdentifier referrer, | 
| +                       ExceptionState&) override { | 
| +    return ScriptModule(); | 
| +  } | 
| + | 
| + private: | 
| +  int m_registerModuleScriptCallCount = 0; | 
| +}; | 
| + | 
| +}  // namespace | 
| + | 
| +class ModuleMapTestModulator final : public DummyModulator { | 
| + public: | 
| +  ModuleMapTestModulator(); | 
| +  ~ModuleMapTestModulator() override {} | 
| + | 
| +  DECLARE_TRACE(); | 
| + | 
| +  TestScriptModuleResolver* testScriptModuleResolver() { | 
| +    return m_resolver.get(); | 
| +  } | 
| +  void resolveFetches(); | 
| + | 
| + private: | 
| +  // Implements Modulator: | 
| + | 
| +  ScriptModuleResolver* scriptModuleResolver() override { | 
| +    return m_resolver.get(); | 
| +  } | 
| + | 
| +  WebTaskRunner* taskRunner() override { | 
| +    return Platform::current()->currentThread()->getWebTaskRunner(); | 
| +  }; | 
| + | 
| +  void fetchNewSingleModule(const ModuleScriptFetchRequest&, | 
| +                            ModuleGraphLevel, | 
| +                            ModuleScriptLoaderClient*) override; | 
| + | 
| +  struct TestRequest : public GarbageCollectedFinalized<TestRequest> { | 
| +    KURL url; | 
| +    String nonce; | 
| +    Member<ModuleScriptLoaderClient> client; | 
| + | 
| +    DEFINE_INLINE_TRACE() { visitor->trace(client); } | 
| +  }; | 
| +  HeapVector<Member<TestRequest>> m_testRequests; | 
| + | 
| +  Member<TestScriptModuleResolver> m_resolver; | 
| +}; | 
| + | 
| +ModuleMapTestModulator::ModuleMapTestModulator() | 
| +    : m_resolver(new TestScriptModuleResolver) {} | 
| + | 
| +DEFINE_TRACE(ModuleMapTestModulator) { | 
| +  visitor->trace(m_testRequests); | 
| +  visitor->trace(m_resolver); | 
| +  DummyModulator::trace(visitor); | 
| +} | 
| + | 
| +void ModuleMapTestModulator::fetchNewSingleModule( | 
| +    const ModuleScriptFetchRequest& request, | 
| +    ModuleGraphLevel, | 
| +    ModuleScriptLoaderClient* client) { | 
| +  TestRequest* testRequest = new TestRequest; | 
| +  testRequest->url = request.url(); | 
| +  testRequest->nonce = request.nonce(); | 
| +  testRequest->client = client; | 
| +  m_testRequests.push_back(testRequest); | 
| +} | 
| + | 
| +void ModuleMapTestModulator::resolveFetches() { | 
| +  for (const auto& testRequest : m_testRequests) { | 
| +    ModuleScript* moduleScript = ModuleScript::create( | 
| +        ScriptModule(), testRequest->url, testRequest->nonce, ParserInserted, | 
| +        WebURLRequest::FetchCredentialsModeOmit); | 
| +    taskRunner()->postTask( | 
| +        BLINK_FROM_HERE, | 
| +        WTF::bind(&ModuleScriptLoaderClient::notifyNewSingleModuleFinished, | 
| +                  wrapPersistent(testRequest->client.get()), | 
| +                  wrapPersistent(moduleScript))); | 
| +  } | 
| +  m_testRequests.clear(); | 
| +} | 
| + | 
| +class ModuleMapTest : public testing::Test { | 
| + public: | 
| +  void SetUp() override; | 
| + | 
| +  ModuleMapTestModulator* modulator() { return m_modulator.get(); } | 
| +  ModuleMap* map() { return m_map; } | 
| + | 
| + protected: | 
| +  Persistent<ModuleMapTestModulator> m_modulator; | 
| +  Persistent<ModuleMap> m_map; | 
| +}; | 
| + | 
| +void ModuleMapTest::SetUp() { | 
| +  m_modulator = new ModuleMapTestModulator(); | 
| +  m_map = ModuleMap::create(m_modulator.get()); | 
| +} | 
| + | 
| +TEST_F(ModuleMapTest, sequentialRequests) { | 
| +  ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> | 
| +      platform; | 
| +  platform->advanceClockSeconds(1.);  // For non-zero DocumentParserTimings | 
| + | 
| +  KURL url(KURL(), "https://example.com/foo.js"); | 
| +  ModuleScriptFetchRequest moduleRequest( | 
| +      url, String(), ParserInserted, WebURLRequest::FetchCredentialsModeOmit); | 
| + | 
| +  // First request | 
| +  TestSingleModuleClient* client = new TestSingleModuleClient; | 
| +  map()->retrieveAndFetchIfNeeded( | 
| +      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client); | 
| +  modulator()->resolveFetches(); | 
| +  platform->runUntilIdle(); | 
| + | 
| +  EXPECT_EQ( | 
| +      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(), | 
| +      1); | 
| +  EXPECT_TRUE(client->wasNotifyFinished()); | 
| +  EXPECT_TRUE(client->moduleScript()); | 
| +  EXPECT_EQ(client->moduleScript()->instantiationState(), | 
| +            ModuleInstantiationState::Uninstantiated); | 
| + | 
| +  // Secondary request | 
| +  TestSingleModuleClient* client2 = new TestSingleModuleClient; | 
| +  map()->retrieveAndFetchIfNeeded( | 
| +      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2); | 
| +  modulator()->resolveFetches(); | 
| +  platform->runUntilIdle(); | 
| + | 
| +  EXPECT_EQ( | 
| +      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(), | 
| +      1) | 
| +      << "registerModuleScript sholudn't be called in secondary request."; | 
| +  EXPECT_TRUE(client2->wasNotifyFinished()); | 
| +  EXPECT_TRUE(client2->moduleScript()); | 
| +  EXPECT_EQ(client2->moduleScript()->instantiationState(), | 
| +            ModuleInstantiationState::Uninstantiated); | 
| +} | 
| + | 
| +TEST_F(ModuleMapTest, concurrentRequestsShouldJoin) { | 
| +  ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler> | 
| +      platform; | 
| +  platform->advanceClockSeconds(1.);  // For non-zero DocumentParserTimings | 
| + | 
| +  KURL url(KURL(), "https://example.com/foo.js"); | 
| +  ModuleScriptFetchRequest moduleRequest( | 
| +      url, String(), ParserInserted, WebURLRequest::FetchCredentialsModeOmit); | 
| + | 
| +  // First request | 
| +  TestSingleModuleClient* client = new TestSingleModuleClient; | 
| +  map()->retrieveAndFetchIfNeeded( | 
| +      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client); | 
| + | 
| +  // Secondary request (which should join the first request) | 
| +  TestSingleModuleClient* client2 = new TestSingleModuleClient; | 
| +  map()->retrieveAndFetchIfNeeded( | 
| +      moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2); | 
| + | 
| +  modulator()->resolveFetches(); | 
| +  platform->runUntilIdle(); | 
| + | 
| +  EXPECT_EQ( | 
| +      modulator()->testScriptModuleResolver()->registerModuleScriptCallCount(), | 
| +      1); | 
| + | 
| +  EXPECT_TRUE(client->wasNotifyFinished()); | 
| +  EXPECT_TRUE(client->moduleScript()); | 
| +  EXPECT_EQ(client->moduleScript()->instantiationState(), | 
| +            ModuleInstantiationState::Uninstantiated); | 
| +  EXPECT_TRUE(client2->wasNotifyFinished()); | 
| +  EXPECT_TRUE(client2->moduleScript()); | 
| +  EXPECT_EQ(client2->moduleScript()->instantiationState(), | 
| +            ModuleInstantiationState::Uninstantiated); | 
| +} | 
| + | 
| +}  // namespace blink | 
|  |