Home | History | Annotate | Download | only in web_contents
      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 
     27 namespace content {
     28 
     29 void 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 
     48 class 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.
     58 class 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.
     86 class 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_->Stop();
    101       shell_->LoadURL(url_);
    102     }
    103   }
    104 
    105   Shell* shell_;
    106   GURL url_;
    107   bool done_;
    108 };
    109 
    110 class RenderViewSizeDelegate : public WebContentsDelegate {
    111  public:
    112   void set_size_insets(const gfx::Size& size_insets) {
    113     size_insets_ = size_insets;
    114   }
    115 
    116   // WebContentsDelegate:
    117   virtual gfx::Size GetSizeForNewRenderView(
    118       WebContents* web_contents) const OVERRIDE {
    119     gfx::Size size(web_contents->GetContainerBounds().size());
    120     size.Enlarge(size_insets_.width(), size_insets_.height());
    121     return size;
    122   }
    123 
    124  private:
    125   gfx::Size size_insets_;
    126 };
    127 
    128 class RenderViewSizeObserver : public WebContentsObserver {
    129  public:
    130   RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size)
    131       : WebContentsObserver(shell->web_contents()),
    132         shell_(shell),
    133         wcv_new_size_(wcv_new_size) {
    134   }
    135 
    136   // WebContentsObserver:
    137   virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE {
    138     rwhv_create_size_ = rvh->GetView()->GetViewBounds().size();
    139   }
    140 
    141   virtual void DidStartNavigationToPendingEntry(
    142       const GURL& url,
    143       NavigationController::ReloadType reload_type) OVERRIDE {
    144     ResizeWebContentsView(shell_, wcv_new_size_, false);
    145   }
    146 
    147   gfx::Size rwhv_create_size() const { return rwhv_create_size_; }
    148 
    149  private:
    150   Shell* shell_;  // Weak ptr.
    151   gfx::Size wcv_new_size_;
    152   gfx::Size rwhv_create_size_;
    153 };
    154 
    155 class LoadingStateChangedDelegate : public WebContentsDelegate {
    156  public:
    157   LoadingStateChangedDelegate()
    158       : loadingStateChangedCount_(0)
    159       , loadingStateToDifferentDocumentCount_(0) {
    160   }
    161 
    162   // WebContentsDelegate:
    163   virtual void LoadingStateChanged(WebContents* contents,
    164                                    bool to_different_document) OVERRIDE {
    165       loadingStateChangedCount_++;
    166       if (to_different_document)
    167         loadingStateToDifferentDocumentCount_++;
    168   }
    169 
    170   int loadingStateChangedCount() const { return loadingStateChangedCount_; }
    171   int loadingStateToDifferentDocumentCount() const {
    172     return loadingStateToDifferentDocumentCount_;
    173   }
    174 
    175  private:
    176   int loadingStateChangedCount_;
    177   int loadingStateToDifferentDocumentCount_;
    178 };
    179 
    180 // See: http://crbug.com/298193
    181 #if defined(OS_WIN)
    182 #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
    183 #else
    184 #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
    185 #endif
    186 
    187 // Test that DidStopLoading includes the correct URL in the details.
    188 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    189                        MAYBE_DidStopLoadingDetails) {
    190   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    191 
    192   LoadStopNotificationObserver load_observer(
    193       &shell()->web_contents()->GetController());
    194   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    195   load_observer.Wait();
    196 
    197   EXPECT_EQ("/title1.html", load_observer.url_.path());
    198   EXPECT_EQ(0, load_observer.session_index_);
    199   EXPECT_EQ(&shell()->web_contents()->GetController(),
    200             load_observer.controller_);
    201 }
    202 
    203 // See: http://crbug.com/298193
    204 #if defined(OS_WIN)
    205 #define MAYBE_DidStopLoadingDetailsWithPending \
    206   DISABLED_DidStopLoadingDetailsWithPending
    207 #else
    208 #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
    209 #endif
    210 
    211 // Test that DidStopLoading includes the correct URL in the details when a
    212 // pending entry is present.
    213 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    214                        MAYBE_DidStopLoadingDetailsWithPending) {
    215   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    216   GURL url("data:text/html,<div>test</div>");
    217 
    218   // Listen for the first load to stop.
    219   LoadStopNotificationObserver load_observer(
    220       &shell()->web_contents()->GetController());
    221   // Start a new pending navigation as soon as the first load commits.
    222   // We will hear a DidStopLoading from the first load as the new load
    223   // is started.
    224   NavigateOnCommitObserver commit_observer(
    225       shell(), embedded_test_server()->GetURL("/title2.html"));
    226   NavigateToURL(shell(), url);
    227   load_observer.Wait();
    228 
    229   EXPECT_EQ(url, load_observer.url_);
    230   EXPECT_EQ(0, load_observer.session_index_);
    231   EXPECT_EQ(&shell()->web_contents()->GetController(),
    232             load_observer.controller_);
    233 }
    234 // Test that a renderer-initiated navigation to an invalid URL does not leave
    235 // around a pending entry that could be used in a URL spoof.  We test this in
    236 // a browser test because our unit test framework incorrectly calls
    237 // DidStartProvisionalLoadForFrame for in-page navigations.
    238 // See http://crbug.com/280512.
    239 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    240                        ClearNonVisiblePendingOnFail) {
    241   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    242 
    243   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    244 
    245   // Navigate to an invalid URL and make sure it doesn't leave a pending entry.
    246   LoadStopNotificationObserver load_observer1(
    247       &shell()->web_contents()->GetController());
    248   ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
    249                             "window.location.href=\"nonexistent:12121\";"));
    250   load_observer1.Wait();
    251   EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry());
    252 
    253   LoadStopNotificationObserver load_observer2(
    254       &shell()->web_contents()->GetController());
    255   ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
    256                             "window.location.href=\"#foo\";"));
    257   load_observer2.Wait();
    258   EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"),
    259             shell()->web_contents()->GetVisibleURL());
    260 }
    261 
    262 // TODO(shrikant): enable this for Windows when issue with
    263 // force-compositing-mode is resolved (http://crbug.com/281726).
    264 // Also crashes under ThreadSanitizer, http://crbug.com/356758.
    265 #if defined(OS_WIN) || defined(OS_ANDROID) \
    266     || defined(THREAD_SANITIZER)
    267 #define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView
    268 #else
    269 #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView
    270 #endif
    271 // Test that RenderViewHost is created and updated at the size specified by
    272 // WebContentsDelegate::GetSizeForNewRenderView().
    273 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    274                        MAYBE_GetSizeForNewRenderView) {
    275   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    276   // Create a new server with a different site.
    277   net::SpawnedTestServer https_server(
    278       net::SpawnedTestServer::TYPE_HTTPS,
    279       net::SpawnedTestServer::kLocalhost,
    280       base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    281   ASSERT_TRUE(https_server.Start());
    282 
    283   scoped_ptr<RenderViewSizeDelegate> delegate(new RenderViewSizeDelegate());
    284   shell()->web_contents()->SetDelegate(delegate.get());
    285   ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get());
    286 
    287   // When no size is set, RenderWidgetHostView adopts the size of
    288   // WebContentsView.
    289   NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"));
    290   EXPECT_EQ(shell()->web_contents()->GetContainerBounds().size(),
    291             shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
    292                 size());
    293 
    294   // When a size is set, RenderWidgetHostView and WebContentsView honor this
    295   // size.
    296   gfx::Size size(300, 300);
    297   gfx::Size size_insets(10, 15);
    298   ResizeWebContentsView(shell(), size, true);
    299   delegate->set_size_insets(size_insets);
    300   NavigateToURL(shell(), https_server.GetURL("/"));
    301   size.Enlarge(size_insets.width(), size_insets.height());
    302   EXPECT_EQ(size,
    303             shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds().
    304                 size());
    305   // The web_contents size is set by the embedder, and should not depend on the
    306   // rwhv size. The behavior is correct on OSX, but incorrect on other
    307   // platforms.
    308   gfx::Size exp_wcv_size(300, 300);
    309 #if !defined(OS_MACOSX)
    310   exp_wcv_size.Enlarge(size_insets.width(), size_insets.height());
    311 #endif
    312 
    313   EXPECT_EQ(exp_wcv_size,
    314             shell()->web_contents()->GetContainerBounds().size());
    315 
    316   // If WebContentsView is resized after RenderWidgetHostView is created but
    317   // before pending navigation entry is committed, both RenderWidgetHostView and
    318   // WebContentsView use the new size of WebContentsView.
    319   gfx::Size init_size(200, 200);
    320   gfx::Size new_size(100, 100);
    321   size_insets = gfx::Size(20, 30);
    322   ResizeWebContentsView(shell(), init_size, true);
    323   delegate->set_size_insets(size_insets);
    324   RenderViewSizeObserver observer(shell(), new_size);
    325   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    326   // RenderWidgetHostView is created at specified size.
    327   init_size.Enlarge(size_insets.width(), size_insets.height());
    328   EXPECT_EQ(init_size, observer.rwhv_create_size());
    329 
    330 // Once again, the behavior is correct on OSX. The embedder explicitly sets
    331 // the size to (100,100) during navigation. Both the wcv and the rwhv should
    332 // take on that size.
    333 #if !defined(OS_MACOSX)
    334   new_size.Enlarge(size_insets.width(), size_insets.height());
    335 #endif
    336   gfx::Size actual_size = shell()->web_contents()->GetRenderWidgetHostView()->
    337       GetViewBounds().size();
    338 
    339   EXPECT_EQ(new_size, actual_size);
    340   EXPECT_EQ(new_size, shell()->web_contents()->GetContainerBounds().size());
    341 }
    342 
    343 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
    344 
    345   // Navigate with FrameTreeNode ID 4.
    346   const GURL url("http://foo");
    347   OpenURLParams params(url, Referrer(), 4, CURRENT_TAB, PAGE_TRANSITION_LINK,
    348                        true);
    349   shell()->web_contents()->OpenURL(params);
    350 
    351   // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
    352   NavigationController* controller = &shell()->web_contents()->GetController();
    353   EXPECT_TRUE(controller->GetPendingEntry());
    354   EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry(
    355                 controller->GetPendingEntry())->frame_tree_node_id());
    356 }
    357 
    358 // Observer class to track the creation of RenderFrameHost objects. It is used
    359 // in subsequent tests.
    360 class RenderFrameCreatedObserver : public WebContentsObserver {
    361  public:
    362   RenderFrameCreatedObserver(Shell* shell)
    363       : WebContentsObserver(shell->web_contents()),
    364         last_rfh_(NULL) {
    365   }
    366 
    367   virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE {
    368     last_rfh_ = render_frame_host;
    369   }
    370 
    371   RenderFrameHost* last_rfh() const { return last_rfh_; }
    372 
    373  private:
    374   RenderFrameHost* last_rfh_;
    375 
    376   DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
    377 };
    378 
    379 // Test that creation of new RenderFrameHost objects sends the correct object
    380 // to the WebContentObservers. See http://crbug.com/347339.
    381 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    382                        RenderFrameCreatedCorrectProcessForObservers) {
    383   std::string foo_com("foo.com");
    384   GURL::Replacements replace_host;
    385   net::HostPortPair foo_host_port;
    386   GURL cross_site_url;
    387 
    388   // Setup the server to allow serving separate sites, so we can perform
    389   // cross-process navigation.
    390   host_resolver()->AddRule("*", "127.0.0.1");
    391   ASSERT_TRUE(test_server()->Start());
    392 
    393   foo_host_port = test_server()->host_port_pair();
    394   foo_host_port.set_host(foo_com);
    395 
    396   GURL initial_url(test_server()->GetURL("/title1.html"));
    397 
    398   cross_site_url = test_server()->GetURL("/title2.html");
    399   replace_host.SetHostStr(foo_com);
    400   cross_site_url = cross_site_url.ReplaceComponents(replace_host);
    401 
    402   // Navigate to the initial URL and capture the RenderFrameHost for later
    403   // comparison.
    404   NavigateToURL(shell(), initial_url);
    405   RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
    406 
    407   // Install the observer and navigate cross-site.
    408   RenderFrameCreatedObserver observer(shell());
    409   NavigateToURL(shell(), cross_site_url);
    410 
    411   // The observer should've seen a RenderFrameCreated call for the new frame
    412   // and not the old one.
    413   EXPECT_NE(observer.last_rfh(), orig_rfh);
    414   EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
    415 }
    416 
    417 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    418                        LoadingStateChangedForSameDocumentNavigation) {
    419   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    420   scoped_ptr<LoadingStateChangedDelegate> delegate(
    421       new LoadingStateChangedDelegate());
    422   shell()->web_contents()->SetDelegate(delegate.get());
    423 
    424   LoadStopNotificationObserver load_observer(
    425       &shell()->web_contents()->GetController());
    426   TitleWatcher title_watcher(shell()->web_contents(),
    427                              base::ASCIIToUTF16("pushState"));
    428   NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
    429   load_observer.Wait();
    430   base::string16 title = title_watcher.WaitAndGetTitle();
    431   ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
    432 
    433   // LoadingStateChanged should be called 4 times: start and stop for the
    434   // initial load of push_state.html, and start and stop for the "navigation"
    435   // triggered by history.pushState(). However, the start notification for the
    436   // history.pushState() navigation should set to_different_document to false.
    437   EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
    438   EXPECT_EQ(4, delegate->loadingStateChangedCount());
    439   EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
    440 }
    441 
    442 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    443                        RenderViewCreatedForChildWindow) {
    444   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    445 
    446   NavigateToURL(shell(),
    447                 embedded_test_server()->GetURL("/title1.html"));
    448 
    449   WebContentsAddedObserver new_web_contents_observer;
    450   ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
    451                             "var a = document.createElement('a');"
    452                             "a.href='./title2.html';"
    453                             "a.target = '_blank';"
    454                             "document.body.appendChild(a);"
    455                             "a.click();"));
    456   WebContents* new_web_contents = new_web_contents_observer.GetWebContents();
    457   WaitForLoadStop(new_web_contents);
    458   EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled());
    459 }
    460 
    461 struct LoadProgressDelegateAndObserver : public WebContentsDelegate,
    462                                          public WebContentsObserver {
    463   LoadProgressDelegateAndObserver(Shell* shell)
    464       : WebContentsObserver(shell->web_contents()),
    465         did_start_loading(false),
    466         did_stop_loading(false) {
    467     web_contents()->SetDelegate(this);
    468   }
    469 
    470   // WebContentsDelegate:
    471   virtual void LoadProgressChanged(WebContents* source,
    472                                    double progress) OVERRIDE {
    473     EXPECT_TRUE(did_start_loading);
    474     EXPECT_FALSE(did_stop_loading);
    475     progresses.push_back(progress);
    476   }
    477 
    478   // WebContentsObserver:
    479   virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE {
    480     EXPECT_FALSE(did_start_loading);
    481     EXPECT_EQ(0U, progresses.size());
    482     EXPECT_FALSE(did_stop_loading);
    483     did_start_loading = true;
    484   }
    485 
    486   virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE {
    487     EXPECT_TRUE(did_start_loading);
    488     EXPECT_GE(progresses.size(), 1U);
    489     EXPECT_FALSE(did_stop_loading);
    490     did_stop_loading = true;
    491   }
    492 
    493   bool did_start_loading;
    494   std::vector<double> progresses;
    495   bool did_stop_loading;
    496 };
    497 
    498 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) {
    499   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    500   scoped_ptr<LoadProgressDelegateAndObserver> delegate(
    501       new LoadProgressDelegateAndObserver(shell()));
    502 
    503   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    504 
    505   const std::vector<double>& progresses = delegate->progresses;
    506   // All updates should be in order ...
    507   if (std::adjacent_find(progresses.begin(),
    508                          progresses.end(),
    509                          std::greater<double>()) != progresses.end()) {
    510     ADD_FAILURE() << "Progress values should be in order: "
    511                   << ::testing::PrintToString(progresses);
    512   }
    513 
    514   // ... and the last one should be 1.0, meaning complete.
    515   ASSERT_GE(progresses.size(), 1U)
    516       << "There should be at least one progress update";
    517   EXPECT_EQ(1.0, *progresses.rbegin());
    518 }
    519 
    520 }  // namespace content
    521 
    522