Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sync / glue / session_change_processor.cc
blob00cba9ee8ed5525ccf402a104c2ecac2064510de
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/sync/glue/session_change_processor.h"
7 #include <string>
8 #include <vector>
10 #include "base/logging.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/tab_helper.h"
13 #include "chrome/browser/favicon/favicon_changed_details.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/sync/glue/session_model_associator.h"
16 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
17 #include "chrome/browser/sync/profile_sync_service.h"
18 #include "content/public/browser/navigation_controller.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/notification_details.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_source.h"
23 #include "content/public/browser/web_contents.h"
24 #include "sync/api/sync_error.h"
25 #include "sync/internal_api/public/base/model_type.h"
26 #include "sync/internal_api/public/change_record.h"
27 #include "sync/internal_api/public/read_node.h"
28 #include "sync/protocol/session_specifics.pb.h"
30 #if defined(ENABLE_MANAGED_USERS)
31 #include "chrome/browser/managed_mode/managed_user_service.h"
32 #include "chrome/browser/managed_mode/managed_user_service_factory.h"
33 #endif
35 using content::BrowserThread;
36 using content::NavigationController;
37 using content::WebContents;
39 namespace browser_sync {
41 namespace {
43 // The URL at which the set of synced tabs is displayed. We treat it differently
44 // from all other URL's as accessing it triggers a sync refresh of Sessions.
45 static const char kNTPOpenTabSyncURL[] = "chrome://newtab/#open_tabs";
47 // Extract the source SyncedTabDelegate from a NotificationSource originating
48 // from a NavigationController, if it exists. Returns |NULL| otherwise.
49 SyncedTabDelegate* ExtractSyncedTabDelegate(
50 const content::NotificationSource& source) {
51 return SyncedTabDelegate::ImplFromWebContents(
52 content::Source<NavigationController>(source).ptr()->GetWebContents());
55 } // namespace
57 SessionChangeProcessor::SessionChangeProcessor(
58 DataTypeErrorHandler* error_handler,
59 SessionModelAssociator* session_model_associator)
60 : ChangeProcessor(error_handler),
61 session_model_associator_(session_model_associator),
62 profile_(NULL),
63 setup_for_test_(false),
64 weak_ptr_factory_(this) {
65 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
66 DCHECK(error_handler);
67 DCHECK(session_model_associator_);
70 SessionChangeProcessor::SessionChangeProcessor(
71 DataTypeErrorHandler* error_handler,
72 SessionModelAssociator* session_model_associator,
73 bool setup_for_test)
74 : ChangeProcessor(error_handler),
75 session_model_associator_(session_model_associator),
76 profile_(NULL),
77 setup_for_test_(setup_for_test),
78 weak_ptr_factory_(this) {
79 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
80 DCHECK(error_handler);
81 DCHECK(session_model_associator_);
84 SessionChangeProcessor::~SessionChangeProcessor() {
85 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
88 void SessionChangeProcessor::Observe(
89 int type,
90 const content::NotificationSource& source,
91 const content::NotificationDetails& details) {
92 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
93 DCHECK(profile_);
95 // Track which windows and/or tabs are modified.
96 std::vector<SyncedTabDelegate*> modified_tabs;
97 switch (type) {
98 case chrome::NOTIFICATION_FAVICON_CHANGED: {
99 content::Details<FaviconChangedDetails> favicon_details(details);
100 session_model_associator_->FaviconsUpdated(favicon_details->urls);
101 // Note: we favicon notifications don't affect tab contents, so we return
102 // here instead of continuing on to reassociate tabs/windows.
103 return;
106 case chrome::NOTIFICATION_BROWSER_OPENED: {
107 Browser* browser = content::Source<Browser>(source).ptr();
108 if (!browser || browser->profile() != profile_) {
109 return;
111 DVLOG(1) << "Received BROWSER_OPENED for profile " << profile_;
112 break;
115 case chrome::NOTIFICATION_TAB_PARENTED: {
116 WebContents* web_contents = content::Source<WebContents>(source).ptr();
117 SyncedTabDelegate* tab =
118 SyncedTabDelegate::ImplFromWebContents(web_contents);
119 if (!tab || tab->profile() != profile_) {
120 return;
122 modified_tabs.push_back(tab);
123 DVLOG(1) << "Received TAB_PARENTED for profile " << profile_;
124 break;
127 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
128 WebContents* web_contents = content::Source<WebContents>(source).ptr();
129 SyncedTabDelegate* tab =
130 SyncedTabDelegate::ImplFromWebContents(web_contents);
131 if (!tab || tab->profile() != profile_) {
132 return;
134 modified_tabs.push_back(tab);
135 DVLOG(1) << "Received LOAD_COMPLETED_MAIN_FRAME for profile " << profile_;
136 break;
139 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
140 WebContents* web_contents = content::Source<WebContents>(source).ptr();
141 SyncedTabDelegate* tab =
142 SyncedTabDelegate::ImplFromWebContents(web_contents);
143 if (!tab || tab->profile() != profile_)
144 return;
145 modified_tabs.push_back(tab);
146 DVLOG(1) << "Received NOTIFICATION_WEB_CONTENTS_DESTROYED for profile "
147 << profile_;
148 break;
151 case content::NOTIFICATION_NAV_LIST_PRUNED: {
152 SyncedTabDelegate* tab = ExtractSyncedTabDelegate(source);
153 if (!tab || tab->profile() != profile_) {
154 return;
156 modified_tabs.push_back(tab);
157 DVLOG(1) << "Received NAV_LIST_PRUNED for profile " << profile_;
158 break;
161 case content::NOTIFICATION_NAV_ENTRY_CHANGED: {
162 SyncedTabDelegate* tab = ExtractSyncedTabDelegate(source);
163 if (!tab || tab->profile() != profile_) {
164 return;
166 modified_tabs.push_back(tab);
167 DVLOG(1) << "Received NAV_ENTRY_CHANGED for profile " << profile_;
168 break;
171 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
172 SyncedTabDelegate* tab = ExtractSyncedTabDelegate(source);
173 if (!tab || tab->profile() != profile_) {
174 return;
176 modified_tabs.push_back(tab);
177 DVLOG(1) << "Received NAV_ENTRY_COMMITTED for profile " << profile_;
178 break;
181 case chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: {
182 extensions::TabHelper* extension_tab_helper =
183 content::Source<extensions::TabHelper>(source).ptr();
184 if (extension_tab_helper->web_contents()->GetBrowserContext() !=
185 profile_) {
186 return;
188 if (extension_tab_helper->extension_app()) {
189 SyncedTabDelegate* tab = SyncedTabDelegate::ImplFromWebContents(
190 extension_tab_helper->web_contents());
191 modified_tabs.push_back(tab);
193 DVLOG(1) << "Received TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED "
194 << "for profile " << profile_;
195 break;
198 default:
199 LOG(ERROR) << "Received unexpected notification of type "
200 << type;
201 break;
204 ProcessModifiedTabs(modified_tabs);
207 void SessionChangeProcessor::ApplyChangesFromSyncModel(
208 const syncer::BaseTransaction* trans,
209 int64 model_version,
210 const syncer::ImmutableChangeRecordList& changes) {
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
213 syncer::ReadNode root(trans);
214 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) !=
215 syncer::BaseNode::INIT_OK) {
216 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
217 "Sessions root node lookup failed.");
218 return;
221 std::string local_tag = session_model_associator_->GetCurrentMachineTag();
222 for (syncer::ChangeRecordList::const_iterator it =
223 changes.Get().begin(); it != changes.Get().end(); ++it) {
224 const syncer::ChangeRecord& change = *it;
225 syncer::ChangeRecord::Action action(change.action);
226 if (syncer::ChangeRecord::ACTION_DELETE == action) {
227 // Deletions are all or nothing (since we only ever delete entire
228 // sessions). Therefore we don't care if it's a tab node or meta node,
229 // and just ensure we've disassociated.
230 DCHECK_EQ(syncer::GetModelTypeFromSpecifics(it->specifics),
231 syncer::SESSIONS);
232 const sync_pb::SessionSpecifics& specifics = it->specifics.session();
233 if (specifics.session_tag() == local_tag) {
234 // Another client has attempted to delete our local data (possibly by
235 // error or their/our clock is inaccurate). Just ignore the deletion
236 // for now to avoid any possible ping-pong delete/reassociate sequence.
237 LOG(WARNING) << "Local session data deleted. Ignoring until next local "
238 << "navigation event.";
239 } else {
240 if (specifics.has_header()) {
241 // Disassociate only when header node is deleted. For tab node
242 // deletions, the header node will be updated and foreign tab will
243 // get deleted.
244 session_model_associator_->DisassociateForeignSession(
245 specifics.session_tag());
248 continue;
251 // Handle an update or add.
252 syncer::ReadNode sync_node(trans);
253 if (sync_node.InitByIdLookup(change.id) != syncer::BaseNode::INIT_OK) {
254 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE,
255 "Session node lookup failed.");
256 return;
259 // Check that the changed node is a child of the session folder.
260 DCHECK(root.GetId() == sync_node.GetParentId());
261 DCHECK(syncer::SESSIONS == sync_node.GetModelType());
263 const sync_pb::SessionSpecifics& specifics(
264 sync_node.GetSessionSpecifics());
265 if (specifics.session_tag() == local_tag &&
266 !setup_for_test_) {
267 // We should only ever receive a change to our own machine's session info
268 // if encryption was turned on. In that case, the data is still the same,
269 // so we can ignore.
270 LOG(WARNING) << "Dropping modification to local session.";
271 return;
273 const base::Time& mtime = sync_node.GetModificationTime();
274 // The model associator handles foreign session updates and adds the same.
275 session_model_associator_->AssociateForeignSpecifics(specifics, mtime);
278 // Notify foreign session handlers that there are new sessions.
279 content::NotificationService::current()->Notify(
280 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
281 content::Source<Profile>(profile_),
282 content::NotificationService::NoDetails());
285 void SessionChangeProcessor::StartImpl(Profile* profile) {
286 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
287 DCHECK(profile);
288 DCHECK(profile_ == NULL);
289 profile_ = profile;
290 StartObserving();
293 void SessionChangeProcessor::OnNavigationBlocked(WebContents* web_contents) {
294 SyncedTabDelegate* tab =
295 SyncedTabDelegate::ImplFromWebContents(web_contents);
296 if (!tab)
297 return;
299 DCHECK(tab->profile() == profile_);
301 std::vector<SyncedTabDelegate*> modified_tabs;
302 modified_tabs.push_back(tab);
303 ProcessModifiedTabs(modified_tabs);
306 void SessionChangeProcessor::ProcessModifiedTabs(
307 const std::vector<SyncedTabDelegate*>& modified_tabs) {
308 // Check if this tab should trigger a session sync refresh. By virtue of
309 // it being a modified tab, we know the tab is active (so we won't do
310 // refreshes just because the refresh page is open in a background tab).
311 if (!modified_tabs.empty()) {
312 SyncedTabDelegate* tab = modified_tabs.front();
313 const content::NavigationEntry* entry = tab->GetActiveEntry();
314 if (!tab->IsBeingDestroyed() &&
315 entry &&
316 entry->GetVirtualURL().is_valid() &&
317 entry->GetVirtualURL().spec() == kNTPOpenTabSyncURL) {
318 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
319 const syncer::ModelTypeSet types(syncer::SESSIONS);
320 content::NotificationService::current()->Notify(
321 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
322 content::Source<Profile>(profile_),
323 content::Details<const syncer::ModelTypeSet>(&types));
327 // Associate tabs first so the synced session tracker is aware of them.
328 // Note that if we fail to associate, it means something has gone wrong,
329 // such as our local session being deleted, so we disassociate and associate
330 // again.
331 bool reassociation_needed = !modified_tabs.empty() &&
332 !session_model_associator_->AssociateTabs(modified_tabs, NULL);
334 // Note, we always associate windows because it's possible a tab became
335 // "interesting" by going to a valid URL, in which case it needs to be added
336 // to the window's tab information.
337 if (!reassociation_needed) {
338 reassociation_needed =
339 !session_model_associator_->AssociateWindows(false, NULL);
342 if (reassociation_needed) {
343 LOG(WARNING) << "Reassociation of local models triggered.";
344 syncer::SyncError error;
345 error = session_model_associator_->DisassociateModels();
346 error = session_model_associator_->AssociateModels(NULL, NULL);
347 if (error.IsSet()) {
348 error_handler()->OnSingleDatatypeUnrecoverableError(
349 error.location(),
350 error.message());
355 void SessionChangeProcessor::StartObserving() {
356 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
357 if (!profile_)
358 return;
359 notification_registrar_.Add(this, chrome::NOTIFICATION_TAB_PARENTED,
360 content::NotificationService::AllSources());
361 notification_registrar_.Add(this,
362 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
363 content::NotificationService::AllSources());
364 notification_registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED,
365 content::NotificationService::AllSources());
366 notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED,
367 content::NotificationService::AllSources());
368 notification_registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
369 content::NotificationService::AllSources());
370 notification_registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
371 content::NotificationService::AllBrowserContextsAndSources());
372 notification_registrar_.Add(this,
373 chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED,
374 content::NotificationService::AllSources());
375 notification_registrar_.Add(this,
376 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
377 content::NotificationService::AllBrowserContextsAndSources());
378 notification_registrar_.Add(this, chrome::NOTIFICATION_FAVICON_CHANGED,
379 content::Source<Profile>(profile_));
380 #if defined(ENABLE_MANAGED_USERS)
381 if (profile_->IsManaged()) {
382 ManagedUserService* managed_user_service =
383 ManagedUserServiceFactory::GetForProfile(profile_);
384 managed_user_service->AddNavigationBlockedCallback(
385 base::Bind(&SessionChangeProcessor::OnNavigationBlocked,
386 weak_ptr_factory_.GetWeakPtr()));
388 #endif
391 } // namespace browser_sync