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. 4 5 #include "chrome/browser/sync/test/integration/sessions_helper.h" 6 7 #include <algorithm> 8 9 #include "base/bind.h" 10 #include "base/command_line.h" 11 #include "base/memory/weak_ptr.h" 12 #include "base/stl_util.h" 13 #include "base/test/test_timeouts.h" 14 #include "base/time/time.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/sync/open_tabs_ui_delegate.h" 17 #include "chrome/browser/sync/profile_sync_service.h" 18 #include "chrome/browser/sync/profile_sync_service_factory.h" 19 #include "chrome/browser/sync/sessions/notification_service_sessions_router.h" 20 #include "chrome/browser/sync/sessions/sessions_sync_manager.h" 21 #include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h" 22 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h" 23 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h" 24 #include "chrome/browser/sync/test/integration/sync_test.h" 25 #include "chrome/browser/ui/singleton_tabs.h" 26 #include "chrome/common/chrome_switches.h" 27 #include "chrome/test/base/ui_test_utils.h" 28 #include "url/gurl.h" 29 30 using sync_datatype_helper::test; 31 32 namespace sessions_helper { 33 34 ScopedWindowMap::ScopedWindowMap() { 35 } 36 37 ScopedWindowMap::ScopedWindowMap(SessionWindowMap* windows) { 38 Reset(windows); 39 } 40 41 ScopedWindowMap::~ScopedWindowMap() { 42 STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end()); 43 } 44 45 SessionWindowMap* ScopedWindowMap::GetMutable() { 46 return &windows_; 47 } 48 49 const SessionWindowMap* ScopedWindowMap::Get() const { 50 return &windows_; 51 } 52 53 void ScopedWindowMap::Reset(SessionWindowMap* windows) { 54 STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end()); 55 windows_.clear(); 56 std::swap(*windows, windows_); 57 } 58 59 bool GetLocalSession(int index, const browser_sync::SyncedSession** session) { 60 return ProfileSyncServiceFactory::GetInstance()->GetForProfile( 61 test()->GetProfile(index))->GetOpenTabsUIDelegate()-> 62 GetLocalSession(session); 63 } 64 65 bool ModelAssociatorHasTabWithUrl(int index, const GURL& url) { 66 content::RunAllPendingInMessageLoop(); 67 const browser_sync::SyncedSession* local_session; 68 if (!GetLocalSession(index, &local_session)) { 69 return false; 70 } 71 72 if (local_session->windows.size() == 0) { 73 DVLOG(1) << "Empty windows vector"; 74 return false; 75 } 76 77 int nav_index; 78 sessions::SerializedNavigationEntry nav; 79 for (SessionWindowMap::const_iterator it = 80 local_session->windows.begin(); 81 it != local_session->windows.end(); ++it) { 82 if (it->second->tabs.size() == 0) { 83 DVLOG(1) << "Empty tabs vector"; 84 continue; 85 } 86 for (std::vector<SessionTab*>::const_iterator tab_it = 87 it->second->tabs.begin(); 88 tab_it != it->second->tabs.end(); ++tab_it) { 89 if ((*tab_it)->navigations.size() == 0) { 90 DVLOG(1) << "Empty navigations vector"; 91 continue; 92 } 93 nav_index = (*tab_it)->current_navigation_index; 94 nav = (*tab_it)->navigations[nav_index]; 95 if (nav.virtual_url() == url) { 96 DVLOG(1) << "Found tab with url " << url.spec(); 97 DVLOG(1) << "Timestamp is " << nav.timestamp().ToInternalValue(); 98 if (nav.title().empty()) { 99 DVLOG(1) << "Title empty -- tab hasn't finished loading yet"; 100 continue; 101 } 102 return true; 103 } 104 } 105 } 106 DVLOG(1) << "Could not find tab with url " << url.spec(); 107 return false; 108 } 109 110 bool OpenTab(int index, const GURL& url) { 111 DVLOG(1) << "Opening tab: " << url.spec() << " using profile " 112 << index << "."; 113 chrome::ShowSingletonTab(test()->GetBrowser(index), url); 114 return WaitForTabsToLoad(index, std::vector<GURL>(1, url)); 115 } 116 117 bool OpenMultipleTabs(int index, const std::vector<GURL>& urls) { 118 Browser* browser = test()->GetBrowser(index); 119 for (std::vector<GURL>::const_iterator it = urls.begin(); 120 it != urls.end(); ++it) { 121 DVLOG(1) << "Opening tab: " << it->spec() << " using profile " << index 122 << "."; 123 chrome::ShowSingletonTab(browser, *it); 124 } 125 return WaitForTabsToLoad(index, urls); 126 } 127 128 namespace { 129 130 class TabEventHandler : public browser_sync::LocalSessionEventHandler { 131 public: 132 TabEventHandler() : weak_factory_(this) { 133 base::MessageLoop::current()->PostDelayedTask( 134 FROM_HERE, 135 base::Bind(&TabEventHandler::QuitLoop, weak_factory_.GetWeakPtr()), 136 TestTimeouts::action_max_timeout()); 137 } 138 139 virtual void OnLocalTabModified( 140 browser_sync::SyncedTabDelegate* modified_tab) OVERRIDE { 141 // Unwind to ensure SessionsSyncManager has processed the event. 142 base::MessageLoop::current()->PostTask( 143 FROM_HERE, 144 base::Bind(&TabEventHandler::QuitLoop, weak_factory_.GetWeakPtr())); 145 } 146 147 virtual void OnFaviconPageUrlsUpdated( 148 const std::set<GURL>& updated_page_urls) OVERRIDE { 149 // Unwind to ensure SessionsSyncManager has processed the event. 150 base::MessageLoop::current()->PostTask( 151 FROM_HERE, 152 base::Bind(&TabEventHandler::QuitLoop, weak_factory_.GetWeakPtr())); 153 } 154 155 private: 156 void QuitLoop() { 157 base::MessageLoop::current()->Quit(); 158 } 159 160 base::WeakPtrFactory<TabEventHandler> weak_factory_; 161 }; 162 163 } // namespace 164 165 bool WaitForTabsToLoad(int index, const std::vector<GURL>& urls) { 166 DVLOG(1) << "Waiting for session to propagate to associator."; 167 base::TimeTicks start_time = base::TimeTicks::Now(); 168 base::TimeTicks end_time = start_time + TestTimeouts::action_max_timeout(); 169 bool found; 170 for (std::vector<GURL>::const_iterator it = urls.begin(); 171 it != urls.end(); ++it) { 172 found = false; 173 while (!found) { 174 found = ModelAssociatorHasTabWithUrl(index, *it); 175 if (base::TimeTicks::Now() >= end_time) { 176 LOG(ERROR) << "Failed to find all tabs after " 177 << TestTimeouts::action_max_timeout().InSecondsF() 178 << " seconds."; 179 return false; 180 } 181 if (!found) { 182 TabEventHandler handler; 183 browser_sync::NotificationServiceSessionsRouter router( 184 test()->GetProfile(index), 185 syncer::SyncableService::StartSyncFlare()); 186 router.StartRoutingTo(&handler); 187 content::RunMessageLoop(); 188 } 189 } 190 } 191 return true; 192 } 193 194 bool GetLocalWindows(int index, SessionWindowMap* local_windows) { 195 // The local session provided by GetLocalSession is owned, and has lifetime 196 // controlled, by the model associator, so we must make our own copy. 197 const browser_sync::SyncedSession* local_session; 198 if (!GetLocalSession(index, &local_session)) { 199 return false; 200 } 201 for (SessionWindowMap::const_iterator w = local_session->windows.begin(); 202 w != local_session->windows.end(); ++w) { 203 const SessionWindow& window = *(w->second); 204 SessionWindow* new_window = new SessionWindow(); 205 new_window->window_id.set_id(window.window_id.id()); 206 for (size_t t = 0; t < window.tabs.size(); ++t) { 207 const SessionTab& tab = *window.tabs.at(t); 208 SessionTab* new_tab = new SessionTab(); 209 new_tab->navigations.resize(tab.navigations.size()); 210 std::copy(tab.navigations.begin(), tab.navigations.end(), 211 new_tab->navigations.begin()); 212 new_window->tabs.push_back(new_tab); 213 } 214 (*local_windows)[new_window->window_id.id()] = new_window; 215 } 216 217 return true; 218 } 219 220 bool OpenTabAndGetLocalWindows(int index, 221 const GURL& url, 222 SessionWindowMap* local_windows) { 223 if (!OpenTab(index, url)) { 224 return false; 225 } 226 return GetLocalWindows(index, local_windows); 227 } 228 229 bool CheckInitialState(int index) { 230 if (0 != GetNumWindows(index)) 231 return false; 232 if (0 != GetNumForeignSessions(index)) 233 return false; 234 return true; 235 } 236 237 int GetNumWindows(int index) { 238 const browser_sync::SyncedSession* local_session; 239 if (!GetLocalSession(index, &local_session)) { 240 return 0; 241 } 242 return local_session->windows.size(); 243 } 244 245 int GetNumForeignSessions(int index) { 246 SyncedSessionVector sessions; 247 if (!ProfileSyncServiceFactory::GetInstance()->GetForProfile( 248 test()->GetProfile(index))-> 249 GetOpenTabsUIDelegate()->GetAllForeignSessions( 250 &sessions)) { 251 return 0; 252 } 253 return sessions.size(); 254 } 255 256 bool GetSessionData(int index, SyncedSessionVector* sessions) { 257 if (!ProfileSyncServiceFactory::GetInstance()->GetForProfile( 258 test()->GetProfile(index))-> 259 GetOpenTabsUIDelegate()->GetAllForeignSessions( 260 sessions)) { 261 return false; 262 } 263 SortSyncedSessions(sessions); 264 return true; 265 } 266 267 bool CompareSyncedSessions(const browser_sync::SyncedSession* lhs, 268 const browser_sync::SyncedSession* rhs) { 269 if (!lhs || 270 !rhs || 271 lhs->windows.size() < 1 || 272 rhs->windows.size() < 1) { 273 // Catchall for uncomparable data. 274 return false; 275 } 276 277 return lhs->windows < rhs->windows; 278 } 279 280 void SortSyncedSessions(SyncedSessionVector* sessions) { 281 std::sort(sessions->begin(), sessions->end(), 282 CompareSyncedSessions); 283 } 284 285 bool NavigationEquals(const sessions::SerializedNavigationEntry& expected, 286 const sessions::SerializedNavigationEntry& actual) { 287 if (expected.virtual_url() != actual.virtual_url()) { 288 LOG(ERROR) << "Expected url " << expected.virtual_url() 289 << ", actual " << actual.virtual_url(); 290 return false; 291 } 292 if (expected.referrer().url != actual.referrer().url) { 293 LOG(ERROR) << "Expected referrer " 294 << expected.referrer().url 295 << ", actual " 296 << actual.referrer().url; 297 return false; 298 } 299 if (expected.title() != actual.title()) { 300 LOG(ERROR) << "Expected title " << expected.title() 301 << ", actual " << actual.title(); 302 return false; 303 } 304 if (expected.transition_type() != actual.transition_type()) { 305 LOG(ERROR) << "Expected transition " 306 << expected.transition_type() 307 << ", actual " 308 << actual.transition_type(); 309 return false; 310 } 311 return true; 312 } 313 314 bool WindowsMatch(const SessionWindowMap& win1, 315 const SessionWindowMap& win2) { 316 SessionTab* client0_tab; 317 SessionTab* client1_tab; 318 if (win1.size() != win2.size()) 319 return false; 320 for (SessionWindowMap::const_iterator i = win1.begin(); 321 i != win1.end(); ++i) { 322 SessionWindowMap::const_iterator j = win2.find(i->first); 323 if (j == win2.end()) 324 return false; 325 if (i->second->tabs.size() != j->second->tabs.size()) 326 return false; 327 for (size_t t = 0; t < i->second->tabs.size(); ++t) { 328 client0_tab = i->second->tabs[t]; 329 client1_tab = j->second->tabs[t]; 330 for (size_t n = 0; n < client0_tab->navigations.size(); ++n) { 331 if (!NavigationEquals(client0_tab->navigations[n], 332 client1_tab->navigations[n])) { 333 return false; 334 } 335 } 336 } 337 } 338 339 return true; 340 } 341 342 bool CheckForeignSessionsAgainst( 343 int index, 344 const std::vector<ScopedWindowMap>& windows) { 345 SyncedSessionVector sessions; 346 if (!GetSessionData(index, &sessions)) 347 return false; 348 if ((size_t)(test()->num_clients()-1) != sessions.size()) 349 return false; 350 351 int window_index = 0; 352 for (size_t j = 0; j < sessions.size(); ++j, ++window_index) { 353 if (window_index == index) 354 window_index++; // Skip self. 355 if (!WindowsMatch(sessions[j]->windows, 356 *(windows[window_index].Get()))) 357 return false; 358 } 359 360 return true; 361 } 362 363 namespace { 364 365 // Helper class used in the implementation of AwaitCheckForeignSessionsAgainst. 366 class CheckForeignSessionsChecker : public MultiClientStatusChangeChecker { 367 public: 368 CheckForeignSessionsChecker(int index, 369 const std::vector<ScopedWindowMap>& windows); 370 virtual ~CheckForeignSessionsChecker(); 371 372 virtual bool IsExitConditionSatisfied() OVERRIDE; 373 virtual std::string GetDebugMessage() const OVERRIDE; 374 private: 375 int index_; 376 const std::vector<ScopedWindowMap>& windows_; 377 }; 378 379 CheckForeignSessionsChecker::CheckForeignSessionsChecker( 380 int index, const std::vector<ScopedWindowMap>& windows) 381 : MultiClientStatusChangeChecker( 382 sync_datatype_helper::test()->GetSyncServices()), 383 index_(index), 384 windows_(windows) {} 385 386 CheckForeignSessionsChecker::~CheckForeignSessionsChecker() {} 387 388 bool CheckForeignSessionsChecker::IsExitConditionSatisfied() { 389 return CheckForeignSessionsAgainst(index_, windows_); 390 } 391 392 std::string CheckForeignSessionsChecker::GetDebugMessage() const { 393 return "Waiting for matching foreign sessions"; 394 } 395 396 } // namespace 397 398 bool AwaitCheckForeignSessionsAgainst( 399 int index, const std::vector<ScopedWindowMap>& windows) { 400 CheckForeignSessionsChecker checker(index, windows); 401 checker.Wait(); 402 return !checker.TimedOut(); 403 } 404 405 void DeleteForeignSession(int index, std::string session_tag) { 406 ProfileSyncServiceFactory::GetInstance()->GetForProfile( 407 test()->GetProfile(index))-> 408 GetOpenTabsUIDelegate()->DeleteForeignSession(session_tag); 409 } 410 411 } // namespace sessions_helper 412