Commit 978740fb authored by Nicolas Ouellet-Payeur's avatar Nicolas Ouellet-Payeur Committed by Commit Bot

[BrowserSwitcher] Add AlternativeBrowserLauncher

|AlternativeBrowserLauncher| uses prefs to decide which browser to
launch, and then uses an |AlternativeBrowserDriver| to do I/O operations
that launch the browser.

Bug: 876804
Change-Id: I8461d72e8fb711a50148c3d7f27a2b286493df9f
Reviewed-on: https://chromium-review.googlesource.com/1197185
Commit-Queue: Nicolas Ouellet-Payeur <nicolaso@chromium.org>
Reviewed-by: default avatarJulian Pastarmov <pastarmovj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589267}
parent 2b8e9d3a
...@@ -3132,6 +3132,10 @@ jumbo_split_static_library("browser") { ...@@ -3132,6 +3132,10 @@ jumbo_split_static_library("browser") {
if (is_win) { if (is_win) {
sources += [ sources += [
"browser_switcher/alternative_browser_driver.cc",
"browser_switcher/alternative_browser_driver.h",
"browser_switcher/alternative_browser_launcher.cc",
"browser_switcher/alternative_browser_launcher.h",
"browser_switcher/browser_switcher_prefs.cc", "browser_switcher/browser_switcher_prefs.cc",
"browser_switcher/browser_switcher_prefs.h", "browser_switcher/browser_switcher_prefs.h",
"browser_switcher/browser_switcher_sitelist.cc", "browser_switcher/browser_switcher_sitelist.cc",
......
// Copyright 2018 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/browser_switcher/alternative_browser_driver.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/process/launch.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/browser/browser_switcher/alternative_browser_launcher.h"
#include "url/gurl.h"
#include <ddeml.h>
#include <shellapi.h>
#include <shlobj.h>
#include <windows.h>
#include <wininet.h>
namespace browser_switcher {
namespace {
const wchar_t kUrlVarName[] = L"${url}";
const wchar_t kIExploreKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\IEXPLORE.EXE";
const wchar_t kFirefoxKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\firefox.exe";
// Opera does not register itself here for now but it's no harm to keep this.
const wchar_t kOperaKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\opera.exe";
const wchar_t kSafariKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\safari.exe";
const wchar_t kChromeKey[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe";
const wchar_t kIExploreDdeHost[] = L"IExplore";
const wchar_t kChromeVarName[] = L"${chrome}";
const wchar_t kIEVarName[] = L"${ie}";
const wchar_t kFirefoxVarName[] = L"${firefox}";
const wchar_t kOperaVarName[] = L"${opera}";
const wchar_t kSafariVarName[] = L"${safari}";
const struct {
const wchar_t* var_name;
const wchar_t* registry_key;
const wchar_t* dde_host;
} kBrowserVarMappings[] = {
{kChromeVarName, kChromeKey, L""},
{kIEVarName, kIExploreKey, kIExploreDdeHost},
{kFirefoxVarName, kFirefoxKey, L""},
{kOperaVarName, kOperaKey, L""},
{kSafariVarName, kSafariKey, L""},
};
// DDE Callback function which is not used in our case at all.
HDDEDATA CALLBACK DdeCallback(UINT type,
UINT format,
HCONV handle,
HSZ string1,
HSZ string2,
HDDEDATA data,
ULONG_PTR data1,
ULONG_PTR data2) {
return NULL;
}
void PercentEncodeCommas(std::wstring* url) {
size_t pos = url->find(L",");
while (pos != std::wstring::npos) {
url->replace(pos, 1, L"%2C");
pos = url->find(L",", pos);
}
}
std::wstring CompileCommandLine(const std::wstring& raw_command_line,
const GURL& url) {
std::wstring url_spec = base::UTF8ToWide(GURL(url).spec());
std::wstring command_line = raw_command_line;
size_t pos = command_line.find(kUrlVarName);
if (pos != command_line.npos) {
command_line = command_line.replace(pos, wcslen(kUrlVarName), url_spec);
} else {
if (command_line.empty())
command_line = url_spec;
else
command_line.append(L" ").append(url_spec);
}
return command_line;
}
std::wstring GetBrowserLocation(const wchar_t* regkey_name) {
DCHECK(regkey_name);
base::win::RegKey key;
if (ERROR_SUCCESS != key.Open(HKEY_LOCAL_MACHINE, regkey_name, KEY_READ) &&
ERROR_SUCCESS != key.Open(HKEY_CURRENT_USER, regkey_name, KEY_READ)) {
LOG(ERROR) << "Could not open registry key " << regkey_name
<< "! Error Code:" << GetLastError();
return std::wstring();
}
std::wstring location;
if (ERROR_SUCCESS != key.ReadValue(NULL, &location))
return std::wstring();
return location;
}
} // namespace
AlternativeBrowserDriver::~AlternativeBrowserDriver() {}
AlternativeBrowserDriverImpl::AlternativeBrowserDriverImpl() {}
AlternativeBrowserDriverImpl::~AlternativeBrowserDriverImpl() {}
void AlternativeBrowserDriverImpl::SetBrowserPath(base::StringPiece path) {
browser_path_ = base::UTF8ToWide(path);
dde_host_ = std::wstring();
if (browser_path_.empty()) {
browser_path_ = GetBrowserLocation(kIExploreKey);
dde_host_ = kIExploreDdeHost;
return;
}
for (const auto& mapping : kBrowserVarMappings) {
if (browser_path_.compare(mapping.var_name)) {
browser_path_ = GetBrowserLocation(mapping.registry_key);
dde_host_ = mapping.dde_host;
}
}
}
void AlternativeBrowserDriverImpl::SetBrowserParameters(
base::StringPiece parameters) {
browser_params_ = base::UTF8ToWide(parameters);
}
bool AlternativeBrowserDriverImpl::TryLaunch(const GURL& url) {
VLOG(2) << "Launching alternative browser...";
VLOG(2) << " path = " << browser_path_;
VLOG(2) << " parameters = " << browser_params_;
VLOG(2) << " dde_host = " << dde_host_;
VLOG(2) << " url = " << url.spec();
return (TryLaunchWithDde(url) || TryLaunchWithExec(url));
}
bool AlternativeBrowserDriverImpl::TryLaunchWithDde(const GURL& url) {
DWORD dde_instance = 0;
if (DdeInitialize(&dde_instance, DdeCallback, CBF_FAIL_ALLSVRXACTIONS, 0) !=
DMLERR_NO_ERROR) {
return false;
}
bool success = false;
HCONV openurl_service_instance;
HCONV activate_service_instance;
{
HSZ service =
DdeCreateStringHandle(dde_instance, dde_host_.c_str(), CP_WINUNICODE);
HSZ openurl_topic =
DdeCreateStringHandle(dde_instance, L"WWW_OpenURL", CP_WINUNICODE);
HSZ activate_topic =
DdeCreateStringHandle(dde_instance, L"WWW_Activate", CP_WINUNICODE);
openurl_service_instance =
DdeConnect(dde_instance, service, openurl_topic, NULL);
activate_service_instance =
DdeConnect(dde_instance, service, activate_topic, NULL);
DdeFreeStringHandle(dde_instance, service);
DdeFreeStringHandle(dde_instance, openurl_topic);
DdeFreeStringHandle(dde_instance, activate_topic);
}
if (openurl_service_instance) {
// Percent-encode commas and spaces because those mean something else
// for the WWW_OpenURL verb and the url is trimmed on the first one.
// Spaces are already encoded by GURL.
std::wstring encoded_url(base::UTF8ToWide(url.spec()));
PercentEncodeCommas(&encoded_url);
success =
DdeClientTransaction(
reinterpret_cast<LPBYTE>(const_cast<wchar_t*>(encoded_url.data())),
encoded_url.size() * sizeof(wchar_t), openurl_service_instance, 0,
0, XTYP_EXECUTE, TIMEOUT_ASYNC, NULL) != 0;
DdeDisconnect(openurl_service_instance);
if (activate_service_instance) {
if (success) {
// Bring window to the front.
wchar_t cmd[] = L"0xFFFFFFFF,0x0";
DdeClientTransaction(reinterpret_cast<LPBYTE>(cmd), sizeof(cmd),
activate_service_instance, 0, 0, XTYP_EXECUTE,
TIMEOUT_ASYNC, NULL);
}
DdeDisconnect(activate_service_instance);
}
}
DdeUninitialize(dde_instance);
return success;
}
bool AlternativeBrowserDriverImpl::TryLaunchWithExec(const GURL& url) {
base::CommandLine cmd_line = base::CommandLine(base::FilePath(browser_path_));
cmd_line.AppendArgNative(
base::WideToUTF16(CompileCommandLine(browser_params_, url)));
base::LaunchOptions options;
if (!base::LaunchProcess(cmd_line, options).IsValid()) {
LOG(ERROR) << "Could not start the alternative browser! Error: "
<< GetLastError();
return false;
}
return true;
}
} // namespace browser_switcher
// Copyright 2018 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.
#ifndef CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_DRIVER_H_
#define CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_DRIVER_H_
#include "base/macros.h"
#include "base/strings/string_piece_forward.h"
class GURL;
namespace browser_switcher {
// Interface for a helper class that does I/O operations for an
// |AlternativeBrowserLauncher|.
//
// - Reading from the Windows Registry
// - Communicating with an external process using the DDE protocol
// - Launching an external process
class AlternativeBrowserDriver {
public:
virtual ~AlternativeBrowserDriver();
// Updates the executable path that will be used for the browser when it is
// launched. |path| is not necessarily a valid file path. It may be a
// placeholder such as "${ie}".
virtual void SetBrowserPath(base::StringPiece path) = 0;
// Updates the command-line parameters to give to the browser when it is
// launched.
virtual void SetBrowserParameters(base::StringPiece parameters) = 0;
// Tries to launch |browser| at the specified URL with DDE, using whatever
// method is most appropriate.
virtual bool TryLaunch(const GURL& url) = 0;
};
// Default concrete implementation for |AlternativeBrowserDriver|. This uses
// Windows primitives to access
class AlternativeBrowserDriverImpl : public AlternativeBrowserDriver {
public:
AlternativeBrowserDriverImpl();
~AlternativeBrowserDriverImpl() override;
// AlternativeBrowserDriver
void SetBrowserPath(base::StringPiece path) override;
void SetBrowserParameters(base::StringPiece parameters) override;
bool TryLaunch(const GURL& url) override;
private:
// Tries to launch |browser| at the specified URL with DDE, using whatever
bool TryLaunchWithDde(const GURL& url);
bool TryLaunchWithExec(const GURL& url);
// Info on how to launch the currently-selected alternate browser.
std::wstring browser_path_;
std::wstring browser_params_;
std::wstring dde_host_;
DISALLOW_COPY_AND_ASSIGN(AlternativeBrowserDriverImpl);
};
} // namespace browser_switcher
#endif // CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_DRIVER_H_
// Copyright 2018 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/browser_switcher/alternative_browser_launcher.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_switcher/browser_switcher_prefs.h"
#include "components/prefs/pref_service.h"
#include "url/gurl.h"
namespace browser_switcher {
AlternativeBrowserLauncher::AlternativeBrowserLauncher(PrefService* prefs)
: AlternativeBrowserLauncher(
prefs,
std::make_unique<AlternativeBrowserDriverImpl>()) {}
AlternativeBrowserLauncher::AlternativeBrowserLauncher(
PrefService* prefs,
std::unique_ptr<AlternativeBrowserDriver> driver)
: prefs_(prefs), driver_(std::move(driver)) {
DCHECK(prefs_);
DCHECK(driver_);
change_registrar_.Init(prefs);
change_registrar_.Add(
prefs::kAlternativeBrowserPath,
base::BindRepeating(&AlternativeBrowserLauncher::OnAltBrowserPathChanged,
base::Unretained(this)));
change_registrar_.Add(
prefs::kAlternativeBrowserParameters,
base::BindRepeating(
&AlternativeBrowserLauncher::OnAltBrowserParametersChanged,
base::Unretained(this)));
// Ensure |alt_browser_| is initialized.
OnAltBrowserPathChanged();
OnAltBrowserParametersChanged();
}
AlternativeBrowserLauncher::~AlternativeBrowserLauncher() {}
void AlternativeBrowserLauncher::OnAltBrowserPathChanged() {
// This string could be a variable, e.g. "${ie}". Let the driver decide what
// to do with it.
driver_->SetBrowserPath(prefs_->GetString(prefs::kAlternativeBrowserPath));
}
void AlternativeBrowserLauncher::OnAltBrowserParametersChanged() {
// This string could contain a placeholder, e.g. "${url}". Let the driver
// decide what to do with it.
driver_->SetBrowserParameters(
prefs_->GetString(prefs::kAlternativeBrowserParameters));
}
bool AlternativeBrowserLauncher::Launch(const GURL& url) const {
return driver_->TryLaunch(url);
}
} // namespace browser_switcher
// Copyright 2018 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.
#ifndef CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_LAUNCHER_H_
#define CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_LAUNCHER_H_
#include "base/macros.h"
#include "chrome/browser/browser_switcher/alternative_browser_driver.h"
#include "components/prefs/pref_change_registrar.h"
#include "content/public/browser/web_contents_observer.h"
class PrefService;
class GURL;
namespace browser_switcher {
// Used to launch an appropriate alternative browser based on policy/pref
// values.
//
// Delegates I/O operations to an |AlternativeBrowserDriver|.
class AlternativeBrowserLauncher : public content::WebContentsObserver {
public:
explicit AlternativeBrowserLauncher(PrefService* prefs);
AlternativeBrowserLauncher(PrefService* prefs,
std::unique_ptr<AlternativeBrowserDriver> driver);
~AlternativeBrowserLauncher() override;
// Opens |url| in an alternative browser. Returns true on success, false on
// error.
bool Launch(const GURL& url) const;
private:
void OnAltBrowserPathChanged();
void OnAltBrowserParametersChanged();
PrefService* const prefs_;
PrefChangeRegistrar change_registrar_;
const std::unique_ptr<AlternativeBrowserDriver> driver_;
DISALLOW_COPY_AND_ASSIGN(AlternativeBrowserLauncher);
};
} // namespace browser_switcher
#endif // CHROME_BROWSER_BROWSER_SWITCHER_ALTERNATIVE_BROWSER_LAUNCHER_H_
// Copyright 2018 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/browser_switcher/alternative_browser_launcher.h"
#include "base/values.h"
#include "chrome/browser/browser_switcher/browser_switcher_prefs.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using ::testing::Invoke;
using ::testing::Return;
using ::testing::_;
namespace browser_switcher {
namespace {
const char kExampleDotCom[] = "http://example.com/";
class MockAlternativeBrowserDriver : public AlternativeBrowserDriver {
public:
MockAlternativeBrowserDriver() = default;
~MockAlternativeBrowserDriver() override = default;
MOCK_METHOD1(SetBrowserPath, void(base::StringPiece));
MOCK_METHOD1(SetBrowserParameters, void(base::StringPiece));
MOCK_METHOD1(TryLaunch, bool(const GURL&));
};
} // namespace
class AlternativeBrowserLauncherTest : public testing::Test {
public:
void SetUp() override {
prefs_.registry()->RegisterStringPref(prefs::kAlternativeBrowserPath, "");
prefs_.registry()->RegisterStringPref(prefs::kAlternativeBrowserParameters,
"");
driver_ = new MockAlternativeBrowserDriver();
EXPECT_CALL(*driver_, SetBrowserPath(_));
EXPECT_CALL(*driver_, SetBrowserParameters(_));
launcher_ = std::make_unique<AlternativeBrowserLauncher>(
&prefs_, std::unique_ptr<AlternativeBrowserDriver>(driver_));
}
PrefService* prefs() { return &prefs_; }
AlternativeBrowserLauncher* launcher() { return launcher_.get(); }
MockAlternativeBrowserDriver& driver() { return *driver_; }
private:
TestingPrefServiceSimple prefs_;
std::unique_ptr<AlternativeBrowserLauncher> launcher_;
MockAlternativeBrowserDriver* driver_;
};
TEST_F(AlternativeBrowserLauncherTest, LaunchSucceeds) {
EXPECT_CALL(driver(), TryLaunch(_)).WillOnce(Invoke([](const GURL& url) {
EXPECT_EQ(kExampleDotCom, url.spec());
return true;
}));
EXPECT_TRUE(launcher()->Launch(GURL(kExampleDotCom)));
}
TEST_F(AlternativeBrowserLauncherTest, LaunchFails) {
EXPECT_CALL(driver(), TryLaunch(_)).WillOnce(Return(false));
EXPECT_FALSE(launcher()->Launch(GURL(kExampleDotCom)));
}
TEST_F(AlternativeBrowserLauncherTest, LaunchPicksUpPrefChanges) {
EXPECT_CALL(driver(), SetBrowserPath(_))
.WillOnce(
Invoke([](base::StringPiece path) { EXPECT_EQ("bogus.exe", path); }));
prefs()->SetString(prefs::kAlternativeBrowserPath, "bogus.exe");
EXPECT_CALL(driver(), SetBrowserParameters(_))
.WillOnce(Invoke([](base::StringPiece parameters) {
EXPECT_EQ("--single-process", parameters);
}));
prefs()->SetString(prefs::kAlternativeBrowserParameters, "--single-process");
}
} // namespace browser_switcher
...@@ -9,6 +9,16 @@ ...@@ -9,6 +9,16 @@
namespace browser_switcher { namespace browser_switcher {
namespace prefs { namespace prefs {
// Path to the executable of the alternative browser, or one of "${chrome}",
// "${ie}", "${firefox}", "${opera}", "${safari}".
const char kAlternativeBrowserPath[] =
"browser_switcher.alternative_browser_path";
// Arguments to pass to the alternative browser when invoking it via
// |ShellExecute()|.
const char kAlternativeBrowserParameters[] =
"browser_switcher.alternative_browser_parameters";
// List of host domain names to be opened in an alternative browser. // List of host domain names to be opened in an alternative browser.
const char kUrlList[] = "browser_switcher.url_list"; const char kUrlList[] = "browser_switcher.url_list";
...@@ -16,6 +26,8 @@ const char kUrlList[] = "browser_switcher.url_list"; ...@@ -16,6 +26,8 @@ const char kUrlList[] = "browser_switcher.url_list";
const char kUrlGreylist[] = "browser_switcher.url_greylist"; const char kUrlGreylist[] = "browser_switcher.url_greylist";
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterStringPref(prefs::kAlternativeBrowserPath, "");
registry->RegisterStringPref(prefs::kAlternativeBrowserParameters, "");
registry->RegisterListPref(prefs::kUrlList); registry->RegisterListPref(prefs::kUrlList);
registry->RegisterListPref(prefs::kUrlGreylist); registry->RegisterListPref(prefs::kUrlGreylist);
} }
......
...@@ -12,6 +12,8 @@ class PrefRegistrySyncable; ...@@ -12,6 +12,8 @@ class PrefRegistrySyncable;
namespace browser_switcher { namespace browser_switcher {
namespace prefs { namespace prefs {
extern const char kAlternativeBrowserPath[];
extern const char kAlternativeBrowserParameters[];
extern const char kUrlList[]; extern const char kUrlList[];
extern const char kUrlGreylist[]; extern const char kUrlGreylist[];
......
...@@ -2787,6 +2787,7 @@ test("unit_tests") { ...@@ -2787,6 +2787,7 @@ test("unit_tests") {
if (is_win) { if (is_win) {
assert(toolkit_views) assert(toolkit_views)
sources += [ sources += [
"../browser/browser_switcher/alternative_browser_launcher_unittest.cc",
"../browser/browser_switcher/browser_switcher_sitelist_unittest.cc", "../browser/browser_switcher/browser_switcher_sitelist_unittest.cc",
"../browser/media/widevine_hardware_caps_win_unittest.cc", "../browser/media/widevine_hardware_caps_win_unittest.cc",
"../browser/notifications/win/mock_notification_image_retainer.cc", "../browser/notifications/win/mock_notification_image_retainer.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment