extension_sync_service.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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/threading/sequenced_worker_pool.h"
11#include "base/threading/thread_restrictions.h"
12#include "chrome/browser/extensions/app_sync_data.h"
13#include "chrome/browser/extensions/extension_error_ui.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/common/extensions/sync_helper.h"
22#include "components/sync_driver/sync_prefs.h"
23#include "extensions/browser/app_sorting.h"
24#include "extensions/browser/extension_prefs.h"
25#include "extensions/browser/extension_registry.h"
26#include "extensions/common/extension.h"
27#include "extensions/common/feature_switch.h"
28#include "extensions/common/manifest_constants.h"
29#include "sync/api/sync_change.h"
30#include "sync/api/sync_error_factory.h"
31
32using extensions::Extension;
33using extensions::ExtensionPrefs;
34using extensions::ExtensionRegistry;
35using extensions::FeatureSwitch;
36
37ExtensionSyncService::ExtensionSyncService(Profile* profile,
38                                           ExtensionPrefs* extension_prefs,
39                                           ExtensionService* extension_service)
40    : profile_(profile),
41      extension_prefs_(extension_prefs),
42      extension_service_(extension_service),
43      app_sync_bundle_(this),
44      extension_sync_bundle_(this),
45      pending_app_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
46                               extension_prefs_->pref_service())),
47                           &app_sync_bundle_,
48                           syncer::APPS),
49      pending_extension_enables_(make_scoped_ptr(new sync_driver::SyncPrefs(
50                                     extension_prefs_->pref_service())),
51                                 &extension_sync_bundle_,
52                                 syncer::EXTENSIONS) {
53  SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
54      profile_->GetPath()));
55
56  extension_service_->set_extension_sync_service(this);
57  extension_prefs_->app_sorting()->SetExtensionSyncService(this);
58}
59
60ExtensionSyncService::~ExtensionSyncService() {}
61
62// static
63ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
64  return ExtensionSyncServiceFactory::GetForProfile(profile);
65}
66
67syncer::SyncChange ExtensionSyncService::PrepareToSyncUninstallExtension(
68    const extensions::Extension* extension, bool extensions_ready) {
69  // Extract the data we need for sync now, but don't actually sync until we've
70  // completed the uninstallation.
71  // TODO(tim): If we get here and IsSyncing is false, this will cause
72  // "back from the dead" style bugs, because sync will add-back the extension
73  // that was uninstalled here when MergeDataAndStartSyncing is called.
74  // See crbug.com/256795.
75  if (extensions::sync_helper::IsSyncableApp(extension)) {
76    if (app_sync_bundle_.IsSyncing())
77      return app_sync_bundle_.CreateSyncChangeToDelete(extension);
78    else if (extensions_ready && !flare_.is_null())
79      flare_.Run(syncer::APPS);  // Tell sync to start ASAP.
80  } else if (extensions::sync_helper::IsSyncableExtension(extension)) {
81    if (extension_sync_bundle_.IsSyncing())
82      return extension_sync_bundle_.CreateSyncChangeToDelete(extension);
83    else if (extensions_ready && !flare_.is_null())
84      flare_.Run(syncer::EXTENSIONS);  // Tell sync to start ASAP.
85  }
86
87  return syncer::SyncChange();
88}
89
90void ExtensionSyncService::ProcessSyncUninstallExtension(
91    const std::string& extension_id,
92    const syncer::SyncChange& sync_change) {
93  if (app_sync_bundle_.HasExtensionId(extension_id) &&
94      sync_change.sync_data().GetDataType() == syncer::APPS) {
95    app_sync_bundle_.ProcessDeletion(extension_id, sync_change);
96  } else if (extension_sync_bundle_.HasExtensionId(extension_id) &&
97             sync_change.sync_data().GetDataType() == syncer::EXTENSIONS) {
98    extension_sync_bundle_.ProcessDeletion(extension_id, sync_change);
99  }
100}
101
102void ExtensionSyncService::SyncEnableExtension(
103    const extensions::Extension& extension) {
104
105  // Syncing may not have started yet, so handle pending enables.
106  if (extensions::sync_helper::IsSyncableApp(&extension))
107    pending_app_enables_.OnExtensionEnabled(extension.id());
108
109  if (extensions::sync_helper::IsSyncableExtension(&extension))
110    pending_extension_enables_.OnExtensionEnabled(extension.id());
111
112  SyncExtensionChangeIfNeeded(extension);
113}
114
115void ExtensionSyncService::SyncDisableExtension(
116    const extensions::Extension& extension) {
117
118  // Syncing may not have started yet, so handle pending enables.
119  if (extensions::sync_helper::IsSyncableApp(&extension))
120    pending_app_enables_.OnExtensionDisabled(extension.id());
121
122  if (extensions::sync_helper::IsSyncableExtension(&extension))
123    pending_extension_enables_.OnExtensionDisabled(extension.id());
124
125  SyncExtensionChangeIfNeeded(extension);
126}
127
128syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
129    syncer::ModelType type,
130    const syncer::SyncDataList& initial_sync_data,
131    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
132    scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
133  CHECK(sync_processor.get());
134  CHECK(sync_error_factory.get());
135
136  switch (type) {
137    case syncer::EXTENSIONS:
138      extension_sync_bundle_.SetupSync(sync_processor.release(),
139                                       sync_error_factory.release(),
140                                       initial_sync_data);
141      pending_extension_enables_.OnSyncStarted(extension_service_);
142      break;
143
144    case syncer::APPS:
145      app_sync_bundle_.SetupSync(sync_processor.release(),
146                                 sync_error_factory.release(),
147                                 initial_sync_data);
148      pending_app_enables_.OnSyncStarted(extension_service_);
149      break;
150
151    default:
152      LOG(FATAL) << "Got " << type << " ModelType";
153  }
154
155  // Process local extensions.
156  // TODO(yoz): Determine whether pending extensions should be considered too.
157  //            See crbug.com/104399.
158  syncer::SyncDataList sync_data_list = GetAllSyncData(type);
159  syncer::SyncChangeList sync_change_list;
160  for (syncer::SyncDataList::const_iterator i = sync_data_list.begin();
161       i != sync_data_list.end();
162       ++i) {
163    switch (type) {
164        case syncer::EXTENSIONS:
165          sync_change_list.push_back(
166              extension_sync_bundle_.CreateSyncChange(*i));
167          break;
168        case syncer::APPS:
169          sync_change_list.push_back(app_sync_bundle_.CreateSyncChange(*i));
170          break;
171      default:
172        LOG(FATAL) << "Got " << type << " ModelType";
173    }
174  }
175
176
177  if (type == syncer::EXTENSIONS) {
178    extension_sync_bundle_.ProcessSyncChangeList(sync_change_list);
179  } else if (type == syncer::APPS) {
180    app_sync_bundle_.ProcessSyncChangeList(sync_change_list);
181  }
182
183  return syncer::SyncMergeResult(type);
184}
185
186void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
187  if (type == syncer::APPS) {
188    app_sync_bundle_.Reset();
189  } else if (type == syncer::EXTENSIONS) {
190    extension_sync_bundle_.Reset();
191  }
192}
193
194syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
195    syncer::ModelType type) const {
196  if (type == syncer::EXTENSIONS)
197    return extension_sync_bundle_.GetAllSyncData();
198  if (type == syncer::APPS)
199    return app_sync_bundle_.GetAllSyncData();
200
201  // We should only get sync data for extensions and apps.
202  NOTREACHED();
203
204  return syncer::SyncDataList();
205}
206
207syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
208    const tracked_objects::Location& from_here,
209    const syncer::SyncChangeList& change_list) {
210  for (syncer::SyncChangeList::const_iterator i = change_list.begin();
211      i != change_list.end();
212      ++i) {
213    syncer::ModelType type = i->sync_data().GetDataType();
214    if (type == syncer::EXTENSIONS) {
215      extension_sync_bundle_.ProcessSyncChange(
216          extensions::ExtensionSyncData(*i));
217    } else if (type == syncer::APPS) {
218      app_sync_bundle_.ProcessSyncChange(extensions::AppSyncData(*i));
219    }
220  }
221
222  extension_prefs_->app_sorting()->FixNTPOrdinalCollisions();
223
224  return syncer::SyncError();
225}
226
227extensions::ExtensionSyncData ExtensionSyncService::GetExtensionSyncData(
228    const Extension& extension) const {
229  return extensions::ExtensionSyncData(
230      extension,
231      extension_service_->IsExtensionEnabled(extension.id()),
232      extensions::util::IsIncognitoEnabled(extension.id(), profile_));
233}
234
235extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
236    const Extension& extension) const {
237  return extensions::AppSyncData(
238      extension,
239      extension_service_->IsExtensionEnabled(extension.id()),
240      extensions::util::IsIncognitoEnabled(extension.id(), profile_),
241      extension_prefs_->app_sorting()->GetAppLaunchOrdinal(extension.id()),
242      extension_prefs_->app_sorting()->GetPageOrdinal(extension.id()),
243      extensions::GetLaunchTypePrefValue(extension_prefs_, extension.id()));
244}
245
246std::vector<extensions::ExtensionSyncData>
247  ExtensionSyncService::GetExtensionSyncDataList() const {
248  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
249  std::vector<extensions::ExtensionSyncData> extension_sync_list;
250  extension_sync_bundle_.GetExtensionSyncDataListHelper(
251      registry->enabled_extensions(), &extension_sync_list);
252  extension_sync_bundle_.GetExtensionSyncDataListHelper(
253      registry->disabled_extensions(), &extension_sync_list);
254  extension_sync_bundle_.GetExtensionSyncDataListHelper(
255      registry->terminated_extensions(), &extension_sync_list);
256
257  std::vector<extensions::ExtensionSyncData> pending_extensions =
258      extension_sync_bundle_.GetPendingData();
259  extension_sync_list.insert(extension_sync_list.begin(),
260                             pending_extensions.begin(),
261                             pending_extensions.end());
262
263  return extension_sync_list;
264}
265
266std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
267    const {
268  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
269  std::vector<extensions::AppSyncData> app_sync_list;
270  app_sync_bundle_.GetAppSyncDataListHelper(
271      registry->enabled_extensions(), &app_sync_list);
272  app_sync_bundle_.GetAppSyncDataListHelper(
273      registry->disabled_extensions(), &app_sync_list);
274  app_sync_bundle_.GetAppSyncDataListHelper(
275      registry->terminated_extensions(), &app_sync_list);
276
277  std::vector<extensions::AppSyncData> pending_apps =
278      app_sync_bundle_.GetPendingData();
279  app_sync_list.insert(app_sync_list.begin(),
280                       pending_apps.begin(),
281                       pending_apps.end());
282
283  return app_sync_list;
284}
285
286bool ExtensionSyncService::ProcessExtensionSyncData(
287    const extensions::ExtensionSyncData& extension_sync_data) {
288  if (!ProcessExtensionSyncDataHelper(extension_sync_data,
289                                      syncer::EXTENSIONS)) {
290    extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
291                                               extension_sync_data);
292    extension_service_->CheckForUpdatesSoon();
293    return false;
294  }
295
296  return true;
297}
298
299bool ExtensionSyncService::ProcessAppSyncData(
300    const extensions::AppSyncData& app_sync_data) {
301  const std::string& id = app_sync_data.id();
302
303  if (app_sync_data.app_launch_ordinal().IsValid() &&
304      app_sync_data.page_ordinal().IsValid()) {
305    extension_prefs_->app_sorting()->SetAppLaunchOrdinal(
306        id,
307        app_sync_data.app_launch_ordinal());
308    extension_prefs_->app_sorting()->SetPageOrdinal(
309        id,
310        app_sync_data.page_ordinal());
311  }
312
313  // The corresponding validation of this value during AppSyncData population
314  // is in AppSyncData::PopulateAppSpecifics.
315  if (app_sync_data.launch_type() >= extensions::LAUNCH_TYPE_FIRST &&
316      app_sync_data.launch_type() < extensions::NUM_LAUNCH_TYPES) {
317    extensions::SetLaunchType(extension_service_, id,
318                              app_sync_data.launch_type());
319  }
320
321  if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
322                                      syncer::APPS)) {
323    app_sync_bundle_.AddPendingApp(id, app_sync_data);
324    extension_service_->CheckForUpdatesSoon();
325    return false;
326  }
327
328  return true;
329}
330
331void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
332  const extensions::Extension* ext = extension_service_->GetInstalledExtension(
333      extension_id);
334
335  if (ext)
336    SyncExtensionChangeIfNeeded(*ext);
337}
338
339void ExtensionSyncService::SetSyncStartFlare(
340    const syncer::SyncableService::StartSyncFlare& flare) {
341  flare_ = flare;
342}
343
344bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
345                                         syncer::ModelType type) const {
346  if (type == syncer::EXTENSIONS &&
347      extensions::sync_helper::IsSyncableExtension(&extension)) {
348    return true;
349  }
350
351  if (type == syncer::APPS &&
352      extensions::sync_helper::IsSyncableApp(&extension)) {
353    return true;
354  }
355
356  return false;
357}
358
359bool ExtensionSyncService::IsPendingEnable(
360    const std::string& extension_id) const {
361  return pending_app_enables_.Contains(extension_id) ||
362      pending_extension_enables_.Contains(extension_id);
363}
364
365bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
366    const extensions::ExtensionSyncData& extension_sync_data,
367    syncer::ModelType type) {
368  const std::string& id = extension_sync_data.id();
369  const Extension* extension = extension_service_->GetInstalledExtension(id);
370
371  // TODO(bolms): we should really handle this better.  The particularly bad
372  // case is where an app becomes an extension or vice versa, and we end up with
373  // a zombie extension that won't go away.
374  if (extension && !IsCorrectSyncType(*extension, type))
375    return true;
376
377  // Handle uninstalls first.
378  if (extension_sync_data.uninstalled()) {
379    if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
380      LOG(WARNING) << "Could not uninstall extension " << id
381                   << " for sync";
382    }
383    return true;
384  }
385
386  // Extension from sync was uninstalled by the user as external extensions.
387  // Honor user choice and skip installation/enabling.
388  if (extensions::ExtensionPrefs::Get(profile_)
389          ->IsExternalExtensionUninstalled(id)) {
390    LOG(WARNING) << "Extension with id " << id
391                 << " from sync was uninstalled as external extension";
392    return true;
393  }
394
395  // Set user settings.
396  // If the extension has been disabled from sync, it may not have
397  // been installed yet, so we don't know if the disable reason was a
398  // permissions increase.  That will be updated once CheckPermissionsIncrease
399  // is called for it.
400  if (extension_sync_data.enabled())
401    extension_service_->EnableExtension(id);
402  else if (!IsPendingEnable(id))
403    extension_service_->DisableExtension(
404        id, Extension::DISABLE_UNKNOWN_FROM_SYNC);
405
406  // We need to cache some version information here because setting the
407  // incognito flag invalidates the |extension| pointer (it reloads the
408  // extension).
409  bool extension_installed = (extension != NULL);
410  int result = extension ?
411      extension->version()->CompareTo(extension_sync_data.version()) : 0;
412  extensions::util::SetIsIncognitoEnabled(
413      id, profile_, extension_sync_data.incognito_enabled());
414  extension = NULL;  // No longer safe to use.
415
416  if (extension_installed) {
417    // If the extension is already installed, check if it's outdated.
418    if (result < 0) {
419      // Extension is outdated.
420      return false;
421    }
422  } else {
423    // TODO(akalin): Replace silent update with a list of enabled
424    // permissions.
425    const bool kInstallSilently = true;
426
427    CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
428    extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
429        (type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
430                                 extensions::sync_helper::IsSyncableExtension;
431
432    if (!extension_service_->pending_extension_manager()->AddFromSync(
433            id,
434            extension_sync_data.update_url(),
435            filter,
436            kInstallSilently)) {
437      LOG(WARNING) << "Could not add pending extension for " << id;
438      // This means that the extension is already pending installation, with a
439      // non-INTERNAL location.  Add to pending_sync_data, even though it will
440      // never be removed (we'll never install a syncable version of the
441      // extension), so that GetAllSyncData() continues to send it.
442    }
443    // Track pending extensions so that we can return them in GetAllSyncData().
444    return false;
445  }
446
447  return true;
448}
449
450void ExtensionSyncService::SyncExtensionChangeIfNeeded(
451    const Extension& extension) {
452  if (extensions::sync_helper::IsSyncableApp(&extension)) {
453    if (app_sync_bundle_.IsSyncing())
454      app_sync_bundle_.SyncChangeIfNeeded(extension);
455    else if (extension_service_->is_ready() && !flare_.is_null())
456      flare_.Run(syncer::APPS);
457  } else if (extensions::sync_helper::IsSyncableExtension(&extension)) {
458    if (extension_sync_bundle_.IsSyncing())
459      extension_sync_bundle_.SyncChangeIfNeeded(extension);
460    else if (extension_service_->is_ready() && !flare_.is_null())
461      flare_.Run(syncer::EXTENSIONS);
462  }
463}
464