web_contents_impl_browsertest.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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/public/browser/load_notification_details.h"
10#include "content/public/browser/navigation_controller.h"
11#include "content/public/browser/notification_details.h"
12#include "content/public/browser/notification_observer.h"
13#include "content/public/browser/notification_types.h"
14#include "content/public/browser/render_view_host.h"
15#include "content/public/browser/render_widget_host_view.h"
16#include "content/public/browser/web_contents_observer.h"
17#include "content/public/browser/web_contents_view.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(TOOLKIT_GTK) || 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  shell->web_contents()->GetView()->SizeContents(size);
44#endif  // defined(TOOLKIT_GTK) || defined(OS_MACOSX)
45}
46
47class WebContentsImplBrowserTest : public ContentBrowserTest {
48 public:
49  WebContentsImplBrowserTest() {}
50
51 private:
52  DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest);
53};
54
55// Keeps track of data from LoadNotificationDetails so we can later verify that
56// they are correct, after the LoadNotificationDetails object is deleted.
57class LoadStopNotificationObserver : public WindowedNotificationObserver {
58 public:
59  LoadStopNotificationObserver(NavigationController* controller)
60      : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP,
61                                     Source<NavigationController>(controller)),
62        session_index_(-1),
63        controller_(NULL) {
64  }
65  virtual void Observe(int type,
66                       const NotificationSource& source,
67                       const NotificationDetails& details) OVERRIDE {
68    if (type == NOTIFICATION_LOAD_STOP) {
69      const Details<LoadNotificationDetails> load_details(details);
70      url_ = load_details->url;
71      session_index_ = load_details->session_index;
72      controller_ = load_details->controller;
73    }
74    WindowedNotificationObserver::Observe(type, source, details);
75  }
76
77  GURL url_;
78  int session_index_;
79  NavigationController* controller_;
80};
81
82// Starts a new navigation as soon as the current one commits, but does not
83// wait for it to complete.  This allows us to observe DidStopLoading while
84// a pending entry is present.
85class NavigateOnCommitObserver : public WebContentsObserver {
86 public:
87  NavigateOnCommitObserver(Shell* shell, GURL url)
88      : WebContentsObserver(shell->web_contents()),
89        shell_(shell),
90        url_(url),
91        done_(false) {
92  }
93
94  // WebContentsObserver:
95  virtual void NavigationEntryCommitted(
96      const LoadCommittedDetails& load_details) OVERRIDE {
97    if (!done_) {
98      done_ = true;
99      shell_->LoadURL(url_);
100    }
101  }
102
103  Shell* shell_;
104  GURL url_;
105  bool done_;
106};
107
108class RenderViewSizeDelegate : public WebContentsDelegate {
109 public:
110  void set_size_insets(const gfx::Size& size_insets) {
111    size_insets_ = size_insets;
112  }
113
114  // WebContentsDelegate:
115  virtual gfx::Size GetSizeForNewRenderView(
116      const WebContents* web_contents) const OVERRIDE {
117    gfx::Size size(web_contents->GetView()->GetContainerSize());
118    size.Enlarge(size_insets_.width(), size_insets_.height());
119    return size;
120  }
121
122 private:
123  gfx::Size size_insets_;
124};
125
126class RenderViewSizeObserver : public WebContentsObserver {
127 public:
128  RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size)
129      : WebContentsObserver(shell->web_contents()),
130        shell_(shell),
131        wcv_new_size_(wcv_new_size) {
132  }
133
134  // WebContentsObserver:
135  virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE {
136    rwhv_create_size_ = rvh->GetView()->GetViewBounds().size();
137  }
138
139  virtual void DidStartNavigationToPendingEntry(
140      const GURL& url,
141      NavigationController::ReloadType reload_type) OVERRIDE {
142    ResizeWebContentsView(shell_, wcv_new_size_, false);
143  }
144
145  gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
146
147 private:
148  Shell* shell_;  // Weak ptr.
149  gfx::Size wcv_new_size_;
150  gfx::Size rwhv_create_size_;
151};
152
153class LoadingStateChangedDelegate : public WebContentsDelegate {
154 public:
155  LoadingStateChangedDelegate()
156      : loadingStateChangedCount_(0)
157      , loadingStateToDifferentDocumentCount_(0) {
158  }
159
160  // WebContentsDelgate:
161  virtual void LoadingStateChanged(WebContents* contents,
162                                   bool to_different_document) OVERRIDE {
163      loadingStateChangedCount_++;
164      if (to_different_document)
165        loadingStateToDifferentDocumentCount_++;
166  }
167
168  int loadingStateChangedCount() const { return loadingStateChangedCount_; }
169  int loadingStateToDifferentDocumentCount() const {
170    return loadingStateToDifferentDocumentCount_;
171  }
172
173 private:
174  int loadingStateChangedCount_;
175  int loadingStateToDifferentDocumentCount_;
176};
177
178// See: http://crbug.com/298193
179#if defined(OS_WIN)
180#define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
181#else
182#define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
183#endif
184
185// Test that DidStopLoading includes the correct URL in the details.
186IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
187                       MAYBE_DidStopLoadingDetails) {
188  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
189
190  LoadStopNotificationObserver load_observer(
191      &shell()->web_contents()->GetController());
192  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
193  load_observer.Wait();
194
195  EXPECT_EQ("/title1.html", load_observer.url_.path());
196  EXPECT_EQ(0, load_observer.session_index_);
197  EXPECT_EQ(&shell()->web_contents()->GetController(),
198            load_observer.controller_);
199}
200
201// See: http://crbug.com/298193
202#if defined(OS_WIN)
203#define MAYBE_DidStopLoadingDetailsWithPending \
204  DISABLED_DidStopLoadingDetailsWithPending
205#else
206#define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
207#endif
208
209// Test that DidStopLoading includes the correct URL in the details when a
210// pending entry is present.
211IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
212                       MAYBE_DidStopLoadingDetailsWithPending) {
213  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
214
215  // Listen for the first load to stop.
216  LoadStopNotificationObserver load_observer(
217      &shell()->web_contents()->GetController());
218  // Start a new pending navigation as soon as the first load commits.
219  // We will hear a DidStopLoading from the first load as the new load
220  // is started.
221  NavigateOnCommitObserver commit_observer(
222      shell(), embedded_test_server()->GetURL("/title2.html"));
223  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
224  load_observer.Wait();
225
226  EXPECT_EQ("/title1.html", load_observer.url_.path());
227  EXPECT_EQ(0, load_observer.session_index_);
228  EXPECT_EQ(&shell()->web_contents()->GetController(),
229            load_observer.controller_);
230}
231// Test that a renderer-initiated navigation to an invalid URL does not leave
232// around a pending entry that could be used in a URL spoof.  We test this in
233// a browser test because our unit test framework incorrectly calls
234// DidStartProvisionalLoadForFrame for in-page navigations.
235// See http://crbug.com/280512.
236IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
237                       ClearNonVisiblePendingOnFail) {
238  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
239
240  NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
241
242  // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
243  LoadStopNotificationObserver load_observer1(
244      &shell()->web_contents()->GetController());
245  ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
246                            "window.location.href=\"nonexistent:12121\";"));
247  load_observer1.Wait();
248  EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
249
250  LoadStopNotificationObserver load_observer2(
251      &shell()->web_contents()->GetController());
252  ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
253                            "window.location.href=\"#foo\";"));
254  load_observer2.Wait();
255  EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
256            shell()->web_contents()->GetVisibleURL());
257}
258
259// TODO(shrikant): enable this for Windows when issue with
260// force-compositing-mode is resolved (http://crbug.com/281726).
261// For TOOLKIT_GTK failure, see http://crbug.com/351234.
262// Also crashes under ThreadSanitizer, http://crbug.com/356758.
263#if defined(OS_WIN) || defined(OS_ANDROID) || defined(TOOLKIT_GTK) \
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()->GetView()->GetContainerSize(),
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()->GetView()->GetContainerSize());
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()->GetView()->GetContainerSize());
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    LOG(ERROR) << "RFCreated: " << render_frame_host;
367    last_rfh_ = render_frame_host;
368  }
369
370  RenderFrameHost* last_rfh() const { return last_rfh_; }
371
372 private:
373  RenderFrameHost* last_rfh_;
374
375  DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
376};
377
378// Test that creation of new RenderFrameHost objects sends the correct object
379// to the WebContentObservers. See http://crbug.com/347339.
380IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
381                       RenderFrameCreatedCorrectProcessForObservers) {
382  std::string foo_com("foo.com");
383  GURL::Replacements replace_host;
384  net::HostPortPair foo_host_port;
385  GURL cross_site_url;
386
387  // Setup the server to allow serving separate sites, so we can perform
388  // cross-process navigation.
389  host_resolver()->AddRule("*", "127.0.0.1");
390  ASSERT_TRUE(test_server()->Start());
391
392  foo_host_port = test_server()->host_port_pair();
393  foo_host_port.set_host(foo_com);
394
395  GURL initial_url(test_server()->GetURL("/title1.html"));
396
397  cross_site_url = test_server()->GetURL("/title2.html");
398  replace_host.SetHostStr(foo_com);
399  cross_site_url = cross_site_url.ReplaceComponents(replace_host);
400
401  // Navigate to the initial URL and capture the RenderFrameHost for later
402  // comparison.
403  NavigateToURL(shell(), initial_url);
404  RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
405
406  // Install the observer and navigate cross-site.
407  RenderFrameCreatedObserver observer(shell());
408  NavigateToURL(shell(), cross_site_url);
409
410  // The observer should've seen a RenderFrameCreated call for the new frame
411  // and not the old one.
412  EXPECT_NE(observer.last_rfh(), orig_rfh);
413  EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
414}
415
416IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
417                       LoadingStateChangedForSameDocumentNavigation) {
418  ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
419  scoped_ptr<LoadingStateChangedDelegate> delegate(
420      new LoadingStateChangedDelegate());
421  shell()->web_contents()->SetDelegate(delegate.get());
422
423  LoadStopNotificationObserver load_observer(
424      &shell()->web_contents()->GetController());
425  TitleWatcher title_watcher(shell()->web_contents(),
426                             base::ASCIIToUTF16("pushState"));
427  NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
428  load_observer.Wait();
429  base::string16 title = title_watcher.WaitAndGetTitle();
430  ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
431
432  // LoadingStateChanged should be called 4 times: start and stop for the
433  // initial load of push_state.html, and start and stop for the "navigation"
434  // triggered by history.pushState(). However, the start notification for the
435  // history.pushState() navigation should set to_different_document to false.
436  EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
437  EXPECT_EQ(4, delegate->loadingStateChangedCount());
438  EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
439}
440
441}  // namespace content
442