#include "components/constrained_window/constrained_window_views.h"
#include <algorithm>
#include <memory>
#include "base/check_op.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/scoped_observation.h"
#include "build/build_config.h"
#include "components/constrained_window/constrained_window_views_client.h"
#include "components/guest_view/browser/guest_view_base.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/bubble/bubble_dialog_model_host.h"
#include "ui/views/widget/native_widget.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif
using web_modal::ModalDialogHost;
using web_modal::ModalDialogHostObserver;
DEFINE_UI_CLASS_PROPERTY_TYPE(ModalDialogHostObserver*)
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ModalDialogHostObserver,
kModalDialogHostObserverKey)
namespace constrained_window {
const void* kConstrainedWindowWidgetIdentifier = "ConstrainedWindowWidget";
namespace {
std::unique_ptr<ConstrainedWindowViewsClient>& CurrentBrowserModalClient() {
static base::NoDestructor<std::unique_ptr<ConstrainedWindowViewsClient>>
client;
return *client;
}
class ModalDialogHostObserverViews : public ModalDialogHostObserver {
public:
ModalDialogHostObserverViews(ModalDialogHost* host,
views::Widget* dialog_widget,
bool auto_update_position)
: host_(host),
dialog_widget_(dialog_widget),
auto_update_position_(auto_update_position) {
CHECK(host_);
CHECK(dialog_widget_);
modal_dialog_host_observation_.Observe(host);
}
ModalDialogHostObserverViews(const ModalDialogHostObserverViews&) = delete;
ModalDialogHostObserverViews& operator=(const ModalDialogHostObserverViews&) =
delete;
~ModalDialogHostObserverViews() override = default;
void OnPositionRequiresUpdate() override {
if (auto_update_position_) {
CHECK(host_);
UpdateWidgetModalDialogPosition(dialog_widget_, host_);
}
}
void OnHostDestroying() override {
modal_dialog_host_observation_.Reset();
host_ = nullptr;
dialog_widget_->CloseNow();
}
private:
raw_ptr<ModalDialogHost> host_;
raw_ptr<views::Widget> dialog_widget_;
const bool auto_update_position_;
base::ScopedObservation<ModalDialogHost, ModalDialogHostObserver>
modal_dialog_host_observation_{this};
};
gfx::Rect GetModalDialogBounds(views::Widget* widget,
web_modal::ModalDialogHost* dialog_host,
const gfx::Size& size) {
auto host_view = dialog_host->GetHostView();
views::Widget* host_widget =
host_view ? views::Widget::GetWidgetForNativeView(host_view) : nullptr;
if (!host_widget) {
return gfx::Rect(dialog_host->GetDialogPosition(size), size);
}
gfx::Point position = dialog_host->GetDialogPosition(size);
position.set_y(position.y() -
widget->non_client_view()->frame_view()->GetInsets().top());
if (widget->is_top_level() && SupportsGlobalScreenCoordinates()) {
position += host_widget->GetClientAreaBoundsInScreen().OffsetFromOrigin();
gfx::Rect display_rect = gfx::Rect(position, size);
const display::Display display =
display::Screen::Get()->GetDisplayNearestView(
dialog_host->GetHostView());
const gfx::Rect work_area = display.work_area();
if (!work_area.Contains(display_rect))
display_rect.AdjustToFit(work_area);
position = display_rect.origin();
}
return gfx::Rect(position, size);
}
void UpdateModalDialogPosition(views::Widget* widget,
web_modal::ModalDialogHost* dialog_host,
const gfx::Size& size) {
if (widget->HasCapture()) {
return;
}
auto host_view = dialog_host->GetHostView();
views::Widget* host_widget =
host_view ? views::Widget::GetWidgetForNativeView(host_view) : nullptr;
if (!host_widget) {
#if BUILDFLAG(IS_MAC)
widget->SetSize(size);
#elif BUILDFLAG(IS_POSIX)
widget->SetBounds(gfx::Rect(dialog_host->GetDialogPosition(size), size));
#endif
return;
}
widget->SetBounds(GetModalDialogBounds(widget, dialog_host, size));
}
void ConfigureDesiredBoundsDelegate(views::WidgetDelegate* dialog_delegate,
web_modal::ModalDialogHost* dialog_host) {
views::Widget* widget = dialog_delegate->GetWidget();
CHECK(widget)
<< "SetDesiredBoundsDelegate() must be called after creating the widget.";
dialog_delegate->set_desired_bounds_delegate(base::BindRepeating(
[](views::Widget* widget,
web_modal::ModalDialogHost* dialog_host) -> gfx::Rect {
return GetModalDialogBounds(
widget, dialog_host, widget->GetRootView()->GetPreferredSize({}));
},
widget, dialog_host));
}
}
class BrowserModalHelper {
public:
static views::Widget* Show(std::unique_ptr<ui::DialogModel> dialog_model,
gfx::NativeWindow parent) {
gfx::NativeView parent_view =
parent ? CurrentBrowserModalClient()->GetDialogHostView(parent) :
gfx::NativeView();
gfx::AcceleratedWidget parent_widget =
parent ? CurrentBrowserModalClient()->GetModalDialogHost(parent)->
GetAcceleratedWidget() : gfx::kNullAcceleratedWidget;
bool will_use_custom_frame = views::DialogDelegate::CanSupportCustomFrame(
parent_view, parent_widget);
auto dialog = views::BubbleDialogModelHost::CreateModal(
std::move(dialog_model), ui::mojom::ModalType::kWindow,
will_use_custom_frame);
dialog->SetOwnedByWidget(views::WidgetDelegate::OwnedByWidgetPassKey());
auto* widget = constrained_window::CreateBrowserModalDialogViews(
std::move(dialog), parent);
CHECK_EQ(widget->widget_delegate()->AsDialogDelegate()->use_custom_frame(),
will_use_custom_frame);
widget->Show();
return widget;
}
};
void SetConstrainedWindowViewsClient(
std::unique_ptr<ConstrainedWindowViewsClient> new_client) {
CurrentBrowserModalClient() = std::move(new_client);
}
void UpdateWebContentsModalDialogPosition(
views::Widget* widget,
web_modal::WebContentsModalDialogHost* dialog_host) {
gfx::Size size = widget->GetRootView()->GetPreferredSize({});
gfx::Size max_size = dialog_host->GetMaximumDialogSize();
max_size.Enlarge(0,
widget->non_client_view()->frame_view()->GetInsets().top());
size.SetToMin(max_size);
UpdateModalDialogPosition(widget, dialog_host, size);
}
void UpdateWidgetModalDialogPosition(views::Widget* widget,
web_modal::ModalDialogHost* dialog_host) {
UpdateModalDialogPosition(widget, dialog_host,
widget->GetRootView()->GetPreferredSize({}));
}
content::WebContents* GetTopLevelWebContents(
content::WebContents* initiator_web_contents) {
return guest_view::GuestViewBase::GetTopLevelWebContents(
initiator_web_contents->GetResponsibleWebContents());
}
views::Widget* ShowWebModalDialogViews(
views::WidgetDelegate* dialog,
content::WebContents* initiator_web_contents) {
content::WebContents* web_contents =
GetTopLevelWebContents(initiator_web_contents);
views::Widget* widget = CreateWebModalDialogViews(dialog, web_contents);
ShowModalDialog(widget->GetNativeWindow(), web_contents);
return widget;
}
std::unique_ptr<views::Widget> ShowWebModalDialogViewsOwned(
views::WidgetDelegate* dialog,
content::WebContents* initiator_web_contents,
views::Widget::InitParams::Ownership expected_ownership) {
views::Widget* widget =
ShowWebModalDialogViews(dialog, initiator_web_contents);
CHECK_EQ(widget->ownership(), expected_ownership);
return base::WrapUnique<views::Widget>(widget);
}
views::Widget* CreateWebModalDialogViews(views::WidgetDelegate* dialog,
content::WebContents* web_contents) {
DCHECK_EQ(ui::mojom::ModalType::kChild, dialog->GetModalType());
web_modal::WebContentsModalDialogManager* manager =
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
web_modal::ModalDialogHost* const dialog_host =
manager->delegate()->GetWebContentsModalDialogHost(web_contents);
CHECK(dialog_host);
dialog->set_use_desktop_widget_override(
!dialog_host->ShouldConstrainDialogBoundsByHost());
views::Widget* widget = views::DialogDelegate::CreateDialogWidget(
dialog, gfx::NativeWindow(), dialog_host->GetHostView());
std::unique_ptr<ModalDialogHostObserver> observer =
std::make_unique<ModalDialogHostObserverViews>(
dialog_host, widget, false);
widget->SetProperty(kModalDialogHostObserverKey, std::move(observer));
ConfigureDesiredBoundsDelegate(dialog, dialog_host);
widget->SetNativeWindowProperty(
views::kWidgetIdentifierKey,
const_cast<void*>(kConstrainedWindowWidgetIdentifier));
return widget;
}
views::Widget* CreateBrowserModalDialogViews(
std::unique_ptr<views::DialogDelegate> dialog,
gfx::NativeWindow parent) {
return CreateBrowserModalDialogViews(dialog.release(), parent);
}
views::Widget* CreateBrowserModalDialogViews(views::DialogDelegate* dialog,
gfx::NativeWindow parent) {
DCHECK_NE(ui::mojom::ModalType::kChild, dialog->GetModalType());
DCHECK_NE(ui::mojom::ModalType::kNone, dialog->GetModalType());
DCHECK(!parent || CurrentBrowserModalClient());
gfx::NativeView parent_view =
parent ? CurrentBrowserModalClient()->GetDialogHostView(parent)
: gfx::NativeView();
gfx::AcceleratedWidget parent_widget =
parent ? CurrentBrowserModalClient()->GetModalDialogHost(parent)->
GetAcceleratedWidget() : gfx::kNullAcceleratedWidget;
views::Widget* widget = views::DialogDelegate::CreateDialogWidget(
dialog, gfx::NativeWindow(), parent_view, parent_widget);
widget->SetNativeWindowProperty(
views::kWidgetIdentifierKey,
const_cast<void*>(kConstrainedWindowWidgetIdentifier));
bool requires_positioning = dialog->use_custom_frame();
#if BUILDFLAG(IS_APPLE)
requires_positioning = false;
#endif
if (!requires_positioning)
return widget;
ModalDialogHost* host =
CurrentBrowserModalClient()->GetModalDialogHost(parent);
if (host) {
DCHECK_EQ(parent_view, host->GetHostView());
std::unique_ptr<ModalDialogHostObserver> observer =
std::make_unique<ModalDialogHostObserverViews>(
host, widget, true);
widget->SetProperty(kModalDialogHostObserverKey, std::move(observer));
widget->GetProperty(kModalDialogHostObserverKey)
->OnPositionRequiresUpdate();
ConfigureDesiredBoundsDelegate(dialog, host);
}
return widget;
}
views::Widget* ShowBrowserModal(std::unique_ptr<ui::DialogModel> dialog_model,
gfx::NativeWindow parent) {
return BrowserModalHelper::Show(std::move(dialog_model), parent);
}
views::Widget* ShowWebModal(std::unique_ptr<ui::DialogModel> dialog_model,
content::WebContents* web_contents) {
return constrained_window::ShowWebModalDialogViews(
views::BubbleDialogModelHost::CreateModal(std::move(dialog_model),
ui::mojom::ModalType::kChild)
.release(),
web_contents);
}
bool SupportsGlobalScreenCoordinates() {
#if !BUILDFLAG(IS_OZONE)
return true;
#else
return ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.supports_global_screen_coordinates;
#endif
}
bool PlatformClipsChildrenToViewport() {
#if BUILDFLAG(IS_LINUX)
return true;
#else
return false;
#endif
}
}