blob: e5df67c95aafa1420c6a69bcc22b144752542da9 [file] [log] [blame]
// Copyright 2020 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 "third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "net/http/http_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_observer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/worker_main_script_loader_client.h"
#include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
namespace blink {
namespace {
using ::testing::_;
const char kTopLevelScriptURL[] = "https://example.com/worker.js";
const char kHeader[] =
"HTTP/1.1 200 OK\n"
"Content-Type: text/javascript\n\n";
const char kFailHeader[] = "HTTP/1.1 404 Not Found\n\n";
const std::string kTopLevelScript = "fetch(\"empty.html\");";
class WorkerMainScriptLoaderTest : public testing::Test {
public:
WorkerMainScriptLoaderTest()
: fake_loader_(pending_remote_loader_.InitWithNewPipeAndPassReceiver()),
client_(MakeGarbageCollected<TestClient>()) {
scoped_feature_list_.InitWithFeatureState(
blink::features::kPlzDedicatedWorker, true);
}
~WorkerMainScriptLoaderTest() override {
// Forced GC in order to finalize objects depending on MockResourceObserver,
// see details https://crbug.com/1132634.
ThreadState::Current()->CollectAllGarbageForTesting();
}
protected:
class TestClient final : public GarbageCollected<TestClient>,
public WorkerMainScriptLoaderClient {
public:
// Implements WorkerMainScriptLoaderClient.
void DidReceiveData(base::span<const char> data) override {
if (!data_)
data_ = SharedBuffer::Create(data.data(), data.size());
else
data_->Append(data.data(), data.size());
}
void OnFinishedLoadingWorkerMainScript() override { finished_ = true; }
void OnFailedLoadingWorkerMainScript() override { failed_ = true; }
bool LoadingIsFinished() const { return finished_; }
bool LoadingIsFailed() const { return failed_; }
SharedBuffer* Data() const { return data_.get(); }
void Trace(Visitor* visitor) const override {
visitor->Trace(worker_main_script_loader_);
}
private:
Member<WorkerMainScriptLoader> worker_main_script_loader_;
scoped_refptr<SharedBuffer> data_;
bool finished_ = false;
bool failed_ = false;
};
class FakeURLLoader final : public network::mojom::URLLoader {
public:
explicit FakeURLLoader(
mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver)
: receiver_(this, std::move(url_loader_receiver)) {}
~FakeURLLoader() override = default;
FakeURLLoader(const FakeURLLoader&) = delete;
FakeURLLoader& operator=(const FakeURLLoader&) = delete;
// network::mojom::URLLoader overrides.
void FollowRedirect(const std::vector<std::string>&,
const net::HttpRequestHeaders&,
const net::HttpRequestHeaders&,
const base::Optional<GURL>&) override {}
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
private:
mojo::Receiver<network::mojom::URLLoader> receiver_;
};
class FakeResourceLoadInfoNotifier final
: public blink::mojom::ResourceLoadInfoNotifier {
public:
FakeResourceLoadInfoNotifier() = default;
FakeResourceLoadInfoNotifier(const FakeResourceLoadInfoNotifier&) = delete;
FakeResourceLoadInfoNotifier& operator=(
const FakeResourceLoadInfoNotifier&) = delete;
// blink::mojom::ResourceLoadInfoNotifier overrides.
#if defined(OS_ANDROID)
void NotifyUpdateUserGestureCarryoverInfo() override {}
#endif
void NotifyResourceRedirectReceived(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr redirect_response) override {}
void NotifyResourceResponseReceived(
int64_t request_id,
const GURL& final_url,
network::mojom::URLResponseHeadPtr head,
network::mojom::RequestDestination request_destination,
int32_t previews_state) override {}
void NotifyResourceTransferSizeUpdated(
int64_t request_id,
int32_t transfer_size_diff) override {}
void NotifyResourceLoadCompleted(
blink::mojom::ResourceLoadInfoPtr resource_load_info,
const ::network::URLLoaderCompletionStatus& status) override {
resource_load_info_ = std::move(resource_load_info);
}
void NotifyResourceLoadCanceled(int64_t request_id) override {}
void Clone(mojo::PendingReceiver<blink::mojom::ResourceLoadInfoNotifier>
pending_resource_load_info_notifier) override {}
std::string GetMimeType() { return resource_load_info_->mime_type; }
private:
blink::mojom::ResourceLoadInfoPtr resource_load_info_;
};
class MockResourceLoadObserver : public ResourceLoadObserver {
public:
MOCK_METHOD2(DidStartRequest, void(const FetchParameters&, ResourceType));
MOCK_METHOD6(WillSendRequest,
void(uint64_t identifier,
const ResourceRequest&,
const ResourceResponse& redirect_response,
ResourceType,
const FetchInitiatorInfo&,
RenderBlockingBehavior));
MOCK_METHOD3(DidChangePriority,
void(uint64_t identifier,
ResourceLoadPriority,
int intra_priority_value));
MOCK_METHOD5(DidReceiveResponse,
void(uint64_t identifier,
const ResourceRequest& request,
const ResourceResponse& response,
const Resource* resource,
ResponseSource));
MOCK_METHOD2(DidReceiveData,
void(uint64_t identifier, base::span<const char> chunk));
MOCK_METHOD2(DidReceiveTransferSizeUpdate,
void(uint64_t identifier, int transfer_size_diff));
MOCK_METHOD2(DidDownloadToBlob, void(uint64_t identifier, BlobDataHandle*));
MOCK_METHOD5(DidFinishLoading,
void(uint64_t identifier,
base::TimeTicks finish_time,
int64_t encoded_data_length,
int64_t decoded_body_length,
bool should_report_corb_blocking));
MOCK_METHOD5(DidFailLoading,
void(const KURL&,
uint64_t identifier,
const ResourceError&,
int64_t encoded_data_length,
IsInternalRequest));
MOCK_METHOD1(EvictFromBackForwardCache,
void(blink::mojom::RendererEvictionReason));
};
MojoCreateDataPipeOptions CreateDataPipeOptions() {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = 1024;
return options;
}
std::unique_ptr<WorkerMainScriptLoadParameters> CreateMainScriptLoaderParams(
const char* header,
mojo::ScopedDataPipeProducerHandle* body_producer) {
auto head = network::mojom::URLResponseHead::New();
head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(header));
head->headers->GetMimeType(&head->mime_type);
network::mojom::URLLoaderClientEndpointsPtr endpoints =
network::mojom::URLLoaderClientEndpoints::New(
std::move(pending_remote_loader_),
loader_client_.BindNewPipeAndPassReceiver());
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params =
std::make_unique<WorkerMainScriptLoadParameters>();
worker_main_script_load_params->response_head = std::move(head);
worker_main_script_load_params->url_loader_client_endpoints =
std::move(endpoints);
mojo::ScopedDataPipeConsumerHandle body_consumer;
MojoCreateDataPipeOptions options = CreateDataPipeOptions();
EXPECT_EQ(MOJO_RESULT_OK,
mojo::CreateDataPipe(&options, *body_producer, body_consumer));
worker_main_script_load_params->response_body = std::move(body_consumer);
return worker_main_script_load_params;
}
WorkerMainScriptLoader* CreateWorkerMainScriptLoaderAndStartLoading(
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
ResourceLoadObserver* observer,
mojom::ResourceLoadInfoNotifier* resource_load_info_notifier) {
ResourceRequest request(kTopLevelScriptURL);
request.SetRequestContext(mojom::blink::RequestContextType::SHARED_WORKER);
request.SetRequestDestination(
network::mojom::RequestDestination::kSharedWorker);
FetchParameters fetch_params(std::move(request),
ResourceLoaderOptions(nullptr /* world */));
WorkerMainScriptLoader* worker_main_script_loader =
MakeGarbageCollected<WorkerMainScriptLoader>();
MockFetchContext* fetch_context = MakeGarbageCollected<MockFetchContext>();
fetch_context->SetResourceLoadInfoNotifier(resource_load_info_notifier);
worker_main_script_loader->Start(fetch_params,
std::move(worker_main_script_load_params),
fetch_context, observer, client_);
return worker_main_script_loader;
}
void Complete(int net_error) {
loader_client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
base::RunLoop().RunUntilIdle();
}
protected:
base::test::TaskEnvironment task_environment_;
mojo::PendingRemote<network::mojom::URLLoader> pending_remote_loader_;
mojo::Remote<network::mojom::URLLoaderClient> loader_client_;
FakeURLLoader fake_loader_;
Persistent<TestClient> client_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(WorkerMainScriptLoaderTest, ResponseWithSucessThenOnComplete) {
mojo::ScopedDataPipeProducerHandle body_producer;
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params =
CreateMainScriptLoaderParams(kHeader, &body_producer);
MockResourceLoadObserver* mock_observer =
MakeGarbageCollected<MockResourceLoadObserver>();
FakeResourceLoadInfoNotifier fake_resource_load_info_notifier;
EXPECT_CALL(*mock_observer, WillSendRequest(_, _, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveResponse(_, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveData(_, _));
EXPECT_CALL(*mock_observer, DidFinishLoading(_, _, _, _, _));
EXPECT_CALL(*mock_observer, DidFailLoading(_, _, _, _, _)).Times(0);
Persistent<WorkerMainScriptLoader> worker_main_script_loader =
CreateWorkerMainScriptLoaderAndStartLoading(
std::move(worker_main_script_load_params), mock_observer,
&fake_resource_load_info_notifier);
mojo::BlockingCopyFromString(kTopLevelScript, body_producer);
body_producer.reset();
Complete(net::OK);
EXPECT_TRUE(client_->LoadingIsFinished());
EXPECT_FALSE(client_->LoadingIsFailed());
EXPECT_EQ(KURL(kTopLevelScriptURL),
worker_main_script_loader->GetRequestURL());
EXPECT_EQ(UTF8Encoding(), worker_main_script_loader->GetScriptEncoding());
EXPECT_EQ(kTopLevelScript,
std::string(client_->Data()->Data(), client_->Data()->size()));
EXPECT_EQ("text/javascript", fake_resource_load_info_notifier.GetMimeType());
}
TEST_F(WorkerMainScriptLoaderTest, ResponseWithFailureThenOnComplete) {
mojo::ScopedDataPipeProducerHandle body_producer;
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params =
CreateMainScriptLoaderParams(kFailHeader, &body_producer);
MockResourceLoadObserver* mock_observer =
MakeGarbageCollected<MockResourceLoadObserver>();
FakeResourceLoadInfoNotifier fake_resource_load_info_notifier;
EXPECT_CALL(*mock_observer, WillSendRequest(_, _, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveResponse(_, _, _, _, _));
EXPECT_CALL(*mock_observer, DidFinishLoading(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mock_observer, DidFailLoading(_, _, _, _, _));
Persistent<WorkerMainScriptLoader> worker_main_script_loader =
CreateWorkerMainScriptLoaderAndStartLoading(
std::move(worker_main_script_load_params), mock_observer,
&fake_resource_load_info_notifier);
mojo::BlockingCopyFromString("PAGE NOT FOUND\n", body_producer);
Complete(net::OK);
body_producer.reset();
EXPECT_FALSE(client_->LoadingIsFinished());
EXPECT_TRUE(client_->LoadingIsFailed());
}
TEST_F(WorkerMainScriptLoaderTest, DisconnectBeforeOnComplete) {
mojo::ScopedDataPipeProducerHandle body_producer;
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params =
CreateMainScriptLoaderParams(kHeader, &body_producer);
MockResourceLoadObserver* mock_observer =
MakeGarbageCollected<MockResourceLoadObserver>();
FakeResourceLoadInfoNotifier fake_resource_load_info_notifier;
EXPECT_CALL(*mock_observer, WillSendRequest(_, _, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveResponse(_, _, _, _, _));
EXPECT_CALL(*mock_observer, DidFinishLoading(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mock_observer, DidFailLoading(_, _, _, _, _));
Persistent<WorkerMainScriptLoader> worker_main_script_loader =
CreateWorkerMainScriptLoaderAndStartLoading(
std::move(worker_main_script_load_params), mock_observer,
&fake_resource_load_info_notifier);
loader_client_.reset();
body_producer.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(client_->LoadingIsFinished());
EXPECT_TRUE(client_->LoadingIsFailed());
}
TEST_F(WorkerMainScriptLoaderTest, OnCompleteWithError) {
mojo::ScopedDataPipeProducerHandle body_producer;
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params =
CreateMainScriptLoaderParams(kHeader, &body_producer);
MockResourceLoadObserver* mock_observer =
MakeGarbageCollected<MockResourceLoadObserver>();
FakeResourceLoadInfoNotifier fake_resource_load_info_notifier;
EXPECT_CALL(*mock_observer, WillSendRequest(_, _, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveResponse(_, _, _, _, _));
EXPECT_CALL(*mock_observer, DidReceiveData(_, _));
EXPECT_CALL(*mock_observer, DidFinishLoading(_, _, _, _, _)).Times(0);
EXPECT_CALL(*mock_observer, DidFailLoading(_, _, _, _, _));
Persistent<WorkerMainScriptLoader> worker_main_script_loader =
CreateWorkerMainScriptLoaderAndStartLoading(
std::move(worker_main_script_load_params), mock_observer,
&fake_resource_load_info_notifier);
mojo::BlockingCopyFromString(kTopLevelScript, body_producer);
Complete(net::ERR_FAILED);
body_producer.reset();
EXPECT_FALSE(client_->LoadingIsFinished());
EXPECT_TRUE(client_->LoadingIsFailed());
}
} // namespace
} // namespace blink