|  | // Copyright 2013 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/ash/extensions/install_limiter.h" | 
|  |  | 
|  | #include <optional> | 
|  | #include <string> | 
|  |  | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "chrome/browser/ash/extensions/install_limiter_factory.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "extensions/browser/extensions_browser_client.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // InstallLimiter::DeferredInstall | 
|  |  | 
|  | InstallLimiter::DeferredInstall::DeferredInstall( | 
|  | const scoped_refptr<CrxInstaller>& installer, | 
|  | const CRXFileInfo& file_info) | 
|  | : installer(installer), file_info(file_info) {} | 
|  |  | 
|  | InstallLimiter::DeferredInstall::DeferredInstall(const DeferredInstall& other) = | 
|  | default; | 
|  |  | 
|  | InstallLimiter::DeferredInstall::~DeferredInstall() = default; | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // InstallLimiter | 
|  |  | 
|  | // static | 
|  | InstallLimiter* InstallLimiter::Get(Profile* profile) { | 
|  | return InstallLimiterFactory::GetForProfile(profile); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool InstallLimiter::ShouldDeferInstall(int64_t app_size, | 
|  | const std::string& app_id) { | 
|  | constexpr int64_t kBigAppSizeThreshold = 1048576;  // 1MB in bytes | 
|  | return app_size > kBigAppSizeThreshold && | 
|  | !ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(app_id); | 
|  | } | 
|  |  | 
|  | InstallLimiter::InstallLimiter() : disabled_for_test_(false) {} | 
|  |  | 
|  | InstallLimiter::~InstallLimiter() = default; | 
|  |  | 
|  | void InstallLimiter::DisableForTest() { | 
|  | disabled_for_test_ = true; | 
|  | } | 
|  |  | 
|  | void InstallLimiter::Add(const scoped_refptr<CrxInstaller>& installer, | 
|  | const CRXFileInfo& file_info) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  |  | 
|  | // No deferred installs when disabled for test. | 
|  | if (disabled_for_test_) { | 
|  | installer->InstallCrxFile(file_info); | 
|  | return; | 
|  | } | 
|  |  | 
|  | num_installs_waiting_for_file_size_++; | 
|  |  | 
|  | base::ThreadPool::PostTaskAndReplyWithResult( | 
|  | FROM_HERE, {base::MayBlock()}, base::GetFileSizeCallback(file_info.path), | 
|  | base::BindOnce(&InstallLimiter::AddWithSize, | 
|  | weak_ptr_factory_.GetWeakPtr(), installer, file_info)); | 
|  | } | 
|  |  | 
|  | void InstallLimiter::OnAllExternalProvidersReady() { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | all_external_providers_ready_ = true; | 
|  |  | 
|  | if (AllInstallsQueuedWithFileSize()) { | 
|  | // Stop wait timer and let install notification drive deferred installs. | 
|  | wait_timer_.Stop(); | 
|  | CheckAndRunDeferrredInstalls(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void InstallLimiter::AddWithSize(const scoped_refptr<CrxInstaller>& installer, | 
|  | const CRXFileInfo& file_info, | 
|  | std::optional<int64_t> size) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  |  | 
|  | num_installs_waiting_for_file_size_--; | 
|  |  | 
|  | if (!ShouldDeferInstall(size.value_or(0), installer->expected_id()) || | 
|  | AllInstallsQueuedWithFileSize()) { | 
|  | RunInstall(installer, file_info); | 
|  |  | 
|  | // Stop wait timer and let install notification drive deferred installs. | 
|  | wait_timer_.Stop(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | deferred_installs_.push(DeferredInstall(installer, file_info)); | 
|  |  | 
|  | // When there are no running installs, wait a bit before running deferred | 
|  | // installs to allow small app install to take precedence, especially when a | 
|  | // big app is the first one in the list. | 
|  | if (num_running_installs_ == 0 && !wait_timer_.IsRunning()) { | 
|  | const int kMaxWaitTimeInMs = 5000;  // 5 seconds. | 
|  | wait_timer_.Start(FROM_HERE, base::Milliseconds(kMaxWaitTimeInMs), this, | 
|  | &InstallLimiter::CheckAndRunDeferrredInstalls); | 
|  | } | 
|  | } | 
|  |  | 
|  | void InstallLimiter::CheckAndRunDeferrredInstalls() { | 
|  | if (deferred_installs_.empty() || num_running_installs_ > 0) | 
|  | return; | 
|  |  | 
|  | const DeferredInstall& deferred = deferred_installs_.front(); | 
|  | RunInstall(deferred.installer, deferred.file_info); | 
|  | deferred_installs_.pop(); | 
|  | } | 
|  |  | 
|  | void InstallLimiter::RunInstall(const scoped_refptr<CrxInstaller>& installer, | 
|  | const CRXFileInfo& file_info) { | 
|  | installer->AddInstallerCallback(base::BindOnce( | 
|  | &InstallLimiter::OnInstallerDone, weak_ptr_factory_.GetWeakPtr())); | 
|  | installer->InstallCrxFile(file_info); | 
|  | num_running_installs_++; | 
|  | } | 
|  |  | 
|  | void InstallLimiter::OnInstallerDone( | 
|  | const std::optional<CrxInstallError>& error) { | 
|  | CHECK(num_running_installs_ > 0); | 
|  | num_running_installs_--; | 
|  | CheckAndRunDeferrredInstalls(); | 
|  | } | 
|  |  | 
|  | bool InstallLimiter::AllInstallsQueuedWithFileSize() const { | 
|  | return all_external_providers_ready_ && | 
|  | num_installs_waiting_for_file_size_ == 0; | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |