| // 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 |