// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/values.h" #include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/load_notification_details.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_view.h" #include "content/public/common/content_paths.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/content_browser_test.h" #include "content/test/content_browser_test_utils.h" #include "net/test/embedded_test_server/embedded_test_server.h" namespace content { void ResizeWebContentsView(Shell* shell, const gfx::Size& size, bool set_start_page) { // Shell::SizeTo is not implemented on Aura; WebContentsView::SizeContents // works on Win and ChromeOS but not Linux - we need to resize the shell // window on Linux because if we don't, the next layout of the unchanged shell // window will resize WebContentsView back to the previous size. // The cleaner and shorter SizeContents is preferred as more platforms convert // to Aura. #if defined(TOOLKIT_GTK) || defined(OS_MACOSX) shell->SizeTo(size); // If |set_start_page| is true, start with blank page to make sure resize // takes effect. if (set_start_page) NavigateToURL(shell, GURL("about://blank")); #else shell->web_contents()->GetView()->SizeContents(size); #endif // defined(TOOLKIT_GTK) || defined(OS_MACOSX) } class WebContentsImplBrowserTest : public ContentBrowserTest { public: WebContentsImplBrowserTest() {} private: DISALLOW_COPY_AND_ASSIGN(WebContentsImplBrowserTest); }; // Keeps track of data from LoadNotificationDetails so we can later verify that // they are correct, after the LoadNotificationDetails object is deleted. class LoadStopNotificationObserver : public WindowedNotificationObserver { public: LoadStopNotificationObserver(NavigationController* controller) : WindowedNotificationObserver(NOTIFICATION_LOAD_STOP, Source(controller)), session_index_(-1), controller_(NULL) { } virtual void Observe(int type, const NotificationSource& source, const NotificationDetails& details) OVERRIDE { if (type == NOTIFICATION_LOAD_STOP) { const Details load_details(details); url_ = load_details->url; session_index_ = load_details->session_index; controller_ = load_details->controller; } WindowedNotificationObserver::Observe(type, source, details); } GURL url_; int session_index_; NavigationController* controller_; }; // Starts a new navigation as soon as the current one commits, but does not // wait for it to complete. This allows us to observe DidStopLoading while // a pending entry is present. class NavigateOnCommitObserver : public WebContentsObserver { public: NavigateOnCommitObserver(Shell* shell, GURL url) : WebContentsObserver(shell->web_contents()), shell_(shell), url_(url), done_(false) { } // WebContentsObserver: virtual void NavigationEntryCommitted( const LoadCommittedDetails& load_details) OVERRIDE { if (!done_) { done_ = true; shell_->LoadURL(url_); } } Shell* shell_; GURL url_; bool done_; }; class RenderViewSizeDelegate : public WebContentsDelegate { public: void set_size_insets(const gfx::Size& size_insets) { size_insets_ = size_insets; } // WebContentsDelegate: virtual gfx::Size GetSizeForNewRenderView( const WebContents* web_contents) const OVERRIDE { gfx::Size size(web_contents->GetView()->GetContainerSize()); size.Enlarge(size_insets_.width(), size_insets_.height()); return size; } private: gfx::Size size_insets_; }; class RenderViewSizeObserver : public WebContentsObserver { public: RenderViewSizeObserver(Shell* shell, const gfx::Size& wcv_new_size) : WebContentsObserver(shell->web_contents()), shell_(shell), wcv_new_size_(wcv_new_size) { } // WebContentsObserver: virtual void RenderViewCreated(RenderViewHost* rvh) OVERRIDE { rwhv_create_size_ = rvh->GetView()->GetViewBounds().size(); } virtual void NavigateToPendingEntry( const GURL& url, NavigationController::ReloadType reload_type) OVERRIDE { ResizeWebContentsView(shell_, wcv_new_size_, false); } gfx::Size rwhv_create_size() const { return rwhv_create_size_; } private: Shell* shell_; // Weak ptr. gfx::Size wcv_new_size_; gfx::Size rwhv_create_size_; }; // See: http://crbug.com/298193 #if defined(OS_WIN) #define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails #else #define MAYBE_DidStopLoadingDetails DidStopLoadingDetails #endif // Test that DidStopLoading includes the correct URL in the details. IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, MAYBE_DidStopLoadingDetails) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); LoadStopNotificationObserver load_observer( &shell()->web_contents()->GetController()); NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); load_observer.Wait(); EXPECT_EQ("/title1.html", load_observer.url_.path()); EXPECT_EQ(0, load_observer.session_index_); EXPECT_EQ(&shell()->web_contents()->GetController(), load_observer.controller_); } // See: http://crbug.com/298193 #if defined(OS_WIN) #define MAYBE_DidStopLoadingDetailsWithPending \ DISABLED_DidStopLoadingDetailsWithPending #else #define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending #endif // Test that DidStopLoading includes the correct URL in the details when a // pending entry is present. IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, MAYBE_DidStopLoadingDetailsWithPending) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // Listen for the first load to stop. LoadStopNotificationObserver load_observer( &shell()->web_contents()->GetController()); // Start a new pending navigation as soon as the first load commits. // We will hear a DidStopLoading from the first load as the new load // is started. NavigateOnCommitObserver commit_observer( shell(), embedded_test_server()->GetURL("/title2.html")); NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); load_observer.Wait(); EXPECT_EQ("/title1.html", load_observer.url_.path()); EXPECT_EQ(0, load_observer.session_index_); EXPECT_EQ(&shell()->web_contents()->GetController(), load_observer.controller_); } // Test that a renderer-initiated navigation to an invalid URL does not leave // around a pending entry that could be used in a URL spoof. We test this in // a browser test because our unit test framework incorrectly calls // DidStartProvisionalLoadForFrame for in-page navigations. // See http://crbug.com/280512. IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, ClearNonVisiblePendingOnFail) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // Navigate to an invalid URL and make sure it doesn't leave a pending entry. LoadStopNotificationObserver load_observer1( &shell()->web_contents()->GetController()); ASSERT_TRUE(ExecuteScript(shell()->web_contents(), "window.location.href=\"nonexistent:12121\";")); load_observer1.Wait(); EXPECT_FALSE(shell()->web_contents()->GetController().GetPendingEntry()); LoadStopNotificationObserver load_observer2( &shell()->web_contents()->GetController()); ASSERT_TRUE(ExecuteScript(shell()->web_contents(), "window.location.href=\"#foo\";")); load_observer2.Wait(); EXPECT_EQ(embedded_test_server()->GetURL("/title1.html#foo"), shell()->web_contents()->GetVisibleURL()); } // TODO(sail): enable this for MAC when auto resizing of WebContentsViewCocoa is // fixed. // TODO(shrikant): enable this for Windows when issue with // force-compositing-mode is resolved (http://crbug.com/281726). #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_ANDROID) #define MAYBE_GetSizeForNewRenderView DISABLED_GetSizeForNewRenderView #else #define MAYBE_GetSizeForNewRenderView GetSizeForNewRenderView #endif // Test that RenderViewHost is created and updated at the size specified by // WebContentsDelegate::GetSizeForNewRenderView(). IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, MAYBE_GetSizeForNewRenderView) { ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // Create a new server with a different site. net::SpawnedTestServer https_server( net::SpawnedTestServer::TYPE_HTTPS, net::SpawnedTestServer::kLocalhost, base::FilePath(FILE_PATH_LITERAL("content/test/data"))); ASSERT_TRUE(https_server.Start()); scoped_ptr delegate(new RenderViewSizeDelegate()); shell()->web_contents()->SetDelegate(delegate.get()); ASSERT_TRUE(shell()->web_contents()->GetDelegate() == delegate.get()); // When no size is set, RenderWidgetHostView adopts the size of // WebContenntsView. NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")); EXPECT_EQ(shell()->web_contents()->GetView()->GetContainerSize(), shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). size()); // When a size is set, RenderWidgetHostView and WebContentsView honor this // size. gfx::Size size(300, 300); gfx::Size size_insets(-10, -15); ResizeWebContentsView(shell(), size, true); delegate->set_size_insets(size_insets); NavigateToURL(shell(), https_server.GetURL("/")); size.Enlarge(size_insets.width(), size_insets.height()); EXPECT_EQ(size, shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). size()); EXPECT_EQ(size, shell()->web_contents()->GetView()->GetContainerSize()); // If WebContentsView is resized after RenderWidgetHostView is created but // before pending navigation entry is committed, both RenderWidgetHostView and // WebContentsView use the new size of WebContentsView. gfx::Size init_size(200, 200); gfx::Size new_size(100, 100); size_insets = gfx::Size(-20, -30); ResizeWebContentsView(shell(), init_size, true); delegate->set_size_insets(size_insets); RenderViewSizeObserver observer(shell(), new_size); NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")); // RenderWidgetHostView is created at specified size. init_size.Enlarge(size_insets.width(), size_insets.height()); EXPECT_EQ(init_size, observer.rwhv_create_size()); // RenderViewSizeObserver resizes WebContentsView in NavigateToPendingEntry, // so both WebContentsView and RenderWidgetHostView adopt this new size. new_size.Enlarge(size_insets.width(), size_insets.height()); EXPECT_EQ(new_size, shell()->web_contents()->GetRenderWidgetHostView()->GetViewBounds(). size()); EXPECT_EQ(new_size, shell()->web_contents()->GetView()->GetContainerSize()); } IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, OpenURLSubframe) { // Navigate with source_frame_id 3, FrameTreeNode ID 4. const GURL url("http://foo"); OpenURLParams params(url, Referrer(), 3, 4, CURRENT_TAB, PAGE_TRANSITION_LINK, true); shell()->web_contents()->OpenURL(params); // Make sure the NavigationEntry ends up with the FrameTreeNode ID. NavigationController* controller = &shell()->web_contents()->GetController(); EXPECT_TRUE(controller->GetPendingEntry()); EXPECT_EQ(4, NavigationEntryImpl::FromNavigationEntry( controller->GetPendingEntry())->frame_tree_node_id()); } } // namespace content