Commit 80351342 authored by Eugene Ostroukhov's avatar Eugene Ostroukhov Committed by Commit Bot

DevTools: emulate offline mode for SW sync API

This change introduces a way for the DevTools to emulate offline mode
for the service worker background sync API. Notifications are queued up
and dispatched once the service worker comes online.

Bug: 625798
Change-Id: Ie804b3699991b9b4cbc7035be8e1f3e03fb39596
Reviewed-on: https://chromium-review.googlesource.com/956099
Commit-Queue: Eugene Ostroukhov <eostroukhov@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarJosh Karlin <jkarlin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550844}
parent 10a4e34c
......@@ -279,10 +279,30 @@ void BackgroundSyncManager::EmulateDispatchSyncEvent(
bool last_chance,
ServiceWorkerVersion::StatusCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ServiceWorkerStatusCode code = CanEmulateSyncEvent(active_version);
if (code != SERVICE_WORKER_OK) {
std::move(callback).Run(code);
return;
}
DispatchSyncEvent(tag, std::move(active_version), last_chance,
std::move(callback));
}
void BackgroundSyncManager::EmulateServiceWorkerOffline(
int64_t service_worker_id,
bool is_offline) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Multiple DevTools sessions may want to set the same SW offline, which
// is supposed to disable the background sync. For consistency with the
// network stack, SW remains offline until all DevTools sessions disable
// the offline mode.
emulated_offline_sw_[service_worker_id] += is_offline ? 1 : -1;
if (emulated_offline_sw_[service_worker_id] > 0)
return;
emulated_offline_sw_.erase(service_worker_id);
FireReadyEvents();
}
BackgroundSyncManager::BackgroundSyncManager(
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
: op_scheduler_(CacheStorageSchedulerClient::CLIENT_BACKGROUND_SYNC),
......@@ -845,7 +865,8 @@ bool BackgroundSyncManager::AreOptionConditionsMet(
}
bool BackgroundSyncManager::IsRegistrationReadyToFire(
const BackgroundSyncRegistration& registration) {
const BackgroundSyncRegistration& registration,
int64_t service_worker_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (registration.sync_state() != blink::mojom::BackgroundSyncState::PENDING)
......@@ -854,6 +875,9 @@ bool BackgroundSyncManager::IsRegistrationReadyToFire(
if (clock_->Now() < registration.delay_until())
return false;
if (base::ContainsKey(emulated_offline_sw_, service_worker_id))
return false;
return AreOptionConditionsMet(*registration.options());
}
......@@ -933,7 +957,8 @@ void BackgroundSyncManager::FireReadyEventsImpl(base::OnceClosure callback) {
for (auto& key_and_registration :
sw_id_and_registrations.second.registration_map) {
BackgroundSyncRegistration* registration = &key_and_registration.second;
if (IsRegistrationReadyToFire(*registration)) {
if (IsRegistrationReadyToFire(*registration, service_worker_id)) {
sw_id_and_tags_to_fire.push_back(
std::make_pair(service_worker_id, key_and_registration.first));
// The state change is not saved to persistent storage because
......@@ -1202,4 +1227,16 @@ base::OnceClosure BackgroundSyncManager::MakeEmptyCompletion() {
return op_scheduler_.WrapCallbackToRunNext(base::DoNothing::Once());
}
ServiceWorkerStatusCode BackgroundSyncManager::CanEmulateSyncEvent(
scoped_refptr<ServiceWorkerVersion> active_version) {
if (!active_version)
return SERVICE_WORKER_ERROR_FAILED;
if (!network_observer_->NetworkSufficient(NETWORK_STATE_ONLINE))
return SERVICE_WORKER_ERROR_NETWORK;
int64_t registration_id = active_version->registration_id();
if (base::ContainsKey(emulated_offline_sw_, registration_id))
return SERVICE_WORKER_ERROR_NETWORK;
return SERVICE_WORKER_OK;
}
} // namespace content
......@@ -107,6 +107,9 @@ class CONTENT_EXPORT BackgroundSyncManager
bool last_chance,
ServiceWorkerVersion::StatusCallback callback);
// Called from DevTools to toggle service worker "offline" status
void EmulateServiceWorkerOffline(int64_t service_worker_id, bool is_offline);
protected:
explicit BackgroundSyncManager(
scoped_refptr<ServiceWorkerContextWrapper> context);
......@@ -223,8 +226,8 @@ class CONTENT_EXPORT BackgroundSyncManager
StatusAndRegistrationsCallback callback);
bool AreOptionConditionsMet(const BackgroundSyncRegistrationOptions& options);
bool IsRegistrationReadyToFire(
const BackgroundSyncRegistration& registration);
bool IsRegistrationReadyToFire(const BackgroundSyncRegistration& registration,
int64_t service_worker_id);
// Determines if the browser needs to be able to run in the background (e.g.,
// to run a pending registration or verify that a firing registration
......@@ -282,6 +285,9 @@ class CONTENT_EXPORT BackgroundSyncManager
base::OnceClosure MakeEmptyCompletion();
ServiceWorkerStatusCode CanEmulateSyncEvent(
scoped_refptr<ServiceWorkerVersion> active_version);
SWIdToRegistrationsMap active_registrations_;
CacheStorageScheduler op_scheduler_;
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
......@@ -300,6 +306,8 @@ class CONTENT_EXPORT BackgroundSyncManager
base::Clock* clock_;
std::map<int64_t, int> emulated_offline_sw_;
base::WeakPtrFactory<BackgroundSyncManager> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(BackgroundSyncManager);
......
......@@ -392,7 +392,7 @@ class BackgroundSyncManagerTest : public testing::Test {
EXPECT_TRUE(Register(sync_options));
EXPECT_EQ(sync_events_called + 1, sync_events_called_);
EXPECT_TRUE(GetRegistration(sync_options_1_));
EXPECT_TRUE(GetRegistration(sync_options));
EXPECT_TRUE(sync_fired_callback_);
}
......@@ -1348,4 +1348,114 @@ TEST_F(BackgroundSyncManagerTest, LastChance) {
EXPECT_TRUE(test_background_sync_manager_->last_chance());
}
TEST_F(BackgroundSyncManagerTest, EmulateOfflineSingleClient) {
InitSyncEventTest();
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
true);
EXPECT_TRUE(Register(sync_options_1_));
EXPECT_EQ(0, sync_events_called_);
EXPECT_TRUE(GetRegistration(sync_options_1_));
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
false);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, sync_events_called_);
EXPECT_FALSE(GetRegistration(sync_options_1_));
EXPECT_TRUE(Register(sync_options_2_));
EXPECT_EQ(2, sync_events_called_);
EXPECT_FALSE(GetRegistration(sync_options_2_));
}
TEST_F(BackgroundSyncManagerTest, EmulateOfflineMultipleClients) {
InitSyncEventTest();
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
true);
EXPECT_TRUE(Register(sync_options_1_));
EXPECT_EQ(0, sync_events_called_);
EXPECT_TRUE(GetRegistration(sync_options_1_));
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
true);
EXPECT_TRUE(Register(sync_options_2_));
EXPECT_EQ(0, sync_events_called_);
EXPECT_TRUE(GetRegistration(sync_options_2_));
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
false);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, sync_events_called_);
EXPECT_TRUE(GetRegistration(sync_options_1_));
EXPECT_TRUE(GetRegistration(sync_options_2_));
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
false);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, sync_events_called_);
EXPECT_FALSE(GetRegistration(sync_options_1_));
EXPECT_FALSE(GetRegistration(sync_options_2_));
}
static void EmulateDispatchSyncEventCallback(
bool* was_called,
ServiceWorkerStatusCode* code,
ServiceWorkerStatusCode status_code) {
*was_called = true;
*code = status_code;
}
TEST_F(BackgroundSyncManagerTest, EmulateDispatchSyncEvent) {
InitSyncEventTest();
bool was_called = false;
ServiceWorkerStatusCode code = SERVICE_WORKER_ERROR_MAX_VALUE;
background_sync_manager_->EmulateDispatchSyncEvent(
"emulated_tag", sw_registration_1_->active_version(), false,
base::BindOnce(EmulateDispatchSyncEventCallback, &was_called, &code));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, code);
EXPECT_EQ(1, sync_events_called_);
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
true);
was_called = false;
code = SERVICE_WORKER_ERROR_MAX_VALUE;
background_sync_manager_->EmulateDispatchSyncEvent(
"emulated_tag", sw_registration_1_->active_version(), false,
base::BindOnce(EmulateDispatchSyncEventCallback, &was_called, &code));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_ERROR_NETWORK, code);
background_sync_manager_->EmulateServiceWorkerOffline(sw_registration_id_1_,
false);
SetNetwork(net::NetworkChangeNotifier::CONNECTION_NONE);
was_called = false;
code = SERVICE_WORKER_ERROR_MAX_VALUE;
background_sync_manager_->EmulateDispatchSyncEvent(
"emulated_tag", sw_registration_1_->active_version(), false,
base::BindOnce(EmulateDispatchSyncEventCallback, &was_called, &code));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_ERROR_NETWORK, code);
SetNetwork(net::NetworkChangeNotifier::CONNECTION_WIFI);
was_called = false;
code = SERVICE_WORKER_ERROR_MAX_VALUE;
background_sync_manager_->EmulateDispatchSyncEvent(
"emulated_tag", sw_registration_1_->active_version(), false,
base::BindOnce(EmulateDispatchSyncEventCallback, &was_called, &code));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, code);
EXPECT_EQ(2, sync_events_called_);
}
} // namespace content
......@@ -17,14 +17,18 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/browser/background_sync/background_sync_manager.h"
#include "content/browser/devtools/devtools_interceptor_controller.h"
#include "content/browser/devtools/devtools_session.h"
#include "content/browser/devtools/devtools_url_loader_interceptor.h"
#include "content/browser/devtools/protocol/page.h"
#include "content/browser/devtools/protocol/security.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/devtools/service_worker_devtools_manager.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/navigation_params.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
......@@ -857,6 +861,62 @@ std::string StripFragment(const GURL& url) {
} // namespace
class BackgroundSyncRestorer {
public:
BackgroundSyncRestorer(const std::string& host_id,
StoragePartition* storage_partition)
: host_id_(host_id), storage_partition_(storage_partition) {
SetServiceWorkerOffline(true);
}
~BackgroundSyncRestorer() { SetServiceWorkerOffline(false); }
void SetStoragePartition(StoragePartition* storage_partition) {
storage_partition_ = storage_partition;
}
private:
void SetServiceWorkerOffline(bool offline) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<DevToolsAgentHost> host =
DevToolsAgentHost::GetForId(host_id_);
if (!host || !storage_partition_ ||
host->GetType() != DevToolsAgentHost::kTypeServiceWorker) {
return;
}
scoped_refptr<ServiceWorkerDevToolsAgentHost> service_worker_host =
static_cast<ServiceWorkerDevToolsAgentHost*>(host.get());
scoped_refptr<BackgroundSyncContext> sync_context =
static_cast<StoragePartitionImpl*>(storage_partition_)
->GetBackgroundSyncContext();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(
&SetServiceWorkerOfflineOnIO, sync_context,
base::RetainedRef(static_cast<ServiceWorkerContextWrapper*>(
storage_partition_->GetServiceWorkerContext())),
service_worker_host->version_id(), offline));
}
static void SetServiceWorkerOfflineOnIO(
scoped_refptr<BackgroundSyncContext> sync_context,
scoped_refptr<ServiceWorkerContextWrapper> swcontext,
int64_t version_id,
bool offline) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
ServiceWorkerVersion* version = swcontext.get()->GetLiveVersion(version_id);
if (!version)
return;
sync_context->background_sync_manager()->EmulateServiceWorkerOffline(
version->registration_id(), offline);
}
std::string host_id_;
StoragePartition* storage_partition_;
DISALLOW_COPY_AND_ASSIGN(BackgroundSyncRestorer);
};
NetworkHandler::NetworkHandler(const std::string& host_id)
: DevToolsDomainHandler(Network::Metainfo::domainName),
browser_context_(nullptr),
......@@ -902,6 +962,8 @@ void NetworkHandler::SetRenderer(int render_process_host_id,
browser_context_ = nullptr;
}
host_ = frame_host;
if (background_sync_restorer_)
background_sync_restorer_->SetStoragePartition(storage_partition_);
}
Response NetworkHandler::Enable(Maybe<int> max_total_size,
......@@ -1891,7 +1953,14 @@ void NetworkHandler::SetNetworkConditions(
return;
network::mojom::NetworkContext* context =
storage_partition_->GetNetworkContext();
bool offline = conditions ? conditions->offline : false;
context->SetNetworkConditions(host_id_, std::move(conditions));
if (offline == !!background_sync_restorer_)
return;
background_sync_restorer_.reset(
offline ? new BackgroundSyncRestorer(host_id_, storage_partition_)
: nullptr);
}
} // namespace protocol
......
......@@ -46,6 +46,7 @@ struct GlobalRequestID;
struct InterceptedRequestInfo;
namespace protocol {
class BackgroundSyncRestorer;
class NetworkHandler : public DevToolsDomainHandler,
public Network::Backend {
......@@ -184,6 +185,7 @@ class NetworkHandler : public DevToolsDomainHandler,
std::unique_ptr<DevToolsURLLoaderInterceptor> url_loader_interceptor_;
bool bypass_service_worker_;
bool cache_disabled_;
std::unique_ptr<BackgroundSyncRestorer> background_sync_restorer_;
base::WeakPtrFactory<NetworkHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NetworkHandler);
......
......@@ -72,6 +72,8 @@ class ServiceWorkerDevToolsAgentHost : public DevToolsAgentHostImpl {
// Returns the time when the ServiceWorker was doomed.
base::Time version_doomed_time() const { return version_doomed_time_; }
int64_t version_id() const { return version_id_; }
bool Matches(const ServiceWorkerContextCore* context, int64_t version_id);
private:
......
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