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