blob: ff171c0f2fb076d84678c6c45798f310fa321a0c [file] [log] [blame]
// 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 "chrome/browser/web_applications/generated_icon_fix_util.h"
#include <stdint.h>
#include <compare>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <utility>
#include "base/feature_list.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/to_string.h"
#include "base/values.h"
#include "chrome/browser/web_applications/locks/with_app_resources.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/common/chrome_features.h"
#include "components/sync/base/time.h"
namespace web_app::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 proto::GeneratedIconFix& generated_icon_fix) {
return generated_icon_fix.has_source() &&
generated_icon_fix.source() !=
proto::GENERATED_ICON_FIX_SOURCE_UNKNOWN &&
generated_icon_fix.has_window_start_time() &&
generated_icon_fix.has_attempt_count();
}
void SetNowForTesting(base::Time now) {
g_now_override_for_testing_ = now;
}
bool HasRemainingAttempts(const WebApp& app) {
const std::optional<proto::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<proto::GeneratedIconFix>& generated_icon_fix =
app.generated_icon_fix();
if (!generated_icon_fix.has_value()) {
return true;
}
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,
proto::GeneratedIconFixSource source) {
if (resources.registrar()
.GetAppById(app_id)
->generated_icon_fix()
.has_value()) {
return;
}
update->UpdateApp(app_id)->SetGeneratedIconFix(
CreateInitialTimeWindow(source));
}
proto::GeneratedIconFix CreateInitialTimeWindow(
proto::GeneratedIconFixSource source) {
proto::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<proto::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,
proto::GeneratedIconFixSource source) {
EnsureFixTimeWindowStarted(resources, update, app_id, source);
WebApp* app = update->UpdateApp(app_id);
proto::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 web_app::generated_icon_fix_util