Home | History | Annotate | Download | only in glue
      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