// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/sync/glue/session_change_processor.h" #include <sstream> #include <string> #include <vector> #include "base/logging.h" #include "base/memory/scoped_vector.h" #include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_details.h" #include "content/common/notification_service.h" #include "content/common/notification_source.h" namespace browser_sync { SessionChangeProcessor::SessionChangeProcessor( UnrecoverableErrorHandler* error_handler, SessionModelAssociator* session_model_associator) : ChangeProcessor(error_handler), session_model_associator_(session_model_associator), profile_(NULL), setup_for_test_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(error_handler); DCHECK(session_model_associator_); } SessionChangeProcessor::SessionChangeProcessor( UnrecoverableErrorHandler* error_handler, SessionModelAssociator* session_model_associator, bool setup_for_test) : ChangeProcessor(error_handler), session_model_associator_(session_model_associator), profile_(NULL), setup_for_test_(setup_for_test) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(error_handler); DCHECK(session_model_associator_); } SessionChangeProcessor::~SessionChangeProcessor() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } void SessionChangeProcessor::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(running()); DCHECK(profile_); // Track which windows and/or tabs are modified. std::vector<TabContents*> modified_tabs; bool windows_changed = false; switch (type.value) { case NotificationType::BROWSER_OPENED: { Browser* browser = Source<Browser>(source).ptr(); if (browser->profile() != profile_) { return; } windows_changed = true; break; } case NotificationType::TAB_PARENTED: { NavigationController* controller = Source<NavigationController>(source).ptr(); if (controller->profile() != profile_) { return; } windows_changed = true; modified_tabs.push_back(controller->tab_contents()); break; } case NotificationType::TAB_CLOSED: { NavigationController* controller = Source<NavigationController>(source).ptr(); if (controller->profile() != profile_) { return; } windows_changed = true; modified_tabs.push_back(controller->tab_contents()); break; } case NotificationType::NAV_LIST_PRUNED: { NavigationController* controller = Source<NavigationController>(source).ptr(); if (controller->profile() != profile_) { return; } modified_tabs.push_back(controller->tab_contents()); break; } case NotificationType::NAV_ENTRY_CHANGED: { NavigationController* controller = Source<NavigationController>(source).ptr(); if (controller->profile() != profile_) { return; } modified_tabs.push_back(controller->tab_contents()); break; } case NotificationType::NAV_ENTRY_COMMITTED: { NavigationController* controller = Source<NavigationController>(source).ptr(); if (controller->profile() != profile_) { return; } modified_tabs.push_back(controller->tab_contents()); break; } case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { ExtensionTabHelper* extension_tab_helper = Source<ExtensionTabHelper>(source).ptr(); if (extension_tab_helper->tab_contents()->profile() != profile_) { return; } if (extension_tab_helper->extension_app()) { modified_tabs.push_back(extension_tab_helper->tab_contents()); } break; } default: LOG(ERROR) << "Received unexpected notification of type " << type.value; break; } // Associate windows first to ensure tabs have homes. if (windows_changed) session_model_associator_->ReassociateWindows(false); if (!modified_tabs.empty()) session_model_associator_->ReassociateTabs(modified_tabs); } void SessionChangeProcessor::ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, int change_count) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!running()) { return; } StopObserving(); sync_api::ReadNode root(trans); if (!root.InitByTagLookup(kSessionsTag)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Sessions root node lookup failed."); return; } for (int i = 0; i < change_count; ++i) { const sync_api::SyncManager::ChangeRecord& change = changes[i]; sync_api::SyncManager::ChangeRecord::Action action(change.action); if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { // Deletions should only be for a foreign client itself, and hence affect // the header node, never a tab node. sync_api::ReadNode node(trans); if (!node.InitByIdLookup(change.id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Session node lookup failed."); return; } DCHECK_EQ(node.GetModelType(), syncable::SESSIONS); const sync_pb::SessionSpecifics& specifics = node.GetSessionSpecifics(); session_model_associator_->DisassociateForeignSession( specifics.session_tag()); continue; } // Handle an update or add. sync_api::ReadNode sync_node(trans); if (!sync_node.InitByIdLookup(change.id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Session node lookup failed."); return; } // Check that the changed node is a child of the session folder. DCHECK(root.GetId() == sync_node.GetParentId()); DCHECK(syncable::SESSIONS == sync_node.GetModelType()); const sync_pb::SessionSpecifics& specifics( sync_node.GetSessionSpecifics()); if (specifics.session_tag() == session_model_associator_->GetCurrentMachineTag() && !setup_for_test_) { // We should only ever receive a change to our own machine's session info // if encryption was turned on. In that case, the data is still the same, // so we can ignore. LOG(WARNING) << "Dropping modification to local session."; return; } const int64 mtime = sync_node.GetModificationTime(); // Model associator handles foreign session update and add the same. session_model_associator_->AssociateForeignSpecifics(specifics, mtime); } // Notify foreign session handlers that there are new sessions. NotificationService::current()->Notify( NotificationType::FOREIGN_SESSION_UPDATED, NotificationService::AllSources(), NotificationService::NoDetails()); StartObserving(); } void SessionChangeProcessor::StartImpl(Profile* profile) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(profile); DCHECK(profile_ == NULL); profile_ = profile; StartObserving(); } void SessionChangeProcessor::StopImpl() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); StopObserving(); profile_ = NULL; } void SessionChangeProcessor::StartObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(profile_); notification_registrar_.Add(this, NotificationType::TAB_PARENTED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::TAB_CLOSED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::BROWSER_OPENED, NotificationService::AllSources()); notification_registrar_.Add(this, NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, NotificationService::AllSources()); } void SessionChangeProcessor::StopObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(profile_); notification_registrar_.RemoveAll(); } } // namespace browser_sync