extension_sync_service.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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
40using extensions::Extension;
41using extensions::ExtensionPrefs;
42using extensions::ExtensionRegistry;
43using extensions::FeatureSwitch;
44
45namespace {
46
47void 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
63ExtensionSyncService::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
86ExtensionSyncService::~ExtensionSyncService() {}
87
88// static
89ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
90  return ExtensionSyncServiceFactory::GetForProfile(profile);
91}
92
93syncer::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
116void 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
128void 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
141void 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
154syncer::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
212void 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
220syncer::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
233syncer::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
253extensions::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
263extensions::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
276std::vector<extensions::ExtensionSyncData>
277  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
296std::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
316bool 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
329bool 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
364void 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
404void 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
412void ExtensionSyncService::SetSyncStartFlare(
413    const syncer::SyncableService::StartSyncFlare& flare) {
414  flare_ = flare;
415}
416
417bool 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
432bool 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
438bool 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
542void 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