1 // Copyright (c) 2011 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_change_processor.h"
6
7 #include <sstream>
8 #include <string>
9
10 #include "base/logging.h"
11 #include "base/stl_util-inl.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_sync_data.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/sync/glue/extension_sync.h"
16 #include "chrome/browser/sync/glue/extension_util.h"
17 #include "chrome/browser/sync/profile_sync_service.h"
18 #include "chrome/browser/sync/protocol/extension_specifics.pb.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "content/browser/browser_thread.h"
21 #include "content/common/notification_details.h"
22 #include "content/common/notification_source.h"
23
24 namespace browser_sync {
25
ExtensionChangeProcessor(const ExtensionSyncTraits & traits,UnrecoverableErrorHandler * error_handler)26 ExtensionChangeProcessor::ExtensionChangeProcessor(
27 const ExtensionSyncTraits& traits,
28 UnrecoverableErrorHandler* error_handler)
29 : ChangeProcessor(error_handler),
30 traits_(traits),
31 profile_(NULL),
32 extension_service_(NULL),
33 user_share_(NULL) {
34 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
35 DCHECK(error_handler);
36 }
37
~ExtensionChangeProcessor()38 ExtensionChangeProcessor::~ExtensionChangeProcessor() {
39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40 }
41
42 // TODO(akalin): We need to make sure events we receive from either
43 // the browser or the syncapi are done in order; this is tricky since
44 // some events (e.g., extension installation) are done asynchronously.
45
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)46 void ExtensionChangeProcessor::Observe(NotificationType type,
47 const NotificationSource& source,
48 const NotificationDetails& details) {
49 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
50 DCHECK(running());
51 DCHECK(profile_);
52 if ((type != NotificationType::EXTENSION_INSTALLED) &&
53 (type != NotificationType::EXTENSION_UNINSTALLED) &&
54 (type != NotificationType::EXTENSION_LOADED) &&
55 (type != NotificationType::EXTENSION_UPDATE_DISABLED) &&
56 (type != NotificationType::EXTENSION_UNLOADED)) {
57 LOG(DFATAL) << "Received unexpected notification of type "
58 << type.value;
59 return;
60 }
61
62 DCHECK_EQ(Source<Profile>(source).ptr(), profile_);
63 if (type == NotificationType::EXTENSION_UNINSTALLED) {
64 const UninstalledExtensionInfo* uninstalled_extension_info =
65 Details<UninstalledExtensionInfo>(details).ptr();
66 CHECK(uninstalled_extension_info);
67 if (traits_.should_handle_extension_uninstall(
68 *uninstalled_extension_info)) {
69 const std::string& id = uninstalled_extension_info->extension_id;
70 VLOG(1) << "Removing server data for uninstalled extension " << id
71 << " of type " << uninstalled_extension_info->extension_type;
72 RemoveServerData(traits_, id, user_share_);
73 }
74 } else {
75 const Extension* extension = NULL;
76 if (type == NotificationType::EXTENSION_UNLOADED) {
77 extension = Details<UnloadedExtensionInfo>(details)->extension;
78 } else {
79 extension = Details<const Extension>(details).ptr();
80 }
81 CHECK(extension);
82 VLOG(1) << "Updating server data for extension " << extension->id()
83 << " (notification type = " << type.value << ")";
84 if (!traits_.is_valid_and_syncable(*extension)) {
85 return;
86 }
87 std::string error;
88 if (!UpdateServerData(traits_, *extension, *extension_service_,
89 user_share_, &error)) {
90 error_handler()->OnUnrecoverableError(FROM_HERE, error);
91 }
92 }
93 }
94
ApplyChangesFromSyncModel(const sync_api::BaseTransaction * trans,const sync_api::SyncManager::ChangeRecord * changes,int change_count)95 void ExtensionChangeProcessor::ApplyChangesFromSyncModel(
96 const sync_api::BaseTransaction* trans,
97 const sync_api::SyncManager::ChangeRecord* changes,
98 int change_count) {
99 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
100 if (!running()) {
101 return;
102 }
103 for (int i = 0; i < change_count; ++i) {
104 const sync_api::SyncManager::ChangeRecord& change = changes[i];
105 sync_pb::ExtensionSpecifics specifics;
106 switch (change.action) {
107 case sync_api::SyncManager::ChangeRecord::ACTION_ADD:
108 case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
109 sync_api::ReadNode node(trans);
110 if (!node.InitByIdLookup(change.id)) {
111 std::stringstream error;
112 error << "Extension node lookup failed for change " << change.id
113 << " of action type " << change.action;
114 error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
115 return;
116 }
117 DCHECK_EQ(node.GetModelType(), traits_.model_type);
118 specifics = (*traits_.extension_specifics_getter)(node);
119 break;
120 }
121 case sync_api::SyncManager::ChangeRecord::ACTION_DELETE: {
122 if (!(*traits_.extension_specifics_entity_getter)(
123 change.specifics, &specifics)) {
124 std::stringstream error;
125 error << "Could not get extension specifics from deleted node "
126 << change.id;
127 error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
128 LOG(DFATAL) << error.str();
129 }
130 break;
131 }
132 }
133 ExtensionSyncData sync_data;
134 if (!GetExtensionSyncData(specifics, &sync_data)) {
135 // TODO(akalin): Should probably recover or drop.
136 std::string error =
137 std::string("Invalid server specifics: ") +
138 ExtensionSpecificsToString(specifics);
139 error_handler()->OnUnrecoverableError(FROM_HERE, error);
140 return;
141 }
142 sync_data.uninstalled =
143 (change.action == sync_api::SyncManager::ChangeRecord::ACTION_DELETE);
144 StopObserving();
145 extension_service_->ProcessSyncData(sync_data,
146 traits_.is_valid_and_syncable);
147 StartObserving();
148 }
149 }
150
StartImpl(Profile * profile)151 void ExtensionChangeProcessor::StartImpl(Profile* profile) {
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
153 profile_ = profile;
154 extension_service_ = profile_->GetExtensionService();
155 user_share_ = profile_->GetProfileSyncService()->GetUserShare();
156 DCHECK(profile_);
157 DCHECK(extension_service_);
158 DCHECK(user_share_);
159 StartObserving();
160 }
161
StopImpl()162 void ExtensionChangeProcessor::StopImpl() {
163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164 StopObserving();
165 profile_ = NULL;
166 extension_service_ = NULL;
167 user_share_ = NULL;
168 }
169
StartObserving()170 void ExtensionChangeProcessor::StartObserving() {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
172 DCHECK(profile_);
173 notification_registrar_.Add(
174 this, NotificationType::EXTENSION_INSTALLED,
175 Source<Profile>(profile_));
176 notification_registrar_.Add(
177 this, NotificationType::EXTENSION_UNINSTALLED,
178 Source<Profile>(profile_));
179
180 notification_registrar_.Add(
181 this, NotificationType::EXTENSION_LOADED,
182 Source<Profile>(profile_));
183 // Despite the name, this notification is exactly like
184 // EXTENSION_LOADED but with an initial state of DISABLED.
185 notification_registrar_.Add(
186 this, NotificationType::EXTENSION_UPDATE_DISABLED,
187 Source<Profile>(profile_));
188
189 notification_registrar_.Add(
190 this, NotificationType::EXTENSION_UNLOADED,
191 Source<Profile>(profile_));
192 }
193
StopObserving()194 void ExtensionChangeProcessor::StopObserving() {
195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
196 DCHECK(profile_);
197 VLOG(1) << "Unobserving all notifications";
198 notification_registrar_.RemoveAll();
199 }
200
201 } // namespace browser_sync
202