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