blob: a63572cc3677767245a3823237b5b5dd5710b278 [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 "content/renderer/service_worker/service_worker_context_client.h"
#include <utility>
#include <vector>
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "content/child/thread_safe_sender.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/common/content_client.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/renderer/service_worker/embedded_worker_instance_client_impl.h"
#include "content/renderer/service_worker/service_worker_timeout_timer.h"
#include "content/renderer/service_worker/service_worker_type_util.h"
#include "content/renderer/worker_thread_registry.h"
#include "mojo/public/cpp/bindings/associated_binding_set.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/fetch/fetch_api_request_headers_map.h"
#include "third_party/blink/public/common/messaging/message_port_channel.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
#include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "third_party/blink/public/platform/modules/notifications/web_notification_data.h"
#include "third_party/blink/public/platform/modules/payments/web_payment_request_event_data.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_clients_info.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_error.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_registration_object_info.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_request.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_data_consumer_handle.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h"
namespace content {
namespace {
// Pipes connected to the context client.
struct ContextClientPipes {
// From the browser to ServiceWorkerContextClient.
blink::mojom::ServiceWorkerPtr service_worker;
blink::mojom::ControllerServiceWorkerPtr controller;
blink::mojom::ServiceWorkerRegistrationObjectAssociatedPtr registration;
// From ServiceWorkerContextClient to the browser.
blink::mojom::ServiceWorkerHostAssociatedRequest service_worker_host_request;
mojom::EmbeddedWorkerInstanceHostAssociatedRequest
embedded_worker_host_request;
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedRequest
registration_host_request;
};
class MockWebServiceWorkerContextProxy
: public blink::WebServiceWorkerContextProxy {
public:
~MockWebServiceWorkerContextProxy() override = default;
void BindServiceWorkerHost(
mojo::ScopedInterfaceEndpointHandle service_worker_host) override {}
void SetRegistration(
blink::WebServiceWorkerRegistrationObjectInfo info) override {
DCHECK(!registration_object_info_);
registration_object_info_ =
std::make_unique<blink::WebServiceWorkerRegistrationObjectInfo>(
std::move(info));
}
void ReadyToEvaluateScript() override {}
bool HasFetchEventHandler() override { return false; }
void DispatchFetchEvent(int fetch_event_id,
const blink::WebServiceWorkerRequest& web_request,
bool navigation_preload_sent) override {
fetch_events_.emplace_back(fetch_event_id, web_request);
}
void DispatchActivateEvent(int event_id) override { NOTREACHED(); }
void DispatchBackgroundFetchAbortEvent(
int event_id,
const blink::WebBackgroundFetchRegistration& registration) override {
NOTREACHED();
}
void DispatchBackgroundFetchClickEvent(
int event_id,
const blink::WebBackgroundFetchRegistration& registration) override {
NOTREACHED();
}
void DispatchBackgroundFetchFailEvent(
int event_id,
const blink::WebBackgroundFetchRegistration& registration) override {
NOTREACHED();
}
void DispatchBackgroundFetchSuccessEvent(
int event_id,
const blink::WebBackgroundFetchRegistration& registration) override {
NOTREACHED();
}
void DispatchCookieChangeEvent(
int event_id,
const blink::WebCanonicalCookie& cookie,
::network::mojom::CookieChangeCause change_cause) override {
NOTREACHED();
}
void DispatchExtendableMessageEvent(
int event_id,
blink::TransferableMessage message,
const blink::WebSecurityOrigin& source_origin,
const blink::WebServiceWorkerClientInfo&) override {
NOTREACHED();
}
void DispatchExtendableMessageEvent(
int event_id,
blink::TransferableMessage message,
const blink::WebSecurityOrigin& source_origin,
blink::WebServiceWorkerObjectInfo) override {
NOTREACHED();
}
void DispatchInstallEvent(int event_id) override { NOTREACHED(); }
void DispatchNotificationClickEvent(int event_id,
const blink::WebString& notification_id,
const blink::WebNotificationData&,
int action_index,
const blink::WebString& reply) override {
NOTREACHED();
}
void DispatchNotificationCloseEvent(
int event_id,
const blink::WebString& notification_id,
const blink::WebNotificationData&) override {
NOTREACHED();
}
void DispatchPushEvent(int event_id, const blink::WebString& data) override {
NOTREACHED();
}
void DispatchSyncEvent(int sync_event_id,
const blink::WebString& tag,
bool last_chance) override {
NOTREACHED();
}
void DispatchAbortPaymentEvent(int event_id) override { NOTREACHED(); }
void DispatchCanMakePaymentEvent(
int event_id,
const blink::WebCanMakePaymentEventData&) override {
NOTREACHED();
}
void DispatchPaymentRequestEvent(
int event_id,
const blink::WebPaymentRequestEventData&) override {
NOTREACHED();
}
void OnNavigationPreloadResponse(
int fetch_event_id,
std::unique_ptr<blink::WebURLResponse>,
mojo::ScopedDataPipeConsumerHandle) override {
NOTREACHED();
}
void OnNavigationPreloadError(
int fetch_event_id,
std::unique_ptr<blink::WebServiceWorkerError>) override {
NOTREACHED();
}
void OnNavigationPreloadComplete(int fetch_event_id,
base::TimeTicks completion_time,
int64_t encoded_data_length,
int64_t encoded_body_length,
int64_t decoded_body_length) override {
NOTREACHED();
}
const std::vector<
std::pair<int /* event_id */, blink::WebServiceWorkerRequest>>&
fetch_events() const {
return fetch_events_;
}
private:
std::unique_ptr<blink::WebServiceWorkerRegistrationObjectInfo>
registration_object_info_;
std::vector<std::pair<int /* event_id */, blink::WebServiceWorkerRequest>>
fetch_events_;
};
base::RepeatingClosure CreateCallbackWithCalledFlag(bool* out_is_called) {
return base::BindRepeating([](bool* out_is_called) { *out_is_called = true; },
out_is_called);
}
class MockServiceWorkerObjectHost
: public blink::mojom::ServiceWorkerObjectHost {
public:
explicit MockServiceWorkerObjectHost(int64_t version_id)
: version_id_(version_id) {}
~MockServiceWorkerObjectHost() override = default;
blink::mojom::ServiceWorkerObjectInfoPtr CreateObjectInfo() {
auto info = blink::mojom::ServiceWorkerObjectInfo::New();
info->version_id = version_id_;
bindings_.AddBinding(this, mojo::MakeRequest(&info->host_ptr_info));
info->request = mojo::MakeRequest(&remote_object_);
return info;
}
int GetBindingCount() const { return bindings_.size(); }
private:
// Implements blink::mojom::ServiceWorkerObjectHost.
void PostMessageToServiceWorker(
::blink::TransferableMessage message) override {
NOTREACHED();
}
void TerminateForTesting(TerminateForTestingCallback callback) override {
NOTREACHED();
}
const int64_t version_id_;
mojo::AssociatedBindingSet<blink::mojom::ServiceWorkerObjectHost> bindings_;
blink::mojom::ServiceWorkerObjectAssociatedPtr remote_object_;
};
} // namespace
class ServiceWorkerContextClientTest : public testing::Test {
public:
ServiceWorkerContextClientTest() = default;
protected:
void SetUp() override {
task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
message_loop_.SetTaskRunner(task_runner_);
// Use this thread as the worker thread.
WorkerThreadRegistry::Instance()->DidStartCurrentWorkerThread();
}
void TearDown() override {
ServiceWorkerContextClient::ResetThreadSpecificInstanceForTesting();
// Unregister this thread from worker threads.
WorkerThreadRegistry::Instance()->WillStopCurrentWorkerThread();
task_runner_->RunUntilIdle();
}
void EnableServicification() {
feature_list_.InitWithFeatures({network::features::kNetworkService}, {});
ASSERT_TRUE(blink::ServiceWorkerUtils::IsServicificationEnabled());
}
// Creates an empty struct to initialize ServiceWorkerProviderContext.
mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfo() {
auto info = mojom::ServiceWorkerProviderInfoForStartWorker::New();
info->provider_id = 10; // dummy
return info;
}
// Creates an ContextClient, whose pipes are connected to |out_pipes|, then
// simulates that the service worker thread has started with |proxy|.
std::unique_ptr<ServiceWorkerContextClient> CreateContextClient(
ContextClientPipes* out_pipes,
blink::WebServiceWorkerContextProxy* proxy) {
auto service_worker_request = mojo::MakeRequest(&out_pipes->service_worker);
auto controller_request = mojo::MakeRequest(&out_pipes->controller);
mojom::EmbeddedWorkerInstanceHostAssociatedPtr embedded_worker_host_ptr;
out_pipes->embedded_worker_host_request =
mojo::MakeRequestAssociatedWithDedicatedPipe(&embedded_worker_host_ptr);
const GURL kScope("https://example.com");
const GURL kScript("https://example.com/SW.js");
std::unique_ptr<ServiceWorkerContextClient> context_client =
std::make_unique<ServiceWorkerContextClient>(
1 /* embedded_worker_id */, 1 /* service_worker_version_id */,
kScope, kScript, false /* is_script_streaming */,
RendererPreferences(), std::move(service_worker_request),
std::move(controller_request),
embedded_worker_host_ptr.PassInterface(), CreateProviderInfo(),
nullptr /* embedded_worker_client */,
mojom::EmbeddedWorkerStartTiming::New(),
nullptr /* preference_watcher_request */,
nullptr /* subresource_loaders */,
blink::scheduler::GetSingleThreadTaskRunnerForTesting());
context_client->WorkerContextStarted(proxy);
blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host;
out_pipes->service_worker_host_request =
mojo::MakeRequest(&service_worker_host);
auto registration_info =
blink::mojom::ServiceWorkerRegistrationObjectInfo::New();
registration_info->registration_id = 100; // dummy
registration_info->options =
blink::mojom::ServiceWorkerRegistrationOptions::New(
kScope, blink::mojom::ScriptType::kClassic,
blink::mojom::ServiceWorkerUpdateViaCache::kAll);
out_pipes->registration_host_request =
mojo::MakeRequest(&registration_info->host_ptr_info);
registration_info->request = mojo::MakeRequest(&out_pipes->registration);
out_pipes->service_worker->InitializeGlobalScope(
std::move(service_worker_host), std::move(registration_info));
task_runner()->RunUntilIdle();
return context_client;
}
scoped_refptr<base::TestMockTimeTaskRunner> task_runner() const {
return task_runner_;
}
private:
base::MessageLoop message_loop_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ServiceWorkerContextClientTest, Ping) {
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
bool is_called = false;
pipes.service_worker->Ping(CreateCallbackWithCalledFlag(&is_called));
task_runner()->RunUntilIdle();
EXPECT_TRUE(is_called);
}
TEST_F(ServiceWorkerContextClientTest, DispatchFetchEvent) {
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
context_client->DidEvaluateScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
const GURL expected_url("https://example.com/expected");
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url;
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
pipes.service_worker->DispatchFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
ASSERT_EQ(1u, mock_proxy.fetch_events().size());
EXPECT_EQ(expected_url,
static_cast<GURL>(mock_proxy.fetch_events()[0].second.Url()));
}
class HeaderContentRendererClient : public ContentRendererClient {
bool IsExcludedHeaderForServiceWorkerFetchEvent(
const std::string& header_name) override {
return header_name == "x-bye-bye";
}
};
TEST_F(ServiceWorkerContextClientTest, DispatchFetchEvent_Headers) {
HeaderContentRendererClient header_client;
auto* old_client = SetRendererClientForTesting(&header_client);
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
context_client->DidEvaluateScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
const GURL expected_url("https://example.com/expected");
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url;
request->headers.emplace("x-bye-bye", "excluded");
request->headers.emplace("x-hi-hi", "present");
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
pipes.service_worker->DispatchFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
ASSERT_EQ(1u, mock_proxy.fetch_events().size());
const blink::WebServiceWorkerRequest& received_request =
mock_proxy.fetch_events()[0].second;
blink::FetchAPIRequestHeadersMap header_map;
GetServiceWorkerHeaderMapFromWebRequest(received_request, &header_map);
EXPECT_EQ(expected_url, static_cast<GURL>(received_request.Url()));
EXPECT_TRUE(header_map.find(std::string("x-bye-bye")) == header_map.end());
auto iter = header_map.find(std::string("x-hi-hi"));
ASSERT_TRUE(iter != header_map.end());
EXPECT_EQ("present", iter->second);
SetRendererClientForTesting(old_client);
}
TEST_F(ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_NotRequestedTermination) {
EnableServicification();
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
context_client->DidEvaluateScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
bool is_idle = false;
auto timer = std::make_unique<ServiceWorkerTimeoutTimer>(
CreateCallbackWithCalledFlag(&is_idle),
task_runner()->GetMockTickClock());
context_client->SetTimeoutTimerForTesting(std::move(timer));
// The dispatched fetch event should be recorded by |mock_proxy|.
const GURL expected_url("https://example.com/expected");
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url;
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
context_client->DispatchOrQueueFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
EXPECT_FALSE(context_client->RequestedTermination());
ASSERT_EQ(1u, mock_proxy.fetch_events().size());
EXPECT_EQ(expected_url,
static_cast<GURL>(mock_proxy.fetch_events()[0].second.Url()));
}
TEST_F(ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_RequestedTerminationAndDie) {
EnableServicification();
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
context_client->DidEvaluateScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
bool is_idle = false;
auto timer = std::make_unique<ServiceWorkerTimeoutTimer>(
CreateCallbackWithCalledFlag(&is_idle),
task_runner()->GetMockTickClock());
context_client->SetTimeoutTimerForTesting(std::move(timer));
// Ensure the idle state.
EXPECT_FALSE(context_client->RequestedTermination());
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kIdleDelay +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_TRUE(context_client->RequestedTermination());
const GURL expected_url("https://example.com/expected");
// FetchEvent dispatched directly from the controlled clients through
// blink::mojom::ControllerServiceWorker should be queued in the idle state.
{
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url;
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
pipes.controller->DispatchFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
}
EXPECT_TRUE(mock_proxy.fetch_events().empty());
// Destruction of |context_client| should not hit any DCHECKs.
context_client.reset();
}
TEST_F(ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_RequestedTerminationAndWakeUp) {
EnableServicification();
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes, &mock_proxy);
context_client->DidEvaluateScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
bool is_idle = false;
auto timer = std::make_unique<ServiceWorkerTimeoutTimer>(
CreateCallbackWithCalledFlag(&is_idle),
task_runner()->GetMockTickClock());
context_client->SetTimeoutTimerForTesting(std::move(timer));
// Ensure the idle state.
EXPECT_FALSE(context_client->RequestedTermination());
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kIdleDelay +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_TRUE(context_client->RequestedTermination());
const GURL expected_url_1("https://example.com/expected_1");
const GURL expected_url_2("https://example.com/expected_2");
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request_1;
blink::mojom::ServiceWorkerFetchResponseCallbackRequest
fetch_callback_request_2;
// FetchEvent dispatched directly from the controlled clients through
// blink::mojom::ControllerServiceWorker should be queued in the idle state.
{
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request_1 = mojo::MakeRequest(&fetch_callback_ptr);
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url_1;
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
pipes.controller->DispatchFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
}
EXPECT_TRUE(mock_proxy.fetch_events().empty());
// Another event dispatched to blink::mojom::ServiceWorker wakes up
// the context client.
{
blink::mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request_2 = mojo::MakeRequest(&fetch_callback_ptr);
auto request = blink::mojom::FetchAPIRequest::New();
request->url = expected_url_2;
auto params = blink::mojom::DispatchFetchEventParams::New();
params->request = std::move(request);
pipes.service_worker->DispatchFetchEvent(
std::move(params), std::move(fetch_callback_ptr),
base::BindOnce([](blink::mojom::ServiceWorkerEventStatus) {}));
task_runner()->RunUntilIdle();
}
EXPECT_FALSE(context_client->RequestedTermination());
// All events should fire. The order of events should be kept.
ASSERT_EQ(2u, mock_proxy.fetch_events().size());
EXPECT_EQ(expected_url_1,
static_cast<GURL>(mock_proxy.fetch_events()[0].second.Url()));
EXPECT_EQ(expected_url_2,
static_cast<GURL>(mock_proxy.fetch_events()[1].second.Url()));
}
} // namespace content