summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/web_contents_modal_dialog_manager_views_mac.mm
blob: 31f057fa9e60354beddffac721758c8d75945cfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/web_contents_modal_dialog_manager_views_mac.h"

#import <Cocoa/Cocoa.h>

#import "base/mac/foundation_util.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h"
#include "content/public/browser/web_contents.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
#import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
#include "ui/views/widget/widget.h"

// A wrapper for a views::Widget dialog to interact with a Cocoa browser's
// ContrainedWindowSheetController. Similar to CustomConstrainedWindowSheet, but
// since Widgets of dialog type animate themselves, and also manage their own
// parenting, there's not much to do except position properly.
@interface WrappedConstrainedWindowSheet : NSObject<ConstrainedWindowSheet> {
 @private
  base::scoped_nsobject<NSWindow> customWindow_;
}
- (id)initWithCustomWindow:(NSWindow*)customWindow;
@end

@implementation WrappedConstrainedWindowSheet

- (id)initWithCustomWindow:(NSWindow*)customWindow {
  if ((self = [super init])) {
    customWindow_.reset([customWindow retain]);
  }
  return self;
}

// ConstrainedWindowSheet implementation.

- (void)showSheetForWindow:(NSWindow*)window {
  // This is only called for the initial show, then calls go to unhideSheet.
  // Since Widget::Show() will be called, just update the position.
  [self updateSheetPosition];
}

- (void)closeSheetWithAnimation:(BOOL)withAnimation {
  // Nothing to do here. Either SingleWebContentsDialogManagerViewsMac::Close()
  // was called or Widget::Close(). Both cases ending up in OnWidgetClosing() to
  // call [ConstrainedWindowSheetController closeSheet:], which calls this.
  // However, the Widget is already closing in those cases.

  // OnWidgetClosing() is also the _only_ trigger. The exception would be
  // -[ConstrainedWindowSheetController onParentWindowWillClose:] which also
  // calls closeSheetWithAnimation:, but a Widget never gets there because
  // WebContentsModalDialogManager::CloseAllDialogs() is triggered from
  // -[BrowserWindowController windowShouldClose:], but the notification that
  // calls onParentWindowWillClose always happens once that has returned YES.

  // So, since onParentWindowWillClose never calls this, we can assert that
  // withAnimation is YES, otherwise there's some code path that might not be
  // catered for.
  DCHECK(withAnimation);
}

- (void)hideSheet {
  // Hide the sheet window by setting the alpha to 0. This technique is used
  // instead of -orderOut: because that may cause a Spaces change or window
  // ordering change.
  [customWindow_ setAlphaValue:0.0];
  // TODO(tapted): Support child windows.
  DCHECK_EQ(0u, [[customWindow_ childWindows] count]);
}

- (void)unhideSheet {
  [customWindow_ setAlphaValue:1.0];
  DCHECK_EQ(0u, [[customWindow_ childWindows] count]);
}

- (void)pulseSheet {
  base::scoped_nsobject<NSAnimation> animation(
      [[ConstrainedWindowAnimationPulse alloc] initWithWindow:customWindow_]);
  [animation startAnimation];
}

- (void)makeSheetKeyAndOrderFront {
  // If the window is not visible, do nothing. Widget::Show() is responsible for
  // showing, and it may want to animate it.
  if ([customWindow_ isVisible])
    [customWindow_ makeKeyAndOrderFront:nil];
}

- (void)updateSheetPosition {
  ConstrainedWindowSheetController* controller =
      [ConstrainedWindowSheetController controllerForSheet:self];
  NSPoint origin = [controller originForSheet:self
                               withWindowSize:[customWindow_ frame].size];
  [customWindow_ setFrameOrigin:origin];
}

- (void)resizeWithNewSize:(NSSize)size {
  // NOOP
}

- (NSWindow*)sheetWindow {
  return customWindow_;
}

@end

SingleWebContentsDialogManagerViewsMac::SingleWebContentsDialogManagerViewsMac(
    NSWindow* dialog,
    web_modal::SingleWebContentsDialogManagerDelegate* delegate)
    : delegate_(delegate), host_(nullptr) {
  sheet_.reset(
      [[WrappedConstrainedWindowSheet alloc] initWithCustomWindow:dialog]);
  widget_ = views::Widget::GetWidgetForNativeWindow(dialog);
  DCHECK(widget_);
  widget_->AddObserver(this);
}

SingleWebContentsDialogManagerViewsMac::
    ~SingleWebContentsDialogManagerViewsMac() {
  DCHECK(!widget_->HasObserver(this));
}

void SingleWebContentsDialogManagerViewsMac::Show() {
  DCHECK(host_);

  NSView* parent_view = host_->GetHostView();
  // Note that simply [parent_view window] for an inactive tab is nil. However,
  // the following should always be non-nil for all WebContents containers.
  NSWindow* parent_window =
      delegate_->GetWebContents()->GetTopLevelNativeWindow();
  // Register with the ConstrainedWindowSheetController. This ensures that, e.g.
  // the NSView that overlays the Cocoa WebContents to intercept clicks is
  // installed and managed.
  [[ConstrainedWindowSheetController controllerForParentWindow:parent_window]
          showSheet:sheet_
      forParentView:parent_view];

  if (!widget_->IsVisible())
    widget_->Show();
}

void SingleWebContentsDialogManagerViewsMac::Hide() {
  NSWindow* parent_window =
      delegate_->GetWebContents()->GetTopLevelNativeWindow();
  [[ConstrainedWindowSheetController controllerForParentWindow:parent_window]
      hideSheet];
}

void SingleWebContentsDialogManagerViewsMac::Close() {
  // When the WebContents is destroyed, WebContentsModalDialogManager
  // ::CloseAllDialogs will call this. Close the Widget in the same manner as
  // the dialogs so that codepaths are consistent.
  widget_->Close();  // Note: Synchronously calls OnWidgetClosing() below.
}

void SingleWebContentsDialogManagerViewsMac::Focus() {
  // Handled by ConstrainedWindowSheetController.
}
void SingleWebContentsDialogManagerViewsMac::Pulse() {
  // Handled by ConstrainedWindowSheetController.
}

void SingleWebContentsDialogManagerViewsMac::HostChanged(
    web_modal::WebContentsModalDialogHost* new_host) {
  // No need to observe the host. For Cocoa, the constrained window controller
  // will reposition the dialog when necessary. The host can also never change.
  // Tabs showing a dialog can not be dragged off a Cocoa browser window.
  // However, closing a tab with a dialog open will set the host back to null.
  DCHECK_NE(!!host_, !!new_host);
  host_ = new_host;
}

gfx::NativeWindow SingleWebContentsDialogManagerViewsMac::dialog() {
  return [sheet_ sheetWindow];
}

// views::WidgetObserver:
void SingleWebContentsDialogManagerViewsMac::OnWidgetClosing(
    views::Widget* widget) {
  DCHECK_EQ(widget, widget_);
  widget->RemoveObserver(this);
  [[ConstrainedWindowSheetController controllerForSheet:sheet_]
      closeSheet:sheet_];
  delegate_->WillClose(dialog());  // Deletes |this|.
}

void SingleWebContentsDialogManagerViewsMac::OnWidgetDestroying(
    views::Widget* widget) {
  // On Mac, this would only be reached if something called -[NSWindow close]
  // on the dialog without going through Widget::Close or CloseNow(). Since
  // dialogs have no titlebar, it won't come from the system. If something
  // internally calls -[NSWindow close] it might break lifetime assumptions
  // made by DialogDelegate.
  NOTREACHED();
}