sessions_helper.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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
30using sync_datatype_helper::test;
31
32namespace sessions_helper {
33
34ScopedWindowMap::ScopedWindowMap() {
35}
36
37ScopedWindowMap::ScopedWindowMap(SessionWindowMap* windows) {
38  Reset(windows);
39}
40
41ScopedWindowMap::~ScopedWindowMap() {
42  STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end());
43}
44
45SessionWindowMap* ScopedWindowMap::GetMutable() {
46  return &windows_;
47}
48
49const SessionWindowMap* ScopedWindowMap::Get() const {
50  return &windows_;
51}
52
53void ScopedWindowMap::Reset(SessionWindowMap* windows) {
54  STLDeleteContainerPairSecondPointers(windows_.begin(), windows_.end());
55  windows_.clear();
56  std::swap(*windows, windows_);
57}
58
59bool GetLocalSession(int index, const browser_sync::SyncedSession** session) {
60  return ProfileSyncServiceFactory::GetInstance()->GetForProfile(
61      test()->GetProfile(index))->GetOpenTabsUIDelegate()->
62          GetLocalSession(session);
63}
64
65bool 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
110bool 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
117bool 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
128namespace {
129
130class 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
165bool 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
194bool 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
220bool 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
229bool CheckInitialState(int index) {
230  if (0 != GetNumWindows(index))
231    return false;
232  if (0 != GetNumForeignSessions(index))
233    return false;
234  return true;
235}
236
237int 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
245int 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
256bool 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
267bool 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
280void SortSyncedSessions(SyncedSessionVector* sessions) {
281  std::sort(sessions->begin(), sessions->end(),
282            CompareSyncedSessions);
283}
284
285bool 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
314bool 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
342bool 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
363namespace {
364
365// Helper class used in the implementation of AwaitCheckForeignSessionsAgainst.
366class 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
379CheckForeignSessionsChecker::CheckForeignSessionsChecker(
380    int index, const std::vector<ScopedWindowMap>& windows)
381    : MultiClientStatusChangeChecker(
382        sync_datatype_helper::test()->GetSyncServices()),
383      index_(index),
384      windows_(windows) {}
385
386CheckForeignSessionsChecker::~CheckForeignSessionsChecker() {}
387
388bool CheckForeignSessionsChecker::IsExitConditionSatisfied() {
389  return CheckForeignSessionsAgainst(index_, windows_);
390}
391
392std::string CheckForeignSessionsChecker::GetDebugMessage() const {
393  return "Waiting for matching foreign sessions";
394}
395
396}  //  namespace
397
398bool AwaitCheckForeignSessionsAgainst(
399    int index, const std::vector<ScopedWindowMap>& windows) {
400  CheckForeignSessionsChecker checker(index, windows);
401  checker.Wait();
402  return !checker.TimedOut();
403}
404
405void 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