Update broken references to image assets
[chromium-blink-merge.git] / chrome / browser / extensions / extension_sync_service.cc
blobcb8535681b989bbd029a8bc3f4bd4d5b761109cb
1 // Copyright 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.
5 #include "chrome/browser/extensions/extension_sync_service.h"
7 #include "base/basictypes.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/threading/thread_restrictions.h"
10 #include "chrome/browser/extensions/bookmark_app_helper.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/extension_sync_data.h"
13 #include "chrome/browser/extensions/extension_sync_service_factory.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/extensions/launch_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/sync/glue/sync_start_util.h"
18 #include "chrome/common/extensions/extension_constants.h"
19 #include "chrome/common/extensions/sync_helper.h"
20 #include "chrome/common/web_application_info.h"
21 #include "components/sync_driver/sync_prefs.h"
22 #include "extensions/browser/app_sorting.h"
23 #include "extensions/browser/extension_prefs.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/extension_util.h"
27 #include "extensions/browser/uninstall_reason.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #include "extensions/common/image_util.h"
31 #include "sync/api/sync_change.h"
32 #include "sync/api/sync_error_factory.h"
34 using extensions::AppSorting;
35 using extensions::Extension;
36 using extensions::ExtensionPrefs;
37 using extensions::ExtensionRegistry;
38 using extensions::ExtensionSet;
39 using extensions::ExtensionSyncData;
40 using extensions::ExtensionSystem;
41 using extensions::SyncBundle;
43 namespace {
45 void OnWebApplicationInfoLoaded(
46 WebApplicationInfo synced_info,
47 base::WeakPtr<ExtensionService> extension_service,
48 const WebApplicationInfo& loaded_info) {
49 DCHECK_EQ(synced_info.app_url, loaded_info.app_url);
51 if (!extension_service)
52 return;
54 // Use the old icons if they exist.
55 synced_info.icons = loaded_info.icons;
56 CreateOrUpdateBookmarkApp(extension_service.get(), &synced_info);
59 // Returns the pref value for "all urls enabled" for the given extension id.
60 ExtensionSyncData::OptionalBoolean GetAllowedOnAllUrlsOptionalBoolean(
61 const std::string& extension_id,
62 content::BrowserContext* context) {
63 bool allowed_on_all_urls =
64 extensions::util::AllowedScriptingOnAllUrls(extension_id, context);
65 // If the extension is not allowed on all urls (which is not the default),
66 // then we have to sync the preference.
67 if (!allowed_on_all_urls)
68 return ExtensionSyncData::BOOLEAN_FALSE;
70 // If the user has explicitly set a value, then we sync it.
71 if (extensions::util::HasSetAllowedScriptingOnAllUrls(extension_id, context))
72 return ExtensionSyncData::BOOLEAN_TRUE;
74 // Otherwise, unset.
75 return ExtensionSyncData::BOOLEAN_UNSET;
78 // Returns true if the sync type of |extension| matches |type|.
79 bool IsCorrectSyncType(const Extension& extension, syncer::ModelType type) {
80 return (type == syncer::EXTENSIONS && extension.is_extension()) ||
81 (type == syncer::APPS && extension.is_app());
84 syncer::SyncDataList ToSyncerSyncDataList(
85 const std::vector<ExtensionSyncData>& data) {
86 syncer::SyncDataList result;
87 result.reserve(data.size());
88 for (const ExtensionSyncData& item : data)
89 result.push_back(item.GetSyncData());
90 return result;
93 } // namespace
95 ExtensionSyncService::ExtensionSyncService(Profile* profile)
96 : profile_(profile),
97 flare_(sync_start_util::GetFlareForSyncableService(profile->GetPath())) {
100 ExtensionSyncService::~ExtensionSyncService() {}
102 // static
103 ExtensionSyncService* ExtensionSyncService::Get(
104 content::BrowserContext* context) {
105 return ExtensionSyncServiceFactory::GetForBrowserContext(context);
108 void ExtensionSyncService::SyncUninstallExtension(
109 const Extension& extension) {
110 if (!extensions::util::ShouldSync(&extension, profile_))
111 return;
113 // TODO(tim): If we get here and IsSyncing is false, this will cause
114 // "back from the dead" style bugs, because sync will add-back the extension
115 // that was uninstalled here when MergeDataAndStartSyncing is called.
116 // See crbug.com/256795.
117 syncer::ModelType type =
118 extension.is_app() ? syncer::APPS : syncer::EXTENSIONS;
119 SyncBundle* bundle = GetSyncBundle(type);
120 if (bundle->IsSyncing()) {
121 bundle->PushSyncDeletion(extension.id(),
122 CreateSyncData(extension).GetSyncData());
123 } else if (extension_service()->is_ready() && !flare_.is_null()) {
124 flare_.Run(type); // Tell sync to start ASAP.
128 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
129 const Extension& extension) {
130 if (!extensions::util::ShouldSync(&extension, profile_))
131 return;
133 syncer::ModelType type =
134 extension.is_app() ? syncer::APPS : syncer::EXTENSIONS;
135 SyncBundle* bundle = GetSyncBundle(type);
136 if (bundle->IsSyncing()) {
137 bundle->PushSyncAddOrUpdate(extension.id(),
138 CreateSyncData(extension).GetSyncData());
139 DCHECK(!ExtensionPrefs::Get(profile_)->NeedsSync(extension.id()));
140 } else {
141 ExtensionPrefs::Get(profile_)->SetNeedsSync(extension.id(), true);
142 if (extension_service()->is_ready() && !flare_.is_null())
143 flare_.Run(type); // Tell sync to start ASAP.
147 syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
148 syncer::ModelType type,
149 const syncer::SyncDataList& initial_sync_data,
150 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
151 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
152 CHECK(sync_processor.get());
153 LOG_IF(FATAL, type != syncer::EXTENSIONS && type != syncer::APPS)
154 << "Got " << type << " ModelType";
156 SyncBundle* bundle = GetSyncBundle(type);
157 bundle->StartSyncing(sync_processor.Pass());
159 // Apply the initial sync data, filtering out any items where we have more
160 // recent local changes. Also tell the SyncBundle the extension IDs.
161 for (const syncer::SyncData& sync_data : initial_sync_data) {
162 scoped_ptr<ExtensionSyncData> extension_sync_data(
163 ExtensionSyncData::CreateFromSyncData(sync_data));
164 // If the extension has local state that needs to be synced, ignore this
165 // change (we assume the local state is more recent).
166 if (extension_sync_data &&
167 !ExtensionPrefs::Get(profile_)->NeedsSync(extension_sync_data->id())) {
168 ApplySyncData(*extension_sync_data);
172 // Now push those local changes to sync.
173 // TODO(treib,kalman): We should only have to send out changes for extensions
174 // which have NeedsSync set (i.e. |GetLocalSyncDataList(type, false)|). That
175 // makes some sync_integration_tests fail though - figure out why and fix it!
176 std::vector<ExtensionSyncData> data_list = GetLocalSyncDataList(type, true);
177 bundle->PushSyncDataList(ToSyncerSyncDataList(data_list));
178 for (const ExtensionSyncData& data : data_list)
179 ExtensionPrefs::Get(profile_)->SetNeedsSync(data.id(), false);
181 if (type == syncer::APPS)
182 ExtensionSystem::Get(profile_)->app_sorting()->FixNTPOrdinalCollisions();
184 return syncer::SyncMergeResult(type);
187 void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
188 GetSyncBundle(type)->Reset();
191 syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
192 syncer::ModelType type) const {
193 const SyncBundle* bundle = GetSyncBundle(type);
194 if (!bundle->IsSyncing())
195 return syncer::SyncDataList();
197 std::vector<ExtensionSyncData> sync_data_list =
198 GetLocalSyncDataList(type, true);
200 // Add pending data (where the local extension is either not installed yet or
201 // outdated).
202 std::vector<ExtensionSyncData> pending_extensions = bundle->GetPendingData();
203 sync_data_list.insert(sync_data_list.begin(),
204 pending_extensions.begin(),
205 pending_extensions.end());
207 return ToSyncerSyncDataList(sync_data_list);
210 syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
211 const tracked_objects::Location& from_here,
212 const syncer::SyncChangeList& change_list) {
213 for (const syncer::SyncChange& sync_change : change_list) {
214 scoped_ptr<ExtensionSyncData> extension_sync_data(
215 ExtensionSyncData::CreateFromSyncChange(sync_change));
216 if (extension_sync_data)
217 ApplySyncData(*extension_sync_data);
220 ExtensionSystem::Get(profile_)->app_sorting()->FixNTPOrdinalCollisions();
222 return syncer::SyncError();
225 ExtensionSyncData ExtensionSyncService::CreateSyncData(
226 const Extension& extension) const {
227 const ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
228 bool enabled = extension_service()->IsExtensionEnabled(extension.id());
229 int disable_reasons = extension_prefs->GetDisableReasons(extension.id());
230 bool incognito_enabled = extensions::util::IsIncognitoEnabled(extension.id(),
231 profile_);
232 bool remote_install =
233 extension_prefs->HasDisableReason(extension.id(),
234 Extension::DISABLE_REMOTE_INSTALL);
235 ExtensionSyncData::OptionalBoolean allowed_on_all_url =
236 GetAllowedOnAllUrlsOptionalBoolean(extension.id(), profile_);
237 if (extension.is_app()) {
238 AppSorting* app_sorting = ExtensionSystem::Get(profile_)->app_sorting();
239 return ExtensionSyncData(
240 extension, enabled, disable_reasons, incognito_enabled, remote_install,
241 allowed_on_all_url,
242 app_sorting->GetAppLaunchOrdinal(extension.id()),
243 app_sorting->GetPageOrdinal(extension.id()),
244 extensions::GetLaunchTypePrefValue(extension_prefs, extension.id()));
246 return ExtensionSyncData(
247 extension, enabled, disable_reasons, incognito_enabled, remote_install,
248 allowed_on_all_url);
251 bool ExtensionSyncService::ApplySyncData(
252 const ExtensionSyncData& extension_sync_data) {
253 syncer::ModelType type = extension_sync_data.is_app() ? syncer::APPS
254 : syncer::EXTENSIONS;
255 SyncBundle* bundle = GetSyncBundle(type);
256 bundle->ApplySyncData(extension_sync_data);
258 const std::string& id = extension_sync_data.id();
260 if (extension_sync_data.is_app()) {
261 if (extension_sync_data.app_launch_ordinal().IsValid() &&
262 extension_sync_data.page_ordinal().IsValid()) {
263 AppSorting* app_sorting = ExtensionSystem::Get(profile_)->app_sorting();
264 app_sorting->SetAppLaunchOrdinal(
266 extension_sync_data.app_launch_ordinal());
267 app_sorting->SetPageOrdinal(id, extension_sync_data.page_ordinal());
270 // The corresponding validation of this value during ExtensionSyncData
271 // population is in ExtensionSyncData::ToAppSpecifics.
272 if (extension_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
273 extension_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
274 extensions::SetLaunchType(
275 profile_, id, extension_sync_data.launch_type());
278 if (!extension_sync_data.bookmark_app_url().empty())
279 ApplyBookmarkAppSyncData(extension_sync_data);
282 if (!ApplyExtensionSyncDataHelper(extension_sync_data, type)) {
283 bundle->AddPendingExtension(id, extension_sync_data);
284 extension_service()->CheckForUpdatesSoon();
285 return false;
288 return true;
291 void ExtensionSyncService::ApplyBookmarkAppSyncData(
292 const ExtensionSyncData& extension_sync_data) {
293 DCHECK(extension_sync_data.is_app());
295 // Process bookmark app sync if necessary.
296 GURL bookmark_app_url(extension_sync_data.bookmark_app_url());
297 if (!bookmark_app_url.is_valid() ||
298 extension_sync_data.uninstalled()) {
299 return;
302 const Extension* extension =
303 extension_service()->GetInstalledExtension(extension_sync_data.id());
305 // Return if there are no bookmark app details that need updating.
306 if (extension &&
307 extension->non_localized_name() == extension_sync_data.name() &&
308 extension->description() ==
309 extension_sync_data.bookmark_app_description()) {
310 return;
313 WebApplicationInfo web_app_info;
314 web_app_info.app_url = bookmark_app_url;
315 web_app_info.title = base::UTF8ToUTF16(extension_sync_data.name());
316 web_app_info.description =
317 base::UTF8ToUTF16(extension_sync_data.bookmark_app_description());
318 if (!extension_sync_data.bookmark_app_icon_color().empty()) {
319 extensions::image_util::ParseCSSColorString(
320 extension_sync_data.bookmark_app_icon_color(),
321 &web_app_info.generated_icon_color);
323 for (const auto& icon : extension_sync_data.linked_icons()) {
324 WebApplicationInfo::IconInfo icon_info;
325 icon_info.url = icon.url;
326 icon_info.width = icon.size;
327 icon_info.height = icon.size;
328 web_app_info.icons.push_back(icon_info);
331 // If the bookmark app already exists, keep the old icons.
332 if (!extension) {
333 CreateOrUpdateBookmarkApp(extension_service(), &web_app_info);
334 } else {
335 GetWebApplicationInfoFromApp(profile_,
336 extension,
337 base::Bind(&OnWebApplicationInfoLoaded,
338 web_app_info,
339 extension_service()->AsWeakPtr()));
343 void ExtensionSyncService::SetSyncStartFlareForTesting(
344 const syncer::SyncableService::StartSyncFlare& flare) {
345 flare_ = flare;
348 ExtensionService* ExtensionSyncService::extension_service() const {
349 return ExtensionSystem::Get(profile_)->extension_service();
352 SyncBundle* ExtensionSyncService::GetSyncBundle(syncer::ModelType type) {
353 return const_cast<SyncBundle*>(
354 const_cast<const ExtensionSyncService&>(*this).GetSyncBundle(type));
357 const SyncBundle* ExtensionSyncService::GetSyncBundle(
358 syncer::ModelType type) const {
359 return (type == syncer::APPS) ? &app_sync_bundle_ : &extension_sync_bundle_;
362 std::vector<ExtensionSyncData> ExtensionSyncService::GetLocalSyncDataList(
363 syncer::ModelType type,
364 bool include_everything) const {
365 // Collect the local state. FillSyncDataList will filter out extensions for
366 // which we have pending data that should be used instead.
367 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
368 std::vector<ExtensionSyncData> data;
369 // TODO(treib, kalman): Should we be including blacklisted/blocked extensions
370 // here? I.e. just calling registry->GeneratedInstalledExtensionsSet()?
371 // It would be more consistent, but the danger is that the black/blocklist
372 // hasn't been updated on all clients by the time sync has kicked in -
373 // so it's safest not to. Take care to add any other extension lists here
374 // in the future if they are added.
375 FillSyncDataList(
376 registry->enabled_extensions(), type, include_everything, &data);
377 FillSyncDataList(
378 registry->disabled_extensions(), type, include_everything, &data);
379 FillSyncDataList(
380 registry->terminated_extensions(), type, include_everything, &data);
381 return data;
384 void ExtensionSyncService::FillSyncDataList(
385 const ExtensionSet& extensions,
386 syncer::ModelType type,
387 bool include_everything,
388 std::vector<ExtensionSyncData>* sync_data_list) const {
389 const SyncBundle* bundle = GetSyncBundle(type);
390 for (const scoped_refptr<const Extension>& extension : extensions) {
391 if (IsCorrectSyncType(*extension, type) &&
392 extensions::util::ShouldSync(extension.get(), profile_) &&
393 !bundle->HasPendingExtensionId(extension->id()) &&
394 (include_everything ||
395 ExtensionPrefs::Get(profile_)->NeedsSync(extension->id()))) {
396 sync_data_list->push_back(CreateSyncData(*extension));
401 bool ExtensionSyncService::ApplyExtensionSyncDataHelper(
402 const ExtensionSyncData& extension_sync_data,
403 syncer::ModelType type) {
404 const std::string& id = extension_sync_data.id();
405 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
406 const Extension* extension = registry->GetInstalledExtension(id);
408 // TODO(bolms): we should really handle this better. The particularly bad
409 // case is where an app becomes an extension or vice versa, and we end up with
410 // a zombie extension that won't go away.
411 if (extension && !IsCorrectSyncType(*extension, type))
412 return true;
414 // Handle uninstalls first.
415 if (extension_sync_data.uninstalled()) {
416 if (!ExtensionService::UninstallExtensionHelper(
417 extension_service(), id, extensions::UNINSTALL_REASON_SYNC)) {
418 LOG(WARNING) << "Could not uninstall extension " << id << " for sync";
420 return true;
423 // Extension from sync was uninstalled by the user as external extensions.
424 // Honor user choice and skip installation/enabling.
425 if (ExtensionPrefs::Get(profile_)->IsExternalExtensionUninstalled(id)) {
426 LOG(WARNING) << "Extension with id " << id
427 << " from sync was uninstalled as external extension";
428 return true;
431 int version_compare_result = extension ?
432 extension->version()->CompareTo(extension_sync_data.version()) : 0;
434 // Set user settings.
435 if (extension_sync_data.enabled()) {
436 DCHECK(!extension_sync_data.disable_reasons());
438 // Only grant permissions if the sync data explicitly sets the disable
439 // reasons to Extension::DISABLE_NONE (as opposed to the legacy (<M45) case
440 // where they're not set at all), and if the version from sync matches our
441 // local one. Otherwise we just enable it without granting permissions. If
442 // any permissions are missing, CheckPermissionsIncrease will soon disable
443 // it again.
444 bool grant_permissions =
445 extension_sync_data.supports_disable_reasons() &&
446 extension && (version_compare_result == 0);
447 if (grant_permissions)
448 extension_service()->GrantPermissionsAndEnableExtension(extension);
449 else
450 extension_service()->EnableExtension(id);
451 } else {
452 int disable_reasons = extension_sync_data.disable_reasons();
453 if (extension_sync_data.remote_install()) {
454 if (!(disable_reasons & Extension::DISABLE_REMOTE_INSTALL)) {
455 // In the non-legacy case (>=M45) where disable reasons are synced at
456 // all, DISABLE_REMOTE_INSTALL should be among them already.
457 DCHECK(!extension_sync_data.supports_disable_reasons());
458 disable_reasons |= Extension::DISABLE_REMOTE_INSTALL;
460 } else if (!extension_sync_data.supports_disable_reasons()) {
461 // Legacy case (<M45), from before we synced disable reasons (see
462 // crbug.com/484214).
463 disable_reasons = Extension::DISABLE_UNKNOWN_FROM_SYNC;
466 // In the non-legacy case (>=M45), clear any existing disable reasons first.
467 // Otherwise sync can't remove just some of them.
468 if (extension_sync_data.supports_disable_reasons())
469 ExtensionPrefs::Get(profile_)->ClearDisableReasons(id);
471 extension_service()->DisableExtension(id, disable_reasons);
474 // We need to cache some information here because setting the incognito flag
475 // invalidates the |extension| pointer (it reloads the extension).
476 bool extension_installed = (extension != NULL);
478 // If the target extension has already been installed ephemerally, it can
479 // be promoted to a regular installed extension and downloading from the Web
480 // Store is not necessary.
481 if (extension && extensions::util::IsEphemeralApp(id, profile_))
482 extension_service()->PromoteEphemeralApp(extension, true);
484 // Update the incognito flag.
485 extensions::util::SetIsIncognitoEnabled(
486 id, profile_, extension_sync_data.incognito_enabled());
487 extension = NULL; // No longer safe to use.
489 // Update the all urls flag.
490 if (extension_sync_data.all_urls_enabled() !=
491 ExtensionSyncData::BOOLEAN_UNSET) {
492 bool allowed = extension_sync_data.all_urls_enabled() ==
493 ExtensionSyncData::BOOLEAN_TRUE;
494 extensions::util::SetAllowedScriptingOnAllUrls(id, profile_, allowed);
497 if (extension_installed) {
498 // If the extension is already installed, check if it's outdated.
499 if (version_compare_result < 0) {
500 // Extension is outdated.
501 return false;
503 } else {
504 CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
505 if (!extension_service()->pending_extension_manager()->AddFromSync(
507 extension_sync_data.update_url(),
508 extensions::sync_helper::IsSyncable,
509 extension_sync_data.remote_install(),
510 extension_sync_data.installed_by_custodian())) {
511 LOG(WARNING) << "Could not add pending extension for " << id;
512 // This means that the extension is already pending installation, with a
513 // non-INTERNAL location. Add to pending_sync_data, even though it will
514 // never be removed (we'll never install a syncable version of the
515 // extension), so that GetAllSyncData() continues to send it.
517 // Track pending extensions so that we can return them in GetAllSyncData().
518 return false;
521 return true;