// Copyright (c) 2012 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 #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" #include "base/sys_info.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/command_updater.h" #include "chrome/browser/content_settings/host_content_settings_map.h" #include "chrome/browser/defaults.h" #include "chrome/browser/devtools/devtools_window_testing.h" #include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/first_run/first_run.h" #include "chrome/browser/lifetime/application_lifetime.h" #include "chrome/browser/prefs/incognito_mode_prefs.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/search/search.h" #include "chrome/browser/sessions/session_backend.h" #include "chrome/browser/sessions/session_service_factory.h" #include "chrome/browser/translate/chrome_translate_client.h" #include "chrome/browser/translate/cld_data_harness.h" #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog.h" #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h" #include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_command_controller.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_iterator.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_ui_prefs.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/host_desktop.h" #include "chrome/browser/ui/startup/startup_browser_creator.h" #include "chrome/browser/ui/startup/startup_browser_creator_impl.h" #include "chrome/browser/ui/tabs/pinned_tab_codec.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "components/translate/core/browser/language_state.h" #include "components/translate/core/common/language_detection_details.h" #include "content/public/browser/favicon_status.h" #include "content/public/browser/host_zoom_map.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/interstitial_page_delegate.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/resource_context.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/common/frame_navigate_params.h" #include "content/public/common/page_transition_types.h" #include "content/public/common/renderer_preferences.h" #include "content/public/common/url_constants.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/uninstall_reason.h" #include "extensions/common/extension.h" #include "extensions/common/extension_set.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "net/dns/mock_host_resolver.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "ui/base/l10n/l10n_util.h" #if defined(OS_MACOSX) #include "base/mac/mac_util.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "chrome/browser/ui/cocoa/run_loop_testing.h" #endif #if defined(OS_WIN) #include "base/i18n/rtl.h" #include "chrome/browser/browser_process.h" #endif using base::ASCIIToUTF16; using content::InterstitialPage; using content::HostZoomMap; using content::NavigationController; using content::NavigationEntry; using content::OpenURLParams; using content::Referrer; using content::WebContents; using content::WebContentsObserver; using extensions::Extension; namespace { const char* kBeforeUnloadHTML = "beforeunload" "" ""; const char* kOpenNewBeforeUnloadPage = "w=window.open(); w.onbeforeunload=function(e){return 'foo'};"; const base::FilePath::CharType* kBeforeUnloadFile = FILE_PATH_LITERAL("beforeunload.html"); const base::FilePath::CharType* kTitle1File = FILE_PATH_LITERAL("title1.html"); const base::FilePath::CharType* kTitle2File = FILE_PATH_LITERAL("title2.html"); const base::FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data"); // Given a page title, returns the expected window caption string. base::string16 WindowCaptionFromPageTitle(const base::string16& page_title) { #if defined(OS_MACOSX) || defined(OS_CHROMEOS) // On Mac or ChromeOS, we don't want to suffix the page title with // the application name. if (page_title.empty()) return l10n_util::GetStringUTF16(IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED); return page_title; #else if (page_title.empty()) return l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); return l10n_util::GetStringFUTF16(IDS_BROWSER_WINDOW_TITLE_FORMAT, page_title); #endif } // Returns the number of active RenderProcessHosts. int CountRenderProcessHosts() { int result = 0; for (content::RenderProcessHost::iterator i( content::RenderProcessHost::AllHostsIterator()); !i.IsAtEnd(); i.Advance()) ++result; return result; } class MockTabStripModelObserver : public TabStripModelObserver { public: MockTabStripModelObserver() : closing_count_(0) {} virtual void TabClosingAt(TabStripModel* tab_strip_model, WebContents* contents, int index) OVERRIDE { ++closing_count_; } int closing_count() const { return closing_count_; } private: int closing_count_; DISALLOW_COPY_AND_ASSIGN(MockTabStripModelObserver); }; class InterstitialObserver : public content::WebContentsObserver { public: InterstitialObserver(content::WebContents* web_contents, const base::Closure& attach_callback, const base::Closure& detach_callback) : WebContentsObserver(web_contents), attach_callback_(attach_callback), detach_callback_(detach_callback) { } virtual void DidAttachInterstitialPage() OVERRIDE { attach_callback_.Run(); } virtual void DidDetachInterstitialPage() OVERRIDE { detach_callback_.Run(); } private: base::Closure attach_callback_; base::Closure detach_callback_; DISALLOW_COPY_AND_ASSIGN(InterstitialObserver); }; // Causes the browser to swap processes on a redirect to an HTTPS URL. class TransferHttpsRedirectsContentBrowserClient : public chrome::ChromeContentBrowserClient { public: virtual bool ShouldSwapProcessesForRedirect( content::ResourceContext* resource_context, const GURL& current_url, const GURL& new_url) OVERRIDE { return new_url.SchemeIs(url::kHttpsScheme); } }; // Used by CloseWithAppMenuOpen. Invokes CloseWindow on the supplied browser. void CloseWindowCallback(Browser* browser) { chrome::CloseWindow(browser); } // Used by CloseWithAppMenuOpen. Posts a CloseWindowCallback and shows the app // menu. void RunCloseWithAppMenuCallback(Browser* browser) { // ShowAppMenu is modal under views. Schedule a task that closes the window. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&CloseWindowCallback, browser)); chrome::ShowAppMenu(browser); } // Displays "INTERSTITIAL" while the interstitial is attached. // (InterstitialPage can be used in a test directly, but there would be no way // to visually tell if it is showing or not.) class TestInterstitialPage : public content::InterstitialPageDelegate { public: TestInterstitialPage(WebContents* tab, bool new_navigation, const GURL& url) { interstitial_page_ = InterstitialPage::Create( tab, new_navigation, url , this); interstitial_page_->Show(); } virtual ~TestInterstitialPage() { } void Proceed() { interstitial_page_->Proceed(); } void DontProceed() { interstitial_page_->DontProceed(); } virtual std::string GetHTMLContents() OVERRIDE { return "

