blob: 87adbf24eb630a745fd3008a9a46c3568cf4da27 [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(module_script_); }
void NotifyModuleLoadFinished(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 TestScriptModuleResolver final : public ScriptModuleResolver {
public:
TestScriptModuleResolver() {}
int RegisterModuleScriptCallCount() const {
return register_module_script_call_count_;
}
void RegisterModuleScript(ModuleScript*) override {
register_module_script_call_count_++;
}
ScriptModule Resolve(const String& specifier,
const ScriptModule& referrer,
ExceptionState&) override {
NOTREACHED();
return ScriptModule();
}
private:
int register_module_script_call_count_ = 0;
};
} // namespace
class ModuleMapTestModulator final : public DummyModulator {
public:
ModuleMapTestModulator();
virtual ~ModuleMapTestModulator() {}
DECLARE_TRACE();
TestScriptModuleResolver* GetTestScriptModuleResolver() {
return resolver_.Get();
}
void ResolveFetches();
private:
// Implements Modulator:
ScriptModuleResolver* GetScriptModuleResolver() override {
return 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>> test_requests_;
Member<TestScriptModuleResolver> resolver_;
};
ModuleMapTestModulator::ModuleMapTestModulator()
: resolver_(new TestScriptModuleResolver) {}
DEFINE_TRACE(ModuleMapTestModulator) {
visitor->Trace(test_requests_);
visitor->Trace(resolver_);
DummyModulator::Trace(visitor);
}
void ModuleMapTestModulator::FetchNewSingleModule(
const ModuleScriptFetchRequest& request,
ModuleGraphLevel,
ModuleScriptLoaderClient* client) {
TestRequest* test_request = new TestRequest;
test_request->url = request.Url();
test_request->nonce = request.Nonce();
test_request->client = client;
test_requests_.push_back(test_request);
}
void ModuleMapTestModulator::ResolveFetches() {
for (const auto& test_request : test_requests_) {
ModuleScript* module_script = ModuleScript::Create(
this, ScriptModule(), test_request->url, test_request->nonce,
kParserInserted, WebURLRequest::kFetchCredentialsModeOmit);
TaskRunner()->PostTask(
BLINK_FROM_HERE,
WTF::Bind(&ModuleScriptLoaderClient::NotifyNewSingleModuleFinished,
WrapPersistent(test_request->client.Get()),
WrapPersistent(module_script)));
}
test_requests_.Clear();
}
class ModuleMapTest : public testing::Test {
public:
void SetUp() override;
ModuleMapTestModulator* Modulator() { return modulator_.Get(); }
ModuleMap* Map() { return map_; }
protected:
Persistent<ModuleMapTestModulator> modulator_;
Persistent<ModuleMap> map_;
};
void ModuleMapTest::SetUp() {
modulator_ = new ModuleMapTestModulator();
map_ = ModuleMap::Create(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 module_request(
url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit);
// First request
TestSingleModuleClient* client = new TestSingleModuleClient;
Map()->FetchSingleModuleScript(
module_request, ModuleGraphLevel::kTopLevelModuleFetch, client);
Modulator()->ResolveFetches();
EXPECT_FALSE(client->WasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
platform->RunUntilIdle();
EXPECT_EQ(Modulator()
->GetTestScriptModuleResolver()
->RegisterModuleScriptCallCount(),
1);
EXPECT_TRUE(client->WasNotifyFinished());
EXPECT_TRUE(client->GetModuleScript());
EXPECT_EQ(client->GetModuleScript()->InstantiationState(),
ModuleInstantiationState::kUninstantiated);
// Secondary request
TestSingleModuleClient* client2 = new TestSingleModuleClient;
Map()->FetchSingleModuleScript(
module_request, ModuleGraphLevel::kTopLevelModuleFetch, client2);
Modulator()->ResolveFetches();
EXPECT_FALSE(client2->WasNotifyFinished())
<< "fetchSingleModuleScript shouldn't complete synchronously";
platform->RunUntilIdle();
EXPECT_EQ(Modulator()
->GetTestScriptModuleResolver()
->RegisterModuleScriptCallCount(),
1)
<< "registerModuleScript sholudn't be called in secondary request.";
EXPECT_TRUE(client2->WasNotifyFinished());
EXPECT_TRUE(client2->GetModuleScript());
EXPECT_EQ(client2->GetModuleScript()->InstantiationState(),
ModuleInstantiationState::kUninstantiated);
}
TEST_F(ModuleMapTest, concurrentRequestsShouldJoin) {
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform;
platform->AdvanceClockSeconds(1.); // For non-zero DocumentParserTimings
KURL url(KURL(), "https://example.com/foo.js");
ModuleScriptFetchRequest module_request(
url, String(), kParserInserted, WebURLRequest::kFetchCredentialsModeOmit);
// First request
TestSingleModuleClient* client = new TestSingleModuleClient;
Map()->FetchSingleModuleScript(
module_request, ModuleGraphLevel::kTopLevelModuleFetch, client);
// Secondary request (which should join the first request)
TestSingleModuleClient* client2 = new TestSingleModuleClient;
Map()->FetchSingleModuleScript(
module_request, ModuleGraphLevel::kTopLevelModuleFetch, 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()
->GetTestScriptModuleResolver()
->RegisterModuleScriptCallCount(),
1);
EXPECT_TRUE(client->WasNotifyFinished());
EXPECT_TRUE(client->GetModuleScript());
EXPECT_EQ(client->GetModuleScript()->InstantiationState(),
ModuleInstantiationState::kUninstantiated);
EXPECT_TRUE(client2->WasNotifyFinished());
EXPECT_TRUE(client2->GetModuleScript());
EXPECT_EQ(client2->GetModuleScript()->InstantiationState(),
ModuleInstantiationState::kUninstantiated);
}
} // namespace blink