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
41using extensions::Extension;
42using extensions::ExtensionPrefs;
43using extensions::ExtensionRegistry;
44using extensions::FeatureSwitch;
45
46namespace {
47
48void 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
64ExtensionSyncService::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
87ExtensionSyncService::~ExtensionSyncService() {}
88
89// static
90ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
91  return ExtensionSyncServiceFactory::GetForProfile(profile);
92}
93
94syncer::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
117void 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
129void 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
142void 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
155syncer::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
213void 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
221syncer::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
234syncer::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
254extensions::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
264extensions::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
277std::vector<extensions::ExtensionSyncData>
278  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
297std::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
317bool 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
330bool 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
365void 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
405void 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
413void ExtensionSyncService::SetSyncStartFlare(
414    const syncer::SyncableService::StartSyncFlare& flare) {
415  flare_ = flare;
416}
417
418bool 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
433bool 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
439bool 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
545void 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