INTERSTITIAL

"; } private: InterstitialPage* interstitial_page_; // Owns us. }; class RenderViewSizeObserver : public content::WebContentsObserver { public: RenderViewSizeObserver(content::WebContents* web_contents, BrowserWindow* browser_window) : WebContentsObserver(web_contents), browser_window_(browser_window) { } void GetSizeForRenderViewHost( content::RenderViewHost* render_view_host, gfx::Size* rwhv_create_size, gfx::Size* rwhv_commit_size, gfx::Size* wcv_commit_size) { RenderViewSizes::const_iterator result = render_view_sizes_.end(); result = render_view_sizes_.find(render_view_host); if (result != render_view_sizes_.end()) { *rwhv_create_size = result->second.rwhv_create_size; *rwhv_commit_size = result->second.rwhv_commit_size; *wcv_commit_size = result->second.wcv_commit_size; } } void set_wcv_resize_insets(const gfx::Size& wcv_resize_insets) { wcv_resize_insets_ = wcv_resize_insets; } // Cache the size when RenderViewHost is first created. virtual void RenderViewCreated( content::RenderViewHost* render_view_host) OVERRIDE { render_view_sizes_[render_view_host].rwhv_create_size = render_view_host->GetView()->GetViewBounds().size(); } // Enlarge WebContentsView by |wcv_resize_insets_| while the navigation entry // is pending. virtual void DidStartNavigationToPendingEntry( const GURL& url, NavigationController::ReloadType reload_type) OVERRIDE { if (wcv_resize_insets_.IsEmpty()) return; // Resizing the main browser window by |wcv_resize_insets_| will // automatically resize the WebContentsView by the same amount. // Just resizing WebContentsView directly doesn't work on Linux, because the // next automatic layout of the browser window will resize WebContentsView // back to the previous size. To make it consistent, resize main browser // window on all platforms. gfx::Rect bounds(browser_window_->GetBounds()); gfx::Size size(bounds.size()); size.Enlarge(wcv_resize_insets_.width(), wcv_resize_insets_.height()); bounds.set_size(size); browser_window_->SetBounds(bounds); // Let the message loop run so that resize actually takes effect. content::RunAllPendingInMessageLoop(); } // Cache the sizes of RenderWidgetHostView and WebContentsView when the // navigation entry is committed, which is before // WebContentsDelegate::DidNavigateMainFramePostCommit is called. virtual void NavigationEntryCommitted( const content::LoadCommittedDetails& details) OVERRIDE { content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); render_view_sizes_[rvh].rwhv_commit_size = web_contents()->GetRenderWidgetHostView()->GetViewBounds().size(); render_view_sizes_[rvh].wcv_commit_size = web_contents()->GetContainerBounds().size(); } private: struct Sizes { gfx::Size rwhv_create_size; // Size of RenderWidgetHostView when created. gfx::Size rwhv_commit_size; // Size of RenderWidgetHostView when committed. gfx::Size wcv_commit_size; // Size of WebContentsView when committed. }; typedef std::map RenderViewSizes; RenderViewSizes render_view_sizes_; // Enlarge WebContentsView by this size insets in // DidStartNavigationToPendingEntry. gfx::Size wcv_resize_insets_; BrowserWindow* browser_window_; // Weak ptr. DISALLOW_COPY_AND_ASSIGN(RenderViewSizeObserver); }; } // namespace class BrowserTest : public ExtensionBrowserTest { protected: // In RTL locales wrap the page title with RTL embedding characters so that it // matches the value returned by GetWindowTitle(). base::string16 LocaleWindowCaptionFromPageTitle( const base::string16& expected_title) { base::string16 page_title = WindowCaptionFromPageTitle(expected_title); #if defined(OS_WIN) std::string locale = g_browser_process->GetApplicationLocale(); if (base::i18n::GetTextDirectionForLocale(locale.c_str()) == base::i18n::RIGHT_TO_LEFT) { base::i18n::WrapStringWithLTRFormatting(&page_title); } return page_title; #else // Do we need to use the above code on POSIX as well? return page_title; #endif } // Returns the app extension aptly named "App Test". const Extension* GetExtension() { const extensions::ExtensionSet* extensions = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->extensions(); for (extensions::ExtensionSet::const_iterator it = extensions->begin(); it != extensions->end(); ++it) { if ((*it)->name() == "App Test") return it->get(); } NOTREACHED(); return NULL; } }; // Launch the app on a page with no title, check that the app title was set // correctly. IN_PROC_BROWSER_TEST_F(BrowserTest, NoTitle) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (http://crbug.com/262796). if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) return; #endif ui_test_utils::NavigateToURL( browser(), ui_test_utils::GetTestUrl( base::FilePath(base::FilePath::kCurrentDirectory), base::FilePath(kTitle1File))); EXPECT_EQ(LocaleWindowCaptionFromPageTitle(ASCIIToUTF16("title1.html")), browser()->GetWindowTitleForCurrentTab()); base::string16 tab_title; ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); EXPECT_EQ(ASCIIToUTF16("title1.html"), tab_title); } // Launch the app, navigate to a page with a title, check that the app title // was set correctly. IN_PROC_BROWSER_TEST_F(BrowserTest, Title) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (http://crbug.com/262796). if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) return; #endif ui_test_utils::NavigateToURL( browser(), ui_test_utils::GetTestUrl( base::FilePath(base::FilePath::kCurrentDirectory), base::FilePath(kTitle2File))); const base::string16 test_title(ASCIIToUTF16("Title Of Awesomeness")); EXPECT_EQ(LocaleWindowCaptionFromPageTitle(test_title), browser()->GetWindowTitleForCurrentTab()); base::string16 tab_title; ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title)); EXPECT_EQ(test_title, tab_title); } IN_PROC_BROWSER_TEST_F(BrowserTest, JavascriptAlertActivatesTab) { GURL url(ui_test_utils::GetTestUrl(base::FilePath( base::FilePath::kCurrentDirectory), base::FilePath(kTitle1File))); ui_test_utils::NavigateToURL(browser(), url); AddTabAtIndex(0, url, content::PAGE_TRANSITION_TYPED); EXPECT_EQ(2, browser()->tab_strip_model()->count()); EXPECT_EQ(0, browser()->tab_strip_model()->active_index()); WebContents* second_tab = browser()->tab_strip_model()->GetWebContentsAt(1); ASSERT_TRUE(second_tab); second_tab->GetMainFrame()->ExecuteJavaScript( ASCIIToUTF16("alert('Activate!');")); AppModalDialog* alert = ui_test_utils::WaitForAppModalDialog(); alert->CloseModalDialog(); EXPECT_EQ(2, browser()->tab_strip_model()->count()); EXPECT_EQ(1, browser()->tab_strip_model()->active_index()); } #if defined(OS_WIN) && !defined(NDEBUG) // http://crbug.com/114859. Times out frequently on Windows. #define MAYBE_ThirtyFourTabs DISABLED_ThirtyFourTabs #else #define MAYBE_ThirtyFourTabs ThirtyFourTabs #endif // Create 34 tabs and verify that a lot of processes have been created. The // exact number of processes depends on the amount of memory. Previously we // had a hard limit of 31 processes and this test is mainly directed at // verifying that we don't crash when we pass this limit. // Warning: this test can take >30 seconds when running on a slow (low // memory?) Mac builder. IN_PROC_BROWSER_TEST_F(BrowserTest, MAYBE_ThirtyFourTabs) { GURL url(ui_test_utils::GetTestUrl(base::FilePath( base::FilePath::kCurrentDirectory), base::FilePath(kTitle2File))); // There is one initial tab. const int kTabCount = 34; for (int ix = 0; ix != (kTabCount - 1); ++ix) { chrome::AddSelectedTabWithURL(browser(), url, content::PAGE_TRANSITION_TYPED); } EXPECT_EQ(kTabCount, browser()->tab_strip_model()->count()); // See GetMaxRendererProcessCount() in // content/browser/renderer_host/render_process_host_impl.cc // for the algorithm to decide how many processes to create. const int kExpectedProcessCount = #if defined(ARCH_CPU_64_BITS) 17; #else 25; #endif if (base::SysInfo::AmountOfPhysicalMemoryMB() >= 2048) { EXPECT_GE(CountRenderProcessHosts(), kExpectedProcessCount); } else { EXPECT_LT(CountRenderProcessHosts(), kExpectedProcessCount); } } // Test that a browser-initiated navigation to an aborted URL load leaves around // a pending entry if we start from the NTP but not from a normal page. // See http://crbug.com/355537. IN_PROC_BROWSER_TEST_F(BrowserTest, ClearPendingOnFailUnlessNTP) { ASSERT_TRUE(test_server()->Start()); WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); GURL ntp_url(chrome::GetNewTabPageURL(browser()->profile())); ui_test_utils::NavigateToURL(browser(), ntp_url); // Navigate to a 204 URL (aborts with no content) on the NTP and make sure it // sticks around so that the user can edit it. GURL abort_url(test_server()->GetURL("nocontent")); { content::WindowedNotificationObserver stop_observer( content::NOTIFICATION_LOAD_STOP, content::Source( &web_contents->GetController())); browser()->OpenURL(OpenURLParams(abort_url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, false)); stop_observer.Wait(); EXPECT_TRUE(web_contents->GetController().GetPendingEntry()); EXPECT_EQ(abort_url, web_contents->GetVisibleURL()); } // Navigate to a real URL. GURL real_url(test_server()->GetURL("title1.html")); ui_test_utils::NavigateToURL(browser(), real_url); EXPECT_EQ(real_url, web_contents->GetVisibleURL()); // Now navigating to a 204 URL should clear the pending entry. { content::WindowedNotificationObserver stop_observer( content::NOTIFICATION_LOAD_STOP, content::Source( &web_contents->GetController())); browser()->OpenURL(OpenURLParams(abort_url, Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED, false)); stop_observer.Wait(); EXPECT_FALSE(web_contents->GetController().GetPendingEntry()); EXPECT_EQ(real_url, web_contents->GetVisibleURL()); } } // Test for crbug.com/297289. Ensure that modal dialogs are closed when a // cross-process navigation is ready to commit. IN_PROC_BROWSER_TEST_F(BrowserTest, CrossProcessNavCancelsDialogs) { ASSERT_TRUE(test_server()->Start()); host_resolver()->AddRule("www.example.com", "127.0.0.1"); GURL url(test_server()->GetURL("empty.html")); ui_test_utils::NavigateToURL(browser(), url); // Test this with multiple alert dialogs to ensure that we can navigate away // even if the renderer tries to synchronously create more. // See http://crbug.com/312490. WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); contents->GetMainFrame()->ExecuteJavaScript( ASCIIToUTF16("alert('one'); alert('two');")); AppModalDialog* alert = ui_test_utils::WaitForAppModalDialog(); EXPECT_TRUE(alert->IsValid()); AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance(); EXPECT_TRUE(dialog_queue->HasActiveDialog()); // A cross-site navigation should force the dialog to close. GURL url2("http://www.example.com/empty.html"); ui_test_utils::NavigateToURL(browser(), url2); EXPECT_FALSE(dialog_queue->HasActiveDialog()); // Make sure input events still work in the renderer process. EXPECT_FALSE(contents->GetRenderProcessHost()->IgnoreInputEvents()); } // Make sure that dialogs are closed after a renderer process dies, and that // subsequent navigations work. See http://crbug/com/343265. IN_PROC_BROWSER_TEST_F(BrowserTest, SadTabCancelsDialogs) { ASSERT_TRUE(test_server()->Start()); host_resolver()->AddRule("www.example.com", "127.0.0.1"); GURL beforeunload_url(test_server()->GetURL("files/beforeunload.html")); ui_test_utils::NavigateToURL(browser(), beforeunload_url); // Start a navigation to trigger the beforeunload dialog. WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); contents->GetMainFrame()->ExecuteJavaScript( ASCIIToUTF16("window.location.href = 'data:text/html,foo'")); AppModalDialog* alert = ui_test_utils::WaitForAppModalDialog(); EXPECT_TRUE(alert->IsValid()); AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance(); EXPECT_TRUE(dialog_queue->HasActiveDialog()); // Crash the renderer process and ensure the dialog is gone. content::RenderProcessHost* child_process = contents->GetRenderProcessHost(); content::RenderProcessHostWatcher crash_observer( child_process, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); base::KillProcess(child_process->GetHandle(), 0, false); crash_observer.Wait(); EXPECT_FALSE(dialog_queue->HasActiveDialog()); // Make sure subsequent navigations work. GURL url2("http://www.example.com/files/empty.html"); ui_test_utils::NavigateToURL(browser(), url2); } // Make sure that dialogs opened by subframes are closed when the process dies. // See http://crbug.com/366510. IN_PROC_BROWSER_TEST_F(BrowserTest, SadTabCancelsSubframeDialogs) { // Navigate to an iframe that opens an alert dialog. WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); contents->GetMainFrame()->ExecuteJavaScript( ASCIIToUTF16("window.location.href = 'data:text/html," "