blob: 55116062ce6a222e0a811417e7e7a066693394af [file] [log] [blame]
// Copyright (c) 2019 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/ui/webui/browser_switcher/browser_switch_ui.h"
#include <memory>
#include "base/bind.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/values.h"
#include "chrome/browser/browser_switcher/alternative_browser_driver.h"
#include "chrome/browser/browser_switcher/browser_switcher_service.h"
#include "chrome/browser/browser_switcher/browser_switcher_service_factory.h"
#include "chrome/browser/browser_switcher/browser_switcher_sitelist.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/dark_mode_handler.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/browser_resources.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/grit/components_resources.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace {
void GotoNewTabPage(content::WebContents* web_contents) {
GURL url(chrome::kChromeUINewTabURL);
content::OpenURLParams params(url, content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false);
web_contents->OpenURL(params);
}
// Returns true if there's only 1 tab left open in this profile. Incognito
// window tabs count as the same profile.
bool IsLastTab(const Profile* profile) {
profile = profile->GetOriginalProfile();
int tab_count = 0;
for (const Browser* browser : *BrowserList::GetInstance()) {
if (browser->profile()->GetOriginalProfile() != profile)
continue;
tab_count += browser->tab_strip_model()->count();
if (tab_count > 1)
return false;
}
return true;
}
browser_switcher::BrowserSwitcherService* GetBrowserSwitcherService(
content::WebUI* web_ui) {
return browser_switcher::BrowserSwitcherServiceFactory::GetForBrowserContext(
web_ui->GetWebContents()->GetBrowserContext());
}
content::WebUIDataSource* CreateBrowserSwitchUIHTMLSource(
content::WebUI* web_ui) {
content::WebUIDataSource* source =
content::WebUIDataSource::Create(chrome::kChromeUIBrowserSwitchHost);
auto* service = GetBrowserSwitcherService(web_ui);
source->AddInteger("launchDelay", service->prefs().GetDelay());
std::string browser_name = service->driver()->GetBrowserName();
source->AddString("browserName", browser_name);
if (browser_name.empty()) {
// Browser name could not be auto-detected. Say "alternative browser"
// instead of naming the browser.
source->AddLocalizedString(
"countdownTitle",
IDS_ABOUT_BROWSER_SWITCH_COUNTDOWN_TITLE_UNKNOWN_BROWSER);
source->AddLocalizedString(
"description", IDS_ABOUT_BROWSER_SWITCH_DESCRIPTION_UNKNOWN_BROWSER);
source->AddLocalizedString(
"errorTitle", IDS_ABOUT_BROWSER_SWITCH_ERROR_TITLE_UNKNOWN_BROWSER);
source->AddLocalizedString(
"genericError", IDS_ABOUT_BROWSER_SWITCH_GENERIC_ERROR_UNKNOWN_BROWSER);
source->AddLocalizedString(
"openingTitle", IDS_ABOUT_BROWSER_SWITCH_OPENING_TITLE_UNKNOWN_BROWSER);
} else {
// Browser name was auto-detected. Name it in the text.
source->AddLocalizedString(
"countdownTitle",
IDS_ABOUT_BROWSER_SWITCH_COUNTDOWN_TITLE_KNOWN_BROWSER);
source->AddLocalizedString(
"description", IDS_ABOUT_BROWSER_SWITCH_DESCRIPTION_KNOWN_BROWSER);
source->AddLocalizedString(
"errorTitle", IDS_ABOUT_BROWSER_SWITCH_ERROR_TITLE_KNOWN_BROWSER);
source->AddLocalizedString(
"genericError", IDS_ABOUT_BROWSER_SWITCH_GENERIC_ERROR_KNOWN_BROWSER);
source->AddLocalizedString(
"openingTitle", IDS_ABOUT_BROWSER_SWITCH_OPENING_TITLE_KNOWN_BROWSER);
}
source->AddLocalizedString("protocolError",
IDS_ABOUT_BROWSER_SWITCH_PROTOCOL_ERROR);
source->AddLocalizedString("title", IDS_ABOUT_BROWSER_SWITCH_TITLE);
source->AddResourcePath("app.html", IDR_BROWSER_SWITCHER_APP_HTML);
source->AddResourcePath("app.js", IDR_BROWSER_SWITCHER_APP_JS);
source->AddResourcePath("browser_switch.html",
IDR_BROWSER_SWITCHER_BROWSER_SWITCH_HTML);
source->AddResourcePath("browser_switcher_proxy.html",
IDR_BROWSER_SWITCHER_BROWSER_SWITCHER_PROXY_HTML);
source->AddResourcePath("browser_switcher_proxy.js",
IDR_BROWSER_SWITCHER_BROWSER_SWITCHER_PROXY_JS);
source->SetDefaultResource(IDR_BROWSER_SWITCHER_BROWSER_SWITCH_HTML);
source->SetJsonPath("strings.js");
source->UseGzip();
return source;
}
class BrowserSwitchHandler : public content::WebUIMessageHandler {
public:
BrowserSwitchHandler();
~BrowserSwitchHandler() override;
// WebUIMessageHandler
void RegisterMessages() override;
private:
// Launches the given URL in the configured alternative browser. Acts as a
// bridge for |AlternativeBrowserDriver::TryLaunch()|. Then, if that succeeds,
// closes the current tab.
//
// If it fails, the JavaScript promise is rejected. If it succeeds, the
// JavaScript promise is not resolved, because we close the tab anyways.
void HandleLaunchAlternativeBrowserAndCloseTab(const base::ListValue* args);
// Navigates to the New Tab Page.
void HandleGotoNewTabPage(const base::ListValue* args);
DISALLOW_COPY_AND_ASSIGN(BrowserSwitchHandler);
};
BrowserSwitchHandler::BrowserSwitchHandler() = default;
BrowserSwitchHandler::~BrowserSwitchHandler() = default;
void BrowserSwitchHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"launchAlternativeBrowserAndCloseTab",
base::BindRepeating(
&BrowserSwitchHandler::HandleLaunchAlternativeBrowserAndCloseTab,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"gotoNewTabPage",
base::BindRepeating(&BrowserSwitchHandler::HandleGotoNewTabPage,
base::Unretained(this)));
}
void BrowserSwitchHandler::HandleLaunchAlternativeBrowserAndCloseTab(
const base::ListValue* args) {
DCHECK(args);
AllowJavascript();
std::string callback_id = args->GetList()[0].GetString();
std::string url_spec = args->GetList()[1].GetString();
GURL url(url_spec);
auto* service = GetBrowserSwitcherService(web_ui());
bool should_switch = service->sitelist()->ShouldSwitch(url);
if (!url.is_valid() || !should_switch) {
// This URL shouldn't open in an alternative browser. Abort launch, because
// something weird is going on (e.g. race condition from a new sitelist
// being loaded).
RejectJavascriptCallback(args->GetList()[0], base::Value());
return;
}
bool success;
{
SCOPED_UMA_HISTOGRAM_TIMER("BrowserSwitcher.LaunchTime");
success = service->driver()->TryLaunch(url);
UMA_HISTOGRAM_BOOLEAN("BrowserSwitcher.LaunchSuccess", success);
}
if (!success) {
RejectJavascriptCallback(args->GetList()[0], base::Value());
return;
}
auto* profile = Profile::FromWebUI(web_ui());
if (service->prefs().KeepLastTab() && IsLastTab(profile)) {
GotoNewTabPage(web_ui()->GetWebContents());
} else {
// We don't need to resolve the promise, because the tab will close anyways.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&content::WebContents::ClosePage,
base::Unretained(web_ui()->GetWebContents())));
}
}
void BrowserSwitchHandler::HandleGotoNewTabPage(const base::ListValue* args) {
GotoNewTabPage(web_ui()->GetWebContents());
}
} // namespace
BrowserSwitchUI::BrowserSwitchUI(content::WebUI* web_ui)
: WebUIController(web_ui) {
content::WebUIDataSource* data_source =
CreateBrowserSwitchUIHTMLSource(web_ui);
DarkModeHandler::Initialize(web_ui, data_source);
web_ui->AddMessageHandler(std::make_unique<BrowserSwitchHandler>());
// Set up the about:browser-switch source.
Profile* profile = Profile::FromWebUI(web_ui);
content::WebUIDataSource::Add(profile, data_source);
}