blob: 61bda2325cad7a03c598a8fc791b13cb5524f595 [file] [log] [blame]
// Copyright 2014 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/ui/views/permission_bubble/permission_prompt_impl.h"
#include <memory>
#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/permission_bubble/file_handling_permission_prompt.h"
#include "chrome/browser/ui/views/permission_bubble/permission_prompt_bubble_view.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_launch/web_launch_files_helper.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/permission_ui_selector.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/request_type.h"
#include "content/public/browser/web_contents.h"
#include "ui/views/bubble/bubble_frame_view.h"
namespace {
bool IsFullScreenMode(content::WebContents* web_contents, Browser* browser) {
DCHECK(web_contents);
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();
}
} // namespace
std::unique_ptr<permissions::PermissionPrompt> CreatePermissionPrompt(
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser) {
DLOG(WARNING) << "Permission prompt suppressed because the WebContents is "
"not attached to any Browser window.";
return nullptr;
}
permissions::PermissionRequestManager* manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (manager->ShouldDropCurrentRequestIfCannotShowQuietly() &&
IsFullScreenMode(web_contents, browser)) {
return nullptr;
}
if (delegate->Requests().size() == 1U &&
delegate->Requests()[0]->request_type() ==
permissions::RequestType::kFileHandling) {
return FileHandlingPermissionPrompt::Create(web_contents, delegate);
}
return std::make_unique<PermissionPromptImpl>(browser, web_contents,
delegate);
}
PermissionPromptImpl::PermissionPromptImpl(Browser* browser,
content::WebContents* web_contents,
Delegate* delegate)
: prompt_bubble_(nullptr),
web_contents_(web_contents),
delegate_(delegate),
browser_(browser),
permission_requested_time_(base::TimeTicks::Now()),
manager_(permissions::PermissionRequestManager::FromWebContents(
web_contents)) {
if (web_app::AppBrowserController::IsWebApp(browser_)) {
SelectPwaPrompt();
} else if (manager_->ShouldCurrentRequestUseQuietUI()) {
SelectQuietPrompt();
} else {
SelectNormalPrompt();
}
}
PermissionPromptImpl::~PermissionPromptImpl() {
switch (prompt_style_) {
case PermissionPromptStyle::kBubbleOnly:
DCHECK(!chip_);
if (prompt_bubble_)
prompt_bubble_->GetWidget()->Close();
break;
case PermissionPromptStyle::kChip:
case PermissionPromptStyle::kQuietChip:
DCHECK(!prompt_bubble_);
DCHECK(chip_);
FinalizeChip();
break;
case PermissionPromptStyle::kLocationBarRightIcon:
DCHECK(!prompt_bubble_);
DCHECK(!chip_);
content_settings::UpdateLocationBarUiForWebContents(web_contents_);
break;
}
CHECK(!IsInObserverList());
}
void PermissionPromptImpl::UpdateAnchor() {
Browser* current_browser = chrome::FindBrowserWithWebContents(web_contents_);
// Browser for |web_contents_| might change when for example the tab was
// dragged to another window.
bool was_browser_changed = false;
if (current_browser != browser_) {
browser_ = current_browser;
was_browser_changed = true;
}
LocationBarView* lbv = GetLocationBarView();
const bool is_location_bar_drawn = lbv && lbv->IsDrawn();
switch (prompt_style_) {
case PermissionPromptStyle::kBubbleOnly:
DCHECK(!chip_);
// TODO(crbug.com/1175231): Investigate why prompt_bubble_ can be null
// here. Early return is preventing the crash from happening but we still
// don't know the reason why it is null here and cannot reproduce it.
if (!prompt_bubble_)
return;
if (ShouldCurrentRequestUseChip() && is_location_bar_drawn) {
// Change prompt style to chip to avoid dismissing request while
// switching UI style.
prompt_bubble_->SetPromptStyle(PermissionPromptStyle::kChip);
prompt_bubble_->GetWidget()->Close();
ShowChip();
chip_->OpenBubble();
} else {
// If |browser_| changed, recreate bubble for correct browser.
if (was_browser_changed) {
prompt_bubble_->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kUnspecified);
ShowBubble();
} else {
prompt_bubble_->UpdateAnchorPosition();
}
}
break;
case PermissionPromptStyle::kChip:
DCHECK(!prompt_bubble_);
if (!lbv->chip()) {
chip_ = lbv->DisplayChip(delegate_);
}
// If there is fresh pending request shown as chip UI and location bar
// isn't visible anymore, show bubble UI instead.
if (!chip_->is_fully_collapsed() && !is_location_bar_drawn) {
FinalizeChip();
ShowBubble();
}
break;
case PermissionPromptStyle::kQuietChip:
DCHECK(!prompt_bubble_);
if (!lbv->chip()) {
chip_ = lbv->DisplayQuietChip(
delegate_,
!permissions::PermissionUiSelector::ShouldSuppressAnimation(
manager_->ReasonForUsingQuietUi()));
}
// If there is fresh pending request shown as chip UI and location bar
// isn't visible anymore, show bubble UI instead.
if (!chip_->is_fully_collapsed() && !is_location_bar_drawn) {
FinalizeChip();
ShowBubble();
}
break;
case PermissionPromptStyle::kLocationBarRightIcon:
break;
}
}
permissions::PermissionPrompt::TabSwitchingBehavior
PermissionPromptImpl::GetTabSwitchingBehavior() {
return permissions::PermissionPrompt::TabSwitchingBehavior::
kDestroyPromptButKeepRequestPending;
}
permissions::PermissionPromptDisposition
PermissionPromptImpl::GetPromptDisposition() const {
switch (prompt_style_) {
case PermissionPromptStyle::kBubbleOnly:
return permissions::PermissionPromptDisposition::ANCHORED_BUBBLE;
case PermissionPromptStyle::kChip:
return permissions::PermissionPromptDisposition::LOCATION_BAR_LEFT_CHIP;
case PermissionPromptStyle::kQuietChip:
return permissions::PermissionPromptDisposition::
LOCATION_BAR_LEFT_QUIET_CHIP;
case PermissionPromptStyle::kLocationBarRightIcon: {
return permissions::PermissionUiSelector::ShouldSuppressAnimation(
manager_->ReasonForUsingQuietUi())
? permissions::PermissionPromptDisposition::
LOCATION_BAR_RIGHT_STATIC_ICON
: permissions::PermissionPromptDisposition::
LOCATION_BAR_RIGHT_ANIMATED_ICON;
}
}
}
views::Widget* PermissionPromptImpl::GetPromptBubbleWidgetForTesting() {
if (prompt_bubble_) {
return prompt_bubble_->GetWidget();
}
return chip_ ? chip_->GetPromptBubbleWidgetForTesting() // IN-TEST
: nullptr;
}
void PermissionPromptImpl::OnWidgetClosing(views::Widget* widget) {
DCHECK_EQ(widget, prompt_bubble_->GetWidget());
widget->RemoveObserver(this);
prompt_bubble_ = nullptr;
}
bool PermissionPromptImpl::IsLocationBarDisplayed() {
LocationBarView* lbv = GetLocationBarView();
return lbv && lbv->IsDrawn();
}
void PermissionPromptImpl::SelectPwaPrompt() {
if (manager_->ShouldCurrentRequestUseQuietUI()) {
ShowQuietIcon();
} else {
ShowBubble();
}
}
void PermissionPromptImpl::SelectNormalPrompt() {
DCHECK(!manager_->ShouldCurrentRequestUseQuietUI());
if (ShouldCurrentRequestUseChip()) {
ShowChip();
} else {
ShowBubble();
}
}
void PermissionPromptImpl::SelectQuietPrompt() {
if (ShouldCurrentRequestUseQuietChip()) {
if (IsLocationBarDisplayed()) {
ShowChip();
} else {
// If LocationBar is not displayed (Fullscreen mode), display a default
// bubble only for non-abusive origins.
DCHECK(!manager_->ShouldDropCurrentRequestIfCannotShowQuietly());
ShowBubble();
}
} else {
ShowQuietIcon();
}
}
LocationBarView* PermissionPromptImpl::GetLocationBarView() {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
return browser_view ? browser_view->GetLocationBarView() : nullptr;
}
void PermissionPromptImpl::ShowQuietIcon() {
prompt_style_ = PermissionPromptStyle::kLocationBarRightIcon;
// Shows the prompt as an indicator in the right side of the omnibox.
content_settings::UpdateLocationBarUiForWebContents(web_contents_);
}
void PermissionPromptImpl::ShowBubble() {
prompt_style_ = PermissionPromptStyle::kBubbleOnly;
prompt_bubble_ = new PermissionPromptBubbleView(
browser_, delegate_, permission_requested_time_, prompt_style_);
prompt_bubble_->Show();
prompt_bubble_->GetWidget()->AddObserver(this);
}
void PermissionPromptImpl::ShowChip() {
LocationBarView* lbv = GetLocationBarView();
DCHECK(lbv);
if (manager_->ShouldCurrentRequestUseQuietUI()) {
chip_ = lbv->DisplayQuietChip(
delegate_, !permissions::PermissionUiSelector::ShouldSuppressAnimation(
manager_->ReasonForUsingQuietUi()));
prompt_style_ = PermissionPromptStyle::kQuietChip;
} else {
chip_ = lbv->DisplayChip(delegate_);
prompt_style_ = PermissionPromptStyle::kChip;
}
}
bool PermissionPromptImpl::ShouldCurrentRequestUseChip() {
if (!base::FeatureList::IsEnabled(permissions::features::kPermissionChip))
return false;
std::vector<permissions::PermissionRequest*> requests = delegate_->Requests();
return std::all_of(requests.begin(), requests.end(), [](auto* request) {
return request->GetChipText().has_value();
});
}
bool PermissionPromptImpl::ShouldCurrentRequestUseQuietChip() {
if (!base::FeatureList::IsEnabled(
permissions::features::kPermissionQuietChip)) {
return false;
}
std::vector<permissions::PermissionRequest*> requests = delegate_->Requests();
return std::all_of(requests.begin(), requests.end(), [](auto* request) {
return request->request_type() ==
permissions::RequestType::kNotifications ||
request->request_type() == permissions::RequestType::kGeolocation;
});
}
void PermissionPromptImpl::FinalizeChip() {
GetLocationBarView()->FinalizeChip();
chip_ = nullptr;
}