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
ScopedWindowMap()34 ScopedWindowMap::ScopedWindowMap() {
35 }
36
ScopedWindowMap(SessionWindowMap * windows)37 ScopedWindowMap::ScopedWindowMap(SessionWindowMap* windows) {
38 Reset(windows);
39 }
40
~ScopedWindowMap()41 ScopedWindowMap::~ScopedWindowMap() {
42 STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end());
43 }
44
GetMutable()45 SessionWindowMap* ScopedWindowMap::GetMutable() {
46 return &windows_;
47 }
48
Get() const49 const SessionWindowMap* ScopedWindowMap::Get() const {
50 return &windows_;
51 }
52
Reset(SessionWindowMap * windows)53 void ScopedWindowMap::Reset(SessionWindowMap* windows) {
54 STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end());
55 windows_.clear();
56 std::swap(*windows, windows_);
57 }
58
GetLocalSession(int index,const browser_sync::SyncedSession ** session)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
ModelAssociatorHasTabWithUrl(int index,const GURL & url)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
OpenTab(int index,const GURL & url)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
OpenMultipleTabs(int index,const std::vector<GURL> & urls)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:
TabEventHandler()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
OnLocalTabModified(browser_sync::SyncedTabDelegate * modified_tab)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
OnFaviconPageUrlsUpdated(const std::set<GURL> & updated_page_urls)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:
QuitLoop()156 void QuitLoop() {
157 base::MessageLoop::current()->Quit();
158 }
159
160 base::WeakPtrFactory<TabEventHandler> weak_factory_;
161 };
162
163 } // namespace
164
WaitForTabsToLoad(int index,const std::vector<GURL> & urls)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
GetLocalWindows(int index,SessionWindowMap * local_windows)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
OpenTabAndGetLocalWindows(int index,const GURL & url,SessionWindowMap * local_windows)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
CheckInitialState(int index)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
GetNumWindows(int index)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
GetNumForeignSessions(int index)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
GetSessionData(int index,SyncedSessionVector * sessions)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
CompareSyncedSessions(const browser_sync::SyncedSession * lhs,const browser_sync::SyncedSession * rhs)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
SortSyncedSessions(SyncedSessionVector * sessions)280 void SortSyncedSessions(SyncedSessionVector* sessions) {
281 std::sort(sessions->begin(), sessions->end(),
282 CompareSyncedSessions);
283 }
284
NavigationEquals(const sessions::SerializedNavigationEntry & expected,const sessions::SerializedNavigationEntry & actual)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
WindowsMatch(const SessionWindowMap & win1,const SessionWindowMap & win2)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
CheckForeignSessionsAgainst(int index,const std::vector<ScopedWindowMap> & windows)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
CheckForeignSessionsChecker(int index,const std::vector<ScopedWindowMap> & windows)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
~CheckForeignSessionsChecker()386 CheckForeignSessionsChecker::~CheckForeignSessionsChecker() {}
387
IsExitConditionSatisfied()388 bool CheckForeignSessionsChecker::IsExitConditionSatisfied() {
389 return CheckForeignSessionsAgainst(index_, windows_);
390 }
391
GetDebugMessage() const392 std::string CheckForeignSessionsChecker::GetDebugMessage() const {
393 return "Waiting for matching foreign sessions";
394 }
395
396 } // namespace
397
AwaitCheckForeignSessionsAgainst(int index,const std::vector<ScopedWindowMap> & windows)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
DeleteForeignSession(int index,std::string session_tag)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