Automated Commit: Committing new LKGM version 7479.0.0 for chromeos.
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget.mm
blob7031b15e385c3eddabeeb5166054084c068cab68
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 extern "C" {
37 typedef int32_t CGSConnection;
38 CGSConnection _CGSDefaultConnection();
39 CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection,
40                                          NSInteger windowNumber,
41                                          int radius);
45 // The NSView that hosts the composited CALayer drawing the UI. It fills the
46 // window but is not hittable so that accessibility hit tests always go to the
47 // BridgedContentView.
48 @interface ViewsCompositorSuperview : NSView
49 @end
51 @implementation ViewsCompositorSuperview
52 - (NSView*)hitTest:(NSPoint)aPoint {
53   return nil;
55 @end
57 // This class overrides NSAnimation methods to invalidate the shadow for each
58 // frame. It is required because the show animation uses CGSSetWindowWarp()
59 // which is touchy about the consistency of the points it is given. The show
60 // animation includes a translate, which fails to apply properly to the window
61 // shadow, when that shadow is derived from a layer-hosting view. So invalidate
62 // it. This invalidation is only needed to cater for the translate. It is not
63 // required if CGSSetWindowWarp() is used in a way that keeps the center point
64 // of the window stationary (e.g. a scale). It's also not required for the hide
65 // animation: in that case, the shadow is never invalidated so retains the
66 // shadow calculated before a translate is applied.
67 @interface ModalShowAnimationWithLayer : ConstrainedWindowAnimationShow
68 @end
70 @implementation ModalShowAnimationWithLayer
71 - (void)stopAnimation {
72   [super stopAnimation];
73   [window_ invalidateShadow];
75 - (void)setCurrentProgress:(NSAnimationProgress)progress {
76   [super setCurrentProgress:progress];
77   [window_ invalidateShadow];
79 @end
81 namespace {
83 const CGFloat kMavericksMenuOpacity = 251.0 / 255.0;
84 const CGFloat kYosemiteMenuOpacity = 194.0 / 255.0;
85 const int kYosemiteMenuBlur = 80;
87 int kWindowPropertiesKey;
89 float GetDeviceScaleFactorFromView(NSView* view) {
90   gfx::Display display =
91       gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
92   DCHECK(display.is_valid());
93   return display.device_scale_factor();
96 // Returns true if bounds passed to window in SetBounds should be treated as
97 // though they are in screen coordinates.
98 bool PositionWindowInScreenCoordinates(views::Widget* widget,
99                                        views::Widget::InitParams::Type type) {
100   // Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
101   if (views::GetAuraWindowTypeForWidgetType(type) == ui::wm::WINDOW_TYPE_POPUP)
102     return true;
104   return widget && widget->is_top_level();
107 // Return the content size for a minimum or maximum widget size.
108 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
109                                      const gfx::Size& window_size) {
110   NSRect frame_rect =
111       NSMakeRect(0, 0, window_size.width(), window_size.height());
112   // Note gfx::Size will prevent dimensions going negative. They are allowed to
113   // be zero at this point, because Widget::GetMinimumSize() may later increase
114   // the size.
115   return gfx::Size([window contentRectForFrameRect:frame_rect].size);
118 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
119   id delegate = [[ns_event window] delegate];
120   return
121       [delegate
122           respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
123       [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
126 // Check if a mouse-down event should drag the window. If so, repost the event.
127 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
128   enum RepostState {
129     // Nothing reposted: hit-test new mouse-downs to see if they need to be
130     // ignored and reposted after changing draggability.
131     NONE,
132     // Expecting the next event to be the reposted event: let it go through.
133     EXPECTING_REPOST,
134     // If, while reposting, another mousedown was received: when the reposted
135     // event is seen, ignore it.
136     REPOST_CANCELLED,
137   };
139   // Which repost we're expecting to receive.
140   static RepostState repost_state = NONE;
141   // The event number of the reposted event. This let's us track whether an
142   // event is actually the repost since user-generated events have increasing
143   // event numbers. This is only valid while |repost_state != NONE|.
144   static NSInteger reposted_event_number;
146   NSInteger event_number = [ns_event eventNumber];
148   // The logic here is a bit convoluted because we want to mitigate race
149   // conditions if somehow a different mouse-down occurs between reposts.
150   // Specifically, we want to avoid:
151   // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is
152   //   draggable outside of a repost cycle),
153   // - any repost loop.
155   if (repost_state == NONE) {
156     if (WindowWantsMouseDownReposted(ns_event)) {
157       repost_state = EXPECTING_REPOST;
158       reposted_event_number = event_number;
159       CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
160       return nil;
161     }
163     return ns_event;
164   }
166   if (repost_state == EXPECTING_REPOST) {
167     // Call through so that the window is made non-draggable again.
168     WindowWantsMouseDownReposted(ns_event);
170     if (reposted_event_number == event_number) {
171       // Reposted event received.
172       repost_state = NONE;
173       return nil;
174     }
176     // We were expecting a repost, but since this is a new mouse-down, cancel
177     // reposting and allow event to continue as usual.
178     repost_state = REPOST_CANCELLED;
179     return ns_event;
180   }
182   DCHECK_EQ(REPOST_CANCELLED, repost_state);
183   if (reposted_event_number == event_number) {
184     // Reposting was cancelled, now that we've received the event, we don't
185     // expect to see it again.
186     repost_state = NONE;
187     return nil;
188   }
190   return ns_event;
193 // Support window caption/draggable regions.
194 // In AppKit, non-client regions are set by overriding
195 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
196 // installed and performs window moving when mouse-downs land in the area.
197 // In Views, non-client regions are determined via hit-tests when the event
198 // occurs.
199 // To bridge the two models, we monitor mouse-downs with
200 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
201 // events after window dragging is handled, so for mouse-downs that land on a
202 // draggable point, we cancel the event and repost it at the CGSessionEventTap
203 // level so that window dragging will be handled again.
204 void SetupDragEventMonitor() {
205   static id monitor = nil;
206   if (monitor)
207     return;
209   monitor = [NSEvent
210       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
211       handler:^NSEvent*(NSEvent* ns_event) {
212         return RepostEventIfHandledByWindow(ns_event);
213       }];
216 }  // namespace
218 namespace views {
220 // static
221 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
222     NSWindow* window,
223     const gfx::Size& content_size) {
224   NSRect content_rect =
225       NSMakeRect(0, 0, content_size.width(), content_size.height());
226   NSRect frame_rect = [window frameRectForContentRect:content_rect];
227   return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
230 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
231     : native_widget_mac_(parent),
232       focus_manager_(nullptr),
233       widget_type_(Widget::InitParams::TYPE_WINDOW),  // Updated in Init().
234       parent_(nullptr),
235       target_fullscreen_state_(false),
236       in_fullscreen_transition_(false),
237       window_visible_(false),
238       wants_to_be_visible_(false) {
239   SetupDragEventMonitor();
240   DCHECK(parent);
241   window_delegate_.reset(
242       [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
245 BridgedNativeWidget::~BridgedNativeWidget() {
246   RemoveOrDestroyChildren();
247   DCHECK(child_windows_.empty());
248   SetFocusManager(NULL);
249   SetRootView(NULL);
250   DestroyCompositor();
251   if ([window_ delegate]) {
252     // If the delegate is still set on a modal dialog, it means it was not
253     // closed via [NSApplication endSheet:]. This is probably OK if the widget
254     // was never shown. But Cocoa ignores close() calls on open sheets. Calling
255     // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
256     // DialogClientView assumes its delegate is alive when closing, which isn't
257     // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
258     // So ban it. Modal dialogs should be closed via Widget::Close().
259     DCHECK(!native_widget_mac_->IsWindowModalSheet());
261     // If the delegate is still set, it means OnWindowWillClose has not been
262     // called and the window is still open. Calling -[NSWindow close] will
263     // synchronously call OnWindowWillClose and notify NativeWidgetMac.
264     [window_ close];
265   }
266   DCHECK(![window_ delegate]);
269 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
270                                const Widget::InitParams& params) {
271   widget_type_ = params.type;
273   DCHECK(!window_);
274   window_.swap(window);
275   [window_ setDelegate:window_delegate_];
277   // Register for application hide notifications so that visibility can be
278   // properly tracked. This is not done in the delegate so that the lifetime is
279   // tied to the C++ object, rather than the delegate (which may be reference
280   // counted). This is required since the application hides do not send an
281   // orderOut: to individual windows. Unhide, however, does send an order
282   // message.
283   [[NSNotificationCenter defaultCenter]
284       addObserver:window_delegate_
285          selector:@selector(onWindowOrderChanged:)
286              name:NSApplicationDidHideNotification
287            object:nil];
289   // Validate the window's initial state, otherwise the bridge's initial
290   // tracking state will be incorrect.
291   DCHECK(![window_ isVisible]);
292   DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
294   if (params.parent) {
295     // Disallow creating child windows of views not currently in an NSWindow.
296     CHECK([params.parent window]);
297     BridgedNativeWidget* bridged_native_widget_parent =
298         NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
299     // If the parent is another BridgedNativeWidget, just add to the collection
300     // of child windows it owns and manages. Otherwise, create an adapter to
301     // anchor the child widget and observe when the parent NSWindow is closed.
302     if (bridged_native_widget_parent) {
303       parent_ = bridged_native_widget_parent;
304       bridged_native_widget_parent->child_windows_.push_back(this);
305     } else {
306       parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
307     }
308   }
310   // OSX likes to put shadows on most things. However, frameless windows (with
311   // styleMask = NSBorderlessWindowMask) default to no shadow. So change that.
312   // SHADOW_TYPE_DROP is used for Menus, which get the same shadow style on Mac.
313   switch (params.shadow_type) {
314     case Widget::InitParams::SHADOW_TYPE_NONE:
315       [window_ setHasShadow:NO];
316       break;
317     case Widget::InitParams::SHADOW_TYPE_DEFAULT:
318     case Widget::InitParams::SHADOW_TYPE_DROP:
319       [window_ setHasShadow:YES];
320       break;
321   }  // No default case, to pick up new types.
323   // Set a meaningful initial bounds. Note that except for frameless widgets
324   // with no WidgetDelegate, the bounds will be set again by Widget after
325   // initializing the non-client view. In the former case, if bounds were not
326   // set at all, the creator of the Widget is expected to call SetBounds()
327   // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
328   // (i.e. 1x1) window appearing.
329   if (!params.bounds.IsEmpty()) {
330     SetBounds(params.bounds);
331   } else {
332     // If a position is set, but no size, complain. Otherwise, a 1x1 window
333     // would appear there, which might be unexpected.
334     DCHECK(params.bounds.origin().IsOrigin())
335         << "Zero-sized windows not supported on Mac.";
337     // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
338     // the bottom left of the screen. To support a client calling SetSize() only
339     // (and for consistency across platforms) put it at the top-left instead.
340     // Read back the current frame: it will be a 1x1 context rect but the frame
341     // size also depends on the window style.
342     NSRect frame_rect = [window_ frame];
343     SetBounds(gfx::Rect(gfx::Point(),
344                         gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))));
345   }
347   // Widgets for UI controls (usually layered above web contents) start visible.
348   if (params.type == Widget::InitParams::TYPE_CONTROL)
349     SetVisibilityState(SHOW_INACTIVE);
351   // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are
352   // native on Mac, so nothing should ever want one in Widget form.
353   DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP);
354   tooltip_manager_.reset(new TooltipManagerMac(this));
357 void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
358   if (focus_manager_ == focus_manager)
359     return;
361   if (focus_manager_)
362     focus_manager_->RemoveFocusChangeListener(this);
364   if (focus_manager)
365     focus_manager->AddFocusChangeListener(this);
367   focus_manager_ = focus_manager;
370 void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
371   Widget* widget = native_widget_mac_->GetWidget();
372   // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated
373   // resizes. This is not what toolkit-views expects, so clamp. Note there is
374   // no check for maximum size (consistent with aura::Window::SetBounds()).
375   gfx::Size clamped_content_size =
376       GetClientSizeForWindowSize(window_, new_bounds.size());
377   clamped_content_size.SetToMax(widget->GetMinimumSize());
379   // A contentRect with zero width or height is a banned practice in ChromeMac,
380   // due to unpredictable OSX treatment.
381   DCHECK(!clamped_content_size.IsEmpty())
382       << "Zero-sized windows not supported on Mac";
384   if (!window_visible_ && native_widget_mac_->IsWindowModalSheet()) {
385     // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for
386     // the first time. They also have no frame, so just update the content size.
387     [window_ setContentSize:NSMakeSize(clamped_content_size.width(),
388                                        clamped_content_size.height())];
389     return;
390   }
391   gfx::Rect actual_new_bounds(
392       new_bounds.origin(),
393       GetWindowSizeForClientSize(window_, clamped_content_size));
395   if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_))
396     actual_new_bounds.Offset(parent_->GetChildWindowOffset());
398   [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
399             display:YES
400             animate:NO];
403 void BridgedNativeWidget::SetRootView(views::View* view) {
404   if (view == [bridged_view_ hostedView])
405     return;
407   // If this is ever false, the compositor will need to be properly torn down
408   // and replaced, pointing at the new view.
409   DCHECK(!view || !compositor_widget_);
411   [bridged_view_ clearView];
412   bridged_view_.reset();
413   // Note that there can still be references to the old |bridged_view_|
414   // floating around in Cocoa libraries at this point. However, references to
415   // the old views::View will be gone, so any method calls will become no-ops.
417   if (view) {
418     bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
419     // Objective C initializers can return nil. However, if |view| is non-NULL
420     // this should be treated as an error and caught early.
421     CHECK(bridged_view_);
422   }
423   [window_ setContentView:bridged_view_];
426 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
427   // Ensure that:
428   //  - A window with an invisible parent is not made visible.
429   //  - A parent changing visibility updates child window visibility.
430   //    * But only when changed via this function - ignore changes via the
431   //      NSWindow API, or changes propagating out from here.
432   wants_to_be_visible_ = new_state != HIDE_WINDOW;
434   if (new_state == HIDE_WINDOW) {
435     [window_ orderOut:nil];
436     DCHECK(!window_visible_);
437     return;
438   }
440   DCHECK(wants_to_be_visible_);
441   // If the parent (or an ancestor) is hidden, return and wait for it to become
442   // visible.
443   if (parent() && !parent()->IsVisibleParent())
444     return;
446   if (native_widget_mac_->IsWindowModalSheet()) {
447     NSWindow* parent_window = parent_->GetNSWindow();
448     DCHECK(parent_window);
450     [NSApp beginSheet:window_
451         modalForWindow:parent_window
452          modalDelegate:[window_ delegate]
453         didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
454            contextInfo:nullptr];
455     return;
456   }
458   if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
459     [window_ makeKeyAndOrderFront:nil];
460     [NSApp activateIgnoringOtherApps:YES];
461   } else {
462     // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a
463     // parent window. So, if there's a parent, order above that. Otherwise, this
464     // will order above all windows at the same level.
465     NSInteger parent_window_number = 0;
466     if (parent_)
467       parent_window_number = [parent_->GetNSWindow() windowNumber];
469     [window_ orderWindow:NSWindowAbove
470               relativeTo:parent_window_number];
471   }
472   DCHECK(window_visible_);
474   // For non-sheet modal types, use the constrained window animations to make
475   // the window appear.
476   if (native_widget_mac_->GetWidget()->IsModal()) {
477     base::scoped_nsobject<NSAnimation> show_animation(
478         [[ModalShowAnimationWithLayer alloc] initWithWindow:window_]);
479     // The default mode is blocking, which would block the UI thread for the
480     // duration of the animation, but would keep it smooth. The window also
481     // hasn't yet received a frame from the compositor at this stage, so it is
482     // fully transparent until the GPU sends a frame swap IPC. For the blocking
483     // option, the animation needs to wait until AcceleratedWidgetSwapCompleted
484     // has been called at least once, otherwise it will animate nothing.
485     [show_animation setAnimationBlockingMode:NSAnimationNonblocking];
486     [show_animation startAnimation];
487   }
490 void BridgedNativeWidget::AcquireCapture() {
491   DCHECK(!HasCapture());
492   if (!window_visible_)
493     return;  // Capture on hidden windows is disallowed.
495   mouse_capture_.reset(new CocoaMouseCapture(this));
497   // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
498   // will reset the mouse cursor to an arrow. Asking the window for an update
499   // here will restore what we want. However, it can sometimes cause the cursor
500   // to flicker, once, on the initial mouseDown.
501   // TOOD(tapted): Make this unnecessary by only asking for global mouse capture
502   // for the cases that need it (e.g. menus, but not drag and drop).
503   [window_ cursorUpdate:[NSApp currentEvent]];
506 void BridgedNativeWidget::ReleaseCapture() {
507   mouse_capture_.reset();
510 bool BridgedNativeWidget::HasCapture() {
511   return mouse_capture_ && mouse_capture_->IsActive();
514 void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
515                                                   void* value) {
516   NSString* key = [NSString stringWithUTF8String:name];
517   if (value) {
518     [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
519                               forKey:key];
520   } else {
521     [GetWindowProperties() removeObjectForKey:key];
522   }
525 void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const {
526   NSString* key = [NSString stringWithUTF8String:name];
527   return [[GetWindowProperties() objectForKey:key] pointerValue];
530 void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
531   [window_delegate_ setCursor:cursor];
534 void BridgedNativeWidget::OnWindowWillClose() {
535   if (parent_) {
536     parent_->RemoveChildWindow(this);
537     parent_ = nullptr;
538   }
539   [window_ setDelegate:nil];
540   [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
541   native_widget_mac_->OnWindowWillClose();
544 void BridgedNativeWidget::OnFullscreenTransitionStart(
545     bool target_fullscreen_state) {
546   // Note: This can fail for fullscreen changes started externally, but a user
547   // shouldn't be able to do that if the window is invisible to begin with.
548   DCHECK(window_visible_);
550   DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
551   target_fullscreen_state_ = target_fullscreen_state;
552   in_fullscreen_transition_ = true;
554   // If going into fullscreen, store an answer for GetRestoredBounds().
555   if (target_fullscreen_state)
556     bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
559 void BridgedNativeWidget::OnFullscreenTransitionComplete(
560     bool actual_fullscreen_state) {
561   in_fullscreen_transition_ = false;
563   if (target_fullscreen_state_ == actual_fullscreen_state) {
564     // Ensure constraints are re-applied when completing a transition.
565     OnSizeConstraintsChanged();
566     return;
567   }
569   // First update to reflect reality so that OnTargetFullscreenStateChanged()
570   // expects the change.
571   target_fullscreen_state_ = actual_fullscreen_state;
572   ToggleDesiredFullscreenState();
574   // Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
575   // OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
576   // toggleFullScreen: request. This can occur when the fullscreen transition
577   // fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
578   // Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
579   if (in_fullscreen_transition_)
580     DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
583 void BridgedNativeWidget::ToggleDesiredFullscreenState() {
584   // If there is currently an animation into or out of fullscreen, then AppKit
585   // emits the string "not in fullscreen state" to stdio and does nothing. For
586   // this case, schedule a transition back into the desired state when the
587   // animation completes.
588   if (in_fullscreen_transition_) {
589     target_fullscreen_state_ = !target_fullscreen_state_;
590     return;
591   }
593   // Going fullscreen implicitly makes the window visible. AppKit does this.
594   // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
595   // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
596   // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an
597   // orderWindow message. So intercepting the implicit change is hard.
598   // Luckily, to trigger externally, the window typically needs to be visible in
599   // the first place. So we can just ensure the window is visible here instead
600   // of relying on AppKit to do it, and not worry that OnVisibilityChanged()
601   // won't be called for externally triggered fullscreen requests.
602   if (!window_visible_)
603     SetVisibilityState(SHOW_INACTIVE);
605   if (base::mac::IsOSSnowLeopard()) {
606     NOTIMPLEMENTED();
607     return;  // TODO(tapted): Implement this for Snow Leopard.
608   }
610   // Enable fullscreen collection behavior because:
611   // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
612   // 2: the fullscreen button must be enabled so the user can leave fullscreen.
613   // This will be reset when a transition out of fullscreen completes.
614   gfx::SetNSWindowCanFullscreen(window_, true);
616   [window_ toggleFullScreen:nil];
619 void BridgedNativeWidget::OnSizeChanged() {
620   gfx::Size new_size = GetClientAreaSize();
621   native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size);
622   if (layer())
623     UpdateLayerProperties();
626 void BridgedNativeWidget::OnVisibilityChanged() {
627   OnVisibilityChangedTo([window_ isVisible]);
630 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
631   if (window_visible_ == new_visibility)
632     return;
634   window_visible_ = new_visibility;
636   // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
637   // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
638   // to distinguish being *hidden* externally from being hidden by a parent
639   // window - we might not need that.
640   if (window_visible_) {
641     wants_to_be_visible_ = true;
643     if (parent_)
644       [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
645   } else {
646     mouse_capture_.reset();  // Capture on hidden windows is not permitted.
648     // When becoming invisible, remove the entry in any parent's childWindow
649     // list. Cocoa's childWindow management breaks down when child windows are
650     // hidden.
651     if (parent_)
652       [parent_->GetNSWindow() removeChildWindow:window_];
653   }
655   // TODO(tapted): Investigate whether we want this for Mac. This is what Aura
656   // does, and it is what tests expect. However, because layer drawing is
657   // asynchronous (and things like deminiaturize in AppKit are not), it can
658   // result in a CALayer appearing on screen before it has been redrawn in the
659   // GPU process. This is a general problem. In content, a helper class,
660   // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:]
661   // and RenderWidgetHostView::Show() until a frame is ready.
662   if (layer()) {
663     layer()->SetVisible(window_visible_);
664     layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
665   }
667   NotifyVisibilityChangeDown();
669   native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
670       window_visible_);
672   // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking
673   // for an "empty" draw, disable auto-display while hidden. For example, this
674   // prevents Cocoa drawing just *after* a minimize, resulting in a blank window
675   // represented in the deminiaturize animation.
676   [window_ setAutodisplay:window_visible_];
679 void BridgedNativeWidget::OnBackingPropertiesChanged() {
680   if (layer())
681     UpdateLayerProperties();
684 void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
685   Widget* widget = native_widget_mac()->GetWidget();
686   widget->OnNativeWidgetActivationChanged(is_key);
687   // The contentView is the BridgedContentView hosting the views::RootView. The
688   // focus manager will already know if a native subview has focus.
689   if ([window_ contentView] == [window_ firstResponder]) {
690     if (is_key) {
691       widget->OnNativeFocus();
692       widget->GetFocusManager()->RestoreFocusedView();
693     } else {
694       widget->OnNativeBlur();
695       widget->GetFocusManager()->StoreFocusedView(true);
696     }
697   }
700 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
701     NSPoint location_in_window) {
702   if (!bridged_view_)
703     return false;
705   if ([bridged_view_ mouseDownCanMoveWindow]) {
706     // This is a re-post, the movement has already started, so we can make the
707     // window non-draggable again.
708     SetDraggable(false);
709     return false;
710   }
712   gfx::Point point(location_in_window.x,
713                    NSHeight([window_ frame]) - location_in_window.y);
714   bool should_move_window =
715       native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
716       HTCAPTION;
718   // Check that the point is not obscured by non-content NSViews.
719   for (NSView* subview : [[bridged_view_ superview] subviews]) {
720     if (subview == bridged_view_.get())
721       continue;
723     if (![subview mouseDownCanMoveWindow] &&
724         NSPointInRect(location_in_window, [subview frame])) {
725       should_move_window = false;
726       break;
727     }
728   }
730   if (!should_move_window)
731     return false;
733   // Make the window draggable, then return true to repost the event.
734   SetDraggable(true);
735   return true;
738 void BridgedNativeWidget::OnSizeConstraintsChanged() {
739   // Don't modify the size constraints or fullscreen collection behavior while
740   // in fullscreen or during a transition. OnFullscreenTransitionComplete will
741   // reset these after leaving fullscreen.
742   if (target_fullscreen_state_ || in_fullscreen_transition_)
743     return;
745   Widget* widget = native_widget_mac()->GetWidget();
746   gfx::Size min_size = widget->GetMinimumSize();
747   gfx::Size max_size = widget->GetMaximumSize();
748   bool is_resizable = widget->widget_delegate()->CanResize();
749   bool shows_resize_controls =
750       is_resizable && (min_size.IsEmpty() || min_size != max_size);
751   bool shows_fullscreen_controls =
752       is_resizable && widget->widget_delegate()->CanMaximize();
754   gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size,
755                                     shows_resize_controls,
756                                     shows_fullscreen_controls);
759 ui::InputMethod* BridgedNativeWidget::GetInputMethod() {
760   if (!input_method_) {
761     input_method_ = ui::CreateInputMethod(this, nil);
762     // For now, use always-focused mode on Mac for the input method.
763     // TODO(tapted): Move this to OnWindowKeyStatusChangedTo() and balance.
764     input_method_->OnFocus();
765   }
766   return input_method_.get();
769 gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
770   if (target_fullscreen_state_ || in_fullscreen_transition_)
771     return bounds_before_fullscreen_;
773   return gfx::ScreenRectFromNSRect([window_ frame]);
776 void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
777                                       bool translucent) {
778   DCHECK(bridged_view_);
779   DCHECK(!layer());
781   CreateCompositor();
782   DCHECK(compositor_);
784   SetLayer(new ui::Layer(layer_type));
785   // Note, except for controls, this will set the layer to be hidden, since it
786   // is only called during Init().
787   layer()->SetVisible(window_visible_);
788   layer()->set_delegate(this);
790   InitCompositor();
792   // Transparent window support.
793   layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
794   layer()->SetFillsBoundsOpaquely(!translucent);
796   if (translucent) {
797     [window_ setOpaque:NO];
798     [window_ setBackgroundColor:[NSColor clearColor]];
799   }
801   UpdateLayerProperties();
804 ////////////////////////////////////////////////////////////////////////////////
805 // BridgedNativeWidget, internal::InputMethodDelegate:
807 ui::EventDispatchDetails BridgedNativeWidget::DispatchKeyEventPostIME(
808     ui::KeyEvent* key) {
809   DCHECK(focus_manager_);
810   native_widget_mac_->GetWidget()->OnKeyEvent(key);
811   if (!key->handled()) {
812     if (!focus_manager_->OnKeyEvent(*key))
813       key->StopPropagation();
814   }
815   return ui::EventDispatchDetails();
818 ////////////////////////////////////////////////////////////////////////////////
819 // BridgedNativeWidget, CocoaMouseCaptureDelegate:
821 void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) {
822   [bridged_view_ processCapturedMouseEvent:event];
825 void BridgedNativeWidget::OnMouseCaptureLost() {
826   native_widget_mac_->GetWidget()->OnMouseCaptureLost();
829 ////////////////////////////////////////////////////////////////////////////////
830 // BridgedNativeWidget, FocusChangeListener:
832 void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
833                                             View* focused_now) {
836 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
837                                            View* focused_now) {
838   ui::InputMethod* input_method =
839       native_widget_mac_->GetWidget()->GetInputMethod();
840   if (input_method) {
841     ui::TextInputClient* input_client = input_method->GetTextInputClient();
842     [bridged_view_ setTextInputClient:input_client];
843   }
846 ////////////////////////////////////////////////////////////////////////////////
847 // BridgedNativeWidget, LayerDelegate:
849 void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) {
850   DCHECK(window_visible_);
851   native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context);
854 void BridgedNativeWidget::OnDelegatedFrameDamage(
855     const gfx::Rect& damage_rect_in_dip) {
856   NOTIMPLEMENTED();
859 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
860     float device_scale_factor) {
861   NOTIMPLEMENTED();
864 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
865   NOTIMPLEMENTED();
866   return base::Closure();
869 ////////////////////////////////////////////////////////////////////////////////
870 // BridgedNativeWidget, AcceleratedWidgetMac:
872 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
873   return compositor_superview_;
876 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
877   return true;
880 void BridgedNativeWidget::AcceleratedWidgetGetVSyncParameters(
881   base::TimeTicks* timebase, base::TimeDelta* interval) const {
882   // TODO(tapted): Add vsync support.
883   *timebase = base::TimeTicks();
884   *interval = base::TimeDelta();
887 void BridgedNativeWidget::AcceleratedWidgetSwapCompleted(
888     const std::vector<ui::LatencyInfo>& latency_info) {
889   // Ignore frames arriving "late" for an old size. A frame at the new size
890   // should arrive soon.
891   if (!compositor_widget_->HasFrameOfSize(GetClientAreaSize()))
892     return;
894   if (invalidate_shadow_on_frame_swap_) {
895     invalidate_shadow_on_frame_swap_ = false;
896     [window_ invalidateShadow];
897   }
900 void BridgedNativeWidget::AcceleratedWidgetHitError() {
901   compositor_->ScheduleFullRedraw();
904 ////////////////////////////////////////////////////////////////////////////////
905 // BridgedNativeWidget, BridgedNativeWidgetOwner:
907 NSWindow* BridgedNativeWidget::GetNSWindow() {
908   return window_;
911 gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const {
912   return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin();
915 bool BridgedNativeWidget::IsVisibleParent() const {
916   return parent_ ? window_visible_ && parent_->IsVisibleParent()
917                  : window_visible_;
920 void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
921   auto location = std::find(
922       child_windows_.begin(), child_windows_.end(), child);
923   DCHECK(location != child_windows_.end());
924   child_windows_.erase(location);
926   // Note the child is sometimes removed already by AppKit. This depends on OS
927   // version, and possibly some unpredictable reference counting. Removing it
928   // here should be safe regardless.
929   [window_ removeChildWindow:child->window_];
932 ////////////////////////////////////////////////////////////////////////////////
933 // BridgedNativeWidget, private:
935 void BridgedNativeWidget::RemoveOrDestroyChildren() {
936   // TODO(tapted): Implement unowned child windows if required.
937   while (!child_windows_.empty()) {
938     // The NSWindow can only be destroyed after -[NSWindow close] is complete.
939     // Retain the window, otherwise the reference count can reach zero when the
940     // child calls back into RemoveChildWindow() via its OnWindowWillClose().
941     base::scoped_nsobject<NSWindow> child(
942         [child_windows_.back()->ns_window() retain]);
943     [child close];
944   }
947 void BridgedNativeWidget::NotifyVisibilityChangeDown() {
948   // Child windows sometimes like to close themselves in response to visibility
949   // changes. That's supported, but only with the asynchronous Widget::Close().
950   // Perform a heuristic to detect child removal that would break these loops.
951   const size_t child_count = child_windows_.size();
952   if (!window_visible_) {
953     for (BridgedNativeWidget* child : child_windows_) {
954       if (child->window_visible_)
955         [child->ns_window() orderOut:nil];
957       DCHECK(!child->window_visible_);
958       CHECK_EQ(child_count, child_windows_.size());
959     }
960     // The orderOut calls above should result in a call to OnVisibilityChanged()
961     // in each child. There, children will remove themselves from the NSWindow
962     // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
963     // to any children of their own.
964     DCHECK_EQ(0u, [[window_ childWindows] count]);
965     return;
966   }
968   NSUInteger visible_children = 0;  // For a DCHECK below.
969   NSInteger parent_window_number = [window_ windowNumber];
970   for (BridgedNativeWidget* child: child_windows_) {
971     // Note: order the child windows on top, regardless of whether or not they
972     // are currently visible. They probably aren't, since the parent was hidden
973     // prior to this, but they could have been made visible in other ways.
974     if (child->wants_to_be_visible_) {
975       ++visible_children;
976       // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
977       // screen. However, that by itself is insufficient to guarantee a correct
978       // z-order relationship. If this function is being called from a z-order
979       // change in the parent, orderWindow turns out to be unreliable (i.e. the
980       // ordering doesn't always take effect). What this actually relies on is
981       // the resulting call to OnVisibilityChanged() in the child, which will
982       // then insert itself into -[NSWindow childWindows] to let Cocoa do its
983       // internal layering magic.
984       [child->ns_window() orderWindow:NSWindowAbove
985                            relativeTo:parent_window_number];
986       DCHECK(child->window_visible_);
987     }
988     CHECK_EQ(child_count, child_windows_.size());
989   }
990   DCHECK_EQ(visible_children, [[window_ childWindows] count]);
993 gfx::Size BridgedNativeWidget::GetClientAreaSize() const {
994   NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]];
995   return gfx::Size(NSWidth(content_rect), NSHeight(content_rect));
998 void BridgedNativeWidget::CreateCompositor() {
999   DCHECK(!compositor_);
1000   DCHECK(!compositor_widget_);
1001   DCHECK(ViewsDelegate::GetInstance());
1003   ui::ContextFactory* context_factory =
1004       ViewsDelegate::GetInstance()->GetContextFactory();
1005   DCHECK(context_factory);
1007   AddCompositorSuperview();
1009   // TODO(tapted): Get this value from GpuDataManagerImpl via ViewsDelegate.
1010   bool needs_gl_finish_workaround = false;
1012   compositor_widget_.reset(
1013       new ui::AcceleratedWidgetMac(needs_gl_finish_workaround));
1014   compositor_.reset(
1015       new ui::Compositor(context_factory, base::ThreadTaskRunnerHandle::Get()));
1016   compositor_->SetAcceleratedWidgetAndStartCompositor(
1017       compositor_widget_->accelerated_widget());
1018   compositor_widget_->SetNSView(this);
1021 void BridgedNativeWidget::InitCompositor() {
1022   DCHECK(layer());
1023   float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
1024   gfx::Size size_in_dip = GetClientAreaSize();
1025   compositor_->SetScaleAndSize(scale_factor,
1026                                ConvertSizeToPixel(scale_factor, size_in_dip));
1027   compositor_->SetRootLayer(layer());
1030 void BridgedNativeWidget::DestroyCompositor() {
1031   if (layer()) {
1032     // LayerOwner supports a change in ownership, e.g., to animate a closing
1033     // window, but that won't work as expected for the root layer in
1034     // BridgedNativeWidget.
1035     DCHECK_EQ(this, layer()->owner());
1036     layer()->CompleteAllAnimations();
1037     layer()->SuppressPaint();
1038     layer()->set_delegate(nullptr);
1039   }
1040   DestroyLayer();
1042   if (!compositor_widget_) {
1043     DCHECK(!compositor_);
1044     return;
1045   }
1046   compositor_widget_->ResetNSView();
1047   compositor_.reset();
1048   compositor_widget_.reset();
1051 void BridgedNativeWidget::AddCompositorSuperview() {
1052   DCHECK(!compositor_superview_);
1053   compositor_superview_.reset(
1054       [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
1056   // Size and resize automatically with |bridged_view_|.
1057   [compositor_superview_
1058       setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
1060   // Enable HiDPI backing when supported (only on 10.7+).
1061   if ([compositor_superview_ respondsToSelector:
1062       @selector(setWantsBestResolutionOpenGLSurface:)]) {
1063     [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES];
1064   }
1066   base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
1067   [background_layer
1068       setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
1070   if (widget_type_ == Widget::InitParams::TYPE_MENU) {
1071     // Giving the canvas opacity messes up subpixel font rendering, so use a
1072     // solid background, but make the CALayer transparent.
1073     if (base::mac::IsOSYosemiteOrLater()) {
1074       [background_layer setOpacity:kYosemiteMenuOpacity];
1075       CGSSetWindowBackgroundBlurRadius(
1076           _CGSDefaultConnection(), [window_ windowNumber], kYosemiteMenuBlur);
1077       // The blur effect does not occur with a fully transparent (or fully
1078       // layer-backed) window. Setting a window background will use square
1079       // corners, so ask the contentView to draw one instead.
1080       [bridged_view_ setDrawMenuBackgroundForBlur:YES];
1081     } else {
1082       [background_layer setOpacity:kMavericksMenuOpacity];
1083     }
1084   }
1086   // Set the layer first to create a layer-hosting view (not layer-backed).
1087   [compositor_superview_ setLayer:background_layer];
1088   [compositor_superview_ setWantsLayer:YES];
1090   // The UI compositor should always be the first subview, to ensure webviews
1091   // are drawn on top of it.
1092   DCHECK_EQ(0u, [[bridged_view_ subviews] count]);
1093   [bridged_view_ addSubview:compositor_superview_];
1096 void BridgedNativeWidget::UpdateLayerProperties() {
1097   DCHECK(layer());
1098   DCHECK(compositor_superview_);
1099   gfx::Size size_in_dip = GetClientAreaSize();
1100   layer()->SetBounds(gfx::Rect(size_in_dip));
1102   float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
1103   compositor_->SetScaleAndSize(scale_factor,
1104                                ConvertSizeToPixel(scale_factor, size_in_dip));
1106   // For a translucent window, the shadow calculation needs to be carried out
1107   // after the frame from the compositor arrives.
1108   if (![window_ isOpaque])
1109     invalidate_shadow_on_frame_swap_ = true;
1112 NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
1113   NSMutableDictionary* properties = objc_getAssociatedObject(
1114       window_, &kWindowPropertiesKey);
1115   if (!properties) {
1116     properties = [NSMutableDictionary dictionary];
1117     objc_setAssociatedObject(window_, &kWindowPropertiesKey,
1118                              properties, OBJC_ASSOCIATION_RETAIN);
1119   }
1120   return properties;
1123 void BridgedNativeWidget::SetDraggable(bool draggable) {
1124   [bridged_view_ setMouseDownCanMoveWindow:draggable];
1125   // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1126   // changes. Previously we tried adding an NSView and removing it, but for some
1127   // reason it required reposting the mouse-down event, and didn't always work.
1128   // Calling the below seems to be an effective solution.
1129   [window_ setMovableByWindowBackground:NO];
1130   [window_ setMovableByWindowBackground:YES];
1133 }  // namespace views