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"
9 #include "base/basictypes.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/threading/sequenced_worker_pool.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "chrome/browser/extensions/app_sync_data.h"
14 #include "chrome/browser/extensions/bookmark_app_helper.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/extension_sync_data.h"
17 #include "chrome/browser/extensions/extension_sync_service_factory.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/extensions/launch_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/sync/glue/sync_start_util.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
24 #include "chrome/common/extensions/sync_helper.h"
25 #include "chrome/common/web_application_info.h"
26 #include "components/sync_driver/sync_prefs.h"
27 #include "extensions/browser/app_sorting.h"
28 #include "extensions/browser/extension_prefs.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_util.h"
31 #include "extensions/browser/uninstall_reason.h"
32 #include "extensions/common/extension.h"
33 #include "extensions/common/extension_icon_set.h"
34 #include "extensions/common/feature_switch.h"
35 #include "extensions/common/image_util.h"
36 #include "extensions/common/manifest_constants.h"
37 #include "extensions/common/manifest_handlers/icons_handler.h"
38 #include "sync/api/sync_change.h"
39 #include "sync/api/sync_error_factory.h"
40 #include "ui/gfx/image/image_family.h"
42 using extensions::AppSyncData
;
43 using extensions::Extension
;
44 using extensions::ExtensionPrefs
;
45 using extensions::ExtensionRegistry
;
46 using extensions::ExtensionSyncData
;
47 using extensions::FeatureSwitch
;
51 void OnWebApplicationInfoLoaded(
52 WebApplicationInfo synced_info
,
53 base::WeakPtr
<ExtensionService
> extension_service
,
54 const WebApplicationInfo
& loaded_info
) {
55 DCHECK_EQ(synced_info
.app_url
, loaded_info
.app_url
);
57 if (!extension_service
)
60 // Use the old icons if they exist.
61 synced_info
.icons
= loaded_info
.icons
;
62 CreateOrUpdateBookmarkApp(extension_service
.get(), &synced_info
);
65 // Returns the pref value for "all urls enabled" for the given extension id.
66 ExtensionSyncData::OptionalBoolean
GetAllowedOnAllUrlsOptionalBoolean(
67 const std::string
& extension_id
,
68 content::BrowserContext
* context
) {
69 bool allowed_on_all_urls
=
70 extensions::util::AllowedScriptingOnAllUrls(extension_id
, context
);
71 // If the extension is not allowed on all urls (which is not the default),
72 // then we have to sync the preference.
73 if (!allowed_on_all_urls
)
74 return ExtensionSyncData::BOOLEAN_FALSE
;
76 // If the user has explicitly set a value, then we sync it.
77 if (extensions::util::HasSetAllowedScriptingOnAllUrls(extension_id
, context
))
78 return ExtensionSyncData::BOOLEAN_TRUE
;
81 return ExtensionSyncData::BOOLEAN_UNSET
;
86 ExtensionSyncService::ExtensionSyncService(Profile
* profile
,
87 ExtensionPrefs
* extension_prefs
,
88 ExtensionService
* extension_service
)
90 extension_prefs_(extension_prefs
),
91 extension_service_(extension_service
),
92 app_sync_bundle_(this),
93 extension_sync_bundle_(this),
94 pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
95 extension_prefs_
->pref_service())),
98 pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
99 extension_prefs_
->pref_service())),
100 &extension_sync_bundle_
,
101 syncer::EXTENSIONS
) {
102 SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
103 profile_
->GetPath()));
105 extension_service_
->set_extension_sync_service(this);
106 extension_prefs_
->app_sorting()->SetExtensionSyncService(this);
109 ExtensionSyncService::~ExtensionSyncService() {}
112 ExtensionSyncService
* ExtensionSyncService::Get(
113 content::BrowserContext
* context
) {
114 return ExtensionSyncServiceFactory::GetForBrowserContext(context
);
117 syncer::SyncChange
ExtensionSyncService::PrepareToSyncUninstallExtension(
118 const extensions::Extension
* extension
, bool extensions_ready
) {
119 // Extract the data we need for sync now, but don't actually sync until we've
120 // completed the uninstallation.
121 // TODO(tim): If we get here and IsSyncing is false, this will cause
122 // "back from the dead" style bugs, because sync will add-back the extension
123 // that was uninstalled here when MergeDataAndStartSyncing is called.
124 // See crbug.com/256795.
125 if (extensions::util::ShouldSyncApp(extension
, profile_
)) {
126 if (app_sync_bundle_
.IsSyncing())
127 return app_sync_bundle_
.CreateSyncChangeToDelete(extension
);
128 else if (extensions_ready
&& !flare_
.is_null())
129 flare_
.Run(syncer::APPS
); // Tell sync to start ASAP.
130 } else if (extensions::sync_helper::IsSyncableExtension(extension
)) {
131 if (extension_sync_bundle_
.IsSyncing())
132 return extension_sync_bundle_
.CreateSyncChangeToDelete(extension
);
133 else if (extensions_ready
&& !flare_
.is_null())
134 flare_
.Run(syncer::EXTENSIONS
); // Tell sync to start ASAP.
137 return syncer::SyncChange();
140 void ExtensionSyncService::ProcessSyncUninstallExtension(
141 const std::string
& extension_id
,
142 const syncer::SyncChange
& sync_change
) {
143 if (app_sync_bundle_
.HasExtensionId(extension_id
) &&
144 sync_change
.sync_data().GetDataType() == syncer::APPS
) {
145 app_sync_bundle_
.ProcessDeletion(extension_id
, sync_change
);
146 } else if (extension_sync_bundle_
.HasExtensionId(extension_id
) &&
147 sync_change
.sync_data().GetDataType() == syncer::EXTENSIONS
) {
148 extension_sync_bundle_
.ProcessDeletion(extension_id
, sync_change
);
152 void ExtensionSyncService::SyncEnableExtension(
153 const extensions::Extension
& extension
) {
155 // Syncing may not have started yet, so handle pending enables.
156 if (extensions::util::ShouldSyncApp(&extension
, profile_
))
157 pending_app_enables_
.OnExtensionEnabled(extension
.id());
159 if (extensions::util::ShouldSyncExtension(&extension
, profile_
))
160 pending_extension_enables_
.OnExtensionEnabled(extension
.id());
162 SyncExtensionChangeIfNeeded(extension
);
165 void ExtensionSyncService::SyncDisableExtension(
166 const extensions::Extension
& extension
) {
168 // Syncing may not have started yet, so handle pending enables.
169 if (extensions::util::ShouldSyncApp(&extension
, profile_
))
170 pending_app_enables_
.OnExtensionDisabled(extension
.id());
172 if (extensions::util::ShouldSyncExtension(&extension
, profile_
))
173 pending_extension_enables_
.OnExtensionDisabled(extension
.id());
175 SyncExtensionChangeIfNeeded(extension
);
178 syncer::SyncMergeResult
ExtensionSyncService::MergeDataAndStartSyncing(
179 syncer::ModelType type
,
180 const syncer::SyncDataList
& initial_sync_data
,
181 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
182 scoped_ptr
<syncer::SyncErrorFactory
> sync_error_factory
) {
183 CHECK(sync_processor
.get());
184 CHECK(sync_error_factory
.get());
187 case syncer::EXTENSIONS
:
188 extension_sync_bundle_
.SetupSync(sync_processor
.release(),
189 sync_error_factory
.release(),
191 pending_extension_enables_
.OnSyncStarted(extension_service_
);
195 app_sync_bundle_
.SetupSync(sync_processor
.release(),
196 sync_error_factory
.release(),
198 pending_app_enables_
.OnSyncStarted(extension_service_
);
202 LOG(FATAL
) << "Got " << type
<< " ModelType";
205 // Process local extensions.
206 // TODO(yoz): Determine whether pending extensions should be considered too.
207 // See crbug.com/104399.
208 syncer::SyncDataList sync_data_list
= GetAllSyncData(type
);
209 syncer::SyncChangeList sync_change_list
;
210 for (syncer::SyncDataList::const_iterator i
= sync_data_list
.begin();
211 i
!= sync_data_list
.end();
214 case syncer::EXTENSIONS
:
215 sync_change_list
.push_back(
216 extension_sync_bundle_
.CreateSyncChange(*i
));
219 sync_change_list
.push_back(app_sync_bundle_
.CreateSyncChange(*i
));
222 LOG(FATAL
) << "Got " << type
<< " ModelType";
227 if (type
== syncer::EXTENSIONS
) {
228 extension_sync_bundle_
.ProcessSyncChangeList(sync_change_list
);
229 } else if (type
== syncer::APPS
) {
230 app_sync_bundle_
.ProcessSyncChangeList(sync_change_list
);
233 return syncer::SyncMergeResult(type
);
236 void ExtensionSyncService::StopSyncing(syncer::ModelType type
) {
237 if (type
== syncer::APPS
) {
238 app_sync_bundle_
.Reset();
239 } else if (type
== syncer::EXTENSIONS
) {
240 extension_sync_bundle_
.Reset();
244 syncer::SyncDataList
ExtensionSyncService::GetAllSyncData(
245 syncer::ModelType type
) const {
246 if (type
== syncer::EXTENSIONS
)
247 return extension_sync_bundle_
.GetAllSyncData();
248 if (type
== syncer::APPS
)
249 return app_sync_bundle_
.GetAllSyncData();
251 // We should only get sync data for extensions and apps.
254 return syncer::SyncDataList();
257 syncer::SyncError
ExtensionSyncService::ProcessSyncChanges(
258 const tracked_objects::Location
& from_here
,
259 const syncer::SyncChangeList
& change_list
) {
260 for (syncer::SyncChangeList::const_iterator i
= change_list
.begin();
261 i
!= change_list
.end();
263 syncer::ModelType type
= i
->sync_data().GetDataType();
264 if (type
== syncer::EXTENSIONS
) {
265 scoped_ptr
<ExtensionSyncData
> extension_data(
266 ExtensionSyncData::CreateFromSyncChange(*i
));
267 if (extension_data
.get())
268 extension_sync_bundle_
.ProcessSyncChange(*extension_data
);
269 } else if (type
== syncer::APPS
) {
270 scoped_ptr
<AppSyncData
> app_data(AppSyncData::CreateFromSyncChange(*i
));
272 app_sync_bundle_
.ProcessSyncChange(*app_data
);
276 extension_prefs_
->app_sorting()->FixNTPOrdinalCollisions();
278 return syncer::SyncError();
281 ExtensionSyncData
ExtensionSyncService::GetExtensionSyncData(
282 const Extension
& extension
) const {
283 return ExtensionSyncData(
285 extension_service_
->IsExtensionEnabled(extension
.id()),
286 extensions::util::IsIncognitoEnabled(extension
.id(), profile_
),
287 extension_prefs_
->HasDisableReason(extension
.id(),
288 Extension::DISABLE_REMOTE_INSTALL
),
289 GetAllowedOnAllUrlsOptionalBoolean(extension
.id(), profile_
));
292 AppSyncData
ExtensionSyncService::GetAppSyncData(
293 const Extension
& extension
) const {
295 extension
, extension_service_
->IsExtensionEnabled(extension
.id()),
296 extensions::util::IsIncognitoEnabled(extension
.id(), profile_
),
297 extension_prefs_
->HasDisableReason(extension
.id(),
298 Extension::DISABLE_REMOTE_INSTALL
),
299 GetAllowedOnAllUrlsOptionalBoolean(extension
.id(), profile_
),
300 extension_prefs_
->app_sorting()->GetAppLaunchOrdinal(extension
.id()),
301 extension_prefs_
->app_sorting()->GetPageOrdinal(extension
.id()),
302 extensions::GetLaunchTypePrefValue(extension_prefs_
, extension
.id()));
305 std::vector
<ExtensionSyncData
>
306 ExtensionSyncService::GetExtensionSyncDataList() const {
307 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
308 std::vector
<ExtensionSyncData
> extension_sync_list
;
309 extension_sync_bundle_
.GetExtensionSyncDataListHelper(
310 registry
->enabled_extensions(), &extension_sync_list
);
311 extension_sync_bundle_
.GetExtensionSyncDataListHelper(
312 registry
->disabled_extensions(), &extension_sync_list
);
313 extension_sync_bundle_
.GetExtensionSyncDataListHelper(
314 registry
->terminated_extensions(), &extension_sync_list
);
316 std::vector
<ExtensionSyncData
> pending_extensions
=
317 extension_sync_bundle_
.GetPendingData();
318 extension_sync_list
.insert(extension_sync_list
.begin(),
319 pending_extensions
.begin(),
320 pending_extensions
.end());
322 return extension_sync_list
;
325 std::vector
<AppSyncData
> ExtensionSyncService::GetAppSyncDataList() const {
326 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
327 std::vector
<AppSyncData
> app_sync_list
;
328 app_sync_bundle_
.GetAppSyncDataListHelper(
329 registry
->enabled_extensions(), &app_sync_list
);
330 app_sync_bundle_
.GetAppSyncDataListHelper(
331 registry
->disabled_extensions(), &app_sync_list
);
332 app_sync_bundle_
.GetAppSyncDataListHelper(
333 registry
->terminated_extensions(), &app_sync_list
);
335 std::vector
<AppSyncData
> pending_apps
= app_sync_bundle_
.GetPendingData();
336 app_sync_list
.insert(app_sync_list
.begin(),
337 pending_apps
.begin(),
340 return app_sync_list
;
343 bool ExtensionSyncService::ProcessExtensionSyncData(
344 const ExtensionSyncData
& extension_sync_data
) {
345 if (!ProcessExtensionSyncDataHelper(extension_sync_data
,
346 syncer::EXTENSIONS
)) {
347 extension_sync_bundle_
.AddPendingExtension(extension_sync_data
.id(),
348 extension_sync_data
);
349 extension_service_
->CheckForUpdatesSoon();
356 bool ExtensionSyncService::ProcessAppSyncData(
357 const AppSyncData
& app_sync_data
) {
358 const std::string
& id
= app_sync_data
.id();
360 if (app_sync_data
.app_launch_ordinal().IsValid() &&
361 app_sync_data
.page_ordinal().IsValid()) {
362 extension_prefs_
->app_sorting()->SetAppLaunchOrdinal(
364 app_sync_data
.app_launch_ordinal());
365 extension_prefs_
->app_sorting()->SetPageOrdinal(
367 app_sync_data
.page_ordinal());
370 // The corresponding validation of this value during AppSyncData population
371 // is in AppSyncData::PopulateAppSpecifics.
372 if (app_sync_data
.launch_type() >= extensions::LAUNCH_TYPE_FIRST
&&
373 app_sync_data
.launch_type() < extensions::NUM_LAUNCH_TYPES
) {
374 extensions::SetLaunchType(profile_
, id
, app_sync_data
.launch_type());
377 if (!app_sync_data
.bookmark_app_url().empty())
378 ProcessBookmarkAppSyncData(app_sync_data
);
380 if (!ProcessExtensionSyncDataHelper(app_sync_data
.extension_sync_data(),
382 app_sync_bundle_
.AddPendingApp(id
, app_sync_data
);
383 extension_service_
->CheckForUpdatesSoon();
390 void ExtensionSyncService::ProcessBookmarkAppSyncData(
391 const AppSyncData
& app_sync_data
) {
392 // Process bookmark app sync if necessary.
393 GURL
bookmark_app_url(app_sync_data
.bookmark_app_url());
394 if (!bookmark_app_url
.is_valid() ||
395 app_sync_data
.extension_sync_data().uninstalled()) {
399 const extensions::Extension
* extension
=
400 extension_service_
->GetInstalledExtension(
401 app_sync_data
.extension_sync_data().id());
403 // Return if there are no bookmark app details that need updating.
404 if (extension
&& extension
->non_localized_name() ==
405 app_sync_data
.extension_sync_data().name() &&
406 extension
->description() == app_sync_data
.bookmark_app_description()) {
410 WebApplicationInfo web_app_info
;
411 web_app_info
.app_url
= bookmark_app_url
;
413 base::UTF8ToUTF16(app_sync_data
.extension_sync_data().name());
414 web_app_info
.description
=
415 base::UTF8ToUTF16(app_sync_data
.bookmark_app_description());
416 if (!app_sync_data
.bookmark_app_icon_color().empty()) {
417 extensions::image_util::ParseCSSColorString(
418 app_sync_data
.bookmark_app_icon_color(),
419 &web_app_info
.generated_icon_color
);
422 // If the bookmark app already exists, keep the old icons.
424 CreateOrUpdateBookmarkApp(extension_service_
, &web_app_info
);
426 app_sync_data
.extension_sync_data().name();
427 GetWebApplicationInfoFromApp(profile_
,
429 base::Bind(&OnWebApplicationInfoLoaded
,
431 extension_service_
->AsWeakPtr()));
435 void ExtensionSyncService::SyncOrderingChange(const std::string
& extension_id
) {
436 const extensions::Extension
* ext
= extension_service_
->GetInstalledExtension(
440 SyncExtensionChangeIfNeeded(*ext
);
443 void ExtensionSyncService::SetSyncStartFlare(
444 const syncer::SyncableService::StartSyncFlare
& flare
) {
448 bool ExtensionSyncService::IsCorrectSyncType(const Extension
& extension
,
449 syncer::ModelType type
) const {
450 if (type
== syncer::EXTENSIONS
&&
451 extensions::sync_helper::IsSyncableExtension(&extension
)) {
455 if (type
== syncer::APPS
&&
456 extensions::sync_helper::IsSyncableApp(&extension
)) {
463 bool ExtensionSyncService::IsPendingEnable(
464 const std::string
& extension_id
) const {
465 return pending_app_enables_
.Contains(extension_id
) ||
466 pending_extension_enables_
.Contains(extension_id
);
469 bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
470 const ExtensionSyncData
& extension_sync_data
,
471 syncer::ModelType type
) {
472 const std::string
& id
= extension_sync_data
.id();
473 const Extension
* extension
= extension_service_
->GetInstalledExtension(id
);
475 // TODO(bolms): we should really handle this better. The particularly bad
476 // case is where an app becomes an extension or vice versa, and we end up with
477 // a zombie extension that won't go away.
478 if (extension
&& !IsCorrectSyncType(*extension
, type
))
481 // Handle uninstalls first.
482 if (extension_sync_data
.uninstalled()) {
483 if (!extension_service_
->UninstallExtensionHelper(
484 extension_service_
, id
, extensions::UNINSTALL_REASON_SYNC
)) {
485 LOG(WARNING
) << "Could not uninstall extension " << id
491 // Extension from sync was uninstalled by the user as external extensions.
492 // Honor user choice and skip installation/enabling.
493 if (extensions::ExtensionPrefs::Get(profile_
)
494 ->IsExternalExtensionUninstalled(id
)) {
495 LOG(WARNING
) << "Extension with id " << id
496 << " from sync was uninstalled as external extension";
500 // Set user settings.
501 // If the extension has been disabled from sync, it may not have
502 // been installed yet, so we don't know if the disable reason was a
503 // permissions increase. That will be updated once CheckPermissionsIncrease
505 // However if the extension is marked as a remote install in sync, we know
506 // what the disable reason is, so set it to that directly. Note that when
507 // CheckPermissionsIncrease runs, it might still add permissions increase
508 // as a disable reason for the extension.
509 if (extension_sync_data
.enabled()) {
510 extension_service_
->EnableExtension(id
);
511 } else if (!IsPendingEnable(id
)) {
512 if (extension_sync_data
.remote_install()) {
513 extension_service_
->DisableExtension(id
,
514 Extension::DISABLE_REMOTE_INSTALL
);
516 extension_service_
->DisableExtension(
517 id
, Extension::DISABLE_UNKNOWN_FROM_SYNC
);
521 // We need to cache some version information here because setting the
522 // incognito flag invalidates the |extension| pointer (it reloads the
524 bool extension_installed
= (extension
!= NULL
);
525 int version_compare_result
= extension
?
526 extension
->version()->CompareTo(extension_sync_data
.version()) : 0;
528 // If the target extension has already been installed ephemerally, it can
529 // be promoted to a regular installed extension and downloading from the Web
530 // Store is not necessary.
531 if (extension
&& extensions::util::IsEphemeralApp(id
, profile_
))
532 extension_service_
->PromoteEphemeralApp(extension
, true);
534 // Update the incognito flag.
535 extensions::util::SetIsIncognitoEnabled(
536 id
, profile_
, extension_sync_data
.incognito_enabled());
537 extension
= NULL
; // No longer safe to use.
539 // Update the all urls flag.
540 if (extension_sync_data
.all_urls_enabled() !=
541 ExtensionSyncData::BOOLEAN_UNSET
) {
542 bool allowed
= extension_sync_data
.all_urls_enabled() ==
543 ExtensionSyncData::BOOLEAN_TRUE
;
544 extensions::util::SetAllowedScriptingOnAllUrls(id
, profile_
, allowed
);
547 if (extension_installed
) {
548 // If the extension is already installed, check if it's outdated.
549 if (version_compare_result
< 0) {
550 // Extension is outdated.
554 CHECK(type
== syncer::EXTENSIONS
|| type
== syncer::APPS
);
555 extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter
=
556 (type
== syncer::APPS
) ? extensions::sync_helper::IsSyncableApp
:
557 extensions::sync_helper::IsSyncableExtension
;
559 if (!extension_service_
->pending_extension_manager()->AddFromSync(
561 extension_sync_data
.update_url(),
563 extension_sync_data
.remote_install(),
564 extension_sync_data
.installed_by_custodian())) {
565 LOG(WARNING
) << "Could not add pending extension for " << id
;
566 // This means that the extension is already pending installation, with a
567 // non-INTERNAL location. Add to pending_sync_data, even though it will
568 // never be removed (we'll never install a syncable version of the
569 // extension), so that GetAllSyncData() continues to send it.
571 // Track pending extensions so that we can return them in GetAllSyncData().
578 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
579 const Extension
& extension
) {
580 if (extensions::util::ShouldSyncApp(&extension
, profile_
)) {
581 if (app_sync_bundle_
.IsSyncing())
582 app_sync_bundle_
.SyncChangeIfNeeded(extension
);
583 else if (extension_service_
->is_ready() && !flare_
.is_null())
584 flare_
.Run(syncer::APPS
);
585 } else if (extensions::util::ShouldSyncExtension(&extension
, profile_
)) {
586 if (extension_sync_bundle_
.IsSyncing())
587 extension_sync_bundle_
.SyncChangeIfNeeded(extension
);
588 else if (extension_service_
->is_ready() && !flare_
.is_null())
589 flare_
.Run(syncer::EXTENSIONS
);