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