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/cocoa_mouse_capture.h"
22 #import "ui/views/cocoa/bridged_content_view.h"
23 #import "ui/views/cocoa/views_nswindow_delegate.h"
24 #include "ui/views/widget/native_widget_mac.h"
25 #include "ui/views/ime/input_method_bridge.h"
26 #include "ui/views/ime/null_input_method.h"
27 #include "ui/views/view.h"
28 #include "ui/views/views_delegate.h"
29 #include "ui/views/widget/widget.h"
30 #include "ui/views/widget/widget_aura_utils.h"
31 #include "ui/views/widget/widget_delegate.h"
33 // The NSView that hosts the composited CALayer drawing the UI. It fills the
34 // window but is not hittable so that accessibility hit tests always go to the
35 // BridgedContentView.
36 @interface ViewsCompositorSuperview : NSView
39 @implementation ViewsCompositorSuperview
40 - (NSView*)hitTest:(NSPoint)aPoint {
47 int kWindowPropertiesKey;
49 float GetDeviceScaleFactorFromView(NSView* view) {
50 gfx::Display display =
51 gfx::Screen::GetScreenFor(view)->GetDisplayNearestWindow(view);
52 DCHECK(display.is_valid());
53 return display.device_scale_factor();
56 // Returns true if bounds passed to window in SetBounds should be treated as
57 // though they are in screen coordinates.
58 bool PositionWindowInScreenCoordinates(views::Widget* widget,
59 views::Widget::InitParams::Type type) {
60 // Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
61 if (views::GetAuraWindowTypeForWidgetType(type) == ui::wm::WINDOW_TYPE_POPUP)
64 return widget && widget->is_top_level();
71 BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent)
72 : native_widget_mac_(parent),
73 focus_manager_(nullptr),
74 widget_type_(Widget::InitParams::TYPE_WINDOW), // Updated in Init().
76 target_fullscreen_state_(false),
77 in_fullscreen_transition_(false),
78 window_visible_(false),
79 wants_to_be_visible_(false) {
81 window_delegate_.reset(
82 [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]);
85 BridgedNativeWidget::~BridgedNativeWidget() {
86 RemoveOrDestroyChildren();
87 DCHECK(child_windows_.empty());
88 SetFocusManager(NULL);
91 if ([window_ delegate]) {
92 // If the delegate is still set on a modal dialog, it means it was not
93 // closed via [NSApplication endSheet:]. This is probably OK if the widget
94 // was never shown. But Cocoa ignores close() calls on open sheets. Calling
95 // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
96 // DialogClientView assumes its delegate is alive when closing, which isn't
97 // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
98 // So ban it. Modal dialogs should be closed via Widget::Close().
99 DCHECK(!native_widget_mac_->GetWidget()->IsModal());
101 // If the delegate is still set, it means OnWindowWillClose has not been
102 // called and the window is still open. Calling -[NSWindow close] will
103 // synchronously call OnWindowWillClose and notify NativeWidgetMac.
106 DCHECK(![window_ delegate]);
109 void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
110 const Widget::InitParams& params) {
111 widget_type_ = params.type;
114 window_.swap(window);
115 [window_ setDelegate:window_delegate_];
117 // Register for application hide notifications so that visibility can be
118 // properly tracked. This is not done in the delegate so that the lifetime is
119 // tied to the C++ object, rather than the delegate (which may be reference
120 // counted). This is required since the application hides do not send an
121 // orderOut: to individual windows. Unhide, however, does send an order
123 [[NSNotificationCenter defaultCenter]
124 addObserver:window_delegate_
125 selector:@selector(onWindowOrderChanged:)
126 name:NSApplicationDidHideNotification
129 // Validate the window's initial state, otherwise the bridge's initial
130 // tracking state will be incorrect.
131 DCHECK(![window_ isVisible]);
132 DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask);
135 // Disallow creating child windows of views not currently in an NSWindow.
136 CHECK([params.parent window]);
137 BridgedNativeWidget* parent =
138 NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
139 // The parent could be an NSWindow without an associated Widget. That could
140 // work by observing NSWindowWillCloseNotification, but for now it's not
141 // supported, and there might not be a use-case for that.
144 parent->child_windows_.push_back(this);
147 // Set a meaningful initial bounds. Note that except for frameless widgets
148 // with no WidgetDelegate, the bounds will be set again by Widget after
149 // initializing the non-client view. In the former case, if bounds were not
150 // set at all, the creator of the Widget is expected to call SetBounds()
151 // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized
152 // (i.e. 1x1) window appearing.
153 if (!params.bounds.IsEmpty()) {
154 SetBounds(params.bounds);
156 // If a position is set, but no size, complain. Otherwise, a 1x1 window
157 // would appear there, which might be unexpected.
158 DCHECK(params.bounds.origin().IsOrigin())
159 << "Zero-sized windows not supported on Mac.";
161 // Otherwise, bounds is all zeroes. Cocoa will currently have the window at
162 // the bottom left of the screen. To support a client calling SetSize() only
163 // (and for consistency across platforms) put it at the top-left instead.
164 // Read back the current frame: it will be a 1x1 context rect but the frame
165 // size also depends on the window style.
166 NSRect frame_rect = [window_ frame];
167 SetBounds(gfx::Rect(gfx::Point(),
168 gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))));
171 // Widgets for UI controls (usually layered above web contents) start visible.
172 if (params.type == Widget::InitParams::TYPE_CONTROL)
173 SetVisibilityState(SHOW_INACTIVE);
176 void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) {
177 if (focus_manager_ == focus_manager)
181 focus_manager_->RemoveFocusChangeListener(this);
184 focus_manager->AddFocusChangeListener(this);
186 focus_manager_ = focus_manager;
189 void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
190 // A contentRect with zero width or height is a banned practice in ChromeMac,
191 // due to unpredictable OSX treatment.
192 DCHECK(!new_bounds.IsEmpty()) << "Zero-sized windows not supported on Mac";
194 if (native_widget_mac_->GetWidget()->IsModal()) {
195 // Modal dialogs are positioned by Cocoa. Just update the size.
197 setContentSize:NSMakeSize(new_bounds.width(), new_bounds.height())];
201 gfx::Rect actual_new_bounds(new_bounds);
204 !PositionWindowInScreenCoordinates(native_widget_mac_->GetWidget(),
206 actual_new_bounds.Offset(parent_->GetRestoredBounds().OffsetFromOrigin());
208 [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
213 void BridgedNativeWidget::SetRootView(views::View* view) {
214 if (view == [bridged_view_ hostedView])
217 // If this is ever false, the compositor will need to be properly torn down
218 // and replaced, pointing at the new view.
219 DCHECK(!view || !compositor_widget_);
221 [bridged_view_ clearView];
222 bridged_view_.reset();
223 // Note that there can still be references to the old |bridged_view_|
224 // floating around in Cocoa libraries at this point. However, references to
225 // the old views::View will be gone, so any method calls will become no-ops.
228 bridged_view_.reset([[BridgedContentView alloc] initWithView:view]);
229 // Objective C initializers can return nil. However, if |view| is non-NULL
230 // this should be treated as an error and caught early.
231 CHECK(bridged_view_);
233 [window_ setContentView:bridged_view_];
236 void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
238 // - A window with an invisible parent is not made visible.
239 // - A parent changing visibility updates child window visibility.
240 // * But only when changed via this function - ignore changes via the
241 // NSWindow API, or changes propagating out from here.
242 wants_to_be_visible_ = new_state != HIDE_WINDOW;
244 if (new_state == HIDE_WINDOW) {
245 [window_ orderOut:nil];
246 DCHECK(!window_visible_);
250 DCHECK(wants_to_be_visible_);
252 // If there's a hidden ancestor, return and wait for it to become visible.
253 for (BridgedNativeWidget* ancestor = parent();
255 ancestor = ancestor->parent()) {
256 if (!ancestor->window_visible_)
260 if (native_widget_mac_->GetWidget()->IsModal()) {
261 NSWindow* parent_window = parent_->ns_window();
262 DCHECK(parent_window);
264 [NSApp beginSheet:window_
265 modalForWindow:parent_window
266 modalDelegate:[window_ delegate]
267 didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
268 contextInfo:nullptr];
272 if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
273 [window_ makeKeyAndOrderFront:nil];
274 [NSApp activateIgnoringOtherApps:YES];
276 // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a
277 // parent window. So, if there's a parent, order above that. Otherwise, this
278 // will order above all windows at the same level.
279 NSInteger parent_window_number = 0;
281 parent_window_number = [parent()->ns_window() windowNumber];
283 [window_ orderWindow:NSWindowAbove
284 relativeTo:parent_window_number];
286 DCHECK(window_visible_);
289 void BridgedNativeWidget::AcquireCapture() {
290 DCHECK(!HasCapture());
291 if (!window_visible_)
292 return; // Capture on hidden windows is disallowed.
294 mouse_capture_.reset(new CocoaMouseCapture(this));
296 // Initiating global event capture with addGlobalMonitorForEventsMatchingMask:
297 // will reset the mouse cursor to an arrow. Asking the window for an update
298 // here will restore what we want. However, it can sometimes cause the cursor
299 // to flicker, once, on the initial mouseDown.
300 // TOOD(tapted): Make this unnecessary by only asking for global mouse capture
301 // for the cases that need it (e.g. menus, but not drag and drop).
302 [window_ cursorUpdate:[NSApp currentEvent]];
305 void BridgedNativeWidget::ReleaseCapture() {
306 mouse_capture_.reset();
309 bool BridgedNativeWidget::HasCapture() {
310 return mouse_capture_ && mouse_capture_->IsActive();
313 void BridgedNativeWidget::SetNativeWindowProperty(const char* name,
315 NSString* key = [NSString stringWithUTF8String:name];
317 [GetWindowProperties() setObject:[NSValue valueWithPointer:value]
320 [GetWindowProperties() removeObjectForKey:key];
324 void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const {
325 NSString* key = [NSString stringWithUTF8String:name];
326 return [[GetWindowProperties() objectForKey:key] pointerValue];
329 void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
330 [window_delegate_ setCursor:cursor];
333 void BridgedNativeWidget::OnWindowWillClose() {
335 parent_->RemoveChildWindow(this);
336 [window_ setDelegate:nil];
337 [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
338 native_widget_mac_->OnWindowWillClose();
341 void BridgedNativeWidget::OnFullscreenTransitionStart(
342 bool target_fullscreen_state) {
343 // Note: This can fail for fullscreen changes started externally, but a user
344 // shouldn't be able to do that if the window is invisible to begin with.
345 DCHECK(window_visible_);
347 DCHECK_NE(target_fullscreen_state, target_fullscreen_state_);
348 target_fullscreen_state_ = target_fullscreen_state;
349 in_fullscreen_transition_ = true;
351 // If going into fullscreen, store an answer for GetRestoredBounds().
352 if (target_fullscreen_state)
353 bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]);
356 void BridgedNativeWidget::OnFullscreenTransitionComplete(
357 bool actual_fullscreen_state) {
358 in_fullscreen_transition_ = false;
359 if (target_fullscreen_state_ == actual_fullscreen_state)
362 // First update to reflect reality so that OnTargetFullscreenStateChanged()
363 // expects the change.
364 target_fullscreen_state_ = actual_fullscreen_state;
365 ToggleDesiredFullscreenState();
367 // Usually ToggleDesiredFullscreenState() sets |in_fullscreen_transition_| via
368 // OnFullscreenTransitionStart(). When it does not, it means Cocoa ignored the
369 // toggleFullScreen: request. This can occur when the fullscreen transition
370 // fails and Cocoa is *about* to send windowDidFailToEnterFullScreen:.
371 // Annoyingly, for this case, Cocoa first sends windowDidExitFullScreen:.
372 if (in_fullscreen_transition_)
373 DCHECK_NE(target_fullscreen_state_, actual_fullscreen_state);
376 void BridgedNativeWidget::ToggleDesiredFullscreenState() {
377 // If there is currently an animation into or out of fullscreen, then AppKit
378 // emits the string "not in fullscreen state" to stdio and does nothing. For
379 // this case, schedule a transition back into the desired state when the
380 // animation completes.
381 if (in_fullscreen_transition_) {
382 target_fullscreen_state_ = !target_fullscreen_state_;
386 // Going fullscreen implicitly makes the window visible. AppKit does this.
387 // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow
388 // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls
389 // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an
390 // orderWindow message. So intercepting the implicit change is hard.
391 // Luckily, to trigger externally, the window typically needs to be visible in
392 // the first place. So we can just ensure the window is visible here instead
393 // of relying on AppKit to do it, and not worry that OnVisibilityChanged()
394 // won't be called for externally triggered fullscreen requests.
395 if (!window_visible_)
396 SetVisibilityState(SHOW_INACTIVE);
398 if (base::mac::IsOSSnowLeopard()) {
400 return; // TODO(tapted): Implement this for Snow Leopard.
403 // Since fullscreen requests are ignored if the collection behavior does not
404 // allow it, save the collection behavior and restore it after.
405 NSWindowCollectionBehavior behavior = [window_ collectionBehavior];
406 [window_ setCollectionBehavior:behavior |
407 NSWindowCollectionBehaviorFullScreenPrimary];
408 [window_ toggleFullScreen:nil];
409 [window_ setCollectionBehavior:behavior];
412 void BridgedNativeWidget::OnSizeChanged() {
413 gfx::Size new_size = GetClientAreaSize();
414 native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size);
416 UpdateLayerProperties();
419 void BridgedNativeWidget::OnVisibilityChanged() {
420 OnVisibilityChangedTo([window_ isVisible]);
423 void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
424 if (window_visible_ == new_visibility)
427 window_visible_ = new_visibility;
429 // If arriving via SetVisible(), |wants_to_be_visible_| should already be set.
430 // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet)
431 // to distinguish being *hidden* externally from being hidden by a parent
432 // window - we might not need that.
433 if (window_visible_) {
434 wants_to_be_visible_ = true;
437 [parent_->ns_window() addChildWindow:window_ ordered:NSWindowAbove];
439 mouse_capture_.reset(); // Capture on hidden windows is not permitted.
441 // When becoming invisible, remove the entry in any parent's childWindow
442 // list. Cocoa's childWindow management breaks down when child windows are
445 [parent_->ns_window() removeChildWindow:window_];
448 // TODO(tapted): Investigate whether we want this for Mac. This is what Aura
449 // does, and it is what tests expect. However, because layer drawing is
450 // asynchronous (and things like deminiaturize in AppKit are not), it can
451 // result in a CALayer appearing on screen before it has been redrawn in the
452 // GPU process. This is a general problem. In content, a helper class,
453 // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:]
454 // and RenderWidgetHostView::Show() until a frame is ready.
456 layer()->SetVisible(window_visible_);
457 layer()->SchedulePaint(gfx::Rect(GetClientAreaSize()));
460 NotifyVisibilityChangeDown();
462 native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(
465 // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking
466 // for an "empty" draw, disable auto-display while hidden. For example, this
467 // prevents Cocoa drawing just *after* a minimize, resulting in a blank window
468 // represented in the deminiaturize animation.
469 [window_ setAutodisplay:window_visible_];
472 void BridgedNativeWidget::OnBackingPropertiesChanged() {
474 UpdateLayerProperties();
477 void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
478 Widget* widget = native_widget_mac()->GetWidget();
479 widget->OnNativeWidgetActivationChanged(is_key);
480 // The contentView is the BridgedContentView hosting the views::RootView. The
481 // focus manager will already know if a native subview has focus.
482 if ([window_ contentView] == [window_ firstResponder]) {
484 widget->OnNativeFocus();
485 widget->GetFocusManager()->RestoreFocusedView();
487 widget->OnNativeBlur();
488 widget->GetFocusManager()->StoreFocusedView(true);
493 void BridgedNativeWidget::OnSizeConstraintsChanged() {
494 NSWindow* window = ns_window();
495 Widget* widget = native_widget_mac()->GetWidget();
496 gfx::Size min_size = widget->GetMinimumSize();
497 gfx::Size max_size = widget->GetMaximumSize();
498 bool is_resizable = widget->widget_delegate()->CanResize();
499 bool shows_resize_controls =
500 is_resizable && (min_size.IsEmpty() || min_size != max_size);
501 bool shows_fullscreen_controls =
502 is_resizable && widget->widget_delegate()->CanMaximize();
504 gfx::ApplyNSWindowSizeConstraints(window, min_size, max_size,
505 shows_resize_controls,
506 shows_fullscreen_controls);
509 InputMethod* BridgedNativeWidget::CreateInputMethod() {
510 if (switches::IsTextInputFocusManagerEnabled())
511 return new NullInputMethod();
513 return new InputMethodBridge(this, GetHostInputMethod(), true);
516 ui::InputMethod* BridgedNativeWidget::GetHostInputMethod() {
517 if (!input_method_) {
518 // Delegate is NULL because Mac IME does not need DispatchKeyEventPostIME
520 input_method_ = ui::CreateInputMethod(NULL, nil);
522 return input_method_.get();
525 gfx::Rect BridgedNativeWidget::GetRestoredBounds() const {
526 if (target_fullscreen_state_ || in_fullscreen_transition_)
527 return bounds_before_fullscreen_;
529 return gfx::ScreenRectFromNSRect([window_ frame]);
532 void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
534 DCHECK(bridged_view_);
540 SetLayer(new ui::Layer(layer_type));
541 // Note, except for controls, this will set the layer to be hidden, since it
542 // is only called during Init().
543 layer()->SetVisible(window_visible_);
544 layer()->set_delegate(this);
548 // Transparent window support.
549 layer()->GetCompositor()->SetHostHasTransparentBackground(translucent);
550 layer()->SetFillsBoundsOpaquely(!translucent);
552 [window_ setOpaque:NO];
553 [window_ setBackgroundColor:[NSColor clearColor]];
556 UpdateLayerProperties();
559 ////////////////////////////////////////////////////////////////////////////////
560 // BridgedNativeWidget, internal::InputMethodDelegate:
562 void BridgedNativeWidget::DispatchKeyEventPostIME(const ui::KeyEvent& key) {
563 DCHECK(focus_manager_);
564 native_widget_mac_->GetWidget()->OnKeyEvent(const_cast<ui::KeyEvent*>(&key));
566 focus_manager_->OnKeyEvent(key);
569 ////////////////////////////////////////////////////////////////////////////////
570 // BridgedNativeWidget, CocoaMouseCaptureDelegate:
572 void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) {
573 [bridged_view_ processCapturedMouseEvent:event];
576 void BridgedNativeWidget::OnMouseCaptureLost() {
577 native_widget_mac_->GetWidget()->OnMouseCaptureLost();
580 ////////////////////////////////////////////////////////////////////////////////
581 // BridgedNativeWidget, FocusChangeListener:
583 void BridgedNativeWidget::OnWillChangeFocus(View* focused_before,
587 void BridgedNativeWidget::OnDidChangeFocus(View* focused_before,
589 ui::TextInputClient* input_client =
590 focused_now ? focused_now->GetTextInputClient() : NULL;
591 [bridged_view_ setTextInputClient:input_client];
594 ////////////////////////////////////////////////////////////////////////////////
595 // BridgedNativeWidget, LayerDelegate:
597 void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) {
598 DCHECK(window_visible_);
599 native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context);
602 void BridgedNativeWidget::OnDelegatedFrameDamage(
603 const gfx::Rect& damage_rect_in_dip) {
607 void BridgedNativeWidget::OnDeviceScaleFactorChanged(
608 float device_scale_factor) {
612 base::Closure BridgedNativeWidget::PrepareForLayerBoundsChange() {
614 return base::Closure();
617 ////////////////////////////////////////////////////////////////////////////////
618 // BridgedNativeWidget, AcceleratedWidgetMac:
620 NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const {
621 return compositor_superview_;
624 bool BridgedNativeWidget::AcceleratedWidgetShouldIgnoreBackpressure() const {
628 void BridgedNativeWidget::AcceleratedWidgetSwapCompleted(
629 const std::vector<ui::LatencyInfo>& latency_info) {
632 void BridgedNativeWidget::AcceleratedWidgetHitError() {
633 compositor_->ScheduleFullRedraw();
636 ////////////////////////////////////////////////////////////////////////////////
637 // BridgedNativeWidget, private:
639 void BridgedNativeWidget::RemoveOrDestroyChildren() {
640 // TODO(tapted): Implement unowned child windows if required.
641 while (!child_windows_.empty()) {
642 // The NSWindow can only be destroyed after -[NSWindow close] is complete.
643 // Retain the window, otherwise the reference count can reach zero when the
644 // child calls back into RemoveChildWindow() via its OnWindowWillClose().
645 base::scoped_nsobject<NSWindow> child(
646 [child_windows_.back()->ns_window() retain]);
651 void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
652 auto location = std::find(
653 child_windows_.begin(), child_windows_.end(), child);
654 DCHECK(location != child_windows_.end());
655 child_windows_.erase(location);
656 child->parent_ = nullptr;
658 // Note the child is sometimes removed already by AppKit. This depends on OS
659 // version, and possibly some unpredictable reference counting. Removing it
660 // here should be safe regardless.
661 [window_ removeChildWindow:child->window_];
664 void BridgedNativeWidget::NotifyVisibilityChangeDown() {
665 // Child windows sometimes like to close themselves in response to visibility
666 // changes. That's supported, but only with the asynchronous Widget::Close().
667 // Perform a heuristic to detect child removal that would break these loops.
668 const size_t child_count = child_windows_.size();
669 if (!window_visible_) {
670 for (BridgedNativeWidget* child : child_windows_) {
671 if (child->window_visible_)
672 [child->ns_window() orderOut:nil];
674 DCHECK(!child->window_visible_);
675 CHECK_EQ(child_count, child_windows_.size());
677 // The orderOut calls above should result in a call to OnVisibilityChanged()
678 // in each child. There, children will remove themselves from the NSWindow
679 // childWindow list as well as propagate NotifyVisibilityChangeDown() calls
680 // to any children of their own.
681 DCHECK_EQ(0u, [[window_ childWindows] count]);
685 NSUInteger visible_children = 0; // For a DCHECK below.
686 NSInteger parent_window_number = [window_ windowNumber];
687 for (BridgedNativeWidget* child: child_windows_) {
688 // Note: order the child windows on top, regardless of whether or not they
689 // are currently visible. They probably aren't, since the parent was hidden
690 // prior to this, but they could have been made visible in other ways.
691 if (child->wants_to_be_visible_) {
693 // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on
694 // screen. However, that by itself is insufficient to guarantee a correct
695 // z-order relationship. If this function is being called from a z-order
696 // change in the parent, orderWindow turns out to be unreliable (i.e. the
697 // ordering doesn't always take effect). What this actually relies on is
698 // the resulting call to OnVisibilityChanged() in the child, which will
699 // then insert itself into -[NSWindow childWindows] to let Cocoa do its
700 // internal layering magic.
701 [child->ns_window() orderWindow:NSWindowAbove
702 relativeTo:parent_window_number];
703 DCHECK(child->window_visible_);
705 CHECK_EQ(child_count, child_windows_.size());
707 DCHECK_EQ(visible_children, [[window_ childWindows] count]);
710 gfx::Size BridgedNativeWidget::GetClientAreaSize() const {
711 NSRect content_rect = [window_ contentRectForFrameRect:[window_ frame]];
712 return gfx::Size(NSWidth(content_rect), NSHeight(content_rect));
715 void BridgedNativeWidget::CreateCompositor() {
716 DCHECK(!compositor_);
717 DCHECK(!compositor_widget_);
718 DCHECK(ViewsDelegate::views_delegate);
720 ui::ContextFactory* context_factory =
721 ViewsDelegate::views_delegate->GetContextFactory();
722 DCHECK(context_factory);
724 AddCompositorSuperview();
726 // TODO(tapted): Get this value from GpuDataManagerImpl via ViewsDelegate.
727 bool needs_gl_finish_workaround = false;
729 compositor_widget_.reset(
730 new ui::AcceleratedWidgetMac(needs_gl_finish_workaround));
731 compositor_.reset(new ui::Compositor(compositor_widget_->accelerated_widget(),
733 base::ThreadTaskRunnerHandle::Get()));
734 compositor_widget_->SetNSView(this);
737 void BridgedNativeWidget::InitCompositor() {
739 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
740 gfx::Size size_in_dip = GetClientAreaSize();
741 compositor_->SetScaleAndSize(scale_factor,
742 ConvertSizeToPixel(scale_factor, size_in_dip));
743 compositor_->SetRootLayer(layer());
746 void BridgedNativeWidget::DestroyCompositor() {
748 layer()->set_delegate(nullptr);
751 if (!compositor_widget_) {
752 DCHECK(!compositor_);
755 compositor_widget_->ResetNSView();
757 compositor_widget_.reset();
760 void BridgedNativeWidget::AddCompositorSuperview() {
761 DCHECK(!compositor_superview_);
762 compositor_superview_.reset(
763 [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]);
765 // Size and resize automatically with |bridged_view_|.
766 [compositor_superview_
767 setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
769 // Enable HiDPI backing when supported (only on 10.7+).
770 if ([compositor_superview_ respondsToSelector:
771 @selector(setWantsBestResolutionOpenGLSurface:)]) {
772 [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES];
775 base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
777 setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
779 // Set the layer first to create a layer-hosting view (not layer-backed).
780 [compositor_superview_ setLayer:background_layer];
781 [compositor_superview_ setWantsLayer:YES];
783 // The UI compositor should always be the first subview, to ensure webviews
784 // are drawn on top of it.
785 DCHECK_EQ(0u, [[bridged_view_ subviews] count]);
786 [bridged_view_ addSubview:compositor_superview_];
789 void BridgedNativeWidget::UpdateLayerProperties() {
791 DCHECK(compositor_superview_);
792 gfx::Size size_in_dip = GetClientAreaSize();
793 layer()->SetBounds(gfx::Rect(size_in_dip));
795 float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_);
796 compositor_->SetScaleAndSize(scale_factor,
797 ConvertSizeToPixel(scale_factor, size_in_dip));
800 NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const {
801 NSMutableDictionary* properties = objc_getAssociatedObject(
802 window_, &kWindowPropertiesKey);
804 properties = [NSMutableDictionary dictionary];
805 objc_setAssociatedObject(window_, &kWindowPropertiesKey,
806 properties, OBJC_ASSOCIATION_RETAIN);