blob: bc286584bc162ef4816942b2dd63bd26058099f1 [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/embedded_permission_prompt.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
#include "chrome/browser/permissions/system/system_permission_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_ask_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_content_scrim_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_policy_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_previously_denied_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_previously_granted_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_show_system_prompt_view.h"
#include "chrome/browser/ui/views/permissions/embedded_permission_prompt_system_settings_view.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/embedded_permission_prompt_flow_model.h"
#include "components/permissions/permission_uma_util.h"
#include "content/public/browser/web_contents.h"
#include "ui/color/color_id.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "chrome/browser/permissions/system/system_media_capture_permissions_mac.h"
#endif
using Variant = permissions::EmbeddedPermissionPromptFlowModel::Variant;
EmbeddedPermissionPrompt::EmbeddedPermissionPrompt(
Browser* browser,
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate)
: PermissionPromptDesktop(browser, web_contents, delegate),
delegate_(delegate) {
prompt_model_ =
std::make_unique<permissions::EmbeddedPermissionPromptFlowModel>(
web_contents, delegate);
CloseCurrentViewAndMaybeShowNext(/*first_prompt=*/true);
}
EmbeddedPermissionPrompt::~EmbeddedPermissionPrompt() {
CloseViewAndScrim();
}
void EmbeddedPermissionPrompt::CloseCurrentViewAndMaybeShowNext(
bool first_prompt) {
if (!first_prompt) {
CloseView();
}
prompt_model_->CalculateCurrentVariant();
EmbeddedPermissionPromptBaseView* prompt_view = nullptr;
switch (prompt_variant()) {
case Variant::kAsk:
prompt_view = new EmbeddedPermissionPromptAskView(
browser(), weak_factory_.GetWeakPtr());
break;
case Variant::kPreviouslyGranted:
if (first_prompt) {
prompt_view = new EmbeddedPermissionPromptPreviouslyGrantedView(
browser(), weak_factory_.GetWeakPtr());
} else {
FinalizePrompt();
return;
}
break;
case Variant::kPreviouslyDenied:
prompt_view = new EmbeddedPermissionPromptPreviouslyDeniedView(
browser(), weak_factory_.GetWeakPtr());
break;
case Variant::kOsPrompt:
prompt_view = new EmbeddedPermissionPromptShowSystemPromptView(
browser(), weak_factory_.GetWeakPtr());
prompt_model_->StartFirstDisplayTime();
// This view has no buttons, so the OS level prompt should be triggered at
// the same time as the |EmbeddedPermissionPromptShowSystemPromptView|.
PromptForOsPermission();
break;
case Variant::kOsSystemSettings:
prompt_view = new EmbeddedPermissionPromptSystemSettingsView(
browser(), weak_factory_.GetWeakPtr());
prompt_model_->StartFirstDisplayTime();
break;
case Variant::kAdministratorGranted:
prompt_view = new EmbeddedPermissionPromptPolicyView(
browser(), weak_factory_.GetWeakPtr(),
/*is_permission_allowed=*/true);
break;
case Variant::kAdministratorDenied:
prompt_view = new EmbeddedPermissionPromptPolicyView(
browser(), weak_factory_.GetWeakPtr(),
/*is_permission_allowed=*/false);
break;
case Variant::kUninitialized:
NOTREACHED();
}
prompt_model_->RecordElementAnchoredBubbleVariantUMA(prompt_variant());
if (prompt_view) {
prompt_view_tracker_.SetView(prompt_view);
if (!content_scrim_widget_) {
scoped_ignore_input_events_ =
web_contents()->IgnoreInputEvents(std::nullopt);
// Creating the widget will display it. That's why we create it only if
// the tab can show modal UI.
tabs::TabInterface* tab =
tabs::TabInterface::GetFromContents(web_contents());
scoped_tab_modal_ui_ = tab->ShowModalUI();
content_scrim_widget_ =
EmbeddedPermissionPromptContentScrimView::CreateScrimWidget(
weak_factory_.GetWeakPtr(),
SkColorSetA(web_contents()->GetColorProvider().GetColor(
ui::kColorRefNeutral20),
0.8 * SK_AlphaOPAQUE),
/*should_dismiss_on_click=*/true);
}
// If the tab/native view is closed, the `content_scrim_widget_` may be
// nullptr. In this scenario, skip showing the prompt.
if (!content_scrim_widget_) {
return;
}
prompt_view->UpdateAnchor(content_scrim_widget_.get());
prompt_view->Show();
}
if (prompt_view->GetWidget()) {
prompt_view->GetWidget()->UpdateAccessibleNameForRootView();
}
}
EmbeddedPermissionPrompt::TabSwitchingBehavior
EmbeddedPermissionPrompt::GetTabSwitchingBehavior() {
return TabSwitchingBehavior::kDestroyPromptAndIgnoreRequest;
}
permissions::PermissionPromptDisposition
EmbeddedPermissionPrompt::GetPromptDisposition() const {
return permissions::PermissionPromptDisposition::ELEMENT_ANCHORED_BUBBLE;
}
bool EmbeddedPermissionPrompt::ShouldFinalizeRequestAfterDecided() const {
return false;
}
std::vector<permissions::ElementAnchoredBubbleVariant>
EmbeddedPermissionPrompt::GetPromptVariants() const {
std::vector<permissions::ElementAnchoredBubbleVariant> variants;
return prompt_model_->GetPromptVariants();
}
bool EmbeddedPermissionPrompt::IsAskPrompt() const {
return (prompt_variant() == Variant::kAsk);
}
std::optional<permissions::feature_params::PermissionElementPromptPosition>
EmbeddedPermissionPrompt::GetPromptPosition() const {
if (auto* prompt_view = static_cast<const EmbeddedPermissionPromptBaseView*>(
prompt_view_tracker_.view())) {
return prompt_view->GetPromptPosition();
}
return std::nullopt;
}
std::optional<gfx::Rect> EmbeddedPermissionPrompt::GetViewBoundsInScreen()
const {
if (prompt_view_tracker_.view()) {
// This is a modal prompt, the view bounds will cover the whole content
// view.
return web_contents()->GetContainerBounds();
}
return std::nullopt;
}
void EmbeddedPermissionPrompt::Allow() {
prompt_model_->PrecalculateVariantsForMetrics();
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kGranted);
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::kAllow);
CloseCurrentViewAndMaybeShowNext(/*first_prompt=*/false);
}
void EmbeddedPermissionPrompt::AllowThisTime() {
prompt_model_->PrecalculateVariantsForMetrics();
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kGrantedOnce);
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::
kAllowThisTime);
CloseCurrentViewAndMaybeShowNext(/*first_prompt=*/false);
}
void EmbeddedPermissionPrompt::Dismiss() {
prompt_model_->PrecalculateVariantsForMetrics();
permissions::PermissionUmaUtil::RecordElementAnchoredBubbleDismiss(
delegate()->Requests(), permissions::DismissedReason::kDismissedXButton);
prompt_model_->RecordOsMetrics(
permissions::OsScreenAction::kDismissedXButton);
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kDismissedXButton);
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::kDismiss);
FinalizePrompt();
}
void EmbeddedPermissionPrompt::Acknowledge() {
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kOk);
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::kDismiss);
FinalizePrompt();
}
void EmbeddedPermissionPrompt::StopAllowing() {
prompt_model_->PrecalculateVariantsForMetrics();
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kDenied);
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::kDeny);
FinalizePrompt();
}
void EmbeddedPermissionPrompt::ShowSystemSettings() {
const auto& requests = delegate()->Requests();
CHECK_GT(requests.size(), 0U);
prompt_model_->RecordOsMetrics(permissions::OsScreenAction::kSystemSettings);
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kSystemSettings);
for (const auto& request : requests) {
if (system_permission_settings::IsDenied(
request->GetContentSettingsType())) {
system_permission_settings::OpenSystemSettings(
delegate()->GetAssociatedWebContents(),
request->GetContentSettingsType());
return;
}
}
// Since we don't observe system level permission status changes, there is a
// possibility that all permission settings have been granted at this point.
SystemPermissionsNoLongerDenied();
}
void EmbeddedPermissionPrompt::SystemPermissionsNoLongerDenied() {
CHECK(prompt_model_->prompt_variant() ==
permissions::EmbeddedPermissionPromptFlowModel::Variant::
kOsSystemSettings);
prompt_model_->PrecalculateVariantsForMetrics();
CloseCurrentViewAndMaybeShowNext(/*first_prompt=*/false);
}
base::WeakPtr<permissions::PermissionPrompt::Delegate>
EmbeddedPermissionPrompt::GetPermissionPromptDelegate() const {
return delegate_->GetWeakPtr();
}
const std::vector<base::WeakPtr<permissions::PermissionRequest>>&
EmbeddedPermissionPrompt::Requests() const {
return prompt_model_->requests();
}
void EmbeddedPermissionPrompt::DismissScrim() {
permissions::PermissionUmaUtil::RecordElementAnchoredBubbleDismiss(
delegate()->Requests(), permissions::DismissedReason::kDismissedScrim);
prompt_model_->RecordOsMetrics(permissions::OsScreenAction::kDismissedScrim);
prompt_model_->RecordPermissionActionUKM(
permissions::ElementAnchoredBubbleAction::kDismissedScrim);
prompt_model_->PrecalculateVariantsForMetrics();
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::kDismiss);
FinalizePrompt();
}
void EmbeddedPermissionPrompt::PromptForOsPermission() {
const auto& prompt_types = prompt_model_->prompt_types();
// We currently support <=2 grouped permissions.
CHECK_LE(prompt_types.size(), 2U);
std::vector<ContentSettingsType> types(prompt_types.begin(),
prompt_types.end());
for (unsigned int idx = 0; idx < types.size(); idx++) {
system_permission_settings::Request(
types[idx],
base::BindOnce(
&EmbeddedPermissionPrompt::OnRequestSystemPermissionResponse,
weak_factory_.GetWeakPtr(), types[idx],
// Pass the other type for grouped permission case.
(types.size() == 2U ? types[1U - idx]
: ContentSettingsType::DEFAULT)));
}
}
void EmbeddedPermissionPrompt::OnRequestSystemPermissionResponse(
const ContentSettingsType request_type,
const ContentSettingsType other_request_type) {
bool permission_determined =
!system_permission_settings::CanPrompt(request_type);
// `other_permission_determined` is left with true in non-grouped scenario,
// which would make the final logic fully rely on `permission_determined`.
auto other_permission_determined = true;
if (other_request_type != ContentSettingsType::DEFAULT) {
other_permission_determined =
!system_permission_settings::CanPrompt(other_request_type);
}
if (permission_determined) {
#if BUILDFLAG(IS_MAC)
system_permission_settings::SystemPermission permission;
if (request_type == ContentSettingsType::MEDIASTREAM_MIC) {
permission =
system_permission_settings::CheckSystemAudioCapturePermission();
}
if (request_type == ContentSettingsType::MEDIASTREAM_CAMERA) {
permission =
system_permission_settings::CheckSystemVideoCapturePermission();
}
switch (permission) {
case system_permission_settings::SystemPermission::kRestricted:
break;
case system_permission_settings::SystemPermission::kDenied:
prompt_model_->RecordOsMetrics(
permissions::OsScreenAction::kOsPromptDenied);
break;
case system_permission_settings::SystemPermission::kAllowed:
prompt_model_->RecordOsMetrics(
permissions::OsScreenAction::kOsPromptAllowed);
break;
case system_permission_settings::SystemPermission::kNotDetermined:
NOTREACHED();
}
#endif // BUILDFLAG(IS_MAC)
// Do not finalize request until all the necessary system permissions are
// granted.
if (other_permission_determined) {
FinalizePrompt();
}
} else {
NOTREACHED();
}
}
void EmbeddedPermissionPrompt::CloseView() {
if (auto* prompt_view = static_cast<EmbeddedPermissionPromptBaseView*>(
prompt_view_tracker_.view())) {
prompt_view->PrepareToClose();
prompt_view->GetWidget()->Close();
prompt_view_tracker_.SetView(nullptr);
prompt_model_->Clear();
}
}
void EmbeddedPermissionPrompt::CloseViewAndScrim() {
CloseView();
if (content_scrim_widget_) {
content_scrim_widget_->Close();
content_scrim_widget_ = nullptr;
scoped_ignore_input_events_.reset();
}
scoped_tab_modal_ui_.reset();
}
void EmbeddedPermissionPrompt::FinalizePrompt() {
CloseViewAndScrim();
// If by this point we've not sent an action to the delegate, send a dismiss
// action.
if (!prompt_model_->HasDelegateActionSet()) {
prompt_model_->SetDelegateAction(
permissions::EmbeddedPermissionPromptFlowModel::DelegateAction::
kDismiss);
}
delegate_->FinalizeCurrentRequests();
}