#include "ui/views/controls/menu/menu_host.h"
#include <utility>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/scoped_observation.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "ui/aura/window_observer.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/compositor.h"
#include "ui/events/gestures/gesture_recognizer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_host_root_view.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_scroll_view_container.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/round_rect_painter.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/base/ui_base_features.h"
#include "ui/ozone/public/ozone_platform.h"
#endif
namespace views {
namespace internal {
#if defined(USE_AURA)
class PreMenuEventDispatchHandler : public ui::EventHandler,
aura::WindowObserver {
public:
PreMenuEventDispatchHandler(MenuController* controller,
SubmenuView* submenu,
aura::Window* window)
: menu_controller_(controller->AsWeakPtr()), submenu_(submenu) {
window_observation_.Observe(window);
window->AddPreTargetHandler(this);
}
PreMenuEventDispatchHandler(const PreMenuEventDispatchHandler&) = delete;
PreMenuEventDispatchHandler& operator=(const PreMenuEventDispatchHandler&) =
delete;
~PreMenuEventDispatchHandler() override { StopObserving(); }
void OnTouchEvent(ui::TouchEvent* event) override {
menu_controller_->OnTouchEvent(submenu_, event);
}
void OnWindowDestroying(aura::Window* window) override {
DCHECK(window_observation_.IsObserving());
DCHECK_EQ(window_observation_.GetSource(), window);
StopObserving();
}
private:
void StopObserving() {
if (!window_observation_.IsObserving()) {
return;
}
window_observation_.GetSource()->RemovePreTargetHandler(this);
window_observation_.Reset();
}
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
const base::WeakPtr<MenuController> menu_controller_;
const raw_ptr<SubmenuView> submenu_;
};
#endif
void TransferGesture(ui::GestureRecognizer* gesture_recognizer,
gfx::NativeView source,
gfx::NativeView target) {
#if defined(USE_AURA)
gesture_recognizer->TransferEventsTo(source, target,
ui::TransferTouchesBehavior::kCancel);
#else
NOTIMPLEMENTED();
#endif
}
}
MenuHost::MenuHost(SubmenuView* submenu) : submenu_(submenu) {
set_auto_release_capture(false);
}
MenuHost::~MenuHost() = default;
void MenuHost::InitMenuHost(const InitParams& init_params) {
TRACE_EVENT0("views", "MenuHost::InitMenuHost");
Widget::InitParams params(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_MENU);
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
bool bubble_border = submenu_->GetScrollViewContainer() &&
submenu_->GetScrollViewContainer()->HasBubbleBorder();
params.name = "MenuHost";
params.shadow_type = bubble_border ? Widget::InitParams::ShadowType::kNone
: Widget::InitParams::ShadowType::kDrop;
const int corner_radius =
MenuConfig::instance().CornerRadiusForMenu(menu_controller);
params.opacity = (bubble_border || corner_radius)
? Widget::InitParams::WindowOpacity::kTranslucent
: Widget::InitParams::WindowOpacity::kOpaque;
if (!bubble_border) {
params.rounded_corners = gfx::RoundedCornersF(corner_radius);
}
params.parent = init_params.parent ? init_params.parent->GetNativeView()
: gfx::NativeView();
params.context = init_params.context ? init_params.context->GetNativeWindow()
: gfx::NativeWindow();
params.bounds = init_params.bounds;
params.parent_widget = init_params.parent_widget;
#if defined(USE_AURA)
params.init_properties_container.SetProperty(aura::client::kOwnedWindowAnchor,
init_params.owned_window_anchor);
#endif
if (init_params.parent == nullptr &&
init_params.parent_widget == gfx::kNullAcceleratedWidget) {
params.activatable = Widget::InitParams::Activatable::kYes;
}
#if BUILDFLAG(IS_WIN)
params.force_software_compositing = true;
#endif
Init(std::move(params));
std::optional<std::string> show_menu_host_duration_histogram =
menu_controller->TakeShowMenuHostDurationHistogram();
CHECK(!menu_controller->TakeShowMenuHostDurationHistogram().has_value());
if (show_menu_host_duration_histogram.has_value()) {
GetCompositor()->RequestSuccessfulPresentationTimeForNextFrame(
base::BindOnce(
[](std::string histogram, base::TimeTicks menu_host_init_time,
const viz::FrameTimingDetails& frame_timing_details) {
base::TimeTicks presentation_time =
frame_timing_details.presentation_feedback.timestamp;
UMA_HISTOGRAM_TIMES(histogram,
presentation_time - menu_host_init_time);
},
show_menu_host_duration_histogram.value(), base::TimeTicks::Now()));
}
#if defined(USE_AURA)
pre_dispatch_handler_ =
std::make_unique<internal::PreMenuEventDispatchHandler>(
menu_controller, submenu_, GetNativeView());
#endif
DCHECK(!GetOwner());
if (init_params.parent) {
owner_observation_.Observe(init_params.parent);
}
native_view_for_gestures_ = init_params.native_view_for_gestures;
SetContentsView(init_params.contents_view);
ShowMenuHost(init_params.do_capture);
}
bool MenuHost::IsMenuHostVisible() {
return IsVisible();
}
void MenuHost::ShowMenuHost(bool do_capture) {
base::AutoReset<bool> reseter(&ignore_capture_lost_, true);
ShowInactive();
if (do_capture) {
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (menu_controller && menu_controller->send_gesture_events_to_owner()) {
gfx::NativeView source_view = native_view_for_gestures_
? native_view_for_gestures_
: GetOwner()->GetNativeView();
internal::TransferGesture(GetGestureRecognizer(), source_view,
GetNativeView());
} else {
GetGestureRecognizer()->CancelActiveTouchesExcept(nullptr);
}
if (GetOwner() == nullptr) {
Show();
}
native_widget_private()->SetCapture();
}
}
void MenuHost::HideMenuHost() {
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (GetOwner() && menu_controller &&
menu_controller->send_gesture_events_to_owner()) {
gfx::NativeView target_view = native_view_for_gestures_
? native_view_for_gestures_
: GetOwner()->GetNativeView();
internal::TransferGesture(GetGestureRecognizer(), GetNativeView(),
target_view);
}
ignore_capture_lost_ = true;
ReleaseMenuHostCapture();
Hide();
ignore_capture_lost_ = false;
}
void MenuHost::DestroyMenuHost() {
HideMenuHost();
destroying_ = true;
submenu_ = nullptr;
#if defined(USE_AURA)
pre_dispatch_handler_.reset();
#endif
static_cast<MenuHostRootView*>(GetRootView())->ClearSubmenu();
Close();
}
void MenuHost::SetMenuHostBounds(const gfx::Rect& bounds) {
SetBounds(bounds);
}
void MenuHost::SetMenuHostOwnedWindowAnchor(
const ui::OwnedWindowAnchor& anchor) {
#if defined(USE_AURA)
native_widget_private()->GetNativeWindow()->SetProperty(
aura::client::kOwnedWindowAnchor, anchor);
#endif
}
void MenuHost::ReleaseMenuHostCapture() {
if (native_widget_private()->HasCapture()) {
native_widget_private()->ReleaseCapture();
}
}
internal::RootView* MenuHost::CreateRootView() {
return new MenuHostRootView(this, submenu_);
}
void MenuHost::OnMouseCaptureLost() {
if (destroying_ || ignore_capture_lost_) {
return;
}
if (!ViewsDelegate::GetInstance()->ShouldCloseMenuIfMouseCaptureLost()) {
return;
}
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (menu_controller && !menu_controller->drag_in_progress()) {
menu_controller->Cancel(MenuController::ExitType::kAll);
}
Widget::OnMouseCaptureLost();
}
void MenuHost::OnNativeWidgetDestroyed() {
if (!destroying_) {
submenu_->MenuHostDestroyed();
}
Widget::OnNativeWidgetDestroyed();
}
void MenuHost::OnOwnerClosing() {
if (destroying_) {
return;
}
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (menu_controller && !menu_controller->drag_in_progress()) {
menu_controller->Cancel(MenuController::ExitType::kAll);
}
}
void MenuHost::OnDragWillStart() {
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
DCHECK(menu_controller);
menu_controller->OnDragWillStart();
}
void MenuHost::OnDragComplete() {
if (destroying_) {
return;
}
MenuController* menu_controller =
submenu_->GetMenuItem()->GetMenuController();
if (!menu_controller) {
return;
}
bool should_close =
menu_controller->exit_type() != MenuController::ExitType::kNone;
if (auto* const delegate = submenu_->GetMenuItem()->GetDelegate()) {
should_close |= delegate->ShouldCloseOnDragComplete();
}
menu_controller->OnDragComplete(should_close);
}
Widget* MenuHost::GetPrimaryWindowWidget() {
return GetOwner() ? GetOwner()->GetPrimaryWindowWidget()
: Widget::GetPrimaryWindowWidget();
}
gfx::Insets MenuHost::GetCustomInsetsInDIP() const {
#if BUILDFLAG(IS_OZONE)
if (submenu_) {
return submenu_->GetScrollViewContainer()->outside_border_insets();
}
#endif
return gfx::Insets();
}
void MenuHost::OnWidgetDestroying(Widget* widget) {
DCHECK_EQ(GetOwner(), widget);
owner_observation_.Reset();
native_view_for_gestures_ = gfx::NativeView();
}
Widget* MenuHost::GetOwner() {
return owner_observation_.GetSource();
}
}