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 callback.Run(file_util::InstallExtension( | |
148 unpacked_extension_root, | |
149 extension->id(), | |
150 extension->VersionString(), | |
151 local_install_dir)); | |
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(); | |
asargent_no_longer_on_chrome
2014/05/20 21:20:25
I assume that GetProfileName() is guaranteed to be
Dmitry Polukhin
2014/05/20 23:42:16
On Chrome OS it is user email and Chrome OS distin
| |
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(&ExtensionAssetsManagerChromeOS::RunInstallCallback, | |
263 base::FilePath(shared_path), | |
264 callback)); | |
265 } else { | |
266 // Desired version is not found in shared location. | |
267 ExtensionAssetsManagerHelper* helper = | |
268 ExtensionAssetsManagerHelper::GetInstance(); | |
269 if (helper->RecordSharedInstall(id, version, unpacked_extension_root, | |
270 local_install_dir, profile, callback)) { | |
271 // There is no install in progress for given <id, version> so run install. | |
272 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
273 FROM_HERE, | |
274 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension, | |
275 id, | |
276 version, | |
277 unpacked_extension_root)); | |
278 } | |
279 } | |
280 } | |
281 | |
282 // static | |
283 void ExtensionAssetsManagerChromeOS::RunInstallCallback( | |
284 const base::FilePath& shared_version_dir, | |
285 InstallExtensionCallback callback) { | |
286 callback.Run(shared_version_dir); | |
asargent_no_longer_on_chrome
2014/05/20 21:20:25
Since you don't do anything here but dispatch the
Dmitry Polukhin
2014/05/20 23:42:16
Yes, I used to have some extra thing but now it ca
| |
287 } | |
288 | |
289 // static | |
290 void ExtensionAssetsManagerChromeOS::InstallSharedExtension( | |
291 const std::string& id, | |
292 const std::string& version, | |
293 const base::FilePath& unpacked_extension_root) { | |
294 base::FilePath shared_install_dir = GetSharedInstallDir(); | |
295 base::FilePath shared_version_dir = file_util::InstallExtension( | |
296 unpacked_extension_root, id, version, shared_install_dir); | |
297 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
298 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone, | |
299 id, version, shared_version_dir)); | |
300 } | |
301 | |
302 // static | |
303 void ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone( | |
304 const std::string& id, | |
305 const std::string& version, | |
306 const base::FilePath& shared_version_dir) { | |
307 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
308 | |
309 ExtensionAssetsManagerHelper* helper = | |
310 ExtensionAssetsManagerHelper::GetInstance(); | |
311 ExtensionAssetsManagerHelper::PendingInstallList pending_installs; | |
312 helper->SharedInstallDone(id, version, &pending_installs); | |
313 | |
314 PrefService* local_state = g_browser_process->local_state(); | |
315 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); | |
316 base::DictionaryValue* extension_info = NULL; | |
317 base::DictionaryValue* version_info = NULL; | |
318 base::ListValue* users = NULL; | |
319 | |
320 for (size_t i = 0; i < pending_installs.size(); i++) { | |
321 ExtensionAssetsManagerHelper::PendingInstallInfo& info = | |
322 pending_installs[i]; | |
323 if (shared_version_dir.empty()) { | |
324 // Installation to shared location failed, try local dir. | |
325 // TODO(dpolukhin): add UMA stats reporting. | |
326 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( | |
327 FROM_HERE, | |
328 base::Bind(&ExtensionAssetsManagerChromeOS::SharedInstallFailed, | |
329 id, | |
330 version, | |
331 info.unpacked_extension_root, | |
332 info.local_install_dir, | |
333 info.callback)); | |
334 } else { | |
335 if (!extension_info && | |
asargent_no_longer_on_chrome
2014/05/20 21:20:25
It looks like extension_info gets initialized to N
Dmitry Polukhin
2014/05/20 23:42:16
If I move declaration to for-loop it will be re-in
| |
336 !shared_extensions->GetDictionary(id, &extension_info)) { | |
337 extension_info = new base::DictionaryValue; | |
338 shared_extensions->Set(id, extension_info); | |
339 } | |
340 if (!version_info && | |
asargent_no_longer_on_chrome
2014/05/20 21:20:25
similar comment about version_info
Dmitry Polukhin
2014/05/20 23:42:16
Done.
| |
341 !extension_info->GetDictionaryWithoutPathExpansion(version, | |
342 &version_info)) { | |
343 version_info = new base::DictionaryValue; | |
344 extension_info->SetWithoutPathExpansion(version, version_info); | |
345 | |
346 CHECK(!version_info->HasKey(kSharedExtensionPath)); | |
347 CHECK(!version_info->HasKey(kSharedExtensionUsers)); | |
348 version_info->SetString(kSharedExtensionPath, | |
349 shared_version_dir.value()); | |
350 } | |
351 if (!users) { | |
352 users = new base::ListValue; | |
353 version_info->Set(kSharedExtensionUsers, users); | |
354 } | |
355 users->AppendString(info.profile->GetProfileName()); | |
356 | |
357 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( | |
358 FROM_HERE, | |
359 base::Bind(&ExtensionAssetsManagerChromeOS::RunInstallCallback, | |
360 shared_version_dir, | |
361 info.callback)); | |
362 } | |
363 } | |
364 } | |
365 | |
366 // static | |
367 void ExtensionAssetsManagerChromeOS::SharedInstallFailed( | |
368 const std::string& id, | |
369 const std::string& version, | |
370 const base::FilePath& unpacked_extension_root, | |
371 const base::FilePath& local_install_dir, | |
372 InstallExtensionCallback callback) { | |
373 callback.Run(file_util::InstallExtension( | |
374 unpacked_extension_root, id, version, local_install_dir)); | |
375 } | |
376 | |
377 // static | |
378 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused( | |
379 const std::string& id, | |
380 Profile* profile) { | |
381 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
382 | |
383 PrefService* local_state = g_browser_process->local_state(); | |
384 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); | |
385 base::DictionaryValue* extension_info = NULL; | |
386 if (!shared_extensions->GetDictionary(id, &extension_info)) { | |
387 NOTREACHED(); | |
388 return; | |
389 } | |
390 | |
391 std::vector<std::string> versions; | |
392 versions.reserve(extension_info->size()); | |
393 for (base::DictionaryValue::Iterator it(*extension_info); | |
394 !it.IsAtEnd(); | |
395 it.Advance()) { | |
396 versions.push_back(it.key()); | |
397 } | |
398 | |
399 base::StringValue user_name(profile->GetProfileName()); | |
400 for (std::vector<std::string>::const_iterator it = versions.begin(); | |
401 it != versions.end(); it++) { | |
402 base::DictionaryValue* version_info = NULL; | |
403 if (!extension_info->GetDictionaryWithoutPathExpansion(*it, | |
404 &version_info)) { | |
405 NOTREACHED(); | |
406 continue; | |
407 } | |
408 base::ListValue* users = NULL; | |
409 if (!version_info->GetList(kSharedExtensionUsers, &users)) { | |
410 NOTREACHED(); | |
411 continue; | |
412 } | |
413 if (users->Remove(user_name, NULL) && !users->GetSize()) { | |
414 std::string shared_path; | |
415 if (!version_info->GetString(kSharedExtensionPath, &shared_path)) { | |
416 NOTREACHED(); | |
417 continue; | |
418 } | |
419 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
420 FROM_HERE, | |
421 base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion, | |
422 base::FilePath(shared_path))); | |
423 extension_info->RemoveWithoutPathExpansion(*it, NULL); | |
424 } | |
425 } | |
426 if (!extension_info->size()) { | |
427 shared_extensions->RemoveWithoutPathExpansion(id, NULL); | |
428 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( | |
429 FROM_HERE, | |
430 base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedExtension, id)); | |
asargent_no_longer_on_chrome
2014/05/20 21:20:25
Imagine this scenario:
-We have a version of exte
Dmitry Polukhin
2014/05/20 23:42:16
Yep, we do have race here even between removing gi
Dmitry Polukhin
2014/05/21 00:37:46
I thought more about it and it seems that this rac
| |
431 } | |
432 } | |
433 | |
434 // static | |
435 void ExtensionAssetsManagerChromeOS::DeleteSharedVersion( | |
436 const base::FilePath& shared_version_dir) { | |
437 CHECK(GetSharedInstallDir().IsParent(shared_version_dir)); | |
438 base::DeleteFile(shared_version_dir, true); // recursive. | |
439 } | |
440 | |
441 // static | |
442 void ExtensionAssetsManagerChromeOS::DeleteSharedExtension( | |
443 const std::string& id) { | |
444 file_util::UninstallExtension(GetSharedInstallDir(), id); | |
445 } | |
446 | |
447 } // namespace extensions | |
OLD | NEW |