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 // 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().
    271 IN_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 
    341 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) {
    342   // Navigate to a page with frames and grab a subframe's FrameTreeNode ID.
    343   ASSERT_TRUE(test_server()->Start());
    344   NavigateToURL(shell(),
    345                 test_server()->GetURL("files/frame_tree/top.html"));
    346   WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
    347   FrameTreeNode* root = wc->GetFrameTree()->root();
    348   ASSERT_EQ(3UL, root->child_count());
    349   int64 frame_tree_node_id = root->child_at(0)->frame_tree_node_id();
    350   EXPECT_NE(-1, frame_tree_node_id);
    351 
    352   // Navigate with the subframe's FrameTreeNode ID.
    353   const GURL url(test_server()->GetURL("files/title1.html"));
    354   OpenURLParams params(url, Referrer(), frame_tree_node_id, CURRENT_TAB,
    355                        ui::PAGE_TRANSITION_LINK, true);
    356   shell()->web_contents()->OpenURL(params);
    357 
    358   // Make sure the NavigationEntry ends up with the FrameTreeNode ID.
    359   NavigationController* controller = &shell()->web_contents()->GetController();
    360   EXPECT_TRUE(controller->GetPendingEntry());
    361   EXPECT_EQ(frame_tree_node_id,
    362             NavigationEntryImpl::FromNavigationEntry(
    363                 controller->GetPendingEntry())->frame_tree_node_id());
    364 }
    365 
    366 // Observer class to track the creation of RenderFrameHost objects. It is used
    367 // in subsequent tests.
    368 class RenderFrameCreatedObserver : public WebContentsObserver {
    369  public:
    370   RenderFrameCreatedObserver(Shell* shell)
    371       : WebContentsObserver(shell->web_contents()),
    372         last_rfh_(NULL) {
    373   }
    374 
    375   virtual void RenderFrameCreated(RenderFrameHost* render_frame_host) OVERRIDE {
    376     last_rfh_ = render_frame_host;
    377   }
    378 
    379   RenderFrameHost* last_rfh() const { return last_rfh_; }
    380 
    381  private:
    382   RenderFrameHost* last_rfh_;
    383 
    384   DISALLOW_COPY_AND_ASSIGN(RenderFrameCreatedObserver);
    385 };
    386 
    387 // Test that creation of new RenderFrameHost objects sends the correct object
    388 // to the WebContentObservers. See http://crbug.com/347339.
    389 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    390                        RenderFrameCreatedCorrectProcessForObservers) {
    391   std::string foo_com("foo.com");
    392   GURL::Replacements replace_host;
    393   net::HostPortPair foo_host_port;
    394   GURL cross_site_url;
    395 
    396   // Setup the server to allow serving separate sites, so we can perform
    397   // cross-process navigation.
    398   host_resolver()->AddRule("*", "127.0.0.1");
    399   ASSERT_TRUE(test_server()->Start());
    400 
    401   foo_host_port = test_server()->host_port_pair();
    402   foo_host_port.set_host(foo_com);
    403 
    404   GURL initial_url(test_server()->GetURL("/title1.html"));
    405 
    406   cross_site_url = test_server()->GetURL("/title2.html");
    407   replace_host.SetHostStr(foo_com);
    408   cross_site_url = cross_site_url.ReplaceComponents(replace_host);
    409 
    410   // Navigate to the initial URL and capture the RenderFrameHost for later
    411   // comparison.
    412   NavigateToURL(shell(), initial_url);
    413   RenderFrameHost* orig_rfh = shell()->web_contents()->GetMainFrame();
    414 
    415   // Install the observer and navigate cross-site.
    416   RenderFrameCreatedObserver observer(shell());
    417   NavigateToURL(shell(), cross_site_url);
    418 
    419   // The observer should've seen a RenderFrameCreated call for the new frame
    420   // and not the old one.
    421   EXPECT_NE(observer.last_rfh(), orig_rfh);
    422   EXPECT_EQ(observer.last_rfh(), shell()->web_contents()->GetMainFrame());
    423 }
    424 
    425 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    426                        LoadingStateChangedForSameDocumentNavigation) {
    427   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    428   scoped_ptr<LoadingStateChangedDelegate> delegate(
    429       new LoadingStateChangedDelegate());
    430   shell()->web_contents()->SetDelegate(delegate.get());
    431 
    432   LoadStopNotificationObserver load_observer(
    433       &shell()->web_contents()->GetController());
    434   TitleWatcher title_watcher(shell()->web_contents(),
    435                              base::ASCIIToUTF16("pushState"));
    436   NavigateToURL(shell(), embedded_test_server()->GetURL("/push_state.html"));
    437   load_observer.Wait();
    438   base::string16 title = title_watcher.WaitAndGetTitle();
    439   ASSERT_EQ(title, base::ASCIIToUTF16("pushState"));
    440 
    441   // LoadingStateChanged should be called 4 times: start and stop for the
    442   // initial load of push_state.html, and start and stop for the "navigation"
    443   // triggered by history.pushState(). However, the start notification for the
    444   // history.pushState() navigation should set to_different_document to false.
    445   EXPECT_EQ("pushState", shell()->web_contents()->GetURL().ref());
    446   EXPECT_EQ(4, delegate->loadingStateChangedCount());
    447   EXPECT_EQ(3, delegate->loadingStateToDifferentDocumentCount());
    448 }
    449 
    450 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    451                        RenderViewCreatedForChildWindow) {
    452   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    453 
    454   NavigateToURL(shell(),
    455                 embedded_test_server()->GetURL("/title1.html"));
    456 
    457   WebContentsAddedObserver new_web_contents_observer;
    458   ASSERT_TRUE(ExecuteScript(shell()->web_contents(),
    459                             "var a = document.createElement('a');"
    460                             "a.href='./title2.html';"
    461                             "a.target = '_blank';"
    462                             "document.body.appendChild(a);"
    463                             "a.click();"));
    464   WebContents* new_web_contents = new_web_contents_observer.GetWebContents();
    465   WaitForLoadStop(new_web_contents);
    466   EXPECT_TRUE(new_web_contents_observer.RenderViewCreatedCalled());
    467 }
    468 
    469 struct LoadProgressDelegateAndObserver : public WebContentsDelegate,
    470                                          public WebContentsObserver {
    471   LoadProgressDelegateAndObserver(Shell* shell)
    472       : WebContentsObserver(shell->web_contents()),
    473         did_start_loading(false),
    474         did_stop_loading(false) {
    475     web_contents()->SetDelegate(this);
    476   }
    477 
    478   // WebContentsDelegate:
    479   virtual void LoadProgressChanged(WebContents* source,
    480                                    double progress) OVERRIDE {
    481     EXPECT_TRUE(did_start_loading);
    482     EXPECT_FALSE(did_stop_loading);
    483     progresses.push_back(progress);
    484   }
    485 
    486   // WebContentsObserver:
    487   virtual void DidStartLoading(RenderViewHost* render_view_host) OVERRIDE {
    488     EXPECT_FALSE(did_start_loading);
    489     EXPECT_EQ(0U, progresses.size());
    490     EXPECT_FALSE(did_stop_loading);
    491     did_start_loading = true;
    492   }
    493 
    494   virtual void DidStopLoading(RenderViewHost* render_view_host) OVERRIDE {
    495     EXPECT_TRUE(did_start_loading);
    496     EXPECT_GE(progresses.size(), 1U);
    497     EXPECT_FALSE(did_stop_loading);
    498     did_stop_loading = true;
    499   }
    500 
    501   bool did_start_loading;
    502   std::vector<double> progresses;
    503   bool did_stop_loading;
    504 };
    505 
    506 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, LoadProgress) {
    507   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    508   scoped_ptr<LoadProgressDelegateAndObserver> delegate(
    509       new LoadProgressDelegateAndObserver(shell()));
    510 
    511   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    512 
    513   const std::vector<double>& progresses = delegate->progresses;
    514   // All updates should be in order ...
    515   if (std::adjacent_find(progresses.begin(),
    516                          progresses.end(),
    517                          std::greater<double>()) != progresses.end()) {
    518     ADD_FAILURE() << "Progress values should be in order: "
    519                   << ::testing::PrintToString(progresses);
    520   }
    521 
    522   // ... and the last one should be 1.0, meaning complete.
    523   ASSERT_GE(progresses.size(), 1U)
    524       << "There should be at least one progress update";
    525   EXPECT_EQ(1.0, *progresses.rbegin());
    526 }
    527 
    528 struct FirstVisuallyNonEmptyPaintObserver : public WebContentsObserver {
    529   FirstVisuallyNonEmptyPaintObserver(Shell* shell)
    530       : WebContentsObserver(shell->web_contents()),
    531         did_fist_visually_non_empty_paint_(false) {}
    532 
    533   virtual void DidFirstVisuallyNonEmptyPaint() OVERRIDE {
    534     did_fist_visually_non_empty_paint_ = true;
    535     on_did_first_visually_non_empty_paint_.Run();
    536   }
    537 
    538   void WaitForDidFirstVisuallyNonEmptyPaint() {
    539     if (did_fist_visually_non_empty_paint_)
    540       return;
    541     base::RunLoop run_loop;
    542     on_did_first_visually_non_empty_paint_ = run_loop.QuitClosure();
    543     run_loop.Run();
    544   }
    545 
    546   base::Closure on_did_first_visually_non_empty_paint_;
    547   bool did_fist_visually_non_empty_paint_;
    548 };
    549 
    550 // See: http://crbug.com/395664
    551 #if defined(OS_ANDROID)
    552 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
    553 #else
    554 // http://crbug.com/398471
    555 #define MAYBE_FirstVisuallyNonEmptyPaint DISABLED_FirstVisuallyNonEmptyPaint
    556 #endif
    557 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
    558                        MAYBE_FirstVisuallyNonEmptyPaint) {
    559   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
    560   scoped_ptr<FirstVisuallyNonEmptyPaintObserver> observer(
    561       new FirstVisuallyNonEmptyPaintObserver(shell()));
    562 
    563   NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
    564 
    565   observer->WaitForDidFirstVisuallyNonEmptyPaint();
    566   ASSERT_TRUE(observer->did_fist_visually_non_empty_paint_);
    567 }
    568 
    569 }  // namespace content
    570 
    571