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 #include "base/mac/mac_util.h"
11 #import "base/mac/sdk_forward_declarations.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "ui/base/ime/input_method.h"
14 #include "ui/base/ime/input_method_factory.h"
15 #include "ui/base/ui_base_switches_util.h"
16 #include "ui/gfx/display.h"
17 #include "ui/gfx/geometry/dip_util.h"
18 #import "ui/gfx/mac/coordinate_conversion.h"
19 #import "ui/gfx/mac/nswindow_frame_controls.h"
20 #include "ui/gfx/screen.h"
21 #import "ui/views/cocoa/bridged_content_view.h"
22 #import "ui/views/cocoa/cocoa_mouse_capture.h"
23 #include "ui/views/cocoa/tooltip_manager_mac.h"
24 #import "ui/views/cocoa/views_nswindow_delegate.h"
25 #import "ui/views/cocoa/widget_owner_nswindow_adapter.h"
26 #include "ui/views/ime/input_method_bridge.h"
27 #include "ui/views/ime/null_input_method.h"
28 #include "ui/views/view.h"
29 #include "ui/views/views_delegate.h"
30 #include "ui/views/widget/native_widget_mac.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/views/widget/widget_aura_utils.h"
33 #include "ui/views/widget/widget_delegate.h"
35 // The NSView that hosts the composited CALayer drawing the UI. It fills the
36 // window but is not hittable so that accessibility hit tests always go to the
37 // BridgedContentView.
38 @interface ViewsCompositorSuperview : NSView
41 @implementation ViewsCompositorSuperview
42 - (NSView*)hitTest:(NSPoint)aPoint {
49 int kWindowPropertiesKey;
51 float GetDeviceScaleFactorFromView(NSView* view) {
52 gfx::Display display =
53 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
54 DCHECK(display.is_valid());
55 return display.device_scale_factor();
58 // Returns true if bounds passed to window in SetBounds should be treated as
59 // though they are in screen coordinates.
60 bool PositionWindowInScreenCoordinates(views::Widget* widget,
61 views::Widget::InitParams::Type type) {
62 // Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
63 if (views::GetAuraWindowTypeForWidgetType(type) == ui::wm::WINDOW_TYPE_POPUP)
66 return widget && widget->is_top_level();
69 // Return the content size for a minimum or maximum widget size.
70 gfx::Size GetClientSizeForWindowSize(NSWindow* window,
71 const gfx::Size& window_size) {
73 NSMakeRect(0, 0, window_size.width(), window_size.height());
74 // Note gfx::Size will prevent dimensions going negative. They are allowed to
75 // be zero at this point, because Widget::GetMinimumSize() may later increase
77 return gfx::Size([window contentRectForFrameRect:frame_rect].size);
85 gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize(
87 const gfx::Size& content_size) {
89 NSMakeRect(0, 0, content_size.width(), content_size.height());
90 NSRect frame_rect = [window frameRectForContentRect:content_rect];
91 return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect));
94 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
95 : native_widget_mac_(parent),
96 focus_manager_(nullptr),
97 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
99 target_fullscreen_state_(false),
100 in_fullscreen_transition_(false),
101 window_visible_(false),
102 wants_to_be_visible_(false) {
104 window_delegate_.reset(
105 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
108 BridgedNativeWidget::~BridgedNativeWidget() {
109 RemoveOrDestroyChildren();
110 DCHECK(child_windows_.empty());
111 SetFocusManager(NULL);
114 if ([window_ delegate]) {
115 // If the delegate is still set on a modal dialog, it means it was not
116 // closed via [NSApplication endSheet:]. This is probably OK if the widget
117 // was never shown. But Cocoa ignores close() calls on open sheets. Calling
118 // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
119 // DialogClientView assumes its delegate is alive when closing, which isn't
120 // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
121 // So ban it. Modal dialogs should be closed via Widget::Close().
122 DCHECK(!native_widget_mac_->GetWidget()->IsModal());
124 // If the delegate is still set, it means OnWindowWillClose has not been
125 // called and the window is still open. Calling -[NSWindow close] will
126 // synchronously call OnWindowWillClose and notify NativeWidgetMac.
129 DCHECK(![window_ delegate]);
132 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
133 const Widget::InitParams& params) {
134 widget_type_ = params.type;
137 window_.swap(window);
138 [window_ setDelegate:window_delegate_];
140 // Register for application hide notifications so that visibility can be
141 // properly tracked. This is not done in the delegate so that the lifetime is
142 // tied to the C++ object, rather than the delegate (which may be reference
143 // counted). This is required since the application hides do not send an
144 // orderOut: to individual windows. Unhide, however, does send an order
146 [[NSNotificationCenter defaultCenter]
147 addObserver:window_delegate_
148 selector:@selector(onWindowOrderChanged:)
149 name:NSApplicationDidHideNotification
152 // Validate the window's initial state, otherwise the bridge's initial
153 // tracking state will be incorrect.
154 DCHECK(![window_ isVisible]);
155 DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
158 // Disallow creating child windows of views not currently in an NSWindow.
159 CHECK([params.parent window]);
160 BridgedNativeWidget* bridged_native_widget_parent =
161 NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
162 // If the parent is another BridgedNativeWidget, just add to the collection
163 // of child windows it owns and manages. Otherwise, create an adapter to
164 // anchor the child widget and observe when the parent NSWindow is closed.
165 if (bridged_native_widget_parent) {
166 parent_ = bridged_native_widget_parent;
167 bridged_native_widget_parent->child_windows_.push_back(this);
169 parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
173 // Set a meaningful initial bounds. Note that except for frameless widgets
174 // with no WidgetDelegate, the bounds will be set again by Widget after
175 // initializing the non-client view. In the former case, if bounds were not
176 // set at all, the creator of the Widget is expected to call SetBounds()
177 // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
178 // (i.e. 1x1) window appearing.
179 if (!params.bounds.IsEmpty()) {
180 SetBounds(params.bounds);
182 // If a position is set, but no size, complain. Otherwise, a 1x1 window
183 // would appear there, which might be unexpected.
184 DCHECK(params.bounds.origin().IsOrigin())
185 << "Zero-sized windows not supported on Mac.";
187 // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
188 // the bottom left of the screen. To support a client calling SetSize() only
189 // (and for consistency across platforms) put it at the top-left instead.
190 // Read back the current frame: it will be a 1x1 context rect but the frame
191 // size also depends on the window style.
192 NSRect frame_rect = [window_ frame];
193 SetBounds(gfx::Rect(gfx::Point(),
194 gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))));
197 // Widgets for UI controls (usually layered above web contents) start visible.
198 if (params.type == Widget::InitParams::TYPE_CONTROL)
199 SetVisibilityState(SHOW_INACTIVE);
201 // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are
202 // native on Mac, so nothing should ever want one in Widget form.
203 DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP);
204 tooltip_manager_.reset(new TooltipManagerMac(this));
207 void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
208 if (focus_manager_ == focus_manager)
212 focus_manager_->RemoveFocusChangeListener(this);
215 focus_manager->AddFocusChangeListener(this);
217 focus_manager_ = focus_manager;
220 void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
221 Widget* widget = native_widget_mac_->GetWidget();
222 // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated
223 // resizes. This is not what toolkit-views expects, so clamp. Note there is
224 // no check for maximum size (consistent with aura::Window::SetBounds()).
225 gfx::Size clamped_content_size =
226 GetClientSizeForWindowSize(window_, new_bounds.size());
227 clamped_content_size.SetToMax(widget->GetMinimumSize());
229 // A contentRect with zero width or height is a banned practice in ChromeMac,
230 // due to unpredictable OSX treatment.
231 DCHECK(!clamped_content_size.IsEmpty())
232 << "Zero-sized windows not supported on Mac";
234 if (!window_visible_ && widget->IsModal()) {
235 // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for
236 // the first time. They also have no frame, so just update the content size.
237 [window_ setContentSize:NSMakeSize(clamped_content_size.width(),
238 clamped_content_size.height())];
241 gfx::Rect actual_new_bounds(
243 GetWindowSizeForClientSize(window_, clamped_content_size));
245 if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_))
246 actual_new_bounds.Offset(parent_->GetChildWindowOffset());
248 [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
253 void BridgedNativeWidget::SetRootView(views::View* view) {
254 if (view == [bridged_view_ hostedView])
257 // If this is ever false, the compositor will need to be properly torn down
258 // and replaced, pointing at the new view.
259 DCHECK(!view || !compositor_widget_);
261 [bridged_view_ clearView];
262 bridged_view_.reset();
263 // Note that there can still be references to the old |bridged_view_|
264 // floating around in Cocoa libraries at this point. However, references to
265 // the old views::View will be gone, so any method calls will become no-ops.
268 bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
269 // Objective C initializers can return nil. However, if |view| is non-NULL
270 // this should be treated as an error and caught early.
271 CHECK(bridged_view_);
273 [window_ setContentView:bridged_view_];
276 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
278 // - A window with an invisible parent is not made visible.
279 // - A parent changing visibility updates child window visibility.
280 // * But only when changed via this function - ignore changes via the
281 // NSWindow API, or changes propagating out from here.
282 wants_to_be_visible_ = new_state != HIDE_WINDOW;
284 if (new_state == HIDE_WINDOW) {
285 [window_ orderOut:nil];
286 DCHECK(!window_visible_);
290 DCHECK(wants_to_be_visible_);
291 // If the parent (or an ancestor) is hidden, return and wait for it to become
293 if (parent() && !parent()->IsVisibleParent())
296 if (native_widget_mac_->GetWidget()->IsModal()) {
297 NSWindow* parent_window = parent_->GetNSWindow();
298 DCHECK(parent_window);
300 [NSApp beginSheet:window_
301 modalForWindow:parent_window
302 modalDelegate:[window_ delegate]
303 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
304 contextInfo:nullptr];
308 if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
309 [window_ makeKeyAndOrderFront:nil];
310 [NSApp activateIgnoringOtherApps:YES];
312 // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a
313 // parent window. So, if there's a parent, order above that. Otherwise, this
314 // will order above all windows at the same level.
315 NSInteger parent_window_number = 0;
317 parent_window_number = [parent_->GetNSWindow() windowNumber];
319 [window_ orderWindow:NSWindowAbove
320 relativeTo:parent_window_number];
322 DCHECK(window_visible_);
325 void BridgedNativeWidget::AcquireCapture() {
326 DCHECK(!HasCapture());
327 if (!window_visible_)
328 return; // Capture on hidden windows is disallowed.
330 mouse_capture_.reset(new CocoaMouseCapture(this));
332 // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
333 // will reset the mouse cursor to an arrow. Asking the window for an update
334 // here will restore what we want. However, it can sometimes cause the cursor
335 // to flicker, once, on the initial mouseDown.
336 // TOOD(tapted): Make this unnecessary by only asking for global mouse capture
337 // for the cases that need it (e.g. menus, but not drag and drop).
338 [window_ cursorUpdate:[NSApp currentEvent]];
341 void BridgedNativeWidget::ReleaseCapture() {
342 mouse_capture_.reset();
345 bool BridgedNativeWidget::HasCapture() {
346 return mouse_capture_ && mouse_capture_->IsActive();
349 void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
351 NSString* key = [NSString stringWithUTF8String:name];
353 [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
356 [GetWindowProperties() removeObjectForKey:key];
360 void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const {
361 NSString* key = [NSString stringWithUTF8String:name];
362 return [[GetWindowProperties() objectForKey:key] pointerValue];
365 void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
366 [window_delegate_ setCursor:cursor];
369 void BridgedNativeWidget::OnWindowWillClose() {
371 parent_->RemoveChildWindow(this);
374 [window_ setDelegate:nil];
375 [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
376 native_widget_mac_->OnWindowWillClose();
379 void BridgedNativeWidget::OnFullscreenTransitionStart(
380 bool target_fullscreen_state) {
381 // Note: This can fail for fullscreen changes started externally, but a user
382 // shouldn't be able to do that if the window is invisible to begin with.
383 DCHECK(window_visible_);
385 DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
386 target_fullscreen_state_ = target_fullscreen_state;
387 in_fullscreen_transition_ = true;
389 // If going into fullscreen, store an answer for GetRestoredBounds().
390 if (target_fullscreen_state)
391 bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
394 void BridgedNativeWidget::OnFullscreenTransitionComplete(
395 bool actual_fullscreen_state) {
396 in_fullscreen_transition_ = false;
398 if (target_fullscreen_state_ == actual_fullscreen_state) {
399 // Ensure constraints are re-applied when completing a transition.
400 OnSizeConstraintsChanged();
404 // First update to reflect reality so that OnTargetFullscreenStateChanged()
405 // expects the change.
406 target_fullscreen_state_ = actual_fullscreen_state;
407 ToggleDesiredFullscreenState();
409 // Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
410 // OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
411 // toggleFullScreen: request. This can occur when the fullscreen transition
412 // fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
413 // Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
414 if (in_fullscreen_transition_)
415 DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
418 void BridgedNativeWidget::ToggleDesiredFullscreenState() {
419 // If there is currently an animation into or out of fullscreen, then AppKit
420 // emits the string "not in fullscreen state" to stdio and does nothing. For
421 // this case, schedule a transition back into the desired state when the
422 // animation completes.
423 if (in_fullscreen_transition_) {
424 target_fullscreen_state_ = !target_fullscreen_state_;
428 // Going fullscreen implicitly makes the window visible. AppKit does this.
429 // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
430 // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
431 // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an
432 // orderWindow message. So intercepting the implicit change is hard.
433 // Luckily, to trigger externally, the window typically needs to be visible in
434 // the first place. So we can just ensure the window is visible here instead
435 // of relying on AppKit to do it, and not worry that OnVisibilityChanged()
436 // won't be called for externally triggered fullscreen requests.
437 if (!window_visible_)
438 SetVisibilityState(SHOW_INACTIVE);
440 if (base::mac::IsOSSnowLeopard()) {
442 return; // TODO(tapted): Implement this for Snow Leopard.
445 // Enable fullscreen collection behavior because:
446 // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored,
447 // 2: the fullscreen button must be enabled so the user can leave fullscreen.
448 // This will be reset when a transition out of fullscreen completes.
449 gfx::SetNSWindowCanFullscreen(window_, true);
451 [window_ toggleFullScreen:nil];
454 void BridgedNativeWidget::OnSizeChanged() {
455 gfx::Size new_size = GetClientAreaSize();
456 native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size);
458 UpdateLayerProperties();
461 void BridgedNativeWidget::OnVisibilityChanged() {
462 OnVisibilityChangedTo([window_ isVisible]);
465 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
466 if (window_visible_ == new_visibility)
469 window_visible_ = new_visibility;
471 // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
472 // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
473 // to distinguish being *hidden* externally from being hidden by a parent
474 // window - we might not need that.
475 if (window_visible_) {
476 wants_to_be_visible_ = true;
479 [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
481 mouse_capture_.reset(); // Capture on hidden windows is not permitted.
483 // When becoming invisible, remove the entry in any parent's childWindow
484 // list. Cocoa's childWindow management breaks down when child windows are
487 [parent_->GetNSWindow() removeChildWindow:window_];
490 // TODO(tapted): Investigate whether we want this for Mac. This is what Aura
491 // does, and it is what tests expect. However, because layer drawing is
492 // asynchronous (and things like deminiaturize in AppKit are not), it can
493 // result in a CALayer appearing on screen before it has been redrawn in the
494 // GPU process. This is a general problem. In content, a helper class,
495 // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:]
496 // and RenderWidgetHostView::Show() until a frame is ready.
498 layer()->SetVisible(window_visible_);
499 layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
502 NotifyVisibilityChangeDown();
504 native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
507 // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking
508 // for an "empty" draw, disable auto-display while hidden. For example, this
509 // prevents Cocoa drawing just *after* a minimize, resulting in a blank window
510 // represented in the deminiaturize animation.
511 [window_ setAutodisplay:window_visible_];
514 void BridgedNativeWidget::OnBackingPropertiesChanged() {
516 UpdateLayerProperties();
519 void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
520 Widget* widget = native_widget_mac()->GetWidget();
521 widget->OnNativeWidgetActivationChanged(is_key);
522 // The contentView is the BridgedContentView hosting the views::RootView. The
523 // focus manager will already know if a native subview has focus.
524 if ([window_ contentView] == [window_ firstResponder]) {
526 widget->OnNativeFocus();
527 widget->GetFocusManager()->RestoreFocusedView();
529 widget->OnNativeBlur();
530 widget->GetFocusManager()->StoreFocusedView(true);
535 void BridgedNativeWidget::OnSizeConstraintsChanged() {
536 // Don't modify the size constraints or fullscreen collection behavior while
537 // in fullscreen or during a transition. OnFullscreenTransitionComplete will
538 // reset these after leaving fullscreen.
539 if (target_fullscreen_state_ || in_fullscreen_transition_)
542 Widget* widget = native_widget_mac()->GetWidget();
543 gfx::Size min_size = widget->GetMinimumSize();
544 gfx::Size max_size = widget->GetMaximumSize();
545 bool is_resizable = widget->widget_delegate()->CanResize();
546 bool shows_resize_controls =
547 is_resizable && (min_size.IsEmpty() || min_size != max_size);
548 bool shows_fullscreen_controls =
549 is_resizable && widget->widget_delegate()->CanMaximize();
551 gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size,
552 shows_resize_controls,
553 shows_fullscreen_controls);
556 InputMethod* BridgedNativeWidget::CreateInputMethod() {
557 if (switches::IsTextInputFocusManagerEnabled())
558 return new NullInputMethod();
560 return new InputMethodBridge(this, GetHostInputMethod(), true);
563 ui::InputMethod* BridgedNativeWidget::GetHostInputMethod() {
564 if (!input_method_) {
565 // Delegate is NULL because Mac IME does not need DispatchKeyEventPostIME
567 input_method_ = ui::CreateInputMethod(NULL, nil);
569 return input_method_.get();
572 gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
573 if (target_fullscreen_state_ || in_fullscreen_transition_)
574 return bounds_before_fullscreen_;
576 return gfx::ScreenRectFromNSRect([window_ frame]);
579 void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
581 DCHECK(bridged_view_);
587 SetLayer(new ui::Layer(layer_type));
588 // Note, except for controls, this will set the layer to be hidden, since it
589 // is only called during Init().
590 layer()->SetVisible(window_visible_);
591 layer()->set_delegate(this);
595 // Transparent window support.
596 layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
597 layer()->SetFillsBoundsOpaquely(!translucent);
599 [window_ setOpaque:NO];
600 [window_ setBackgroundColor:[NSColor clearColor]];
603 UpdateLayerProperties();
606 ////////////////////////////////////////////////////////////////////////////////
607 // BridgedNativeWidget, internal::InputMethodDelegate:
609 void BridgedNativeWidget::DispatchKeyEventPostIME(const ui::KeyEvent& key) {
610 DCHECK(focus_manager_);
611 native_widget_mac_->GetWidget()->OnKeyEvent(const_cast<ui::KeyEvent*>(&key));
613 focus_manager_->OnKeyEvent(key);
616 ////////////////////////////////////////////////////////////////////////////////
617 // BridgedNativeWidget, CocoaMouseCaptureDelegate:
619 void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) {
620 [bridged_view_ processCapturedMouseEvent:event];
623 void BridgedNativeWidget::OnMouseCaptureLost() {
624 native_widget_mac_->GetWidget()->OnMouseCaptureLost();
627 ////////////////////////////////////////////////////////////////////////////////
628 // BridgedNativeWidget, FocusChangeListener:
630 void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
634 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
636 ui::TextInputClient* input_client =
637 focused_now ? focused_now->GetTextInputClient() : NULL;
638 [bridged_view_ setTextInputClient:input_client];
641 ////////////////////////////////////////////////////////////////////////////////
642 // BridgedNativeWidget, LayerDelegate:
644 void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) {
645 DCHECK(window_visible_);
646 native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context);
649 void BridgedNativeWidget::OnDelegatedFrameDamage(
650 const gfx::Rect& damage_rect_in_dip) {
654 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
655 float device_scale_factor) {
659 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
661 return base::Closure();
664 ////////////////////////////////////////////////////////////////////////////////
665 // BridgedNativeWidget, AcceleratedWidgetMac:
667 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
668 return compositor_superview_;
671 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
675 void BridgedNativeWidget::AcceleratedWidgetSwapCompleted(
676 const std::vector<ui::LatencyInfo>& latency_info) {
679 void BridgedNativeWidget::AcceleratedWidgetHitError() {
680 compositor_->ScheduleFullRedraw();
683 ////////////////////////////////////////////////////////////////////////////////
684 // BridgedNativeWidget, BridgedNativeWidgetOwner:
686 NSWindow* BridgedNativeWidget::GetNSWindow() {
690 gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const {
691 return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin();
694 bool BridgedNativeWidget::IsVisibleParent() const {
695 return parent_ ? window_visible_ && parent_->IsVisibleParent()
699 void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
700 auto location = std::find(
701 child_windows_.begin(), child_windows_.end(), child);
702 DCHECK(location != child_windows_.end());
703 child_windows_.erase(location);
705 // Note the child is sometimes removed already by AppKit. This depends on OS
706 // version, and possibly some unpredictable reference counting. Removing it
707 // here should be safe regardless.
708 [window_ removeChildWindow:child->window_];
711 ////////////////////////////////////////////////////////////////////////////////
712 // BridgedNativeWidget, private:
714 void BridgedNativeWidget::RemoveOrDestroyChildren() {
715 // TODO(tapted): Implement unowned child windows if required.
716 while (!child_windows_.empty()) {
717 // The NSWindow can only be destroyed after -[NSWindow close] is complete.
718 // Retain the window, otherwise the reference count can reach zero when the
719 // child calls back into RemoveChildWindow() via its OnWindowWillClose().
720 base::scoped_nsobject<NSWindow> child(
721 [child_windows_.back()->ns_window() retain]);
726 void BridgedNativeWidget::NotifyVisibilityChangeDown() {
727 // Child windows sometimes like to close themselves in response to visibility
728 // changes. That's supported, but only with the asynchronous Widget::Close().
729 // Perform a heuristic to detect child removal that would break these loops.
730 const size_t child_count = child_windows_.size();
731 if (!window_visible_) {
732 for (BridgedNativeWidget* child : child_windows_) {
733 if (child->window_visible_)
734 [child->ns_window() orderOut:nil];
736 DCHECK(!child->window_visible_);
737 CHECK_EQ(child_count, child_windows_.size());
739 // The orderOut calls above should result in a call to OnVisibilityChanged()
740 // in each child. There, children will remove themselves from the NSWindow
741 // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
742 // to any children of their own.
743 DCHECK_EQ(0u, [[window_ childWindows] count]);
747 NSUInteger visible_children = 0; // For a DCHECK below.
748 NSInteger parent_window_number = [window_ windowNumber];
749 for (BridgedNativeWidget* child: child_windows_) {
750 // Note: order the child windows on top, regardless of whether or not they
751 // are currently visible. They probably aren't, since the parent was hidden
752 // prior to this, but they could have been made visible in other ways.
753 if (child->wants_to_be_visible_) {
755 // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
756 // screen. However, that by itself is insufficient to guarantee a correct
757 // z-order relationship. If this function is being called from a z-order
758 // change in the parent, orderWindow turns out to be unreliable (i.e. the
759 // ordering doesn't always take effect). What this actually relies on is
760 // the resulting call to OnVisibilityChanged() in the child, which will
761 // then insert itself into -[NSWindow childWindows] to let Cocoa do its
762 // internal layering magic.
763 [child->ns_window() orderWindow:NSWindowAbove
764 relativeTo:parent_window_number];
765 DCHECK(child->window_visible_);
767 CHECK_EQ(child_count, child_windows_.size());
769 DCHECK_EQ(visible_children, [[window_ childWindows] count]);
772 gfx::Size BridgedNativeWidget::GetClientAreaSize() const {
773 NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]];
774 return gfx::Size(NSWidth(content_rect), NSHeight(content_rect));
777 void BridgedNativeWidget::CreateCompositor() {
778 DCHECK(!compositor_);
779 DCHECK(!compositor_widget_);
780 DCHECK(ViewsDelegate::views_delegate);
782 ui::ContextFactory* context_factory =
783 ViewsDelegate::views_delegate->GetContextFactory();
784 DCHECK(context_factory);
786 AddCompositorSuperview();
788 // TODO(tapted): Get this value from GpuDataManagerImpl via ViewsDelegate.
789 bool needs_gl_finish_workaround = false;
791 compositor_widget_.reset(
792 new ui::AcceleratedWidgetMac(needs_gl_finish_workaround));
793 compositor_.reset(new ui::Compositor(compositor_widget_->accelerated_widget(),
795 base::ThreadTaskRunnerHandle::Get()));
796 compositor_widget_->SetNSView(this);
799 void BridgedNativeWidget::InitCompositor() {
801 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
802 gfx::Size size_in_dip = GetClientAreaSize();
803 compositor_->SetScaleAndSize(scale_factor,
804 ConvertSizeToPixel(scale_factor, size_in_dip));
805 compositor_->SetRootLayer(layer());
808 void BridgedNativeWidget::DestroyCompositor() {
810 layer()->set_delegate(nullptr);
813 if (!compositor_widget_) {
814 DCHECK(!compositor_);
817 compositor_widget_->ResetNSView();
819 compositor_widget_.reset();
822 void BridgedNativeWidget::AddCompositorSuperview() {
823 DCHECK(!compositor_superview_);
824 compositor_superview_.reset(
825 [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
827 // Size and resize automatically with |bridged_view_|.
828 [compositor_superview_
829 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
831 // Enable HiDPI backing when supported (only on 10.7+).
832 if ([compositor_superview_ respondsToSelector:
833 @selector(setWantsBestResolutionOpenGLSurface:)]) {
834 [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES];
837 base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
839 setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
841 // Set the layer first to create a layer-hosting view (not layer-backed).
842 [compositor_superview_ setLayer:background_layer];
843 [compositor_superview_ setWantsLayer:YES];
845 // The UI compositor should always be the first subview, to ensure webviews
846 // are drawn on top of it.
847 DCHECK_EQ(0u, [[bridged_view_ subviews] count]);
848 [bridged_view_ addSubview:compositor_superview_];
851 void BridgedNativeWidget::UpdateLayerProperties() {
853 DCHECK(compositor_superview_);
854 gfx::Size size_in_dip = GetClientAreaSize();
855 layer()->SetBounds(gfx::Rect(size_in_dip));
857 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
858 compositor_->SetScaleAndSize(scale_factor,
859 ConvertSizeToPixel(scale_factor, size_in_dip));
862 NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
863 NSMutableDictionary* properties = objc_getAssociatedObject(
864 window_, &kWindowPropertiesKey);
866 properties = [NSMutableDictionary dictionary];
867 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
868 properties, OBJC_ASSOCIATION_RETAIN);