blob: 4b4a959b01685f8e9a27c54856cbaf76b381ce50 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/web_applications/components/manifest_update_manager.h"
#include "base/metrics/histogram_macros.h"
#include "base/util/values/values_util.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/common/chrome_features.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
namespace web_app {
namespace {
const char kLastUpdateCheckKey[] = "last_update_check";
class AppPrefs {
public:
AppPrefs(Profile* profile, const GURL& origin) {
settings_ = HostContentSettingsMapFactory::GetForProfile(profile);
if (!settings_)
return;
origin_data_ = settings_->GetWebsiteSetting(
origin, GURL(), ContentSettingsType::INSTALLED_WEB_APP_METADATA,
std::string(), nullptr);
}
bool IsAvailable() const { return settings_; }
const base::Value* GetAppData(const AppId& app_id) const {
if (!origin_data_)
return nullptr;
return origin_data_->FindKey(app_id);
}
base::Value& GetAppDataMutable(const AppId& app_id) {
DCHECK(IsAvailable());
if (!origin_data_)
origin_data_ =
std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
base::Value* app_data = origin_data_->FindKey(app_id);
if (!app_data) {
app_data = origin_data_->SetKey(
app_id, base::Value(base::Value::Type::DICTIONARY));
}
return *app_data;
}
void Save(const GURL& origin) {
DCHECK(IsAvailable());
settings_->SetWebsiteSettingDefaultScope(
origin, GURL(), ContentSettingsType::INSTALLED_WEB_APP_METADATA,
std::string(), std::move(origin_data_));
}
private:
HostContentSettingsMap* settings_ = nullptr;
std::unique_ptr<base::Value> origin_data_;
};
} // namespace
ManifestUpdateManager::ManifestUpdateManager(Profile* profile)
: profile_(profile) {}
ManifestUpdateManager::~ManifestUpdateManager() = default;
void ManifestUpdateManager::SetSubsystems(AppRegistrar* registrar,
WebAppUiManager* ui_manager,
InstallManager* install_manager) {
registrar_ = registrar;
ui_manager_ = ui_manager;
install_manager_ = install_manager;
}
void ManifestUpdateManager::Start() {
registrar_observer_.Add(registrar_);
}
void ManifestUpdateManager::Shutdown() {
registrar_observer_.RemoveAll();
}
void ManifestUpdateManager::MaybeUpdate(const GURL& url,
const AppId& app_id,
content::WebContents* web_contents) {
if (!base::FeatureList::IsEnabled(features::kDesktopPWAsLocalUpdating))
return;
if (app_id.empty() || !registrar_->IsLocallyInstalled(app_id)) {
NotifyResult(url, ManifestUpdateResult::kNoAppInScope);
return;
}
if (registrar_->IsPlaceholderApp(app_id)) {
NotifyResult(url, ManifestUpdateResult::kAppIsPlaceholder);
return;
}
if (base::Contains(tasks_, app_id))
return;
if (!MaybeConsumeUpdateCheck(url.GetOrigin(), app_id)) {
NotifyResult(url, ManifestUpdateResult::kThrottled);
return;
}
tasks_.insert_or_assign(
app_id, std::make_unique<ManifestUpdateTask>(
url, app_id, web_contents,
base::Bind(&ManifestUpdateManager::OnUpdateStopped,
base::Unretained(this)),
hang_update_checks_for_testing_, *registrar_, ui_manager_,
install_manager_));
}
// AppRegistrarObserver:
void ManifestUpdateManager::OnWebAppUninstalled(const AppId& app_id) {
auto it = tasks_.find(app_id);
if (it != tasks_.end()) {
NotifyResult(it->second->url(), ManifestUpdateResult::kAppUninstalled);
tasks_.erase(it);
}
DCHECK(!tasks_.contains(app_id));
}
bool ManifestUpdateManager::MaybeConsumeUpdateCheck(const GURL& origin,
const AppId& app_id) {
base::Optional<base::Time> last_check_time =
GetLastUpdateCheckTime(origin, app_id);
if (!last_check_time)
return false;
base::Time now = time_override_for_testing_.value_or(base::Time::Now());
// Throttling updates to at most once per day is consistent with Android.
// See |UPDATE_INTERVAL| in WebappDataStorage.java.
constexpr base::TimeDelta kDelayBetweenChecks = base::TimeDelta::FromDays(1);
if (now < *last_check_time + kDelayBetweenChecks)
return false;
SetLastUpdateCheckTime(origin, app_id, now);
return true;
}
base::Optional<base::Time> ManifestUpdateManager::GetLastUpdateCheckTime(
const GURL& origin,
const AppId& app_id) const {
AppPrefs app_prefs(profile_, origin);
if (!app_prefs.IsAvailable())
return base::nullopt;
const base::Value* app_data = app_prefs.GetAppData(app_id);
if (!app_data)
return base::Time();
return util::ValueToTime(app_data->FindKey(kLastUpdateCheckKey))
.value_or(base::Time());
}
void ManifestUpdateManager::SetLastUpdateCheckTime(const GURL& origin,
const AppId& app_id,
base::Time time) {
AppPrefs app_prefs(profile_, origin);
if (!app_prefs.IsAvailable())
return;
base::Value& app_data = app_prefs.GetAppDataMutable(app_id);
app_data.SetKey(kLastUpdateCheckKey, util::TimeToValue(time));
app_prefs.Save(origin);
}
void ManifestUpdateManager::OnUpdateStopped(const ManifestUpdateTask& task,
ManifestUpdateResult result) {
DCHECK_EQ(&task, tasks_[task.app_id()].get());
NotifyResult(task.url(), result);
tasks_.erase(task.app_id());
}
void ManifestUpdateManager::SetResultCallbackForTesting(
ResultCallback callback) {
DCHECK(result_callback_for_testing_.is_null());
result_callback_for_testing_ = std::move(callback);
}
void ManifestUpdateManager::NotifyResult(const GURL& url,
ManifestUpdateResult result) {
// Don't log kNoAppInScope because it will be far too noisy (most page loads
// will hit it).
if (result != ManifestUpdateResult::kNoAppInScope) {
UMA_HISTOGRAM_ENUMERATION("Webapp.Update.ManifestUpdateResult", result);
}
if (result_callback_for_testing_)
std::move(result_callback_for_testing_).Run(url, result);
}
} // namespace web_app