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/session_change_processor.h" 6 7 #include <sstream> 8 #include <string> 9 #include <vector> 10 11 #include "base/logging.h" 12 #include "base/memory/scoped_vector.h" 13 #include "chrome/browser/extensions/extension_tab_helper.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/sync/engine/syncapi.h" 16 #include "chrome/browser/sync/glue/session_model_associator.h" 17 #include "chrome/browser/sync/profile_sync_service.h" 18 #include "content/browser/tab_contents/navigation_controller.h" 19 #include "content/browser/tab_contents/tab_contents.h" 20 #include "content/common/notification_details.h" 21 #include "content/common/notification_service.h" 22 #include "content/common/notification_source.h" 23 24 namespace browser_sync { 25 26 SessionChangeProcessor::SessionChangeProcessor( 27 UnrecoverableErrorHandler* error_handler, 28 SessionModelAssociator* session_model_associator) 29 : ChangeProcessor(error_handler), 30 session_model_associator_(session_model_associator), 31 profile_(NULL), 32 setup_for_test_(false) { 33 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 34 DCHECK(error_handler); 35 DCHECK(session_model_associator_); 36 } 37 38 SessionChangeProcessor::SessionChangeProcessor( 39 UnrecoverableErrorHandler* error_handler, 40 SessionModelAssociator* session_model_associator, 41 bool setup_for_test) 42 : ChangeProcessor(error_handler), 43 session_model_associator_(session_model_associator), 44 profile_(NULL), 45 setup_for_test_(setup_for_test) { 46 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 47 DCHECK(error_handler); 48 DCHECK(session_model_associator_); 49 } 50 51 SessionChangeProcessor::~SessionChangeProcessor() { 52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 53 } 54 55 void SessionChangeProcessor::Observe(NotificationType type, 56 const NotificationSource& source, 57 const NotificationDetails& details) { 58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 59 DCHECK(running()); 60 DCHECK(profile_); 61 62 // Track which windows and/or tabs are modified. 63 std::vector<TabContents*> modified_tabs; 64 bool windows_changed = false; 65 switch (type.value) { 66 case NotificationType::BROWSER_OPENED: { 67 Browser* browser = Source<Browser>(source).ptr(); 68 if (browser->profile() != profile_) { 69 return; 70 } 71 72 windows_changed = true; 73 break; 74 } 75 76 case NotificationType::TAB_PARENTED: { 77 NavigationController* controller = 78 Source<NavigationController>(source).ptr(); 79 if (controller->profile() != profile_) { 80 return; 81 } 82 windows_changed = true; 83 modified_tabs.push_back(controller->tab_contents()); 84 break; 85 } 86 87 case NotificationType::TAB_CLOSED: { 88 NavigationController* controller = 89 Source<NavigationController>(source).ptr(); 90 if (controller->profile() != profile_) { 91 return; 92 } 93 windows_changed = true; 94 modified_tabs.push_back(controller->tab_contents()); 95 break; 96 } 97 98 case NotificationType::NAV_LIST_PRUNED: { 99 NavigationController* controller = 100 Source<NavigationController>(source).ptr(); 101 if (controller->profile() != profile_) { 102 return; 103 } 104 modified_tabs.push_back(controller->tab_contents()); 105 break; 106 } 107 108 case NotificationType::NAV_ENTRY_CHANGED: { 109 NavigationController* controller = 110 Source<NavigationController>(source).ptr(); 111 if (controller->profile() != profile_) { 112 return; 113 } 114 modified_tabs.push_back(controller->tab_contents()); 115 break; 116 } 117 118 case NotificationType::NAV_ENTRY_COMMITTED: { 119 NavigationController* controller = 120 Source<NavigationController>(source).ptr(); 121 if (controller->profile() != profile_) { 122 return; 123 } 124 modified_tabs.push_back(controller->tab_contents()); 125 break; 126 } 127 128 case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { 129 ExtensionTabHelper* extension_tab_helper = 130 Source<ExtensionTabHelper>(source).ptr(); 131 if (extension_tab_helper->tab_contents()->profile() != profile_) { 132 return; 133 } 134 if (extension_tab_helper->extension_app()) { 135 modified_tabs.push_back(extension_tab_helper->tab_contents()); 136 } 137 break; 138 } 139 default: 140 LOG(ERROR) << "Received unexpected notification of type " 141 << type.value; 142 break; 143 } 144 145 // Associate windows first to ensure tabs have homes. 146 if (windows_changed) 147 session_model_associator_->ReassociateWindows(false); 148 if (!modified_tabs.empty()) 149 session_model_associator_->ReassociateTabs(modified_tabs); 150 } 151 152 void SessionChangeProcessor::ApplyChangesFromSyncModel( 153 const sync_api::BaseTransaction* trans, 154 const sync_api::SyncManager::ChangeRecord* changes, 155 int change_count) { 156 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 157 if (!running()) { 158 return; 159 } 160 161 StopObserving(); 162 163 sync_api::ReadNode root(trans); 164 if (!root.InitByTagLookup(kSessionsTag)) { 165 error_handler()->OnUnrecoverableError(FROM_HERE, 166 "Sessions root node lookup failed."); 167 return; 168 } 169 170 for (int i = 0; i < change_count; ++i) { 171 const sync_api::SyncManager::ChangeRecord& change = changes[i]; 172 sync_api::SyncManager::ChangeRecord::Action action(change.action); 173 if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { 174 // Deletions should only be for a foreign client itself, and hence affect 175 // the header node, never a tab node. 176 sync_api::ReadNode node(trans); 177 if (!node.InitByIdLookup(change.id)) { 178 error_handler()->OnUnrecoverableError(FROM_HERE, 179 "Session node lookup failed."); 180 return; 181 } 182 DCHECK_EQ(node.GetModelType(), syncable::SESSIONS); 183 const sync_pb::SessionSpecifics& specifics = node.GetSessionSpecifics(); 184 session_model_associator_->DisassociateForeignSession( 185 specifics.session_tag()); 186 continue; 187 } 188 189 // Handle an update or add. 190 sync_api::ReadNode sync_node(trans); 191 if (!sync_node.InitByIdLookup(change.id)) { 192 error_handler()->OnUnrecoverableError(FROM_HERE, 193 "Session node lookup failed."); 194 return; 195 } 196 197 // Check that the changed node is a child of the session folder. 198 DCHECK(root.GetId() == sync_node.GetParentId()); 199 DCHECK(syncable::SESSIONS == sync_node.GetModelType()); 200 201 const sync_pb::SessionSpecifics& specifics( 202 sync_node.GetSessionSpecifics()); 203 if (specifics.session_tag() == 204 session_model_associator_->GetCurrentMachineTag() && 205 !setup_for_test_) { 206 // We should only ever receive a change to our own machine's session info 207 // if encryption was turned on. In that case, the data is still the same, 208 // so we can ignore. 209 LOG(WARNING) << "Dropping modification to local session."; 210 return; 211 } 212 const int64 mtime = sync_node.GetModificationTime(); 213 // Model associator handles foreign session update and add the same. 214 session_model_associator_->AssociateForeignSpecifics(specifics, mtime); 215 } 216 217 // Notify foreign session handlers that there are new sessions. 218 NotificationService::current()->Notify( 219 NotificationType::FOREIGN_SESSION_UPDATED, 220 NotificationService::AllSources(), 221 NotificationService::NoDetails()); 222 223 StartObserving(); 224 } 225 226 void SessionChangeProcessor::StartImpl(Profile* profile) { 227 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 228 DCHECK(profile); 229 DCHECK(profile_ == NULL); 230 profile_ = profile; 231 StartObserving(); 232 } 233 234 void SessionChangeProcessor::StopImpl() { 235 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 236 StopObserving(); 237 profile_ = NULL; 238 } 239 240 void SessionChangeProcessor::StartObserving() { 241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 242 DCHECK(profile_); 243 notification_registrar_.Add(this, NotificationType::TAB_PARENTED, 244 NotificationService::AllSources()); 245 notification_registrar_.Add(this, NotificationType::TAB_CLOSED, 246 NotificationService::AllSources()); 247 notification_registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, 248 NotificationService::AllSources()); 249 notification_registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED, 250 NotificationService::AllSources()); 251 notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 252 NotificationService::AllSources()); 253 notification_registrar_.Add(this, NotificationType::BROWSER_OPENED, 254 NotificationService::AllSources()); 255 notification_registrar_.Add(this, 256 NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, 257 NotificationService::AllSources()); 258 } 259 260 void SessionChangeProcessor::StopObserving() { 261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 262 DCHECK(profile_); 263 notification_registrar_.RemoveAll(); 264 } 265 266 } // namespace browser_sync 267