| // Copyright 2019 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/manifest_update_manager.h" |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/web_applications/os_integration/os_integration_manager.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "chrome/browser/web_applications/web_app_system_web_app_delegate_map_utils.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 { |
| |
| constexpr base::TimeDelta kDelayBetweenChecks = base::Days(1); |
| constexpr const char kDisableManifestUpdateThrottle[] = |
| "disable-manifest-update-throttle"; |
| |
| ManifestUpdateManager::ManifestUpdateManager() = default; |
| |
| ManifestUpdateManager::~ManifestUpdateManager() = default; |
| |
| void ManifestUpdateManager::SetSubsystems( |
| WebAppInstallManager* install_manager, |
| WebAppRegistrar* registrar, |
| WebAppIconManager* icon_manager, |
| WebAppUiManager* ui_manager, |
| WebAppInstallFinalizer* install_finalizer, |
| OsIntegrationManager* os_integration_manager, |
| WebAppSyncBridge* sync_bridge) { |
| install_manager_ = install_manager; |
| registrar_ = registrar; |
| icon_manager_ = icon_manager; |
| ui_manager_ = ui_manager; |
| install_finalizer_ = install_finalizer; |
| os_integration_manager_ = os_integration_manager; |
| sync_bridge_ = sync_bridge; |
| } |
| |
| void ManifestUpdateManager::SetSystemWebAppDelegateMap( |
| const ash::SystemWebAppDelegateMap* system_web_apps_delegate_map) { |
| system_web_apps_delegate_map_ = system_web_apps_delegate_map; |
| } |
| |
| void ManifestUpdateManager::Start() { |
| install_manager_observation_.Observe(install_manager_.get()); |
| |
| DCHECK(!started_); |
| started_ = true; |
| } |
| |
| void ManifestUpdateManager::Shutdown() { |
| install_manager_observation_.Reset(); |
| |
| tasks_.clear(); |
| started_ = false; |
| } |
| |
| void ManifestUpdateManager::MaybeUpdate(const GURL& url, |
| const absl::optional<AppId>& app_id, |
| content::WebContents* web_contents) { |
| if (!started_) { |
| return; |
| } |
| |
| if (!app_id.has_value() || !registrar_->IsLocallyInstalled(*app_id)) { |
| NotifyResult(url, app_id, ManifestUpdateResult::kNoAppInScope); |
| return; |
| } |
| |
| if (system_web_apps_delegate_map_ && |
| IsSystemWebApp(*registrar_, *system_web_apps_delegate_map_, *app_id)) { |
| NotifyResult(url, *app_id, ManifestUpdateResult::kAppIsSystemWebApp); |
| return; |
| } |
| |
| if (registrar_->IsPlaceholderApp(*app_id, WebAppManagement::kPolicy) || |
| registrar_->IsPlaceholderApp(*app_id, WebAppManagement::kKiosk)) { |
| NotifyResult(url, *app_id, ManifestUpdateResult::kAppIsPlaceholder); |
| return; |
| } |
| |
| if (base::Contains(tasks_, *app_id)) |
| return; |
| |
| if (!MaybeConsumeUpdateCheck(url.DeprecatedGetOriginAsURL(), *app_id)) { |
| NotifyResult(url, *app_id, ManifestUpdateResult::kThrottled); |
| return; |
| } |
| |
| tasks_.insert_or_assign( |
| *app_id, std::make_unique<ManifestUpdateTask>( |
| url, *app_id, web_contents, |
| base::BindOnce(&ManifestUpdateManager::OnUpdateStopped, |
| base::Unretained(this)), |
| hang_update_checks_for_testing_, *registrar_, *icon_manager_, |
| ui_manager_, install_finalizer_, *os_integration_manager_, |
| sync_bridge_)); |
| } |
| |
| bool ManifestUpdateManager::IsUpdateConsumed(const AppId& app_id) { |
| absl::optional<base::Time> last_check_time = GetLastUpdateCheckTime(app_id); |
| base::Time now = time_override_for_testing_.value_or(base::Time::Now()); |
| if (last_check_time.has_value() && |
| now < *last_check_time + kDelayBetweenChecks && |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kDisableManifestUpdateThrottle)) { |
| return true; |
| } |
| return false; |
| } |
| |
| // WebAppInstallManager: |
| void ManifestUpdateManager::OnWebAppWillBeUninstalled(const AppId& app_id) { |
| DCHECK(started_); |
| |
| auto it = tasks_.find(app_id); |
| if (it != tasks_.end()) { |
| NotifyResult(it->second->url(), app_id, |
| ManifestUpdateResult::kAppUninstalling); |
| tasks_.erase(it); |
| } |
| DCHECK(!tasks_.contains(app_id)); |
| last_update_check_.erase(app_id); |
| } |
| |
| void ManifestUpdateManager::OnWebAppInstallManagerDestroyed() { |
| install_manager_observation_.Reset(); |
| } |
| |
| // Throttling updates to at most once per day is consistent with Android. |
| // See |UPDATE_INTERVAL| in WebappDataStorage.java. |
| bool ManifestUpdateManager::MaybeConsumeUpdateCheck(const GURL& origin, |
| const AppId& app_id) { |
| if (IsUpdateConsumed(app_id)) |
| return false; |
| |
| base::Time now = time_override_for_testing_.value_or(base::Time::Now()); |
| SetLastUpdateCheckTime(origin, app_id, now); |
| return true; |
| } |
| |
| absl::optional<base::Time> ManifestUpdateManager::GetLastUpdateCheckTime( |
| const AppId& app_id) const { |
| auto it = last_update_check_.find(app_id); |
| return it != last_update_check_.end() ? absl::optional<base::Time>(it->second) |
| : absl::nullopt; |
| } |
| |
| void ManifestUpdateManager::SetLastUpdateCheckTime(const GURL& origin, |
| const AppId& app_id, |
| base::Time time) { |
| last_update_check_[app_id] = time; |
| } |
| |
| void ManifestUpdateManager::OnUpdateStopped(const ManifestUpdateTask& task, |
| ManifestUpdateResult result) { |
| DCHECK_EQ(&task, tasks_[task.app_id()].get()); |
| NotifyResult(task.url(), task.app_id(), 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, |
| const absl::optional<AppId>& app_id, |
| ManifestUpdateResult result) { |
| // Don't log kNoAppInScope because it will be far too noisy (most page loads |
| // will hit it). |
| if (result != ManifestUpdateResult::kNoAppInScope) { |
| base::UmaHistogramEnumeration("Webapp.Update.ManifestUpdateResult", result); |
| } |
| if (result_callback_for_testing_) |
| std::move(result_callback_for_testing_).Run(url, result); |
| } |
| |
| void ManifestUpdateManager::ResetManifestThrottleForTesting( |
| const AppId& app_id) { |
| // Erase the throttle info from the map so that corresponding |
| // manifest writes can go through. |
| auto it = last_update_check_.find(app_id); |
| if (it != last_update_check_.end()) { |
| last_update_check_.erase(app_id); |
| } |
| } |
| |
| } // namespace web_app |