extension_sync_service.cc revision f2477e01787aa58f445919b809d89e252beef54f
190c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein// Copyright 2013 The Chromium Authors. All rights reserved.
290c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein// Use of this source code is governed by a BSD-style license that can be
390c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein// found in the LICENSE file.
45d9d10e8217d2138b5514a4d4216f95373240942mtklein
540b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein#include "chrome/browser/extensions/extension_sync_service.h"
640b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein
76238688af0d758660d344ec047243d4efefd6f4dmtklein#include <iterator>
86238688af0d758660d344ec047243d4efefd6f4dmtklein
96238688af0d758660d344ec047243d4efefd6f4dmtklein#include "base/basictypes.h"
106238688af0d758660d344ec047243d4efefd6f4dmtklein#include "base/threading/sequenced_worker_pool.h"
116238688af0d758660d344ec047243d4efefd6f4dmtklein#include "base/threading/thread_restrictions.h"
125d9d10e8217d2138b5514a4d4216f95373240942mtklein#include "chrome/browser/extensions/app_sync_data.h"
1390c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_error_ui.h"
1490c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_prefs.h"
1590c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_service.h"
1690c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_sync_data.h"
1790c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_sync_service_factory.h"
1890c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/extensions/extension_util.h"
1990c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/profiles/profile.h"
2090c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/sync/glue/sync_start_util.h"
2190c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/browser/sync/sync_prefs.h"
2290c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "chrome/common/extensions/sync_helper.h"
2390c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "content/public/browser/browser_thread.h"
2490c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "extensions/browser/app_sorting.h"
2590c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "extensions/common/extension.h"
2690c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "extensions/common/feature_switch.h"
2790c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "extensions/common/manifest_constants.h"
2890c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "sync/api/sync_change.h"
2990c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein#include "sync/api/sync_error_factory.h"
3090c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein
3190c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtkleinusing extensions::Extension;
3290c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtkleinusing extensions::ExtensionPrefs;
3340b32be3718f0f2e01c4a21bb0004b7f93670c42mtkleinusing extensions::FeatureSwitch;
3440b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein
3540b32be3718f0f2e01c4a21bb0004b7f93670c42mtkleinExtensionSyncService::ExtensionSyncService(Profile* profile,
3640b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein                                           ExtensionPrefs* extension_prefs,
3740b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein                                           ExtensionService* extension_service)
385d9d10e8217d2138b5514a4d4216f95373240942mtklein    : profile_(profile),
39a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein      extension_prefs_(extension_prefs),
405d9d10e8217d2138b5514a4d4216f95373240942mtklein      extension_service_(extension_service),
41a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein      app_sync_bundle_(this),
42a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein      extension_sync_bundle_(this),
43a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein      pending_app_enables_(
44a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein          make_scoped_ptr(new browser_sync::SyncPrefs(
45a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein              extension_prefs_->pref_service())),
46a189ccdb4d5efdbbfa34a26c08656634b615f930mtklein          &app_sync_bundle_,
475d9d10e8217d2138b5514a4d4216f95373240942mtklein          syncer::APPS),
485d9d10e8217d2138b5514a4d4216f95373240942mtklein      pending_extension_enables_(
495d9d10e8217d2138b5514a4d4216f95373240942mtklein          make_scoped_ptr(new browser_sync::SyncPrefs(
505d9d10e8217d2138b5514a4d4216f95373240942mtklein              extension_prefs_->pref_service())),
51912947737a973421f4c58682b6171cb5ee00ad3aMike Klein          &extension_sync_bundle_,
525d9d10e8217d2138b5514a4d4216f95373240942mtklein          syncer::EXTENSIONS) {
535d9d10e8217d2138b5514a4d4216f95373240942mtklein  SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
545d9d10e8217d2138b5514a4d4216f95373240942mtklein      profile_->GetPath()));
5590c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein
5690c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein  extension_service_->set_extension_sync_service(this);
5790c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein  extension_prefs_->app_sorting()->SetExtensionSyncService(this);
5890c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein}
595d9d10e8217d2138b5514a4d4216f95373240942mtklein
605d9d10e8217d2138b5514a4d4216f95373240942mtkleinExtensionSyncService::~ExtensionSyncService() {}
6140b32be3718f0f2e01c4a21bb0004b7f93670c42mtklein
625d9d10e8217d2138b5514a4d4216f95373240942mtklein// static
6390c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtkleinExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
6490c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein  return ExtensionSyncServiceFactory::GetForProfile(profile);
6590c471e73fb0bd09aef8ad1e3f0842e5d46fb342mtklein}
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      extension_util::IsIncognitoEnabled(extension.id(), extension_service_));
233}
234
235extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
236    const Extension& extension) const {
237  return extensions::AppSyncData(
238      extension,
239      extension_service_->IsExtensionEnabled(extension.id()),
240      extension_util::IsIncognitoEnabled(extension.id(), extension_service_),
241      extension_prefs_->app_sorting()->GetAppLaunchOrdinal(extension.id()),
242      extension_prefs_->app_sorting()->GetPageOrdinal(extension.id()));
243}
244
245std::vector<extensions::ExtensionSyncData>
246  ExtensionSyncService::GetExtensionSyncDataList() const {
247  std::vector<extensions::ExtensionSyncData> extension_sync_list;
248  extension_sync_bundle_.GetExtensionSyncDataListHelper(
249      extension_service_->extensions(), &extension_sync_list);
250  extension_sync_bundle_.GetExtensionSyncDataListHelper(
251      extension_service_->disabled_extensions(), &extension_sync_list);
252  extension_sync_bundle_.GetExtensionSyncDataListHelper(
253      extension_service_->terminated_extensions(), &extension_sync_list);
254
255  std::vector<extensions::ExtensionSyncData> pending_extensions =
256      extension_sync_bundle_.GetPendingData();
257  extension_sync_list.insert(extension_sync_list.begin(),
258                             pending_extensions.begin(),
259                             pending_extensions.end());
260
261  return extension_sync_list;
262}
263
264std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
265    const {
266  std::vector<extensions::AppSyncData> app_sync_list;
267  app_sync_bundle_.GetAppSyncDataListHelper(
268      extension_service_->extensions(), &app_sync_list);
269  app_sync_bundle_.GetAppSyncDataListHelper(
270      extension_service_->disabled_extensions(), &app_sync_list);
271  app_sync_bundle_.GetAppSyncDataListHelper(
272      extension_service_->terminated_extensions(), &app_sync_list);
273
274  std::vector<extensions::AppSyncData> pending_apps =
275      app_sync_bundle_.GetPendingData();
276  app_sync_list.insert(app_sync_list.begin(),
277                       pending_apps.begin(),
278                       pending_apps.end());
279
280  return app_sync_list;
281}
282
283bool ExtensionSyncService::ProcessExtensionSyncData(
284    const extensions::ExtensionSyncData& extension_sync_data) {
285  if (!ProcessExtensionSyncDataHelper(extension_sync_data,
286                                      syncer::EXTENSIONS)) {
287    extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
288                                               extension_sync_data);
289    extension_service_->CheckForUpdatesSoon();
290    return false;
291  }
292
293  return true;
294}
295
296bool ExtensionSyncService::ProcessAppSyncData(
297    const extensions::AppSyncData& app_sync_data) {
298  const std::string& id = app_sync_data.id();
299
300  if (app_sync_data.app_launch_ordinal().IsValid() &&
301      app_sync_data.page_ordinal().IsValid()) {
302    extension_prefs_->app_sorting()->SetAppLaunchOrdinal(
303        id,
304        app_sync_data.app_launch_ordinal());
305    extension_prefs_->app_sorting()->SetPageOrdinal(
306        id,
307        app_sync_data.page_ordinal());
308  }
309
310  if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
311                                      syncer::APPS)) {
312    app_sync_bundle_.AddPendingApp(id, app_sync_data);
313    extension_service_->CheckForUpdatesSoon();
314    return false;
315  }
316
317  return true;
318}
319
320void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
321  const extensions::Extension* ext = extension_service_->GetInstalledExtension(
322      extension_id);
323
324  if (ext)
325    SyncExtensionChangeIfNeeded(*ext);
326}
327
328void ExtensionSyncService::SetSyncStartFlare(
329    const syncer::SyncableService::StartSyncFlare& flare) {
330  flare_ = flare;
331}
332
333bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
334                                         syncer::ModelType type) const {
335  if (type == syncer::EXTENSIONS &&
336      extensions::sync_helper::IsSyncableExtension(&extension)) {
337    return true;
338  }
339
340  if (type == syncer::APPS &&
341      extensions::sync_helper::IsSyncableApp(&extension)) {
342    return true;
343  }
344
345  return false;
346}
347
348bool ExtensionSyncService::IsPendingEnable(
349    const std::string& extension_id) const {
350  return pending_app_enables_.Contains(extension_id) ||
351      pending_extension_enables_.Contains(extension_id);
352}
353
354bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
355    const extensions::ExtensionSyncData& extension_sync_data,
356    syncer::ModelType type) {
357  const std::string& id = extension_sync_data.id();
358  const Extension* extension = extension_service_->GetInstalledExtension(id);
359
360  // TODO(bolms): we should really handle this better.  The particularly bad
361  // case is where an app becomes an extension or vice versa, and we end up with
362  // a zombie extension that won't go away.
363  if (extension && !IsCorrectSyncType(*extension, type))
364    return true;
365
366  // Handle uninstalls first.
367  if (extension_sync_data.uninstalled()) {
368    if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
369      LOG(WARNING) << "Could not uninstall extension " << id
370                   << " for sync";
371    }
372    return true;
373  }
374
375  // Extension from sync was uninstalled by the user as external extensions.
376  // Honor user choice and skip installation/enabling.
377  if (extension_service_->IsExternalExtensionUninstalled(id)) {
378    LOG(WARNING) << "Extension with id " << id
379                 << " from sync was uninstalled as external extension";
380    return true;
381  }
382
383  // Set user settings.
384  // If the extension has been disabled from sync, it may not have
385  // been installed yet, so we don't know if the disable reason was a
386  // permissions increase.  That will be updated once CheckPermissionsIncrease
387  // is called for it.
388  if (extension_sync_data.enabled())
389    extension_service_->EnableExtension(id);
390  else if (!IsPendingEnable(id))
391    extension_service_->DisableExtension(
392        id, Extension::DISABLE_UNKNOWN_FROM_SYNC);
393
394  // We need to cache some version information here because setting the
395  // incognito flag invalidates the |extension| pointer (it reloads the
396  // extension).
397  bool extension_installed = (extension != NULL);
398  int result = extension ?
399      extension->version()->CompareTo(extension_sync_data.version()) : 0;
400  extension_util::SetIsIncognitoEnabled(
401      id, extension_service_, extension_sync_data.incognito_enabled());
402  extension = NULL;  // No longer safe to use.
403
404  if (extension_installed) {
405    // If the extension is already installed, check if it's outdated.
406    if (result < 0) {
407      // Extension is outdated.
408      return false;
409    }
410  } else {
411    // TODO(akalin): Replace silent update with a list of enabled
412    // permissions.
413    const bool kInstallSilently = true;
414
415    CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
416    extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
417        (type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
418                                 extensions::sync_helper::IsSyncableExtension;
419
420    if (!extension_service_->pending_extension_manager()->AddFromSync(
421            id,
422            extension_sync_data.update_url(),
423            filter,
424            kInstallSilently)) {
425      LOG(WARNING) << "Could not add pending extension for " << id;
426      // This means that the extension is already pending installation, with a
427      // non-INTERNAL location.  Add to pending_sync_data, even though it will
428      // never be removed (we'll never install a syncable version of the
429      // extension), so that GetAllSyncData() continues to send it.
430    }
431    // Track pending extensions so that we can return them in GetAllSyncData().
432    return false;
433  }
434
435  return true;
436}
437
438void ExtensionSyncService::SyncExtensionChangeIfNeeded(
439    const Extension& extension) {
440  if (extensions::sync_helper::IsSyncableApp(&extension)) {
441    if (app_sync_bundle_.IsSyncing())
442      app_sync_bundle_.SyncChangeIfNeeded(extension);
443    else if (extension_service_->is_ready() && !flare_.is_null())
444      flare_.Run(syncer::APPS);
445  } else if (extensions::sync_helper::IsSyncableExtension(&extension)) {
446    if (extension_sync_bundle_.IsSyncing())
447      extension_sync_bundle_.SyncChangeIfNeeded(extension);
448    else if (extension_service_->is_ready() && !flare_.is_null())
449      flare_.Run(syncer::EXTENSIONS);
450  }
451}
452