web_contents_impl_browsertest.cc revision 010d83a9304c5a91596085d917d248abff47903a
1// Copyright 2013 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 "base/strings/utf_string_conversions.h"
6#include "base/values.h"
7#include "content/browser/frame_host/navigation_entry_impl.h"
8#include "content/browser/web_contents/web_contents_impl.h"
9#include "content/browser/web_contents/web_contents_view.h"
10#include "content/public/browser/load_notification_details.h"
11#include "content/public/browser/navigation_controller.h"
12#include "content/public/browser/notification_details.h"
13#include "content/public/browser/notification_observer.h"
14#include "content/public/browser/notification_types.h"
15#include "content/public/browser/render_view_host.h"
16#include "content/public/browser/render_widget_host_view.h"
17#include "content/public/browser/web_contents_observer.h"
18#include "content/public/common/content_paths.h"
19#include "content/public/test/browser_test_utils.h"
20#include "content/public/test/content_browser_test.h"
21#include "content/public/test/content_browser_test_utils.h"
22#include "content/public/test/test_utils.h"
23#include "content/shell/browser/shell.h"
24#include "net/dns/mock_host_resolver.h"
25#include "net/test/embedded_test_server/embedded_test_server.h"
26
27namespace content {
28
29void ResizeWebContentsView(Shell* shell, const gfx::Size& size,
30                           bool set_start_page) {
31  // Shell::SizeTo is not implemented on Aura; WebContentsView::SizeContents
32  // works on Win and ChromeOS but not Linux - we need to resize the shell
33  // window on Linux because if we don't, the next layout of the unchanged shell
34  // window will resize WebContentsView back to the previous size.
35  // SizeContents is a hack and should not be relied on.
36#if defined(OS_MACOSX)
37  shell->SizeTo(size);
38  // If |set_start_page| is true, start with blank page to make sure resize
39  // takes effect.
40  if (set_start_page)
41    NavigateToURL(shell, GURL("about://blank"));
42#else
43  static_cast<WebContentsImpl*>(shell->web_contents())->GetView()->
44      SizeContents(size);
45#endif  // defined(OS_MACOSX)
46}
47
48class WebContentsImplBrowserTest : public ContentBrowserTest {
49 public:
50  WebContentsImplBrowserTest() {}
51
52 private:
53  DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
54};
55
56// Keeps track of data from LoadNotificationDetails so we can later verify that
57// they are correct, after the LoadNotificationDetails object is deleted.
58class LoadStopNotificationObserver : public WindowedNotificationObserver {
59 public:
60  LoadStopNotificationObserver(NavigationController* controller)
61      : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP,
62                                     Source<NavigationController>(controller)),
63        session_index_(-1),
64        controller_(NULL) {
65  }
66  virtual void Observe(int type,
67                       const NotificationSource& source,
68                       const NotificationDetails& details) OVERRIDE {
69    if (type == NOTIFICATION_LOAD_STOP) {
70      const Details<LoadNotificationDetails> load_details(details);
71      url_ = load_details->url;
72      session_index_ = load_details->session_index;
73      controller_ = load_details->controller;
74    }
75    WindowedNotificationObserver::Observe(type, source, details);
76  }
77
78  GURL url_;
79  int session_index_;
80  NavigationController* controller_;
81};
82
83// Starts a new navigation as soon as the current one commits, but does not
84// wait for it to complete.  This allows us to observe DidStopLoading while
85// a pending entry is present.
86class NavigateOnCommitObserver : public WebContentsObserver {
87 public:
88  NavigateOnCommitObserver(Shell* shell, GURL url)
89      : WebContentsObserver(shell->web_contents()),
90        shell_(shell),
91        url_(url),
92        done_(false) {
93  }
94
95  // WebContentsObserver:
96  virtual void NavigationEntryCommitted(
97      const LoadCommittedDetails& load_details) OVERRIDE {
98    if (!done_) {
99      done_ = true;
100      shell_->LoadURL(url_);
101    }
102  }
103
104  Shell* shell_;
105  GURL url_;
106  bool done_;
107};
108
109class RenderViewSizeDelegate : public WebContentsDelegate {
110 public:
111  void set_size_insets(const gfx::Size& size_insets) {
112    size_insets_ = size_insets;
113  }
114
115  // WebContentsDelegate:
116  virtual gfx::Size GetSizeForNewRenderView(
117      WebContents* web_contents) const OVERRIDE {
118    gfx::Size size(web_contents->GetContainerBounds().size());
119    size.Enlarge(size_insets_.width(), size_insets_.height());
120    return size;
121  }
122
123 private:
124  gfx::Size size_insets_;
125};
126
127class RenderViewSizeObserver : public WebContentsObserver {
128 public:
129  RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size)
130      : WebContentsObserver(shell->web_contents()),
131        shell_(shell),
132        wcv_new_size_(wcv_new_size) {
133  }
134
135  // WebContentsObserver:
136  virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE {
137    rwhv_create_size_ = rvh->GetView()->GetViewBounds().size();
138  }
139
140  virtual void DidStartNavigationToPendingEntry(
141      const GURL& url,
142      NavigationController::ReloadType reload_type) OVERRIDE {
143    ResizeWebContentsView(shell_, wcv_new_size_, false);
144  }
145
146  gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
147
148 private:
149  Shell* shell_;  // Weak ptr.
150  gfx::Size wcv_new_size_;
151  gfx::Size rwhv_create_size_;
152};
153
154class LoadingStateChangedDelegate : public WebContentsDelegate {
155 public:
156  LoadingStateChangedDelegate()
157      : loadingStateChangedCount_(0)
158      , loadingStateToDifferentDocumentCount_(0) {
159  }
160
161  // WebContentsDelgate:
162  virtual void LoadingStateChanged(WebContents* contents,
163                                   bool to_different_document) OVERRIDE {
164      loadingStateChangedCount_++;
165      if (to_different_document)
166        loadingStateToDifferentDocumentCount_++;
167  }
168
169  int loadingStateChangedCount() const { return loadingStateChangedCount_; }
170  int loadingStateToDifferentDocumentCount() const {
171    return loadingStateToDifferentDocumentCount_;
172  }
173
174 private:
175  int loadingStateChangedCount_;
176  int loadingStateToDifferentDocumentCount_;
177};
178
179// See: http://crbug.com/298193
180#if defined(OS_WIN)
181#define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
182#else
183#define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
184#endif
185
186// Test that DidStopLoading includes the correct URL in the details.
187IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
188                       MAYBE_DidStopLoadingDetails) {
189  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
190
191  LoadStopNotificationObserver load_observer(
192      &shell()->web_contents()->GetController());
193  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
194  load_observer.Wait();
195
196  EXPECT_EQ("/title1.html", load_observer.url_.path());
197  EXPECT_EQ(0, load_observer.session_index_);
198  EXPECT_EQ(&shell()->web_contents()->GetController(),
199            load_observer.controller_);
200}
201
202// See: http://crbug.com/298193
203#if defined(OS_WIN)
204#define MAYBE_DidStopLoadingDetailsWithPending \
205  DISABLED_DidStopLoadingDetailsWithPending
206#else
207#define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
208#endif
209
210// Test that DidStopLoading includes the correct URL in the details when a
211// pending entry is present.
212IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
213                       MAYBE_DidStopLoadingDetailsWithPending) {
214  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
215
216  // Listen for the first load to stop.
217  LoadStopNotificationObserver load_observer(
218      &shell()->web_contents()->GetController());
219  // Start a new pending navigation as soon as the first load commits.
220  // We will hear a DidStopLoading from the first load as the new load
221  // is started.
222  NavigateOnCommitObserver commit_observer(
223      shell(), embedded_test_server()->GetURL("/title2.html"));
224  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
225  load_observer.Wait();
226
227  EXPECT_EQ("/title1.html", load_observer.url_.path());
228  EXPECT_EQ(0, load_observer.session_index_);
229  EXPECT_EQ(&shell()->web_contents()->GetController(),
230            load_observer.controller_);
231}
232// Test that a renderer-initiated navigation to an invalid URL does not leave
233// around a pending entry that could be used in a URL spoof.  We test this in
234// a browser test because our unit test framework incorrectly calls
235// DidStartProvisionalLoadForFrame for in-page navigations.
236// See http://crbug.com/280512.
237IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
238                       ClearNonVisiblePendingOnFail) {
239  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
240
241  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
242
243  // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
244  LoadStopNotificationObserver load_observer1(
245      &shell()->web_contents()->GetController());
246  ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
247                            "window.location.href=\"nonexistent:12121\";"));
248  load_observer1.Wait();
249  EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
250
251  LoadStopNotificationObserver load_observer2(
252      &shell()->web_contents()->GetController());
253  ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
254                            "window.location.href=\"#foo\";"));
255  load_observer2.Wait();
256  EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
257            shell()->web_contents()->GetVisibleURL());
258}
259
260// TODO(shrikant): enable this for Windows when issue with
261// force-compositing-mode is resolved (http://crbug.com/281726).
262// Also crashes under ThreadSanitizer, http://crbug.com/356758.
263#if defined(OS_WIN) || defined(OS_ANDROID) \
264    || defined(THREAD_SANITIZER)
265#define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
266#else
267#define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView
268#endif
269// Test that RenderViewHost is created and updated at the size specified by
270// WebContentsDelegate::GetSizeForNewRenderView().
271IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
272                       MAYBE_GetSizeForNewRenderView) {
273  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
274  // Create a new server with a different site.
275  net::SpawnedTestServer https_server(
276      net::SpawnedTestServer::TYPE_HTTPS,
277      net::SpawnedTestServer::kLocalhost,
278      base::FilePath(FILE_PATH_LITERAL("content/test/data")));
279  ASSERT_TRUE(https_server.Start());
280
281  scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate());
282  shell()->web_contents()->SetDelegate(delegate.get());
283  ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get());
284
285  // When no size is set, RenderWidgetHostView adopts the size of
286  // WebContentsView.
287  NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
288  EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(),
289            shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
290                size());
291
292  // When a size is set, RenderWidgetHostView and WebContentsView honor this
293  // size.
294  gfx::Size size(300, 300);
295  gfx::Size size_insets(10, 15);
296  ResizeWebContentsView(shell(), size, true);
297  delegate->set_size_insets(size_insets);
298  NavigateToURL(shell(), https_server.GetURL("/"));
299  size.Enlarge(size_insets.width(), size_insets.height());
300  EXPECT_EQ(size,
301            shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
302                size());
303  // The web_contents size is set by the embedder, and should not depend on the
304  // rwhv size. The behavior is correct on OSX, but incorrect on other
305  // platforms.
306  gfx::Size exp_wcv_size(300, 300);
307#if !defined(OS_MACOSX)
308  exp_wcv_size.Enlarge(size_insets.width(), size_insets.height());
309#endif
310
311  EXPECT_EQ(exp_wcv_size,
312            shell()->web_contents()->GetContainerBounds().size());
313
314  // If WebContentsView is resized after RenderWidgetHostView is created but
315  // before pending navigation entry is committed, both RenderWidgetHostView and
316  // WebContentsView use the new size of WebContentsView.
317  gfx::Size init_size(200, 200);
318  gfx::Size new_size(100, 100);
319  size_insets = gfx::Size(20, 30);
320  ResizeWebContentsView(shell(), init_size, true);
321  delegate->set_size_insets(size_insets);
322  RenderViewSizeObserver observer(shell(), new_size);
323  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
324  // RenderWidgetHostView is created at specified size.
325  init_size.Enlarge(size_insets.width(), size_insets.height());
326  EXPECT_EQ(init_size, observer.rwhv_create_size());
327
328// Once again, the behavior is correct on OSX. The embedder explicitly sets
329// the size to (100,100) during navigation. Both the wcv and the rwhv should
330// take on that size.
331#if !defined(OS_MACOSX)
332  new_size.Enlarge(size_insets.width(), size_insets.height());
333#endif
334  gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()->
335      GetViewBounds().size();
336
337  EXPECT_EQ(new_size, actual_size);
338  EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size());
339}
340
341IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
342
343  // Navigate with FrameTreeNode ID 4.
344  const GURL url("http://foo");
345  OpenURLParams params(url, Referrer(), 4, CURRENT_TAB, PAGE_TRANSITION_LINK,
346                       true);
347  shell()->web_contents()->OpenURL(params);
348
349  // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
350  NavigationController* controller = &shell()->web_contents()->GetController();
351  EXPECT_TRUE(controller->GetPendingEntry());
352  EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry(
353                controller->GetPendingEntry())->frame_tree_node_id());
354}
355
356// Observer class to track the creation of RenderFrameHost objects. It is used
357// in subsequent tests.
358class RenderFrameCreatedObserver : public WebContentsObserver {
359 public:
360  RenderFrameCreatedObserver(Shell* shell)
361      : WebContentsObserver(shell->web_contents()),
362        last_rfh_(NULL) {
363  }
364
365  virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE {
366    last_rfh_ = render_frame_host;
367  }
368
369  RenderFrameHost* last_rfh() const { return last_rfh_; }
370
371 private:
372  RenderFrameHost* last_rfh_;
373
374  DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
375};
376
377// Test that creation of new RenderFrameHost objects sends the correct object
378// to the WebContentObservers. See http://crbug.com/347339.
379IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
380                       RenderFrameCreatedCorrectProcessForObservers) {
381  std::string foo_com("foo.com");
382  GURL::Replacements replace_host;
383  net::HostPortPair foo_host_port;
384  GURL cross_site_url;
385
386  // Setup the server to allow serving separate sites, so we can perform
387  // cross-process navigation.
388  host_resolver()->AddRule("*", "127.0.0.1");
389  ASSERT_TRUE(test_server()->Start());
390
391  foo_host_port = test_server()->host_port_pair();
392  foo_host_port.set_host(foo_com);
393
394  GURL initial_url(test_server()->GetURL("/title1.html"));
395
396  cross_site_url = test_server()->GetURL("/title2.html");
397  replace_host.SetHostStr(foo_com);
398  cross_site_url = cross_site_url.ReplaceComponents(replace_host);
399
400  // Navigate to the initial URL and capture the RenderFrameHost for later
401  // comparison.
402  NavigateToURL(shell(), initial_url);
403  RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
404
405  // Install the observer and navigate cross-site.
406  RenderFrameCreatedObserver observer(shell());
407  NavigateToURL(shell(), cross_site_url);
408
409  // The observer should've seen a RenderFrameCreated call for the new frame
410  // and not the old one.
411  EXPECT_NE(observer.last_rfh(), orig_rfh);
412  EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
413}
414
415IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
416                       LoadingStateChangedForSameDocumentNavigation) {
417  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
418  scoped_ptr<LoadingStateChangedDelegate> delegate(
419      new LoadingStateChangedDelegate());
420  shell()->web_contents()->SetDelegate(delegate.get());
421
422  LoadStopNotificationObserver load_observer(
423      &shell()->web_contents()->GetController());
424  TitleWatcher title_watcher(shell()->web_contents(),
425                             base::ASCIIToUTF16("pushState"));
426  NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
427  load_observer.Wait();
428  base::string16 title = title_watcher.WaitAndGetTitle();
429  ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
430
431  // LoadingStateChanged should be called 4 times: start and stop for the
432  // initial load of push_state.html, and start and stop for the "navigation"
433  // triggered by history.pushState(). However, the start notification for the
434  // history.pushState() navigation should set to_different_document to false.
435  EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
436  EXPECT_EQ(4, delegate->loadingStateChangedCount());
437  EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
438}
439
440}  // namespace content
441
442