chrome/browser/extensions: Remove use of MessageLoopProxy and deprecated MessageLoop...
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget.mm
bloba465d24e4211ae97ada810938e8f601b3ecd9e1a
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/base/ui_base_switches_util.h"
19 #include "ui/gfx/display.h"
20 #include "ui/gfx/geometry/dip_util.h"
21 #import "ui/gfx/mac/coordinate_conversion.h"
22 #import "ui/gfx/mac/nswindow_frame_controls.h"
23 #include "ui/gfx/screen.h"
24 #import "ui/views/cocoa/bridged_content_view.h"
25 #import "ui/views/cocoa/cocoa_mouse_capture.h"
26 #include "ui/views/cocoa/tooltip_manager_mac.h"
27 #import "ui/views/cocoa/views_nswindow_delegate.h"
28 #import "ui/views/cocoa/widget_owner_nswindow_adapter.h"
29 #include "ui/views/ime/input_method_bridge.h"
30 #include "ui/views/ime/null_input_method.h"
31 #include "ui/views/view.h"
32 #include "ui/views/views_delegate.h"
33 #include "ui/views/widget/native_widget_mac.h"
34 #include "ui/views/widget/widget.h"
35 #include "ui/views/widget/widget_aura_utils.h"
36 #include "ui/views/widget/widget_delegate.h"
38 // The NSView that hosts the composited CALayer drawing the UI. It fills the
39 // window but is not hittable so that accessibility hit tests always go to the
40 // BridgedContentView.
41 @interface ViewsCompositorSuperview : NSView
42 @end
44 @implementation ViewsCompositorSuperview
45 - (NSView*)hitTest:(NSPoint)aPoint {
46   return nil;
48 @end
50 namespace {
52 int kWindowPropertiesKey;
54 float GetDeviceScaleFactorFromView(NSView* view) {
55   gfx::Display display =
56       gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
57   DCHECK(display.is_valid());
58   return display.device_scale_factor();
61 // Returns true if bounds passed to window in SetBounds should be treated as
62 // though they are in screen coordinates.
63 bool PositionWindowInScreenCoordinates(views::Widget* widget,
64                                        views::Widget::InitParams::Type type) {
65   // Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
66   if (views::GetAuraWindowTypeForWidgetType(type) == ui::wm::WINDOW_TYPE_POPUP)
67     return true;
69   return widget && widget->is_top_level();
72 // Return the content size for a minimum or maximum widget size.
73 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
74                                      const gfx::Size& window_size) {
75   NSRect frame_rect =
76       NSMakeRect(0, 0, window_size.width(), window_size.height());
77   // Note gfx::Size will prevent dimensions going negative. They are allowed to
78   // be zero at this point, because Widget::GetMinimumSize() may later increase
79   // the size.
80   return gfx::Size([window contentRectForFrameRect:frame_rect].size);
83 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
84   id delegate = [[ns_event window] delegate];
85   return
86       [delegate
87           respondsToSelector:@selector(shouldRepostPendingLeftMouseDown:)] &&
88       [delegate shouldRepostPendingLeftMouseDown:[ns_event locationInWindow]];
91 // Check if a mouse-down event should drag the window. If so, repost the event.
92 NSEvent* RepostEventIfHandledByWindow(NSEvent* ns_event) {
93   enum RepostState {
94     // Nothing reposted: hit-test new mouse-downs to see if they need to be
95     // ignored and reposted after changing draggability.
96     NONE,
97     // Expecting the next event to be the reposted event: let it go through.
98     EXPECTING_REPOST,
99     // If, while reposting, another mousedown was received: when the reposted
100     // event is seen, ignore it.
101     REPOST_CANCELLED,
102   };
104   // Which repost we're expecting to receive.
105   static RepostState repost_state = NONE;
106   // The event number of the reposted event. This let's us track whether an
107   // event is actually the repost since user-generated events have increasing
108   // event numbers. This is only valid while |repost_state != NONE|.
109   static NSInteger reposted_event_number;
111   NSInteger event_number = [ns_event eventNumber];
113   // The logic here is a bit convoluted because we want to mitigate race
114   // conditions if somehow a different mouse-down occurs between reposts.
115   // Specifically, we want to avoid:
116   // - BridgedNativeWidget's draggability getting out of sync (e.g. if it is
117   //   draggable outside of a repost cycle),
118   // - any repost loop.
120   if (repost_state == NONE) {
121     if (WindowWantsMouseDownReposted(ns_event)) {
122       repost_state = EXPECTING_REPOST;
123       reposted_event_number = event_number;
124       CGEventPost(kCGSessionEventTap, [ns_event CGEvent]);
125       return nil;
126     }
128     return ns_event;
129   }
131   if (repost_state == EXPECTING_REPOST) {
132     // Call through so that the window is made non-draggable again.
133     WindowWantsMouseDownReposted(ns_event);
135     if (reposted_event_number == event_number) {
136       // Reposted event received.
137       repost_state = NONE;
138       return nil;
139     }
141     // We were expecting a repost, but since this is a new mouse-down, cancel
142     // reposting and allow event to continue as usual.
143     repost_state = REPOST_CANCELLED;
144     return ns_event;
145   }
147   DCHECK_EQ(REPOST_CANCELLED, repost_state);
148   if (reposted_event_number == event_number) {
149     // Reposting was cancelled, now that we've received the event, we don't
150     // expect to see it again.
151     repost_state = NONE;
152     return nil;
153   }
155   return ns_event;
158 // Support window caption/draggable regions.
159 // In AppKit, non-client regions are set by overriding
160 // -[NSView mouseDownCanMoveWindow]. NSApplication caches this area as views are
161 // installed and performs window moving when mouse-downs land in the area.
162 // In Views, non-client regions are determined via hit-tests when the event
163 // occurs.
164 // To bridge the two models, we monitor mouse-downs with
165 // +[NSEvent addLocalMonitorForEventsMatchingMask:handler:]. This receives
166 // events after window dragging is handled, so for mouse-downs that land on a
167 // draggable point, we cancel the event and repost it at the CGSessionEventTap
168 // level so that window dragging will be handled again.
169 void SetupDragEventMonitor() {
170   static id monitor = nil;
171   if (monitor)
172     return;
174   monitor = [NSEvent
175       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
176       handler:^NSEvent*(NSEvent* ns_event) {
177         return RepostEventIfHandledByWindow(ns_event);
178       }];
181 }  // namespace
183 namespace views {
185 // static
186 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
187     NSWindow* window,
188     const gfx::Size& content_size) {
189   NSRect content_rect =
190       NSMakeRect(0, 0, content_size.width(), content_size.height());
191   NSRect frame_rect = [window frameRectForContentRect:content_rect];
192   return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
195 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
196     : native_widget_mac_(parent),
197       focus_manager_(nullptr),
198       widget_type_(Widget::InitParams::TYPE_WINDOW),  // Updated in Init().
199       parent_(nullptr),
200       target_fullscreen_state_(false),
201       in_fullscreen_transition_(false),
202       window_visible_(false),
203       wants_to_be_visible_(false) {
204   SetupDragEventMonitor();
205   DCHECK(parent);
206   window_delegate_.reset(
207       [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
210 BridgedNativeWidget::~BridgedNativeWidget() {
211   RemoveOrDestroyChildren();
212   DCHECK(child_windows_.empty());
213   SetFocusManager(NULL);
214   SetRootView(NULL);
215   DestroyCompositor();
216   if ([window_ delegate]) {
217     // If the delegate is still set on a modal dialog, it means it was not
218     // closed via [NSApplication endSheet:]. This is probably OK if the widget
219     // was never shown. But Cocoa ignores close() calls on open sheets. Calling
220     // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
221     // DialogClientView assumes its delegate is alive when closing, which isn't
222     // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
223     // So ban it. Modal dialogs should be closed via Widget::Close().
224     DCHECK(!native_widget_mac_->IsWindowModalSheet());
226     // If the delegate is still set, it means OnWindowWillClose has not been
227     // called and the window is still open. Calling -[NSWindow close] will
228     // synchronously call OnWindowWillClose and notify NativeWidgetMac.
229     [window_ close];
230   }
231   DCHECK(![window_ delegate]);
234 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
235                                const Widget::InitParams& params) {
236   widget_type_ = params.type;
238   DCHECK(!window_);
239   window_.swap(window);
240   [window_ setDelegate:window_delegate_];
242   // Register for application hide notifications so that visibility can be
243   // properly tracked. This is not done in the delegate so that the lifetime is
244   // tied to the C++ object, rather than the delegate (which may be reference
245   // counted). This is required since the application hides do not send an
246   // orderOut: to individual windows. Unhide, however, does send an order
247   // message.
248   [[NSNotificationCenter defaultCenter]
249       addObserver:window_delegate_
250          selector:@selector(onWindowOrderChanged:)
251              name:NSApplicationDidHideNotification
252            object:nil];
254   // Validate the window's initial state, otherwise the bridge's initial
255   // tracking state will be incorrect.
256   DCHECK(![window_ isVisible]);
257   DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
259   if (params.parent) {
260     // Disallow creating child windows of views not currently in an NSWindow.
261     CHECK([params.parent window]);
262     BridgedNativeWidget* bridged_native_widget_parent =
263         NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
264     // If the parent is another BridgedNativeWidget, just add to the collection
265     // of child windows it owns and manages. Otherwise, create an adapter to
266     // anchor the child widget and observe when the parent NSWindow is closed.
267     if (bridged_native_widget_parent) {
268       parent_ = bridged_native_widget_parent;
269       bridged_native_widget_parent->child_windows_.push_back(this);
270     } else {
271       parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
272     }
273   }
275   // Set a meaningful initial bounds. Note that except for frameless widgets
276   // with no WidgetDelegate, the bounds will be set again by Widget after
277   // initializing the non-client view. In the former case, if bounds were not
278   // set at all, the creator of the Widget is expected to call SetBounds()
279   // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
280   // (i.e. 1x1) window appearing.
281   if (!params.bounds.IsEmpty()) {
282     SetBounds(params.bounds);
283   } else {
284     // If a position is set, but no size, complain. Otherwise, a 1x1 window
285     // would appear there, which might be unexpected.
286     DCHECK(params.bounds.origin().IsOrigin())
287         << "Zero-sized windows not supported on Mac.";
289     // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
290     // the bottom left of the screen. To support a client calling SetSize() only
291     // (and for consistency across platforms) put it at the top-left instead.
292     // Read back the current frame: it will be a 1x1 context rect but the frame
293     // size also depends on the window style.
294     NSRect frame_rect = [window_ frame];
295     SetBounds(gfx::Rect(gfx::Point(),
296                         gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))));
297   }
299   // Widgets for UI controls (usually layered above web contents) start visible.
300   if (params.type == Widget::InitParams::TYPE_CONTROL)
301     SetVisibilityState(SHOW_INACTIVE);
303   // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are
304   // native on Mac, so nothing should ever want one in Widget form.
305   DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP);
306   tooltip_manager_.reset(new TooltipManagerMac(this));
309 void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
310   if (focus_manager_ == focus_manager)
311     return;
313   if (focus_manager_)
314     focus_manager_->RemoveFocusChangeListener(this);
316   if (focus_manager)
317     focus_manager->AddFocusChangeListener(this);
319   focus_manager_ = focus_manager;
322 void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
323   Widget* widget = native_widget_mac_->GetWidget();
324   // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated
325   // resizes. This is not what toolkit-views expects, so clamp. Note there is
326   // no check for maximum size (consistent with aura::Window::SetBounds()).
327   gfx::Size clamped_content_size =
328       GetClientSizeForWindowSize(window_, new_bounds.size());
329   clamped_content_size.SetToMax(widget->GetMinimumSize());
331   // A contentRect with zero width or height is a banned practice in ChromeMac,
332   // due to unpredictable OSX treatment.
333   DCHECK(!clamped_content_size.IsEmpty())
334       << "Zero-sized windows not supported on Mac";
336   if (!window_visible_ && native_widget_mac_->IsWindowModalSheet()) {
337     // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for
338     // the first time. They also have no frame, so just update the content size.
339     [window_ setContentSize:NSMakeSize(clamped_content_size.width(),
340                                        clamped_content_size.height())];
341     return;
342   }
343   gfx::Rect actual_new_bounds(
344       new_bounds.origin(),
345       GetWindowSizeForClientSize(window_, clamped_content_size));
347   if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_))
348     actual_new_bounds.Offset(parent_->GetChildWindowOffset());
350   [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
351             display:YES
352             animate:NO];
355 void BridgedNativeWidget::SetRootView(views::View* view) {
356   if (view == [bridged_view_ hostedView])
357     return;
359   // If this is ever false, the compositor will need to be properly torn down
360   // and replaced, pointing at the new view.
361   DCHECK(!view || !compositor_widget_);
363   [bridged_view_ clearView];
364   bridged_view_.reset();
365   // Note that there can still be references to the old |bridged_view_|
366   // floating around in Cocoa libraries at this point. However, references to
367   // the old views::View will be gone, so any method calls will become no-ops.
369   if (view) {
370     bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
371     // Objective C initializers can return nil. However, if |view| is non-NULL
372     // this should be treated as an error and caught early.
373     CHECK(bridged_view_);
374   }
375   [window_ setContentView:bridged_view_];
378 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
379   // Ensure that:
380   //  - A window with an invisible parent is not made visible.
381   //  - A parent changing visibility updates child window visibility.
382   //    * But only when changed via this function - ignore changes via the
383   //      NSWindow API, or changes propagating out from here.
384   wants_to_be_visible_ = new_state != HIDE_WINDOW;
386   if (new_state == HIDE_WINDOW) {
387     [window_ orderOut:nil];
388     DCHECK(!window_visible_);
389     return;
390   }
392   DCHECK(wants_to_be_visible_);
393   // If the parent (or an ancestor) is hidden, return and wait for it to become
394   // visible.
395   if (parent() && !parent()->IsVisibleParent())
396     return;
398   if (native_widget_mac_->IsWindowModalSheet()) {
399     NSWindow* parent_window = parent_->GetNSWindow();
400     DCHECK(parent_window);
402     [NSApp beginSheet:window_
403         modalForWindow:parent_window
404          modalDelegate:[window_ delegate]
405         didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
406            contextInfo:nullptr];
407     return;
408   }
410   if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
411     [window_ makeKeyAndOrderFront:nil];
412     [NSApp activateIgnoringOtherApps:YES];
413   } else {
414     // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a
415     // parent window. So, if there's a parent, order above that. Otherwise, this
416     // will order above all windows at the same level.
417     NSInteger parent_window_number = 0;
418     if (parent_)
419       parent_window_number = [parent_->GetNSWindow() windowNumber];
421     [window_ orderWindow:NSWindowAbove
422               relativeTo:parent_window_number];
423   }
424   DCHECK(window_visible_);
426   // For non-sheet modal types, use the constrained window animations to make
427   // the window appear.
428   if (native_widget_mac_->GetWidget()->IsModal()) {
429     base::scoped_nsobject<NSAnimation> show_animation(
430         [[ConstrainedWindowAnimationShow alloc] initWithWindow:window_]);
431     // The default mode is blocking, which would block the UI thread for the
432     // duration of the animation, but would keep it smooth. The window also
433     // hasn't yet received a frame from the compositor at this stage, so it is
434     // fully transparent until the GPU sends a frame swap IPC. For the blocking
435     // option, the animation needs to wait until AcceleratedWidgetSwapCompleted
436     // has been called at least once, otherwise it will animate nothing.
437     [show_animation setAnimationBlockingMode:NSAnimationNonblocking];
438     [show_animation startAnimation];
439   }
442 void BridgedNativeWidget::AcquireCapture() {
443   DCHECK(!HasCapture());
444   if (!window_visible_)
445     return;  // Capture on hidden windows is disallowed.
447   mouse_capture_.reset(new CocoaMouseCapture(this));
449   // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
450   // will reset the mouse cursor to an arrow. Asking the window for an update
451   // here will restore what we want. However, it can sometimes cause the cursor
452   // to flicker, once, on the initial mouseDown.
453   // TOOD(tapted): Make this unnecessary by only asking for global mouse capture
454   // for the cases that need it (e.g. menus, but not drag and drop).
455   [window_ cursorUpdate:[NSApp currentEvent]];
458 void BridgedNativeWidget::ReleaseCapture() {
459   mouse_capture_.reset();
462 bool BridgedNativeWidget::HasCapture() {
463   return mouse_capture_ && mouse_capture_->IsActive();
466 void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
467                                                   void* value) {
468   NSString* key = [NSString stringWithUTF8String:name];
469   if (value) {
470     [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
471                               forKey:key];
472   } else {
473     [GetWindowProperties() removeObjectForKey:key];
474   }
477 void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const {
478   NSString* key = [NSString stringWithUTF8String:name];
479   return [[GetWindowProperties() objectForKey:key] pointerValue];
482 void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
483   [window_delegate_ setCursor:cursor];
486 void BridgedNativeWidget::OnWindowWillClose() {
487   if (parent_) {
488     parent_->RemoveChildWindow(this);
489     parent_ = nullptr;
490   }
491   [window_ setDelegate:nil];
492   [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
493   native_widget_mac_->OnWindowWillClose();
496 void BridgedNativeWidget::OnFullscreenTransitionStart(
497     bool target_fullscreen_state) {
498   // Note: This can fail for fullscreen changes started externally, but a user
499   // shouldn't be able to do that if the window is invisible to begin with.
500   DCHECK(window_visible_);
502   DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
503   target_fullscreen_state_ = target_fullscreen_state;
504   in_fullscreen_transition_ = true;
506   // If going into fullscreen, store an answer for GetRestoredBounds().
507   if (target_fullscreen_state)
508     bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
511 void BridgedNativeWidget::OnFullscreenTransitionComplete(
512     bool actual_fullscreen_state) {
513   in_fullscreen_transition_ = false;
515   if (target_fullscreen_state_ == actual_fullscreen_state) {
516     // Ensure constraints are re-applied when completing a transition.
517     OnSizeConstraintsChanged();
518     return;
519   }
521   // First update to reflect reality so that OnTargetFullscreenStateChanged()
522   // expects the change.
523   target_fullscreen_state_ = actual_fullscreen_state;
524   ToggleDesiredFullscreenState();
526   // Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
527   // OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
528   // toggleFullScreen: request. This can occur when the fullscreen transition
529   // fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
530   // Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
531   if (in_fullscreen_transition_)
532     DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
535 void BridgedNativeWidget::ToggleDesiredFullscreenState() {
536   // If there is currently an animation into or out of fullscreen, then AppKit
537   // emits the string "not in fullscreen state" to stdio and does nothing. For
538   // this case, schedule a transition back into the desired state when the
539   // animation completes.
540   if (in_fullscreen_transition_) {
541     target_fullscreen_state_ = !target_fullscreen_state_;
542     return;
543   }
545   // Going fullscreen implicitly makes the window visible. AppKit does this.
546   // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
547   // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
548   // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an
549   // orderWindow message. So intercepting the implicit change is hard.
550   // Luckily, to trigger externally, the window typically needs to be visible in
551   // the first place. So we can just ensure the window is visible here instead
552   // of relying on AppKit to do it, and not worry that OnVisibilityChanged()
553   // won't be called for externally triggered fullscreen requests.
554   if (!window_visible_)
555     SetVisibilityState(SHOW_INACTIVE);
557   if (base::mac::IsOSSnowLeopard()) {
558     NOTIMPLEMENTED();
559     return;  // TODO(tapted): Implement this for Snow Leopard.
560   }
562   // Enable fullscreen collection behavior because:
563   // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
564   // 2: the fullscreen button must be enabled so the user can leave fullscreen.
565   // This will be reset when a transition out of fullscreen completes.
566   gfx::SetNSWindowCanFullscreen(window_, true);
568   [window_ toggleFullScreen:nil];
571 void BridgedNativeWidget::OnSizeChanged() {
572   gfx::Size new_size = GetClientAreaSize();
573   native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size);
574   if (layer())
575     UpdateLayerProperties();
578 void BridgedNativeWidget::OnVisibilityChanged() {
579   OnVisibilityChangedTo([window_ isVisible]);
582 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
583   if (window_visible_ == new_visibility)
584     return;
586   window_visible_ = new_visibility;
588   // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
589   // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
590   // to distinguish being *hidden* externally from being hidden by a parent
591   // window - we might not need that.
592   if (window_visible_) {
593     wants_to_be_visible_ = true;
595     if (parent_)
596       [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
597   } else {
598     mouse_capture_.reset();  // Capture on hidden windows is not permitted.
600     // When becoming invisible, remove the entry in any parent's childWindow
601     // list. Cocoa's childWindow management breaks down when child windows are
602     // hidden.
603     if (parent_)
604       [parent_->GetNSWindow() removeChildWindow:window_];
605   }
607   // TODO(tapted): Investigate whether we want this for Mac. This is what Aura
608   // does, and it is what tests expect. However, because layer drawing is
609   // asynchronous (and things like deminiaturize in AppKit are not), it can
610   // result in a CALayer appearing on screen before it has been redrawn in the
611   // GPU process. This is a general problem. In content, a helper class,
612   // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:]
613   // and RenderWidgetHostView::Show() until a frame is ready.
614   if (layer()) {
615     layer()->SetVisible(window_visible_);
616     layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
617   }
619   NotifyVisibilityChangeDown();
621   native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
622       window_visible_);
624   // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking
625   // for an "empty" draw, disable auto-display while hidden. For example, this
626   // prevents Cocoa drawing just *after* a minimize, resulting in a blank window
627   // represented in the deminiaturize animation.
628   [window_ setAutodisplay:window_visible_];
631 void BridgedNativeWidget::OnBackingPropertiesChanged() {
632   if (layer())
633     UpdateLayerProperties();
636 void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
637   Widget* widget = native_widget_mac()->GetWidget();
638   widget->OnNativeWidgetActivationChanged(is_key);
639   // The contentView is the BridgedContentView hosting the views::RootView. The
640   // focus manager will already know if a native subview has focus.
641   if ([window_ contentView] == [window_ firstResponder]) {
642     if (is_key) {
643       widget->OnNativeFocus();
644       widget->GetFocusManager()->RestoreFocusedView();
645     } else {
646       widget->OnNativeBlur();
647       widget->GetFocusManager()->StoreFocusedView(true);
648     }
649   }
652 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
653     NSPoint location_in_window) {
654   if (!bridged_view_)
655     return false;
657   if ([bridged_view_ mouseDownCanMoveWindow]) {
658     // This is a re-post, the movement has already started, so we can make the
659     // window non-draggable again.
660     SetDraggable(false);
661     return false;
662   }
664   gfx::Point point(location_in_window.x,
665                    NSHeight([window_ frame]) - location_in_window.y);
666   bool should_move_window =
667       native_widget_mac()->GetWidget()->GetNonClientComponent(point) ==
668       HTCAPTION;
670   // Check that the point is not obscured by non-content NSViews.
671   for (NSView* subview : [[bridged_view_ superview] subviews]) {
672     if (subview == bridged_view_.get())
673       continue;
675     if (![subview mouseDownCanMoveWindow] &&
676         NSPointInRect(location_in_window, [subview frame])) {
677       should_move_window = false;
678       break;
679     }
680   }
682   if (!should_move_window)
683     return false;
685   // Make the window draggable, then return true to repost the event.
686   SetDraggable(true);
687   return true;
690 void BridgedNativeWidget::OnSizeConstraintsChanged() {
691   // Don't modify the size constraints or fullscreen collection behavior while
692   // in fullscreen or during a transition. OnFullscreenTransitionComplete will
693   // reset these after leaving fullscreen.
694   if (target_fullscreen_state_ || in_fullscreen_transition_)
695     return;
697   Widget* widget = native_widget_mac()->GetWidget();
698   gfx::Size min_size = widget->GetMinimumSize();
699   gfx::Size max_size = widget->GetMaximumSize();
700   bool is_resizable = widget->widget_delegate()->CanResize();
701   bool shows_resize_controls =
702       is_resizable && (min_size.IsEmpty() || min_size != max_size);
703   bool shows_fullscreen_controls =
704       is_resizable && widget->widget_delegate()->CanMaximize();
706   gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size,
707                                     shows_resize_controls,
708                                     shows_fullscreen_controls);
711 InputMethod* BridgedNativeWidget::CreateInputMethod() {
712   if (switches::IsTextInputFocusManagerEnabled())
713     return new NullInputMethod();
715   return new InputMethodBridge(this, GetHostInputMethod(), true);
718 ui::InputMethod* BridgedNativeWidget::GetHostInputMethod() {
719   if (!input_method_) {
720     // Delegate is NULL because Mac IME does not need DispatchKeyEventPostIME
721     // callbacks.
722     input_method_ = ui::CreateInputMethod(NULL, nil);
723   }
724   return input_method_.get();
727 gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
728   if (target_fullscreen_state_ || in_fullscreen_transition_)
729     return bounds_before_fullscreen_;
731   return gfx::ScreenRectFromNSRect([window_ frame]);
734 void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
735                                       bool translucent) {
736   DCHECK(bridged_view_);
737   DCHECK(!layer());
739   CreateCompositor();
740   DCHECK(compositor_);
742   SetLayer(new ui::Layer(layer_type));
743   // Note, except for controls, this will set the layer to be hidden, since it
744   // is only called during Init().
745   layer()->SetVisible(window_visible_);
746   layer()->set_delegate(this);
748   InitCompositor();
750   // Transparent window support.
751   layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
752   layer()->SetFillsBoundsOpaquely(!translucent);
753   if (translucent) {
754     [window_ setOpaque:NO];
755     [window_ setBackgroundColor:[NSColor clearColor]];
756   }
758   UpdateLayerProperties();
761 ////////////////////////////////////////////////////////////////////////////////
762 // BridgedNativeWidget, internal::InputMethodDelegate:
764 void BridgedNativeWidget::DispatchKeyEventPostIME(const ui::KeyEvent& key) {
765   DCHECK(focus_manager_);
766   native_widget_mac_->GetWidget()->OnKeyEvent(const_cast<ui::KeyEvent*>(&key));
767   if (!key.handled())
768     focus_manager_->OnKeyEvent(key);
771 ////////////////////////////////////////////////////////////////////////////////
772 // BridgedNativeWidget, CocoaMouseCaptureDelegate:
774 void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) {
775   [bridged_view_ processCapturedMouseEvent:event];
778 void BridgedNativeWidget::OnMouseCaptureLost() {
779   native_widget_mac_->GetWidget()->OnMouseCaptureLost();
782 ////////////////////////////////////////////////////////////////////////////////
783 // BridgedNativeWidget, FocusChangeListener:
785 void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
786                                             View* focused_now) {
789 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
790                                            View* focused_now) {
791   ui::TextInputClient* input_client =
792       focused_now ? focused_now->GetTextInputClient() : NULL;
793   [bridged_view_ setTextInputClient:input_client];
796 ////////////////////////////////////////////////////////////////////////////////
797 // BridgedNativeWidget, LayerDelegate:
799 void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) {
800   DCHECK(window_visible_);
801   native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context);
804 void BridgedNativeWidget::OnDelegatedFrameDamage(
805     const gfx::Rect& damage_rect_in_dip) {
806   NOTIMPLEMENTED();
809 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
810     float device_scale_factor) {
811   NOTIMPLEMENTED();
814 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
815   NOTIMPLEMENTED();
816   return base::Closure();
819 ////////////////////////////////////////////////////////////////////////////////
820 // BridgedNativeWidget, AcceleratedWidgetMac:
822 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
823   return compositor_superview_;
826 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
827   return true;
830 void BridgedNativeWidget::AcceleratedWidgetSwapCompleted(
831     const std::vector<ui::LatencyInfo>& latency_info) {
834 void BridgedNativeWidget::AcceleratedWidgetHitError() {
835   compositor_->ScheduleFullRedraw();
838 ////////////////////////////////////////////////////////////////////////////////
839 // BridgedNativeWidget, BridgedNativeWidgetOwner:
841 NSWindow* BridgedNativeWidget::GetNSWindow() {
842   return window_;
845 gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const {
846   return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin();
849 bool BridgedNativeWidget::IsVisibleParent() const {
850   return parent_ ? window_visible_ && parent_->IsVisibleParent()
851                  : window_visible_;
854 void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
855   auto location = std::find(
856       child_windows_.begin(), child_windows_.end(), child);
857   DCHECK(location != child_windows_.end());
858   child_windows_.erase(location);
860   // Note the child is sometimes removed already by AppKit. This depends on OS
861   // version, and possibly some unpredictable reference counting. Removing it
862   // here should be safe regardless.
863   [window_ removeChildWindow:child->window_];
866 ////////////////////////////////////////////////////////////////////////////////
867 // BridgedNativeWidget, private:
869 void BridgedNativeWidget::RemoveOrDestroyChildren() {
870   // TODO(tapted): Implement unowned child windows if required.
871   while (!child_windows_.empty()) {
872     // The NSWindow can only be destroyed after -[NSWindow close] is complete.
873     // Retain the window, otherwise the reference count can reach zero when the
874     // child calls back into RemoveChildWindow() via its OnWindowWillClose().
875     base::scoped_nsobject<NSWindow> child(
876         [child_windows_.back()->ns_window() retain]);
877     [child close];
878   }
881 void BridgedNativeWidget::NotifyVisibilityChangeDown() {
882   // Child windows sometimes like to close themselves in response to visibility
883   // changes. That's supported, but only with the asynchronous Widget::Close().
884   // Perform a heuristic to detect child removal that would break these loops.
885   const size_t child_count = child_windows_.size();
886   if (!window_visible_) {
887     for (BridgedNativeWidget* child : child_windows_) {
888       if (child->window_visible_)
889         [child->ns_window() orderOut:nil];
891       DCHECK(!child->window_visible_);
892       CHECK_EQ(child_count, child_windows_.size());
893     }
894     // The orderOut calls above should result in a call to OnVisibilityChanged()
895     // in each child. There, children will remove themselves from the NSWindow
896     // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
897     // to any children of their own.
898     DCHECK_EQ(0u, [[window_ childWindows] count]);
899     return;
900   }
902   NSUInteger visible_children = 0;  // For a DCHECK below.
903   NSInteger parent_window_number = [window_ windowNumber];
904   for (BridgedNativeWidget* child: child_windows_) {
905     // Note: order the child windows on top, regardless of whether or not they
906     // are currently visible. They probably aren't, since the parent was hidden
907     // prior to this, but they could have been made visible in other ways.
908     if (child->wants_to_be_visible_) {
909       ++visible_children;
910       // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
911       // screen. However, that by itself is insufficient to guarantee a correct
912       // z-order relationship. If this function is being called from a z-order
913       // change in the parent, orderWindow turns out to be unreliable (i.e. the
914       // ordering doesn't always take effect). What this actually relies on is
915       // the resulting call to OnVisibilityChanged() in the child, which will
916       // then insert itself into -[NSWindow childWindows] to let Cocoa do its
917       // internal layering magic.
918       [child->ns_window() orderWindow:NSWindowAbove
919                            relativeTo:parent_window_number];
920       DCHECK(child->window_visible_);
921     }
922     CHECK_EQ(child_count, child_windows_.size());
923   }
924   DCHECK_EQ(visible_children, [[window_ childWindows] count]);
927 gfx::Size BridgedNativeWidget::GetClientAreaSize() const {
928   NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]];
929   return gfx::Size(NSWidth(content_rect), NSHeight(content_rect));
932 void BridgedNativeWidget::CreateCompositor() {
933   DCHECK(!compositor_);
934   DCHECK(!compositor_widget_);
935   DCHECK(ViewsDelegate::GetInstance());
937   ui::ContextFactory* context_factory =
938       ViewsDelegate::GetInstance()->GetContextFactory();
939   DCHECK(context_factory);
941   AddCompositorSuperview();
943   // TODO(tapted): Get this value from GpuDataManagerImpl via ViewsDelegate.
944   bool needs_gl_finish_workaround = false;
946   compositor_widget_.reset(
947       new ui::AcceleratedWidgetMac(needs_gl_finish_workaround));
948   compositor_.reset(new ui::Compositor(compositor_widget_->accelerated_widget(),
949                                        context_factory,
950                                        base::ThreadTaskRunnerHandle::Get()));
951   compositor_widget_->SetNSView(this);
954 void BridgedNativeWidget::InitCompositor() {
955   DCHECK(layer());
956   float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
957   gfx::Size size_in_dip = GetClientAreaSize();
958   compositor_->SetScaleAndSize(scale_factor,
959                                ConvertSizeToPixel(scale_factor, size_in_dip));
960   compositor_->SetRootLayer(layer());
963 void BridgedNativeWidget::DestroyCompositor() {
964   if (layer())
965     layer()->set_delegate(nullptr);
966   DestroyLayer();
968   if (!compositor_widget_) {
969     DCHECK(!compositor_);
970     return;
971   }
972   compositor_widget_->ResetNSView();
973   compositor_.reset();
974   compositor_widget_.reset();
977 void BridgedNativeWidget::AddCompositorSuperview() {
978   DCHECK(!compositor_superview_);
979   compositor_superview_.reset(
980       [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
982   // Size and resize automatically with |bridged_view_|.
983   [compositor_superview_
984       setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
986   // Enable HiDPI backing when supported (only on 10.7+).
987   if ([compositor_superview_ respondsToSelector:
988       @selector(setWantsBestResolutionOpenGLSurface:)]) {
989     [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES];
990   }
992   base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
993   [background_layer
994       setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
996   // Set the layer first to create a layer-hosting view (not layer-backed).
997   [compositor_superview_ setLayer:background_layer];
998   [compositor_superview_ setWantsLayer:YES];
1000   // The UI compositor should always be the first subview, to ensure webviews
1001   // are drawn on top of it.
1002   DCHECK_EQ(0u, [[bridged_view_ subviews] count]);
1003   [bridged_view_ addSubview:compositor_superview_];
1006 void BridgedNativeWidget::UpdateLayerProperties() {
1007   DCHECK(layer());
1008   DCHECK(compositor_superview_);
1009   gfx::Size size_in_dip = GetClientAreaSize();
1010   layer()->SetBounds(gfx::Rect(size_in_dip));
1012   float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
1013   compositor_->SetScaleAndSize(scale_factor,
1014                                ConvertSizeToPixel(scale_factor, size_in_dip));
1017 NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
1018   NSMutableDictionary* properties = objc_getAssociatedObject(
1019       window_, &kWindowPropertiesKey);
1020   if (!properties) {
1021     properties = [NSMutableDictionary dictionary];
1022     objc_setAssociatedObject(window_, &kWindowPropertiesKey,
1023                              properties, OBJC_ASSOCIATION_RETAIN);
1024   }
1025   return properties;
1028 void BridgedNativeWidget::SetDraggable(bool draggable) {
1029   [bridged_view_ setMouseDownCanMoveWindow:draggable];
1030   // AppKit will not update its cache of mouseDownCanMoveWindow unless something
1031   // changes. Previously we tried adding an NSView and removing it, but for some
1032   // reason it required reposting the mouse-down event, and didn't always work.
1033   // Calling the below seems to be an effective solution.
1034   [window_ setMovableByWindowBackground:NO];
1035   [window_ setMovableByWindowBackground:YES];
1038 }  // namespace views