diff options
author | benwells@chromium.org <benwells@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-23 05:04:38 +0000 |
---|---|---|
committer | benwells@chromium.org <benwells@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-23 05:04:38 +0000 |
commit | 956eabbb1da6f947c8cbbc94131f3f6bdd8e93da (patch) | |
tree | 225ac2fc36bfaba4b824f297fff11620444ce164 /chrome/browser/external_protocol | |
parent | 886ee30c5a5ee2a43d1532a1bf145d2c5814b0b6 (diff) | |
download | chromium_src-956eabbb1da6f947c8cbbc94131f3f6bdd8e93da.zip chromium_src-956eabbb1da6f947c8cbbc94131f3f6bdd8e93da.tar.gz chromium_src-956eabbb1da6f947c8cbbc94131f3f6bdd8e93da.tar.bz2 |
Open external application dialog should not show for Chrome.
This change prevents the external application dialog from coming up if Chrome
detects that it would just start itself. To determine this it uses the
shell_integration functionality added for registerProtocolHandler to find out
if Chrome is the default handler.
BUG=90373
TEST=Unit tests added.
Review URL: http://codereview.chromium.org/7790021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102449 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/external_protocol')
3 files changed, 344 insertions, 15 deletions
diff --git a/chrome/browser/external_protocol/external_protocol_handler.cc b/chrome/browser/external_protocol/external_protocol_handler.cc index 7388dd9..a6dd49b 100644 --- a/chrome/browser/external_protocol/external_protocol_handler.cc +++ b/chrome/browser/external_protocol/external_protocol_handler.cc @@ -24,6 +24,116 @@ // each user gesture. This variable should only be accessed from the UI thread. static bool g_accept_requests = true; +namespace { + +// Functions enabling unit testing. Using a NULL delegate will use the default +// behavior; if a delegate is provided it will be used instead. +ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker( + ShellIntegration::DefaultWebClientObserver* observer, + const std::string& protocol, + ExternalProtocolHandler::Delegate* delegate) { + if (!delegate) + return new ShellIntegration::DefaultProtocolClientWorker(observer, + protocol); + + return delegate->CreateShellWorker(observer, protocol); +} + +ExternalProtocolHandler::BlockState GetBlockStateWithDelegate( + const std::string& scheme, + ExternalProtocolHandler::Delegate* delegate) { + if (!delegate) + return ExternalProtocolHandler::GetBlockState(scheme); + + return delegate->GetBlockState(scheme); +} + +void RunExternalProtocolDialogWithDelegate( + const GURL& url, + int render_process_host_id, + int routing_id, + ExternalProtocolHandler::Delegate* delegate) { + if (!delegate) { + ExternalProtocolHandler::RunExternalProtocolDialog(url, + render_process_host_id, + routing_id); + } else { + delegate->RunExternalProtocolDialog(url, render_process_host_id, + routing_id); + } +} + +void LaunchUrlWithoutSecurityCheckWithDelegate( + const GURL& url, + ExternalProtocolHandler::Delegate* delegate) { + if (!delegate) + ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(url); + else + delegate->LaunchUrlWithoutSecurityCheck(url); +} + +// When we are about to launch a URL with the default OS level application, +// we check if that external application will be us. If it is we just ignore +// the request. +class ExternalDefaultProtocolObserver + : public ShellIntegration::DefaultWebClientObserver { + public: + ExternalDefaultProtocolObserver(const GURL& escaped_url, + int render_process_host_id, + int tab_contents_id, + bool prompt_user, + ExternalProtocolHandler::Delegate* delegate) + : delegate_(delegate), + escaped_url_(escaped_url), + render_process_host_id_(render_process_host_id), + tab_contents_id_(tab_contents_id), + prompt_user_(prompt_user) {} + + virtual void SetDefaultWebClientUIState( + ShellIntegration::DefaultWebClientUIState state) OVERRIDE { + DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); + + // If we are still working out if we're the default, or we've found + // out we definately are the default, we end here. + if (state == ShellIntegration::STATE_PROCESSING) { + return; + } + + if (delegate_) + delegate_->FinishedProcessingCheck(); + + if (state == ShellIntegration::STATE_IS_DEFAULT) { + if (delegate_) + delegate_->BlockRequest(); + return; + } + + // If we get here, either we are not the default or we cannot work out + // what the default is, so we proceed. + if (prompt_user_) { + // Ask the user if they want to allow the protocol. This will call + // LaunchUrlWithoutSecurityCheck if the user decides to accept the + // protocol. + RunExternalProtocolDialogWithDelegate(escaped_url_, + render_process_host_id_, tab_contents_id_, delegate_); + return; + } + + LaunchUrlWithoutSecurityCheckWithDelegate(escaped_url_, delegate_); + } + + virtual bool IsOwnedByWorker() OVERRIDE { return true; } + + private: + ExternalProtocolHandler::Delegate* delegate_; + GURL escaped_url_; + int render_process_host_id_; + int tab_contents_id_; + bool prompt_user_; +}; + +} // namespace + // static void ExternalProtocolHandler::PrepopulateDictionary(DictionaryValue* win_pref) { static bool is_warm = false; @@ -126,31 +236,42 @@ void ExternalProtocolHandler::SetBlockState(const std::string& scheme, } // static -void ExternalProtocolHandler::LaunchUrl(const GURL& url, - int render_process_host_id, - int tab_contents_id) { +void ExternalProtocolHandler::LaunchUrlWithDelegate(const GURL& url, + int render_process_host_id, + int tab_contents_id, + Delegate* delegate) { DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); // Escape the input scheme to be sure that the command does not // have parameters unexpected by the external program. std::string escaped_url_string = EscapeExternalHandlerValue(url.spec()); GURL escaped_url(escaped_url_string); - BlockState block_state = GetBlockState(escaped_url.scheme()); - if (block_state == BLOCK) + BlockState block_state = GetBlockStateWithDelegate(escaped_url.scheme(), + delegate); + if (block_state == BLOCK) { + if (delegate) + delegate->BlockRequest(); return; + } g_accept_requests = false; - if (block_state == UNKNOWN) { - // Ask the user if they want to allow the protocol. This will call - // LaunchUrlWithoutSecurityCheck if the user decides to accept the protocol. - RunExternalProtocolDialog(escaped_url, - render_process_host_id, - tab_contents_id); - return; - } + // The worker creates tasks with references to itself and puts them into + // message loops. When no tasks are left it will delete the observer and + // eventually be deleted itself. + ShellIntegration::DefaultWebClientObserver* observer = + new ExternalDefaultProtocolObserver(url, + render_process_host_id, + tab_contents_id, + block_state == UNKNOWN, + delegate); + scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker = + CreateShellWorker(observer, escaped_url.scheme(), delegate); - LaunchUrlWithoutSecurityCheck(escaped_url); + // Start the check process running. This will send tasks to the FILE thread + // and when the answer is known will send the result back to the observer on + // the UI thread. + worker->StartCheckIsDefault(); } // static diff --git a/chrome/browser/external_protocol/external_protocol_handler.h b/chrome/browser/external_protocol/external_protocol_handler.h index 002b0c8..780861c 100644 --- a/chrome/browser/external_protocol/external_protocol_handler.h +++ b/chrome/browser/external_protocol/external_protocol_handler.h @@ -8,6 +8,8 @@ #include <string> +#include "chrome/browser/shell_integration.h" + class GURL; class PrefService; @@ -23,6 +25,22 @@ class ExternalProtocolHandler { UNKNOWN, }; + // Delegate to allow unit testing to provide different behavior. + class Delegate { + public: + virtual ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker( + ShellIntegration::DefaultWebClientObserver* observer, + const std::string& protocol) = 0; + virtual BlockState GetBlockState(const std::string& scheme) = 0; + virtual void BlockRequest() = 0; + virtual void RunExternalProtocolDialog(const GURL& url, + int render_process_host_id, + int routing_id) = 0; + virtual void LaunchUrlWithoutSecurityCheck(const GURL& url) = 0; + virtual void FinishedProcessingCheck() = 0; + virtual ~Delegate() {} + }; + // Returns whether we should block a given scheme. static BlockState GetBlockState(const std::string& scheme); @@ -37,7 +55,14 @@ class ExternalProtocolHandler { // application is launched. // Must run on the UI thread. static void LaunchUrl(const GURL& url, int render_process_host_id, - int tab_contents_id); + int tab_contents_id) { + LaunchUrlWithDelegate(url, render_process_host_id, tab_contents_id, NULL); + } + + // Version of LaunchUrl allowing use of a delegate to facilitate unit + // testing. + static void LaunchUrlWithDelegate(const GURL& url, int render_process_host_id, + int tab_contents_id, Delegate* delegate); // Creates and runs a External Protocol dialog box. // |url| - The url of the request. diff --git a/chrome/browser/external_protocol/external_protocol_handler_unittest.cc b/chrome/browser/external_protocol/external_protocol_handler_unittest.cc new file mode 100644 index 0000000..77246c8 --- /dev/null +++ b/chrome/browser/external_protocol/external_protocol_handler_unittest.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2011 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 "chrome/browser/external_protocol/external_protocol_handler.h" + +#include "content/browser/browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +class FakeExternalProtocolHandlerWorker + : public ShellIntegration::DefaultProtocolClientWorker { + public: + FakeExternalProtocolHandlerWorker( + ShellIntegration::DefaultWebClientObserver* observer, + const std::string& protocol, + ShellIntegration::DefaultWebClientState os_state) + : ShellIntegration::DefaultProtocolClientWorker(observer, protocol), + os_state_(os_state) {} + + private: + virtual ShellIntegration::DefaultWebClientState CheckIsDefault() { + return os_state_; + } + + virtual void SetAsDefault() {} + + ShellIntegration::DefaultWebClientState os_state_; +}; + +class FakeExternalProtocolHandlerDelegate + : public ExternalProtocolHandler::Delegate { + public: + FakeExternalProtocolHandlerDelegate() + : block_state_(ExternalProtocolHandler::BLOCK), + os_state_(ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT), + has_launched_(false), + has_prompted_(false), + has_blocked_ (false) {} + + virtual ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker( + ShellIntegration::DefaultWebClientObserver* observer, + const std::string& protocol) { + return new FakeExternalProtocolHandlerWorker(observer, protocol, os_state_); + } + + virtual ExternalProtocolHandler::BlockState GetBlockState( + const std::string& scheme) { return block_state_; } + + virtual void BlockRequest() { + ASSERT_TRUE(block_state_ == ExternalProtocolHandler::BLOCK || + os_state_ == ShellIntegration::IS_DEFAULT_WEB_CLIENT); + has_blocked_ = true; + } + + virtual void RunExternalProtocolDialog(const GURL& url, + int render_process_host_id, + int routing_id) { + ASSERT_EQ(block_state_, ExternalProtocolHandler::UNKNOWN); + ASSERT_NE(os_state_, ShellIntegration::IS_DEFAULT_WEB_CLIENT); + has_prompted_ = true; + } + + virtual void LaunchUrlWithoutSecurityCheck(const GURL& url) { + ASSERT_EQ(block_state_, ExternalProtocolHandler::DONT_BLOCK); + ASSERT_NE(os_state_, ShellIntegration::IS_DEFAULT_WEB_CLIENT); + has_launched_ = true; + } + + virtual void FinishedProcessingCheck() { + MessageLoop::current()->Quit(); + } + + void set_os_state(ShellIntegration::DefaultWebClientState value) { + os_state_ = value; + } + + void set_block_state(ExternalProtocolHandler::BlockState value) { + block_state_ = value; + } + + bool has_launched() { return has_launched_; } + bool has_prompted() { return has_prompted_; } + bool has_blocked() { return has_blocked_; } + + private: + ExternalProtocolHandler::BlockState block_state_; + ShellIntegration::DefaultWebClientState os_state_; + bool has_launched_; + bool has_prompted_; + bool has_blocked_; +}; + +class ExternalProtocolHandlerTest : public testing::Test { + protected: + ExternalProtocolHandlerTest() + : ui_thread_(BrowserThread::UI, MessageLoop::current()), + file_thread_(BrowserThread::FILE) {} + + virtual void SetUp() { + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_DEFAULT; + file_thread_.StartWithOptions(options); + } + + void DoTest(ExternalProtocolHandler::BlockState block_state, + ShellIntegration::DefaultWebClientState os_state, + bool should_prompt, bool should_launch, bool should_block) { + GURL url("mailto:test@test.com"); + ASSERT_FALSE(delegate_.has_prompted()); + ASSERT_FALSE(delegate_.has_launched()); + ASSERT_FALSE(delegate_.has_blocked()); + + delegate_.set_block_state(block_state); + delegate_.set_os_state(os_state); + ExternalProtocolHandler::LaunchUrlWithDelegate(url, 0, 0, &delegate_); + if (block_state != ExternalProtocolHandler::BLOCK) + MessageLoop::current()->Run(); + + ASSERT_EQ(should_prompt, delegate_.has_prompted()); + ASSERT_EQ(should_launch, delegate_.has_launched()); + ASSERT_EQ(should_block, delegate_.has_blocked()); + } + + MessageLoopForUI ui_message_loop_; + BrowserThread ui_thread_; + BrowserThread file_thread_; + + FakeExternalProtocolHandlerDelegate delegate_; +}; + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeDefault) { + DoTest(ExternalProtocolHandler::BLOCK, + ShellIntegration::IS_DEFAULT_WEB_CLIENT, + false, false, true); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeNotDefault) { + DoTest(ExternalProtocolHandler::BLOCK, + ShellIntegration::NOT_DEFAULT_WEB_CLIENT, + false, false, true); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeBlockedChromeUnknown) { + DoTest(ExternalProtocolHandler::BLOCK, + ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT, + false, false, true); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeDefault) { + DoTest(ExternalProtocolHandler::DONT_BLOCK, + ShellIntegration::IS_DEFAULT_WEB_CLIENT, + false, false, true); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeNotDefault) { + DoTest(ExternalProtocolHandler::DONT_BLOCK, + ShellIntegration::NOT_DEFAULT_WEB_CLIENT, + false, true, false); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnBlockedChromeUnknown) { + DoTest(ExternalProtocolHandler::DONT_BLOCK, + ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT, + false, true, false); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeDefault) { + DoTest(ExternalProtocolHandler::UNKNOWN, + ShellIntegration::IS_DEFAULT_WEB_CLIENT, + false, false, true); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeNotDefault) { + DoTest(ExternalProtocolHandler::UNKNOWN, + ShellIntegration::NOT_DEFAULT_WEB_CLIENT, + true, false, false); +} + +TEST_F(ExternalProtocolHandlerTest, TestLaunchSchemeUnknownChromeUnknown) { + DoTest(ExternalProtocolHandler::UNKNOWN, + ShellIntegration::UNKNOWN_DEFAULT_WEB_CLIENT, + true, false, false); +} |