Add ICU message format support
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget.mm
blob1bd03ff243ee6d097a8d3ac0e766240a09d3199e
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
39 @end
41 @implementation ViewsCompositorSuperview
42 - (NSView*)hitTest:(NSPoint)aPoint {
43   return nil;
45 @end
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
58 @end
60 @implementation ModalShowAnimationWithLayer
61 - (void)stopAnimation {
62   [super stopAnimation];
63   [window_ invalidateShadow];
65 - (void)setCurrentProgress:(NSAnimationProgress)progress {
66   [super setCurrentProgress:progress];
67   [window_ invalidateShadow];
69 @end
71 namespace {
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)
88     return true;
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) {
96   NSRect frame_rect =
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
100   // the size.
101   return gfx::Size([window contentRectForFrameRect:frame_rect].size);
104 BOOL WindowWantsMouseDownReposted(NSEvent* ns_event) {
105   id delegate = [[ns_event window] delegate];
106   return
107       [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) {
114   enum RepostState {
115     // Nothing reposted: hit-test new mouse-downs to see if they need to be
116     // ignored and reposted after changing draggability.
117     NONE,
118     // Expecting the next event to be the reposted event: let it go through.
119     EXPECTING_REPOST,
120     // If, while reposting, another mousedown was received: when the reposted
121     // event is seen, ignore it.
122     REPOST_CANCELLED,
123   };
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]);
146       return nil;
147     }
149     return ns_event;
150   }
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.
158       repost_state = NONE;
159       return nil;
160     }
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;
165     return ns_event;
166   }
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.
172     repost_state = NONE;
173     return nil;
174   }
176   return ns_event;
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
184 // occurs.
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;
192   if (monitor)
193     return;
195   monitor = [NSEvent
196       addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask
197       handler:^NSEvent*(NSEvent* ns_event) {
198         return RepostEventIfHandledByWindow(ns_event);
199       }];
202 }  // namespace
204 namespace views {
206 // static
207 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
208     NSWindow* window,
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().
220       parent_(nullptr),
221       target_fullscreen_state_(false),
222       in_fullscreen_transition_(false),
223       window_visible_(false),
224       wants_to_be_visible_(false) {
225   SetupDragEventMonitor();
226   DCHECK(parent);
227   window_delegate_.reset(
228       [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
231 BridgedNativeWidget::~BridgedNativeWidget() {
232   RemoveOrDestroyChildren();
233   DCHECK(child_windows_.empty());
234   SetFocusManager(NULL);
235   SetRootView(NULL);
236   DestroyCompositor();
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.
250     [window_ close];
251   }
252   DCHECK(![window_ delegate]);
255 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
256                                const Widget::InitParams& params) {
257   widget_type_ = params.type;
259   DCHECK(!window_);
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
268   // message.
269   [[NSNotificationCenter defaultCenter]
270       addObserver:window_delegate_
271          selector:@selector(onWindowOrderChanged:)
272              name:NSApplicationDidHideNotification
273            object:nil];
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);
280   if (params.parent) {
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);
291     } else {
292       parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
293     }
294   }
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];
302       break;
303     case Widget::InitParams::SHADOW_TYPE_DEFAULT:
304     case Widget::InitParams::SHADOW_TYPE_DROP:
305       [window_ setHasShadow:YES];
306       break;
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);
317   } else {
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))));
331   }
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)
345     return;
347   if (focus_manager_)
348     focus_manager_->RemoveFocusChangeListener(this);
350   if (focus_manager)
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())];
375     return;
376   }
377   gfx::Rect actual_new_bounds(
378       new_bounds.origin(),
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)
385             display:YES
386             animate:NO];
389 void BridgedNativeWidget::SetRootView(views::View* view) {
390   if (view == [bridged_view_ hostedView])
391     return;
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.
403   if (view) {
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_);
408   }
409   [window_ setContentView:bridged_view_];
412 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
413   // Ensure that:
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_);
423     return;
424   }
426   DCHECK(wants_to_be_visible_);
427   // If the parent (or an ancestor) is hidden, return and wait for it to become
428   // visible.
429   if (parent() && !parent()->IsVisibleParent())
430     return;
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];
441     return;
442   }
444   if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
445     [window_ makeKeyAndOrderFront:nil];
446     [NSApp activateIgnoringOtherApps:YES];
447   } else {
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;
452     if (parent_)
453       parent_window_number = [parent_->GetNSWindow() windowNumber];
455     [window_ orderWindow:NSWindowAbove
456               relativeTo:parent_window_number];
457   }
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];
473   }
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,
501                                                   void* value) {
502   NSString* key = [NSString stringWithUTF8String:name];
503   if (value) {
504     [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
505                               forKey:key];
506   } else {
507     [GetWindowProperties() removeObjectForKey:key];
508   }
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() {
521   if (parent_) {
522     parent_->RemoveChildWindow(this);
523     parent_ = nullptr;
524   }
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();
552     return;
553   }
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_;
576     return;
577   }
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()) {
592     NOTIMPLEMENTED();
593     return;  // TODO(tapted): Implement this for Snow Leopard.
594   }
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);
608   if (layer())
609     UpdateLayerProperties();
612 void BridgedNativeWidget::OnVisibilityChanged() {
613   OnVisibilityChangedTo([window_ isVisible]);
616 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
617   if (window_visible_ == new_visibility)
618     return;
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;
629     if (parent_)
630       [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
631   } else {
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
636     // hidden.
637     if (parent_)
638       [parent_->GetNSWindow() removeChildWindow:window_];
639   }
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.
648   if (layer()) {
649     layer()->SetVisible(window_visible_);
650     layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
651   }
653   NotifyVisibilityChangeDown();
655   native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
656       window_visible_);
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() {
666   if (layer())
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]) {
676     if (is_key) {
677       widget->OnNativeFocus();
678       widget->GetFocusManager()->RestoreFocusedView();
679     } else {
680       widget->OnNativeBlur();
681       widget->GetFocusManager()->StoreFocusedView(true);
682     }
683   }
686 bool BridgedNativeWidget::ShouldRepostPendingLeftMouseDown(
687     NSPoint location_in_window) {
688   if (!bridged_view_)
689     return false;
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.
694     SetDraggable(false);
695     return false;
696   }
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) ==
702       HTCAPTION;
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())
707       continue;
709     if (![subview mouseDownCanMoveWindow] &&
710         NSPointInRect(location_in_window, [subview frame])) {
711       should_move_window = false;
712       break;
713     }
714   }
716   if (!should_move_window)
717     return false;
719   // Make the window draggable, then return true to repost the event.
720   SetDraggable(true);
721   return true;
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_)
729     return;
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();
751   }
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,
763                                       bool translucent) {
764   DCHECK(bridged_view_);
765   DCHECK(!layer());
767   CreateCompositor();
768   DCHECK(compositor_);
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);
776   InitCompositor();
778   // Transparent window support.
779   layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
780   layer()->SetFillsBoundsOpaquely(!translucent);
781   if (translucent) {
782     [window_ setOpaque:NO];
783     [window_ setBackgroundColor:[NSColor clearColor]];
784   }
786   UpdateLayerProperties();
789 ////////////////////////////////////////////////////////////////////////////////
790 // BridgedNativeWidget, internal::InputMethodDelegate:
792 ui::EventDispatchDetails BridgedNativeWidget::DispatchKeyEventPostIME(
793     ui::KeyEvent* key) {
794   DCHECK(focus_manager_);
795   native_widget_mac_->GetWidget()->OnKeyEvent(key);
796   if (!key->handled()) {
797     if (!focus_manager_->OnKeyEvent(*key))
798       key->StopPropagation();
799   }
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,
818                                             View* focused_now) {
821 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
822                                            View* focused_now) {
823   ui::InputMethod* input_method =
824       native_widget_mac_->GetWidget()->GetInputMethod();
825   if (input_method) {
826     ui::TextInputClient* input_client = input_method->GetTextInputClient();
827     [bridged_view_ setTextInputClient:input_client];
828   }
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) {
841   NOTIMPLEMENTED();
844 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
845     float device_scale_factor) {
846   NOTIMPLEMENTED();
849 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
850   NOTIMPLEMENTED();
851   return base::Closure();
854 ////////////////////////////////////////////////////////////////////////////////
855 // BridgedNativeWidget, AcceleratedWidgetMac:
857 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
858   return compositor_superview_;
861 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
862   return true;
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()))
870     return;
872   if (invalidate_shadow_on_frame_swap_) {
873     invalidate_shadow_on_frame_swap_ = false;
874     [window_ invalidateShadow];
875   }
878 void BridgedNativeWidget::AcceleratedWidgetHitError() {
879   compositor_->ScheduleFullRedraw();
882 ////////////////////////////////////////////////////////////////////////////////
883 // BridgedNativeWidget, BridgedNativeWidgetOwner:
885 NSWindow* BridgedNativeWidget::GetNSWindow() {
886   return window_;
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()
895                  : window_visible_;
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]);
921     [child close];
922   }
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());
937     }
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]);
943     return;
944   }
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_) {
953       ++visible_children;
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_);
965     }
966     CHECK_EQ(child_count, child_windows_.size());
967   }
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(),
993                                        context_factory,
994                                        base::ThreadTaskRunnerHandle::Get()));
995   compositor_widget_->SetNSView(this);
998 void BridgedNativeWidget::InitCompositor() {
999   DCHECK(layer());
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() {
1008   if (layer()) {
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);
1016   }
1017   DestroyLayer();
1019   if (!compositor_widget_) {
1020     DCHECK(!compositor_);
1021     return;
1022   }
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];
1041   }
1043   base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
1044   [background_layer
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() {
1058   DCHECK(layer());
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);
1076   if (!properties) {
1077     properties = [NSMutableDictionary dictionary];
1078     objc_setAssociatedObject(window_, &kWindowPropertiesKey,
1079                              properties, OBJC_ASSOCIATION_RETAIN);
1080   }
1081   return properties;
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