Rename CoalescedPermissionMessage to PermissionMessage
[chromium-blink-merge.git] / chrome / browser / extensions / extension_sync_service.cc
blobae454e492b34cb04a2fcab20abbc32f9146562f6
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 "chrome/browser/extensions/bookmark_app_helper.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/extensions/extension_sync_data.h"
12 #include "chrome/browser/extensions/extension_sync_service_factory.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/extensions/launch_util.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sync/glue/sync_start_util.h"
17 #include "chrome/common/extensions/extension_constants.h"
18 #include "chrome/common/extensions/sync_helper.h"
19 #include "chrome/common/web_application_info.h"
20 #include "components/sync_driver/sync_prefs.h"
21 #include "extensions/browser/app_sorting.h"
22 #include "extensions/browser/extension_prefs.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.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::AppSorting;
34 using extensions::Extension;
35 using extensions::ExtensionPrefs;
36 using extensions::ExtensionRegistry;
37 using extensions::ExtensionSet;
38 using extensions::ExtensionSyncData;
39 using extensions::ExtensionSystem;
40 using extensions::SyncBundle;
42 namespace {
44 void OnWebApplicationInfoLoaded(
45 WebApplicationInfo synced_info,
46 base::WeakPtr<ExtensionService> extension_service,
47 const WebApplicationInfo& loaded_info) {
48 DCHECK_EQ(synced_info.app_url, loaded_info.app_url);
50 if (!extension_service)
51 return;
53 // Use the old icons if they exist.
54 synced_info.icons = loaded_info.icons;
55 CreateOrUpdateBookmarkApp(extension_service.get(), &synced_info);
58 // Returns the pref value for "all urls enabled" for the given extension id.
59 ExtensionSyncData::OptionalBoolean GetAllowedOnAllUrlsOptionalBoolean(
60 const std::string& extension_id,
61 content::BrowserContext* context) {
62 bool allowed_on_all_urls =
63 extensions::util::AllowedScriptingOnAllUrls(extension_id, context);
64 // If the extension is not allowed on all urls (which is not the default),
65 // then we have to sync the preference.
66 if (!allowed_on_all_urls)
67 return ExtensionSyncData::BOOLEAN_FALSE;
69 // If the user has explicitly set a value, then we sync it.
70 if (extensions::util::HasSetAllowedScriptingOnAllUrls(extension_id, context))
71 return ExtensionSyncData::BOOLEAN_TRUE;
73 // Otherwise, unset.
74 return ExtensionSyncData::BOOLEAN_UNSET;
77 // Returns true if the sync type of |extension| matches |type|.
78 bool IsCorrectSyncType(const Extension& extension, syncer::ModelType type) {
79 return (type == syncer::EXTENSIONS && extension.is_extension()) ||
80 (type == syncer::APPS && extension.is_app());
83 syncer::SyncDataList ToSyncerSyncDataList(
84 const std::vector<ExtensionSyncData>& data) {
85 syncer::SyncDataList result;
86 result.reserve(data.size());
87 for (const ExtensionSyncData& item : data)
88 result.push_back(item.GetSyncData());
89 return result;
92 } // namespace
94 ExtensionSyncService::ExtensionSyncService(Profile* profile)
95 : profile_(profile),
96 registry_observer_(this),
97 prefs_observer_(this),
98 flare_(sync_start_util::GetFlareForSyncableService(profile->GetPath())) {
99 registry_observer_.Add(ExtensionRegistry::Get(profile_));
100 prefs_observer_.Add(ExtensionPrefs::Get(profile_));
103 ExtensionSyncService::~ExtensionSyncService() {
106 // static
107 ExtensionSyncService* ExtensionSyncService::Get(
108 content::BrowserContext* context) {
109 return ExtensionSyncServiceFactory::GetForBrowserContext(context);
112 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
113 const Extension& extension) {
114 if (!extensions::util::ShouldSync(&extension, profile_))
115 return;
117 syncer::ModelType type =
118 extension.is_app() ? syncer::APPS : syncer::EXTENSIONS;
119 SyncBundle* bundle = GetSyncBundle(type);
120 if (bundle->IsSyncing()) {
121 bundle->PushSyncAddOrUpdate(extension.id(),
122 CreateSyncData(extension).GetSyncData());
123 DCHECK(!ExtensionPrefs::Get(profile_)->NeedsSync(extension.id()));
124 } else {
125 ExtensionPrefs::Get(profile_)->SetNeedsSync(extension.id(), true);
126 if (extension_service()->is_ready() && !flare_.is_null())
127 flare_.Run(type); // Tell sync to start ASAP.
131 syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
132 syncer::ModelType type,
133 const syncer::SyncDataList& initial_sync_data,
134 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
135 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
136 CHECK(sync_processor.get());
137 LOG_IF(FATAL, type != syncer::EXTENSIONS && type != syncer::APPS)
138 << "Got " << type << " ModelType";
140 SyncBundle* bundle = GetSyncBundle(type);
141 bundle->StartSyncing(sync_processor.Pass());
143 // Apply the initial sync data, filtering out any items where we have more
144 // recent local changes. Also tell the SyncBundle the extension IDs.
145 for (const syncer::SyncData& sync_data : initial_sync_data) {
146 scoped_ptr<ExtensionSyncData> extension_sync_data(
147 ExtensionSyncData::CreateFromSyncData(sync_data));
148 // If the extension has local state that needs to be synced, ignore this
149 // change (we assume the local state is more recent).
150 if (extension_sync_data &&
151 !ExtensionPrefs::Get(profile_)->NeedsSync(extension_sync_data->id())) {
152 ApplySyncData(*extension_sync_data);
156 // Now push those local changes to sync.
157 // TODO(treib,kalman): We should only have to send out changes for extensions
158 // which have NeedsSync set (i.e. |GetLocalSyncDataList(type, false)|). That
159 // makes some sync_integration_tests fail though - figure out why and fix it!
160 std::vector<ExtensionSyncData> data_list = GetLocalSyncDataList(type, true);
161 bundle->PushSyncDataList(ToSyncerSyncDataList(data_list));
162 for (const ExtensionSyncData& data : data_list)
163 ExtensionPrefs::Get(profile_)->SetNeedsSync(data.id(), false);
165 if (type == syncer::APPS)
166 ExtensionSystem::Get(profile_)->app_sorting()->FixNTPOrdinalCollisions();
168 return syncer::SyncMergeResult(type);
171 void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
172 GetSyncBundle(type)->Reset();
175 syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
176 syncer::ModelType type) const {
177 const SyncBundle* bundle = GetSyncBundle(type);
178 if (!bundle->IsSyncing())
179 return syncer::SyncDataList();
181 std::vector<ExtensionSyncData> sync_data_list =
182 GetLocalSyncDataList(type, true);
184 // Add pending data (where the local extension is either not installed yet or
185 // outdated).
186 std::vector<ExtensionSyncData> pending_extensions = bundle->GetPendingData();
187 sync_data_list.insert(sync_data_list.begin(),
188 pending_extensions.begin(),
189 pending_extensions.end());
191 return ToSyncerSyncDataList(sync_data_list);
194 syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
195 const tracked_objects::Location& from_here,
196 const syncer::SyncChangeList& change_list) {
197 for (const syncer::SyncChange& sync_change : change_list) {
198 scoped_ptr<ExtensionSyncData> extension_sync_data(
199 ExtensionSyncData::CreateFromSyncChange(sync_change));
200 if (extension_sync_data)
201 ApplySyncData(*extension_sync_data);
204 ExtensionSystem::Get(profile_)->app_sorting()->FixNTPOrdinalCollisions();
206 return syncer::SyncError();
209 ExtensionSyncData ExtensionSyncService::CreateSyncData(
210 const Extension& extension) const {
211 const ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
212 // Query the enabled state from ExtensionPrefs rather than
213 // ExtensionService::IsExtensionEnabled - the latter uses ExtensionRegistry
214 // which might not have been updated yet (ExtensionPrefs are updated first,
215 // and we're listening for changes to these).
216 bool enabled = !extension_prefs->IsExtensionDisabled(extension.id());
217 enabled = enabled &&
218 !extension_prefs->IsExternalExtensionUninstalled(extension.id());
219 // Blacklisted extensions are not marked as disabled in ExtensionPrefs.
220 enabled = enabled &&
221 extension_prefs->GetExtensionBlacklistState(extension.id()) ==
222 extensions::NOT_BLACKLISTED;
223 int disable_reasons = extension_prefs->GetDisableReasons(extension.id());
224 bool incognito_enabled = extensions::util::IsIncognitoEnabled(extension.id(),
225 profile_);
226 bool remote_install =
227 extension_prefs->HasDisableReason(extension.id(),
228 Extension::DISABLE_REMOTE_INSTALL);
229 ExtensionSyncData::OptionalBoolean allowed_on_all_url =
230 GetAllowedOnAllUrlsOptionalBoolean(extension.id(), profile_);
231 if (extension.is_app()) {
232 AppSorting* app_sorting = ExtensionSystem::Get(profile_)->app_sorting();
233 return ExtensionSyncData(
234 extension, enabled, disable_reasons, incognito_enabled, remote_install,
235 allowed_on_all_url,
236 app_sorting->GetAppLaunchOrdinal(extension.id()),
237 app_sorting->GetPageOrdinal(extension.id()),
238 extensions::GetLaunchTypePrefValue(extension_prefs, extension.id()));
240 return ExtensionSyncData(
241 extension, enabled, disable_reasons, incognito_enabled, remote_install,
242 allowed_on_all_url);
245 bool ExtensionSyncService::ApplySyncData(
246 const ExtensionSyncData& extension_sync_data) {
247 syncer::ModelType type = extension_sync_data.is_app() ? syncer::APPS
248 : syncer::EXTENSIONS;
249 SyncBundle* bundle = GetSyncBundle(type);
250 bundle->ApplySyncData(extension_sync_data);
252 const std::string& id = extension_sync_data.id();
254 if (extension_sync_data.is_app()) {
255 if (extension_sync_data.app_launch_ordinal().IsValid() &&
256 extension_sync_data.page_ordinal().IsValid()) {
257 AppSorting* app_sorting = ExtensionSystem::Get(profile_)->app_sorting();
258 app_sorting->SetAppLaunchOrdinal(
260 extension_sync_data.app_launch_ordinal());
261 app_sorting->SetPageOrdinal(id, extension_sync_data.page_ordinal());
264 // The corresponding validation of this value during ExtensionSyncData
265 // population is in ExtensionSyncData::ToAppSpecifics.
266 if (extension_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
267 extension_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
268 extensions::SetLaunchType(
269 profile_, id, extension_sync_data.launch_type());
272 if (!extension_sync_data.bookmark_app_url().empty())
273 ApplyBookmarkAppSyncData(extension_sync_data);
276 if (!ApplyExtensionSyncDataHelper(extension_sync_data, type)) {
277 bundle->AddPendingExtension(id, extension_sync_data);
278 extension_service()->CheckForUpdatesSoon();
279 return false;
282 return true;
285 void ExtensionSyncService::ApplyBookmarkAppSyncData(
286 const ExtensionSyncData& extension_sync_data) {
287 DCHECK(extension_sync_data.is_app());
289 // Process bookmark app sync if necessary.
290 GURL bookmark_app_url(extension_sync_data.bookmark_app_url());
291 if (!bookmark_app_url.is_valid() ||
292 extension_sync_data.uninstalled()) {
293 return;
296 const Extension* extension =
297 extension_service()->GetInstalledExtension(extension_sync_data.id());
299 // Return if there are no bookmark app details that need updating.
300 if (extension &&
301 extension->non_localized_name() == extension_sync_data.name() &&
302 extension->description() ==
303 extension_sync_data.bookmark_app_description()) {
304 return;
307 WebApplicationInfo web_app_info;
308 web_app_info.app_url = bookmark_app_url;
309 web_app_info.title = base::UTF8ToUTF16(extension_sync_data.name());
310 web_app_info.description =
311 base::UTF8ToUTF16(extension_sync_data.bookmark_app_description());
312 if (!extension_sync_data.bookmark_app_icon_color().empty()) {
313 extensions::image_util::ParseCSSColorString(
314 extension_sync_data.bookmark_app_icon_color(),
315 &web_app_info.generated_icon_color);
317 for (const auto& icon : extension_sync_data.linked_icons()) {
318 WebApplicationInfo::IconInfo icon_info;
319 icon_info.url = icon.url;
320 icon_info.width = icon.size;
321 icon_info.height = icon.size;
322 web_app_info.icons.push_back(icon_info);
325 // If the bookmark app already exists, keep the old icons.
326 if (!extension) {
327 CreateOrUpdateBookmarkApp(extension_service(), &web_app_info);
328 } else {
329 GetWebApplicationInfoFromApp(profile_,
330 extension,
331 base::Bind(&OnWebApplicationInfoLoaded,
332 web_app_info,
333 extension_service()->AsWeakPtr()));
337 void ExtensionSyncService::SetSyncStartFlareForTesting(
338 const syncer::SyncableService::StartSyncFlare& flare) {
339 flare_ = flare;
342 ExtensionService* ExtensionSyncService::extension_service() const {
343 return ExtensionSystem::Get(profile_)->extension_service();
346 void ExtensionSyncService::OnExtensionInstalled(
347 content::BrowserContext* browser_context,
348 const extensions::Extension* extension,
349 bool is_update) {
350 DCHECK_EQ(profile_, browser_context);
351 SyncExtensionChangeIfNeeded(*extension);
354 void ExtensionSyncService::OnExtensionUninstalled(
355 content::BrowserContext* browser_context,
356 const extensions::Extension* extension,
357 extensions::UninstallReason reason) {
358 DCHECK_EQ(profile_, browser_context);
359 // Don't bother syncing if the extension will be re-installed momentarily.
360 if (reason == extensions::UNINSTALL_REASON_REINSTALL ||
361 !extensions::util::ShouldSync(extension, profile_))
362 return;
364 // TODO(tim): If we get here and IsSyncing is false, this will cause
365 // "back from the dead" style bugs, because sync will add-back the extension
366 // that was uninstalled here when MergeDataAndStartSyncing is called.
367 // See crbug.com/256795.
368 // Possible fix: Set NeedsSync here, then in MergeDataAndStartSyncing, if
369 // NeedsSync is set but the extension isn't installed, send a sync deletion.
370 syncer::ModelType type =
371 extension->is_app() ? syncer::APPS : syncer::EXTENSIONS;
372 SyncBundle* bundle = GetSyncBundle(type);
373 if (bundle->IsSyncing()) {
374 bundle->PushSyncDeletion(extension->id(),
375 CreateSyncData(*extension).GetSyncData());
376 } else if (extension_service()->is_ready() && !flare_.is_null()) {
377 flare_.Run(type); // Tell sync to start ASAP.
381 void ExtensionSyncService::OnExtensionStateChanged(
382 const std::string& extension_id,
383 bool state) {
384 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
385 const Extension* extension = registry->GetInstalledExtension(extension_id);
386 // We can get pref change notifications for extensions that aren't installed
387 // (yet). In that case, we'll pick up the change later via ExtensionRegistry
388 // observation (in OnExtensionInstalled).
389 if (extension)
390 SyncExtensionChangeIfNeeded(*extension);
393 void ExtensionSyncService::OnExtensionDisableReasonsChanged(
394 const std::string& extension_id,
395 int disabled_reasons) {
396 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
397 const Extension* extension = registry->GetInstalledExtension(extension_id);
398 // We can get pref change notifications for extensions that aren't installed
399 // (yet). In that case, we'll pick up the change later via ExtensionRegistry
400 // observation (in OnExtensionInstalled).
401 if (extension)
402 SyncExtensionChangeIfNeeded(*extension);
405 SyncBundle* ExtensionSyncService::GetSyncBundle(syncer::ModelType type) {
406 return const_cast<SyncBundle*>(
407 const_cast<const ExtensionSyncService&>(*this).GetSyncBundle(type));
410 const SyncBundle* ExtensionSyncService::GetSyncBundle(
411 syncer::ModelType type) const {
412 return (type == syncer::APPS) ? &app_sync_bundle_ : &extension_sync_bundle_;
415 std::vector<ExtensionSyncData> ExtensionSyncService::GetLocalSyncDataList(
416 syncer::ModelType type,
417 bool include_everything) const {
418 // Collect the local state. FillSyncDataList will filter out extensions for
419 // which we have pending data that should be used instead.
420 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
421 std::vector<ExtensionSyncData> data;
422 // TODO(treib, kalman): Should we be including blacklisted/blocked extensions
423 // here? I.e. just calling registry->GeneratedInstalledExtensionsSet()?
424 // It would be more consistent, but the danger is that the black/blocklist
425 // hasn't been updated on all clients by the time sync has kicked in -
426 // so it's safest not to. Take care to add any other extension lists here
427 // in the future if they are added.
428 FillSyncDataList(
429 registry->enabled_extensions(), type, include_everything, &data);
430 FillSyncDataList(
431 registry->disabled_extensions(), type, include_everything, &data);
432 FillSyncDataList(
433 registry->terminated_extensions(), type, include_everything, &data);
434 return data;
437 void ExtensionSyncService::FillSyncDataList(
438 const ExtensionSet& extensions,
439 syncer::ModelType type,
440 bool include_everything,
441 std::vector<ExtensionSyncData>* sync_data_list) const {
442 const SyncBundle* bundle = GetSyncBundle(type);
443 for (const scoped_refptr<const Extension>& extension : extensions) {
444 if (IsCorrectSyncType(*extension, type) &&
445 extensions::util::ShouldSync(extension.get(), profile_) &&
446 !bundle->HasPendingExtensionId(extension->id()) &&
447 (include_everything ||
448 ExtensionPrefs::Get(profile_)->NeedsSync(extension->id()))) {
449 sync_data_list->push_back(CreateSyncData(*extension));
454 bool ExtensionSyncService::ApplyExtensionSyncDataHelper(
455 const ExtensionSyncData& extension_sync_data,
456 syncer::ModelType type) {
457 const std::string& id = extension_sync_data.id();
458 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
459 const Extension* extension = registry->GetInstalledExtension(id);
461 // TODO(bolms): we should really handle this better. The particularly bad
462 // case is where an app becomes an extension or vice versa, and we end up with
463 // a zombie extension that won't go away.
464 if (extension && !IsCorrectSyncType(*extension, type))
465 return true;
467 // Handle uninstalls first.
468 if (extension_sync_data.uninstalled()) {
469 if (!ExtensionService::UninstallExtensionHelper(
470 extension_service(), id, extensions::UNINSTALL_REASON_SYNC)) {
471 LOG(WARNING) << "Could not uninstall extension " << id << " for sync";
473 return true;
476 // Extension from sync was uninstalled by the user as external extensions.
477 // Honor user choice and skip installation/enabling.
478 if (ExtensionPrefs::Get(profile_)->IsExternalExtensionUninstalled(id)) {
479 LOG(WARNING) << "Extension with id " << id
480 << " from sync was uninstalled as external extension";
481 return true;
484 int version_compare_result = extension ?
485 extension->version()->CompareTo(extension_sync_data.version()) : 0;
487 // Set user settings.
488 if (extension_sync_data.enabled()) {
489 DCHECK(!extension_sync_data.disable_reasons());
491 // Only grant permissions if the sync data explicitly sets the disable
492 // reasons to Extension::DISABLE_NONE (as opposed to the legacy (<M45) case
493 // where they're not set at all), and if the version from sync matches our
494 // local one. Otherwise we just enable it without granting permissions. If
495 // any permissions are missing, CheckPermissionsIncrease will soon disable
496 // it again.
497 bool grant_permissions =
498 extension_sync_data.supports_disable_reasons() &&
499 extension && (version_compare_result == 0);
500 if (grant_permissions)
501 extension_service()->GrantPermissionsAndEnableExtension(extension);
502 else
503 extension_service()->EnableExtension(id);
504 } else {
505 int disable_reasons = extension_sync_data.disable_reasons();
506 if (extension_sync_data.remote_install()) {
507 if (!(disable_reasons & Extension::DISABLE_REMOTE_INSTALL)) {
508 // In the non-legacy case (>=M45) where disable reasons are synced at
509 // all, DISABLE_REMOTE_INSTALL should be among them already.
510 DCHECK(!extension_sync_data.supports_disable_reasons());
511 disable_reasons |= Extension::DISABLE_REMOTE_INSTALL;
513 } else if (!extension_sync_data.supports_disable_reasons()) {
514 // Legacy case (<M45), from before we synced disable reasons (see
515 // crbug.com/484214).
516 disable_reasons = Extension::DISABLE_UNKNOWN_FROM_SYNC;
519 // In the non-legacy case (>=M45), clear any existing disable reasons first.
520 // Otherwise sync can't remove just some of them.
521 if (extension_sync_data.supports_disable_reasons())
522 ExtensionPrefs::Get(profile_)->ClearDisableReasons(id);
524 extension_service()->DisableExtension(id, disable_reasons);
527 // We need to cache some information here because setting the incognito flag
528 // invalidates the |extension| pointer (it reloads the extension).
529 bool extension_installed = (extension != NULL);
531 // If the target extension has already been installed ephemerally, it can
532 // be promoted to a regular installed extension and downloading from the Web
533 // Store is not necessary.
534 if (extension && extensions::util::IsEphemeralApp(id, profile_))
535 extension_service()->PromoteEphemeralApp(extension, true);
537 // Update the incognito flag.
538 extensions::util::SetIsIncognitoEnabled(
539 id, profile_, extension_sync_data.incognito_enabled());
540 extension = NULL; // No longer safe to use.
542 // Update the all urls flag.
543 if (extension_sync_data.all_urls_enabled() !=
544 ExtensionSyncData::BOOLEAN_UNSET) {
545 bool allowed = extension_sync_data.all_urls_enabled() ==
546 ExtensionSyncData::BOOLEAN_TRUE;
547 extensions::util::SetAllowedScriptingOnAllUrls(id, profile_, allowed);
550 if (extension_installed) {
551 // If the extension is already installed, check if it's outdated.
552 if (version_compare_result < 0) {
553 // Extension is outdated.
554 return false;
556 } else {
557 CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
558 if (!extension_service()->pending_extension_manager()->AddFromSync(
560 extension_sync_data.update_url(),
561 extensions::sync_helper::IsSyncable,
562 extension_sync_data.remote_install(),
563 extension_sync_data.installed_by_custodian())) {
564 LOG(WARNING) << "Could not add pending extension for " << id;
565 // This means that the extension is already pending installation, with a
566 // non-INTERNAL location. Add to pending_sync_data, even though it will
567 // never be removed (we'll never install a syncable version of the
568 // extension), so that GetAllSyncData() continues to send it.
570 // Track pending extensions so that we can return them in GetAllSyncData().
571 return false;
574 return true;