Cleanup ExtensionSyncService and SyncBundle.
[chromium-blink-merge.git] / chrome / browser / extensions / extension_sync_service.cc
blob5848247298cabda3defcb33f45ba31769e552b70
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;
41 namespace {
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)
50 return;
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;
72 // Otherwise, unset.
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)) {
80 return true;
83 if (type == syncer::APPS &&
84 extensions::sync_helper::IsSyncableApp(&extension)) {
85 return true;
88 return false;
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);
98 } // namespace
100 ExtensionSyncService::ExtensionSyncService(Profile* profile,
101 ExtensionPrefs* extension_prefs,
102 ExtensionService* extension_service)
103 : profile_(profile),
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())),
110 &app_sync_bundle_,
111 syncer::APPS),
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() {}
125 // static
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_))
185 return;
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())
193 flare_.Run(type);
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));
218 if (is_apps)
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());
235 return result;
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(),
256 profile_);
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,
265 allowed_on_all_url,
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,
272 allowed_on_all_url);
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();
308 return false;
311 return true;
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()) {
322 return;
325 const Extension* extension =
326 extension_service_->GetInstalledExtension(extension_sync_data.id());
328 // Return if there are no bookmark app details that need updating.
329 if (extension &&
330 extension->non_localized_name() == extension_sync_data.name() &&
331 extension->description() ==
332 extension_sync_data.bookmark_app_description()) {
333 return;
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.
355 if (!extension) {
356 CreateOrUpdateBookmarkApp(extension_service_, &web_app_info);
357 } else {
358 GetWebApplicationInfoFromApp(profile_,
359 extension,
360 base::Bind(&OnWebApplicationInfoLoaded,
361 web_app_info,
362 extension_service_->AsWeakPtr()));
366 void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
367 const Extension* ext =
368 extension_service_->GetInstalledExtension(extension_id);
370 if (ext)
371 SyncExtensionChangeIfNeeded(*ext);
374 void ExtensionSyncService::SetSyncStartFlare(
375 const syncer::SyncableService::StartSyncFlare& flare) {
376 flare_ = 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);
403 FillSyncDataList(
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))
440 return true;
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";
448 return true;
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";
456 return true;
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
471 // it again.
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);
477 else
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.
529 return false;
531 } else {
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(),
540 filter,
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().
550 return false;
553 return true;