• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <iterator>
8 
9 #include "base/basictypes.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/threading/sequenced_worker_pool.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "chrome/browser/extensions/app_sync_data.h"
14 #include "chrome/browser/extensions/bookmark_app_helper.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/extension_sync_data.h"
17 #include "chrome/browser/extensions/extension_sync_service_factory.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/extensions/launch_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/sync/glue/sync_start_util.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
24 #include "chrome/common/extensions/sync_helper.h"
25 #include "chrome/common/web_application_info.h"
26 #include "components/sync_driver/sync_prefs.h"
27 #include "extensions/browser/app_sorting.h"
28 #include "extensions/browser/extension_prefs.h"
29 #include "extensions/browser/extension_registry.h"
30 #include "extensions/browser/extension_util.h"
31 #include "extensions/common/extension.h"
32 #include "extensions/common/extension_icon_set.h"
33 #include "extensions/common/feature_switch.h"
34 #include "extensions/common/manifest_constants.h"
35 #include "extensions/common/manifest_handlers/icons_handler.h"
36 #include "sync/api/sync_change.h"
37 #include "sync/api/sync_error_factory.h"
38 #include "ui/gfx/image/image_family.h"
39 
40 using extensions::Extension;
41 using extensions::ExtensionPrefs;
42 using extensions::ExtensionRegistry;
43 using extensions::FeatureSwitch;
44 
45 namespace {
46 
OnWebApplicationInfoLoaded(WebApplicationInfo synced_info,base::WeakPtr<ExtensionService> extension_service,const WebApplicationInfo & loaded_info)47 void OnWebApplicationInfoLoaded(
48     WebApplicationInfo synced_info,
49     base::WeakPtr<ExtensionService> extension_service,
50     const WebApplicationInfo& loaded_info) {
51   DCHECK_EQ(synced_info.app_url, loaded_info.app_url);
52 
53   if (!extension_service)
54     return;
55 
56   // Use the old icons if they exist.
57   synced_info.icons = loaded_info.icons;
58   CreateOrUpdateBookmarkApp(extension_service.get(), synced_info);
59 }
60 
61 }  // namespace
62 
ExtensionSyncService(Profile * profile,ExtensionPrefs * extension_prefs,ExtensionService * extension_service)63 ExtensionSyncService::ExtensionSyncService(Profile* profile,
64                                            ExtensionPrefs* extension_prefs,
65                                            ExtensionService* extension_service)
66     : profile_(profile),
67       extension_prefs_(extension_prefs),
68       extension_service_(extension_service),
69       app_sync_bundle_(this),
70       extension_sync_bundle_(this),
71       pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
72                                extension_prefs_->pref_service())),
73                            &app_sync_bundle_,
74                            syncer::APPS),
75       pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
76                                      extension_prefs_->pref_service())),
77                                  &extension_sync_bundle_,
78                                  syncer::EXTENSIONS) {
79   SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
80       profile_->GetPath()));
81 
82   extension_service_->set_extension_sync_service(this);
83   extension_prefs_->app_sorting()->SetExtensionSyncService(this);
84 }
85 
~ExtensionSyncService()86 ExtensionSyncService::~ExtensionSyncService() {}
87 
88 // static
Get(Profile * profile)89 ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
90   return ExtensionSyncServiceFactory::GetForProfile(profile);
91 }
92 
PrepareToSyncUninstallExtension(const extensions::Extension * extension,bool extensions_ready)93 syncer::SyncChange ExtensionSyncService::PrepareToSyncUninstallExtension(
94     const extensions::Extension* extension, bool extensions_ready) {
95   // Extract the data we need for sync now, but don't actually sync until we've
96   // completed the uninstallation.
97   // TODO(tim): If we get here and IsSyncing is false, this will cause
98   // "back from the dead" style bugs, because sync will add-back the extension
99   // that was uninstalled here when MergeDataAndStartSyncing is called.
100   // See crbug.com/256795.
101   if (extensions::util::ShouldSyncApp(extension, profile_)) {
102     if (app_sync_bundle_.IsSyncing())
103       return app_sync_bundle_.CreateSyncChangeToDelete(extension);
104     else if (extensions_ready && !flare_.is_null())
105       flare_.Run(syncer::APPS);  // Tell sync to start ASAP.
106   } else if (extensions::sync_helper::IsSyncableExtension(extension)) {
107     if (extension_sync_bundle_.IsSyncing())
108       return extension_sync_bundle_.CreateSyncChangeToDelete(extension);
109     else if (extensions_ready && !flare_.is_null())
110       flare_.Run(syncer::EXTENSIONS);  // Tell sync to start ASAP.
111   }
112 
113   return syncer::SyncChange();
114 }
115 
ProcessSyncUninstallExtension(const std::string & extension_id,const syncer::SyncChange & sync_change)116 void ExtensionSyncService::ProcessSyncUninstallExtension(
117     const std::string& extension_id,
118     const syncer::SyncChange& sync_change) {
119   if (app_sync_bundle_.HasExtensionId(extension_id) &&
120       sync_change.sync_data().GetDataType() == syncer::APPS) {
121     app_sync_bundle_.ProcessDeletion(extension_id, sync_change);
122   } else if (extension_sync_bundle_.HasExtensionId(extension_id) &&
123              sync_change.sync_data().GetDataType() == syncer::EXTENSIONS) {
124     extension_sync_bundle_.ProcessDeletion(extension_id, sync_change);
125   }
126 }
127 
SyncEnableExtension(const extensions::Extension & extension)128 void ExtensionSyncService::SyncEnableExtension(
129     const extensions::Extension& extension) {
130 
131   // Syncing may not have started yet, so handle pending enables.
132   if (extensions::util::ShouldSyncApp(&extension, profile_))
133     pending_app_enables_.OnExtensionEnabled(extension.id());
134 
135   if (extensions::util::ShouldSyncExtension(&extension, profile_))
136     pending_extension_enables_.OnExtensionEnabled(extension.id());
137 
138   SyncExtensionChangeIfNeeded(extension);
139 }
140 
SyncDisableExtension(const extensions::Extension & extension)141 void ExtensionSyncService::SyncDisableExtension(
142     const extensions::Extension& extension) {
143 
144   // Syncing may not have started yet, so handle pending enables.
145   if (extensions::util::ShouldSyncApp(&extension, profile_))
146     pending_app_enables_.OnExtensionDisabled(extension.id());
147 
148   if (extensions::util::ShouldSyncExtension(&extension, profile_))
149     pending_extension_enables_.OnExtensionDisabled(extension.id());
150 
151   SyncExtensionChangeIfNeeded(extension);
152 }
153 
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,scoped_ptr<syncer::SyncChangeProcessor> sync_processor,scoped_ptr<syncer::SyncErrorFactory> sync_error_factory)154 syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
155     syncer::ModelType type,
156     const syncer::SyncDataList& initial_sync_data,
157     scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
158     scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
159   CHECK(sync_processor.get());
160   CHECK(sync_error_factory.get());
161 
162   switch (type) {
163     case syncer::EXTENSIONS:
164       extension_sync_bundle_.SetupSync(sync_processor.release(),
165                                        sync_error_factory.release(),
166                                        initial_sync_data);
167       pending_extension_enables_.OnSyncStarted(extension_service_);
168       break;
169 
170     case syncer::APPS:
171       app_sync_bundle_.SetupSync(sync_processor.release(),
172                                  sync_error_factory.release(),
173                                  initial_sync_data);
174       pending_app_enables_.OnSyncStarted(extension_service_);
175       break;
176 
177     default:
178       LOG(FATAL) << "Got " << type << " ModelType";
179   }
180 
181   // Process local extensions.
182   // TODO(yoz): Determine whether pending extensions should be considered too.
183   //            See crbug.com/104399.
184   syncer::SyncDataList sync_data_list = GetAllSyncData(type);
185   syncer::SyncChangeList sync_change_list;
186   for (syncer::SyncDataList::const_iterator i = sync_data_list.begin();
187        i != sync_data_list.end();
188        ++i) {
189     switch (type) {
190         case syncer::EXTENSIONS:
191           sync_change_list.push_back(
192               extension_sync_bundle_.CreateSyncChange(*i));
193           break;
194         case syncer::APPS:
195           sync_change_list.push_back(app_sync_bundle_.CreateSyncChange(*i));
196           break;
197       default:
198         LOG(FATAL) << "Got " << type << " ModelType";
199     }
200   }
201 
202 
203   if (type == syncer::EXTENSIONS) {
204     extension_sync_bundle_.ProcessSyncChangeList(sync_change_list);
205   } else if (type == syncer::APPS) {
206     app_sync_bundle_.ProcessSyncChangeList(sync_change_list);
207   }
208 
209   return syncer::SyncMergeResult(type);
210 }
211 
StopSyncing(syncer::ModelType type)212 void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
213   if (type == syncer::APPS) {
214     app_sync_bundle_.Reset();
215   } else if (type == syncer::EXTENSIONS) {
216     extension_sync_bundle_.Reset();
217   }
218 }
219 
GetAllSyncData(syncer::ModelType type) const220 syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
221     syncer::ModelType type) const {
222   if (type == syncer::EXTENSIONS)
223     return extension_sync_bundle_.GetAllSyncData();
224   if (type == syncer::APPS)
225     return app_sync_bundle_.GetAllSyncData();
226 
227   // We should only get sync data for extensions and apps.
228   NOTREACHED();
229 
230   return syncer::SyncDataList();
231 }
232 
ProcessSyncChanges(const tracked_objects::Location & from_here,const syncer::SyncChangeList & change_list)233 syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
234     const tracked_objects::Location& from_here,
235     const syncer::SyncChangeList& change_list) {
236   for (syncer::SyncChangeList::const_iterator i = change_list.begin();
237       i != change_list.end();
238       ++i) {
239     syncer::ModelType type = i->sync_data().GetDataType();
240     if (type == syncer::EXTENSIONS) {
241       extension_sync_bundle_.ProcessSyncChange(
242           extensions::ExtensionSyncData(*i));
243     } else if (type == syncer::APPS) {
244       app_sync_bundle_.ProcessSyncChange(extensions::AppSyncData(*i));
245     }
246   }
247 
248   extension_prefs_->app_sorting()->FixNTPOrdinalCollisions();
249 
250   return syncer::SyncError();
251 }
252 
GetExtensionSyncData(const Extension & extension) const253 extensions::ExtensionSyncData ExtensionSyncService::GetExtensionSyncData(
254     const Extension& extension) const {
255   return extensions::ExtensionSyncData(
256       extension,
257       extension_service_->IsExtensionEnabled(extension.id()),
258       extensions::util::IsIncognitoEnabled(extension.id(), profile_),
259       extension_prefs_->HasDisableReason(extension.id(),
260                                          Extension::DISABLE_REMOTE_INSTALL));
261 }
262 
GetAppSyncData(const Extension & extension) const263 extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
264     const Extension& extension) const {
265   return extensions::AppSyncData(
266       extension,
267       extension_service_->IsExtensionEnabled(extension.id()),
268       extensions::util::IsIncognitoEnabled(extension.id(), profile_),
269       extension_prefs_->HasDisableReason(extension.id(),
270                                          Extension::DISABLE_REMOTE_INSTALL),
271       extension_prefs_->app_sorting()->GetAppLaunchOrdinal(extension.id()),
272       extension_prefs_->app_sorting()->GetPageOrdinal(extension.id()),
273       extensions::GetLaunchTypePrefValue(extension_prefs_, extension.id()));
274 }
275 
276 std::vector<extensions::ExtensionSyncData>
GetExtensionSyncDataList() const277   ExtensionSyncService::GetExtensionSyncDataList() const {
278   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
279   std::vector<extensions::ExtensionSyncData> extension_sync_list;
280   extension_sync_bundle_.GetExtensionSyncDataListHelper(
281       registry->enabled_extensions(), &extension_sync_list);
282   extension_sync_bundle_.GetExtensionSyncDataListHelper(
283       registry->disabled_extensions(), &extension_sync_list);
284   extension_sync_bundle_.GetExtensionSyncDataListHelper(
285       registry->terminated_extensions(), &extension_sync_list);
286 
287   std::vector<extensions::ExtensionSyncData> pending_extensions =
288       extension_sync_bundle_.GetPendingData();
289   extension_sync_list.insert(extension_sync_list.begin(),
290                              pending_extensions.begin(),
291                              pending_extensions.end());
292 
293   return extension_sync_list;
294 }
295 
GetAppSyncDataList() const296 std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
297     const {
298   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
299   std::vector<extensions::AppSyncData> app_sync_list;
300   app_sync_bundle_.GetAppSyncDataListHelper(
301       registry->enabled_extensions(), &app_sync_list);
302   app_sync_bundle_.GetAppSyncDataListHelper(
303       registry->disabled_extensions(), &app_sync_list);
304   app_sync_bundle_.GetAppSyncDataListHelper(
305       registry->terminated_extensions(), &app_sync_list);
306 
307   std::vector<extensions::AppSyncData> pending_apps =
308       app_sync_bundle_.GetPendingData();
309   app_sync_list.insert(app_sync_list.begin(),
310                        pending_apps.begin(),
311                        pending_apps.end());
312 
313   return app_sync_list;
314 }
315 
ProcessExtensionSyncData(const extensions::ExtensionSyncData & extension_sync_data)316 bool ExtensionSyncService::ProcessExtensionSyncData(
317     const extensions::ExtensionSyncData& extension_sync_data) {
318   if (!ProcessExtensionSyncDataHelper(extension_sync_data,
319                                       syncer::EXTENSIONS)) {
320     extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
321                                                extension_sync_data);
322     extension_service_->CheckForUpdatesSoon();
323     return false;
324   }
325 
326   return true;
327 }
328 
ProcessAppSyncData(const extensions::AppSyncData & app_sync_data)329 bool ExtensionSyncService::ProcessAppSyncData(
330     const extensions::AppSyncData& app_sync_data) {
331   const std::string& id = app_sync_data.id();
332 
333   if (app_sync_data.app_launch_ordinal().IsValid() &&
334       app_sync_data.page_ordinal().IsValid()) {
335     extension_prefs_->app_sorting()->SetAppLaunchOrdinal(
336         id,
337         app_sync_data.app_launch_ordinal());
338     extension_prefs_->app_sorting()->SetPageOrdinal(
339         id,
340         app_sync_data.page_ordinal());
341   }
342 
343   // The corresponding validation of this value during AppSyncData population
344   // is in AppSyncData::PopulateAppSpecifics.
345   if (app_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
346       app_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
347     extensions::SetLaunchType(extension_service_, id,
348                               app_sync_data.launch_type());
349   }
350 
351   if (!app_sync_data.bookmark_app_url().empty())
352     ProcessBookmarkAppSyncData(app_sync_data);
353 
354   if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
355                                       syncer::APPS)) {
356     app_sync_bundle_.AddPendingApp(id, app_sync_data);
357     extension_service_->CheckForUpdatesSoon();
358     return false;
359   }
360 
361   return true;
362 }
363 
ProcessBookmarkAppSyncData(const extensions::AppSyncData & app_sync_data)364 void ExtensionSyncService::ProcessBookmarkAppSyncData(
365     const extensions::AppSyncData& app_sync_data) {
366   // Process bookmark app sync if necessary.
367   GURL bookmark_app_url(app_sync_data.bookmark_app_url());
368   if (!bookmark_app_url.is_valid() ||
369       app_sync_data.extension_sync_data().uninstalled()) {
370     return;
371   }
372 
373   const extensions::Extension* extension =
374       extension_service_->GetInstalledExtension(
375           app_sync_data.extension_sync_data().id());
376 
377   // Return if there are no bookmark app details that need updating.
378   if (extension && extension->non_localized_name() ==
379                        app_sync_data.extension_sync_data().name() &&
380       extension->description() == app_sync_data.bookmark_app_description()) {
381     return;
382   }
383 
384   WebApplicationInfo web_app_info;
385   web_app_info.app_url = bookmark_app_url;
386   web_app_info.title =
387       base::UTF8ToUTF16(app_sync_data.extension_sync_data().name());
388   web_app_info.description =
389       base::UTF8ToUTF16(app_sync_data.bookmark_app_description());
390 
391   // If the bookmark app already exists, keep the old icons.
392   if (!extension) {
393     CreateOrUpdateBookmarkApp(extension_service_, web_app_info);
394   } else {
395     app_sync_data.extension_sync_data().name();
396     GetWebApplicationInfoFromApp(profile_,
397                                  extension,
398                                  base::Bind(&OnWebApplicationInfoLoaded,
399                                             web_app_info,
400                                             extension_service_->AsWeakPtr()));
401   }
402 }
403 
SyncOrderingChange(const std::string & extension_id)404 void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
405   const extensions::Extension* ext = extension_service_->GetInstalledExtension(
406       extension_id);
407 
408   if (ext)
409     SyncExtensionChangeIfNeeded(*ext);
410 }
411 
SetSyncStartFlare(const syncer::SyncableService::StartSyncFlare & flare)412 void ExtensionSyncService::SetSyncStartFlare(
413     const syncer::SyncableService::StartSyncFlare& flare) {
414   flare_ = flare;
415 }
416 
IsCorrectSyncType(const Extension & extension,syncer::ModelType type) const417 bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
418                                          syncer::ModelType type) const {
419   if (type == syncer::EXTENSIONS &&
420       extensions::sync_helper::IsSyncableExtension(&extension)) {
421     return true;
422   }
423 
424   if (type == syncer::APPS &&
425       extensions::sync_helper::IsSyncableApp(&extension)) {
426     return true;
427   }
428 
429   return false;
430 }
431 
IsPendingEnable(const std::string & extension_id) const432 bool ExtensionSyncService::IsPendingEnable(
433     const std::string& extension_id) const {
434   return pending_app_enables_.Contains(extension_id) ||
435       pending_extension_enables_.Contains(extension_id);
436 }
437 
ProcessExtensionSyncDataHelper(const extensions::ExtensionSyncData & extension_sync_data,syncer::ModelType type)438 bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
439     const extensions::ExtensionSyncData& extension_sync_data,
440     syncer::ModelType type) {
441   const std::string& id = extension_sync_data.id();
442   const Extension* extension = extension_service_->GetInstalledExtension(id);
443 
444   // TODO(bolms): we should really handle this better.  The particularly bad
445   // case is where an app becomes an extension or vice versa, and we end up with
446   // a zombie extension that won't go away.
447   if (extension && !IsCorrectSyncType(*extension, type))
448     return true;
449 
450   // Handle uninstalls first.
451   if (extension_sync_data.uninstalled()) {
452     if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
453       LOG(WARNING) << "Could not uninstall extension " << id
454                    << " for sync";
455     }
456     return true;
457   }
458 
459   // Extension from sync was uninstalled by the user as external extensions.
460   // Honor user choice and skip installation/enabling.
461   if (extensions::ExtensionPrefs::Get(profile_)
462           ->IsExternalExtensionUninstalled(id)) {
463     LOG(WARNING) << "Extension with id " << id
464                  << " from sync was uninstalled as external extension";
465     return true;
466   }
467 
468   // Set user settings.
469   // If the extension has been disabled from sync, it may not have
470   // been installed yet, so we don't know if the disable reason was a
471   // permissions increase.  That will be updated once CheckPermissionsIncrease
472   // is called for it.
473   // However if the extension is marked as a remote install in sync, we know
474   // what the disable reason is, so set it to that directly. Note that when
475   // CheckPermissionsIncrease runs, it might still add permissions increase
476   // as a disable reason for the extension.
477   if (extension_sync_data.enabled()) {
478     extension_service_->EnableExtension(id);
479   } else if (!IsPendingEnable(id)) {
480     if (extension_sync_data.remote_install()) {
481       extension_service_->DisableExtension(id,
482                                            Extension::DISABLE_REMOTE_INSTALL);
483     } else {
484       extension_service_->DisableExtension(
485           id, Extension::DISABLE_UNKNOWN_FROM_SYNC);
486     }
487   }
488 
489   // We need to cache some version information here because setting the
490   // incognito flag invalidates the |extension| pointer (it reloads the
491   // extension).
492   bool extension_installed = (extension != NULL);
493   int version_compare_result = extension ?
494       extension->version()->CompareTo(extension_sync_data.version()) : 0;
495 
496   // If the target extension has already been installed ephemerally, it can
497   // be promoted to a regular installed extension and downloading from the Web
498   // Store is not necessary.
499   if (extension && extensions::util::IsEphemeralApp(id, profile_))
500     extension_service_->PromoteEphemeralApp(extension, true);
501 
502   // Update the incognito flag.
503   extensions::util::SetIsIncognitoEnabled(
504       id, profile_, extension_sync_data.incognito_enabled());
505   extension = NULL;  // No longer safe to use.
506 
507   if (extension_installed) {
508     // If the extension is already installed, check if it's outdated.
509     if (version_compare_result < 0) {
510       // Extension is outdated.
511       return false;
512     }
513   } else {
514     // TODO(akalin): Replace silent update with a list of enabled
515     // permissions.
516     const bool kInstallSilently = true;
517 
518     CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
519     extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
520         (type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
521                                  extensions::sync_helper::IsSyncableExtension;
522 
523     if (!extension_service_->pending_extension_manager()->AddFromSync(
524             id,
525             extension_sync_data.update_url(),
526             filter,
527             kInstallSilently,
528             extension_sync_data.remote_install())) {
529       LOG(WARNING) << "Could not add pending extension for " << id;
530       // This means that the extension is already pending installation, with a
531       // non-INTERNAL location.  Add to pending_sync_data, even though it will
532       // never be removed (we'll never install a syncable version of the
533       // extension), so that GetAllSyncData() continues to send it.
534     }
535     // Track pending extensions so that we can return them in GetAllSyncData().
536     return false;
537   }
538 
539   return true;
540 }
541 
SyncExtensionChangeIfNeeded(const Extension & extension)542 void ExtensionSyncService::SyncExtensionChangeIfNeeded(
543     const Extension& extension) {
544   if (extensions::util::ShouldSyncApp(&extension, profile_)) {
545     if (app_sync_bundle_.IsSyncing())
546       app_sync_bundle_.SyncChangeIfNeeded(extension);
547     else if (extension_service_->is_ready() && !flare_.is_null())
548       flare_.Run(syncer::APPS);
549   } else if (extensions::util::ShouldSyncExtension(&extension, profile_)) {
550     if (extension_sync_bundle_.IsSyncing())
551       extension_sync_bundle_.SyncChangeIfNeeded(extension);
552     else if (extension_service_->is_ready() && !flare_.is_null())
553       flare_.Run(syncer::EXTENSIONS);
554   }
555 }
556