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