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