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
17 using content::BrowserThread;
18
19 namespace browser_sync {
20
21 namespace {
22
23 // Helper method to generate a hash from an extension id.
IdToHash(const std::string extension_id)24 std::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
ExtensionBackedDataTypeController(syncer::ModelType type,const std::string & extension_hash,sync_driver::SyncApiComponentFactory * sync_factory,Profile * profile)32 ExtensionBackedDataTypeController::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
~ExtensionBackedDataTypeController()49 ExtensionBackedDataTypeController::~ExtensionBackedDataTypeController() {
50 extensions::ExtensionRegistry* registry =
51 extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_);
52 registry->RemoveObserver(this);
53 }
54
ReadyForStart() const55 bool 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
OnExtensionLoaded(content::BrowserContext * browser_context,const extensions::Extension * extension)61 void 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
OnExtensionUnloaded(content::BrowserContext * browser_context,const extensions::Extension * extension,extensions::UnloadedExtensionInfo::Reason reason)71 void 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
IsSyncingExtensionEnabled() const89 bool 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
DoesExtensionMatch(const extensions::Extension & extension) const109 bool ExtensionBackedDataTypeController::DoesExtensionMatch(
110 const extensions::Extension& extension) const {
111 return IdToHash(extension.id()) == extension_hash_;
112 }
113
StartModels()114 bool 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