1// Copyright 2014 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/sync/glue/extension_backed_data_type_controller.h" 6 7#include "base/sha1.h" 8#include "base/strings/string_number_conversions.h" 9#include "chrome/browser/profiles/profile.h" 10#include "chrome/browser/sync/glue/chrome_report_unrecoverable_error.h" 11#include "chrome/browser/sync/profile_sync_service.h" 12#include "chrome/browser/sync/profile_sync_service_factory.h" 13#include "content/public/browser/browser_thread.h" 14#include "extensions/browser/extension_registry.h" 15#include "extensions/browser/extension_registry_factory.h" 16 17using content::BrowserThread; 18 19namespace browser_sync { 20 21namespace { 22 23// Helper method to generate a hash from an extension id. 24std::string IdToHash(const std::string extension_id) { 25 std::string hash = base::SHA1HashString(extension_id); 26 hash = base::HexEncode(hash.c_str(), hash.length()); 27 return hash; 28} 29 30} // namespace 31 32ExtensionBackedDataTypeController::ExtensionBackedDataTypeController( 33 syncer::ModelType type, 34 const std::string& extension_hash, 35 sync_driver::SyncApiComponentFactory* sync_factory, 36 Profile* profile) 37 : UIDataTypeController( 38 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI), 39 base::Bind(&ChromeReportUnrecoverableError), 40 type, 41 sync_factory), 42 extension_hash_(extension_hash), 43 profile_(profile) { 44 extensions::ExtensionRegistry* registry = 45 extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_); 46 registry->AddObserver(this); 47} 48 49ExtensionBackedDataTypeController::~ExtensionBackedDataTypeController() { 50 extensions::ExtensionRegistry* registry = 51 extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_); 52 registry->RemoveObserver(this); 53} 54 55bool ExtensionBackedDataTypeController::ReadyForStart() const { 56 // TODO(zea): consider checking if the extension was uninstalled without 57 // sync noticing, in which case the datatype should be purged. 58 return IsSyncingExtensionEnabled(); 59} 60 61void ExtensionBackedDataTypeController::OnExtensionLoaded( 62 content::BrowserContext* browser_context, 63 const extensions::Extension* extension) { 64 if (DoesExtensionMatch(*extension)) { 65 ProfileSyncService* sync_service = 66 ProfileSyncServiceFactory::GetForProfile(profile_); 67 sync_service->ReenableDatatype(type()); 68 } 69} 70 71void ExtensionBackedDataTypeController::OnExtensionUnloaded( 72 content::BrowserContext* browser_context, 73 const extensions::Extension* extension, 74 extensions::UnloadedExtensionInfo::Reason reason) { 75 if (DoesExtensionMatch(*extension)) { 76 // This will trigger purging the datatype from the sync directory. This 77 // may not always be the right choice, especially in the face of transient 78 // unloads (e.g. for permission changes). If that becomes a large enough 79 // issue, we should consider using the extension unload reason to just 80 // trigger a reconfiguration without disabling (type will be unready). 81 syncer::SyncError error(FROM_HERE, 82 syncer::SyncError::DATATYPE_POLICY_ERROR, 83 "Extension unloaded", 84 type()); 85 OnSingleDataTypeUnrecoverableError(error); 86 } 87} 88 89bool ExtensionBackedDataTypeController::IsSyncingExtensionEnabled() const { 90 if (extension_hash_.empty()) 91 return true; // For use while extension is in development. 92 93 // TODO(synced notifications): rather than rely on a hash of the extension 94 // id, look through the manifests to see if the extension has permissions 95 // for this model type. 96 extensions::ExtensionRegistry* registry = 97 extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_); 98 const extensions::ExtensionSet& enabled_extensions( 99 registry->enabled_extensions()); 100 for (extensions::ExtensionSet::const_iterator iter = 101 enabled_extensions.begin(); 102 iter != enabled_extensions.end(); ++iter) { 103 if (DoesExtensionMatch(*iter->get())) 104 return true; 105 } 106 return false; 107} 108 109bool ExtensionBackedDataTypeController::DoesExtensionMatch( 110 const extensions::Extension& extension) const { 111 return IdToHash(extension.id()) == extension_hash_; 112} 113 114bool ExtensionBackedDataTypeController::StartModels() { 115 if (IsSyncingExtensionEnabled()) 116 return true; 117 118 // If the extension is not currently enabled, it means that it has been 119 // disabled since the call to ReadyForStart(), and the notification 120 // observer should have already posted a call to disable the type. 121 return false; 122} 123 124} // namespace browser_sync 125