blob: 4928b775160f719219b5523843d84c62fe213150 [file] [log] [blame]
// Copyright 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/chromeos/apps/intent_helper/common_apps_navigation_throttle.h"
#include <utility>
#include "base/bind.h"
#include "base/stl_util.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/intent_helper/apps_navigation_types.h"
#include "chrome/browser/apps/intent_helper/intent_picker_auto_display_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/intent_picker_tab_helper.h"
#include "chrome/common/chrome_features.h"
#include "chrome/services/app_service/public/mojom/types.mojom.h"
#include "chromeos/constants/chromeos_switches.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/display/types/display_constants.h"
namespace {
apps::PickerEntryType GetPickerEntryType(apps::mojom::AppType app_type) {
apps::PickerEntryType picker_entry_type = apps::PickerEntryType::kUnknown;
switch (app_type) {
case apps::mojom::AppType::kUnknown:
case apps::mojom::AppType::kBuiltIn:
case apps::mojom::AppType::kCrostini:
case apps::mojom::AppType::kPluginVm:
case apps::mojom::AppType::kExtension:
case apps::mojom::AppType::kLacros:
break;
case apps::mojom::AppType::kArc:
picker_entry_type = apps::PickerEntryType::kArc;
break;
case apps::mojom::AppType::kWeb:
picker_entry_type = apps::PickerEntryType::kWeb;
break;
case apps::mojom::AppType::kMacNative:
picker_entry_type = apps::PickerEntryType::kMacNative;
break;
}
return picker_entry_type;
}
} // namespace
namespace apps {
// static
std::unique_ptr<apps::AppsNavigationThrottle>
CommonAppsNavigationThrottle::MaybeCreate(content::NavigationHandle* handle) {
if (!handle->IsInMainFrame())
return nullptr;
content::WebContents* web_contents = handle->GetWebContents();
if (!apps::AppsNavigationThrottle::CanCreate(web_contents))
return nullptr;
return std::make_unique<CommonAppsNavigationThrottle>(handle);
}
// static
void CommonAppsNavigationThrottle::ShowIntentPickerBubble(
content::WebContents* web_contents,
IntentPickerAutoDisplayService* ui_auto_display_service,
const GURL& url) {
std::vector<apps::IntentPickerAppInfo> apps_for_picker =
FindAllAppsForUrl(web_contents, url, {});
IntentPickerTabHelper::LoadAppIcons(
web_contents, std::move(apps_for_picker),
base::BindOnce(&OnAppIconsLoaded, web_contents, ui_auto_display_service,
url));
}
// static
void CommonAppsNavigationThrottle::OnIntentPickerClosed(
content::WebContents* web_contents,
IntentPickerAutoDisplayService* ui_auto_display_service,
const GURL& url,
const std::string& launch_name,
apps::PickerEntryType entry_type,
apps::IntentPickerCloseReason close_reason,
bool should_persist) {
if (chromeos::switches::IsTabletFormFactor() && should_persist) {
// On devices of tablet form factor, until the user has decided to persist
// the setting, the browser-side intent picker should always be seen.
auto platform = IntentPickerAutoDisplayPref::Platform::kNone;
if (entry_type == apps::PickerEntryType::kArc) {
platform = IntentPickerAutoDisplayPref::Platform::kArc;
} else if (entry_type == apps::PickerEntryType::kUnknown &&
close_reason == apps::IntentPickerCloseReason::STAY_IN_CHROME) {
platform = IntentPickerAutoDisplayPref::Platform::kChrome;
}
IntentPickerAutoDisplayService::Get(
Profile::FromBrowserContext(web_contents->GetBrowserContext()))
->UpdatePlatformForTablets(url, platform);
}
const bool should_launch_app =
close_reason == apps::IntentPickerCloseReason::OPEN_APP;
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
if (!proxy)
return;
if (should_persist)
proxy->AddPreferredApp(launch_name, url);
if (should_launch_app) {
// TODO(crbug.com/853604): Distinguish the source from link and omnibox.
apps::mojom::LaunchSource launch_source =
apps::mojom::LaunchSource::kFromLink;
proxy->LaunchAppWithUrl(
launch_name,
GetEventFlags(apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW,
/*prefer_container=*/true),
url, launch_source, display::kDefaultDisplayId);
CloseOrGoBack(web_contents);
}
}
// static
void CommonAppsNavigationThrottle::OnAppIconsLoaded(
content::WebContents* web_contents,
IntentPickerAutoDisplayService* ui_auto_display_service,
const GURL& url,
std::vector<apps::IntentPickerAppInfo> apps) {
apps::AppsNavigationThrottle::ShowIntentPickerBubbleForApps(
web_contents, std::move(apps),
/*show_stay_in_chrome=*/true,
/*show_remember_selection=*/true,
base::BindOnce(&OnIntentPickerClosed, web_contents,
ui_auto_display_service, url));
}
CommonAppsNavigationThrottle::CommonAppsNavigationThrottle(
content::NavigationHandle* navigation_handle)
: apps::AppsNavigationThrottle(navigation_handle) {}
CommonAppsNavigationThrottle::~CommonAppsNavigationThrottle() = default;
std::vector<apps::IntentPickerAppInfo>
CommonAppsNavigationThrottle::FindAppsForUrl(
content::WebContents* web_contents,
const GURL& url,
std::vector<apps::IntentPickerAppInfo> apps) {
return FindAllAppsForUrl(web_contents, url, std::move(apps));
}
// static
std::vector<apps::IntentPickerAppInfo>
CommonAppsNavigationThrottle::FindAllAppsForUrl(
content::WebContents* web_contents,
const GURL& url,
std::vector<apps::IntentPickerAppInfo> apps) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
if (!proxy)
return apps;
std::vector<std::string> app_ids = proxy->GetAppIdsForUrl(url);
for (const std::string& app_id : app_ids) {
proxy->AppRegistryCache().ForOneApp(
app_id, [&apps](const apps::AppUpdate& update) {
apps.emplace(apps.begin(), GetPickerEntryType(update.AppType()),
gfx::Image(), update.AppId(), update.Name());
});
}
return apps;
}
apps::AppsNavigationThrottle::PickerShowState
CommonAppsNavigationThrottle::GetPickerShowState(
const std::vector<apps::IntentPickerAppInfo>& apps_for_picker,
content::WebContents* web_contents,
const GURL& url) {
return ShouldAutoDisplayUi(apps_for_picker, web_contents, url) &&
navigate_from_link()
? PickerShowState::kPopOut
: PickerShowState::kOmnibox;
}
IntentPickerResponse CommonAppsNavigationThrottle::GetOnPickerClosedCallback(
content::WebContents* web_contents,
IntentPickerAutoDisplayService* ui_auto_display_service,
const GURL& url) {
return base::BindOnce(&OnIntentPickerClosed, web_contents,
ui_auto_display_service, url);
}
bool CommonAppsNavigationThrottle::ShouldDeferNavigation(
content::NavigationHandle* handle) {
content::WebContents* web_contents = handle->GetWebContents();
const GURL& url = handle->GetURL();
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
if (!proxy)
return false;
std::vector<std::string> app_ids = proxy->GetAppIdsForUrl(url);
if (app_ids.empty())
return false;
if (navigate_from_link()) {
auto preferred_app_id = proxy->PreferredApps().FindPreferredAppForUrl(url);
if (preferred_app_id.has_value() &&
base::Contains(app_ids, preferred_app_id.value())) {
// Only automatically launch PWA if the flag is on.
auto app_type =
proxy->AppRegistryCache().GetAppType(preferred_app_id.value());
if (app_type == apps::mojom::AppType::kArc ||
(app_type == apps::mojom::AppType::kWeb &&
base::FeatureList::IsEnabled(
features::kIntentPickerPWAPersistence))) {
auto launch_source = apps::mojom::LaunchSource::kFromLink;
proxy->LaunchAppWithUrl(
preferred_app_id.value(),
GetEventFlags(apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW,
/*prefer_container=*/true),
url, launch_source, display::kDefaultDisplayId);
CloseOrGoBack(web_contents);
return true;
}
}
}
std::vector<apps::IntentPickerAppInfo> apps_for_picker =
FindAllAppsForUrl(web_contents, url, {});
if (apps_for_picker.empty())
return false;
IntentPickerTabHelper::LoadAppIcons(
web_contents, std::move(apps_for_picker),
base::BindOnce(
&CommonAppsNavigationThrottle::OnDeferredNavigationProcessed,
weak_factory_.GetWeakPtr()));
return true;
}
void CommonAppsNavigationThrottle::OnDeferredNavigationProcessed(
std::vector<apps::IntentPickerAppInfo> apps) {
content::NavigationHandle* handle = navigation_handle();
content::WebContents* web_contents = handle->GetWebContents();
const GURL& url = handle->GetURL();
ShowIntentPickerForApps(web_contents, ui_auto_display_service_, url,
std::move(apps),
base::BindOnce(&OnIntentPickerClosed, web_contents,
ui_auto_display_service_, url));
// We are about to resume the navigation, which may destroy this object.
Resume();
}
bool CommonAppsNavigationThrottle::ShouldAutoDisplayUi(
const std::vector<apps::IntentPickerAppInfo>& apps_for_picker,
content::WebContents* web_contents,
const GURL& url) {
if (apps_for_picker.empty())
return false;
// On devices with tablet form factor we should not pop out the intent
// picker if Chrome has been chosen by the user as the platform for this URL.
if (chromeos::switches::IsTabletFormFactor()) {
if (ui_auto_display_service_->GetLastUsedPlatformForTablets(url) ==
IntentPickerAutoDisplayPref::Platform::kChrome) {
return false;
}
}
// If we only have PWAs in the app list, do not show the intent picker.
// Instead just show the omnibox icon. This is to reduce annoyance to users
// until "Remember my choice" is available for desktop PWAs.
// TODO(crbug.com/826982): show the intent picker when the app registry is
// available to persist "Remember my choice" for PWAs.
if (!base::FeatureList::IsEnabled(features::kIntentPickerPWAPersistence) &&
ContainsOnlyPwasAndMacApps(apps_for_picker)) {
return false;
}
// If the preferred app is use browser, do not show the intent picker.
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile);
if (proxy) {
auto preferred_app_id = proxy->PreferredApps().FindPreferredAppForUrl(url);
if (preferred_app_id.has_value() &&
preferred_app_id.value() == kUseBrowserForLink) {
return false;
}
}
DCHECK(ui_auto_display_service_);
return ui_auto_display_service_->ShouldAutoDisplayUi(url);
}
} // namespace apps