Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 1 | // Copyright 2023 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include <memory> |
| 6 | |
| 7 | #include "chrome/browser/web_applications/generated_icon_fix_util.h" |
| 8 | |
| 9 | #include "base/notreached.h" |
| 10 | #include "base/numerics/safe_conversions.h" |
| 11 | #include "base/strings/string_util.h" |
| 12 | #include "base/values.h" |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 13 | #include "chrome/browser/web_applications/web_app_registrar.h" |
| 14 | #include "chrome/browser/web_applications/web_app_registry_update.h" |
| 15 | #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| 16 | #include "chrome/common/chrome_features.h" |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 17 | #include "components/sync/base/time.h" |
| 18 | |
| 19 | namespace web_app { |
| 20 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 21 | namespace generated_icon_fix_util { |
| 22 | |
| 23 | namespace { |
| 24 | |
| 25 | constexpr base::TimeDelta kFixWindowDuration = base::Days(7); |
Alan Cutter | a027a83 | 2023-10-03 08:05:33 | [diff] [blame] | 26 | constexpr base::TimeDelta kFixAttemptThrottleDuration = base::Days(1); |
| 27 | |
| 28 | // Capping the number of attempts should be redundant with the throttle + window |
| 29 | // but, because retries on failure are self propagating, have an explicit |
| 30 | // attempt count to be extra safe. Ordinarily a constraint like this would be |
| 31 | // CHECK'd but because these attempts run at start up it wouldn't be good for |
| 32 | // stability. |
| 33 | constexpr uint32_t kMaxAttemptCount = |
| 34 | kFixWindowDuration.IntDiv(kFixAttemptThrottleDuration); |
| 35 | static_assert(kMaxAttemptCount == 7u); |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 36 | |
Arthur Sonzogni | fe132ee | 2024-01-15 11:01:04 | [diff] [blame] | 37 | std::optional<base::Time> g_now_override_for_testing_; |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 38 | |
| 39 | base::Time Now() { |
| 40 | return g_now_override_for_testing_.value_or(base::Time::Now()); |
| 41 | } |
| 42 | |
| 43 | } // namespace |
| 44 | |
| 45 | bool IsValid(const GeneratedIconFix& generated_icon_fix) { |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 46 | return generated_icon_fix.has_source() && |
| 47 | generated_icon_fix.source() != GeneratedIconFixSource_UNKNOWN && |
| 48 | generated_icon_fix.has_window_start_time() && |
| 49 | generated_icon_fix.has_attempt_count(); |
| 50 | } |
| 51 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 52 | base::Value ToDebugValue(const GeneratedIconFix* generated_icon_fix) { |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 53 | if (!generated_icon_fix) { |
| 54 | return base::Value(); |
| 55 | } |
| 56 | |
| 57 | base::Value::Dict debug_value; |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 58 | debug_value.Set("source", base::ToString(generated_icon_fix->source())); |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 59 | debug_value.Set("window_start_time", |
| 60 | base::ToString(syncer::ProtoTimeToTime( |
| 61 | generated_icon_fix->window_start_time()))); |
| 62 | debug_value.Set("last_attempt_time", |
| 63 | generated_icon_fix->has_last_attempt_time() |
| 64 | ? base::Value(base::ToString(syncer::ProtoTimeToTime( |
| 65 | generated_icon_fix->last_attempt_time()))) |
| 66 | : base::Value()); |
| 67 | debug_value.Set("attempt_count", base::saturated_cast<int>( |
| 68 | generated_icon_fix->attempt_count())); |
| 69 | return base::Value(std::move(debug_value)); |
| 70 | } |
| 71 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 72 | void SetNowForTesting(base::Time now) { |
| 73 | g_now_override_for_testing_ = now; |
| 74 | } |
| 75 | |
Alan Cutter | a027a83 | 2023-10-03 08:05:33 | [diff] [blame] | 76 | bool HasRemainingAttempts(const WebApp& app) { |
Arthur Sonzogni | fe132ee | 2024-01-15 11:01:04 | [diff] [blame] | 77 | const std::optional<GeneratedIconFix>& generated_icon_fix = |
Alan Cutter | a027a83 | 2023-10-03 08:05:33 | [diff] [blame] | 78 | app.generated_icon_fix(); |
| 79 | if (!generated_icon_fix.has_value()) { |
| 80 | return true; |
| 81 | } |
| 82 | return generated_icon_fix->attempt_count() < kMaxAttemptCount; |
| 83 | } |
| 84 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 85 | bool IsWithinFixTimeWindow(const WebApp& app) { |
Arthur Sonzogni | fe132ee | 2024-01-15 11:01:04 | [diff] [blame] | 86 | const std::optional<GeneratedIconFix>& generated_icon_fix = |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 87 | app.generated_icon_fix(); |
| 88 | if (!generated_icon_fix.has_value()) { |
| 89 | return base::FeatureList::IsEnabled( |
| 90 | features::kWebAppSyncGeneratedIconRetroactiveFix); |
| 91 | } |
| 92 | |
| 93 | base::TimeDelta duration_since_window_started = |
| 94 | Now() - syncer::ProtoTimeToTime(generated_icon_fix->window_start_time()); |
| 95 | return duration_since_window_started <= kFixWindowDuration; |
| 96 | } |
| 97 | |
| 98 | void EnsureFixTimeWindowStarted(WithAppResources& resources, |
| 99 | ScopedRegistryUpdate& update, |
| 100 | const webapps::AppId& app_id, |
| 101 | GeneratedIconFixSource source) { |
| 102 | if (resources.registrar() |
| 103 | .GetAppById(app_id) |
| 104 | ->generated_icon_fix() |
| 105 | .has_value()) { |
| 106 | return; |
| 107 | } |
| 108 | update->UpdateApp(app_id)->SetGeneratedIconFix( |
| 109 | CreateInitialTimeWindow(source)); |
| 110 | } |
| 111 | |
| 112 | GeneratedIconFix CreateInitialTimeWindow(GeneratedIconFixSource source) { |
| 113 | GeneratedIconFix generated_icon_fix; |
| 114 | generated_icon_fix.set_source(source); |
| 115 | generated_icon_fix.set_window_start_time(syncer::TimeToProtoTime(Now())); |
| 116 | generated_icon_fix.set_attempt_count(0); |
| 117 | return generated_icon_fix; |
| 118 | } |
| 119 | |
Alan Cutter | a027a83 | 2023-10-03 08:05:33 | [diff] [blame] | 120 | base::TimeDelta GetThrottleDuration(const WebApp& app) { |
Arthur Sonzogni | fe132ee | 2024-01-15 11:01:04 | [diff] [blame] | 121 | const std::optional<GeneratedIconFix> generated_icon_fix = |
Alan Cutter | a027a83 | 2023-10-03 08:05:33 | [diff] [blame] | 122 | app.generated_icon_fix(); |
| 123 | if (!generated_icon_fix.has_value() || |
| 124 | !generated_icon_fix->has_last_attempt_time()) { |
| 125 | return base::TimeDelta(); |
| 126 | } |
| 127 | |
| 128 | base::TimeDelta throttle_duration = |
| 129 | syncer::ProtoTimeToTime(generated_icon_fix->last_attempt_time()) + |
| 130 | kFixAttemptThrottleDuration - Now(); |
| 131 | // Negative durations could cause us to skip ahead of other tasks already in |
| 132 | // the task queue when used in PostDelayedTask() so clamp to 0. |
| 133 | return throttle_duration.is_negative() ? base::TimeDelta() |
| 134 | : throttle_duration; |
| 135 | } |
| 136 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 137 | void RecordFixAttempt(WithAppResources& resources, |
| 138 | ScopedRegistryUpdate& update, |
| 139 | const webapps::AppId& app_id, |
| 140 | GeneratedIconFixSource source) { |
| 141 | EnsureFixTimeWindowStarted(resources, update, app_id, source); |
| 142 | WebApp* app = update->UpdateApp(app_id); |
| 143 | GeneratedIconFix generated_icon_fix = app->generated_icon_fix().value(); |
| 144 | generated_icon_fix.set_attempt_count(generated_icon_fix.attempt_count() + 1); |
| 145 | generated_icon_fix.set_last_attempt_time(syncer::TimeToProtoTime(Now())); |
| 146 | app->SetGeneratedIconFix(std::move(generated_icon_fix)); |
| 147 | } |
| 148 | |
| 149 | } // namespace generated_icon_fix_util |
| 150 | |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 151 | bool operator==(const GeneratedIconFix& a, const GeneratedIconFix& b) { |
| 152 | return a.SerializeAsString() == b.SerializeAsString(); |
| 153 | } |
| 154 | |
Alan Cutter | 0734569 | 2023-09-28 09:44:57 | [diff] [blame] | 155 | std::ostream& operator<<(std::ostream& out, |
| 156 | const GeneratedIconFixSource& source) { |
| 157 | switch (source) { |
| 158 | case GeneratedIconFixSource_UNKNOWN: |
| 159 | NOTREACHED(); |
| 160 | return out << "Unknown"; |
| 161 | case GeneratedIconFixSource_SYNC_INSTALL: |
| 162 | return out << "SyncInstall"; |
| 163 | case GeneratedIconFixSource_RETROACTIVE: |
| 164 | return out << "Retroactive"; |
| 165 | case GeneratedIconFixSource_MANIFEST_UPDATE: |
| 166 | return out << "ManifestUpdate"; |
| 167 | } |
| 168 | } |
| 169 | |
Alan Cutter | 5d14a63 | 2023-09-28 02:16:13 | [diff] [blame] | 170 | } // namespace web_app |