blob: 3d95fe05ac4a8dca551d8d9d5df2e56bab0129b6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.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/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/views/bubble_anchor_util_views.h"
#include "chrome/browser/ui/views/chrome_widget_sublevel.h"
#include "chrome/browser/ui/views/device_chooser_content_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
#include "chrome/browser/ui/views/title_origin_label.h"
#include "components/permissions/chooser_controller.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/ui/extensions/extensions_dialogs.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/extension.h"
#endif
using bubble_anchor_util::AnchorConfiguration;
namespace {
AnchorConfiguration GetChooserAnchorConfiguration(Browser* browser) {
return bubble_anchor_util::GetPageInfoAnchorConfiguration(browser);
}
gfx::Rect GetChooserAnchorRect(Browser* browser) {
return bubble_anchor_util::GetPageInfoAnchorRect(browser);
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// View implementation for the chooser bubble.
class ChooserBubbleUiViewDelegate : public LocationBarBubbleDelegateView,
public views::TableViewObserver {
METADATA_HEADER(ChooserBubbleUiViewDelegate, LocationBarBubbleDelegateView)
public:
ChooserBubbleUiViewDelegate(
Browser* browser,
content::WebContents* web_contents,
std::unique_ptr<permissions::ChooserController> chooser_controller);
ChooserBubbleUiViewDelegate(const ChooserBubbleUiViewDelegate&) = delete;
ChooserBubbleUiViewDelegate& operator=(const ChooserBubbleUiViewDelegate&) =
delete;
~ChooserBubbleUiViewDelegate() override;
// views::View:
void AddedToWidget() override;
// views::WidgetDelegate:
std::u16string GetWindowTitle() const override;
// views::DialogDelegate:
bool IsDialogButtonEnabled(ui::mojom::DialogButton button) const override;
views::View* GetInitiallyFocusedView() override;
// views::TableViewObserver:
void OnSelectionChanged() override;
// Updates the anchor's arrow and view. Also repositions the bubble so it's
// displayed in the correct location.
void UpdateAnchor(Browser* browser);
void UpdateTableView() const;
base::OnceClosure MakeCloseClosure();
void Close();
private:
raw_ptr<DeviceChooserContentView> device_chooser_content_view_ = nullptr;
base::WeakPtrFactory<ChooserBubbleUiViewDelegate> weak_ptr_factory_{this};
};
ChooserBubbleUiViewDelegate::ChooserBubbleUiViewDelegate(
Browser* browser,
content::WebContents* contents,
std::unique_ptr<permissions::ChooserController> chooser_controller)
: LocationBarBubbleDelegateView(
GetChooserAnchorConfiguration(browser).anchor,
contents) {
// ------------------------------------
// | Chooser bubble title |
// | -------------------------------- |
// | | option 0 | |
// | | option 1 | |
// | | option 2 | |
// | | | |
// | | | |
// | | | |
// | -------------------------------- |
// | [ Connect ] [ Cancel ] |
// |----------------------------------|
// | Get help |
// ------------------------------------
SetButtonLabel(ui::mojom::DialogButton::kOk,
chooser_controller->GetOkButtonLabel());
SetButtonLabel(ui::mojom::DialogButton::kCancel,
chooser_controller->GetCancelButtonLabel());
SetLayoutManager(std::make_unique<views::FillLayout>());
device_chooser_content_view_ =
new DeviceChooserContentView(this, std::move(chooser_controller));
AddChildViewRaw(device_chooser_content_view_.get());
SetExtraView(device_chooser_content_view_->CreateExtraView());
SetAcceptCallback(
base::BindOnce(&DeviceChooserContentView::Accept,
base::Unretained(device_chooser_content_view_)));
SetCancelCallback(
base::BindOnce(&DeviceChooserContentView::Cancel,
base::Unretained(device_chooser_content_view_)));
SetCloseCallback(
base::BindOnce(&DeviceChooserContentView::Close,
base::Unretained(device_chooser_content_view_)));
}
ChooserBubbleUiViewDelegate::~ChooserBubbleUiViewDelegate() = default;
void ChooserBubbleUiViewDelegate::AddedToWidget() {
GetBubbleFrameView()->SetTitleView(CreateTitleOriginLabel(GetWindowTitle()));
}
std::u16string ChooserBubbleUiViewDelegate::GetWindowTitle() const {
return device_chooser_content_view_->GetWindowTitle();
}
views::View* ChooserBubbleUiViewDelegate::GetInitiallyFocusedView() {
return GetCancelButton();
}
bool ChooserBubbleUiViewDelegate::IsDialogButtonEnabled(
ui::mojom::DialogButton button) const {
return device_chooser_content_view_->IsDialogButtonEnabled(button);
}
void ChooserBubbleUiViewDelegate::OnSelectionChanged() {
DialogModelChanged();
}
void ChooserBubbleUiViewDelegate::UpdateAnchor(Browser* browser) {
AnchorConfiguration configuration = GetChooserAnchorConfiguration(browser);
SetAnchor(configuration.anchor);
SetHighlightedButton(configuration.highlighted_button);
if (std::holds_alternative<std::nullptr_t>(configuration.anchor)) {
SetAnchorRect(GetChooserAnchorRect(browser));
}
SetArrow(configuration.bubble_arrow);
}
void ChooserBubbleUiViewDelegate::UpdateTableView() const {
device_chooser_content_view_->UpdateTableView();
}
base::OnceClosure ChooserBubbleUiViewDelegate::MakeCloseClosure() {
return base::BindOnce(&ChooserBubbleUiViewDelegate::Close,
weak_ptr_factory_.GetWeakPtr());
}
void ChooserBubbleUiViewDelegate::Close() {
if (GetWidget()) {
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
}
BEGIN_METADATA(ChooserBubbleUiViewDelegate)
END_METADATA
namespace chrome {
namespace {
#if BUILDFLAG(ENABLE_EXTENSIONS)
base::OnceClosure ShowDeviceChooserDialogForExtension(
content::RenderFrameHost* owner,
const extensions::Extension* extension,
std::unique_ptr<permissions::ChooserController> controller) {
auto* contents = content::WebContents::FromRenderFrameHost(owner);
auto* browser = chrome::FindBrowserWithTab(contents);
if (!browser) {
return base::DoNothing();
}
if (browser->tab_strip_model()->GetActiveWebContents() != contents) {
return base::DoNothing();
}
// `GetExtensionsToolbarContainer` may return `nullptr`, for instance in
// extension popup windows.
auto* extensions_toolbar_container =
BrowserView::GetBrowserViewForBrowser(browser)
->toolbar_button_provider()
->GetExtensionsToolbarContainer();
if (!extensions_toolbar_container) {
return base::DoNothing();
}
auto bubble = std::make_unique<ChooserBubbleUiViewDelegate>(
browser, contents, std::move(controller));
base::OnceClosure close_closure = bubble->MakeCloseClosure();
extensions_toolbar_container->ShowWidgetForExtension(
views::BubbleDialogDelegateView::CreateBubble(std::move(bubble)),
extension->id());
return close_closure;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace
base::OnceClosure ShowDeviceChooserDialog(
content::RenderFrameHost* owner,
std::unique_ptr<permissions::ChooserController> controller) {
auto* contents = content::WebContents::FromRenderFrameHost(owner);
#if BUILDFLAG(ENABLE_EXTENSIONS)
auto* browser_context = owner->GetBrowserContext();
if (extensions::AppWindowRegistry::Get(browser_context)
->GetAppWindowForWebContents(contents)) {
extensions::ShowConstrainedDeviceChooserDialog(contents,
std::move(controller));
// This version of the chooser dialog does not support being closed by the
// code which created it.
return base::DoNothing();
}
auto origin = owner->GetMainFrame()->GetLastCommittedOrigin();
if (origin.scheme() == extensions::kExtensionScheme) {
const auto* extension =
extensions::ExtensionRegistry::Get(browser_context)
->GetExtensionById(origin.host(),
extensions::ExtensionRegistry::EVERYTHING);
DCHECK(extension);
return ShowDeviceChooserDialogForExtension(owner, extension,
std::move(controller));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
auto* browser = chrome::FindBrowserWithTab(contents);
if (!browser) {
return base::DoNothing();
}
if (browser->tab_strip_model()->GetActiveWebContents() != contents) {
return base::DoNothing();
}
auto bubble = std::make_unique<ChooserBubbleUiViewDelegate>(
browser, contents, std::move(controller));
// Set |parent_window_| because some valid anchors can become hidden.
views::Widget* parent_widget = views::Widget::GetWidgetForNativeWindow(
browser->window()->GetNativeWindow());
gfx::NativeView parent = parent_widget->GetNativeView();
DCHECK(parent);
bubble->set_parent_window(parent);
bubble->UpdateAnchor(browser);
base::OnceClosure close_closure = bubble->MakeCloseClosure();
views::Widget* widget =
views::BubbleDialogDelegateView::CreateBubble(std::move(bubble));
widget->SetZOrderSublevel(ChromeWidgetSublevel::kSublevelSecurity);
widget->Show();
// If we're opening this device chooser dialog on a picture-in-picture window,
// then our widget is also always-on-top and needs to be tracked by the
// PictureInPictureOcclusionTracker so it can handle our widget occluding
// other widgets.
if (browser->is_type_picture_in_picture()) {
PictureInPictureOcclusionTracker* tracker =
PictureInPictureWindowManager::GetInstance()->GetOcclusionTracker();
if (tracker) {
tracker->OnPictureInPictureWidgetOpened(widget);
}
}
return close_closure;
}
} // namespace chrome