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