blob: 3eaa8aa3f55d0c53d688901057b4a0749d2ce9ca [file] [log] [blame]
// Copyright 2015 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 "components/background_sync/background_sync_controller_impl.h"
#include "base/containers/contains.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/background_sync_context.h"
#include "content/public/browser/background_sync_controller.h"
#include "content/public/browser/background_sync_parameters.h"
#include "content/public/browser/background_sync_registration.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/storage_partition.h"
#include "url/gurl.h"
#include "url/origin.h"
// static
const char BackgroundSyncControllerImpl::kFieldTrialName[] = "BackgroundSync";
const char BackgroundSyncControllerImpl::kDisabledParameterName[] = "disabled";
#if defined(OS_ANDROID)
const char BackgroundSyncControllerImpl::kRelyOnAndroidNetworkDetection[] =
"rely_on_android_network_detection";
#endif
const char BackgroundSyncControllerImpl::kKeepBrowserAwakeParameterName[] =
"keep_browser_awake_till_events_complete";
const char BackgroundSyncControllerImpl::kSkipPermissionsCheckParameterName[] =
"skip_permissions_check_for_testing";
const char BackgroundSyncControllerImpl::kMaxAttemptsParameterName[] =
"max_sync_attempts";
const char BackgroundSyncControllerImpl::
kMaxAttemptsWithNotificationPermissionParameterName[] =
"max_sync_attempts_with_notification_permission";
const char BackgroundSyncControllerImpl::kInitialRetryParameterName[] =
"initial_retry_delay_sec";
const char BackgroundSyncControllerImpl::kRetryDelayFactorParameterName[] =
"retry_delay_factor";
const char BackgroundSyncControllerImpl::kMinSyncRecoveryTimeName[] =
"min_recovery_time_sec";
const char BackgroundSyncControllerImpl::kMaxSyncEventDurationName[] =
"max_sync_event_duration_sec";
const char BackgroundSyncControllerImpl::kMinPeriodicSyncEventsInterval[] =
"min_periodic_sync_events_interval_sec";
BackgroundSyncControllerImpl::BackgroundSyncControllerImpl(
content::BrowserContext* browser_context,
std::unique_ptr<background_sync::BackgroundSyncDelegate> delegate)
: browser_context_(browser_context), delegate_(std::move(delegate)) {
DCHECK(browser_context_);
DCHECK(delegate_);
background_sync_metrics_ =
std::make_unique<BackgroundSyncMetrics>(delegate_.get());
delegate_->GetHostContentSettingsMap()->AddObserver(this);
}
BackgroundSyncControllerImpl::~BackgroundSyncControllerImpl() = default;
void BackgroundSyncControllerImpl::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsTypeSet content_type_set) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!content_type_set.Contains(ContentSettingsType::BACKGROUND_SYNC) &&
!content_type_set.Contains(
ContentSettingsType::PERIODIC_BACKGROUND_SYNC)) {
return;
}
std::vector<url::Origin> affected_origins;
for (const auto& origin : periodic_sync_origins_) {
if (!IsContentSettingBlocked(origin))
continue;
auto* storage_partition = browser_context_->GetStoragePartitionForUrl(
origin.GetURL(), /* can_create= */ false);
if (!storage_partition)
continue;
auto* background_sync_context =
storage_partition->GetBackgroundSyncContext();
if (!background_sync_context)
continue;
background_sync_context->UnregisterPeriodicSyncForOrigin(origin);
affected_origins.push_back(origin);
}
// Stop tracking affected origins.
for (const auto& origin : affected_origins) {
periodic_sync_origins_.erase(origin);
}
}
void BackgroundSyncControllerImpl::GetParameterOverrides(
content::BackgroundSyncParameters* parameters) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
#if defined(OS_ANDROID)
if (delegate_->ShouldDisableBackgroundSync())
parameters->disable = true;
#endif
std::map<std::string, std::string> field_params;
if (!variations::GetVariationParams(kFieldTrialName, &field_params))
return;
if (base::LowerCaseEqualsASCII(field_params[kDisabledParameterName],
"true")) {
parameters->disable = true;
}
if (base::LowerCaseEqualsASCII(field_params[kKeepBrowserAwakeParameterName],
"true")) {
parameters->keep_browser_awake_till_events_complete = true;
}
if (base::LowerCaseEqualsASCII(
field_params[kSkipPermissionsCheckParameterName], "true")) {
parameters->skip_permissions_check_for_testing = true;
}
if (base::Contains(field_params,
kMaxAttemptsWithNotificationPermissionParameterName)) {
int max_attempts;
if (base::StringToInt(
field_params[kMaxAttemptsWithNotificationPermissionParameterName],
&max_attempts)) {
parameters->max_sync_attempts_with_notification_permission = max_attempts;
}
}
if (base::Contains(field_params, kMaxAttemptsParameterName)) {
int max_attempts;
if (base::StringToInt(field_params[kMaxAttemptsParameterName],
&max_attempts)) {
parameters->max_sync_attempts = max_attempts;
}
}
if (base::Contains(field_params, kInitialRetryParameterName)) {
int initial_retry_delay_sec;
if (base::StringToInt(field_params[kInitialRetryParameterName],
&initial_retry_delay_sec)) {
parameters->initial_retry_delay = base::Seconds(initial_retry_delay_sec);
}
}
if (base::Contains(field_params, kRetryDelayFactorParameterName)) {
int retry_delay_factor;
if (base::StringToInt(field_params[kRetryDelayFactorParameterName],
&retry_delay_factor)) {
parameters->retry_delay_factor = retry_delay_factor;
}
}
if (base::Contains(field_params, kMinSyncRecoveryTimeName)) {
int min_sync_recovery_time_sec;
if (base::StringToInt(field_params[kMinSyncRecoveryTimeName],
&min_sync_recovery_time_sec)) {
parameters->min_sync_recovery_time =
base::Seconds(min_sync_recovery_time_sec);
}
}
if (base::Contains(field_params, kMaxSyncEventDurationName)) {
int max_sync_event_duration_sec;
if (base::StringToInt(field_params[kMaxSyncEventDurationName],
&max_sync_event_duration_sec)) {
parameters->max_sync_event_duration =
base::Seconds(max_sync_event_duration_sec);
}
}
if (base::Contains(field_params, kMinPeriodicSyncEventsInterval)) {
int min_periodic_sync_events_interval_sec;
if (base::StringToInt(field_params[kMinPeriodicSyncEventsInterval],
&min_periodic_sync_events_interval_sec)) {
parameters->min_periodic_sync_events_interval =
base::Seconds(min_periodic_sync_events_interval_sec);
}
}
#if defined(OS_ANDROID)
// Check if the delegate explicitly disabled this feature.
if (delegate_->ShouldDisableAndroidNetworkDetection()) {
parameters->rely_on_android_network_detection = false;
} else if (base::Contains(field_params, kRelyOnAndroidNetworkDetection)) {
if (base::LowerCaseEqualsASCII(field_params[kRelyOnAndroidNetworkDetection],
"true")) {
parameters->rely_on_android_network_detection = true;
}
}
#endif
return;
}
void BackgroundSyncControllerImpl::NotifyOneShotBackgroundSyncRegistered(
const url::Origin& origin,
bool can_fire,
bool is_reregistered) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
background_sync_metrics_->MaybeRecordOneShotSyncRegistrationEvent(
origin, can_fire, is_reregistered);
}
void BackgroundSyncControllerImpl::NotifyPeriodicBackgroundSyncRegistered(
const url::Origin& origin,
int min_interval,
bool is_reregistered) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
background_sync_metrics_->MaybeRecordPeriodicSyncRegistrationEvent(
origin, min_interval, is_reregistered);
}
void BackgroundSyncControllerImpl::NotifyOneShotBackgroundSyncCompleted(
const url::Origin& origin,
blink::ServiceWorkerStatusCode status_code,
int num_attempts,
int max_attempts) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
background_sync_metrics_->MaybeRecordOneShotSyncCompletionEvent(
origin, status_code, num_attempts, max_attempts);
}
void BackgroundSyncControllerImpl::NotifyPeriodicBackgroundSyncCompleted(
const url::Origin& origin,
blink::ServiceWorkerStatusCode status_code,
int num_attempts,
int max_attempts) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
background_sync_metrics_->MaybeRecordPeriodicSyncEventCompletion(
origin, status_code, num_attempts, max_attempts);
}
void BackgroundSyncControllerImpl::ScheduleBrowserWakeUpWithDelay(
blink::mojom::BackgroundSyncType sync_type,
base::TimeDelta delay) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_->IsProfileOffTheRecord())
return;
#if defined(OS_ANDROID)
delegate_->ScheduleBrowserWakeUpWithDelay(sync_type, delay);
#endif
}
void BackgroundSyncControllerImpl::CancelBrowserWakeup(
blink::mojom::BackgroundSyncType sync_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_->IsProfileOffTheRecord())
return;
#if defined(OS_ANDROID)
delegate_->CancelBrowserWakeup(sync_type);
#endif
}
base::TimeDelta BackgroundSyncControllerImpl::SnapToMaxOriginFrequency(
int64_t min_interval,
int64_t min_gap_for_origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_GE(min_gap_for_origin, 0);
DCHECK_GE(min_interval, 0);
if (min_interval < min_gap_for_origin)
return base::Milliseconds(min_gap_for_origin);
if (min_interval % min_gap_for_origin == 0)
return base::Milliseconds(min_interval);
return base::Milliseconds((min_interval / min_gap_for_origin + 1) *
min_gap_for_origin);
}
base::TimeDelta BackgroundSyncControllerImpl::ApplyMinGapForOrigin(
base::TimeDelta delay,
base::TimeDelta time_till_next_scheduled_event_for_origin,
base::TimeDelta min_gap_for_origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (time_till_next_scheduled_event_for_origin.is_max())
return delay;
if (delay <= time_till_next_scheduled_event_for_origin - min_gap_for_origin)
return delay;
if (delay <= time_till_next_scheduled_event_for_origin)
return time_till_next_scheduled_event_for_origin;
if (delay <= time_till_next_scheduled_event_for_origin + min_gap_for_origin)
return time_till_next_scheduled_event_for_origin + min_gap_for_origin;
return delay;
}
bool BackgroundSyncControllerImpl::IsContentSettingBlocked(
const url::Origin& origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* host_content_settings_map = delegate_->GetHostContentSettingsMap();
DCHECK(host_content_settings_map);
auto url = origin.GetURL();
return CONTENT_SETTING_ALLOW != host_content_settings_map->GetContentSetting(
/* primary_url= */ url,
/* secondary_url= */ url,
ContentSettingsType::BACKGROUND_SYNC);
}
void BackgroundSyncControllerImpl::Shutdown() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
delegate_->GetHostContentSettingsMap()->RemoveObserver(this);
delegate_->Shutdown();
}
base::TimeDelta BackgroundSyncControllerImpl::GetNextEventDelay(
const content::BackgroundSyncRegistration& registration,
content::BackgroundSyncParameters* parameters,
base::TimeDelta time_till_soonest_scheduled_event_for_origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(parameters);
int num_attempts = registration.num_attempts();
if (!num_attempts) {
// First attempt.
switch (registration.sync_type()) {
case blink::mojom::BackgroundSyncType::ONE_SHOT:
return base::TimeDelta();
case blink::mojom::BackgroundSyncType::PERIODIC:
int site_engagement_factor =
delegate_->GetSiteEngagementPenalty(registration.origin().GetURL());
if (!site_engagement_factor)
return base::TimeDelta::Max();
int64_t effective_gap_ms =
site_engagement_factor *
parameters->min_periodic_sync_events_interval.InMilliseconds();
return ApplyMinGapForOrigin(
SnapToMaxOriginFrequency(registration.options()->min_interval,
effective_gap_ms),
time_till_soonest_scheduled_event_for_origin,
parameters->min_periodic_sync_events_interval);
}
}
// After a sync event has been fired.
DCHECK_LT(num_attempts, parameters->max_sync_attempts);
return parameters->initial_retry_delay *
pow(parameters->retry_delay_factor, num_attempts - 1);
}
std::unique_ptr<content::BackgroundSyncController::BackgroundSyncEventKeepAlive>
BackgroundSyncControllerImpl::CreateBackgroundSyncEventKeepAlive() {
#if defined(OS_ANDROID)
// Not needed on Android.
return nullptr;
#else
return delegate_->CreateBackgroundSyncEventKeepAlive();
#endif
}
void BackgroundSyncControllerImpl::NoteSuspendedPeriodicSyncOrigins(
std::set<url::Origin> suspended_origins) {
delegate_->NoteSuspendedPeriodicSyncOrigins(std::move(suspended_origins));
}
void BackgroundSyncControllerImpl::NoteRegisteredPeriodicSyncOrigins(
std::set<url::Origin> registered_origins) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (auto& origin : registered_origins)
periodic_sync_origins_.insert(std::move(origin));
}
void BackgroundSyncControllerImpl::AddToTrackedOrigins(
const url::Origin& origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
periodic_sync_origins_.insert(origin);
}
void BackgroundSyncControllerImpl::RemoveFromTrackedOrigins(
const url::Origin& origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
periodic_sync_origins_.erase(origin);
}