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
;
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
)
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
;
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());
95 ExtensionSyncService::ExtensionSyncService(Profile
* profile
,
96 ExtensionPrefs
* extension_prefs
,
97 ExtensionService
* extension_service
)
99 extension_prefs_(extension_prefs
),
100 extension_service_(extension_service
) {
101 SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
102 profile_
->GetPath()));
105 ExtensionSyncService::~ExtensionSyncService() {}
108 ExtensionSyncService
* ExtensionSyncService::Get(
109 content::BrowserContext
* context
) {
110 return ExtensionSyncServiceFactory::GetForBrowserContext(context
);
113 void ExtensionSyncService::SyncUninstallExtension(
114 const Extension
& extension
) {
115 if (!extensions::util::ShouldSync(&extension
, profile_
))
118 // TODO(tim): If we get here and IsSyncing is false, this will cause
119 // "back from the dead" style bugs, because sync will add-back the extension
120 // that was uninstalled here when MergeDataAndStartSyncing is called.
121 // See crbug.com/256795.
122 syncer::ModelType type
=
123 extension
.is_app() ? syncer::APPS
: syncer::EXTENSIONS
;
124 SyncBundle
* bundle
= GetSyncBundle(type
);
125 if (bundle
->IsSyncing()) {
126 bundle
->PushSyncDeletion(extension
.id(),
127 CreateSyncData(extension
).GetSyncData());
128 } else if (extension_service_
->is_ready() && !flare_
.is_null()) {
129 flare_
.Run(type
); // Tell sync to start ASAP.
133 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
134 const Extension
& extension
) {
135 if (!extensions::util::ShouldSync(&extension
, profile_
))
138 syncer::ModelType type
=
139 extension
.is_app() ? syncer::APPS
: syncer::EXTENSIONS
;
140 SyncBundle
* bundle
= GetSyncBundle(type
);
141 if (bundle
->IsSyncing()) {
142 bundle
->PushSyncAddOrUpdate(extension
.id(),
143 CreateSyncData(extension
).GetSyncData());
144 DCHECK(!ExtensionPrefs::Get(profile_
)->NeedsSync(extension
.id()));
146 ExtensionPrefs::Get(profile_
)->SetNeedsSync(extension
.id(), true);
147 if (extension_service_
->is_ready() && !flare_
.is_null())
148 flare_
.Run(type
); // Tell sync to start ASAP.
152 syncer::SyncMergeResult
ExtensionSyncService::MergeDataAndStartSyncing(
153 syncer::ModelType type
,
154 const syncer::SyncDataList
& initial_sync_data
,
155 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
156 scoped_ptr
<syncer::SyncErrorFactory
> sync_error_factory
) {
157 CHECK(sync_processor
.get());
158 LOG_IF(FATAL
, type
!= syncer::EXTENSIONS
&& type
!= syncer::APPS
)
159 << "Got " << type
<< " ModelType";
161 SyncBundle
* bundle
= GetSyncBundle(type
);
162 bundle
->StartSyncing(sync_processor
.Pass());
164 // Apply the initial sync data, filtering out any items where we have more
165 // recent local changes. Also tell the SyncBundle the extension IDs.
166 for (const syncer::SyncData
& sync_data
: initial_sync_data
) {
167 scoped_ptr
<ExtensionSyncData
> extension_sync_data(
168 ExtensionSyncData::CreateFromSyncData(sync_data
));
169 // If the extension has local state that needs to be synced, ignore this
170 // change (we assume the local state is more recent).
171 if (extension_sync_data
&&
172 !ExtensionPrefs::Get(profile_
)->NeedsSync(extension_sync_data
->id())) {
173 ApplySyncData(*extension_sync_data
);
177 // Now push those local changes to sync.
178 // TODO(treib,kalman): We should only have to send out changes for extensions
179 // which have NeedsSync set (i.e. |GetLocalSyncDataList(type, false)|). That
180 // makes some sync_integration_tests fail though - figure out why and fix it!
181 std::vector
<ExtensionSyncData
> data_list
= GetLocalSyncDataList(type
, true);
182 bundle
->PushSyncDataList(ToSyncerSyncDataList(data_list
));
183 for (const ExtensionSyncData
& data
: data_list
)
184 ExtensionPrefs::Get(profile_
)->SetNeedsSync(data
.id(), false);
186 if (type
== syncer::APPS
)
187 ExtensionSystem::Get(profile_
)->app_sorting()->FixNTPOrdinalCollisions();
189 return syncer::SyncMergeResult(type
);
192 void ExtensionSyncService::StopSyncing(syncer::ModelType type
) {
193 GetSyncBundle(type
)->Reset();
196 syncer::SyncDataList
ExtensionSyncService::GetAllSyncData(
197 syncer::ModelType type
) const {
198 const SyncBundle
* bundle
= GetSyncBundle(type
);
199 if (!bundle
->IsSyncing())
200 return syncer::SyncDataList();
202 std::vector
<ExtensionSyncData
> sync_data_list
=
203 GetLocalSyncDataList(type
, true);
205 // Add pending data (where the local extension is either not installed yet or
207 std::vector
<ExtensionSyncData
> pending_extensions
= bundle
->GetPendingData();
208 sync_data_list
.insert(sync_data_list
.begin(),
209 pending_extensions
.begin(),
210 pending_extensions
.end());
212 return ToSyncerSyncDataList(sync_data_list
);
215 syncer::SyncError
ExtensionSyncService::ProcessSyncChanges(
216 const tracked_objects::Location
& from_here
,
217 const syncer::SyncChangeList
& change_list
) {
218 for (const syncer::SyncChange
& sync_change
: change_list
) {
219 scoped_ptr
<ExtensionSyncData
> extension_sync_data(
220 ExtensionSyncData::CreateFromSyncChange(sync_change
));
221 if (extension_sync_data
)
222 ApplySyncData(*extension_sync_data
);
225 ExtensionSystem::Get(profile_
)->app_sorting()->FixNTPOrdinalCollisions();
227 return syncer::SyncError();
230 ExtensionSyncData
ExtensionSyncService::CreateSyncData(
231 const Extension
& extension
) const {
232 bool enabled
= extension_service_
->IsExtensionEnabled(extension
.id());
233 int disable_reasons
= extension_prefs_
->GetDisableReasons(extension
.id());
234 bool incognito_enabled
= extensions::util::IsIncognitoEnabled(extension
.id(),
236 bool remote_install
=
237 extension_prefs_
->HasDisableReason(extension
.id(),
238 Extension::DISABLE_REMOTE_INSTALL
);
239 ExtensionSyncData::OptionalBoolean allowed_on_all_url
=
240 GetAllowedOnAllUrlsOptionalBoolean(extension
.id(), profile_
);
241 if (extension
.is_app()) {
242 AppSorting
* app_sorting
= ExtensionSystem::Get(profile_
)->app_sorting();
243 return ExtensionSyncData(
244 extension
, enabled
, disable_reasons
, incognito_enabled
, remote_install
,
246 app_sorting
->GetAppLaunchOrdinal(extension
.id()),
247 app_sorting
->GetPageOrdinal(extension
.id()),
248 extensions::GetLaunchTypePrefValue(extension_prefs_
, extension
.id()));
250 return ExtensionSyncData(
251 extension
, enabled
, disable_reasons
, incognito_enabled
, remote_install
,
255 bool ExtensionSyncService::ApplySyncData(
256 const ExtensionSyncData
& extension_sync_data
) {
257 syncer::ModelType type
= extension_sync_data
.is_app() ? syncer::APPS
258 : syncer::EXTENSIONS
;
259 SyncBundle
* bundle
= GetSyncBundle(type
);
260 bundle
->ApplySyncData(extension_sync_data
);
262 const std::string
& id
= extension_sync_data
.id();
264 if (extension_sync_data
.is_app()) {
265 if (extension_sync_data
.app_launch_ordinal().IsValid() &&
266 extension_sync_data
.page_ordinal().IsValid()) {
267 AppSorting
* app_sorting
= ExtensionSystem::Get(profile_
)->app_sorting();
268 app_sorting
->SetAppLaunchOrdinal(
270 extension_sync_data
.app_launch_ordinal());
271 app_sorting
->SetPageOrdinal(id
, extension_sync_data
.page_ordinal());
274 // The corresponding validation of this value during ExtensionSyncData
275 // population is in ExtensionSyncData::ToAppSpecifics.
276 if (extension_sync_data
.launch_type() >= extensions::LAUNCH_TYPE_FIRST
&&
277 extension_sync_data
.launch_type() < extensions::NUM_LAUNCH_TYPES
) {
278 extensions::SetLaunchType(
279 profile_
, id
, extension_sync_data
.launch_type());
282 if (!extension_sync_data
.bookmark_app_url().empty())
283 ApplyBookmarkAppSyncData(extension_sync_data
);
286 if (!ApplyExtensionSyncDataHelper(extension_sync_data
, type
)) {
287 bundle
->AddPendingExtension(id
, extension_sync_data
);
288 extension_service_
->CheckForUpdatesSoon();
295 void ExtensionSyncService::ApplyBookmarkAppSyncData(
296 const ExtensionSyncData
& extension_sync_data
) {
297 DCHECK(extension_sync_data
.is_app());
299 // Process bookmark app sync if necessary.
300 GURL
bookmark_app_url(extension_sync_data
.bookmark_app_url());
301 if (!bookmark_app_url
.is_valid() ||
302 extension_sync_data
.uninstalled()) {
306 const Extension
* extension
=
307 extension_service_
->GetInstalledExtension(extension_sync_data
.id());
309 // Return if there are no bookmark app details that need updating.
311 extension
->non_localized_name() == extension_sync_data
.name() &&
312 extension
->description() ==
313 extension_sync_data
.bookmark_app_description()) {
317 WebApplicationInfo web_app_info
;
318 web_app_info
.app_url
= bookmark_app_url
;
319 web_app_info
.title
= base::UTF8ToUTF16(extension_sync_data
.name());
320 web_app_info
.description
=
321 base::UTF8ToUTF16(extension_sync_data
.bookmark_app_description());
322 if (!extension_sync_data
.bookmark_app_icon_color().empty()) {
323 extensions::image_util::ParseCSSColorString(
324 extension_sync_data
.bookmark_app_icon_color(),
325 &web_app_info
.generated_icon_color
);
327 for (const auto& icon
: extension_sync_data
.linked_icons()) {
328 WebApplicationInfo::IconInfo icon_info
;
329 icon_info
.url
= icon
.url
;
330 icon_info
.width
= icon
.size
;
331 icon_info
.height
= icon
.size
;
332 web_app_info
.icons
.push_back(icon_info
);
335 // If the bookmark app already exists, keep the old icons.
337 CreateOrUpdateBookmarkApp(extension_service_
, &web_app_info
);
339 GetWebApplicationInfoFromApp(profile_
,
341 base::Bind(&OnWebApplicationInfoLoaded
,
343 extension_service_
->AsWeakPtr()));
347 void ExtensionSyncService::SetSyncStartFlare(
348 const syncer::SyncableService::StartSyncFlare
& flare
) {
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.
376 registry
->enabled_extensions(), type
, include_everything
, &data
);
378 registry
->disabled_extensions(), type
, include_everything
, &data
);
380 registry
->terminated_extensions(), type
, include_everything
, &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
))
414 // Handle uninstalls first.
415 if (extension_sync_data
.uninstalled()) {
416 if (!extension_service_
->UninstallExtensionHelper(
417 extension_service_
, id
, extensions::UNINSTALL_REASON_SYNC
)) {
418 LOG(WARNING
) << "Could not uninstall extension " << id
<< " for sync";
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";
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
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
);
450 extension_service_
->EnableExtension(id
);
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.
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().