1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
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 "chrome/browser/chromeos/extensions/install_limiter.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/files/file_util.h"
11 #include "base/task/post_task.h"
12 #include "base/task/thread_pool.h"
13 #include "chrome/browser/chromeos/extensions/install_limiter_factory.h"
14 #include "content/public/browser/notification_details.h"
15 #include "content/public/browser/notification_source.h"
16 #include "extensions/browser/extensions_browser_client.h"
17 #include "extensions/browser/notification_types.h"
18
19 namespace {
20
GetFileSize(const base::FilePath & file)21 int64_t GetFileSize(const base::FilePath& file) {
22 // Get file size. In case of error, sets 0 as file size to let the installer
23 // run and fail.
24 int64_t size;
25 return base::GetFileSize(file, &size) ? size : 0;
26 }
27
28 } // namespace
29
30 namespace extensions {
31
32 ////////////////////////////////////////////////////////////////////////////////
33 // InstallLimiter::DeferredInstall
34
DeferredInstall(const scoped_refptr<CrxInstaller> & installer,const CRXFileInfo & file_info)35 InstallLimiter::DeferredInstall::DeferredInstall(
36 const scoped_refptr<CrxInstaller>& installer,
37 const CRXFileInfo& file_info)
38 : installer(installer), file_info(file_info) {}
39
40 InstallLimiter::DeferredInstall::DeferredInstall(const DeferredInstall& other) =
41 default;
42
~DeferredInstall()43 InstallLimiter::DeferredInstall::~DeferredInstall() {
44 }
45
46 ////////////////////////////////////////////////////////////////////////////////
47 // InstallLimiter
48
49 // static
Get(Profile * profile)50 InstallLimiter* InstallLimiter::Get(Profile* profile) {
51 return InstallLimiterFactory::GetForProfile(profile);
52 }
53
54 // static
ShouldDeferInstall(int64_t app_size,const std::string & app_id)55 bool InstallLimiter::ShouldDeferInstall(int64_t app_size,
56 const std::string& app_id) {
57 constexpr int64_t kBigAppSizeThreshold = 1048576; // 1MB in bytes
58 return app_size > kBigAppSizeThreshold &&
59 !ExtensionsBrowserClient::Get()->IsScreensaverInDemoMode(app_id);
60 }
61
InstallLimiter()62 InstallLimiter::InstallLimiter() : disabled_for_test_(false) {
63 }
64
~InstallLimiter()65 InstallLimiter::~InstallLimiter() {
66 }
67
DisableForTest()68 void InstallLimiter::DisableForTest() {
69 disabled_for_test_ = true;
70 }
71
Add(const scoped_refptr<CrxInstaller> & installer,const CRXFileInfo & file_info)72 void InstallLimiter::Add(const scoped_refptr<CrxInstaller>& installer,
73 const CRXFileInfo& file_info) {
74 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
75
76 // No deferred installs when disabled for test.
77 if (disabled_for_test_) {
78 installer->InstallCrxFile(file_info);
79 return;
80 }
81
82 num_installs_waiting_for_file_size_++;
83
84 base::ThreadPool::PostTaskAndReplyWithResult(
85 FROM_HERE, {base::MayBlock()},
86 base::BindOnce(&GetFileSize, file_info.path),
87 base::BindOnce(&InstallLimiter::AddWithSize, AsWeakPtr(), installer,
88 file_info));
89 }
90
OnAllExternalProvidersReady()91 void InstallLimiter::OnAllExternalProvidersReady() {
92 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
93 all_external_providers_ready_ = true;
94
95 if (AllInstallsQueuedWithFileSize()) {
96 // Stop wait timer and let install notification drive deferred installs.
97 wait_timer_.Stop();
98 CheckAndRunDeferrredInstalls();
99 }
100 }
101
AddWithSize(const scoped_refptr<CrxInstaller> & installer,const CRXFileInfo & file_info,int64_t size)102 void InstallLimiter::AddWithSize(const scoped_refptr<CrxInstaller>& installer,
103 const CRXFileInfo& file_info,
104 int64_t size) {
105 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
106
107 num_installs_waiting_for_file_size_--;
108
109 if (!ShouldDeferInstall(size, installer->expected_id()) ||
110 AllInstallsQueuedWithFileSize()) {
111 RunInstall(installer, file_info);
112
113 // Stop wait timer and let install notification drive deferred installs.
114 wait_timer_.Stop();
115 return;
116 }
117
118 deferred_installs_.push(DeferredInstall(installer, file_info));
119
120 // When there are no running installs, wait a bit before running deferred
121 // installs to allow small app install to take precedence, especially when a
122 // big app is the first one in the list.
123 if (running_installers_.empty() && !wait_timer_.IsRunning()) {
124 const int kMaxWaitTimeInMs = 5000; // 5 seconds.
125 wait_timer_.Start(
126 FROM_HERE,
127 base::TimeDelta::FromMilliseconds(kMaxWaitTimeInMs),
128 this, &InstallLimiter::CheckAndRunDeferrredInstalls);
129 }
130 }
131
CheckAndRunDeferrredInstalls()132 void InstallLimiter::CheckAndRunDeferrredInstalls() {
133 if (deferred_installs_.empty() || !running_installers_.empty())
134 return;
135
136 const DeferredInstall& deferred = deferred_installs_.front();
137 RunInstall(deferred.installer, deferred.file_info);
138 deferred_installs_.pop();
139 }
140
RunInstall(const scoped_refptr<CrxInstaller> & installer,const CRXFileInfo & file_info)141 void InstallLimiter::RunInstall(const scoped_refptr<CrxInstaller>& installer,
142 const CRXFileInfo& file_info) {
143 registrar_.Add(this,
144 extensions::NOTIFICATION_CRX_INSTALLER_DONE,
145 content::Source<CrxInstaller>(installer.get()));
146
147 installer->InstallCrxFile(file_info);
148 running_installers_.insert(installer);
149 }
150
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)151 void InstallLimiter::Observe(int type,
152 const content::NotificationSource& source,
153 const content::NotificationDetails& details) {
154 DCHECK_EQ(extensions::NOTIFICATION_CRX_INSTALLER_DONE, type);
155
156 registrar_.Remove(this, extensions::NOTIFICATION_CRX_INSTALLER_DONE, source);
157
158 const scoped_refptr<CrxInstaller> installer =
159 content::Source<extensions::CrxInstaller>(source).ptr();
160 running_installers_.erase(installer);
161 CheckAndRunDeferrredInstalls();
162 }
163
AllInstallsQueuedWithFileSize() const164 bool InstallLimiter::AllInstallsQueuedWithFileSize() const {
165 return all_external_providers_ready_ &&
166 num_installs_waiting_for_file_size_ == 0;
167 }
168
169 } // namespace extensions
170