blob: ac18a9940f510dd5e6ca150dadceafb21f51b119 [file] [log] [blame]
// 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 final
: 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,
const ScriptModule& referrer,
ExceptionState&) override {
NOTREACHED();
return ScriptModule();
}
private:
int m_registerModuleScriptCallCount = 0;
};
} // namespace
class ModuleMapTestModulator final : public DummyModulator {
public:
ModuleMapTestModulator();
virtual ~ModuleMapTestModulator() {}
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()->fetchSingleModuleScript(moduleRequest,
ModuleGraphLevel::TopLevelModuleFetch, client);
modulator()->resolveFetches();
EXPECT_FALSE(client->wasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
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()->fetchSingleModuleScript(
moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2);
modulator()->resolveFetches();
EXPECT_FALSE(client2->wasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
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()->fetchSingleModuleScript(moduleRequest,
ModuleGraphLevel::TopLevelModuleFetch, client);
// Secondary request (which should join the first request)
TestSingleModuleClient* client2 = new TestSingleModuleClient;
map()->fetchSingleModuleScript(
moduleRequest, ModuleGraphLevel::TopLevelModuleFetch, client2);
modulator()->resolveFetches();
EXPECT_FALSE(client->wasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
EXPECT_FALSE(client2->wasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
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