blob: 148b946bd7e99039c59d037a8dbc66df308c567c [file] [log] [blame]
// Copyright 2020 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/bubble/webui_bubble_dialog_view.h"
#include "build/build_config.h"
#include "components/input/native_web_keyboard_event.h"
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/visibility.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/base/hit_test.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "chrome/browser/ui/views/bubble/webui_bubble_event_handler_aura.h"
#include "ui/aura/window.h"
#endif
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif
namespace {
// The min size available to the WebBubbleDialogView. These are arbitrary sizes
// that match those set by ExtensionPopup.
constexpr gfx::Size kMinSize(25, 25);
#if defined(USE_AURA)
bool ShouldUseEventHandlerForBubbleDrag(aura::Window* window) {
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
// Only use the event handler for non desktop aura windows. In the case of
// desktop aura windows the host WM is responsible for controlling the drag.
return views::DesktopNativeWidgetAura::ForWindow(window) == nullptr;
#else
return true;
#endif
}
#endif
// WebUIBubbleView provides the functionality needed to embed a WebContents
// within a Views hierarchy.
class WebUIBubbleView : public views::WebView {
METADATA_HEADER(WebUIBubbleView, views::WebView)
public:
explicit WebUIBubbleView(content::WebContents* web_contents) {
SetWebContents(web_contents);
// Allow the embedder to handle accelerators not handled by the WebContents.
set_allow_accelerators(true);
SetPreferredSize(web_contents->GetSize());
}
~WebUIBubbleView() override = default;
// WebView:
bool HandleContextMenu(content::RenderFrameHost& render_frame_host,
const content::ContextMenuParams& params) override {
// Ignores context menu.
return true;
}
};
SkRegion ComputeDraggableRegion(
const std::vector<blink::mojom::DraggableRegionPtr>& regions) {
SkRegion draggable_region;
for (const blink::mojom::DraggableRegionPtr& region : regions) {
draggable_region.op(
SkIRect::MakeXYWH(region->bounds.x(), region->bounds.y(),
region->bounds.width(), region->bounds.height()),
region->draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
}
return draggable_region;
}
BEGIN_METADATA(WebUIBubbleView)
END_METADATA
} // namespace
WebUIBubbleDialogView::WebUIBubbleDialogView(
views::View* anchor_view,
base::WeakPtr<WebUIContentsWrapper> contents_wrapper,
const std::optional<gfx::Rect>& anchor_rect,
views::BubbleBorder::Arrow arrow,
bool autosize)
: BubbleDialogDelegateView(anchor_view,
arrow,
views::BubbleBorder::DIALOG_SHADOW,
autosize),
contents_wrapper_(contents_wrapper),
web_view_(AddChildView(std::make_unique<WebUIBubbleView>(
contents_wrapper_->web_contents()))),
bubble_anchor_(anchor_rect) {
DCHECK(!contents_wrapper_->GetHost());
contents_wrapper_->web_contents()->WasShown();
SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
set_margins(gfx::Insets());
SetLayoutManager(std::make_unique<views::FillLayout>());
}
WebUIBubbleDialogView::~WebUIBubbleDialogView() {
ClearContentsWrapper();
}
void WebUIBubbleDialogView::ClearContentsWrapper() {
if (!contents_wrapper_) {
return;
}
DCHECK_EQ(this, contents_wrapper_->GetHost().get());
DCHECK_EQ(web_view_->web_contents(), contents_wrapper_->web_contents());
web_view_->SetWebContents(nullptr);
contents_wrapper_->web_contents()->WasHidden();
contents_wrapper_->SetHost(nullptr);
contents_wrapper_ = nullptr;
}
base::WeakPtr<WebUIBubbleDialogView> WebUIBubbleDialogView::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void WebUIBubbleDialogView::OnWidgetClosing(views::Widget* widget) {
ClearContentsWrapper();
}
gfx::Size WebUIBubbleDialogView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
// Constrain the size to popup min/max.
gfx::Size preferred_size =
BubbleDialogDelegateView::CalculatePreferredSize(available_size);
preferred_size.SetToMax(kMinSize);
preferred_size.SetToMin(GetWidget()->GetWorkAreaBoundsInScreen().size());
return preferred_size;
}
void WebUIBubbleDialogView::AddedToWidget() {
BubbleDialogDelegateView::AddedToWidget();
// This view needs to be added to the widget before setting itself as the host
// of the contents, so that the contents' resizing request can be propagated
// to the widget.
views::Widget* widget = GetWidget();
contents_wrapper_->SetHost(weak_factory_.GetWeakPtr());
bubble_widget_observation_.Observe(widget);
web_view_->holder()->SetCornerRadii(gfx::RoundedCornersF(GetCornerRadius()));
#if defined(USE_AURA)
aura::Window* window = widget->GetNativeView();
if (ShouldUseEventHandlerForBubbleDrag(window)) {
event_handler_ = std::make_unique<WebUIBubbleEventHandlerAura>();
window->AddPreTargetHandler(event_handler_.get());
}
#endif
}
gfx::Rect WebUIBubbleDialogView::GetBubbleBounds() {
// If hosting a draggable bubble do not update widget bounds position while
// the bubble is visible. This allows the bubble to position itself according
// to the anchor initially while retaining its dragged position as the bubble
// resizes.
gfx::Rect bubble_bounds = BubbleDialogDelegateView::GetBubbleBounds();
const views::Widget* widget = GetWidget();
if (contents_wrapper_ && contents_wrapper_->supports_draggable_regions() &&
widget && widget->IsVisible()) {
bubble_bounds.set_origin(widget->GetWindowBoundsInScreen().origin());
}
return bubble_bounds;
}
std::unique_ptr<views::FrameView> WebUIBubbleDialogView::CreateFrameView(
views::Widget* widget) {
// TODO(tluk): Improve the current pattern used to compose functionality on
// bubble frames and eliminate the need for static cast.
auto frame = BubbleDialogDelegateView::CreateFrameView(widget);
static_cast<views::BubbleFrameView*>(frame.get())
->set_non_client_hit_test_cb(base::BindRepeating(
&WebUIBubbleDialogView::NonClientHitTest, base::Unretained(this)));
return frame;
}
void WebUIBubbleDialogView::ShowUI() {
if (!contents_wrapper_) {
// This widget is closing and/or being destroyed.
CHECK(!GetWidget() || GetWidget()->IsClosed());
return;
}
DCHECK(GetWidget());
GetWidget()->Show();
web_view_->GetWebContents()->Focus();
}
void WebUIBubbleDialogView::CloseUI() {
DCHECK(GetWidget());
GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kCloseButtonClicked);
}
void WebUIBubbleDialogView::ResizeDueToAutoResize(content::WebContents* source,
const gfx::Size& new_size) {
web_view_->SetPreferredSize(new_size);
}
bool WebUIBubbleDialogView::HandleKeyboardEvent(
content::WebContents* source,
const input::NativeWebKeyboardEvent& event) {
return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
event, GetFocusManager());
}
void WebUIBubbleDialogView::DraggableRegionsChanged(
const std::vector<blink::mojom::DraggableRegionPtr>& regions,
content::WebContents* contents) {
draggable_region_ = ComputeDraggableRegion(regions);
}
bool WebUIBubbleDialogView::ShouldDescendIntoChildForEventHandling(
gfx::NativeView child,
const gfx::Point& location) {
// The bubble should claim events that fall within the draggable region.
return !draggable_region_.has_value() ||
!draggable_region_->contains(location.x(), location.y());
}
gfx::Rect WebUIBubbleDialogView::GetAnchorRect() const {
if (bubble_anchor_) {
return bubble_anchor_.value();
}
return BubbleDialogDelegateView::GetAnchorRect();
}
int WebUIBubbleDialogView::NonClientHitTest(const gfx::Point& point) const {
// Convert the point to the WebView's coordinates.
gfx::Point point_in_webview =
views::View::ConvertPointToTarget(this, web_view_, point);
return draggable_region_.has_value() &&
draggable_region_->contains(point_in_webview.x(),
point_in_webview.y())
? HTCAPTION
: HTNOWHERE;
}
BEGIN_METADATA(WebUIBubbleDialogView)
END_METADATA