1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/constrained_window/constrained_window_views.h"
6
7 #include <algorithm>
8 #include <memory>
9
10 #include "base/callback.h"
11 #include "base/macros.h"
12 #include "base/no_destructor.h"
13 #include "build/build_config.h"
14 #include "components/constrained_window/constrained_window_views_client.h"
15 #include "components/web_modal/web_contents_modal_dialog_host.h"
16 #include "components/web_modal/web_contents_modal_dialog_manager.h"
17 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
18 #include "ui/display/display.h"
19 #include "ui/display/screen.h"
20 #include "ui/views/border.h"
21 #include "ui/views/widget/widget.h"
22 #include "ui/views/widget/widget_observer.h"
23 #include "ui/views/window/dialog_delegate.h"
24
25 #if defined(OS_MACOSX)
26 #import "components/constrained_window/native_web_contents_modal_dialog_manager_views_mac.h"
27 #endif
28
29 using web_modal::ModalDialogHost;
30 using web_modal::ModalDialogHostObserver;
31
32 namespace constrained_window {
33 namespace {
34
35 // Storage access for the currently active ConstrainedWindowViewsClient.
CurrentClient()36 std::unique_ptr<ConstrainedWindowViewsClient>& CurrentClient() {
37 static base::NoDestructor<std::unique_ptr<ConstrainedWindowViewsClient>>
38 client;
39 return *client;
40 }
41
42 // The name of a key to store on the window handle to associate
43 // WidgetModalDialogHostObserverViews with the Widget.
44 const char* const kWidgetModalDialogHostObserverViewsKey =
45 "__WIDGET_MODAL_DIALOG_HOST_OBSERVER_VIEWS__";
46
47 // Applies positioning changes from the ModalDialogHost to the Widget.
48 class WidgetModalDialogHostObserverViews
49 : public views::WidgetObserver,
50 public ModalDialogHostObserver {
51 public:
WidgetModalDialogHostObserverViews(ModalDialogHost * host,views::Widget * target_widget,const char * const native_window_property)52 WidgetModalDialogHostObserverViews(ModalDialogHost* host,
53 views::Widget* target_widget,
54 const char *const native_window_property)
55 : host_(host),
56 target_widget_(target_widget),
57 native_window_property_(native_window_property) {
58 DCHECK(host_);
59 DCHECK(target_widget_);
60 host_->AddObserver(this);
61 target_widget_->AddObserver(this);
62 }
63
~WidgetModalDialogHostObserverViews()64 ~WidgetModalDialogHostObserverViews() override {
65 if (host_)
66 host_->RemoveObserver(this);
67 target_widget_->RemoveObserver(this);
68 target_widget_->SetNativeWindowProperty(native_window_property_, nullptr);
69 }
70
71 // WidgetObserver overrides
OnWidgetDestroying(views::Widget * widget)72 void OnWidgetDestroying(views::Widget* widget) override { delete this; }
73
74 // WebContentsModalDialogHostObserver overrides
OnPositionRequiresUpdate()75 void OnPositionRequiresUpdate() override {
76 UpdateWidgetModalDialogPosition(target_widget_, host_);
77 }
78
OnHostDestroying()79 void OnHostDestroying() override {
80 host_->RemoveObserver(this);
81 host_ = nullptr;
82 }
83
84 private:
85 ModalDialogHost* host_;
86 views::Widget* target_widget_;
87 const char* const native_window_property_;
88
89 DISALLOW_COPY_AND_ASSIGN(WidgetModalDialogHostObserverViews);
90 };
91
UpdateModalDialogPosition(views::Widget * widget,web_modal::ModalDialogHost * dialog_host,const gfx::Size & size)92 void UpdateModalDialogPosition(views::Widget* widget,
93 web_modal::ModalDialogHost* dialog_host,
94 const gfx::Size& size) {
95 // Do not forcibly update the dialog widget position if it is being dragged.
96 if (widget->HasCapture())
97 return;
98
99 views::Widget* host_widget =
100 views::Widget::GetWidgetForNativeView(dialog_host->GetHostView());
101
102 // If the host view is not backed by a Views::Widget, just update the widget
103 // size. This can happen on MacViews under the Cocoa browser where the window
104 // modal dialogs are displayed as sheets, and their position is managed by a
105 // ConstrainedWindowSheetController instance.
106 if (!host_widget) {
107 widget->SetSize(size);
108 return;
109 }
110
111 gfx::Point position = dialog_host->GetDialogPosition(size);
112 views::Border* border = widget->non_client_view()->frame_view()->border();
113 // Border may be null during widget initialization.
114 if (border) {
115 // Align the first row of pixels inside the border. This is the apparent
116 // top of the dialog.
117 position.set_y(position.y() - border->GetInsets().top());
118 }
119
120 if (widget->is_top_level()) {
121 position += host_widget->GetClientAreaBoundsInScreen().OffsetFromOrigin();
122 // If the dialog extends partially off any display, clamp its position to
123 // be fully visible within that display. If the dialog doesn't intersect
124 // with any display clamp its position to be fully on the nearest display.
125 gfx::Rect display_rect = gfx::Rect(position, size);
126 const display::Display display =
127 display::Screen::GetScreen()->GetDisplayNearestView(
128 dialog_host->GetHostView());
129 const gfx::Rect work_area = display.work_area();
130 if (!work_area.Contains(display_rect))
131 display_rect.AdjustToFit(work_area);
132 position = display_rect.origin();
133 }
134
135 widget->SetBounds(gfx::Rect(position, size));
136 }
137
138 } // namespace
139
140 // static
SetConstrainedWindowViewsClient(std::unique_ptr<ConstrainedWindowViewsClient> new_client)141 void SetConstrainedWindowViewsClient(
142 std::unique_ptr<ConstrainedWindowViewsClient> new_client) {
143 CurrentClient() = std::move(new_client);
144 }
145
UpdateWebContentsModalDialogPosition(views::Widget * widget,web_modal::WebContentsModalDialogHost * dialog_host)146 void UpdateWebContentsModalDialogPosition(
147 views::Widget* widget,
148 web_modal::WebContentsModalDialogHost* dialog_host) {
149 gfx::Size size = widget->GetRootView()->GetPreferredSize();
150 gfx::Size max_size = dialog_host->GetMaximumDialogSize();
151 // Enlarge the max size by the top border, as the dialog will be shifted
152 // outside the area specified by the dialog host by this amount later.
153 views::Border* border =
154 widget->non_client_view()->frame_view()->border();
155 // Border may be null during widget initialization.
156 if (border)
157 max_size.Enlarge(0, border->GetInsets().top());
158 size.SetToMin(max_size);
159 UpdateModalDialogPosition(widget, dialog_host, size);
160 }
161
UpdateWidgetModalDialogPosition(views::Widget * widget,web_modal::ModalDialogHost * dialog_host)162 void UpdateWidgetModalDialogPosition(views::Widget* widget,
163 web_modal::ModalDialogHost* dialog_host) {
164 UpdateModalDialogPosition(widget, dialog_host,
165 widget->GetRootView()->GetPreferredSize());
166 }
167
GetTopLevelWebContents(content::WebContents * initiator_web_contents)168 content::WebContents* GetTopLevelWebContents(
169 content::WebContents* initiator_web_contents) {
170 return initiator_web_contents->GetResponsibleWebContents();
171 }
172
ShowWebModalDialogViews(views::WidgetDelegate * dialog,content::WebContents * initiator_web_contents)173 views::Widget* ShowWebModalDialogViews(
174 views::WidgetDelegate* dialog,
175 content::WebContents* initiator_web_contents) {
176 DCHECK(CurrentClient());
177 // For embedded WebContents, use the embedder's WebContents for constrained
178 // window.
179 content::WebContents* web_contents =
180 GetTopLevelWebContents(initiator_web_contents);
181 views::Widget* widget = CreateWebModalDialogViews(dialog, web_contents);
182 ShowModalDialog(widget->GetNativeWindow(), web_contents);
183 return widget;
184 }
185
186 #if defined(OS_MACOSX)
ShowWebModalDialogWithOverlayViews(views::WidgetDelegate * dialog,content::WebContents * initiator_web_contents,base::OnceCallback<void (views::Widget *)> show_sheet)187 views::Widget* ShowWebModalDialogWithOverlayViews(
188 views::WidgetDelegate* dialog,
189 content::WebContents* initiator_web_contents,
190 base::OnceCallback<void(views::Widget*)> show_sheet) {
191 DCHECK(CurrentClient());
192 // For embedded WebContents, use the embedder's WebContents for constrained
193 // window.
194 content::WebContents* web_contents =
195 GetTopLevelWebContents(initiator_web_contents);
196 views::Widget* widget = CreateWebModalDialogViews(dialog, web_contents);
197 web_modal::WebContentsModalDialogManager* manager =
198 web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
199 std::unique_ptr<web_modal::SingleWebContentsDialogManager> dialog_manager(
200 new NativeWebContentsModalDialogManagerViewsMac(
201 widget->GetNativeWindow(), manager, std::move(show_sheet)));
202 manager->ShowDialogWithManager(widget->GetNativeWindow(),
203 std::move(dialog_manager));
204 return widget;
205 }
206 #endif
207
CreateWebModalDialogViews(views::WidgetDelegate * dialog,content::WebContents * web_contents)208 views::Widget* CreateWebModalDialogViews(views::WidgetDelegate* dialog,
209 content::WebContents* web_contents) {
210 DCHECK_EQ(ui::MODAL_TYPE_CHILD, dialog->GetModalType());
211 web_modal::WebContentsModalDialogManager* manager =
212 web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
213 CHECK(manager);
214 return views::DialogDelegate::CreateDialogWidget(
215 dialog, nullptr,
216 manager->delegate()->GetWebContentsModalDialogHost()->GetHostView());
217 }
218
CreateBrowserModalDialogViews(views::DialogDelegate * dialog,gfx::NativeWindow parent)219 views::Widget* CreateBrowserModalDialogViews(views::DialogDelegate* dialog,
220 gfx::NativeWindow parent) {
221 DCHECK_NE(ui::MODAL_TYPE_CHILD, dialog->GetModalType());
222 DCHECK_NE(ui::MODAL_TYPE_NONE, dialog->GetModalType());
223 DCHECK(!parent || CurrentClient());
224
225 gfx::NativeView parent_view =
226 parent ? CurrentClient()->GetDialogHostView(parent) : nullptr;
227 views::Widget* widget =
228 views::DialogDelegate::CreateDialogWidget(dialog, nullptr, parent_view);
229
230 bool requires_positioning = dialog->use_custom_frame();
231
232 #if defined(OS_MACOSX)
233 // On Mac, window modal dialogs are displayed as sheets, so their position is
234 // managed by the parent window.
235 requires_positioning = false;
236 #endif
237
238 if (!requires_positioning)
239 return widget;
240
241 ModalDialogHost* host =
242 parent ? CurrentClient()->GetModalDialogHost(parent) : nullptr;
243 if (host) {
244 DCHECK_EQ(parent_view, host->GetHostView());
245 ModalDialogHostObserver* dialog_host_observer =
246 new WidgetModalDialogHostObserverViews(
247 host, widget, kWidgetModalDialogHostObserverViewsKey);
248 dialog_host_observer->OnPositionRequiresUpdate();
249 }
250 return widget;
251 }
252
253 } // namespace constrained window
254