1 // Copyright 2014 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.
5 #import "ui/views/cocoa/bridged_native_widget.h"
7 #import <objc/runtime.h>
9 #include "base/logging.h"
10 #import "base/mac/foundation_util.h"
11 #include "base/mac/mac_util.h"
12 #import "base/mac/sdk_forward_declarations.h"
13 #include "base/thread_task_runner_handle.h"
14 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
15 #include "ui/base/hit_test.h"
16 #include "ui/base/ime/input_method.h"
17 #include "ui/base/ime/input_method_factory.h"
18 #include "ui/gfx/display.h"
19 #include "ui/gfx/geometry/dip_util.h"
20 #import "ui/gfx/mac/coordinate_conversion.h"
21 #import "ui/gfx/mac/nswindow_frame_controls.h"
22 #include "ui/gfx/screen.h"
23 #import "ui/views/cocoa/bridged_content_view.h"
24 #import "ui/views/cocoa/cocoa_mouse_capture.h"
25 #include "ui/views/cocoa/tooltip_manager_mac.h"
26 #import "ui/views/cocoa/views_nswindow_delegate.h"
27 #import "ui/views/cocoa/widget_owner_nswindow_adapter.h"
28 #include "ui/views/view.h"
29 #include "ui/views/views_delegate.h"
30 #include "ui/views/widget/native_widget_mac.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/views/widget/widget_aura_utils.h"
33 #include "ui/views/widget/widget_delegate.h"
35 // The NSView that hosts the composited CALayer drawing the UI. It fills the
36 // window but is not hittable so that accessibility hit tests always go to the
37 // BridgedContentView.
38 @interface ViewsCompositorSuperview : NSView
41 @implementation ViewsCompositorSuperview
42 - (NSView*)hitTest:(NSPoint)aPoint {
47 // This class overrides NSAnimation methods to invalidate the shadow for each
48 // frame. It is required because the show animation uses CGSSetWindowWarp()
49 // which is touchy about the consistency of the points it is given. The show
50 // animation includes a translate, which fails to apply properly to the window
51 // shadow, when that shadow is derived from a layer-hosting view. So invalidate
52 // it. This invalidation is only needed to cater for the translate. It is not
53 // required if CGSSetWindowWarp() is used in a way that keeps the center point
54 // of the window stationary (e.g. a scale). It's also not required for the hide
55 // animation: in that case, the shadow is never invalidated so retains the
56 // shadow calculated before a translate is applied.
57 @interface ModalShowAnimationWithLayer : ConstrainedWindowAnimationShow
60 @implementation ModalShowAnimationWithLayer
61 - (void)stopAnimation {
62 [super stopAnimation];
63 [window_ invalidateShadow];
65 - (void)setCurrentProgress:(NSAnimationProgress)progress {
66 [super setCurrentProgress:progress];
67 [window_ invalidateShadow];
73 int kWindowPropertiesKey;
75 float GetDeviceScaleFactorFromView(NSView* view) {
76 gfx::Display display =
77 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
78 DCHECK(display.is_valid());
79 return display.device_scale_factor();
82 // Returns true if bounds passed to window in SetBounds should be treated as
83 // though they are in screen coordinates.
84 bool PositionWindowInScreenCoordinates(views::Widget* widget,
85 views::Widget::InitParams::Type type) {
86 // Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
87 if (views::GetAuraWindowTypeForWidgetType(type) == ui::wm::WINDOW_TYPE_POPUP)
90 return widget && widget->is_top_level();
93 // Return the content size for a minimum or maximum widget size.
94 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
95 const gfx::Size& window_size) {
97 NSMakeRect(0, 0, window_size.width(), window_size.height());
98 // Note gfx::Size will prevent dimensions going negative. They are allowed to
99 // be zero at this point, because Widget::GetMinimumSize() may later increase
101 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
104 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
105 id delegate = [[ns_event window] delegate];
108 respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
109 [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
112 // Check if a mouse-down event should drag the window. If so, repost the event.
113 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
115 // Nothing reposted: hit-test new mouse-downs to see if they need to be
116 // ignored and reposted after changing draggability.
118 // Expecting the next event to be the reposted event: let it go through.
120 // If, while reposting, another mousedown was received: when the reposted
121 // event is seen, ignore it.
125 // Which repost we're expecting to receive.
126 static RepostState repost_state = NONE;
127 // The event number of the reposted event. This let's us track whether an
128 // event is actually the repost since user-generated events have increasing
129 // event numbers. This is only valid while |repost_state != NONE|.
130 static NSInteger reposted_event_number;
132 NSInteger event_number = [ns_event eventNumber];
134 // The logic here is a bit convoluted because we want to mitigate race
135 // conditions if somehow a different mouse-down occurs between reposts.
136 // Specifically, we want to avoid:
137 // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is
138 // draggable outside of a repost cycle),
139 // - any repost loop.
141 if (repost_state == NONE) {
142 if (WindowWantsMouseDownReposted(ns_event)) {
143 repost_state = EXPECTING_REPOST;
144 reposted_event_number = event_number;
145 CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
152 if (repost_state == EXPECTING_REPOST) {
153 // Call through so that the window is made non-draggable again.
154 WindowWantsMouseDownReposted(ns_event);
156 if (reposted_event_number == event_number) {
157 // Reposted event received.
162 // We were expecting a repost, but since this is a new mouse-down, cancel
163 // reposting and allow event to continue as usual.
164 repost_state = REPOST_CANCELLED;
168 DCHECK_EQ(REPOST_CANCELLED, repost_state);
169 if (reposted_event_number == event_number) {
170 // Reposting was cancelled, now that we've received the event, we don't
171 // expect to see it again.
179 // Support window caption/draggable regions.
180 // In AppKit, non-client regions are set by overriding
181 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
182 // installed and performs window moving when mouse-downs land in the area.
183 // In Views, non-client regions are determined via hit-tests when the event
185 // To bridge the two models, we monitor mouse-downs with
186 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
187 // events after window dragging is handled, so for mouse-downs that land on a
188 // draggable point, we cancel the event and repost it at the CGSessionEventTap
189 // level so that window dragging will be handled again.
190 void SetupDragEventMonitor() {
191 static id monitor = nil;
196 addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
197 handler:^NSEvent*(NSEvent* ns_event) {
198 return RepostEventIfHandledByWindow(ns_event);
207 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
209 const gfx::Size& content_size) {
210 NSRect content_rect =
211 NSMakeRect(0, 0, content_size.width(), content_size.height());
212 NSRect frame_rect = [window frameRectForContentRect:content_rect];
213 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
216 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
217 : native_widget_mac_(parent),
218 focus_manager_(nullptr),
219 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
221 target_fullscreen_state_(false),
222 in_fullscreen_transition_(false),
223 window_visible_(false),
224 wants_to_be_visible_(false) {
225 SetupDragEventMonitor();
227 window_delegate_.reset(
228 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
231 BridgedNativeWidget::~BridgedNativeWidget() {
232 RemoveOrDestroyChildren();
233 DCHECK(child_windows_.empty());
234 SetFocusManager(NULL);
237 if ([window_ delegate]) {
238 // If the delegate is still set on a modal dialog, it means it was not
239 // closed via [NSApplication endSheet:]. This is probably OK if the widget
240 // was never shown. But Cocoa ignores close() calls on open sheets. Calling
241 // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
242 // DialogClientView assumes its delegate is alive when closing, which isn't
243 // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
244 // So ban it. Modal dialogs should be closed via Widget::Close().
245 DCHECK(!native_widget_mac_->IsWindowModalSheet());
247 // If the delegate is still set, it means OnWindowWillClose has not been
248 // called and the window is still open. Calling -[NSWindow close] will
249 // synchronously call OnWindowWillClose and notify NativeWidgetMac.
252 DCHECK(![window_ delegate]);
255 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
256 const Widget::InitParams& params) {
257 widget_type_ = params.type;
260 window_.swap(window);
261 [window_ setDelegate:window_delegate_];
263 // Register for application hide notifications so that visibility can be
264 // properly tracked. This is not done in the delegate so that the lifetime is
265 // tied to the C++ object, rather than the delegate (which may be reference
266 // counted). This is required since the application hides do not send an
267 // orderOut: to individual windows. Unhide, however, does send an order
269 [[NSNotificationCenter defaultCenter]
270 addObserver:window_delegate_
271 selector:@selector(onWindowOrderChanged:)
272 name:NSApplicationDidHideNotification
275 // Validate the window's initial state, otherwise the bridge's initial
276 // tracking state will be incorrect.
277 DCHECK(![window_ isVisible]);
278 DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
281 // Disallow creating child windows of views not currently in an NSWindow.
282 CHECK([params.parent window]);
283 BridgedNativeWidget* bridged_native_widget_parent =
284 NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
285 // If the parent is another BridgedNativeWidget, just add to the collection
286 // of child windows it owns and manages. Otherwise, create an adapter to
287 // anchor the child widget and observe when the parent NSWindow is closed.
288 if (bridged_native_widget_parent) {
289 parent_ = bridged_native_widget_parent;
290 bridged_native_widget_parent->child_windows_.push_back(this);
292 parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
296 // OSX likes to put shadows on most things. However, frameless windows (with
297 // styleMask = NSBorderlessWindowMask) default to no shadow. So change that.
298 // SHADOW_TYPE_DROP is used for Menus, which get the same shadow style on Mac.
299 switch (params.shadow_type) {
300 case Widget::InitParams::SHADOW_TYPE_NONE:
301 [window_ setHasShadow:NO];
303 case Widget::InitParams::SHADOW_TYPE_DEFAULT:
304 case Widget::InitParams::SHADOW_TYPE_DROP:
305 [window_ setHasShadow:YES];
307 } // No default case, to pick up new types.
309 // Set a meaningful initial bounds. Note that except for frameless widgets
310 // with no WidgetDelegate, the bounds will be set again by Widget after
311 // initializing the non-client view. In the former case, if bounds were not
312 // set at all, the creator of the Widget is expected to call SetBounds()
313 // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
314 // (i.e. 1x1) window appearing.
315 if (!params.bounds.IsEmpty()) {
316 SetBounds(params.bounds);
318 // If a position is set, but no size, complain. Otherwise, a 1x1 window
319 // would appear there, which might be unexpected.
320 DCHECK(params.bounds.origin().IsOrigin())
321 << "Zero-sized windows not supported on Mac.";
323 // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
324 // the bottom left of the screen. To support a client calling SetSize() only
325 // (and for consistency across platforms) put it at the top-left instead.
326 // Read back the current frame: it will be a 1x1 context rect but the frame
327 // size also depends on the window style.
328 NSRect frame_rect = [window_ frame];
329 SetBounds(gfx::Rect(gfx::Point(),
330 gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))));
333 // Widgets for UI controls (usually layered above web contents) start visible.
334 if (params.type == Widget::InitParams::TYPE_CONTROL)
335 SetVisibilityState(SHOW_INACTIVE);
337 // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are
338 // native on Mac, so nothing should ever want one in Widget form.
339 DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP);
340 tooltip_manager_.reset(new TooltipManagerMac(this));
343 void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
344 if (focus_manager_ == focus_manager)
348 focus_manager_->RemoveFocusChangeListener(this);
351 focus_manager->AddFocusChangeListener(this);
353 focus_manager_ = focus_manager;
356 void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
357 Widget* widget = native_widget_mac_->GetWidget();
358 // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated
359 // resizes. This is not what toolkit-views expects, so clamp. Note there is
360 // no check for maximum size (consistent with aura::Window::SetBounds()).
361 gfx::Size clamped_content_size =
362 GetClientSizeForWindowSize(window_, new_bounds.size());
363 clamped_content_size.SetToMax(widget->GetMinimumSize());
365 // A contentRect with zero width or height is a banned practice in ChromeMac,
366 // due to unpredictable OSX treatment.
367 DCHECK(!clamped_content_size.IsEmpty())
368 << "Zero-sized windows not supported on Mac";
370 if (!window_visible_ && native_widget_mac_->IsWindowModalSheet()) {
371 // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for
372 // the first time. They also have no frame, so just update the content size.
373 [window_ setContentSize:NSMakeSize(clamped_content_size.width(),
374 clamped_content_size.height())];
377 gfx::Rect actual_new_bounds(
379 GetWindowSizeForClientSize(window_, clamped_content_size));
381 if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_))
382 actual_new_bounds.Offset(parent_->GetChildWindowOffset());
384 [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
389 void BridgedNativeWidget::SetRootView(views::View* view) {
390 if (view == [bridged_view_ hostedView])
393 // If this is ever false, the compositor will need to be properly torn down
394 // and replaced, pointing at the new view.
395 DCHECK(!view || !compositor_widget_);
397 [bridged_view_ clearView];
398 bridged_view_.reset();
399 // Note that there can still be references to the old |bridged_view_|
400 // floating around in Cocoa libraries at this point. However, references to
401 // the old views::View will be gone, so any method calls will become no-ops.
404 bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
405 // Objective C initializers can return nil. However, if |view| is non-NULL
406 // this should be treated as an error and caught early.
407 CHECK(bridged_view_);
409 [window_ setContentView:bridged_view_];
412 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
414 // - A window with an invisible parent is not made visible.
415 // - A parent changing visibility updates child window visibility.
416 // * But only when changed via this function - ignore changes via the
417 // NSWindow API, or changes propagating out from here.
418 wants_to_be_visible_ = new_state != HIDE_WINDOW;
420 if (new_state == HIDE_WINDOW) {
421 [window_ orderOut:nil];
422 DCHECK(!window_visible_);
426 DCHECK(wants_to_be_visible_);
427 // If the parent (or an ancestor) is hidden, return and wait for it to become
429 if (parent() && !parent()->IsVisibleParent())
432 if (native_widget_mac_->IsWindowModalSheet()) {
433 NSWindow* parent_window = parent_->GetNSWindow();
434 DCHECK(parent_window);
436 [NSApp beginSheet:window_
437 modalForWindow:parent_window
438 modalDelegate:[window_ delegate]
439 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
440 contextInfo:nullptr];
444 if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
445 [window_ makeKeyAndOrderFront:nil];
446 [NSApp activateIgnoringOtherApps:YES];
448 // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a
449 // parent window. So, if there's a parent, order above that. Otherwise, this
450 // will order above all windows at the same level.
451 NSInteger parent_window_number = 0;
453 parent_window_number = [parent_->GetNSWindow() windowNumber];
455 [window_ orderWindow:NSWindowAbove
456 relativeTo:parent_window_number];
458 DCHECK(window_visible_);
460 // For non-sheet modal types, use the constrained window animations to make
461 // the window appear.
462 if (native_widget_mac_->GetWidget()->IsModal()) {
463 base::scoped_nsobject<NSAnimation> show_animation(
464 [[ModalShowAnimationWithLayer alloc] initWithWindow:window_]);
465 // The default mode is blocking, which would block the UI thread for the
466 // duration of the animation, but would keep it smooth. The window also
467 // hasn't yet received a frame from the compositor at this stage, so it is
468 // fully transparent until the GPU sends a frame swap IPC. For the blocking
469 // option, the animation needs to wait until AcceleratedWidgetSwapCompleted
470 // has been called at least once, otherwise it will animate nothing.
471 [show_animation setAnimationBlockingMode:NSAnimationNonblocking];
472 [show_animation startAnimation];
476 void BridgedNativeWidget::AcquireCapture() {
477 DCHECK(!HasCapture());
478 if (!window_visible_)
479 return; // Capture on hidden windows is disallowed.
481 mouse_capture_.reset(new CocoaMouseCapture(this));
483 // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
484 // will reset the mouse cursor to an arrow. Asking the window for an update
485 // here will restore what we want. However, it can sometimes cause the cursor
486 // to flicker, once, on the initial mouseDown.
487 // TOOD(tapted): Make this unnecessary by only asking for global mouse capture
488 // for the cases that need it (e.g. menus, but not drag and drop).
489 [window_ cursorUpdate:[NSApp currentEvent]];
492 void BridgedNativeWidget::ReleaseCapture() {
493 mouse_capture_.reset();
496 bool BridgedNativeWidget::HasCapture() {
497 return mouse_capture_ && mouse_capture_->IsActive();
500 void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
502 NSString* key = [NSString stringWithUTF8String:name];
504 [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
507 [GetWindowProperties() removeObjectForKey:key];
511 void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const {
512 NSString* key = [NSString stringWithUTF8String:name];
513 return [[GetWindowProperties() objectForKey:key] pointerValue];
516 void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
517 [window_delegate_ setCursor:cursor];
520 void BridgedNativeWidget::OnWindowWillClose() {
522 parent_->RemoveChildWindow(this);
525 [window_ setDelegate:nil];
526 [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
527 native_widget_mac_->OnWindowWillClose();
530 void BridgedNativeWidget::OnFullscreenTransitionStart(
531 bool target_fullscreen_state) {
532 // Note: This can fail for fullscreen changes started externally, but a user
533 // shouldn't be able to do that if the window is invisible to begin with.
534 DCHECK(window_visible_);
536 DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
537 target_fullscreen_state_ = target_fullscreen_state;
538 in_fullscreen_transition_ = true;
540 // If going into fullscreen, store an answer for GetRestoredBounds().
541 if (target_fullscreen_state)
542 bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
545 void BridgedNativeWidget::OnFullscreenTransitionComplete(
546 bool actual_fullscreen_state) {
547 in_fullscreen_transition_ = false;
549 if (target_fullscreen_state_ == actual_fullscreen_state) {
550 // Ensure constraints are re-applied when completing a transition.
551 OnSizeConstraintsChanged();
555 // First update to reflect reality so that OnTargetFullscreenStateChanged()
556 // expects the change.
557 target_fullscreen_state_ = actual_fullscreen_state;
558 ToggleDesiredFullscreenState();
560 // Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
561 // OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
562 // toggleFullScreen: request. This can occur when the fullscreen transition
563 // fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
564 // Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
565 if (in_fullscreen_transition_)
566 DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
569 void BridgedNativeWidget::ToggleDesiredFullscreenState() {
570 // If there is currently an animation into or out of fullscreen, then AppKit
571 // emits the string "not in fullscreen state" to stdio and does nothing. For
572 // this case, schedule a transition back into the desired state when the
573 // animation completes.
574 if (in_fullscreen_transition_) {
575 target_fullscreen_state_ = !target_fullscreen_state_;
579 // Going fullscreen implicitly makes the window visible. AppKit does this.
580 // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
581 // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
582 // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an
583 // orderWindow message. So intercepting the implicit change is hard.
584 // Luckily, to trigger externally, the window typically needs to be visible in
585 // the first place. So we can just ensure the window is visible here instead
586 // of relying on AppKit to do it, and not worry that OnVisibilityChanged()
587 // won't be called for externally triggered fullscreen requests.
588 if (!window_visible_)
589 SetVisibilityState(SHOW_INACTIVE);
591 if (base::mac::IsOSSnowLeopard()) {
593 return; // TODO(tapted): Implement this for Snow Leopard.
596 // Enable fullscreen collection behavior because:
597 // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
598 // 2: the fullscreen button must be enabled so the user can leave fullscreen.
599 // This will be reset when a transition out of fullscreen completes.
600 gfx::SetNSWindowCanFullscreen(window_, true);
602 [window_ toggleFullScreen:nil];
605 void BridgedNativeWidget::OnSizeChanged() {
606 gfx::Size new_size = GetClientAreaSize();
607 native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size);
609 UpdateLayerProperties();
612 void BridgedNativeWidget::OnVisibilityChanged() {
613 OnVisibilityChangedTo([window_ isVisible]);
616 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
617 if (window_visible_ == new_visibility)
620 window_visible_ = new_visibility;
622 // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
623 // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
624 // to distinguish being *hidden* externally from being hidden by a parent
625 // window - we might not need that.
626 if (window_visible_) {
627 wants_to_be_visible_ = true;
630 [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
632 mouse_capture_.reset(); // Capture on hidden windows is not permitted.
634 // When becoming invisible, remove the entry in any parent's childWindow
635 // list. Cocoa's childWindow management breaks down when child windows are
638 [parent_->GetNSWindow() removeChildWindow:window_];
641 // TODO(tapted): Investigate whether we want this for Mac. This is what Aura
642 // does, and it is what tests expect. However, because layer drawing is
643 // asynchronous (and things like deminiaturize in AppKit are not), it can
644 // result in a CALayer appearing on screen before it has been redrawn in the
645 // GPU process. This is a general problem. In content, a helper class,
646 // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:]
647 // and RenderWidgetHostView::Show() until a frame is ready.
649 layer()->SetVisible(window_visible_);
650 layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
653 NotifyVisibilityChangeDown();
655 native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
658 // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking
659 // for an "empty" draw, disable auto-display while hidden. For example, this
660 // prevents Cocoa drawing just *after* a minimize, resulting in a blank window
661 // represented in the deminiaturize animation.
662 [window_ setAutodisplay:window_visible_];
665 void BridgedNativeWidget::OnBackingPropertiesChanged() {
667 UpdateLayerProperties();
670 void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
671 Widget* widget = native_widget_mac()->GetWidget();
672 widget->OnNativeWidgetActivationChanged(is_key);
673 // The contentView is the BridgedContentView hosting the views::RootView. The
674 // focus manager will already know if a native subview has focus.
675 if ([window_ contentView] == [window_ firstResponder]) {
677 widget->OnNativeFocus();
678 widget->GetFocusManager()->RestoreFocusedView();
680 widget->OnNativeBlur();
681 widget->GetFocusManager()->StoreFocusedView(true);
686 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
687 NSPoint location_in_window) {
691 if ([bridged_view_ mouseDownCanMoveWindow]) {
692 // This is a re-post, the movement has already started, so we can make the
693 // window non-draggable again.
698 gfx::Point point(location_in_window.x,
699 NSHeight([window_ frame]) - location_in_window.y);
700 bool should_move_window =
701 native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
704 // Check that the point is not obscured by non-content NSViews.
705 for (NSView* subview : [[bridged_view_ superview] subviews]) {
706 if (subview == bridged_view_.get())
709 if (![subview mouseDownCanMoveWindow] &&
710 NSPointInRect(location_in_window, [subview frame])) {
711 should_move_window = false;
716 if (!should_move_window)
719 // Make the window draggable, then return true to repost the event.
724 void BridgedNativeWidget::OnSizeConstraintsChanged() {
725 // Don't modify the size constraints or fullscreen collection behavior while
726 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
727 // reset these after leaving fullscreen.
728 if (target_fullscreen_state_ || in_fullscreen_transition_)
731 Widget* widget = native_widget_mac()->GetWidget();
732 gfx::Size min_size = widget->GetMinimumSize();
733 gfx::Size max_size = widget->GetMaximumSize();
734 bool is_resizable = widget->widget_delegate()->CanResize();
735 bool shows_resize_controls =
736 is_resizable && (min_size.IsEmpty() || min_size != max_size);
737 bool shows_fullscreen_controls =
738 is_resizable && widget->widget_delegate()->CanMaximize();
740 gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size,
741 shows_resize_controls,
742 shows_fullscreen_controls);
745 ui::InputMethod* BridgedNativeWidget::GetInputMethod() {
746 if (!input_method_) {
747 input_method_ = ui::CreateInputMethod(this, nil);
748 // For now, use always-focused mode on Mac for the input method.
749 // TODO(tapted): Move this to OnWindowKeyStatusChangedTo() and balance.
750 input_method_->OnFocus();
752 return input_method_.get();
755 gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
756 if (target_fullscreen_state_ || in_fullscreen_transition_)
757 return bounds_before_fullscreen_;
759 return gfx::ScreenRectFromNSRect([window_ frame]);
762 void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
764 DCHECK(bridged_view_);
770 SetLayer(new ui::Layer(layer_type));
771 // Note, except for controls, this will set the layer to be hidden, since it
772 // is only called during Init().
773 layer()->SetVisible(window_visible_);
774 layer()->set_delegate(this);
778 // Transparent window support.
779 layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
780 layer()->SetFillsBoundsOpaquely(!translucent);
782 [window_ setOpaque:NO];
783 [window_ setBackgroundColor:[NSColor clearColor]];
786 UpdateLayerProperties();
789 ////////////////////////////////////////////////////////////////////////////////
790 // BridgedNativeWidget, internal::InputMethodDelegate:
792 ui::EventDispatchDetails BridgedNativeWidget::DispatchKeyEventPostIME(
794 DCHECK(focus_manager_);
795 native_widget_mac_->GetWidget()->OnKeyEvent(key);
796 if (!key->handled()) {
797 if (!focus_manager_->OnKeyEvent(*key))
798 key->StopPropagation();
800 return ui::EventDispatchDetails();
803 ////////////////////////////////////////////////////////////////////////////////
804 // BridgedNativeWidget, CocoaMouseCaptureDelegate:
806 void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) {
807 [bridged_view_ processCapturedMouseEvent:event];
810 void BridgedNativeWidget::OnMouseCaptureLost() {
811 native_widget_mac_->GetWidget()->OnMouseCaptureLost();
814 ////////////////////////////////////////////////////////////////////////////////
815 // BridgedNativeWidget, FocusChangeListener:
817 void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
821 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
823 ui::InputMethod* input_method =
824 native_widget_mac_->GetWidget()->GetInputMethod();
826 ui::TextInputClient* input_client = input_method->GetTextInputClient();
827 [bridged_view_ setTextInputClient:input_client];
831 ////////////////////////////////////////////////////////////////////////////////
832 // BridgedNativeWidget, LayerDelegate:
834 void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) {
835 DCHECK(window_visible_);
836 native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context);
839 void BridgedNativeWidget::OnDelegatedFrameDamage(
840 const gfx::Rect& damage_rect_in_dip) {
844 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
845 float device_scale_factor) {
849 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
851 return base::Closure();
854 ////////////////////////////////////////////////////////////////////////////////
855 // BridgedNativeWidget, AcceleratedWidgetMac:
857 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
858 return compositor_superview_;
861 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
865 void BridgedNativeWidget::AcceleratedWidgetSwapCompleted(
866 const std::vector<ui::LatencyInfo>& latency_info) {
867 // Ignore frames arriving "late" for an old size. A frame at the new size
868 // should arrive soon.
869 if (!compositor_widget_->HasFrameOfSize(GetClientAreaSize()))
872 if (invalidate_shadow_on_frame_swap_) {
873 invalidate_shadow_on_frame_swap_ = false;
874 [window_ invalidateShadow];
878 void BridgedNativeWidget::AcceleratedWidgetHitError() {
879 compositor_->ScheduleFullRedraw();
882 ////////////////////////////////////////////////////////////////////////////////
883 // BridgedNativeWidget, BridgedNativeWidgetOwner:
885 NSWindow* BridgedNativeWidget::GetNSWindow() {
889 gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const {
890 return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin();
893 bool BridgedNativeWidget::IsVisibleParent() const {
894 return parent_ ? window_visible_ && parent_->IsVisibleParent()
898 void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
899 auto location = std::find(
900 child_windows_.begin(), child_windows_.end(), child);
901 DCHECK(location != child_windows_.end());
902 child_windows_.erase(location);
904 // Note the child is sometimes removed already by AppKit. This depends on OS
905 // version, and possibly some unpredictable reference counting. Removing it
906 // here should be safe regardless.
907 [window_ removeChildWindow:child->window_];
910 ////////////////////////////////////////////////////////////////////////////////
911 // BridgedNativeWidget, private:
913 void BridgedNativeWidget::RemoveOrDestroyChildren() {
914 // TODO(tapted): Implement unowned child windows if required.
915 while (!child_windows_.empty()) {
916 // The NSWindow can only be destroyed after -[NSWindow close] is complete.
917 // Retain the window, otherwise the reference count can reach zero when the
918 // child calls back into RemoveChildWindow() via its OnWindowWillClose().
919 base::scoped_nsobject<NSWindow> child(
920 [child_windows_.back()->ns_window() retain]);
925 void BridgedNativeWidget::NotifyVisibilityChangeDown() {
926 // Child windows sometimes like to close themselves in response to visibility
927 // changes. That's supported, but only with the asynchronous Widget::Close().
928 // Perform a heuristic to detect child removal that would break these loops.
929 const size_t child_count = child_windows_.size();
930 if (!window_visible_) {
931 for (BridgedNativeWidget* child : child_windows_) {
932 if (child->window_visible_)
933 [child->ns_window() orderOut:nil];
935 DCHECK(!child->window_visible_);
936 CHECK_EQ(child_count, child_windows_.size());
938 // The orderOut calls above should result in a call to OnVisibilityChanged()
939 // in each child. There, children will remove themselves from the NSWindow
940 // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
941 // to any children of their own.
942 DCHECK_EQ(0u, [[window_ childWindows] count]);
946 NSUInteger visible_children = 0; // For a DCHECK below.
947 NSInteger parent_window_number = [window_ windowNumber];
948 for (BridgedNativeWidget* child: child_windows_) {
949 // Note: order the child windows on top, regardless of whether or not they
950 // are currently visible. They probably aren't, since the parent was hidden
951 // prior to this, but they could have been made visible in other ways.
952 if (child->wants_to_be_visible_) {
954 // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
955 // screen. However, that by itself is insufficient to guarantee a correct
956 // z-order relationship. If this function is being called from a z-order
957 // change in the parent, orderWindow turns out to be unreliable (i.e. the
958 // ordering doesn't always take effect). What this actually relies on is
959 // the resulting call to OnVisibilityChanged() in the child, which will
960 // then insert itself into -[NSWindow childWindows] to let Cocoa do its
961 // internal layering magic.
962 [child->ns_window() orderWindow:NSWindowAbove
963 relativeTo:parent_window_number];
964 DCHECK(child->window_visible_);
966 CHECK_EQ(child_count, child_windows_.size());
968 DCHECK_EQ(visible_children, [[window_ childWindows] count]);
971 gfx::Size BridgedNativeWidget::GetClientAreaSize() const {
972 NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]];
973 return gfx::Size(NSWidth(content_rect), NSHeight(content_rect));
976 void BridgedNativeWidget::CreateCompositor() {
977 DCHECK(!compositor_);
978 DCHECK(!compositor_widget_);
979 DCHECK(ViewsDelegate::GetInstance());
981 ui::ContextFactory* context_factory =
982 ViewsDelegate::GetInstance()->GetContextFactory();
983 DCHECK(context_factory);
985 AddCompositorSuperview();
987 // TODO(tapted): Get this value from GpuDataManagerImpl via ViewsDelegate.
988 bool needs_gl_finish_workaround = false;
990 compositor_widget_.reset(
991 new ui::AcceleratedWidgetMac(needs_gl_finish_workaround));
992 compositor_.reset(new ui::Compositor(compositor_widget_->accelerated_widget(),
994 base::ThreadTaskRunnerHandle::Get()));
995 compositor_widget_->SetNSView(this);
998 void BridgedNativeWidget::InitCompositor() {
1000 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
1001 gfx::Size size_in_dip = GetClientAreaSize();
1002 compositor_->SetScaleAndSize(scale_factor,
1003 ConvertSizeToPixel(scale_factor, size_in_dip));
1004 compositor_->SetRootLayer(layer());
1007 void BridgedNativeWidget::DestroyCompositor() {
1009 // LayerOwner supports a change in ownership, e.g., to animate a closing
1010 // window, but that won't work as expected for the root layer in
1011 // BridgedNativeWidget.
1012 DCHECK_EQ(this, layer()->owner());
1013 layer()->CompleteAllAnimations();
1014 layer()->SuppressPaint();
1015 layer()->set_delegate(nullptr);
1019 if (!compositor_widget_) {
1020 DCHECK(!compositor_);
1023 compositor_widget_->ResetNSView();
1024 compositor_.reset();
1025 compositor_widget_.reset();
1028 void BridgedNativeWidget::AddCompositorSuperview() {
1029 DCHECK(!compositor_superview_);
1030 compositor_superview_.reset(
1031 [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
1033 // Size and resize automatically with |bridged_view_|.
1034 [compositor_superview_
1035 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
1037 // Enable HiDPI backing when supported (only on 10.7+).
1038 if ([compositor_superview_ respondsToSelector:
1039 @selector(setWantsBestResolutionOpenGLSurface:)]) {
1040 [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES];
1043 base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
1045 setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
1047 // Set the layer first to create a layer-hosting view (not layer-backed).
1048 [compositor_superview_ setLayer:background_layer];
1049 [compositor_superview_ setWantsLayer:YES];
1051 // The UI compositor should always be the first subview, to ensure webviews
1052 // are drawn on top of it.
1053 DCHECK_EQ(0u, [[bridged_view_ subviews] count]);
1054 [bridged_view_ addSubview:compositor_superview_];
1057 void BridgedNativeWidget::UpdateLayerProperties() {
1059 DCHECK(compositor_superview_);
1060 gfx::Size size_in_dip = GetClientAreaSize();
1061 layer()->SetBounds(gfx::Rect(size_in_dip));
1063 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
1064 compositor_->SetScaleAndSize(scale_factor,
1065 ConvertSizeToPixel(scale_factor, size_in_dip));
1067 // For a translucent window, the shadow calculation needs to be carried out
1068 // after the frame from the compositor arrives.
1069 if (![window_ isOpaque])
1070 invalidate_shadow_on_frame_swap_ = true;
1073 NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
1074 NSMutableDictionary* properties = objc_getAssociatedObject(
1075 window_, &kWindowPropertiesKey);
1077 properties = [NSMutableDictionary dictionary];
1078 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
1079 properties, OBJC_ASSOCIATION_RETAIN);
1084 void BridgedNativeWidget::SetDraggable(bool draggable) {
1085 [bridged_view_ setMouseDownCanMoveWindow:draggable];
1086 // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1087 // changes. Previously we tried adding an NSView and removing it, but for some
1088 // reason it required reposting the mouse-down event, and didn't always work.
1089 // Calling the below seems to be an effective solution.
1090 [window_ setMovableByWindowBackground:NO];
1091 [window_ setMovableByWindowBackground:YES];
1094 } // namespace views