Commit 1968460e authored by Makoto Shimazu's avatar Makoto Shimazu Committed by Commit Bot

S13nServiceWorker: postpone fetch events for subresources when idle

This patch implements a part of idle timer's logic. Fetch events coming through
mojom::ControllerServiceWorker after the worker has requested termination to the
browser should be queued until the worker receives the next event or StopWorker
message from the browser. If the worker receives the next event from the
browser, the queued events should run before the event from the browser,
otherwise the order of the events would be messed up. If the worker receives
StopWorker message instead, the worker shouldn't invoke the callback for the
fetch event, and silently should disconnect the pipe to the clients.

Bug: 774374
Change-Id: I6f3c90405ff037fe07df0310e2234cb81cb2ead9
Reviewed-on: https://chromium-review.googlesource.com/816458
Commit-Queue: Makoto Shimazu <shimazu@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#523677}
parent 7dd7d94a
...@@ -29,9 +29,9 @@ void ControllerServiceWorkerImpl::DispatchFetchEvent( ...@@ -29,9 +29,9 @@ void ControllerServiceWorkerImpl::DispatchFetchEvent(
mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
DispatchFetchEventCallback callback) { DispatchFetchEventCallback callback) {
DCHECK(context_client_); DCHECK(context_client_);
context_client_->DispatchFetchEvent(request, nullptr /* preload_handle */, context_client_->DispatchOrQueueFetchEvent(
std::move(response_callback), request, nullptr /* preload_handle */, std::move(response_callback),
std::move(callback)); std::move(callback));
} }
} // namespace content } // namespace content
...@@ -448,14 +448,18 @@ struct ServiceWorkerContextClient::WorkerContextData { ...@@ -448,14 +448,18 @@ struct ServiceWorkerContextClient::WorkerContextData {
// Inflight navigation preload requests. // Inflight navigation preload requests.
base::IDMap<std::unique_ptr<NavigationPreloadRequest>> preload_requests; base::IDMap<std::unique_ptr<NavigationPreloadRequest>> preload_requests;
// S13nServiceWorker
std::unique_ptr<ControllerServiceWorkerImpl> controller_impl;
// S13nServiceWorker // S13nServiceWorker
// Timer triggered when the service worker considers it should be stopped or // Timer triggered when the service worker considers it should be stopped or
// an event should be aborted. // an event should be aborted.
std::unique_ptr<ServiceWorkerTimeoutTimer> timeout_timer; std::unique_ptr<ServiceWorkerTimeoutTimer> timeout_timer;
// S13nServiceWorker
// |controller_impl| should be destroyed before |timeout_timer| since the
// pipe needs to be disconnected before callbacks passed by
// DispatchSomeEvent() get destructed, which may be stored in |timeout_timer|
// as parameters of pending tasks added after a termination request.
std::unique_ptr<ControllerServiceWorkerImpl> controller_impl;
base::ThreadChecker thread_checker; base::ThreadChecker thread_checker;
base::WeakPtrFactory<ServiceWorkerContextClient> weak_factory; base::WeakPtrFactory<ServiceWorkerContextClient> weak_factory;
base::WeakPtrFactory<blink::WebServiceWorkerContextProxy> proxy_weak_factory; base::WeakPtrFactory<blink::WebServiceWorkerContextProxy> proxy_weak_factory;
...@@ -1267,6 +1271,22 @@ void ServiceWorkerContextClient::Claim( ...@@ -1267,6 +1271,22 @@ void ServiceWorkerContextClient::Claim(
base::Unretained(this), std::move(callbacks))); base::Unretained(this), std::move(callbacks)));
} }
void ServiceWorkerContextClient::DispatchOrQueueFetchEvent(
const ResourceRequest& request,
mojom::FetchEventPreloadHandlePtr preload_handle,
mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
DispatchFetchEventCallback callback) {
if (RequestedTermination()) {
context_->timeout_timer->PushPendingTask(
base::BindOnce(&ServiceWorkerContextClient::DispatchFetchEvent,
GetWeakPtr(), request, std::move(preload_handle),
std::move(response_callback), std::move(callback)));
return;
}
DispatchFetchEvent(request, std::move(preload_handle),
std::move(response_callback), std::move(callback));
}
void ServiceWorkerContextClient::DispatchSyncEvent( void ServiceWorkerContextClient::DispatchSyncEvent(
const std::string& tag, const std::string& tag,
blink::mojom::BackgroundSyncEventLastChance last_chance, blink::mojom::BackgroundSyncEventLastChance last_chance,
...@@ -1358,7 +1378,7 @@ void ServiceWorkerContextClient::SendWorkerStarted() { ...@@ -1358,7 +1378,7 @@ void ServiceWorkerContextClient::SendWorkerStarted() {
// Start the idle timer. // Start the idle timer.
context_->timeout_timer = context_->timeout_timer =
std::make_unique<ServiceWorkerTimeoutTimer>(base::BindRepeating( std::make_unique<ServiceWorkerTimeoutTimer>(base::BindRepeating(
&ServiceWorkerContextClient::OnIdle, base::Unretained(this))); &ServiceWorkerContextClient::OnIdleTimeout, base::Unretained(this)));
} }
void ServiceWorkerContextClient::DispatchActivateEvent( void ServiceWorkerContextClient::DispatchActivateEvent(
...@@ -1828,12 +1848,18 @@ void ServiceWorkerContextClient::SetupNavigationPreload( ...@@ -1828,12 +1848,18 @@ void ServiceWorkerContextClient::SetupNavigationPreload(
fetch_event_id); fetch_event_id);
} }
void ServiceWorkerContextClient::OnIdle() { void ServiceWorkerContextClient::OnIdleTimeout() {
// TODO(crbug.com/774374): Ignore events from clients after this point until // ServiceWorkerTimeoutTimer::did_idle_timeout() returns true if
// an event from the browser process or StopWorker() is received. // ServiceWorkerContextClient::OnIdleTiemout() has been called. It means
// termination has been requested.
DCHECK(RequestedTermination());
(*instance_host_)->RequestTermination(); (*instance_host_)->RequestTermination();
} }
bool ServiceWorkerContextClient::RequestedTermination() const {
return context_->timeout_timer->did_idle_timeout();
}
base::WeakPtr<ServiceWorkerContextClient> base::WeakPtr<ServiceWorkerContextClient>
ServiceWorkerContextClient::GetWeakPtr() { ServiceWorkerContextClient::GetWeakPtr() {
DCHECK(worker_task_runner_->RunsTasksInCurrentSequence()); DCHECK(worker_task_runner_->RunsTasksInCurrentSequence());
...@@ -1846,4 +1872,10 @@ void ServiceWorkerContextClient::ResetThreadSpecificInstanceForTesting() { ...@@ -1846,4 +1872,10 @@ void ServiceWorkerContextClient::ResetThreadSpecificInstanceForTesting() {
g_worker_client_tls.Pointer()->Set(nullptr); g_worker_client_tls.Pointer()->Set(nullptr);
} }
void ServiceWorkerContextClient::SetTimeoutTimerForTesting(
std::unique_ptr<ServiceWorkerTimeoutTimer> timeout_timer) {
DCHECK(context_);
context_->timeout_timer = std::move(timeout_timer);
}
} // namespace content } // namespace content
...@@ -61,10 +61,11 @@ namespace content { ...@@ -61,10 +61,11 @@ namespace content {
struct PlatformNotificationData; struct PlatformNotificationData;
struct PushEventPayload; struct PushEventPayload;
struct ServiceWorkerClientInfo; struct ServiceWorkerClientInfo;
class EmbeddedWorkerInstanceClientImpl;
class ServiceWorkerNetworkProvider; class ServiceWorkerNetworkProvider;
class ServiceWorkerProviderContext; class ServiceWorkerProviderContext;
class ServiceWorkerTimeoutTimer;
class ThreadSafeSender; class ThreadSafeSender;
class EmbeddedWorkerInstanceClientImpl;
class WebWorkerFetchContext; class WebWorkerFetchContext;
// ServiceWorkerContextClient is a "client" of a service worker execution // ServiceWorkerContextClient is a "client" of a service worker execution
...@@ -252,11 +253,33 @@ class CONTENT_EXPORT ServiceWorkerContextClient ...@@ -252,11 +253,33 @@ class CONTENT_EXPORT ServiceWorkerContextClient
void Claim(std::unique_ptr<blink::WebServiceWorkerClientsClaimCallbacks> void Claim(std::unique_ptr<blink::WebServiceWorkerClientsClaimCallbacks>
callbacks) override; callbacks) override;
// Dispatches the fetch event if the worker is running normally, and queues it
// instead if the worker has already requested to be terminated by the
// browser. If queued, the event will be dispatched once the worker resumes
// normal operation (if the browser decides not to terminate it, and instead
// starts another event), or else is dropped if the worker is terminated.
//
// This method needs to be used only if the event comes directly from a
// client, which means it is coming through the ControllerServiceWorkerImpl.
void DispatchOrQueueFetchEvent(
const ResourceRequest& request,
mojom::FetchEventPreloadHandlePtr preload_handle,
mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
DispatchFetchEventCallback callback);
private: private:
struct WorkerContextData; struct WorkerContextData;
class NavigationPreloadRequest; class NavigationPreloadRequest;
friend class ControllerServiceWorkerImpl; friend class ControllerServiceWorkerImpl;
friend class ServiceWorkerContextClientTest; friend class ServiceWorkerContextClientTest;
FRIEND_TEST_ALL_PREFIXES(
ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_RequestedTerminationAndDie);
FRIEND_TEST_ALL_PREFIXES(
ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_RequestedTerminationAndWakeUp);
FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_NotRequestedTermination);
// Get routing_id for sending message to the ServiceWorkerVersion // Get routing_id for sending message to the ServiceWorkerVersion
// in the browser process. // in the browser process.
...@@ -380,12 +403,19 @@ class CONTENT_EXPORT ServiceWorkerContextClient ...@@ -380,12 +403,19 @@ class CONTENT_EXPORT ServiceWorkerContextClient
const GURL& url, const GURL& url,
mojom::FetchEventPreloadHandlePtr preload_handle); mojom::FetchEventPreloadHandlePtr preload_handle);
// Called when a certain time has passed since the last task finished. // Called by ServiceWorkerTimeoutTimer when a certain time has passed since
void OnIdle(); // the last task finished.
void OnIdleTimeout();
// Returns true if the worker has requested to be terminated by the browser
// process. It does this due to idle timeout.
bool RequestedTermination() const;
base::WeakPtr<ServiceWorkerContextClient> GetWeakPtr(); base::WeakPtr<ServiceWorkerContextClient> GetWeakPtr();
static void ResetThreadSpecificInstanceForTesting(); static void ResetThreadSpecificInstanceForTesting();
void SetTimeoutTimerForTesting(
std::unique_ptr<ServiceWorkerTimeoutTimer> timeout_timer);
const int embedded_worker_id_; const int embedded_worker_id_;
const int64_t service_worker_version_id_; const int64_t service_worker_version_id_;
......
...@@ -4,11 +4,22 @@ ...@@ -4,11 +4,22 @@
#include "content/renderer/service_worker/service_worker_context_client.h" #include "content/renderer/service_worker/service_worker_context_client.h"
#include <utility>
#include <vector>
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/run_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/child/thread_safe_sender.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/common/resource_request.h"
#include "content/renderer/service_worker/embedded_worker_instance_client_impl.h" #include "content/renderer/service_worker/embedded_worker_instance_client_impl.h"
#include "content/renderer/service_worker/service_worker_dispatcher.h" #include "content/renderer/service_worker/service_worker_dispatcher.h"
#include "content/renderer/service_worker/service_worker_timeout_timer.h"
#include "content/renderer/worker_thread_registry.h" #include "content/renderer/worker_thread_registry.h"
#include "ipc/ipc_sync_message_filter.h" #include "ipc/ipc_sync_message_filter.h"
#include "ipc/ipc_test_sink.h" #include "ipc/ipc_test_sink.h"
...@@ -72,6 +83,11 @@ class MockWebServiceWorkerContextProxy ...@@ -72,6 +83,11 @@ class MockWebServiceWorkerContextProxy
registration_handle_ = std::move(handle); registration_handle_ = std::move(handle);
} }
bool HasFetchEventHandler() override { return false; } 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 DispatchActivateEvent(int event_id) override { NOTREACHED(); }
void DispatchBackgroundFetchAbortEvent( void DispatchBackgroundFetchAbortEvent(
...@@ -116,11 +132,6 @@ class MockWebServiceWorkerContextProxy ...@@ -116,11 +132,6 @@ class MockWebServiceWorkerContextProxy
NOTREACHED(); NOTREACHED();
} }
void DispatchInstallEvent(int event_id) override { NOTREACHED(); } void DispatchInstallEvent(int event_id) override { NOTREACHED(); }
void DispatchFetchEvent(int fetch_event_id,
const blink::WebServiceWorkerRequest& web_request,
bool navigation_preload_sent) override {
NOTREACHED();
}
void DispatchNotificationClickEvent(int event_id, void DispatchNotificationClickEvent(int event_id,
const blink::WebString& notification_id, const blink::WebString& notification_id,
const blink::WebNotificationData&, const blink::WebNotificationData&,
...@@ -172,11 +183,24 @@ class MockWebServiceWorkerContextProxy ...@@ -172,11 +183,24 @@ class MockWebServiceWorkerContextProxy
NOTREACHED(); NOTREACHED();
} }
const std::vector<
std::pair<int /* event_id */, blink::WebServiceWorkerRequest>>&
fetch_events() const {
return fetch_events_;
}
private: private:
std::unique_ptr<blink::WebServiceWorkerRegistration::Handle> std::unique_ptr<blink::WebServiceWorkerRegistration::Handle>
registration_handle_; registration_handle_;
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);
}
} // namespace } // namespace
class ServiceWorkerContextClientTest : public testing::Test { class ServiceWorkerContextClientTest : public testing::Test {
...@@ -185,6 +209,8 @@ class ServiceWorkerContextClientTest : public testing::Test { ...@@ -185,6 +209,8 @@ class ServiceWorkerContextClientTest : public testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
message_loop_.SetTaskRunner(task_runner_);
sender_ = base::MakeRefCounted<TestSender>(&ipc_sink_); sender_ = base::MakeRefCounted<TestSender>(&ipc_sink_);
// Use this thread as the worker thread. // Use this thread as the worker thread.
WorkerThreadRegistry::Instance()->DidStartCurrentWorkerThread(); WorkerThreadRegistry::Instance()->DidStartCurrentWorkerThread();
...@@ -199,6 +225,12 @@ class ServiceWorkerContextClientTest : public testing::Test { ...@@ -199,6 +225,12 @@ class ServiceWorkerContextClientTest : public testing::Test {
WorkerThreadRegistry::Instance()->WillStopCurrentWorkerThread(); WorkerThreadRegistry::Instance()->WillStopCurrentWorkerThread();
} }
void EnableServicification() {
feature_list_.InitWithFeatures(
{features::kBrowserSideNavigation, features::kNetworkService}, {});
ASSERT_TRUE(ServiceWorkerUtils::IsServicificationEnabled());
}
// Creates an empty struct to initialize ServiceWorkerProviderContext. // Creates an empty struct to initialize ServiceWorkerProviderContext.
mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfo( mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfo(
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedRequest* blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedRequest*
...@@ -246,18 +278,24 @@ class ServiceWorkerContextClientTest : public testing::Test { ...@@ -246,18 +278,24 @@ class ServiceWorkerContextClientTest : public testing::Test {
nullptr /* embedded_worker_client */, sender_, io_task_runner()); nullptr /* embedded_worker_client */, sender_, io_task_runner());
} }
scoped_refptr<base::TestMockTimeTaskRunner> task_runner() const {
return task_runner_;
}
private: private:
scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() { scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() {
// Use this thread as the main thread. // Use this thread as the main thread.
return message_loop_.task_runner(); return task_runner_;
} }
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner() { scoped_refptr<base::SingleThreadTaskRunner> io_task_runner() {
// Use this thread as the IO thread. // Use this thread as the IO thread.
return message_loop_.task_runner(); return task_runner_;
} }
base::MessageLoop message_loop_; base::MessageLoop message_loop_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::test::ScopedFeatureList feature_list_;
IPC::TestSink ipc_sink_; IPC::TestSink ipc_sink_;
scoped_refptr<TestSender> sender_; scoped_refptr<TestSender> sender_;
}; };
...@@ -269,10 +307,188 @@ TEST_F(ServiceWorkerContextClientTest, Ping) { ...@@ -269,10 +307,188 @@ TEST_F(ServiceWorkerContextClientTest, Ping) {
MockWebServiceWorkerContextProxy mock_proxy; MockWebServiceWorkerContextProxy mock_proxy;
context_client->WorkerContextStarted(&mock_proxy); context_client->WorkerContextStarted(&mock_proxy);
// Ping() should invoke the passed callback. bool is_called = false;
base::RunLoop loop; pipes.event_dispatcher->Ping(CreateCallbackWithCalledFlag(&is_called));
pipes.event_dispatcher->Ping(loop.QuitClosure()); task_runner()->RunUntilIdle();
loop.Run(); EXPECT_TRUE(is_called);
}
TEST_F(ServiceWorkerContextClientTest, DispatchFetchEvent) {
ContextClientPipes pipes;
MockWebServiceWorkerContextProxy mock_proxy;
std::unique_ptr<ServiceWorkerContextClient> context_client;
context_client = CreateContextClient(&pipes);
context_client->WorkerContextStarted(&mock_proxy);
context_client->DidEvaluateWorkerScript(true /* success */);
task_runner()->RunUntilIdle();
EXPECT_TRUE(mock_proxy.fetch_events().empty());
const GURL expected_url("https://example.com/expected");
mojom::ServiceWorkerFetchResponseCallbackRequest fetch_callback_request;
auto request = std::make_unique<ResourceRequest>();
request->url = expected_url;
mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
pipes.event_dispatcher->DispatchFetchEvent(
*request, nullptr /* preload_handle */, std::move(fetch_callback_ptr),
base::BindOnce(
[](blink::mojom::ServiceWorkerEventStatus, base::Time) {}));
task_runner()->RunUntilIdle();
ASSERT_EQ(1u, mock_proxy.fetch_events().size());
EXPECT_EQ(request->url,
static_cast<GURL>(mock_proxy.fetch_events()[0].second.Url()));
}
TEST_F(ServiceWorkerContextClientTest,
DispatchOrQueueFetchEvent_NotRequestedTermination) {
EnableServicification();
ContextClientPipes pipes;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes);
MockWebServiceWorkerContextProxy mock_proxy;
context_client->WorkerContextStarted(&mock_proxy);
context_client->DidEvaluateWorkerScript(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");
mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
mojom::ServiceWorkerFetchResponseCallbackRequest fetch_callback_request =
mojo::MakeRequest(&fetch_callback_ptr);
auto request = std::make_unique<ResourceRequest>();
request->url = expected_url;
context_client->DispatchOrQueueFetchEvent(
*request, nullptr /* preload_handle */, std::move(fetch_callback_ptr),
base::BindOnce(
[](blink::mojom::ServiceWorkerEventStatus, base::Time) {}));
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;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes);
MockWebServiceWorkerContextProxy mock_proxy;
context_client->WorkerContextStarted(&mock_proxy);
context_client->DidEvaluateWorkerScript(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");
mojom::ServiceWorkerFetchResponseCallbackRequest fetch_callback_request;
// FetchEvent dispatched directly from the controlled clients through
// mojom::ControllerServiceWorker should be queued in the idle state.
{
mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request = mojo::MakeRequest(&fetch_callback_ptr);
auto request = std::make_unique<ResourceRequest>();
request->url = expected_url;
pipes.controller->DispatchFetchEvent(
*request, std::move(fetch_callback_ptr),
base::BindOnce(
[](blink::mojom::ServiceWorkerEventStatus, base::Time) {}));
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;
std::unique_ptr<ServiceWorkerContextClient> context_client =
CreateContextClient(&pipes);
MockWebServiceWorkerContextProxy mock_proxy;
context_client->WorkerContextStarted(&mock_proxy);
context_client->DidEvaluateWorkerScript(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");
mojom::ServiceWorkerFetchResponseCallbackRequest fetch_callback_request_1;
mojom::ServiceWorkerFetchResponseCallbackRequest fetch_callback_request_2;
// FetchEvent dispatched directly from the controlled clients through
// mojom::ControllerServiceWorker should be queued in the idle state.
{
mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request_1 = mojo::MakeRequest(&fetch_callback_ptr);
auto request = std::make_unique<ResourceRequest>();
request->url = expected_url_1;
pipes.controller->DispatchFetchEvent(
*request, std::move(fetch_callback_ptr),
base::BindOnce(
[](blink::mojom::ServiceWorkerEventStatus, base::Time) {}));
task_runner()->RunUntilIdle();
}
EXPECT_TRUE(mock_proxy.fetch_events().empty());
// Another event dispatched to mojom::ServiceWorkerEventDispatcher wakes up
// the context client.
{
mojom::ServiceWorkerFetchResponseCallbackPtr fetch_callback_ptr;
fetch_callback_request_2 = mojo::MakeRequest(&fetch_callback_ptr);
auto request = std::make_unique<ResourceRequest>();
request->url = expected_url_2;
pipes.event_dispatcher->DispatchFetchEvent(
*request, nullptr /* preload_handle */, std::move(fetch_callback_ptr),
base::BindOnce(
[](blink::mojom::ServiceWorkerEventStatus, base::Time) {}));
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 } // namespace content
...@@ -53,6 +53,15 @@ ServiceWorkerTimeoutTimer::~ServiceWorkerTimeoutTimer() { ...@@ -53,6 +53,15 @@ ServiceWorkerTimeoutTimer::~ServiceWorkerTimeoutTimer() {
int ServiceWorkerTimeoutTimer::StartEvent( int ServiceWorkerTimeoutTimer::StartEvent(
base::OnceCallback<void(int /* event_id */)> abort_callback) { base::OnceCallback<void(int /* event_id */)> abort_callback) {
if (did_idle_timeout()) {
idle_time_ = base::TimeTicks();
did_idle_timeout_ = false;
while (!pending_tasks_.empty()) {
std::move(pending_tasks_.front()).Run();
pending_tasks_.pop();
}
}
idle_time_ = base::TimeTicks(); idle_time_ = base::TimeTicks();
const int event_id = NextEventId(); const int event_id = NextEventId();
std::set<EventInfo>::iterator iter; std::set<EventInfo>::iterator iter;
...@@ -74,6 +83,13 @@ void ServiceWorkerTimeoutTimer::EndEvent(int event_id) { ...@@ -74,6 +83,13 @@ void ServiceWorkerTimeoutTimer::EndEvent(int event_id) {
idle_time_ = tick_clock_->NowTicks() + kIdleDelay; idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
} }
void ServiceWorkerTimeoutTimer::PushPendingTask(
base::OnceClosure pending_task) {
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
DCHECK(did_idle_timeout());
pending_tasks_.emplace(std::move(pending_task));
}
void ServiceWorkerTimeoutTimer::UpdateStatus() { void ServiceWorkerTimeoutTimer::UpdateStatus() {
if (!ServiceWorkerUtils::IsServicificationEnabled()) if (!ServiceWorkerUtils::IsServicificationEnabled())
return; return;
...@@ -94,8 +110,10 @@ void ServiceWorkerTimeoutTimer::UpdateStatus() { ...@@ -94,8 +110,10 @@ void ServiceWorkerTimeoutTimer::UpdateStatus() {
if (inflight_events_.empty() && idle_time_.is_null()) if (inflight_events_.empty() && idle_time_.is_null())
idle_time_ = tick_clock_->NowTicks() + kIdleDelay; idle_time_ = tick_clock_->NowTicks() + kIdleDelay;
if (!idle_time_.is_null() && idle_time_ < now) if (!idle_time_.is_null() && idle_time_ < now) {
did_idle_timeout_ = true;
idle_callback_.Run(); idle_callback_.Run();
}
} }
ServiceWorkerTimeoutTimer::EventInfo::EventInfo( ServiceWorkerTimeoutTimer::EventInfo::EventInfo(
......
...@@ -50,11 +50,23 @@ class CONTENT_EXPORT ServiceWorkerTimeoutTimer { ...@@ -50,11 +50,23 @@ class CONTENT_EXPORT ServiceWorkerTimeoutTimer {
// StartEvent() should be called at the beginning of an event. It returns an // StartEvent() should be called at the beginning of an event. It returns an
// event id. The event id should be passed to EndEvent() when the event has // event id. The event id should be passed to EndEvent() when the event has
// finished. // finished. If there are pending tasks queued by PushPendingTask(), they will
// run in order synchronouslly in StartEvent().
// See the class comment to know when |abort_callback| runs. // See the class comment to know when |abort_callback| runs.
int StartEvent(base::OnceCallback<void(int /* event_id */)> abort_callback); int StartEvent(base::OnceCallback<void(int /* event_id */)> abort_callback);
void EndEvent(int event_id); void EndEvent(int event_id);
// Pushes a task which is expected to run after any event starts again to a
// pending task queue. The tasks will run at the next StartEvent() call.
// PushPendingTask() should be called if the idle timeout occurred
// (did_idle_timeout() returns true).
void PushPendingTask(base::OnceClosure pending_task);
// Returns true if the timer thinks no events ran for a while, and has
// triggered the |idle_callback| passed to the constructor. It'll be reset to
// false again when StartEvent() is called.
bool did_idle_timeout() const { return did_idle_timeout_; }
// Idle timeout duration since the last event has finished. // Idle timeout duration since the last event has finished.
static constexpr base::TimeDelta kIdleDelay = static constexpr base::TimeDelta kIdleDelay =
base::TimeDelta::FromSeconds(30); base::TimeDelta::FromSeconds(30);
...@@ -99,6 +111,14 @@ class CONTENT_EXPORT ServiceWorkerTimeoutTimer { ...@@ -99,6 +111,14 @@ class CONTENT_EXPORT ServiceWorkerTimeoutTimer {
// |idle_time_|. // |idle_time_|.
base::RepeatingClosure idle_callback_; base::RepeatingClosure idle_callback_;
// Set to true once |idle_callback_| has been invoked. Set to false when
// StartEvent() is called.
bool did_idle_timeout_ = false;
// Tasks waiting for the timer getting the next request to start an event
// by StartEvent().
base::queue<base::OnceClosure> pending_tasks_;
// |timer_| invokes UpdateEventStatus() periodically. // |timer_| invokes UpdateEventStatus() periodically.
base::RepeatingTimer timer_; base::RepeatingTimer timer_;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "content/renderer/service_worker/service_worker_timeout_timer.h" #include "content/renderer/service_worker/service_worker_timeout_timer.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h" #include "base/test/test_mock_time_task_runner.h"
...@@ -19,9 +20,11 @@ namespace { ...@@ -19,9 +20,11 @@ namespace {
class MockEvent { class MockEvent {
public: public:
MockEvent() : weak_factory_(this) {}
base::OnceCallback<void(int)> CreateAbortCallback() { base::OnceCallback<void(int)> CreateAbortCallback() {
EXPECT_FALSE(has_aborted_); EXPECT_FALSE(has_aborted_);
return base::BindOnce(&MockEvent::Abort, base::Unretained(this)); return base::BindOnce(&MockEvent::Abort, weak_factory_.GetWeakPtr());
} }
int event_id() const { return event_id_; } int event_id() const { return event_id_; }
...@@ -36,8 +39,14 @@ class MockEvent { ...@@ -36,8 +39,14 @@ class MockEvent {
bool has_aborted_ = false; bool has_aborted_ = false;
int event_id_ = 0; int event_id_ = 0;
base::WeakPtrFactory<MockEvent> weak_factory_;
}; };
base::RepeatingClosure CreateReceiverWithCalledFlag(bool* out_is_called) {
return base::BindRepeating([](bool* out_is_called) { *out_is_called = true; },
out_is_called);
}
} // namespace } // namespace
class ServiceWorkerTimeoutTimerTest : public testing::Test { class ServiceWorkerTimeoutTimerTest : public testing::Test {
...@@ -74,10 +83,8 @@ TEST_F(ServiceWorkerTimeoutTimerTest, IdleTimer) { ...@@ -74,10 +83,8 @@ TEST_F(ServiceWorkerTimeoutTimerTest, IdleTimer) {
base::BindRepeating([](int) {}); base::BindRepeating([](int) {});
bool is_idle = false; bool is_idle = false;
ServiceWorkerTimeoutTimer timer( ServiceWorkerTimeoutTimer timer(CreateReceiverWithCalledFlag(&is_idle),
base::BindRepeating([](bool* out_is_idle) { *out_is_idle = true; }, task_runner()->GetMockTickClock());
&is_idle),
task_runner()->GetMockTickClock());
task_runner()->FastForwardBy(kIdleInterval); task_runner()->FastForwardBy(kIdleInterval);
// |idle_callback| should be fired since there is no event. // |idle_callback| should be fired since there is no event.
EXPECT_TRUE(is_idle); EXPECT_TRUE(is_idle);
...@@ -132,10 +139,8 @@ TEST_F(ServiceWorkerTimeoutTimerTest, BecomeIdleAfterAbort) { ...@@ -132,10 +139,8 @@ TEST_F(ServiceWorkerTimeoutTimerTest, BecomeIdleAfterAbort) {
EnableServicification(); EnableServicification();
bool is_idle = false; bool is_idle = false;
ServiceWorkerTimeoutTimer timer( ServiceWorkerTimeoutTimer timer(CreateReceiverWithCalledFlag(&is_idle),
base::BindRepeating([](bool* out_is_idle) { *out_is_idle = true; }, task_runner()->GetMockTickClock());
&is_idle),
task_runner()->GetMockTickClock());
MockEvent event; MockEvent event;
int event_id = timer.StartEvent(event.CreateAbortCallback()); int event_id = timer.StartEvent(event.CreateAbortCallback());
...@@ -175,6 +180,26 @@ TEST_F(ServiceWorkerTimeoutTimerTest, AbortAllOnDestruction) { ...@@ -175,6 +180,26 @@ TEST_F(ServiceWorkerTimeoutTimerTest, AbortAllOnDestruction) {
EXPECT_TRUE(event2.has_aborted()); EXPECT_TRUE(event2.has_aborted());
} }
TEST_F(ServiceWorkerTimeoutTimerTest, PushPendingTask) {
EnableServicification();
ServiceWorkerTimeoutTimer timer(base::BindRepeating(&base::DoNothing),
task_runner()->GetMockTickClock());
task_runner()->FastForwardBy(ServiceWorkerTimeoutTimer::kIdleDelay +
ServiceWorkerTimeoutTimer::kUpdateInterval +
base::TimeDelta::FromSeconds(1));
EXPECT_TRUE(timer.did_idle_timeout());
bool did_task_run = false;
timer.PushPendingTask(CreateReceiverWithCalledFlag(&did_task_run));
// Start a new event. StartEvent() should run the pending tasks.
MockEvent event;
const int event_id = timer.StartEvent(event.CreateAbortCallback());
event.set_event_id(event_id);
EXPECT_FALSE(timer.did_idle_timeout());
EXPECT_TRUE(did_task_run);
}
TEST_F(ServiceWorkerTimeoutTimerTest, NonS13nServiceWorker) { TEST_F(ServiceWorkerTimeoutTimerTest, NonS13nServiceWorker) {
ASSERT_FALSE(ServiceWorkerUtils::IsServicificationEnabled()); ASSERT_FALSE(ServiceWorkerUtils::IsServicificationEnabled());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment