#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/base/class_property.h"
#include "ui/base/default_style.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_type.h"
#include "ui/display/screen.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/bubble_histograms_variant.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/window/dialog_client_view.h"
#if BUILDFLAG(IS_WIN)
#include "ui/base/win/shell.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "ui/views/widget/widget_utils_mac.h"
#else
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif
DEFINE_UI_CLASS_PROPERTY_TYPE(std::vector<views::BubbleDialogDelegate*>*)
namespace views {
namespace {
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::vector<views::BubbleDialogDelegate*>,
kAnchorVector)
std::vector<BubbleDialogDelegate*>& GetAnchorVector(View* view) {
if (!view->GetProperty(kAnchorVector)) {
return *(view->SetProperty(
kAnchorVector, std::make_unique<std::vector<BubbleDialogDelegate*>>()));
}
return *(view->GetProperty(kAnchorVector));
}
class BubbleWidget : public Widget {
public:
BubbleWidget() = default;
BubbleWidget(const BubbleWidget&) = delete;
BubbleWidget& operator=(const BubbleWidget&) = delete;
const ui::ThemeProvider* GetThemeProvider() const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetThemeProvider() : Widget::GetThemeProvider();
}
ui::ColorProviderKey::ThemeInitializerSupplier* GetCustomTheme()
const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetCustomTheme() : Widget::GetCustomTheme();
}
const ui::NativeTheme* GetNativeTheme() const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetNativeTheme() : Widget::GetNativeTheme();
}
using Widget::GetPrimaryWindowWidget;
Widget* GetPrimaryWindowWidget() override {
Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetPrimaryWindowWidget()
: Widget::GetPrimaryWindowWidget();
}
const ui::ColorProvider* GetColorProvider() const override {
const Widget* const primary = GetPrimaryWindowWidget();
return (primary && primary != this) ? primary->GetColorProvider()
: Widget::GetColorProvider();
}
private:
const Widget* GetAnchorWidget() const {
BubbleDialogDelegate* const bubble_delegate =
static_cast<BubbleDialogDelegate*>(widget_delegate());
return bubble_delegate ? bubble_delegate->anchor_widget() : nullptr;
}
Widget* GetAnchorWidget() {
return const_cast<Widget*>(std::as_const(*this).GetAnchorWidget());
}
};
class BubbleDialogFrameView : public BubbleFrameView {
public:
explicit BubbleDialogFrameView(const gfx::Insets& title_margins)
: BubbleFrameView(title_margins, gfx::Insets()) {}
BubbleDialogFrameView(const BubbleDialogFrameView&) = delete;
BubbleDialogFrameView& operator=(const BubbleDialogFrameView&) = delete;
gfx::Size GetMinimumSize() const override { return gfx::Size(); }
gfx::Size GetMaximumSize() const override { return gfx::Size(); }
};
Widget* CreateBubbleWidget(BubbleDialogDelegate* bubble,
Widget::InitParams::Ownership ownership =
Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
auto bubble_widget = std::make_unique<BubbleWidget>();
Widget::InitParams bubble_params(ownership, Widget::InitParams::TYPE_BUBBLE);
bubble_params.delegate = bubble;
bubble_params.opacity = Widget::InitParams::WindowOpacity::kTranslucent;
bubble_params.accept_events = bubble->accept_events();
bubble_params.remove_standard_frame = true;
bubble_params.layer_type = bubble->layer_type();
CHECK(!bubble->is_autosized() || bubble->use_custom_frame())
<< "Autosizing native frame dialogs is not supported.";
bubble_params.autosize = bubble->is_autosized();
if (bubble->GetShadow() == BubbleBorder::NO_SHADOW) {
bubble_params.shadow_type = Widget::InitParams::ShadowType::kDefault;
} else {
bubble_params.shadow_type = Widget::InitParams::ShadowType::kNone;
}
gfx::NativeView parent = gfx::NativeView();
if (bubble->has_parent()) {
if (bubble->parent_window()) {
parent = bubble->parent_window();
} else if (bubble->anchor_widget()) {
parent = bubble->anchor_widget()->GetNativeView();
}
}
bubble_params.parent = parent;
bubble_params.activatable = bubble->CanActivate()
? Widget::InitParams::Activatable::kYes
: Widget::InitParams::Activatable::kNo;
bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget.get());
DCHECK(bubble_params.parent || !bubble->has_parent());
bubble_widget->Init(std::move(bubble_params));
return bubble_widget.release();
}
}
class BubbleDialogDelegate::AnchorViewObserver : public ViewObserver {
public:
AnchorViewObserver(BubbleDialogDelegate* parent, View* anchor_view)
: parent_(parent), anchor_view_(anchor_view) {
anchor_view_->AddObserver(this);
AddToAnchorVector();
}
AnchorViewObserver(const AnchorViewObserver&) = delete;
AnchorViewObserver& operator=(const AnchorViewObserver&) = delete;
~AnchorViewObserver() override {
RemoveFromAnchorVector();
anchor_view_->RemoveObserver(this);
}
View* anchor_view() const { return anchor_view_; }
void OnViewIsDeleting(View* observed_view) override {
DCHECK_EQ(anchor_view_, observed_view);
parent_->SetAnchorView(nullptr);
}
void OnViewBoundsChanged(View* observed_view) override {
DCHECK_EQ(anchor_view_, observed_view);
parent_->OnAnchorBoundsChanged();
}
private:
void AddToAnchorVector() {
auto& vector = GetAnchorVector(anchor_view_);
DCHECK(!base::Contains(vector, parent_));
vector.push_back(parent_);
}
void RemoveFromAnchorVector() {
auto& vector = GetAnchorVector(anchor_view_);
DCHECK(!vector.empty());
auto iter = vector.cend() - 1;
bool latest_anchor_bubble_will_change = (*iter == parent_);
if (!latest_anchor_bubble_will_change) {
iter = std::find(vector.cbegin(), iter, parent_);
}
DCHECK(iter != vector.cend());
vector.erase(iter);
if (!vector.empty() && latest_anchor_bubble_will_change) {
vector.back()->NotifyAnchoredBubbleIsPrimary();
}
}
const raw_ptr<BubbleDialogDelegate> parent_;
const raw_ptr<View> anchor_view_;
};
#if BUILDFLAG(IS_MAC)
class BubbleDialogDelegate::AnchorWidgetObserver : public WidgetObserver {
#else
class BubbleDialogDelegate::AnchorWidgetObserver : public WidgetObserver,
public aura::WindowObserver {
#endif
public:
AnchorWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
: owner_(owner) {
widget_observation_.Observe(widget);
#if !BUILDFLAG(IS_MAC)
window_observation_.Observe(widget->GetNativeWindow());
#endif
}
~AnchorWidgetObserver() override = default;
void OnWidgetDestroying(Widget* widget) override {
#if !BUILDFLAG(IS_MAC)
DCHECK(window_observation_.IsObservingSource(widget->GetNativeWindow()));
window_observation_.Reset();
#endif
DCHECK(widget_observation_.IsObservingSource(widget));
widget_observation_.Reset();
owner_->OnAnchorWidgetDestroying();
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
owner_->OnWidgetActivationChanged(widget, active);
}
void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect&) override {
owner_->OnAnchorBoundsChanged();
}
void OnWidgetThemeChanged(Widget* widget) override {
owner_->GetWidget()->ThemeChanged();
}
#if !BUILDFLAG(IS_MAC)
void OnWindowTransformed(aura::Window* window,
ui::PropertyChangeReason reason) override {
if (window->is_destroying()) {
return;
}
if (!window->layer()->GetAnimator()->IsAnimatingOnePropertyOf(
ui::LayerAnimationElement::TRANSFORM)) {
owner_->OnAnchorBoundsChanged();
}
}
void OnWindowDestroying(aura::Window* window) override {
window_observation_.Reset();
}
#endif
private:
raw_ptr<BubbleDialogDelegate> owner_;
base::ScopedObservation<views::Widget, views::WidgetObserver>
widget_observation_{this};
#if !BUILDFLAG(IS_MAC)
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
#endif
};
class BubbleDialogDelegate::BubbleWidgetObserver : public WidgetObserver {
public:
BubbleWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
: owner_(owner) {
observation_.Observe(widget);
}
~BubbleWidgetObserver() override = default;
void OnWidgetClosing(Widget* widget) override {
owner_->OnBubbleWidgetClosing();
owner_->OnWidgetClosing(widget);
}
void OnWidgetDestroying(Widget* widget) override {
owner_->OnWidgetDestroying(widget);
}
void OnWidgetDestroyed(Widget* widget) override {
DCHECK(observation_.IsObservingSource(widget));
observation_.Reset();
owner_->OnWidgetDestroyed(widget);
}
void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect& bounds) override {
owner_->OnWidgetBoundsChanged(widget, bounds);
}
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
owner_->OnBubbleWidgetVisibilityChanged(visible);
owner_->OnWidgetVisibilityChanged(widget, visible);
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
owner_->OnBubbleWidgetActivationChanged(active);
owner_->OnWidgetActivationChanged(widget, active);
}
private:
const raw_ptr<BubbleDialogDelegate> owner_;
base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
this};
};
class BubbleDialogDelegateView::CloseOnDeactivatePin::Pins {
public:
Pins() = default;
~Pins() = default;
bool is_pinned() const { return !pins_.empty(); }
base::WeakPtr<Pins> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
void AddPin(CloseOnDeactivatePin* pin) {
const auto result = pins_.insert(pin);
DCHECK(result.second);
}
void RemovePin(CloseOnDeactivatePin* pin) {
const auto result = pins_.erase(pin);
DCHECK(result);
}
protected:
std::set<raw_ptr<CloseOnDeactivatePin, SetExperimental>> pins_;
base::WeakPtrFactory<Pins> weak_ptr_factory_{this};
};
BubbleDialogDelegate::CloseOnDeactivatePin::CloseOnDeactivatePin(
base::WeakPtr<Pins> pins)
: pins_(pins) {
pins_->AddPin(this);
}
BubbleDialogDelegate::CloseOnDeactivatePin::~CloseOnDeactivatePin() {
Pins* const pins = pins_.get();
if (pins) {
pins->RemovePin(this);
}
}
BubbleDialogDelegate::BubbleDialogDelegate(BubbleAnchor anchor,
BubbleBorder::Arrow arrow,
BubbleBorder::Shadow shadow,
bool autosize)
: arrow_(arrow),
shadow_(shadow),
autosize_(autosize),
close_on_deactivate_pins_(std::make_unique<CloseOnDeactivatePin::Pins>()),
bubble_created_time_(base::TimeTicks::Now()) {
bubble_uma_logger().set_delegate(this);
SetAnchor(anchor);
SetArrow(arrow);
SetShowCloseButton(false);
LayoutProvider* const layout_provider = LayoutProvider::Get();
set_frame_margins(
{.contents = layout_provider->GetDialogInsetsForContentType(
DialogContentType::kText, DialogContentType::kText),
.title = layout_provider->GetInsetsMetric(INSETS_DIALOG_TITLE),
.footnote = layout_provider->GetInsetsMetric(INSETS_DIALOG_FOOTNOTE)});
set_desired_bounds_delegate(base::BindRepeating(
&BubbleDialogDelegate::GetDesiredBubbleBounds, base::Unretained(this)));
RegisterWidgetInitializedCallback(base::BindOnce(
[](BubbleDialogDelegate* bubble_delegate) {
bubble_delegate->UpdateFrameColor();
},
this));
RegisterWidgetInitializedCallback(base::BindOnce(
[](BubbleDialogDelegate* delegate, base::TimeTicks bubble_created_time) {
delegate->GetWidget()
->GetCompositor()
->RequestSuccessfulPresentationTimeForNextFrame(base::BindOnce(
[](base::WeakPtr<BubbleDialogDelegate::BubbleUmaLogger>
uma_logger,
base::TimeTicks bubble_created_time,
const viz::FrameTimingDetails& frame_timing_details) {
base::TimeTicks presentation_timestamp =
frame_timing_details.presentation_feedback.timestamp;
if (!uma_logger) {
return;
}
uma_logger->LogMetric(
base::UmaHistogramTimes, "CreateToPresentationTime",
presentation_timestamp - bubble_created_time);
},
delegate->bubble_uma_logger().GetWeakPtr(),
bubble_created_time));
},
base::Unretained(this), *bubble_created_time_));
}
BubbleDialogDelegate::~BubbleDialogDelegate() {
SetAnchorView(nullptr);
}
Widget* BubbleDialogDelegate::CreateBubble(
BubbleDialogDelegate* bubble_delegate,
Widget::InitParams::Ownership ownership) {
if (bubble_delegate->GetModalType() == ui::mojom::ModalType::kWindow) {
DCHECK(!bubble_delegate->GetAnchorView());
DCHECK_EQ(bubble_delegate->GetAnchorRect(), gfx::Rect());
}
bubble_delegate->Init();
if (auto* anchor_view = bubble_delegate->GetAnchorView()) {
bubble_delegate->SetAnchorView(anchor_view);
}
Widget* const bubble_widget = CreateBubbleWidget(bubble_delegate, ownership);
bubble_delegate->set_adjust_if_offscreen(
PlatformStyle::kAdjustBubbleIfOffscreen);
bubble_delegate->SizeToContents();
bubble_delegate->bubble_widget_observer_ =
std::make_unique<BubbleWidgetObserver>(bubble_delegate, bubble_widget);
return bubble_widget;
}
Widget* BubbleDialogDelegate::CreateBubble(
std::unique_ptr<BubbleDialogDelegate> bubble_delegate_unique,
Widget::InitParams::Ownership ownership) {
return CreateBubble(bubble_delegate_unique.release(), ownership);
}
Widget* BubbleDialogDelegateView::CreateBubble(
BubbleDialogDelegateView* delegate_view,
Widget::InitParams::Ownership ownership) {
return CreateBubble(base::WrapUnique(delegate_view), ownership);
}
BubbleDialogDelegateView::BubbleDialogDelegateView(BubbleAnchor anchor,
BubbleBorder::Arrow arrow,
BubbleBorder::Shadow shadow,
bool autosize)
: BubbleDialogDelegate(anchor, arrow, shadow, autosize) {
bubble_uma_logger().set_bubble_view(this);
}
BubbleDialogDelegateView::~BubbleDialogDelegateView() {
SetLayoutManager(nullptr);
SetAnchorView(nullptr);
}
BubbleDialogDelegate* BubbleDialogDelegate::AsBubbleDialogDelegate() {
return this;
}
std::unique_ptr<FrameView> BubbleDialogDelegate::CreateFrameView(
Widget* widget) {
const FrameMargins& margin = frame_margins();
auto frame = std::make_unique<BubbleDialogFrameView>(margin.title.value());
frame->SetFootnoteMargins(margin.footnote.value());
frame->SetFootnoteView(DisownFootnoteView());
std::unique_ptr<BubbleBorder> border =
std::make_unique<BubbleBorder>(arrow(), GetShadow());
border->SetColor(background_color());
if (GetParams().round_corners) {
border->set_rounded_corners(gfx::RoundedCornersF(GetCornerRadius()));
}
frame->SetBubbleBorder(std::move(border));
return frame;
}
ClientView* BubbleDialogDelegate::CreateClientView(Widget* widget) {
auto* client_view = DialogDelegate::CreateClientView(widget);
client_view->SetPaintToLayer(layer_type());
client_view->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(GetCornerRadius()));
client_view->layer()->SetIsFastRoundedCorner(true);
return client_view;
}
Widget* BubbleDialogDelegateView::GetWidget() {
return View::GetWidget();
}
const Widget* BubbleDialogDelegateView::GetWidget() const {
return View::GetWidget();
}
View* BubbleDialogDelegateView::GetContentsView() {
return this;
}
void BubbleDialogDelegate::OnBubbleWidgetClosing() {
if (GetAnchorView() &&
GetAnchorView()->GetProperty(kAnchoredDialogKey) == this) {
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
}
if (bubble_shown_time_.has_value()) {
bubble_shown_duration_ += base::TimeTicks::Now() - *bubble_shown_time_;
bubble_shown_time_.reset();
}
bubble_uma_logger().LogMetric(base::UmaHistogramLongTimes, "TimeVisible",
bubble_shown_duration_);
bubble_uma_logger().LogMetric(base::UmaHistogramEnumeration, "CloseReason",
GetWidget()->closed_reason());
}
void BubbleDialogDelegate::OnAnchorWidgetDestroying() {
SetAnchorView(nullptr);
}
void BubbleDialogDelegate::OnBubbleWidgetActivationChanged(bool active) {
#if BUILDFLAG(IS_MAC)
if (active && !mac_bubble_closer_) {
mac_bubble_closer_ = std::make_unique<ui::BubbleCloser>(
GetWidget()->GetNativeWindow(),
base::BindRepeating(&BubbleDialogDelegate::OnDeactivate,
base::Unretained(this)));
}
#endif
if (!active) {
OnDeactivate();
}
}
void BubbleDialogDelegate::OnAnchorWidgetBoundsChanged() {
if (GetBubbleFrameView()) {
SizeToContents();
}
}
BubbleBorder::Shadow BubbleDialogDelegate::GetShadow() const {
if (!Widget::IsWindowCompositingSupported()) {
return BubbleBorder::Shadow::NO_SHADOW;
}
return shadow_;
}
View* BubbleDialogDelegate::GetAnchorView() const {
if (!anchor_view_observer_) {
return nullptr;
}
return anchor_view_observer_->anchor_view();
}
void BubbleDialogDelegate::SetMainImage(ui::ImageModel main_image) {
if (main_image_.IsEmpty()) {
DCHECK(!GetBubbleFrameView());
}
main_image_ = std::move(main_image);
if (GetBubbleFrameView()) {
GetBubbleFrameView()->UpdateMainImage();
}
}
bool BubbleDialogDelegate::ShouldCloseOnDeactivate() const {
return close_on_deactivate_ && !close_on_deactivate_pins_->is_pinned();
}
std::unique_ptr<BubbleDialogDelegate::CloseOnDeactivatePin>
BubbleDialogDelegate::PreventCloseOnDeactivate() {
return base::WrapUnique(
new CloseOnDeactivatePin(close_on_deactivate_pins_->GetWeakPtr()));
}
void BubbleDialogDelegate::SetHighlightedButton(Button* highlighted_button) {
bool visible = GetWidget() && GetWidget()->IsVisible();
if (visible && highlighted_button != highlighted_button_tracker_.view()) {
UpdateHighlightedButton(false);
}
highlighted_button_tracker_.SetView(highlighted_button);
if (visible) {
UpdateHighlightedButton(true);
}
}
void BubbleDialogDelegate::SetBackgroundColor(ui::ColorVariant color) {
if (color_ == color) {
return;
}
color_ = color;
if (GetWidget()) {
UpdateFrameColor();
}
}
void BubbleDialogDelegate::SetArrow(BubbleBorder::Arrow arrow) {
SetArrowWithoutResizing(arrow);
if (GetBubbleFrameView()) {
SizeToContents();
}
}
void BubbleDialogDelegate::SetArrowWithoutResizing(BubbleBorder::Arrow arrow) {
if (base::i18n::IsRTL()) {
arrow = BubbleBorder::horizontal_mirror(arrow);
}
if (arrow_ == arrow) {
return;
}
arrow_ = arrow;
if (GetBubbleFrameView()) {
GetBubbleFrameView()->SetArrow(arrow);
}
}
gfx::Rect BubbleDialogDelegate::GetAnchorRect() const {
View* anchor_view = GetAnchorView();
if (anchor_view) {
anchor_rect_ = anchor_view->GetAnchorBoundsInScreen();
} else if (anchor_tracked_element_) {
anchor_rect_ = anchor_tracked_element_->GetScreenBounds();
} else {
return anchor_rect_.value_or(gfx::Rect());
}
#if !BUILDFLAG(IS_MAC)
if (anchor_widget_) {
gfx::Transform transform =
anchor_widget_->GetNativeWindow()->layer()->GetTargetTransform();
if (!transform.IsIdentity()) {
anchor_rect_->Offset(
-gfx::ToRoundedVector2d(transform.To2dTranslation()));
}
}
#endif
BubbleFrameView* frame_view = GetBubbleFrameView();
if (anchor_view && frame_view && frame_view->GetDisplayVisibleArrow()) {
gfx::Insets* padding = anchor_view->GetProperty(kInternalPaddingKey);
if (padding != nullptr) {
anchor_rect_->Inset(*padding);
}
}
return anchor_rect_.value();
}
void BubbleDialogDelegate::UseCompactMargins() {
set_frame_margins({.contents = gfx::Insets(6)});
}
gfx::Size BubbleDialogDelegate::GetMaxAvailableScreenSpaceToPlaceBubble(
BubbleAnchor anchor,
BubbleBorder::Arrow arrow,
bool adjust_if_offscreen,
BubbleFrameView::PreferredArrowAdjustment arrow_adjustment) {
DCHECK(arrow == BubbleBorder::TOP_LEFT || arrow == BubbleBorder::TOP_RIGHT ||
arrow == BubbleBorder::BOTTOM_RIGHT ||
arrow == BubbleBorder::BOTTOM_LEFT);
DCHECK_EQ(arrow_adjustment,
BubbleFrameView::PreferredArrowAdjustment::kMirror);
#if BUILDFLAG(IS_OZONE)
DCHECK(ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.supports_global_screen_coordinates);
#endif
gfx::Rect anchor_rect;
if (std::holds_alternative<View*>(anchor)) {
anchor_rect = std::get<View*>(anchor)->GetAnchorBoundsInScreen();
} else {
anchor_rect = std::get<ui::TrackedElement*>(anchor)->GetScreenBounds();
}
gfx::Rect screen_rect =
display::Screen::Get()
->GetDisplayNearestPoint(anchor_rect.CenterPoint())
.work_area();
gfx::Size max_available_space;
if (adjust_if_offscreen) {
max_available_space = GetAvailableSpaceToPlaceBubble(
BubbleBorder::TOP_LEFT, anchor_rect, screen_rect);
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::TOP_RIGHT, anchor_rect, screen_rect));
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::BOTTOM_RIGHT, anchor_rect, screen_rect));
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::BOTTOM_LEFT, anchor_rect, screen_rect));
} else {
max_available_space =
GetAvailableSpaceToPlaceBubble(arrow, anchor_rect, screen_rect);
}
return max_available_space;
}
gfx::Size BubbleDialogDelegate::GetAvailableSpaceToPlaceBubble(
BubbleBorder::Arrow arrow,
gfx::Rect anchor_rect,
gfx::Rect screen_rect) {
int available_height_below = screen_rect.bottom() - anchor_rect.bottom();
int available_height_above = anchor_rect.y() - screen_rect.y();
int available_width_on_left = anchor_rect.right() - screen_rect.x();
int available_width_on_right = screen_rect.right() - anchor_rect.x();
return {BubbleBorder::is_arrow_on_left(arrow) ? available_width_on_right
: available_width_on_left,
BubbleBorder::is_arrow_on_top(arrow) ? available_height_below
: available_height_above};
}
void BubbleDialogDelegate::OnAnchorBoundsChanged() {
if (!GetWidget() || !GetBubbleFrameView()) {
return;
}
SizeToContents();
UpdateInputProtectorsTimeStamp();
}
void BubbleDialogDelegate::UpdateInputProtectorsTimeStamp() {
if (auto* dialog = GetDialogClientView()) {
dialog->UpdateInputProtectorTimeStamp();
}
GetBubbleFrameView()->UpdateInputProtectorTimeStamp();
}
BubbleDialogDelegate::BubbleUmaLogger::BubbleUmaLogger() = default;
BubbleDialogDelegate::BubbleUmaLogger::~BubbleUmaLogger() = default;
base::WeakPtr<BubbleDialogDelegate::BubbleUmaLogger>
BubbleDialogDelegate::BubbleUmaLogger::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
std::optional<std::string>
BubbleDialogDelegate::BubbleUmaLogger::GetBubbleName() const {
if (delegate_.has_value()) {
std::string class_name(
delegate_.value()->GetContentsView()->GetClassName());
if (class_name != "View") {
return class_name;
}
}
if (bubble_view_.has_value()) {
return std::string(bubble_view_.value()->GetClassName());
}
return std::nullopt;
}
template <typename Value>
void BubbleDialogDelegate::BubbleUmaLogger::LogMetric(
void (*uma_func)(std::string_view, Value),
std::string_view histogram_name,
Value value) const {
if (!base::FeatureList::IsEnabled(::features::kBubbleMetricsApi)) {
return;
}
uma_func(base::StrCat({"Bubble.All.", histogram_name}), value);
std::optional<std::string> bubble_name = GetBubbleName();
if (!bubble_name.has_value()) {
return;
}
if (allowed_class_names_for_testing_.has_value()) {
if (!base::Contains(allowed_class_names_for_testing_.value(),
bubble_name.value())) {
return;
}
} else if (!views_metrics::IsValidBubbleName(bubble_name.value())) {
return;
}
uma_func(base::StrCat({"Bubble.", bubble_name.value(), ".", histogram_name}),
value);
}
template VIEWS_EXPORT void BubbleDialogDelegate::BubbleUmaLogger::LogMetric<
base::TimeDelta>(void (*uma_func)(std::string_view, base::TimeDelta),
std::string_view histogram_name,
base::TimeDelta value) const;
gfx::Rect BubbleDialogDelegate::GetBubbleBounds() {
bool anchor_minimized = anchor_widget() && anchor_widget()->IsMinimized();
gfx::Rect anchor_rect = GetAnchorRect();
bool has_anchor = GetAnchorView() || anchor_rect != gfx::Rect();
bool adjust_to_fix_available_bounds =
adjust_if_offscreen_ && !anchor_minimized && has_anchor;
#if BUILDFLAG(IS_OZONE)
if (!ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.supports_global_screen_coordinates) {
adjust_to_fix_available_bounds = false;
}
#endif
return GetBubbleFrameView()->GetUpdatedWindowBounds(
anchor_rect, arrow(), GetWidget()->client_view()->GetPreferredSize({}),
adjust_to_fix_available_bounds);
}
ax::mojom::Role BubbleDialogDelegate::GetAccessibleWindowRole() {
const ax::mojom::Role accessible_role =
WidgetDelegate::GetAccessibleWindowRole();
if (accessible_role != ax::mojom::Role::kWindow) {
return accessible_role;
}
if (GetInitiallyFocusedView()) {
return ax::mojom::Role::kDialog;
}
return ax::mojom::Role::kAlertDialog;
}
gfx::Rect BubbleDialogDelegate::GetDesiredBubbleBounds() {
CHECK(use_custom_frame())
<< "GetBubbleBounds() for native frame dialogs is not supported.";
gfx::Rect bubble_bounds = GetBubbleBounds();
#if BUILDFLAG(IS_MAC)
gfx::Size actual_size =
GetWindowSizeForClientSize(GetWidget(), bubble_bounds.size());
bubble_bounds.set_size(actual_size);
#endif
return bubble_bounds;
}
gfx::Size BubbleDialogDelegateView::GetMinimumSize() const {
return gfx::Size();
}
gfx::Size BubbleDialogDelegateView::GetMaximumSize() const {
return gfx::Size();
}
void BubbleDialogDelegate::SetAnchorWidget(views::Widget* anchor_widget) {
if (anchor_widget_ == anchor_widget) {
return;
}
SetAnchorView(nullptr);
if (anchor_widget_) {
anchor_widget_observer_.reset();
}
anchor_widget_ = anchor_widget;
if (anchor_widget_) {
anchor_widget_observer_ =
std::make_unique<AnchorWidgetObserver>(this, anchor_widget_);
}
}
void BubbleDialogDelegate::SetAnchorView(View* anchor_view) {
if (anchor_view && anchor_view->GetWidget()) {
anchor_widget_observer_ =
std::make_unique<AnchorWidgetObserver>(this, anchor_view->GetWidget());
} else {
anchor_widget_observer_.reset();
}
if (GetAnchorView()) {
if (GetAnchorView()->GetProperty(kAnchoredDialogKey) == this) {
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
}
anchor_view_observer_.reset();
}
if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) {
if (anchor_widget()) {
if (GetWidget() && GetWidget()->IsVisible()) {
UpdateHighlightedButton(false);
}
anchor_widget_ = nullptr;
}
if (anchor_view) {
anchor_widget_ = anchor_view->GetProperty(kWidgetForAnchoringKey);
if (!anchor_widget_) {
anchor_widget_ = anchor_view->GetWidget();
}
if (anchor_widget_) {
const bool visible = GetWidget() && GetWidget()->IsVisible();
UpdateHighlightedButton(visible);
}
}
}
if (anchor_view) {
anchor_view_observer_ =
std::make_unique<AnchorViewObserver>(this, anchor_view);
OnAnchorBoundsChanged();
SetAnchoredDialogKey();
}
}
void BubbleDialogDelegate::SetAnchorRect(const gfx::Rect& rect) {
anchor_rect_ = rect;
if (GetWidget()) {
OnAnchorBoundsChanged();
}
}
void BubbleDialogDelegate::SetAnchor(BubbleAnchor anchor) {
if (std::holds_alternative<View*>(anchor)) {
SetAnchorView(std::get<View*>(anchor));
} else if (std::holds_alternative<ui::TrackedElement*>(anchor)) {
auto* tracked_element = std::get<ui::TrackedElement*>(anchor);
CHECK(tracked_element);
if (auto* element_views = tracked_element->AsA<TrackedElementViews>()) {
SetAnchorView(element_views->view());
} else {
anchor_tracked_element_ = tracked_element;
SetAnchorView(nullptr);
SetAnchorRect(tracked_element->GetScreenBounds());
Widget* widget =
Widget::GetWidgetForNativeView(tracked_element->GetNativeView());
CHECK(widget) << "BubbleAnchor only supports TrackedElement backed by a "
<< "Widget.";
SetAnchorWidget(widget);
}
} else {
CHECK(std::holds_alternative<std::nullptr_t>(anchor));
SetAnchorView(nullptr);
SetAnchorRect(gfx::Rect());
}
}
BubbleAnchor BubbleDialogDelegate::GetAnchor() const {
if (GetAnchorView()) {
return GetAnchorView();
}
if (anchor_tracked_element_) {
return anchor_tracked_element_;
}
return nullptr;
}
void BubbleDialogDelegate::SizeToContents() {
GetWidget()->SetBounds(GetDesiredWidgetBounds());
}
std::u16string BubbleDialogDelegate::GetSubtitle() const {
return subtitle_;
}
void BubbleDialogDelegate::SetSubtitle(const std::u16string& subtitle) {
if (subtitle_ == subtitle) {
return;
}
subtitle_ = subtitle;
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view) {
frame_view->UpdateSubtitle();
}
}
bool BubbleDialogDelegate::GetSubtitleAllowCharacterBreak() const {
return subtitle_allow_character_break_;
}
void BubbleDialogDelegate::SetSubtitleAllowCharacterBreak(bool allow) {
if (subtitle_allow_character_break_ == allow) {
return;
}
subtitle_allow_character_break_ = allow;
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view) {
frame_view->UpdateSubtitle();
}
}
void BubbleDialogDelegate::UpdateFrameColor() {
View* const contents_view = GetContentsView();
DCHECK(contents_view);
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view) {
frame_view->SetBackgroundColor(background_color());
}
const bool contents_layer_opaque =
layer_type() != ui::LAYER_NOT_DRAWN && contents_view->layer() &&
contents_view->layer()->type() != ui::LAYER_NOT_DRAWN &&
contents_view->layer()->fills_bounds_opaquely();
if (contents_layer_opaque) {
CHECK(contents_view->background())
<< "If contents paint to an opaque layer, the bubble border background "
"won't show through, so explicitly paint a background color";
}
}
void BubbleDialogDelegate::OnBubbleWidgetVisibilityChanged(bool visible) {
if (visible) {
if (GetWidget()->IsClosed()) {
return;
}
if (bubble_created_time_.has_value()) {
GetWidget()
->GetCompositor()
->RequestSuccessfulPresentationTimeForNextFrame(base::BindOnce(
[](base::WeakPtr<BubbleDialogDelegate::BubbleUmaLogger>
uma_logger,
base::TimeTicks bubble_created_time,
const viz::FrameTimingDetails& frame_timing_details) {
base::TimeTicks presentation_timestamp =
frame_timing_details.presentation_feedback.timestamp;
if (!uma_logger) {
return;
}
uma_logger->LogMetric(
base::UmaHistogramMediumTimes, "CreateToVisibleTime",
presentation_timestamp - bubble_created_time);
},
bubble_uma_logger().GetWeakPtr(), *bubble_created_time_));
bubble_created_time_.reset();
}
bubble_shown_time_ = base::TimeTicks::Now();
} else {
if (bubble_shown_time_.has_value()) {
bubble_shown_duration_ += base::TimeTicks::Now() - *bubble_shown_time_;
bubble_shown_time_.reset();
}
}
UpdateHighlightedButton(visible);
if (visible && ui::IsAlert(GetAccessibleWindowRole())) {
GetWidget()->GetRootView()->GetViewAccessibility().SetRole(
GetAccessibleWindowRole());
GetWidget()->GetRootView()->NotifyAccessibilityEventDeprecated(
ax::mojom::Event::kAlert, true);
}
}
void BubbleDialogDelegate::OnDeactivate() {
if (ShouldCloseOnDeactivate() && GetWidget()) {
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
}
}
void BubbleDialogDelegate::NotifyAnchoredBubbleIsPrimary() {
const bool visible = GetWidget() && GetWidget()->IsVisible();
UpdateHighlightedButton(visible);
SetAnchoredDialogKey();
}
void BubbleDialogDelegate::SetAnchoredDialogKey() {
auto* anchor_view = GetAnchorView();
DCHECK(anchor_view);
if (focus_traversable_from_anchor_view_) {
auto* old_anchored_dialog = anchor_view->GetProperty(kAnchoredDialogKey);
if (old_anchored_dialog && old_anchored_dialog != this) {
DLOG(WARNING) << "|anchor_view| has already anchored a focusable widget.";
}
anchor_view->SetProperty(kAnchoredDialogKey,
static_cast<DialogDelegate*>(this));
}
}
void BubbleDialogDelegate::UpdateHighlightedButton(bool highlighted) {
Button* button = Button::AsButton(highlighted_button_tracker_.view());
button = button ? button : Button::AsButton(GetAnchorView());
if (button && highlight_button_when_shown_) {
if (highlighted) {
button_anchor_highlight_ = button->AddAnchorHighlight();
} else {
button_anchor_highlight_.reset();
}
}
}
BEGIN_METADATA(BubbleDialogDelegateView)
END_METADATA
}