OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014 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/extensions/extension_assets_manager_chromeos.h" | |
6 | |
7 #include <map> | |
8 #include <vector> | |
9 | |
10 #include "base/command_line.h" | |
11 #include "base/file_util.h" | |
12 #include "base/memory/singleton.h" | |
13 #include "base/prefs/pref_registry_simple.h" | |
14 #include "base/prefs/pref_service.h" | |
15 #include "base/prefs/scoped_user_pref_update.h" | |
16 #include "base/sequenced_task_runner.h" | |
17 #include "base/sys_info.h" | |
18 #include "chrome/browser/browser_process.h" | |
19 #include "chrome/browser/extensions/extension_service.h" | |
20 #include "chrome/browser/profiles/profile.h" | |
21 #include "chromeos/chromeos_switches.h" | |
22 #include "content/public/browser/browser_thread.h" | |
23 #include "extensions/browser/extension_system.h" | |
24 #include "extensions/common/extension.h" | |
25 #include "extensions/common/file_util.h" | |
26 #include "extensions/common/manifest.h" | |
27 | |
28 using content::BrowserThread; | |
29 | |
30 namespace extensions { | |
31 namespace { | |
32 | |
33 // A dictionary that maps shared extension IDs to version/paths/users. | |
34 const char kSharedExtensions[] = "SharedExtensions"; | |
35 | |
36 // Name of path attribute in shared extensions map. | |
37 const char kSharedExtensionPath[] = "path"; | |
38 | |
39 // Name of users attribute (list of user emails) in shared extensions map. | |
40 const char kSharedExtensionUsers[] = "users"; | |
41 | |
42 // Shared install dir overrider for tests only. | |
43 static const base::FilePath* g_shared_install_dir_override = NULL; | |
44 | |
45 // This helper class lives on UI thread only. Main purpose of this class is to | |
46 // track shared installation in progress between multiple profiles. | |
47 class ExtensionAssetsManagerHelper { | |
48 public: | |
49 // Info about pending install request. | |
50 struct PendingInstallInfo { | |
51 base::FilePath unpacked_extension_root; | |
52 base::FilePath local_install_dir; | |
53 Profile* profile; | |
54 ExtensionAssetsManager::InstallExtensionCallback callback; | |
55 }; | |
56 typedef std::vector<PendingInstallInfo> PendingInstallList; | |
57 | |
58 static ExtensionAssetsManagerHelper* GetInstance() { | |
59 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
60 return Singleton<ExtensionAssetsManagerHelper>::get(); | |
61 } | |
62 | |
63 // Remember that shared install is in progress. Return true if there is no | |
64 // other installs for given id and version. | |
65 bool RecordSharedInstall( | |
66 const std::string& id, | |
67 const std::string& version, | |
68 const base::FilePath& unpacked_extension_root, | |
69 const base::FilePath& local_install_dir, | |
70 Profile* profile, | |
71 ExtensionAssetsManager::InstallExtensionCallback callback) { | |
72 PendingInstallInfo install_info; | |
73 install_info.unpacked_extension_root = unpacked_extension_root; | |
74 install_info.local_install_dir = local_install_dir; | |
75 install_info.profile = profile; | |
76 install_info.callback = callback; | |
77 | |
78 std::vector<PendingInstallInfo>& callbacks = | |
79 install_queue_[InstallQueue::key_type(id, version)]; | |
80 callbacks.push_back(install_info); | |
81 | |
82 return callbacks.size() == 1; | |
83 } | |
84 | |
85 // Remove record about shared installation in progress and return | |
86 // |pending_installs|. | |
87 void SharedInstallDone(const std::string& id, | |
88 const std::string& version, | |
89 PendingInstallList* pending_installs) { | |
90 InstallQueue::iterator it = install_queue_.find( | |
91 InstallQueue::key_type(id, version)); | |
92 DCHECK(it != install_queue_.end()); | |
93 pending_installs->swap(it->second); | |
94 install_queue_.erase(it); | |
95 } | |
96 | |
97 private: | |
98 friend struct DefaultSingletonTraits<ExtensionAssetsManagerHelper>; | |
99 | |
100 ExtensionAssetsManagerHelper() {} | |
101 ~ExtensionAssetsManagerHelper() {} | |
102 | |
103 // Extension ID + version pair. | |
104 typedef std::pair<std::string, std::string> InstallItem; | |
105 | |
106 // Queue of pending installs in progress. | |
107 typedef std::map<InstallItem, std::vector<PendingInstallInfo> > InstallQueue; | |
108 | |
109 InstallQueue install_queue_; | |
110 | |
111 DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerHelper); | |
112 }; | |
113 | |
114 } // namespace | |
115 | |
116 // Path to shared extensions install dir. | |
117 const char ExtensionAssetsManagerChromeOS::kSharedExtensionsDir[] = | |
118 "/var/cache/shared_extensions"; | |
119 | |
120 ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { } | |
121 | |
122 ExtensionAssetsManagerChromeOS::~ExtensionAssetsManagerChromeOS() { | |
123 if (g_shared_install_dir_override) { | |
124 delete g_shared_install_dir_override; | |
125 g_shared_install_dir_override = NULL; | |
126 } | |
127 } | |
128 | |
129 // static | |
130 ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() { | |
131 return Singleton<ExtensionAssetsManagerChromeOS>::get(); | |
132 } | |
133 | |
134 // static | |
135 void ExtensionAssetsManagerChromeOS::RegisterPrefs( | |
136 PrefRegistrySimple* registry) { | |
137 registry->RegisterDictionaryPref(kSharedExtensions); | |
138 } | |
139 | |
140 void ExtensionAssetsManagerChromeOS::InstallExtension( | |
141 const Extension* extension, | |
142 const base::FilePath& unpacked_extension_root, | |
143 const base::FilePath& local_install_dir, | |
144 Profile* profile, | |
145 InstallExtensionCallback callback) { | |
146 if (!CanShareAssets(extension)) { | |
147 InstallLocalExtension(extension->id(), | |
148 extension->VersionString(), | |
149 unpacked_extension_root, | |
150 local_install_dir, | |
151 callback); | |
152 return; | |
153 } | |
154 | |
155 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
156 base::Bind(&ExtensionAssetsManagerChromeOS::CheckSharedExtension, | |
157 extension->id(), | |
158 extension->VersionString(), | |
159 unpacked_extension_root, | |
160 local_install_dir, | |
161 profile, | |
162 callback)); | |
163 } | |
164 | |
165 void ExtensionAssetsManagerChromeOS::UninstallExtension( | |
166 const std::string& id, | |
167 Profile* profile, | |
168 const base::FilePath& local_install_dir, | |
169 const base::FilePath& extension_root) { | |
170 if (local_install_dir.IsParent(extension_root)) { | |
171 file_util::UninstallExtension(local_install_dir, id); | |
172 return; | |
173 } | |
174 | |
175 if (GetSharedInstallDir().IsParent(extension_root)) { | |
176 // In some test extensions installed outside local_install_dir emulate | |
177 // previous behavior that just do nothing in this case. | |
178 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
179 base::Bind(&ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused, | |
180 id, | |
181 profile)); | |
182 } | |
183 } | |
184 | |
185 // static | |
186 void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting( | |
187 const base::FilePath& install_dir) { | |
188 DCHECK(!g_shared_install_dir_override); | |
189 g_shared_install_dir_override = new base::FilePath(install_dir); | |
190 } | |
191 | |
192 // static | |
193 base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner( | |
194 Profile* profile) { | |
195 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
196 ExtensionService* extension_service = | |
197 ExtensionSystem::Get(profile)->extension_service(); | |
198 return extension_service->GetFileTaskRunner(); | |
199 } | |
200 | |
201 // static | |
202 base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() { | |
203 if (g_shared_install_dir_override) | |
204 return *g_shared_install_dir_override; | |
205 else | |
206 return base::FilePath(kSharedExtensionsDir); | |
207 } | |
208 | |
209 // static | |
210 bool ExtensionAssetsManagerChromeOS::CanShareAssets( | |
211 const Extension* extension) { | |
212 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
213 chromeos::switches::kEnableExtensionAssetsSharing)) { | |
214 return false; | |
215 } | |
216 | |
217 // Chrome caches crx files for installed by default apps so sharing assets is | |
218 // also possible. User specific apps should be excluded to not expose apps | |
219 // unique for the user outside of user's cryptohome. | |
220 return Manifest::IsExternalLocation(extension->location()); | |
221 } | |
222 | |
223 // static | |
224 void ExtensionAssetsManagerChromeOS::CheckSharedExtension( | |
225 const std::string& id, | |
226 const std::string& version, | |
227 const base::FilePath& unpacked_extension_root, | |
228 const base::FilePath& local_install_dir, | |
229 Profile* profile, | |
230 InstallExtensionCallback callback) { | |
231 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
232 | |
233 PrefService* local_state = g_browser_process->local_state(); | |
234 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); | |
235 base::DictionaryValue* extension_info = NULL; | |
236 base::DictionaryValue* version_info = NULL; | |
237 base::ListValue* users = NULL; | |
238 std::string shared_path; | |
239 if (shared_extensions->GetDictionary(id, &extension_info) && | |
240 extension_info->GetDictionaryWithoutPathExpansion( | |
241 version, &version_info) && | |
242 version_info->GetString(kSharedExtensionPath, &shared_path) && | |
243 version_info->GetList(kSharedExtensionUsers, &users)) { | |
244 // This extension version already in shared location. | |
245 const std::string& user_name = profile->GetProfileName(); | |
246 size_t users_size = users->GetSize(); | |
247 bool user_found = false; | |
248 for (size_t i = 0; i < users_size; i++) { | |
249 std::string temp; | |
250 if (users->GetString(i, &temp) && temp == user_name) { | |
251 // Re-installation for the same user. | |
252 user_found = true; | |
253 break; | |
254 } | |
255 } | |
256 if (!user_found) | |
257 users->AppendString(user_name); | |
258 | |
259 // unpacked_extension_root will be deleted by CrxInstaller. | |
260 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
261 FROM_HERE, | |
262 base::Bind(callback, base::FilePath(shared_path))); | |
263 } else { | |
264 // Desired version is not found in shared location. | |
265 ExtensionAssetsManagerHelper* helper = | |
266 ExtensionAssetsManagerHelper::GetInstance(); | |
267 if (helper->RecordSharedInstall(id, version, unpacked_extension_root, | |
268 local_install_dir, profile, callback)) { | |
269 // There is no install in progress for given <id, version> so run install. | |
270 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
271 FROM_HERE, | |
272 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension, | |
273 id, | |
274 version, | |
275 unpacked_extension_root)); | |
276 } | |
277 } | |
278 } | |
279 | |
280 // static | |
281 void ExtensionAssetsManagerChromeOS::InstallSharedExtension( | |
282 const std::string& id, | |
283 const std::string& version, | |
284 const base::FilePath& unpacked_extension_root) { | |
285 base::FilePath shared_install_dir = GetSharedInstallDir(); | |
286 base::FilePath shared_version_dir = file_util::InstallExtension( | |
287 unpacked_extension_root, id, version, shared_install_dir); | |
288 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
289 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone, | |
290 id, version, shared_version_dir)); | |
291 } | |
292 | |
293 // static | |
294 void ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone( | |
295 const std::string& id, | |
296 const std::string& version, | |
297 const base::FilePath& shared_version_dir) { | |
298 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
299 | |
300 ExtensionAssetsManagerHelper* helper = | |
301 ExtensionAssetsManagerHelper::GetInstance(); | |
302 ExtensionAssetsManagerHelper::PendingInstallList pending_installs; | |
303 helper->SharedInstallDone(id, version, &pending_installs); | |
304 | |
305 if (shared_version_dir.empty()) { | |
306 // Installation to shared location failed, try local dir. | |
307 // TODO(dpolukhin): add UMA stats reporting. | |
308 for (size_t i = 0; i < pending_installs.size(); i++) { | |
309 ExtensionAssetsManagerHelper::PendingInstallInfo& info = | |
310 pending_installs[i]; | |
311 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( | |
312 FROM_HERE, | |
313 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension, | |
314 id, | |
315 version, | |
316 info.unpacked_extension_root, | |
317 info.local_install_dir, | |
318 info.callback)); | |
319 } | |
320 return; | |
321 } | |
322 | |
323 PrefService* local_state = g_browser_process->local_state(); | |
324 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); | |
325 base::DictionaryValue* extension_info = NULL; | |
326 if (!shared_extensions->GetDictionary(id, &extension_info)) { | |
327 extension_info = new base::DictionaryValue; | |
328 shared_extensions->Set(id, extension_info); | |
329 } | |
330 | |
331 CHECK(!shared_extensions->HasKey(version)); | |
332 base::DictionaryValue* version_info = new base::DictionaryValue; | |
333 extension_info->SetWithoutPathExpansion(version, version_info); | |
334 version_info->SetString(kSharedExtensionPath, shared_version_dir.value()); | |
335 | |
336 base::ListValue* users = new base::ListValue; | |
337 version_info->Set(kSharedExtensionUsers, users); | |
338 for (size_t i = 0; i < pending_installs.size(); i++) { | |
339 ExtensionAssetsManagerHelper::PendingInstallInfo& info = | |
340 pending_installs[i]; | |
341 users->AppendString(info.profile->GetProfileName()); | |
342 | |
343 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( | |
344 FROM_HERE, | |
345 base::Bind(info.callback, shared_version_dir)); | |
346 } | |
347 } | |
348 | |
349 // static | |
350 void ExtensionAssetsManagerChromeOS::InstallLocalExtension( | |
351 const std::string& id, | |
352 const std::string& version, | |
353 const base::FilePath& unpacked_extension_root, | |
354 const base::FilePath& local_install_dir, | |
355 InstallExtensionCallback callback) { | |
356 callback.Run(file_util::InstallExtension( | |
357 unpacked_extension_root, id, version, local_install_dir)); | |
358 } | |
359 | |
360 // static | |
361 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused( | |
362 const std::string& id, | |
363 Profile* profile) { | |
364 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
365 | |
366 PrefService* local_state = g_browser_process->local_state(); | |
367 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); | |
368 base::DictionaryValue* extension_info = NULL; | |
369 if (!shared_extensions->GetDictionary(id, &extension_info)) { | |
370 NOTREACHED(); | |
371 return; | |
372 } | |
373 | |
374 std::vector<std::string> versions; | |
375 versions.reserve(extension_info->size()); | |
376 for (base::DictionaryValue::Iterator it(*extension_info); | |
377 !it.IsAtEnd(); | |
378 it.Advance()) { | |
379 versions.push_back(it.key()); | |
380 } | |
381 | |
382 base::StringValue user_name(profile->GetProfileName()); | |
383 for (std::vector<std::string>::const_iterator it = versions.begin(); | |
384 it != versions.end(); it++) { | |
385 base::DictionaryValue* version_info = NULL; | |
386 if (!extension_info->GetDictionaryWithoutPathExpansion(*it, | |
387 &version_info)) { | |
388 NOTREACHED(); | |
389 continue; | |
390 } | |
391 base::ListValue* users = NULL; | |
392 if (!version_info->GetList(kSharedExtensionUsers, &users)) { | |
393 NOTREACHED(); | |
394 continue; | |
395 } | |
396 if (users->Remove(user_name, NULL) && !users->GetSize()) { | |
397 std::string shared_path; | |
398 if (!version_info->GetString(kSharedExtensionPath, &shared_path)) { | |
399 NOTREACHED(); | |
400 continue; | |
401 } | |
402 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
403 FROM_HERE, | |
404 base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion, | |
405 base::FilePath(shared_path))); | |
406 extension_info->RemoveWithoutPathExpansion(*it, NULL); | |
407 } | |
408 } | |
409 // Don't remove extension dir in shared location. It will be removed by GC | |
410 // when it is safe to do avoid race condition between uninstall extension | |
411 // used by single user and installing the same extension for other user. | |
asargent_no_longer_on_chrome
2014/05/28 21:44:49
A few grammar nits with this sentence.
did you m
Dmitry Polukhin
2014/05/28 21:59:19
Done.
| |
412 } | |
413 | |
414 // static | |
415 void ExtensionAssetsManagerChromeOS::DeleteSharedVersion( | |
416 const base::FilePath& shared_version_dir) { | |
417 CHECK(GetSharedInstallDir().IsParent(shared_version_dir)); | |
418 base::DeleteFile(shared_version_dir, true); // recursive. | |
419 } | |
420 | |
421 } // namespace extensions | |
OLD | NEW |