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.
4 
5 #include "chrome/browser/extensions/extension_sync_service.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/auto_reset.h"
11 #include "base/callback_helpers.h"
12 #include "base/one_shot_event.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_sync_data.h"
16 #include "chrome/browser/extensions/extension_sync_service_factory.h"
17 #include "chrome/browser/extensions/extension_util.h"
18 #include "chrome/browser/extensions/launch_util.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sync/glue/sync_start_util.h"
21 #include "chrome/browser/web_applications/components/install_manager.h"
22 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
23 #include "chrome/browser/web_applications/components/web_application_info.h"
24 #include "chrome/common/buildflags.h"
25 #include "chrome/common/extensions/extension_constants.h"
26 #include "chrome/common/extensions/sync_helper.h"
27 #include "components/sync/model/sync_change.h"
28 #include "components/sync/model/sync_error_factory.h"
29 #include "extensions/browser/app_sorting.h"
30 #include "extensions/browser/extension_system.h"
31 #include "extensions/browser/extension_util.h"
32 #include "extensions/browser/uninstall_reason.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/extension_set.h"
35 #include "extensions/common/image_util.h"
36 #include "extensions/common/permissions/permission_message_provider.h"
37 #include "extensions/common/permissions/permissions_data.h"
38 
39 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
40 #include "chrome/browser/supervised_user/supervised_user_service.h"
41 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
42 #endif
43 
44 using extensions::AppSorting;
45 using extensions::Extension;
46 using extensions::ExtensionPrefs;
47 using extensions::ExtensionRegistry;
48 using extensions::ExtensionSet;
49 using extensions::ExtensionSyncData;
50 using extensions::ExtensionSystem;
51 using extensions::SyncBundle;
52 
53 namespace {
54 
55 // Returns true if the sync type of |extension| matches |type|.
IsCorrectSyncType(const Extension & extension,syncer::ModelType type)56 bool IsCorrectSyncType(const Extension& extension, syncer::ModelType type) {
57   return (type == syncer::EXTENSIONS && extension.is_extension()) ||
58          (type == syncer::APPS && extension.is_app());
59 }
60 
61 // Predicate for PendingExtensionManager.
62 // TODO(crbug.com/862665): The !is_theme check should be unnecessary after all
63 // the bad data from crbug.com/558299 has been cleaned up.
ShouldAllowInstall(const Extension * extension)64 bool ShouldAllowInstall(const Extension* extension) {
65   return !extension->is_theme() &&
66          extensions::sync_helper::IsSyncable(extension);
67 }
68 
ToSyncerSyncDataList(const std::vector<ExtensionSyncData> & data)69 syncer::SyncDataList ToSyncerSyncDataList(
70     const std::vector<ExtensionSyncData>& data) {
71   syncer::SyncDataList result;
72   result.reserve(data.size());
73   for (const ExtensionSyncData& item : data)
74     result.push_back(item.GetSyncData());
75   return result;
76 }
77 
78 static_assert(extensions::disable_reason::DISABLE_REASON_LAST == (1LL << 20),
79               "Please consider whether your new disable reason should be"
80               " syncable, and if so update this bitmask accordingly!");
81 const int kKnownSyncableDisableReasons =
82     extensions::disable_reason::DISABLE_USER_ACTION |
83     extensions::disable_reason::DISABLE_PERMISSIONS_INCREASE |
84     extensions::disable_reason::DISABLE_SIDELOAD_WIPEOUT |
85     extensions::disable_reason::DISABLE_GREYLIST |
86     extensions::disable_reason::DISABLE_REMOTE_INSTALL;
87 const int kAllKnownDisableReasons =
88     extensions::disable_reason::DISABLE_REASON_LAST - 1;
89 // We also include any future bits for newer clients that added another disable
90 // reason.
91 const int kSyncableDisableReasons =
92     kKnownSyncableDisableReasons | ~kAllKnownDisableReasons;
93 
94 }  // namespace
95 
96 struct ExtensionSyncService::PendingUpdate {
PendingUpdateExtensionSyncService::PendingUpdate97   PendingUpdate() : grant_permissions_and_reenable(false) {}
PendingUpdateExtensionSyncService::PendingUpdate98   PendingUpdate(const base::Version& version,
99                 bool grant_permissions_and_reenable)
100     : version(version),
101       grant_permissions_and_reenable(grant_permissions_and_reenable) {}
102 
103   base::Version version;
104   bool grant_permissions_and_reenable;
105 };
106 
ExtensionSyncService(Profile * profile)107 ExtensionSyncService::ExtensionSyncService(Profile* profile)
108     : profile_(profile),
109       system_(ExtensionSystem::Get(profile_)),
110       ignore_updates_(false),
111       flare_(sync_start_util::GetFlareForSyncableService(profile->GetPath())) {
112   registry_observer_.Add(ExtensionRegistry::Get(profile_));
113   prefs_observer_.Add(ExtensionPrefs::Get(profile_));
114 }
115 
~ExtensionSyncService()116 ExtensionSyncService::~ExtensionSyncService() {
117 }
118 
119 // static
Get(content::BrowserContext * context)120 ExtensionSyncService* ExtensionSyncService::Get(
121     content::BrowserContext* context) {
122   return ExtensionSyncServiceFactory::GetForBrowserContext(context);
123 }
124 
SyncExtensionChangeIfNeeded(const Extension & extension)125 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
126     const Extension& extension) {
127   if (ignore_updates_ || !ShouldSync(extension))
128     return;
129 
130   syncer::ModelType type =
131       extension.is_app() ? syncer::APPS : syncer::EXTENSIONS;
132   SyncBundle* bundle = GetSyncBundle(type);
133   if (bundle->IsSyncing()) {
134     bundle->PushSyncAddOrUpdate(extension.id(),
135                                 CreateSyncData(extension).GetSyncData());
136     DCHECK(!ExtensionPrefs::Get(profile_)->NeedsSync(extension.id()));
137   } else {
138     ExtensionPrefs::Get(profile_)->SetNeedsSync(extension.id(), true);
139     if (system_->is_ready() && !flare_.is_null())
140       flare_.Run(type);  // Tell sync to start ASAP.
141   }
142 }
143 
WaitUntilReadyToSync(base::OnceClosure done)144 void ExtensionSyncService::WaitUntilReadyToSync(base::OnceClosure done) {
145   // Wait for the extension system to be ready.
146   system_->ready().Post(FROM_HERE, std::move(done));
147 }
148 
149 base::Optional<syncer::ModelError>
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory)150 ExtensionSyncService::MergeDataAndStartSyncing(
151     syncer::ModelType type,
152     const syncer::SyncDataList& initial_sync_data,
153     std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
154     std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory) {
155   CHECK(sync_processor.get());
156   LOG_IF(FATAL, type != syncer::EXTENSIONS && type != syncer::APPS)
157       << "Got " << type << " ModelType";
158 
159   SyncBundle* bundle = GetSyncBundle(type);
160   bundle->StartSyncing(std::move(sync_processor));
161 
162   // Apply the initial sync data, filtering out any items where we have more
163   // recent local changes. Also tell the SyncBundle the extension IDs.
164   for (const syncer::SyncData& sync_data : initial_sync_data) {
165     std::unique_ptr<ExtensionSyncData> extension_sync_data(
166         ExtensionSyncData::CreateFromSyncData(sync_data));
167     // If the extension has local state that needs to be synced, ignore this
168     // change (we assume the local state is more recent).
169     if (extension_sync_data &&
170         !ExtensionPrefs::Get(profile_)->NeedsSync(extension_sync_data->id())) {
171       ApplySyncData(*extension_sync_data);
172     }
173   }
174 
175   // Now push the local state to sync.
176   // Note: We'd like to only send out changes for extensions which have
177   // NeedsSync set. However, we can't tell if our changes ever made it to the
178   // sync server (they might not e.g. when there's a temporary auth error), so
179   // we couldn't safely clear the flag. So just send out everything and let the
180   // sync client handle no-op changes.
181   std::vector<ExtensionSyncData> data_list = GetLocalSyncDataList(type);
182   bundle->PushSyncDataList(ToSyncerSyncDataList(data_list));
183 
184   for (const ExtensionSyncData& data : data_list)
185     ExtensionPrefs::Get(profile_)->SetNeedsSync(data.id(), false);
186 
187   if (type == syncer::APPS)
188     system_->app_sorting()->FixNTPOrdinalCollisions();
189 
190   return base::nullopt;
191 }
192 
StopSyncing(syncer::ModelType type)193 void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
194   GetSyncBundle(type)->Reset();
195 }
196 
GetAllSyncDataForTesting(syncer::ModelType type) const197 syncer::SyncDataList ExtensionSyncService::GetAllSyncDataForTesting(
198     syncer::ModelType type) const {
199   const SyncBundle* bundle = GetSyncBundle(type);
200   if (!bundle->IsSyncing())
201     return syncer::SyncDataList();
202 
203   std::vector<ExtensionSyncData> sync_data_list = GetLocalSyncDataList(type);
204 
205   // Add pending data (where the local extension is not installed yet).
206   std::vector<ExtensionSyncData> pending_extensions =
207       bundle->GetPendingExtensionData();
208   sync_data_list.insert(sync_data_list.begin(),
209                         pending_extensions.begin(),
210                         pending_extensions.end());
211 
212   return ToSyncerSyncDataList(sync_data_list);
213 }
214 
ProcessSyncChanges(const base::Location & from_here,const syncer::SyncChangeList & change_list)215 base::Optional<syncer::ModelError> ExtensionSyncService::ProcessSyncChanges(
216     const base::Location& from_here,
217     const syncer::SyncChangeList& change_list) {
218   for (const syncer::SyncChange& sync_change : change_list) {
219     std::unique_ptr<ExtensionSyncData> extension_sync_data(
220         ExtensionSyncData::CreateFromSyncChange(sync_change));
221     if (extension_sync_data)
222       ApplySyncData(*extension_sync_data);
223   }
224 
225   system_->app_sorting()->FixNTPOrdinalCollisions();
226 
227   return base::nullopt;
228 }
229 
CreateSyncData(const Extension & extension) const230 ExtensionSyncData ExtensionSyncService::CreateSyncData(
231     const Extension& extension) const {
232   const std::string& id = extension.id();
233   const ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
234   int disable_reasons =
235       extension_prefs->GetDisableReasons(id) & kSyncableDisableReasons;
236   // Note that we're ignoring the enabled state during ApplySyncData (we check
237   // for the existence of disable reasons instead), we're just setting it here
238   // for older Chrome versions (<M48).
239   bool enabled = (disable_reasons == extensions::disable_reason::DISABLE_NONE);
240   if (extension_prefs->GetExtensionBlocklistState(extension.id()) ==
241       extensions::BLOCKLISTED_MALWARE) {
242     enabled = false;
243     NOTREACHED() << "Blocklisted extensions should not be getting synced.";
244   }
245 
246   bool incognito_enabled = extensions::util::IsIncognitoEnabled(id, profile_);
247   bool remote_install = extension_prefs->HasDisableReason(
248       id, extensions::disable_reason::DISABLE_REMOTE_INSTALL);
249   AppSorting* app_sorting = system_->app_sorting();
250 
251   ExtensionSyncData result =
252       extension.is_app()
253           ? ExtensionSyncData(
254                 extension, enabled, disable_reasons, incognito_enabled,
255                 remote_install, app_sorting->GetAppLaunchOrdinal(id),
256                 app_sorting->GetPageOrdinal(id),
257                 extensions::GetLaunchTypePrefValue(extension_prefs, id))
258           : ExtensionSyncData(extension, enabled, disable_reasons,
259                               incognito_enabled, remote_install);
260 
261   // If there's a pending update, send the new version to sync instead of the
262   // installed one.
263   auto it = pending_updates_.find(id);
264   if (it != pending_updates_.end()) {
265     const base::Version& version = it->second.version;
266     // If we have a pending version, it should be newer than the installed one.
267     DCHECK_EQ(-1, extension.version().CompareTo(version));
268     result.set_version(version);
269     // If we'll re-enable the extension once it's updated, also send that back
270     // to sync.
271     if (it->second.grant_permissions_and_reenable)
272       result.set_enabled(true);
273   }
274   return result;
275 }
276 
ApplySyncData(const ExtensionSyncData & extension_sync_data)277 void ExtensionSyncService::ApplySyncData(
278     const ExtensionSyncData& extension_sync_data) {
279   const std::string& id = extension_sync_data.id();
280   // Note: |extension| may be null if it hasn't been installed yet.
281   const Extension* extension =
282       ExtensionRegistry::Get(profile_)->GetInstalledExtension(id);
283   // If there is an existing extension that shouldn't be sync'd, don't
284   // apply this sync data. This can happen if the local version of an
285   // extension is default-installed, but the sync server has data from another
286   // (non-default-installed) installation. We can't apply the sync data because
287   // it would always override the local state (which would never get sync'd).
288   // See crbug.com/731824.
289   if (extension && !ShouldSync(*extension))
290     return;
291 
292   // Ignore any pref change notifications etc. while we're applying incoming
293   // sync data, so that we don't end up notifying ourselves.
294   base::AutoReset<bool> ignore_updates(&ignore_updates_, true);
295 
296   syncer::ModelType type = extension_sync_data.is_app() ? syncer::APPS
297                                                         : syncer::EXTENSIONS;
298   SyncBundle* bundle = GetSyncBundle(type);
299   DCHECK(bundle->IsSyncing());
300   if (extension && !IsCorrectSyncType(*extension, type)) {
301     // The installed item isn't the same type as the sync data item, so we need
302     // to remove the sync data item; otherwise it will be a zombie that will
303     // keep coming back even if the installed item with this id is uninstalled.
304     // First tell the bundle about the extension, so that it won't just ignore
305     // the deletion, then push the deletion.
306     bundle->ApplySyncData(extension_sync_data);
307     bundle->PushSyncDeletion(id, extension_sync_data.GetSyncData());
308     return;
309   }
310 
311   // Forward to the bundle. This will just update the list of synced extensions.
312   bundle->ApplySyncData(extension_sync_data);
313 
314   // Handle uninstalls first.
315   if (extension_sync_data.uninstalled()) {
316     base::string16 error;
317     bool uninstalled = true;
318     if (!extension) {
319       error = base::ASCIIToUTF16("Unknown extension");
320       uninstalled = false;
321     } else {
322       uninstalled = extension_service()->UninstallExtension(
323           id, extensions::UNINSTALL_REASON_SYNC, &error);
324     }
325 
326     if (!uninstalled) {
327       LOG(WARNING) << "Failed to uninstall extension with id '" << id
328                    << "' from sync: " << error;
329     }
330     return;
331   }
332 
333   // Extension from sync was uninstalled by the user as an external extension.
334   // Honor user choice and skip installation/enabling.
335   // TODO(treib): Should we still apply pref changes?
336   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
337   if (extension_prefs->IsExternalExtensionUninstalled(id)) {
338     LOG(WARNING) << "Extension with id " << id
339                  << " from sync was uninstalled as external extension";
340     return;
341   }
342 
343   enum {
344     NOT_INSTALLED,
345     INSTALLED_OUTDATED,
346     INSTALLED_MATCHING,
347     INSTALLED_NEWER,
348   } state = NOT_INSTALLED;
349   if (extension) {
350     switch (extension->version().CompareTo(extension_sync_data.version())) {
351       case -1: state = INSTALLED_OUTDATED; break;
352       case 0: state = INSTALLED_MATCHING; break;
353       case 1: state = INSTALLED_NEWER; break;
354       default: NOTREACHED();
355     }
356   }
357 
358   // Figure out the resulting set of disable reasons.
359   int disable_reasons = extension_prefs->GetDisableReasons(id);
360 
361   // Chrome versions M37-M44 used |extension_sync_data.remote_install()| to tag
362   // not-yet-approved remote installs. It's redundant now that disable reasons
363   // are synced (DISABLE_REMOTE_INSTALL should be among them already), but some
364   // old sync data may still be around, and it doesn't hurt to add the reason.
365   // TODO(crbug.com/587804): Deprecate and eventually remove |remote_install|.
366   if (extension_sync_data.remote_install())
367     disable_reasons |= extensions::disable_reason::DISABLE_REMOTE_INSTALL;
368 
369   // Add/remove disable reasons based on the incoming sync data.
370   int incoming_disable_reasons = extension_sync_data.disable_reasons();
371   if (!!incoming_disable_reasons == extension_sync_data.enabled()) {
372     // The enabled flag disagrees with the presence of disable reasons. This
373     // must either come from an old (<M45) client which doesn't sync disable
374     // reasons, or the extension is blocklisted (which doesn't have a
375     // corresponding disable reason).
376     // Update |disable_reasons| based on the enabled flag.
377     if (extension_sync_data.enabled())
378       disable_reasons &= ~kKnownSyncableDisableReasons;
379     else  // Assume the extension was likely disabled by the user.
380       disable_reasons |= extensions::disable_reason::DISABLE_USER_ACTION;
381   } else {
382     // Replace the syncable disable reasons:
383     // 1. Remove any syncable disable reasons we might have.
384     disable_reasons &= ~kSyncableDisableReasons;
385     // 2. Add the incoming reasons. Mask with |kSyncableDisableReasons|, because
386     //    Chrome M45-M47 also wrote local disable reasons to sync, and we don't
387     //    want those.
388     disable_reasons |= incoming_disable_reasons & kSyncableDisableReasons;
389   }
390 
391   // Enable/disable the extension.
392   bool should_be_enabled =
393       (disable_reasons == extensions::disable_reason::DISABLE_NONE);
394   bool reenable_after_update = false;
395   if (should_be_enabled && !extension_service()->IsExtensionEnabled(id)) {
396     if (extension) {
397       // Only grant permissions if the sync data explicitly sets the disable
398       // reasons to extensions::disable_reason::DISABLE_NONE (as opposed to the
399       // legacy
400       // (<M45) case where they're not set at all), and if the version from sync
401       // matches our local one.
402       bool grant_permissions = extension_sync_data.supports_disable_reasons() &&
403                                (state == INSTALLED_MATCHING);
404       if (grant_permissions)
405         extension_service()->GrantPermissions(extension);
406 
407       // Only enable if the extension has all required permissions.
408       // (Even if the version doesn't match - if the new version needs more
409       // permissions, it'll get disabled after the update.)
410       bool has_all_permissions =
411           grant_permissions ||
412           !extensions::PermissionMessageProvider::Get()->IsPrivilegeIncrease(
413               *extension_prefs->GetGrantedPermissions(id),
414               extension->permissions_data()->active_permissions(),
415               extension->GetType());
416       if (has_all_permissions)
417         extension_service()->EnableExtension(id);
418       else if (extension_sync_data.supports_disable_reasons())
419         reenable_after_update = true;
420     } else {
421       // The extension is not installed yet. Set it to enabled; we'll check for
422       // permission increase (more accurately, for a version change) when it's
423       // actually installed.
424       extension_service()->EnableExtension(id);
425     }
426   } else if (!should_be_enabled) {
427     // Note that |disable_reasons| includes any pre-existing reasons that
428     // weren't explicitly removed above.
429     if (extension_service()->IsExtensionEnabled(id))
430       extension_service()->DisableExtension(id, disable_reasons);
431     else  // Already disabled, just replace the disable reasons.
432       extension_prefs->ReplaceDisableReasons(id, disable_reasons);
433   }
434 
435   // Update the incognito flag.
436   extensions::util::SetIsIncognitoEnabled(
437       id, profile_, extension_sync_data.incognito_enabled());
438   extension = nullptr;  // No longer safe to use.
439 
440   // Set app-specific data.
441   if (extension_sync_data.is_app()) {
442     if (extension_sync_data.app_launch_ordinal().IsValid() &&
443         extension_sync_data.page_ordinal().IsValid()) {
444       AppSorting* app_sorting = system_->app_sorting();
445       app_sorting->SetAppLaunchOrdinal(
446           id,
447           extension_sync_data.app_launch_ordinal());
448       app_sorting->SetPageOrdinal(id, extension_sync_data.page_ordinal());
449     }
450 
451     // The corresponding validation of this value during ExtensionSyncData
452     // population is in ExtensionSyncData::ToAppSpecifics.
453     if (extension_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
454         extension_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
455       extensions::SetLaunchType(
456           profile_, id, extension_sync_data.launch_type());
457     }
458 
459     if (!extension_sync_data.bookmark_app_url().empty()) {
460       // Handles creating and updating the bookmark app.
461       ApplyBookmarkAppSyncData(extension_sync_data);
462       return;
463     }
464   }
465 
466   // Finally, trigger installation/update as required.
467   bool check_for_updates = false;
468   if (state == INSTALLED_OUTDATED) {
469     // If the extension is installed but outdated, store the new version.
470     pending_updates_[id] =
471         PendingUpdate(extension_sync_data.version(), reenable_after_update);
472     check_for_updates = true;
473   } else if (state == NOT_INSTALLED) {
474     if (!extension_service()->pending_extension_manager()->AddFromSync(
475             id,
476             extension_sync_data.update_url(),
477             extension_sync_data.version(),
478             ShouldAllowInstall,
479             extension_sync_data.remote_install())) {
480       LOG(WARNING) << "Could not add pending extension for " << id;
481       // This means that the extension is already pending installation, with a
482       // non-INTERNAL location.  Add to pending_sync_data, even though it will
483       // never be removed (we'll never install a syncable version of the
484       // extension), so that GetAllSyncData() continues to send it.
485     }
486     // Track pending extensions so that we can return them in GetAllSyncData().
487     bundle->AddPendingExtensionData(extension_sync_data);
488     check_for_updates = true;
489   }
490 
491   if (check_for_updates)
492     extension_service()->CheckForUpdatesSoon();
493 }
494 
ApplyBookmarkAppSyncData(const ExtensionSyncData & extension_sync_data)495 void ExtensionSyncService::ApplyBookmarkAppSyncData(
496     const ExtensionSyncData& extension_sync_data) {
497   DCHECK(extension_sync_data.is_app());
498 
499   // Process bookmark app sync if necessary.
500   GURL bookmark_app_url(extension_sync_data.bookmark_app_url());
501   if (!bookmark_app_url.is_valid() ||
502       extension_sync_data.uninstalled()) {
503     return;
504   }
505 
506   auto web_app_info = std::make_unique<WebApplicationInfo>();
507   web_app_info->start_url = bookmark_app_url;
508   web_app_info->title = base::UTF8ToUTF16(extension_sync_data.name());
509   web_app_info->description =
510       base::UTF8ToUTF16(extension_sync_data.bookmark_app_description());
511   web_app_info->scope = GURL(extension_sync_data.bookmark_app_scope());
512   web_app_info->theme_color = extension_sync_data.bookmark_app_theme_color();
513   web_app_info->open_as_window =
514       extension_sync_data.launch_type() == extensions::LAUNCH_TYPE_WINDOW;
515 
516   if (!extension_sync_data.bookmark_app_icon_color().empty()) {
517     extensions::image_util::ParseHexColorString(
518         extension_sync_data.bookmark_app_icon_color(),
519         &web_app_info->generated_icon_color);
520   }
521   for (const auto& icon : extension_sync_data.linked_icons()) {
522     WebApplicationIconInfo icon_info;
523     icon_info.url = icon.url;
524     icon_info.square_size_px = icon.size;
525     // Web apps in Extensions system supports Purpose::ANY icons only.
526     icon_info.purpose = blink::mojom::ManifestImageResource_Purpose::ANY;
527     web_app_info->icon_infos.push_back(icon_info);
528   }
529 
530   auto* provider = web_app::WebAppProviderBase::GetProviderBase(profile_);
531   // Legacy profiles containing server-side bookmark apps data must be excluded
532   // from sync if the web apps system is disabled for such a profile.
533   if (provider) {
534     provider->install_manager().InstallBookmarkAppFromSync(
535         extension_sync_data.id(), std::move(web_app_info), base::DoNothing());
536   }
537 }
538 
SetSyncStartFlareForTesting(const syncer::SyncableService::StartSyncFlare & flare)539 void ExtensionSyncService::SetSyncStartFlareForTesting(
540     const syncer::SyncableService::StartSyncFlare& flare) {
541   flare_ = flare;
542 }
543 
DeleteThemeDoNotUse(const Extension & theme)544 void ExtensionSyncService::DeleteThemeDoNotUse(const Extension& theme) {
545   DCHECK(theme.is_theme());
546   GetSyncBundle(syncer::EXTENSIONS)->PushSyncDeletion(
547       theme.id(), CreateSyncData(theme).GetSyncData());
548 }
549 
extension_service() const550 extensions::ExtensionService* ExtensionSyncService::extension_service() const {
551   return system_->extension_service();
552 }
553 
OnExtensionInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update)554 void ExtensionSyncService::OnExtensionInstalled(
555     content::BrowserContext* browser_context,
556     const Extension* extension,
557     bool is_update) {
558   DCHECK_EQ(profile_, browser_context);
559   // Clear pending version if the installed one has caught up.
560   auto it = pending_updates_.find(extension->id());
561   if (it != pending_updates_.end()) {
562     int compare_result = extension->version().CompareTo(it->second.version);
563     if (compare_result == 0 && it->second.grant_permissions_and_reenable) {
564       // The call to SyncExtensionChangeIfNeeded below will take care of syncing
565       // changes to this extension, so we don't want to trigger sync activity
566       // from the call to GrantPermissionsAndEnableExtension.
567       base::AutoReset<bool> ignore_updates(&ignore_updates_, true);
568       extension_service()->GrantPermissionsAndEnableExtension(extension);
569     }
570     if (compare_result >= 0)
571       pending_updates_.erase(it);
572   }
573   SyncExtensionChangeIfNeeded(*extension);
574 }
575 
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension,extensions::UninstallReason reason)576 void ExtensionSyncService::OnExtensionUninstalled(
577     content::BrowserContext* browser_context,
578     const Extension* extension,
579     extensions::UninstallReason reason) {
580   DCHECK_EQ(profile_, browser_context);
581   // Don't bother syncing if the extension will be re-installed momentarily.
582   if (reason == extensions::UNINSTALL_REASON_REINSTALL ||
583       !ShouldSync(*extension)) {
584     return;
585   }
586 
587   // TODO(tim): If we get here and IsSyncing is false, this will cause
588   // "back from the dead" style bugs, because sync will add-back the extension
589   // that was uninstalled here when MergeDataAndStartSyncing is called.
590   // See crbug.com/256795.
591   // Possible fix: Set NeedsSync here, then in MergeDataAndStartSyncing, if
592   // NeedsSync is set but the extension isn't installed, send a sync deletion.
593   if (!ignore_updates_) {
594     syncer::ModelType type =
595         extension->is_app() ? syncer::APPS : syncer::EXTENSIONS;
596     SyncBundle* bundle = GetSyncBundle(type);
597     if (bundle->IsSyncing()) {
598       bundle->PushSyncDeletion(extension->id(),
599                                CreateSyncData(*extension).GetSyncData());
600     } else if (system_->is_ready() && !flare_.is_null()) {
601       flare_.Run(type);  // Tell sync to start ASAP.
602     }
603   }
604 
605   pending_updates_.erase(extension->id());
606 }
607 
OnExtensionStateChanged(const std::string & extension_id,bool state)608 void ExtensionSyncService::OnExtensionStateChanged(
609     const std::string& extension_id,
610     bool state) {
611   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
612   const Extension* extension = registry->GetInstalledExtension(extension_id);
613   // We can get pref change notifications for extensions that aren't installed
614   // (yet). In that case, we'll pick up the change later via ExtensionRegistry
615   // observation (in OnExtensionInstalled).
616   if (extension)
617     SyncExtensionChangeIfNeeded(*extension);
618 }
619 
OnExtensionDisableReasonsChanged(const std::string & extension_id,int disabled_reasons)620 void ExtensionSyncService::OnExtensionDisableReasonsChanged(
621     const std::string& extension_id,
622     int disabled_reasons) {
623   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
624   const Extension* extension = registry->GetInstalledExtension(extension_id);
625   // We can get pref change notifications for extensions that aren't installed
626   // (yet). In that case, we'll pick up the change later via ExtensionRegistry
627   // observation (in OnExtensionInstalled).
628   if (extension)
629     SyncExtensionChangeIfNeeded(*extension);
630 }
631 
GetSyncBundle(syncer::ModelType type)632 SyncBundle* ExtensionSyncService::GetSyncBundle(syncer::ModelType type) {
633   return const_cast<SyncBundle*>(
634       const_cast<const ExtensionSyncService&>(*this).GetSyncBundle(type));
635 }
636 
GetSyncBundle(syncer::ModelType type) const637 const SyncBundle* ExtensionSyncService::GetSyncBundle(
638     syncer::ModelType type) const {
639   return (type == syncer::APPS) ? &app_sync_bundle_ : &extension_sync_bundle_;
640 }
641 
GetLocalSyncDataList(syncer::ModelType type) const642 std::vector<ExtensionSyncData> ExtensionSyncService::GetLocalSyncDataList(
643     syncer::ModelType type) const {
644   // Collect the local state.
645   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
646   std::vector<ExtensionSyncData> data;
647   // Note: Maybe we should include blocklisted/blocked extensions here, i.e.
648   // just call registry->GeneratedInstalledExtensionsSet().
649   // It would be more consistent, but the danger is that the black/blocklist
650   // hasn't been updated on all clients by the time sync has kicked in -
651   // so it's safest not to. Take care to add any other extension lists here
652   // in the future if they are added.
653   FillSyncDataList(registry->enabled_extensions(), type, &data);
654   FillSyncDataList(registry->disabled_extensions(), type, &data);
655   FillSyncDataList(registry->terminated_extensions(), type, &data);
656   return data;
657 }
658 
FillSyncDataList(const ExtensionSet & extensions,syncer::ModelType type,std::vector<ExtensionSyncData> * sync_data_list) const659 void ExtensionSyncService::FillSyncDataList(
660     const ExtensionSet& extensions,
661     syncer::ModelType type,
662     std::vector<ExtensionSyncData>* sync_data_list) const {
663   for (const scoped_refptr<const Extension>& extension : extensions) {
664     if (IsCorrectSyncType(*extension, type) && ShouldSync(*extension)) {
665       // We should never have pending data for an installed extension.
666       DCHECK(!GetSyncBundle(type)->HasPendingExtensionData(extension->id()));
667       sync_data_list->push_back(CreateSyncData(*extension));
668     }
669   }
670 }
671 
ShouldSync(const Extension & extension) const672 bool ExtensionSyncService::ShouldSync(const Extension& extension) const {
673   // Themes are handled by the ThemeSyncableService.
674   return extensions::util::ShouldSync(&extension, profile_) &&
675          !extension.is_theme();
676 }
677