blob: 7af15d4b22fa2cbfdf9f70463d7ae69dd4aedf34 [file] [log] [blame]
// Copyright 2023 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/ui/views/permissions/permission_prompt_base_view.h"
#include <cstddef>
#include <memory>
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/views/bubble_anchor_util_views.h"
#include "chrome/browser/ui/views/title_origin_label.h"
#include "chrome/grit/generated_resources.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_util.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/views/window/dialog_client_view.h"
namespace {
constexpr UrlIdentity::TypeSet allowed_types = {
UrlIdentity::Type::kDefault, UrlIdentity::Type::kChromeExtension,
UrlIdentity::Type::kIsolatedWebApp, UrlIdentity::Type::kFile};
constexpr UrlIdentity::FormatOptions options = {
.default_options = {
UrlIdentity::DefaultFormatOptions::kOmitCryptographicScheme}};
std::u16string GetAllowAlwaysTextInternal(
size_t num_requests,
permissions::PermissionRequest* first_request) {
CHECK_GT(num_requests, 0u);
CHECK(first_request);
if (num_requests == 1 && first_request->GetAllowAlwaysText().has_value()) {
// A prompt for a single request can use an "allow always" text that is
// customized for it.
return first_request->GetAllowAlwaysText().value();
}
// Use the generic text.
return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW_WHILE_VISITING);
}
std::u16string GetBlockTextInternal(
size_t num_requests,
permissions::PermissionRequest* first_request) {
CHECK_GT(num_requests, 0u);
if (auto text = first_request->GetBlockText();
num_requests == 1u && text.has_value()) {
// A prompt for a single request can use a "block" text that is customized
// for it.
return text.value();
}
// Use the generic text.
return l10n_util::GetStringUTF16(IDS_PERMISSION_NEVER_ALLOW);
}
int CountValidRequests(
const std::vector<base::WeakPtr<permissions::PermissionRequest>>&
requests) {
return std::ranges::count_if(
requests.begin(), requests.end(),
[](base::WeakPtr<permissions::PermissionRequest> request_ptr) {
return request_ptr.get() != nullptr;
});
}
} // namespace
PermissionPromptBaseView::PermissionPromptBaseView(
Browser* browser,
base::WeakPtr<permissions::PermissionPrompt::Delegate> delegate)
: BubbleDialogDelegateView(/*anchor_view=*/nullptr,
views::BubbleBorder::TOP_LEFT,
views::BubbleBorder::DIALOG_SHADOW,
/*autosize=*/true),
url_identity_(GetUrlIdentity(browser, *delegate)),
is_for_picture_in_picture_window_(browser &&
browser->is_type_picture_in_picture()),
record_browser_always_active_value_(browser && browser->IsActive()),
browser_(browser) {
// To prevent permissions being accepted accidentally, and as a security
// measure against crbug.com/619429, permission prompts should not be accepted
// as the default action.
SetDefaultButton(static_cast<int>(ui::mojom::DialogButton::kNone));
// `browser` can be null in tests.
if (browser) {
browser_subscription_ = browser->RegisterDidBecomeActive(
base::BindRepeating(&PermissionPromptBaseView::DidBecomeInactive,
base::Unretained(this)));
}
request_type_ =
permissions::PermissionUtil::GetUmaValueForRequests(delegate->Requests());
}
PermissionPromptBaseView::~PermissionPromptBaseView() {
// `request_type_` can be unknown in tests
if (request_type_ != permissions::RequestTypeForUma::UNKNOWN) {
permissions::PermissionUmaUtil::RecordBrowserAlwaysActiveWhilePrompting(
request_type_, /*embedded_permission_element_initiated*/ false,
record_browser_always_active_value_);
}
}
void PermissionPromptBaseView::AddedToWidget() {
if (url_identity_.type == UrlIdentity::Type::kDefault) {
// There is a risk of URL spoofing from origins that are too wide to fit in
// the bubble; elide origins from the front to prevent this.
GetBubbleFrameView()->SetTitleView(
CreateTitleOriginLabel(GetWindowTitle(), GetTitleBoldedRanges()));
}
permissions::PermissionUmaUtil::RecordPromptShownInActiveBrowser(
request_type_, /*embedded_permission_element_initiated*/ false,
record_browser_always_active_value_);
StartTrackingPictureInPictureOcclusion();
}
void PermissionPromptBaseView::AnchorToPageInfoOrChip() {
bubble_anchor_util::AnchorConfiguration configuration =
bubble_anchor_util::GetPermissionPromptBubbleAnchorConfiguration(
browser_);
SetAnchor(configuration.anchor);
// In fullscreen, `anchor` may be nullptr because the toolbar is hidden,
// therefore anchor to the browser window instead.
if (std::holds_alternative<View*>(configuration.anchor)) {
set_parent_window(
std::get<View*>(configuration.anchor)->GetWidget()->GetNativeView());
} else if (std::holds_alternative<ui::TrackedElement*>(
configuration.anchor)) {
set_parent_window(
std::get<ui::TrackedElement*>(configuration.anchor)->GetNativeView());
} else {
set_parent_window(
platform_util::GetViewForWindow(browser_->window()->GetNativeWindow()));
}
SetHighlightedButton(configuration.highlighted_button);
if (std::holds_alternative<std::nullptr_t>(configuration.anchor)) {
SetAnchorRect(bubble_anchor_util::GetPageInfoAnchorRect(browser_));
}
SetArrow(configuration.bubble_arrow);
}
bool PermissionPromptBaseView::ShouldIgnoreButtonPressedEventHandling(
View* button,
const ui::Event& event) const {
// Ignore button pressed events whenever we're occluded by a
// picture-in-picture window.
return occluded_by_picture_in_picture_;
}
void PermissionPromptBaseView::OnOcclusionStateChanged(bool occluded) {
// Protect from immediate input if the dialog has just become unoccluded.
if (occluded_by_picture_in_picture_ && !occluded) {
TriggerInputProtection();
}
occluded_by_picture_in_picture_ = occluded;
}
void PermissionPromptBaseView::FilterUnintenedEventsAndRunCallbacks(
int button_id,
const ui::Event& event) {
if (GetDialogClientView()->IsPossiblyUnintendedInteraction(
event, /*allow_key_events=*/false)) {
return;
}
View* button = AsDialogDelegate()->GetExtraView()->GetViewByID(button_id);
if (ShouldIgnoreButtonPressedEventHandling(button, event)) {
return;
}
RunButtonCallback(button_id);
}
// static
UrlIdentity PermissionPromptBaseView::GetUrlIdentity(
Browser* browser,
permissions::PermissionPrompt::Delegate& delegate) {
DCHECK(!delegate.Requests().empty());
GURL origin_url = delegate.GetRequestingOrigin();
UrlIdentity url_identity =
UrlIdentity::CreateFromUrl(browser ? browser->profile() : nullptr,
origin_url, allowed_types, options);
if (url_identity.type == UrlIdentity::Type::kFile) {
// File URLs will show the same constant.
url_identity.name =
l10n_util::GetStringUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT_THIS_FILE);
}
return url_identity;
}
std::u16string PermissionPromptBaseView::GetAllowAlwaysText(
const std::vector<std::unique_ptr<permissions::PermissionRequest>>&
visible_requests) {
CHECK_GT(visible_requests.size(), 0u);
return GetAllowAlwaysTextInternal(visible_requests.size(),
visible_requests[0].get());
}
std::u16string PermissionPromptBaseView::GetAllowAlwaysText(
const std::vector<base::WeakPtr<permissions::PermissionRequest>>&
visible_requests) {
size_t num_valid_visible_requests = CountValidRequests(visible_requests);
CHECK_EQ(visible_requests.size(), num_valid_visible_requests);
CHECK_GT(num_valid_visible_requests, 0u);
return GetAllowAlwaysTextInternal(num_valid_visible_requests,
visible_requests[0].get());
}
std::u16string PermissionPromptBaseView::GetBlockText(
const std::vector<std::unique_ptr<permissions::PermissionRequest>>&
visible_requests) {
CHECK_GT(visible_requests.size(), 0u);
return GetBlockTextInternal(visible_requests.size(),
visible_requests[0].get());
}
std::u16string PermissionPromptBaseView::GetBlockText(
const std::vector<base::WeakPtr<permissions::PermissionRequest>>&
visible_requests) {
size_t num_valid_visible_requests = CountValidRequests(visible_requests);
CHECK_EQ(visible_requests.size(), num_valid_visible_requests);
CHECK_GT(num_valid_visible_requests, 0u);
return GetBlockTextInternal(num_valid_visible_requests,
visible_requests[0].get());
}
void PermissionPromptBaseView::StartTrackingPictureInPictureOcclusion() {
// If we're for a picture-in-picture window, then we are in an always-on-top
// widget that should be tracked by the PictureInPictureOcclusionTracker.
if (is_for_picture_in_picture_window_) {
PictureInPictureOcclusionTracker* tracker =
PictureInPictureWindowManager::GetInstance()->GetOcclusionTracker();
if (tracker) {
tracker->OnPictureInPictureWidgetOpened(GetWidget());
}
}
// Either way, we want to know if we're ever occluded by an always-on-top
// window.
occlusion_observation_.Observe(GetWidget());
}
std::vector<std::pair<size_t, size_t>>
PermissionPromptBaseView::GetTitleBoldedRanges() {
return title_bolded_ranges_;
}
void PermissionPromptBaseView::SetTitleBoldedRanges(
std::vector<std::pair<size_t, size_t>> bolded_ranges) {
title_bolded_ranges_ = bolded_ranges;
}
void PermissionPromptBaseView::DidBecomeInactive(
BrowserWindowInterface* browser_window_interface) {
record_browser_always_active_value_ = false;
}
BEGIN_METADATA(PermissionPromptBaseView)
END_METADATA