blob: e01f0b44305f6072f83f49b396af1f9ed6a7f3ed [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/apps/intent_helper/intent_picker_helpers.h"
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/task/task_traits.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.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/intent_helper/intent_picker_auto_display_prefs.h"
#include "chrome/browser/apps/intent_helper/intent_picker_constants.h"
#include "chrome/browser/apps/intent_helper/intent_picker_features.h"
#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/intent_picker_tab_helper.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "components/feature_engagement/public/tracker.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/gfx/favicon_size.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.h"
#include "chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.h"
#elif BUILDFLAG(IS_MAC)
#include "base/task/thread_pool.h"
#include "chrome/browser/apps/intent_helper/mac_intent_picker_helpers.h"
#endif // BUILDFLAG(IS_CHROMEOS)
namespace apps {
namespace {
void AppendAppsForUrlSync(
content::WebContents* web_contents,
const GURL& url,
base::OnceCallback<void(std::vector<IntentPickerAppInfo>)> callback,
std::vector<IntentPickerAppInfo> apps) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
std::vector<std::string> app_ids =
proxy->GetAppIdsForUrl(url, /*exclude_browsers=*/true);
for (const std::string& app_id : app_ids) {
proxy->AppRegistryCache().ForOneApp(
app_id, [&apps](const AppUpdate& update) {
apps.emplace(apps.begin(), GetPickerEntryType(update.AppType()),
ui::ImageModel(), update.AppId(), update.Name());
});
}
std::move(callback).Run(std::move(apps));
}
void FindAppsForUrl(
content::WebContents* web_contents,
const GURL& url,
base::OnceCallback<void(std::vector<IntentPickerAppInfo>)> callback) {
auto append_apps =
[](base::WeakPtr<content::WebContents> web_contents, int commit_count,
const GURL& url,
base::OnceCallback<void(std::vector<IntentPickerAppInfo>)> callback,
std::vector<IntentPickerAppInfo> apps) {
if (!web_contents)
return;
IntentPickerTabHelper* helper =
IntentPickerTabHelper::FromWebContents(web_contents.get());
if (helper->commit_count() != commit_count)
return;
AppendAppsForUrlSync(web_contents.get(), url, std::move(callback),
std::move(apps));
};
IntentPickerTabHelper* helper =
IntentPickerTabHelper::FromWebContents(web_contents);
int commit_count = helper->commit_count();
#if BUILDFLAG(IS_MAC)
// On the Mac, if there is a Universal Link, it goes first. Jump to a worker
// thread to do this.
auto get_mac_app = [](const GURL& url) {
std::vector<IntentPickerAppInfo> apps;
if (absl::optional<IntentPickerAppInfo> mac_app = FindMacAppForUrl(url))
apps.push_back(std::move(mac_app.value()));
return apps;
};
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
base::BindOnce(get_mac_app, url),
base::BindOnce(append_apps, web_contents->GetWeakPtr(), commit_count, url,
std::move(callback)));
#else
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(append_apps, web_contents->GetWeakPtr(), commit_count, url,
std::move(callback), std::vector<IntentPickerAppInfo>()));
#endif // BUILDFLAG(IS_MAC)
}
void LaunchAppFromIntentPicker(content::WebContents* web_contents,
const GURL& url,
const std::string& launch_name,
PickerEntryType app_type) {
#if BUILDFLAG(IS_CHROMEOS)
LaunchAppFromIntentPickerChromeOs(web_contents, url, launch_name, app_type);
#else
if (base::FeatureList::IsEnabled(features::kLinkCapturingUiUpdate)) {
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
IntentPickerAutoDisplayPrefs::ResetIntentChipCounter(profile, url);
chrome::FindBrowserWithWebContents(web_contents)->window()
->NotifyFeatureEngagementEvent(kIntentChipOpensAppEvent);
}
switch (app_type) {
case PickerEntryType::kWeb:
web_app::ReparentWebContentsIntoAppBrowser(web_contents, launch_name);
break;
case PickerEntryType::kMacOs:
#if BUILDFLAG(IS_MAC)
LaunchMacApp(url, launch_name);
break;
#endif // BUILDFLAG(IS_MAC)
case PickerEntryType::kArc:
case PickerEntryType::kDevice:
case PickerEntryType::kUnknown:
NOTREACHED();
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
void OnIntentPickerClosed(base::WeakPtr<content::WebContents> web_contents,
const GURL& url,
const std::string& launch_name,
PickerEntryType entry_type,
IntentPickerCloseReason close_reason,
bool should_persist) {
if (!web_contents) {
return;
}
#if BUILDFLAG(IS_CHROMEOS)
OnIntentPickerClosedChromeOs(web_contents, PickerShowState::kOmnibox, url,
launch_name, entry_type, close_reason,
should_persist);
#else
const bool should_launch_app =
close_reason == apps::IntentPickerCloseReason::OPEN_APP;
if (should_launch_app) {
LaunchAppFromIntentPicker(web_contents.get(), url, launch_name, entry_type);
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
void OnAppIconsLoaded(content::WebContents* web_contents,
const GURL& url,
std::vector<IntentPickerAppInfo> apps) {
ShowIntentPickerBubbleForApps(
web_contents, std::move(apps),
#if BUILDFLAG(IS_CHROMEOS)
/*show_stay_in_chrome=*/true,
/*show_remember_selection=*/true,
#else
/*show_stay_in_chrome=*/false,
/*show_remember_selection=*/false,
#endif // BUILDFLAG(IS_CHROMEOS)
base::BindOnce(&OnIntentPickerClosed, web_contents->GetWeakPtr(), url));
}
void GetAppsForIntentPicker(
content::WebContents* web_contents,
base::OnceCallback<void(std::vector<IntentPickerAppInfo>)> callback) {
if (!ShouldCheckAppsForUrl(web_contents)) {
std::move(callback).Run({});
return;
}
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (!AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
std::move(callback).Run({});
return;
}
FindAppsForUrl(web_contents, web_contents->GetLastCommittedURL(),
std::move(callback));
}
void ShowIntentPickerOrLaunchAppImpl(content::WebContents* web_contents,
const GURL& url,
std::vector<IntentPickerAppInfo> apps) {
if (apps.empty())
return;
#if BUILDFLAG(IS_CHROMEOS)
apps::IntentHandlingMetrics::RecordIntentPickerIconEvent(
apps::IntentHandlingMetrics::IntentPickerIconEvent::kIconClicked);
#endif
if (apps.size() == 1) {
// If there is only a single available app, immediately launch it if either:
// - ShouldIntentChipSkipIntentPicker() is enabled, or
// - LinkCapturingUiUpdateEnabled() is enabled and the app is preferred for
// this link.
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
auto* proxy = AppServiceProxyFactory::GetForProfile(profile);
bool should_launch_for_preferred_app =
apps::features::LinkCapturingUiUpdateEnabled() &&
proxy->PreferredAppsList().FindPreferredAppForUrl(url) ==
apps[0].launch_name;
if (apps::features::ShouldIntentChipSkipIntentPicker() ||
should_launch_for_preferred_app) {
LaunchAppFromIntentPicker(web_contents, url, apps[0].launch_name,
apps[0].type);
return;
}
}
IntentPickerTabHelper::LoadAppIcons(
web_contents, std::move(apps),
base::BindOnce(&OnAppIconsLoaded, web_contents, url));
}
} // namespace
void MaybeShowIntentPicker(content::NavigationHandle* navigation_handle) {
content::WebContents* web_contents = navigation_handle->GetWebContents();
IntentPickerTabHelper* helper =
IntentPickerTabHelper::FromWebContents(web_contents);
int commit_count = helper->commit_count();
auto task = [](base::WeakPtr<content::WebContents> web_contents,
#if BUILDFLAG(IS_CHROMEOS)
NavigationInfo navigation_info,
#endif // BUILDFLAG(IS_CHROMEOS)
IntentPickerTabHelper* helper, int commit_count,
std::vector<IntentPickerAppInfo> apps) {
if (!web_contents)
return;
if (helper->commit_count() != commit_count)
return;
helper->ShowIconForApps(apps);
#if BUILDFLAG(IS_CHROMEOS)
MaybeShowIntentPickerBubble(navigation_info, std::move(apps));
#endif // BUILDFLAG(IS_CHROMEOS)
};
#if BUILDFLAG(IS_CHROMEOS)
NavigationInfo navigation_info = {
.web_contents = web_contents,
.url = navigation_handle->GetURL(),
.starting_url = GetStartingGURL(navigation_handle),
.is_navigate_from_link = IsNavigateFromLink(navigation_handle)};
#endif // BUILDFLAG(IS_CHROMEOS)
GetAppsForIntentPicker(web_contents,
base::BindOnce(task, web_contents->GetWeakPtr(),
#if BUILDFLAG(IS_CHROMEOS)
navigation_info,
#endif // BUILDFLAG(IS_CHROMEOS)
helper, commit_count));
}
void MaybeShowIntentPicker(content::WebContents* web_contents) {
IntentPickerTabHelper* helper =
IntentPickerTabHelper::FromWebContents(web_contents);
int commit_count = helper->commit_count();
auto task = [](base::WeakPtr<content::WebContents> web_contents,
IntentPickerTabHelper* helper, int commit_count,
std::vector<IntentPickerAppInfo> apps) {
if (!web_contents)
return;
if (helper->commit_count() != commit_count)
return;
helper->ShowIconForApps(apps);
};
GetAppsForIntentPicker(
web_contents,
base::BindOnce(task, web_contents->GetWeakPtr(), helper, commit_count));
}
void ShowIntentPickerOrLaunchApp(content::WebContents* web_contents,
const GURL& url) {
FindAppsForUrl(
web_contents, url,
base::BindOnce(&ShowIntentPickerOrLaunchAppImpl, web_contents, url));
}
bool IntentPickerPwaPersistenceEnabled() {
#if BUILDFLAG(IS_CHROMEOS)
return true;
#else
return false;
#endif
}
int GetIntentPickerBubbleIconSize() {
constexpr int kIntentPickerUiUpdateIconSize = 40;
return features::LinkCapturingUiUpdateEnabled()
? kIntentPickerUiUpdateIconSize
: gfx::kFaviconSize;
}
} // namespace apps