| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "chrome/browser/web_applications/generated_icon_fix_util.h" |
| |
| #include "base/notreached.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/sync/base/time.h" |
| |
| namespace web_app { |
| |
| namespace generated_icon_fix_util { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kFixWindowDuration = base::Days(7); |
| constexpr base::TimeDelta kFixAttemptThrottleDuration = base::Days(1); |
| |
| // Capping the number of attempts should be redundant with the throttle + window |
| // but, because retries on failure are self propagating, have an explicit |
| // attempt count to be extra safe. Ordinarily a constraint like this would be |
| // CHECK'd but because these attempts run at start up it wouldn't be good for |
| // stability. |
| constexpr uint32_t kMaxAttemptCount = |
| kFixWindowDuration.IntDiv(kFixAttemptThrottleDuration); |
| static_assert(kMaxAttemptCount == 7u); |
| |
| std::optional<base::Time> g_now_override_for_testing_; |
| |
| base::Time Now() { |
| return g_now_override_for_testing_.value_or(base::Time::Now()); |
| } |
| |
| } // namespace |
| |
| bool IsValid(const GeneratedIconFix& generated_icon_fix) { |
| return generated_icon_fix.has_source() && |
| generated_icon_fix.source() != GeneratedIconFixSource_UNKNOWN && |
| generated_icon_fix.has_window_start_time() && |
| generated_icon_fix.has_attempt_count(); |
| } |
| |
| base::Value ToDebugValue(const GeneratedIconFix* generated_icon_fix) { |
| if (!generated_icon_fix) { |
| return base::Value(); |
| } |
| |
| base::Value::Dict debug_value; |
| debug_value.Set("source", base::ToString(generated_icon_fix->source())); |
| debug_value.Set("window_start_time", |
| base::ToString(syncer::ProtoTimeToTime( |
| generated_icon_fix->window_start_time()))); |
| debug_value.Set("last_attempt_time", |
| generated_icon_fix->has_last_attempt_time() |
| ? base::Value(base::ToString(syncer::ProtoTimeToTime( |
| generated_icon_fix->last_attempt_time()))) |
| : base::Value()); |
| debug_value.Set("attempt_count", base::saturated_cast<int>( |
| generated_icon_fix->attempt_count())); |
| return base::Value(std::move(debug_value)); |
| } |
| |
| void SetNowForTesting(base::Time now) { |
| g_now_override_for_testing_ = now; |
| } |
| |
| bool HasRemainingAttempts(const WebApp& app) { |
| const std::optional<GeneratedIconFix>& generated_icon_fix = |
| app.generated_icon_fix(); |
| if (!generated_icon_fix.has_value()) { |
| return true; |
| } |
| return generated_icon_fix->attempt_count() < kMaxAttemptCount; |
| } |
| |
| bool IsWithinFixTimeWindow(const WebApp& app) { |
| const std::optional<GeneratedIconFix>& generated_icon_fix = |
| app.generated_icon_fix(); |
| if (!generated_icon_fix.has_value()) { |
| return base::FeatureList::IsEnabled( |
| features::kWebAppSyncGeneratedIconRetroactiveFix); |
| } |
| |
| base::TimeDelta duration_since_window_started = |
| Now() - syncer::ProtoTimeToTime(generated_icon_fix->window_start_time()); |
| return duration_since_window_started <= kFixWindowDuration; |
| } |
| |
| void EnsureFixTimeWindowStarted(WithAppResources& resources, |
| ScopedRegistryUpdate& update, |
| const webapps::AppId& app_id, |
| GeneratedIconFixSource source) { |
| if (resources.registrar() |
| .GetAppById(app_id) |
| ->generated_icon_fix() |
| .has_value()) { |
| return; |
| } |
| update->UpdateApp(app_id)->SetGeneratedIconFix( |
| CreateInitialTimeWindow(source)); |
| } |
| |
| GeneratedIconFix CreateInitialTimeWindow(GeneratedIconFixSource source) { |
| GeneratedIconFix generated_icon_fix; |
| generated_icon_fix.set_source(source); |
| generated_icon_fix.set_window_start_time(syncer::TimeToProtoTime(Now())); |
| generated_icon_fix.set_attempt_count(0); |
| return generated_icon_fix; |
| } |
| |
| base::TimeDelta GetThrottleDuration(const WebApp& app) { |
| const std::optional<GeneratedIconFix> generated_icon_fix = |
| app.generated_icon_fix(); |
| if (!generated_icon_fix.has_value() || |
| !generated_icon_fix->has_last_attempt_time()) { |
| return base::TimeDelta(); |
| } |
| |
| base::TimeDelta throttle_duration = |
| syncer::ProtoTimeToTime(generated_icon_fix->last_attempt_time()) + |
| kFixAttemptThrottleDuration - Now(); |
| // Negative durations could cause us to skip ahead of other tasks already in |
| // the task queue when used in PostDelayedTask() so clamp to 0. |
| return throttle_duration.is_negative() ? base::TimeDelta() |
| : throttle_duration; |
| } |
| |
| void RecordFixAttempt(WithAppResources& resources, |
| ScopedRegistryUpdate& update, |
| const webapps::AppId& app_id, |
| GeneratedIconFixSource source) { |
| EnsureFixTimeWindowStarted(resources, update, app_id, source); |
| WebApp* app = update->UpdateApp(app_id); |
| GeneratedIconFix generated_icon_fix = app->generated_icon_fix().value(); |
| generated_icon_fix.set_attempt_count(generated_icon_fix.attempt_count() + 1); |
| generated_icon_fix.set_last_attempt_time(syncer::TimeToProtoTime(Now())); |
| app->SetGeneratedIconFix(std::move(generated_icon_fix)); |
| } |
| |
| } // namespace generated_icon_fix_util |
| |
| bool operator==(const GeneratedIconFix& a, const GeneratedIconFix& b) { |
| return a.SerializeAsString() == b.SerializeAsString(); |
| } |
| |
| std::ostream& operator<<(std::ostream& out, |
| const GeneratedIconFixSource& source) { |
| switch (source) { |
| case GeneratedIconFixSource_UNKNOWN: |
| NOTREACHED(); |
| return out << "Unknown"; |
| case GeneratedIconFixSource_SYNC_INSTALL: |
| return out << "SyncInstall"; |
| case GeneratedIconFixSource_RETROACTIVE: |
| return out << "Retroactive"; |
| case GeneratedIconFixSource_MANIFEST_UPDATE: |
| return out << "ManifestUpdate"; |
| } |
| } |
| |
| } // namespace web_app |