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_util.h"
26 #include "extensions/browser/uninstall_reason.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/extension_set.h"
29 #include "extensions/common/image_util.h"
30 #include "sync/api/sync_change.h"
31 #include "sync/api/sync_error_factory.h"
33 using extensions::Extension
;
34 using extensions::ExtensionPrefs
;
35 using extensions::ExtensionRegistry
;
36 using extensions::ExtensionSet
;
37 using extensions::ExtensionSyncData
;
38 using extensions::PendingEnables
;
39 using extensions::SyncBundle
;
43 void OnWebApplicationInfoLoaded(
44 WebApplicationInfo synced_info
,
45 base::WeakPtr
<ExtensionService
> extension_service
,
46 const WebApplicationInfo
& loaded_info
) {
47 DCHECK_EQ(synced_info
.app_url
, loaded_info
.app_url
);
49 if (!extension_service
)
52 // Use the old icons if they exist.
53 synced_info
.icons
= loaded_info
.icons
;
54 CreateOrUpdateBookmarkApp(extension_service
.get(), &synced_info
);
57 // Returns the pref value for "all urls enabled" for the given extension id.
58 ExtensionSyncData::OptionalBoolean
GetAllowedOnAllUrlsOptionalBoolean(
59 const std::string
& extension_id
,
60 content::BrowserContext
* context
) {
61 bool allowed_on_all_urls
=
62 extensions::util::AllowedScriptingOnAllUrls(extension_id
, context
);
63 // If the extension is not allowed on all urls (which is not the default),
64 // then we have to sync the preference.
65 if (!allowed_on_all_urls
)
66 return ExtensionSyncData::BOOLEAN_FALSE
;
68 // If the user has explicitly set a value, then we sync it.
69 if (extensions::util::HasSetAllowedScriptingOnAllUrls(extension_id
, context
))
70 return ExtensionSyncData::BOOLEAN_TRUE
;
73 return ExtensionSyncData::BOOLEAN_UNSET
;
76 // Returns true if the sync type of |extension| matches |type|.
77 bool IsCorrectSyncType(const Extension
& extension
, syncer::ModelType type
) {
78 if (type
== syncer::EXTENSIONS
&&
79 extensions::sync_helper::IsSyncableExtension(&extension
)) {
83 if (type
== syncer::APPS
&&
84 extensions::sync_helper::IsSyncableApp(&extension
)) {
91 // Returns whether the given app or extension should be synced.
92 bool ShouldSync(const Extension
& extension
, Profile
* profile
) {
93 if (extension
.is_app())
94 return extensions::util::ShouldSyncApp(&extension
, profile
);
95 return extensions::util::ShouldSyncExtension(&extension
, profile
);
100 ExtensionSyncService::ExtensionSyncService(Profile
* profile
,
101 ExtensionPrefs
* extension_prefs
,
102 ExtensionService
* extension_service
)
104 extension_prefs_(extension_prefs
),
105 extension_service_(extension_service
),
106 app_sync_bundle_(this),
107 extension_sync_bundle_(this),
108 pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
109 extension_prefs_
->pref_service())),
112 pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
113 extension_prefs_
->pref_service())),
114 &extension_sync_bundle_
,
115 syncer::EXTENSIONS
) {
116 SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
117 profile_
->GetPath()));
119 extension_service_
->set_extension_sync_service(this);
120 extension_prefs_
->app_sorting()->SetExtensionSyncService(this);
123 ExtensionSyncService::~ExtensionSyncService() {}
126 ExtensionSyncService
* ExtensionSyncService::Get(
127 content::BrowserContext
* context
) {
128 return ExtensionSyncServiceFactory::GetForBrowserContext(context
);
131 syncer::SyncData
ExtensionSyncService::PrepareToSyncUninstallExtension(
132 const Extension
& extension
) {
133 // Extract the data we need for sync now, but don't actually sync until we've
134 // completed the uninstallation.
135 // TODO(tim): If we get here and IsSyncing is false, this will cause
136 // "back from the dead" style bugs, because sync will add-back the extension
137 // that was uninstalled here when MergeDataAndStartSyncing is called.
138 // See crbug.com/256795.
139 syncer::ModelType type
=
140 extension
.is_app() ? syncer::APPS
: syncer::EXTENSIONS
;
141 const SyncBundle
* bundle
= GetSyncBundle(type
);
142 if (ShouldSync(extension
, profile_
)) {
143 if (bundle
->IsSyncing())
144 return CreateSyncData(extension
).GetSyncData();
145 if (extension_service_
->is_ready() && !flare_
.is_null())
146 flare_
.Run(type
); // Tell sync to start ASAP.
149 return syncer::SyncData();
152 void ExtensionSyncService::ProcessSyncUninstallExtension(
153 const std::string
& extension_id
,
154 const syncer::SyncData
& sync_data
) {
155 SyncBundle
* bundle
= GetSyncBundle(sync_data
.GetDataType());
156 if (bundle
->HasExtensionId(extension_id
))
157 bundle
->PushSyncDeletion(extension_id
, sync_data
);
160 void ExtensionSyncService::SyncEnableExtension(const Extension
& extension
) {
161 // Syncing may not have started yet, so handle pending enables.
162 if (extensions::util::ShouldSyncApp(&extension
, profile_
))
163 pending_app_enables_
.Add(extension
.id());
165 if (extensions::util::ShouldSyncExtension(&extension
, profile_
))
166 pending_extension_enables_
.Add(extension
.id());
168 SyncExtensionChangeIfNeeded(extension
);
171 void ExtensionSyncService::SyncDisableExtension(const Extension
& extension
) {
172 // Syncing may not have started yet, so handle pending enables.
173 if (extensions::util::ShouldSyncApp(&extension
, profile_
))
174 pending_app_enables_
.Remove(extension
.id());
176 if (extensions::util::ShouldSyncExtension(&extension
, profile_
))
177 pending_extension_enables_
.Remove(extension
.id());
179 SyncExtensionChangeIfNeeded(extension
);
182 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
183 const Extension
& extension
) {
184 if (!ShouldSync(extension
, profile_
))
187 syncer::ModelType type
=
188 extension
.is_app() ? syncer::APPS
: syncer::EXTENSIONS
;
189 SyncBundle
* bundle
= GetSyncBundle(type
);
190 if (bundle
->IsSyncing())
191 bundle
->PushSyncChangeIfNeeded(extension
);
192 else if (extension_service_
->is_ready() && !flare_
.is_null())
196 syncer::SyncMergeResult
ExtensionSyncService::MergeDataAndStartSyncing(
197 syncer::ModelType type
,
198 const syncer::SyncDataList
& initial_sync_data
,
199 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
200 scoped_ptr
<syncer::SyncErrorFactory
> sync_error_factory
) {
201 CHECK(sync_processor
.get());
202 LOG_IF(FATAL
, type
!= syncer::EXTENSIONS
&& type
!= syncer::APPS
)
203 << "Got " << type
<< " ModelType";
205 SyncBundle
* bundle
= GetSyncBundle(type
);
206 bool is_apps
= (type
== syncer::APPS
);
207 PendingEnables
* pending_enables
=
208 is_apps
? &pending_app_enables_
: &pending_extension_enables_
;
210 bundle
->MergeDataAndStartSyncing(initial_sync_data
, sync_processor
.Pass());
211 pending_enables
->OnSyncStarted(extension_service_
);
213 // Process local extensions.
214 // TODO(yoz): Determine whether pending extensions should be considered too.
215 // See crbug.com/104399.
216 bundle
->PushSyncDataList(GetAllSyncData(type
));
219 extension_prefs_
->app_sorting()->FixNTPOrdinalCollisions();
221 return syncer::SyncMergeResult(type
);
224 void ExtensionSyncService::StopSyncing(syncer::ModelType type
) {
225 GetSyncBundle(type
)->Reset();
228 syncer::SyncDataList
ExtensionSyncService::GetAllSyncData(
229 syncer::ModelType type
) const {
230 std::vector
<ExtensionSyncData
> data
= GetSyncDataList(type
);
231 syncer::SyncDataList result
;
232 result
.reserve(data
.size());
233 for (const ExtensionSyncData
& item
: data
)
234 result
.push_back(item
.GetSyncData());
238 syncer::SyncError
ExtensionSyncService::ProcessSyncChanges(
239 const tracked_objects::Location
& from_here
,
240 const syncer::SyncChangeList
& change_list
) {
241 for (const syncer::SyncChange
& sync_change
: change_list
) {
242 syncer::ModelType type
= sync_change
.sync_data().GetDataType();
243 GetSyncBundle(type
)->ApplySyncChange(sync_change
);
246 extension_prefs_
->app_sorting()->FixNTPOrdinalCollisions();
248 return syncer::SyncError();
251 ExtensionSyncData
ExtensionSyncService::CreateSyncData(
252 const extensions::Extension
& extension
) const {
253 bool enabled
= extension_service_
->IsExtensionEnabled(extension
.id());
254 int disable_reasons
= extension_prefs_
->GetDisableReasons(extension
.id());
255 bool incognito_enabled
= extensions::util::IsIncognitoEnabled(extension
.id(),
257 bool remote_install
=
258 extension_prefs_
->HasDisableReason(extension
.id(),
259 Extension::DISABLE_REMOTE_INSTALL
);
260 ExtensionSyncData::OptionalBoolean allowed_on_all_url
=
261 GetAllowedOnAllUrlsOptionalBoolean(extension
.id(), profile_
);
262 if (extension
.is_app()) {
263 return ExtensionSyncData(
264 extension
, enabled
, disable_reasons
, incognito_enabled
, remote_install
,
266 extension_prefs_
->app_sorting()->GetAppLaunchOrdinal(extension
.id()),
267 extension_prefs_
->app_sorting()->GetPageOrdinal(extension
.id()),
268 extensions::GetLaunchTypePrefValue(extension_prefs_
, extension
.id()));
270 return ExtensionSyncData(
271 extension
, enabled
, disable_reasons
, incognito_enabled
, remote_install
,
275 bool ExtensionSyncService::ApplySyncData(
276 const ExtensionSyncData
& extension_sync_data
) {
277 const std::string
& id
= extension_sync_data
.id();
279 if (extension_sync_data
.is_app()) {
280 if (extension_sync_data
.app_launch_ordinal().IsValid() &&
281 extension_sync_data
.page_ordinal().IsValid()) {
282 extension_prefs_
->app_sorting()->SetAppLaunchOrdinal(
284 extension_sync_data
.app_launch_ordinal());
285 extension_prefs_
->app_sorting()->SetPageOrdinal(
287 extension_sync_data
.page_ordinal());
290 // The corresponding validation of this value during AppSyncData population
291 // is in AppSyncData::PopulateAppSpecifics.
292 if (extension_sync_data
.launch_type() >= extensions::LAUNCH_TYPE_FIRST
&&
293 extension_sync_data
.launch_type() < extensions::NUM_LAUNCH_TYPES
) {
294 extensions::SetLaunchType(
295 profile_
, id
, extension_sync_data
.launch_type());
298 if (!extension_sync_data
.bookmark_app_url().empty())
299 ApplyBookmarkAppSyncData(extension_sync_data
);
302 syncer::ModelType type
= extension_sync_data
.is_app() ? syncer::APPS
303 : syncer::EXTENSIONS
;
305 if (!ApplyExtensionSyncDataHelper(extension_sync_data
, type
)) {
306 GetSyncBundle(type
)->AddPendingExtension(id
, extension_sync_data
);
307 extension_service_
->CheckForUpdatesSoon();
314 void ExtensionSyncService::ApplyBookmarkAppSyncData(
315 const extensions::ExtensionSyncData
& extension_sync_data
) {
316 DCHECK(extension_sync_data
.is_app());
318 // Process bookmark app sync if necessary.
319 GURL
bookmark_app_url(extension_sync_data
.bookmark_app_url());
320 if (!bookmark_app_url
.is_valid() ||
321 extension_sync_data
.uninstalled()) {
325 const Extension
* extension
=
326 extension_service_
->GetInstalledExtension(extension_sync_data
.id());
328 // Return if there are no bookmark app details that need updating.
330 extension
->non_localized_name() == extension_sync_data
.name() &&
331 extension
->description() ==
332 extension_sync_data
.bookmark_app_description()) {
336 WebApplicationInfo web_app_info
;
337 web_app_info
.app_url
= bookmark_app_url
;
338 web_app_info
.title
= base::UTF8ToUTF16(extension_sync_data
.name());
339 web_app_info
.description
=
340 base::UTF8ToUTF16(extension_sync_data
.bookmark_app_description());
341 if (!extension_sync_data
.bookmark_app_icon_color().empty()) {
342 extensions::image_util::ParseCSSColorString(
343 extension_sync_data
.bookmark_app_icon_color(),
344 &web_app_info
.generated_icon_color
);
346 for (const auto& icon
: extension_sync_data
.linked_icons()) {
347 WebApplicationInfo::IconInfo icon_info
;
348 icon_info
.url
= icon
.url
;
349 icon_info
.width
= icon
.size
;
350 icon_info
.height
= icon
.size
;
351 web_app_info
.icons
.push_back(icon_info
);
354 // If the bookmark app already exists, keep the old icons.
356 CreateOrUpdateBookmarkApp(extension_service_
, &web_app_info
);
358 GetWebApplicationInfoFromApp(profile_
,
360 base::Bind(&OnWebApplicationInfoLoaded
,
362 extension_service_
->AsWeakPtr()));
366 void ExtensionSyncService::SyncOrderingChange(const std::string
& extension_id
) {
367 const Extension
* ext
=
368 extension_service_
->GetInstalledExtension(extension_id
);
371 SyncExtensionChangeIfNeeded(*ext
);
374 void ExtensionSyncService::SetSyncStartFlare(
375 const syncer::SyncableService::StartSyncFlare
& flare
) {
379 bool ExtensionSyncService::IsPendingEnable(
380 const std::string
& extension_id
) const {
381 return pending_app_enables_
.Contains(extension_id
) ||
382 pending_extension_enables_
.Contains(extension_id
);
385 SyncBundle
* ExtensionSyncService::GetSyncBundle(syncer::ModelType type
) {
386 return const_cast<SyncBundle
*>(
387 const_cast<const ExtensionSyncService
&>(*this).GetSyncBundle(type
));
390 const SyncBundle
* ExtensionSyncService::GetSyncBundle(
391 syncer::ModelType type
) const {
392 if (type
== syncer::APPS
)
393 return &app_sync_bundle_
;
394 return &extension_sync_bundle_
;
397 std::vector
<ExtensionSyncData
> ExtensionSyncService::GetSyncDataList(
398 syncer::ModelType type
) const {
399 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
400 std::vector
<ExtensionSyncData
> extension_sync_list
;
401 FillSyncDataList(registry
->enabled_extensions(), type
, &extension_sync_list
);
402 FillSyncDataList(registry
->disabled_extensions(), type
, &extension_sync_list
);
404 registry
->terminated_extensions(), type
, &extension_sync_list
);
406 std::vector
<ExtensionSyncData
> pending_extensions
=
407 GetSyncBundle(type
)->GetPendingData();
408 extension_sync_list
.insert(extension_sync_list
.begin(),
409 pending_extensions
.begin(),
410 pending_extensions
.end());
412 return extension_sync_list
;
415 void ExtensionSyncService::FillSyncDataList(
416 const ExtensionSet
& extensions
,
417 syncer::ModelType type
,
418 std::vector
<ExtensionSyncData
>* sync_data_list
) const {
419 const SyncBundle
* bundle
= GetSyncBundle(type
);
420 for (const scoped_refptr
<const Extension
>& extension
: extensions
) {
421 if (IsCorrectSyncType(*extension
, type
) &&
422 ShouldSync(*extension
, profile_
) &&
423 bundle
->ShouldIncludeInLocalSyncDataList(*extension
)) {
424 sync_data_list
->push_back(CreateSyncData(*extension
));
429 bool ExtensionSyncService::ApplyExtensionSyncDataHelper(
430 const ExtensionSyncData
& extension_sync_data
,
431 syncer::ModelType type
) {
432 const std::string
& id
= extension_sync_data
.id();
433 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
434 const Extension
* extension
= registry
->GetInstalledExtension(id
);
436 // TODO(bolms): we should really handle this better. The particularly bad
437 // case is where an app becomes an extension or vice versa, and we end up with
438 // a zombie extension that won't go away.
439 if (extension
&& !IsCorrectSyncType(*extension
, type
))
442 // Handle uninstalls first.
443 if (extension_sync_data
.uninstalled()) {
444 if (!extension_service_
->UninstallExtensionHelper(
445 extension_service_
, id
, extensions::UNINSTALL_REASON_SYNC
)) {
446 LOG(WARNING
) << "Could not uninstall extension " << id
<< " for sync";
451 // Extension from sync was uninstalled by the user as external extensions.
452 // Honor user choice and skip installation/enabling.
453 if (ExtensionPrefs::Get(profile_
)->IsExternalExtensionUninstalled(id
)) {
454 LOG(WARNING
) << "Extension with id " << id
455 << " from sync was uninstalled as external extension";
459 int version_compare_result
= extension
?
460 extension
->version()->CompareTo(extension_sync_data
.version()) : 0;
462 // Set user settings.
463 if (extension_sync_data
.enabled()) {
464 DCHECK(!extension_sync_data
.disable_reasons());
466 // Only grant permissions if the sync data explicitly sets the disable
467 // reasons to Extension::DISABLE_NONE (as opposed to the legacy (<M45) case
468 // where they're not set at all), and if the version from sync matches our
469 // local one. Otherwise we just enable it without granting permissions. If
470 // any permissions are missing, CheckPermissionsIncrease will soon disable
472 bool grant_permissions
=
473 extension_sync_data
.supports_disable_reasons() &&
474 extension
&& (version_compare_result
== 0);
475 if (grant_permissions
)
476 extension_service_
->GrantPermissionsAndEnableExtension(extension
);
478 extension_service_
->EnableExtension(id
);
479 } else if (!IsPendingEnable(id
)) {
480 int disable_reasons
= extension_sync_data
.disable_reasons();
481 if (extension_sync_data
.remote_install()) {
482 if (!(disable_reasons
& Extension::DISABLE_REMOTE_INSTALL
)) {
483 // In the non-legacy case (>=M45) where disable reasons are synced at
484 // all, DISABLE_REMOTE_INSTALL should be among them already.
485 DCHECK(!extension_sync_data
.supports_disable_reasons());
486 disable_reasons
|= Extension::DISABLE_REMOTE_INSTALL
;
488 } else if (!extension_sync_data
.supports_disable_reasons()) {
489 // Legacy case (<M45), from before we synced disable reasons (see
490 // crbug.com/484214).
491 disable_reasons
= Extension::DISABLE_UNKNOWN_FROM_SYNC
;
494 // In the non-legacy case (>=M45), clear any existing disable reasons first.
495 // Otherwise sync can't remove just some of them.
496 if (extension_sync_data
.supports_disable_reasons())
497 ExtensionPrefs::Get(profile_
)->ClearDisableReasons(id
);
499 extension_service_
->DisableExtension(id
, disable_reasons
);
502 // We need to cache some information here because setting the incognito flag
503 // invalidates the |extension| pointer (it reloads the extension).
504 bool extension_installed
= (extension
!= NULL
);
506 // If the target extension has already been installed ephemerally, it can
507 // be promoted to a regular installed extension and downloading from the Web
508 // Store is not necessary.
509 if (extension
&& extensions::util::IsEphemeralApp(id
, profile_
))
510 extension_service_
->PromoteEphemeralApp(extension
, true);
512 // Update the incognito flag.
513 extensions::util::SetIsIncognitoEnabled(
514 id
, profile_
, extension_sync_data
.incognito_enabled());
515 extension
= NULL
; // No longer safe to use.
517 // Update the all urls flag.
518 if (extension_sync_data
.all_urls_enabled() !=
519 ExtensionSyncData::BOOLEAN_UNSET
) {
520 bool allowed
= extension_sync_data
.all_urls_enabled() ==
521 ExtensionSyncData::BOOLEAN_TRUE
;
522 extensions::util::SetAllowedScriptingOnAllUrls(id
, profile_
, allowed
);
525 if (extension_installed
) {
526 // If the extension is already installed, check if it's outdated.
527 if (version_compare_result
< 0) {
528 // Extension is outdated.
532 CHECK(type
== syncer::EXTENSIONS
|| type
== syncer::APPS
);
533 extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter
=
534 (type
== syncer::APPS
) ? extensions::sync_helper::IsSyncableApp
:
535 extensions::sync_helper::IsSyncableExtension
;
537 if (!extension_service_
->pending_extension_manager()->AddFromSync(
539 extension_sync_data
.update_url(),
541 extension_sync_data
.remote_install(),
542 extension_sync_data
.installed_by_custodian())) {
543 LOG(WARNING
) << "Could not add pending extension for " << id
;
544 // This means that the extension is already pending installation, with a
545 // non-INTERNAL location. Add to pending_sync_data, even though it will
546 // never be removed (we'll never install a syncable version of the
547 // extension), so that GetAllSyncData() continues to send it.
549 // Track pending extensions so that we can return them in GetAllSyncData().