diff options
author | brg@chromium.com <brg@chromium.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-23 00:02:44 +0000 |
---|---|---|
committer | brg@chromium.com <brg@chromium.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-23 00:02:44 +0000 |
commit | 1ebed56434c220f82cf220bbb72a136fea962c87 (patch) | |
tree | 7058028de7d017612d46ee49ba27b497c89cea7a /chrome | |
parent | 3c4d38386f27b5fc4a7a8db717ea114725c34b4a (diff) | |
download | chromium_src-1ebed56434c220f82cf220bbb72a136fea962c87.zip chromium_src-1ebed56434c220f82cf220bbb72a136fea962c87.tar.gz chromium_src-1ebed56434c220f82cf220bbb72a136fea962c87.tar.bz2 |
Changes to insure that when in app-mode, links open in the default browser. This change should have no affect on Chrome when not in app-mode.Tested against Gmail. UITests added as app_mode_navigation_uitest.
Review URL: http://codereview.chromium.org/18093
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8523 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/app_mode_navigation_uitest.cc | 194 | ||||
-rw-r--r-- | chrome/browser/browser.cc | 24 | ||||
-rw-r--r-- | chrome/browser/browser.h | 12 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents_delegate.h | 14 | ||||
-rw-r--r-- | chrome/browser/tab_contents/web_contents.cc | 49 | ||||
-rw-r--r-- | chrome/browser/tab_contents/web_contents.h | 4 | ||||
-rw-r--r-- | chrome/test/data/appmodenavigation_test.html | 21 | ||||
-rw-r--r-- | chrome/test/ui/ui_tests.vcproj | 8 |
8 files changed, 300 insertions, 26 deletions
diff --git a/chrome/browser/app_mode_navigation_uitest.cc b/chrome/browser/app_mode_navigation_uitest.cc new file mode 100644 index 0000000..e6175af --- /dev/null +++ b/chrome/browser/app_mode_navigation_uitest.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2006-2008 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. + +// Tests browsing in app-mode. Specifically, insures that navigation to +// new windows launch in the default protocol handler and that navigations +// within the same frame do not. Outside of app-mode these tests are covered +// by normal navigation and the fork test. + +#include "base/string_util.h" +#include "base/file_util.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/window_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_unittest.h" + +#include "chromium_strings.h" +#include "generated_resources.h" + +namespace { + +const wchar_t kDocRoot[] = L"chrome/test/data"; +const wchar_t kTestFileName[] = L"appmodenavigation_test.html"; + +class AppModeNavigationTest : public UITest { + protected: + AppModeNavigationTest() : UITest() { + show_window_ = true; + + // Launch chrome in app mode. + std::wstring test_file = test_data_directory_; + file_util::AppendToPath(&test_file, kTestFileName); + std::wstring url = ASCIIToWide(net::FilePathToFileURL(test_file).spec()); + launch_arguments_.AppendSwitchWithValue(switches::kApp, url); + } + + // Helper function for evaluation. + HWND GetMainBrowserWindow() { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + scoped_ptr<WindowProxy> window(browser->GetWindow()); + + HWND window_handle; + EXPECT_TRUE(window->GetHWND(&window_handle)); + return window_handle; + } + + // Get the window title from the main window. + std::wstring GetMainBrowserWindowTitle() { + HWND window_handle = GetMainBrowserWindow(); + std::wstring result; + int length = ::GetWindowTextLength(window_handle) + 1; + ::GetWindowText(window_handle, WriteInto(&result, length), length); + return result; + } + + // Given a page title, returns the expected window caption string. + std::wstring WindowCaptionFromPageTitle(const std::wstring& page_title) { + if (page_title.empty()) + return l10n_util::GetString(IDS_PRODUCT_NAME); + return l10n_util::GetStringF(IDS_BROWSER_WINDOW_TITLE_FORMAT, page_title); + } + + // Selects the anchor tag by index and navigates to it. This is necssary + // since the automation API's navigate the window by simulating address bar + // entries. + void NavigateToIndex(int index) { + scoped_ptr<WindowProxy> window(automation()->GetActiveWindow()); + ASSERT_TRUE(window->Activate()); + Sleep(action_timeout_ms()); + // We are in app mode, hence tab moves from focus to the next UI element + // in the view. + for (; index > 0; --index) { + window->SimulateOSKeyPress(L'\t', 0); // 0 signifies no modifier. + } + Sleep(action_timeout_ms()); + window->SimulateOSKeyPress(L'\r', 0); + Sleep(action_timeout_ms()); + } + + // Validates that the foreground window is the external protocol handler + // validation window, and if so dismisses it by clicking on the default + // input. + bool DismissExternalLauncherPopup() { + // The currently active window should be the protocol handler if this is + // and external link. + scoped_ptr<WindowProxy> window(automation()->GetActiveWindow()); + HWND hwnd; + if (!window->GetHWND(&hwnd)) + return false; + + int size = ::GetWindowTextLengthW(hwnd); + scoped_ptr<wchar_t> buffer(new wchar_t[size+1]); + if (NULL == buffer.get()) + return false; + ::GetWindowText(hwnd, buffer.get(), size+1); + if (0 != wcscmp(L"External Protocol Request", buffer.get())) + return false; + + // The default UI element is the |Cancel| button. + window->SimulateOSKeyPress(L'\r', 0); + Sleep(action_timeout_ms()); + + return true; + } +}; + + +// Follow an normal anchor tag with target=blank (external tab). +TEST_F(AppModeNavigationTest, NavigateToNewTab) { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + scoped_ptr<TabProxy> tab(browser->GetActiveTab()); + + int orig_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&orig_tab_count)); + int orig_process_count = GetBrowserProcessCount(); + ASSERT_TRUE(orig_process_count >= 1); + + NavigateToIndex(1); // First link test. + + ASSERT_TRUE(DismissExternalLauncherPopup()); + + // Insure all the other processes have terminated. + ASSERT_EQ(orig_process_count, GetBrowserProcessCount()); + + // In app-mode there is a single tab. + int new_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&new_tab_count)); + ASSERT_EQ(orig_tab_count, new_tab_count); + + // We must not have navigated. + EXPECT_EQ(WindowCaptionFromPageTitle(kTestFileName), + GetMainBrowserWindowTitle()); + EXPECT_EQ(kTestFileName, GetActiveTabTitle()); +} + +// Open a new tab with a redirect as Gmail does. +TEST_F(AppModeNavigationTest, NavigateByForkToNewTabTest) { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + scoped_ptr<TabProxy> tab(browser->GetActiveTab()); + + int orig_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&orig_tab_count)); + int orig_process_count = GetBrowserProcessCount(); + ASSERT_TRUE(orig_process_count >= 1); + + NavigateToIndex(2); // Second link test. + + ASSERT_TRUE(DismissExternalLauncherPopup()); + + // Insure all the other processes have terminated. + ASSERT_EQ(orig_process_count, GetBrowserProcessCount()); + + // In app-mode there is a single tab. + int new_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&new_tab_count)); + ASSERT_EQ(orig_tab_count, new_tab_count); + + // We must not have navigated. + EXPECT_EQ(WindowCaptionFromPageTitle(kTestFileName), + GetMainBrowserWindowTitle()); + EXPECT_EQ(kTestFileName, GetActiveTabTitle()); +} + +// Normal navigation +TEST_F(AppModeNavigationTest, NavigateToAboutBlankByLink) { + scoped_ptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + scoped_ptr<TabProxy> tab(browser->GetActiveTab()); + + int orig_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&orig_tab_count)); + int orig_process_count = GetBrowserProcessCount(); + ASSERT_TRUE(orig_process_count >= 1); + + NavigateToIndex(3); // Third link test. + + // Insure all the other processes have terminated. + ASSERT_EQ(orig_process_count, GetBrowserProcessCount()); + + // In app-mode there is a single tab. + int new_tab_count = -1; + ASSERT_TRUE(browser->GetTabCount(&new_tab_count)); + ASSERT_EQ(orig_tab_count, new_tab_count); + + // We must not have navigated. + EXPECT_EQ(WindowCaptionFromPageTitle(L"about:blank"), + GetMainBrowserWindowTitle()); + EXPECT_EQ(L"", GetActiveTabTitle()); +} + +} // namespace diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index 3cc13be..bdebf14 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -166,7 +166,8 @@ Browser::Browser(Type type, Profile* profile) is_attempting_to_close_browser_(false), override_maximized_(false), method_factory_(this), - idle_task_(new BrowserIdleTimer) { + idle_task_(new BrowserIdleTimer), + open_new_windows_in_default_browser_(false) { tabstrip_model_.AddObserver(this); NotificationService::current()->AddObserver( @@ -1127,7 +1128,7 @@ void Browser::ExecuteCommand(int id) { case IDC_NEW_WINDOW_PROFILE_5: case IDC_NEW_WINDOW_PROFILE_6: case IDC_NEW_WINDOW_PROFILE_7: - case IDC_NEW_WINDOW_PROFILE_8: + case IDC_NEW_WINDOW_PROFILE_8: NewProfileWindowByIndex(id - IDC_NEW_WINDOW_PROFILE_0); break; #if defined(OS_WIN) case IDC_CLOSE_WINDOW: CloseWindow(); break; @@ -1675,12 +1676,14 @@ void Browser::AddNewContents(TabContents* source, DCHECK(disposition != SAVE_TO_DISK); // No code for this yet // If this is an application we can only have one tab so we need to process - // this in tabbed browser window. - if (tabstrip_model_.count() > 0 && + // this in tabbed browser window. The new window will act as an intermediary + // to launch urls in the default browser. + if (tabstrip_model_.count() > 0 && // A launched application has count of 1. disposition != NEW_WINDOW && disposition != NEW_POPUP && type_ != TYPE_NORMAL) { Browser* b = GetOrCreateTabbedBrowser(); DCHECK(b); + b->set_open_new_windows_in_default_browser(true); PageTransition::Type transition = PageTransition::LINK; // If we were called from an "installed webapp" we want to emulate the code // that is run from browser_init.cc for links from external applications. @@ -1690,7 +1693,13 @@ void Browser::AddNewContents(TabContents* source, if (type_ == TYPE_APP) transition = PageTransition::START_PAGE; b->tabstrip_model()->AddTabContents(new_contents, -1, transition, true); - b->window()->Show(); + + // All new windows from an "installed webapp" which must have their own + // tabs will be launched in the default browser. At this moment the + // url is inaccessbile and so we do not show the window until the url is + // reported to the TabConentsDelegate. + if (type_ != TYPE_APP) + b->window()->Show(); return; } @@ -1800,6 +1809,10 @@ bool Browser::IsApplication() const { return type_ == TYPE_APP; } +bool Browser::ShouldOpenURLInDefaultBrowser() const { + return open_new_windows_in_default_browser_; +} + void Browser::ConvertContentsToApplication(TabContents* contents) { WebContents* web_contents = contents->AsWebContents(); if (!web_contents || !web_contents->web_app()) { @@ -2455,3 +2468,4 @@ void Browser::RegisterAppPrefs(const std::wstring& app_name) { } #endif // OS_WIN + diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index 349b504..488d364 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -112,6 +112,10 @@ class Browser : public TabStripModelDelegate, g_browser_process->user_data_dir_profiles() = profiles; } + void set_open_new_windows_in_default_browser(bool value) { + open_new_windows_in_default_browser_ = value; + } + // Browser Creation Helpers ///////////////////////////////////////////////// // Opens a new window with the default blank tab. @@ -393,11 +397,12 @@ class Browser : public TabStripModelDelegate, virtual void ContentsZoomChange(bool zoom_in); virtual bool IsApplication() const; + virtual bool ShouldOpenURLInDefaultBrowser() const; virtual void ConvertContentsToApplication(TabContents* source); virtual void ContentsStateChanged(TabContents* source); virtual bool ShouldDisplayURLField(); virtual void BeforeUnloadFired(TabContents* source, - bool proceed, + bool proceed, bool* proceed_to_fire_unload); virtual void ShowHtmlDialog(HtmlDialogContentsDelegate* delegate, void* parent_window); @@ -504,7 +509,7 @@ class Browser : public TabStripModelDelegate, bool RemoveFromSet(UnloadListenerSet* set, TabContents* tab); // Cleans up state appropriately when we are trying to close the browser and - // the tab has finished firing it's unload handler. We also use this in the + // the tab has finished firing it's unload handler. We also use this in the // cases where a tab crashes or hangs even if the beforeunload/unload haven't // successfully fired. void ClearUnloadState(TabContents* tab); @@ -651,6 +656,9 @@ class Browser : public TabStripModelDelegate, // Keep track of the encoding auto detect pref. BooleanPrefMember encoding_auto_detect_; + // Allows a client of a TabDelegate to decide how to launch a url. + bool open_new_windows_in_default_browser_; + DISALLOW_COPY_AND_ASSIGN(Browser); }; diff --git a/chrome/browser/tab_contents/tab_contents_delegate.h b/chrome/browser/tab_contents/tab_contents_delegate.h index 599b64c..85458ba 100644 --- a/chrome/browser/tab_contents/tab_contents_delegate.h +++ b/chrome/browser/tab_contents/tab_contents_delegate.h @@ -30,8 +30,7 @@ class TabContentsDelegate : public PageNavigator { virtual void OpenURL(const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, - PageTransition::Type transition) - { + PageTransition::Type transition) { OpenURLFromTab(NULL, url, referrer, disposition, transition); } @@ -102,11 +101,14 @@ class TabContentsDelegate : public PageNavigator { // application. virtual bool IsApplication() { return false; } + // Check whether or not the url should be opened in the default browser. + virtual bool ShouldOpenURLInDefaultBrowser() const { return false; } + // Detach the given tab and convert it to a "webapp" view. The tab must be // a WebContents with a valid WebApp set. virtual void ConvertContentsToApplication(TabContents* source) { } - // Informs the TabContentsDelegate that some of our state has changed + // Informs the TabContentsDelegate that some of our state has changed // for this tab. virtual void ContentsStateChanged(TabContents* source) {} @@ -129,11 +131,11 @@ class TabContentsDelegate : public PageNavigator { // Tells us that we've finished firing this tab's beforeunload event. // The proceed bool tells us whether the user chose to proceed closing the // tab. Returns true if the tab can continue on firing it's unload event. - // If we're closing the entire browser, then we'll want to delay firing + // If we're closing the entire browser, then we'll want to delay firing // unload events until all the beforeunload events have fired. virtual void BeforeUnloadFired(TabContents* tab, - bool proceed, - bool* proceed_to_fire_unload) { + bool proceed, + bool* proceed_to_fire_unload) { *proceed_to_fire_unload = true; } diff --git a/chrome/browser/tab_contents/web_contents.cc b/chrome/browser/tab_contents/web_contents.cc index cf36173..05a026a 100644 --- a/chrome/browser/tab_contents/web_contents.cc +++ b/chrome/browser/tab_contents/web_contents.cc @@ -17,6 +17,7 @@ #include "chrome/browser/dom_operation_notification_details.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_request_manager.h" +#include "chrome/browser/external_protocol_handler.h" #include "chrome/browser/find_notification_details.h" #include "chrome/browser/google_util.h" #include "chrome/browser/js_before_unload_handler.h" @@ -389,7 +390,7 @@ bool WebContents::NavigateToPendingEntry(bool reload) { } // Clear any provisional password saves - this stops password infobars - // showing up on pages the user navigates to while the right page is + // showing up on pages the user navigates to while the right page is // loading. GetPasswordManager()->ClearProvisionalSave(); @@ -417,7 +418,7 @@ void WebContents::Copy() { } void WebContents::Paste() { - render_view_host()->Paste(); + render_view_host()->Paste(); } void WebContents::DisassociateFromPopupCount() { @@ -619,7 +620,7 @@ bool WebContents::IsActiveEntry(int32 page_id) { } void WebContents::SetInitialFocus(bool reverse) { - render_view_host()->SetInitialFocus(reverse); + render_view_host()->SetInitialFocus(reverse); } // Notifies the RenderWidgetHost instance about the fact that the page is @@ -684,22 +685,30 @@ void WebContents::DidNavigate(RenderViewHost* rvh, if (PageTransition::IsMainFrame(params.transition)) render_manager_.DidNavigateMainFrame(rvh); + // This is the first chance a new frame has to launch an external browser + // since it is the first message from the renderer to the WebContents + // containing the URL. + if (delegate() && delegate()->ShouldOpenURLInDefaultBrowser()) { + OpenUrlInDefaultBrowserAndClosePage(params.url, rvh); + return; + } + // We can't do anything about navigations when we're inactive. if (!controller() || !is_active()) - return; + return; // Update the site of the SiteInstance if it doesn't have one yet. if (!GetSiteInstance()->has_site()) GetSiteInstance()->SetSite(params.url); - // Need to update MIME type here because it's referred to in + // Need to update MIME type here because it's referred to in // UpdateNavigationCommands() called by RendererDidNavigate() to - // determine whether or not to enable the encoding menu. - // It's updated only for the main frame. For a subframe, + // determine whether or not to enable the encoding menu. + // It's updated only for the main frame. For a subframe, // RenderView::UpdateURL does not set params.contents_mime_type. // (see http://code.google.com/p/chromium/issues/detail?id=2929 ) - // TODO(jungshik): Add a test for the encoding menu to avoid - // regressing it again. + // TODO(jungshik): Add a test for the encoding menu to avoid + // regressing it again. if (PageTransition::IsMainFrame(params.transition)) contents_mime_type_ = params.contents_mime_type; @@ -958,7 +967,12 @@ void WebContents::DidDownloadImage( void WebContents::RequestOpenURL(const GURL& url, const GURL& referrer, WindowOpenDisposition disposition) { - OpenURL(url, referrer, disposition, PageTransition::LINK); + if (!delegate() || !delegate()->ShouldOpenURLInDefaultBrowser()) { + OpenURL(url, referrer, disposition, PageTransition::LINK); + } else { + OpenUrlInDefaultBrowserAndClosePage(url, render_view_host()); + return; + } } void WebContents::DomOperationResponse(const std::string& json_string, @@ -1073,7 +1087,7 @@ void WebContents::AutofillFormSubmitted( GetAutofillManager()->AutofillFormSubmitted(form); } -void WebContents::GetAutofillSuggestions(const std::wstring& field_name, +void WebContents::GetAutofillSuggestions(const std::wstring& field_name, const std::wstring& user_text, int64 node_id, int request_id) { GetAutofillManager()->FetchValuesForName(field_name, user_text, kMaxAutofillMenuItems, node_id, request_id); @@ -1288,7 +1302,7 @@ bool WebContents::CanBlur() const { return delegate() ? delegate()->CanBlur() : true; } -void WebContents::RendererUnresponsive(RenderViewHost* rvh, +void WebContents::RendererUnresponsive(RenderViewHost* rvh, bool is_during_unload) { if (is_during_unload) { // Hang occurred while firing the beforeunload/unload handler. @@ -1679,7 +1693,7 @@ bool WebContents::UpdateTitleForEntry(NavigationEntry* entry, profile()->GetHistoryService(Profile::IMPLICIT_ACCESS); if (hs) hs->SetPageTitle(entry->display_url(), final_title); - + // Don't allow the title to be saved again for explicitly set ones. received_page_title_ = explicit_set; } @@ -1794,3 +1808,12 @@ void WebContents::GenerateKeywordIfNecessary( new_url->set_safe_for_autoreplace(true); url_model->Add(new_url); } + +void WebContents::OpenUrlInDefaultBrowserAndClosePage(const GURL& url, + RenderViewHost* rvh) { + if (!url.is_empty() && rvh && rvh->process()) { + ExternalProtocolHandler::LaunchUrl(url, rvh->routing_id(), + rvh->process()->host_id()); + } + ShouldClosePage(true); +}
\ No newline at end of file diff --git a/chrome/browser/tab_contents/web_contents.h b/chrome/browser/tab_contents/web_contents.h index c3094a4..c780fe9 100644 --- a/chrome/browser/tab_contents/web_contents.h +++ b/chrome/browser/tab_contents/web_contents.h @@ -480,6 +480,10 @@ class WebContents : public TabContents, void GenerateKeywordIfNecessary( const ViewHostMsg_FrameNavigate_Params& params); + // Helper function to launch the external browser. + void OpenUrlInDefaultBrowserAndClosePage(const GURL& url, + RenderViewHost* rvh); + // Data ---------------------------------------------------------------------- // The corresponding view. diff --git a/chrome/test/data/appmodenavigation_test.html b/chrome/test/data/appmodenavigation_test.html new file mode 100644 index 0000000..c938dc6 --- /dev/null +++ b/chrome/test/data/appmodenavigation_test.html @@ -0,0 +1,21 @@ +<html>
+<head>
+<Script Language="JavaScript">
+function load() {
+ w = window.open();
+ w.opener=null;
+ w.document.location='http://www.google.com';
+ w();
+}
+</Script>
+</head>
+<body>
+<a href="http://www.google.com" target=_blank> Link Test </a>
+<p>
+<div onclick='load()'>
+<a href=""> OnClick Test </a>
+</div>
+<p>
+<a href="about:blank"> Normal Test</a>
+</body>
+</html>
diff --git a/chrome/test/ui/ui_tests.vcproj b/chrome/test/ui/ui_tests.vcproj index e225613..311bc95 100644 --- a/chrome/test/ui/ui_tests.vcproj +++ b/chrome/test/ui/ui_tests.vcproj @@ -542,6 +542,14 @@ > </File> </Filter> + <Filter + Name="TestAppModeNavigation" + > + <File + RelativePath="..\..\browser\app_mode_navigation_uitest.cc" + > + </File> + </Filter> </Files> <Globals> </Globals> |