web_contents_view_aura_browsertest.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 "content/browser/web_contents/web_contents_view_aura.h"
6
7#include "base/command_line.h"
8#include "base/run_loop.h"
9#include "base/test/test_timeouts.h"
10#include "base/utf_string_conversions.h"
11#include "base/values.h"
12#include "content/browser/renderer_host/render_view_host_impl.h"
13#include "content/browser/web_contents/navigation_controller_impl.h"
14#include "content/browser/web_contents/navigation_entry_impl.h"
15#include "content/browser/web_contents/web_contents_impl.h"
16#include "content/public/browser/web_contents_view.h"
17#include "content/public/common/content_switches.h"
18#include "content/public/test/browser_test_utils.h"
19#include "content/public/test/test_utils.h"
20#include "content/shell/shell.h"
21#include "content/test/content_browser_test.h"
22#include "content/test/content_browser_test_utils.h"
23#include "ui/aura/root_window.h"
24#include "ui/aura/test/event_generator.h"
25#include "ui/aura/window.h"
26#include "ui/compositor/scoped_animation_duration_scale_mode.h"
27
28namespace content {
29
30// A dummy callback to reset the screenshot-taker callback.
31void DummyCallback(RenderViewHost* host) {
32}
33
34// This class keeps track of the RenderViewHost whose screenshot was captured.
35class ScreenshotTracker {
36 public:
37  explicit ScreenshotTracker(NavigationControllerImpl* controller)
38      : screenshot_taken_for_(NULL),
39        controller_(controller) {
40    controller_->SetTakeScreenshotCallbackForTest(
41        base::Bind(&ScreenshotTracker::TakeScreenshotCallback,
42                   base::Unretained(this)));
43  }
44
45  virtual ~ScreenshotTracker() {
46    controller_->SetTakeScreenshotCallbackForTest(
47        base::Bind(&DummyCallback));
48  }
49
50  RenderViewHost* screenshot_taken_for() { return screenshot_taken_for_; }
51
52  void Reset() {
53    screenshot_taken_for_ = NULL;
54  }
55
56 private:
57  void TakeScreenshotCallback(RenderViewHost* host) {
58    screenshot_taken_for_ = host;
59  }
60
61  RenderViewHost* screenshot_taken_for_;
62  NavigationControllerImpl* controller_;
63
64  DISALLOW_COPY_AND_ASSIGN(ScreenshotTracker);
65};
66
67class WebContentsViewAuraTest : public ContentBrowserTest {
68 public:
69  WebContentsViewAuraTest() {}
70
71  // Executes the javascript synchronously and makes sure the returned value is
72  // freed properly.
73  void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) {
74    scoped_ptr<base::Value> value =
75        content::ExecuteScriptAndGetValue(rvh, jscript);
76  }
77
78  // Starts the test server and navigates to the given url. Sets a large enough
79  // size to the root window.  Returns after the navigation to the url is
80  // complete.
81  void StartTestWithPage(const std::string& url) {
82    ASSERT_TRUE(test_server()->Start());
83    GURL test_url(test_server()->GetURL(url));
84    NavigateToURL(shell(), test_url);
85    aura::Window* content =
86        shell()->web_contents()->GetView()->GetContentNativeView();
87    content->GetRootWindow()->SetHostSize(gfx::Size(800, 600));
88  }
89
90  void TestOverscrollNavigation(bool touch_handler) {
91    ASSERT_NO_FATAL_FAILURE(
92        StartTestWithPage("files/overscroll_navigation.html"));
93    WebContentsImpl* web_contents =
94        static_cast<WebContentsImpl*>(shell()->web_contents());
95    NavigationController& controller = web_contents->GetController();
96    RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
97        web_contents->GetRenderViewHost());
98    WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
99        web_contents->GetView());
100    view_aura->SetupOverlayWindowForTesting();
101
102    EXPECT_FALSE(controller.CanGoBack());
103    EXPECT_FALSE(controller.CanGoForward());
104    int index = -1;
105    scoped_ptr<base::Value> value =
106        content::ExecuteScriptAndGetValue(view_host, "get_current()");
107    ASSERT_TRUE(value->GetAsInteger(&index));
108    EXPECT_EQ(0, index);
109
110    if (touch_handler)
111      ExecuteSyncJSFunction(view_host, "install_touch_handler()");
112
113    ExecuteSyncJSFunction(view_host, "navigate_next()");
114    ExecuteSyncJSFunction(view_host, "navigate_next()");
115    value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
116    ASSERT_TRUE(value->GetAsInteger(&index));
117    EXPECT_EQ(2, index);
118    EXPECT_TRUE(controller.CanGoBack());
119    EXPECT_FALSE(controller.CanGoForward());
120
121    aura::Window* content = web_contents->GetView()->GetContentNativeView();
122    gfx::Rect bounds = content->GetBoundsInRootWindow();
123    aura::test::EventGenerator generator(content->GetRootWindow(), content);
124
125    {
126      // Do a swipe-right now. That should navigate backwards.
127      string16 expected_title = ASCIIToUTF16("Title: #1");
128      content::TitleWatcher title_watcher(web_contents, expected_title);
129      generator.GestureScrollSequence(
130          gfx::Point(bounds.x() + 2, bounds.y() + 10),
131          gfx::Point(bounds.right() - 10, bounds.y() + 10),
132          base::TimeDelta::FromMilliseconds(20),
133          1);
134      string16 actual_title = title_watcher.WaitAndGetTitle();
135      EXPECT_EQ(expected_title, actual_title);
136      value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
137      ASSERT_TRUE(value->GetAsInteger(&index));
138      EXPECT_EQ(1, index);
139      EXPECT_TRUE(controller.CanGoBack());
140      EXPECT_TRUE(controller.CanGoForward());
141    }
142
143    {
144      // Do a fling-right now. That should navigate backwards.
145      string16 expected_title = ASCIIToUTF16("Title:");
146      content::TitleWatcher title_watcher(web_contents, expected_title);
147      generator.GestureScrollSequence(
148          gfx::Point(bounds.x() + 2, bounds.y() + 10),
149          gfx::Point(bounds.right() - 10, bounds.y() + 10),
150          base::TimeDelta::FromMilliseconds(20),
151          10);
152      string16 actual_title = title_watcher.WaitAndGetTitle();
153      EXPECT_EQ(expected_title, actual_title);
154      value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
155      ASSERT_TRUE(value->GetAsInteger(&index));
156      EXPECT_EQ(0, index);
157      EXPECT_FALSE(controller.CanGoBack());
158      EXPECT_TRUE(controller.CanGoForward());
159    }
160
161    {
162      // Do a swipe-left now. That should navigate forward.
163      string16 expected_title = ASCIIToUTF16("Title: #1");
164      content::TitleWatcher title_watcher(web_contents, expected_title);
165      generator.GestureScrollSequence(
166          gfx::Point(bounds.right() - 10, bounds.y() + 10),
167          gfx::Point(bounds.x() + 2, bounds.y() + 10),
168          base::TimeDelta::FromMilliseconds(20),
169          10);
170      string16 actual_title = title_watcher.WaitAndGetTitle();
171      EXPECT_EQ(expected_title, actual_title);
172      value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
173      ASSERT_TRUE(value->GetAsInteger(&index));
174      EXPECT_EQ(1, index);
175      EXPECT_TRUE(controller.CanGoBack());
176      EXPECT_TRUE(controller.CanGoForward());
177    }
178  }
179
180  int GetCurrentIndex() {
181    WebContentsImpl* web_contents =
182        static_cast<WebContentsImpl*>(shell()->web_contents());
183    RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
184        web_contents->GetRenderViewHost());
185    int index = -1;
186    scoped_ptr<base::Value> value;
187    value = content::ExecuteScriptAndGetValue(view_host, "get_current()");
188    if (!value->GetAsInteger(&index))
189      index = -1;
190    return index;
191  }
192
193 private:
194  DISALLOW_COPY_AND_ASSIGN(WebContentsViewAuraTest);
195};
196
197IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
198                       OverscrollNavigation) {
199  TestOverscrollNavigation(false);
200}
201
202IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
203                       OverscrollNavigationWithTouchHandler) {
204  TestOverscrollNavigation(true);
205}
206
207// Disabled because the test always fails the first time it runs on the Win Aura
208// bots, and usually but not always passes second-try (See crbug.com/179532).
209#if defined(OS_WIN)
210#define MAYBE_QuickOverscrollDirectionChange \
211        DISABLED_QuickOverscrollDirectionChange
212#else
213#define MAYBE_QuickOverscrollDirectionChange QuickOverscrollDirectionChange
214#endif
215IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
216                       MAYBE_QuickOverscrollDirectionChange) {
217  ASSERT_NO_FATAL_FAILURE(
218      StartTestWithPage("files/overscroll_navigation.html"));
219  WebContentsImpl* web_contents =
220      static_cast<WebContentsImpl*>(shell()->web_contents());
221  RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
222      web_contents->GetRenderViewHost());
223
224  // This test triggers a large number of animations. Speed them up to ensure
225  // the test completes within its time limit.
226  ui::ScopedAnimationDurationScaleMode fast_duration_mode(
227      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
228
229  // Make sure the page has both back/forward history.
230  ExecuteSyncJSFunction(view_host, "navigate_next()");
231  EXPECT_EQ(1, GetCurrentIndex());
232  ExecuteSyncJSFunction(view_host, "navigate_next()");
233  EXPECT_EQ(2, GetCurrentIndex());
234  web_contents->GetController().GoBack();
235  EXPECT_EQ(1, GetCurrentIndex());
236
237  aura::Window* content = web_contents->GetView()->GetContentNativeView();
238  aura::RootWindow* root_window = content->GetRootWindow();
239  gfx::Rect bounds = content->GetBoundsInRootWindow();
240
241  base::TimeDelta timestamp;
242  ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
243      gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
244      0, timestamp);
245  root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
246  EXPECT_EQ(1, GetCurrentIndex());
247
248  timestamp += base::TimeDelta::FromMilliseconds(10);
249  ui::TouchEvent move1(ui::ET_TOUCH_MOVED,
250      gfx::Point(bounds.right() - 10, bounds.y() + 5),
251      0, timestamp);
252  root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1);
253  EXPECT_EQ(1, GetCurrentIndex());
254
255  // Swipe back from the right edge, back to the left edge, back to the right
256  // edge.
257
258  for (int x = bounds.right() - 10; x >= bounds.x() + 10; x-= 10) {
259    timestamp += base::TimeDelta::FromMilliseconds(10);
260    ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
261        gfx::Point(x, bounds.y() + 5),
262        0, timestamp);
263    root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
264    EXPECT_EQ(1, GetCurrentIndex());
265  }
266
267  for (int x = bounds.x() + 10; x <= bounds.width() - 10; x+= 10) {
268    timestamp += base::TimeDelta::FromMilliseconds(10);
269    ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
270        gfx::Point(x, bounds.y() + 5),
271        0, timestamp);
272    root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
273    EXPECT_EQ(1, GetCurrentIndex());
274  }
275
276  for (int x = bounds.width() - 10; x >= bounds.x() + 10; x-= 10) {
277    timestamp += base::TimeDelta::FromMilliseconds(10);
278    ui::TouchEvent inc(ui::ET_TOUCH_MOVED,
279        gfx::Point(x, bounds.y() + 5),
280        0, timestamp);
281    root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&inc);
282    EXPECT_EQ(1, GetCurrentIndex());
283  }
284
285  // Do not end the overscroll sequence.
286}
287
288// Tests that the page has has a screenshot when navigation happens:
289//  - from within the page (from a JS function)
290//  - interactively, when user does an overscroll gesture
291//  - interactively, when user navigates in history without the overscroll
292//    gesture.
293IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
294                       OverscrollScreenshot) {
295  ASSERT_NO_FATAL_FAILURE(
296      StartTestWithPage("files/overscroll_navigation.html"));
297  WebContentsImpl* web_contents =
298      static_cast<WebContentsImpl*>(shell()->web_contents());
299  RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
300      web_contents->GetRenderViewHost());
301
302  // Do a few navigations initiated by the page.
303  ExecuteSyncJSFunction(view_host, "navigate_next()");
304  EXPECT_EQ(1, GetCurrentIndex());
305  ExecuteSyncJSFunction(view_host, "navigate_next()");
306  EXPECT_EQ(2, GetCurrentIndex());
307
308  // The current entry won't have any screenshots. But the entries in the
309  // history should now have screenshots.
310  NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
311      web_contents->GetController().GetEntryAtIndex(2));
312  EXPECT_FALSE(entry->screenshot().get());
313
314  entry = NavigationEntryImpl::FromNavigationEntry(
315      web_contents->GetController().GetEntryAtIndex(1));
316  EXPECT_TRUE(entry->screenshot().get());
317
318  entry = NavigationEntryImpl::FromNavigationEntry(
319      web_contents->GetController().GetEntryAtIndex(0));
320  EXPECT_TRUE(entry->screenshot().get());
321
322  // Navigate again. Index 2 should now have a screenshot.
323  ExecuteSyncJSFunction(view_host, "navigate_next()");
324  EXPECT_EQ(3, GetCurrentIndex());
325
326  entry = NavigationEntryImpl::FromNavigationEntry(
327      web_contents->GetController().GetEntryAtIndex(2));
328  EXPECT_TRUE(entry->screenshot().get());
329
330  entry = NavigationEntryImpl::FromNavigationEntry(
331      web_contents->GetController().GetEntryAtIndex(3));
332  EXPECT_FALSE(entry->screenshot().get());
333
334  {
335    // Now, swipe right to navigate backwards. This should navigate away from
336    // index 3 to index 2, and index 3 should have a screenshot.
337    string16 expected_title = ASCIIToUTF16("Title: #2");
338    content::TitleWatcher title_watcher(web_contents, expected_title);
339    aura::Window* content = web_contents->GetView()->GetContentNativeView();
340    gfx::Rect bounds = content->GetBoundsInRootWindow();
341    aura::test::EventGenerator generator(content->GetRootWindow(), content);
342    generator.GestureScrollSequence(
343        gfx::Point(bounds.x() + 2, bounds.y() + 10),
344        gfx::Point(bounds.right() - 10, bounds.y() + 10),
345        base::TimeDelta::FromMilliseconds(20),
346        1);
347    string16 actual_title = title_watcher.WaitAndGetTitle();
348    EXPECT_EQ(expected_title, actual_title);
349    EXPECT_EQ(2, GetCurrentIndex());
350    entry = NavigationEntryImpl::FromNavigationEntry(
351        web_contents->GetController().GetEntryAtIndex(3));
352    EXPECT_TRUE(entry->screenshot().get());
353  }
354
355  // Navigate a couple more times.
356  ExecuteSyncJSFunction(view_host, "navigate_next()");
357  EXPECT_EQ(3, GetCurrentIndex());
358  ExecuteSyncJSFunction(view_host, "navigate_next()");
359  EXPECT_EQ(4, GetCurrentIndex());
360  entry = NavigationEntryImpl::FromNavigationEntry(
361      web_contents->GetController().GetEntryAtIndex(4));
362  EXPECT_FALSE(entry->screenshot().get());
363
364  {
365    // Navigate back in history.
366    string16 expected_title = ASCIIToUTF16("Title: #3");
367    content::TitleWatcher title_watcher(web_contents, expected_title);
368    web_contents->GetController().GoBack();
369    string16 actual_title = title_watcher.WaitAndGetTitle();
370    EXPECT_EQ(expected_title, actual_title);
371    EXPECT_EQ(3, GetCurrentIndex());
372    entry = NavigationEntryImpl::FromNavigationEntry(
373        web_contents->GetController().GetEntryAtIndex(4));
374    EXPECT_TRUE(entry->screenshot().get());
375  }
376}
377
378// Tests that screenshot is taken correctly when navigation causes a
379// RenderViewHost to be swapped out.
380IN_PROC_BROWSER_TEST_F(WebContentsViewAuraTest,
381                       ScreenshotForSwappedOutRenderViews) {
382  ASSERT_NO_FATAL_FAILURE(
383      StartTestWithPage("files/overscroll_navigation.html"));
384  // Create a new server with a different site.
385  net::TestServer https_server(
386      net::TestServer::TYPE_HTTPS,
387      net::TestServer::kLocalhost,
388      base::FilePath(FILE_PATH_LITERAL("content/test/data")));
389  ASSERT_TRUE(https_server.Start());
390
391  WebContentsImpl* web_contents =
392      static_cast<WebContentsImpl*>(shell()->web_contents());
393
394  struct {
395    GURL url;
396    int transition;
397  } navigations[] = {
398    { https_server.GetURL("files/title1.html"),
399      PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR },
400    { test_server()->GetURL("files/title2.html"),
401      PAGE_TRANSITION_AUTO_BOOKMARK },
402    { https_server.GetURL("files/title3.html"),
403      PAGE_TRANSITION_TYPED | PAGE_TRANSITION_FROM_ADDRESS_BAR },
404    { GURL(), 0 }
405  };
406
407  ScreenshotTracker tracker(&web_contents->GetController());
408  for (int i = 0; !navigations[i].url.is_empty(); ++i) {
409    // Navigate via the user initiating a navigation from the UI.
410    NavigationController::LoadURLParams params(navigations[i].url);
411    params.transition_type = PageTransitionFromInt(navigations[i].transition);
412
413    RenderViewHost* old_host = web_contents->GetRenderViewHost();
414    web_contents->GetController().LoadURLWithParams(params);
415    WaitForLoadStop(web_contents);
416
417    EXPECT_NE(old_host, web_contents->GetRenderViewHost())
418        << navigations[i].url.spec();
419    EXPECT_EQ(old_host, tracker.screenshot_taken_for());
420    tracker.Reset();
421
422    NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
423        web_contents->GetController().GetEntryAtOffset(-1));
424    EXPECT_TRUE(entry->screenshot().get());
425
426    entry = NavigationEntryImpl::FromNavigationEntry(
427        web_contents->GetController().GetActiveEntry());
428    EXPECT_FALSE(entry->screenshot().get());
429  }
430}
431
432}  // namespace content
433