blob: a14c5b7313a20da159e516ae9f6b83f9417b0329 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <memory>
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt.h"
#include "chrome/browser/ui/views/permissions/exclusive_access_permission_prompt.h"
#include "chrome/browser/ui/views/permissions/permission_prompt_bubble.h"
#include "chrome/browser/ui/views/permissions/permission_prompt_chip.h"
#include "chrome/browser/ui/views/permissions/permission_prompt_quiet_icon.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/webui_url_constants.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/request_type.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "third_party/blink/public/common/features_generated.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/ui/views/permissions/permission_prompt_notifications_mac.h"
#include "chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.h"
#endif
namespace {
bool IsFullScreenMode(Browser* browser) {
DCHECK(browser);
// PWA uses the title bar as a substitute for LocationBarView.
if (web_app::AppBrowserController::IsWebApp(browser)) {
return false;
}
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
if (!browser_view) {
return false;
}
LocationBarView* location_bar = browser_view->GetLocationBarView();
return !location_bar || !location_bar->IsDrawn() ||
location_bar->GetWidget()->GetTopLevelWidget()->IsFullscreen();
}
LocationBarView* GetLocationBarView(Browser* browser) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
return browser_view ? browser_view->GetLocationBarView() : nullptr;
}
// A permission request should be auto-ignored if:
// - a user interacts with the LocationBar. The only exception is the NTP
// page where the user needs to press on a microphone icon to get a
// permission request.
// - The Lens Overlay is open covering the context. We suppress permission
// prompts until the Lens Overlay is closed.
bool ShouldIgnorePermissionRequest(
content::WebContents* web_contents,
Browser* browser,
permissions::PermissionPrompt::Delegate* delegate) {
DCHECK(web_contents);
DCHECK(browser);
// In case of the NTP, `WebContents::GetVisibleURL()` is equal to
// `chrome://newtab/`, but the `LocationBarView` will be empty.
if (web_contents->GetVisibleURL() == GURL(chrome::kChromeUINewTabURL)) {
return false;
}
LocationBarView* location_bar = GetLocationBarView(browser);
bool can_display_prompt = !(location_bar && location_bar->IsEditingOrEmpty());
LensOverlayController* lens_overlay_controller =
browser->tab_strip_model()
->GetActiveTab()
->GetTabFeatures()
->lens_overlay_controller();
// Don't show prompt if Lens Overlay is showing
// TODO(b/331940245): Refactor to be decoupled from LensOverlayController
if (lens_overlay_controller && lens_overlay_controller->IsOverlayShowing()) {
can_display_prompt = false;
}
permissions::PermissionUmaUtil::RecordPermissionPromptAttempt(
delegate->Requests(), can_display_prompt);
return !can_display_prompt;
}
bool ShouldUseChip(permissions::PermissionPrompt::Delegate* delegate) {
// Permission request chip should not be shown if `delegate->Requests()` were
// requested without a user gesture.
if (!permissions::PermissionUtil::HasUserGesture(delegate)) {
return false;
}
const auto& requests = delegate->Requests();
return std::ranges::all_of(
requests, [](const auto& request) {
return request
->GetRequestChipText(
permissions::PermissionRequest::ChipTextType::LOUD_REQUEST)
.has_value();
});
}
bool IsLocationBarDisplayed(Browser* browser) {
LocationBarView* lbv = GetLocationBarView(browser);
return lbv && lbv->IsDrawn() &&
!lbv->GetWidget()->GetTopLevelWidget()->IsFullscreen();
}
bool ShouldCurrentRequestUseQuietChip(
permissions::PermissionPrompt::Delegate* delegate) {
const auto& requests = delegate->Requests();
return std::ranges::all_of(
requests, [](const auto& request) {
return request->request_type() ==
permissions::RequestType::kNotifications ||
request->request_type() ==
permissions::RequestType::kGeolocation;
});
}
bool ShouldCurrentRequestUseExclusiveAccessUI(
permissions::PermissionPrompt::Delegate* delegate) {
const auto& requests = delegate->Requests();
return permissions::feature_params::kKeyboardLockPromptUIStyle.Get() &&
std::ranges::all_of(
requests, [](const auto& request) {
return request->request_type() ==
permissions::RequestType::kPointerLock ||
request->request_type() ==
permissions::RequestType::kKeyboardLock;
});
}
bool CanCurrentRequestUseModalUI(content::WebContents* web_contents) {
return tabs::TabInterface::GetFromContents(web_contents)->CanShowModalUI();
}
std::unique_ptr<permissions::PermissionPrompt> CreatePwaPrompt(
Browser* browser,
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
if (permissions::PermissionUtil::
ShouldCurrentRequestUsePermissionElementSecondaryUI(delegate)) {
// Run this check inside the if statement to avoid creating another prompt
// type for embedded permission prompts.
return CanCurrentRequestUseModalUI(web_contents)
? std::make_unique<EmbeddedPermissionPrompt>(
browser, web_contents, delegate)
: nullptr;
} else if (delegate->ShouldCurrentRequestUseQuietUI()) {
return std::make_unique<PermissionPromptQuietIcon>(browser, web_contents,
delegate);
} else {
return std::make_unique<PermissionPromptBubble>(browser, web_contents,
delegate);
}
}
std::unique_ptr<permissions::PermissionPrompt> CreateNormalPrompt(
Browser* browser,
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
DCHECK(!delegate->ShouldCurrentRequestUseQuietUI());
if (ShouldCurrentRequestUseExclusiveAccessUI(delegate)) {
return CanCurrentRequestUseModalUI(web_contents)
? std::make_unique<ExclusiveAccessPermissionPrompt>(
browser, web_contents, delegate)
: nullptr;
} else if (permissions::PermissionUtil::
ShouldCurrentRequestUsePermissionElementSecondaryUI(
delegate)) {
// Run this check inside the if statement to avoid creating another prompt
// type for embedded permission prompts.
return CanCurrentRequestUseModalUI(web_contents)
? std::make_unique<EmbeddedPermissionPrompt>(
browser, web_contents, delegate)
: nullptr;
} else if (ShouldUseChip(delegate) && IsLocationBarDisplayed(browser)) {
return std::make_unique<PermissionPromptChip>(browser, web_contents,
delegate);
} else {
return std::make_unique<PermissionPromptBubble>(browser, web_contents,
delegate);
}
}
std::unique_ptr<permissions::PermissionPrompt> CreateQuietPrompt(
Browser* browser,
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
if (ShouldCurrentRequestUseQuietChip(delegate)) {
if (IsLocationBarDisplayed(browser)) {
return std::make_unique<PermissionPromptChip>(browser, web_contents,
delegate);
} else {
// If LocationBar is not displayed (Fullscreen mode), display a default
// bubble only for non-abusive origins.
DCHECK(!delegate->ShouldDropCurrentRequestIfCannotShowQuietly());
return std::make_unique<PermissionPromptBubble>(browser, web_contents,
delegate);
}
} else {
return std::make_unique<PermissionPromptQuietIcon>(browser, web_contents,
delegate);
}
}
} // namespace
std::unique_ptr<permissions::PermissionPrompt> CreatePermissionPrompt(
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
Browser* browser = chrome::FindBrowserWithTab(web_contents);
if (!browser) {
DLOG(WARNING) << "Permission prompt suppressed because the WebContents is "
"not attached to any Browser window.";
return nullptr;
}
if (delegate->ShouldDropCurrentRequestIfCannotShowQuietly() &&
IsFullScreenMode(browser)) {
return nullptr;
}
if (ShouldIgnorePermissionRequest(web_contents, browser, delegate)) {
return nullptr;
}
#if BUILDFLAG(IS_MAC)
// If this is a notification permission request coming from a PWA (or a PWA
// associated tab), and this is the first time we show a permission prompt for
// this request, try using a OS-native permission prompt. If showing the
// prompt fails, it will trigger the view to be recreated for the request, at
// which point we end up in the normal code path below.
if (web_app::UseNotificationAttributionForWebAppShims() &&
!delegate->WasCurrentRequestAlreadyDisplayed() &&
PermissionPromptNotificationsMac::CanHandleRequest(web_contents,
delegate)) {
return std::make_unique<PermissionPromptNotificationsMac>(web_contents,
delegate);
}
#endif
if (web_app::AppBrowserController::IsWebApp(browser)) {
return CreatePwaPrompt(browser, web_contents, delegate);
} else if (delegate->ShouldCurrentRequestUseQuietUI()) {
return CreateQuietPrompt(browser, web_contents, delegate);
}
return CreateNormalPrompt(browser, web_contents, delegate);
}