summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortapted <tapted@chromium.org>2015-05-10 22:59:04 -0700
committerCommit bot <commit-bot@chromium.org>2015-05-11 05:59:24 +0000
commitadc5eefaf1611f00b4b3482baa07bb10a335c681 (patch)
treee926ae50dc684ce3711b085b2af4353429e87104
parentb3adff38ce4a7a85d2ee5e08ce921f445a875f85 (diff)
downloadchromium_src-adc5eefaf1611f00b4b3482baa07bb10a335c681.zip
chromium_src-adc5eefaf1611f00b4b3482baa07bb10a335c681.tar.gz
chromium_src-adc5eefaf1611f00b4b3482baa07bb10a335c681.tar.bz2
MacViews: Allow views::Widgets to be parented off NSWindows
Currently, a views::Widget on Mac can't be owned by a NSWindow that is not also a views::Widget. This would mean that to add a toolkit-views dialog parented to the browser, the whole browser window would need to be ported to views first. This CL abstracts the responsibilities of a NativeViewMac's parent window into a BridgedNativeWidgetOwner interface which BridgedNativeWidget implements to retain the current behaviour. WidgetOwnerNSWindowAdapter is a lightweight class that implements this to allow the parent to be any native NSView. The parent window closing will also close the child, and (for appropriate child types) the child window frame is offset by the parent view's position on screen. BUG=447086 TEST=views_unittests' NativeWidgetMacTest.NonWidgetParent Review URL: https://codereview.chromium.org/1063933003 Cr-Commit-Position: refs/heads/master@{#329097}
-rw-r--r--ui/views/cocoa/bridged_native_widget.h17
-rw-r--r--ui/views/cocoa/bridged_native_widget.mm85
-rw-r--r--ui/views/cocoa/bridged_native_widget_owner.h44
-rw-r--r--ui/views/cocoa/widget_owner_nswindow_adapter.h47
-rw-r--r--ui/views/cocoa/widget_owner_nswindow_adapter.mm93
-rw-r--r--ui/views/views.gyp3
-rw-r--r--ui/views/widget/native_widget_mac.mm12
-rw-r--r--ui/views/widget/native_widget_mac_unittest.mm62
8 files changed, 317 insertions, 46 deletions
diff --git a/ui/views/cocoa/bridged_native_widget.h b/ui/views/cocoa/bridged_native_widget.h
index 149c537..badda95 100644
--- a/ui/views/cocoa/bridged_native_widget.h
+++ b/ui/views/cocoa/bridged_native_widget.h
@@ -12,6 +12,7 @@
#include "base/memory/scoped_ptr.h"
#include "ui/compositor/layer_owner.h"
#import "ui/accelerated_widget_mac/accelerated_widget_mac.h"
+#import "ui/views/cocoa/bridged_native_widget_owner.h"
#import "ui/views/cocoa/cocoa_mouse_capture_delegate.h"
#import "ui/views/focus/focus_manager.h"
#include "ui/views/ime/input_method_delegate.h"
@@ -40,7 +41,8 @@ class VIEWS_EXPORT BridgedNativeWidget : public ui::LayerDelegate,
public internal::InputMethodDelegate,
public CocoaMouseCaptureDelegate,
public FocusChangeListener,
- public ui::AcceleratedWidgetMacNSView {
+ public ui::AcceleratedWidgetMacNSView,
+ public BridgedNativeWidgetOwner {
public:
// Ways of changing the visibility of the bridged NSWindow.
enum WindowVisibilityState {
@@ -151,7 +153,7 @@ class VIEWS_EXPORT BridgedNativeWidget : public ui::LayerDelegate,
// The parent widget specified in Widget::InitParams::parent. If non-null, the
// parent will close children before the parent closes, and children will be
// raised above their parent when window z-order changes.
- BridgedNativeWidget* parent() { return parent_; }
+ BridgedNativeWidgetOwner* parent() { return parent_; }
const std::vector<BridgedNativeWidget*>& child_windows() {
return child_windows_;
}
@@ -166,9 +168,6 @@ class VIEWS_EXPORT BridgedNativeWidget : public ui::LayerDelegate,
// Closes all child windows. BridgedNativeWidget children will be destroyed.
void RemoveOrDestroyChildren();
- // Remove the given |child| from |child_windows_|.
- void RemoveChildWindow(BridgedNativeWidget* child);
-
// Notify descendants of a visibility change.
void NotifyVisibilityChangeDown();
@@ -217,6 +216,12 @@ class VIEWS_EXPORT BridgedNativeWidget : public ui::LayerDelegate,
const std::vector<ui::LatencyInfo>& latency_info) override;
void AcceleratedWidgetHitError() override;
+ // Overridden from BridgedNativeWidgetOwner:
+ NSWindow* GetNSWindow() override;
+ gfx::Vector2d GetChildWindowOffset() const override;
+ bool IsVisibleParent() const override;
+ void RemoveChildWindow(BridgedNativeWidget* child) override;
+
views::NativeWidgetMac* native_widget_mac_; // Weak. Owns this.
base::scoped_nsobject<NSWindow> window_;
base::scoped_nsobject<ViewsNSWindowDelegate> window_delegate_;
@@ -226,7 +231,7 @@ class VIEWS_EXPORT BridgedNativeWidget : public ui::LayerDelegate,
FocusManager* focus_manager_; // Weak. Owned by our Widget.
Widget::InitParams::Type widget_type_;
- BridgedNativeWidget* parent_; // Weak. If non-null, owns this.
+ BridgedNativeWidgetOwner* parent_; // Weak. If non-null, owns this.
std::vector<BridgedNativeWidget*> child_windows_;
base::scoped_nsobject<NSView> compositor_superview_;
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index 2427578..107896f 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -21,6 +21,7 @@
#import "ui/views/cocoa/cocoa_mouse_capture.h"
#import "ui/views/cocoa/bridged_content_view.h"
#import "ui/views/cocoa/views_nswindow_delegate.h"
+#import "ui/views/cocoa/widget_owner_nswindow_adapter.h"
#include "ui/views/widget/native_widget_mac.h"
#include "ui/views/ime/input_method_bridge.h"
#include "ui/views/ime/null_input_method.h"
@@ -155,14 +156,17 @@ void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window,
if (params.parent) {
// Disallow creating child windows of views not currently in an NSWindow.
CHECK([params.parent window]);
- BridgedNativeWidget* parent =
+ BridgedNativeWidget* bridged_native_widget_parent =
NativeWidgetMac::GetBridgeForNativeWindow([params.parent window]);
- // The parent could be an NSWindow without an associated Widget. That could
- // work by observing NSWindowWillCloseNotification, but for now it's not
- // supported, and there might not be a use-case for that.
- CHECK(parent);
- parent_ = parent;
- parent->child_windows_.push_back(this);
+ // If the parent is another BridgedNativeWidget, just add to the collection
+ // of child windows it owns and manages. Otherwise, create an adapter to
+ // anchor the child widget and observe when the parent NSWindow is closed.
+ if (bridged_native_widget_parent) {
+ parent_ = bridged_native_widget_parent;
+ bridged_native_widget_parent->child_windows_.push_back(this);
+ } else {
+ parent_ = new WidgetOwnerNSWindowAdapter(this, params.parent);
+ }
}
// Set a meaningful initial bounds. Note that except for frameless widgets
@@ -233,7 +237,7 @@ void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
GetWindowSizeForClientSize(window_, clamped_content_size));
if (parent_ && !PositionWindowInScreenCoordinates(widget, widget_type_))
- actual_new_bounds.Offset(parent_->GetRestoredBounds().OffsetFromOrigin());
+ actual_new_bounds.Offset(parent_->GetChildWindowOffset());
[window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds)
display:YES
@@ -278,17 +282,13 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
}
DCHECK(wants_to_be_visible_);
-
- // If there's a hidden ancestor, return and wait for it to become visible.
- for (BridgedNativeWidget* ancestor = parent();
- ancestor;
- ancestor = ancestor->parent()) {
- if (!ancestor->window_visible_)
- return;
- }
+ // If the parent (or an ancestor) is hidden, return and wait for it to become
+ // visible.
+ if (parent() && !parent()->IsVisibleParent())
+ return;
if (native_widget_mac_->GetWidget()->IsModal()) {
- NSWindow* parent_window = parent_->ns_window();
+ NSWindow* parent_window = parent_->GetNSWindow();
DCHECK(parent_window);
[NSApp beginSheet:window_
@@ -307,8 +307,8 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
// parent window. So, if there's a parent, order above that. Otherwise, this
// will order above all windows at the same level.
NSInteger parent_window_number = 0;
- if (parent())
- parent_window_number = [parent()->ns_window() windowNumber];
+ if (parent_)
+ parent_window_number = [parent_->GetNSWindow() windowNumber];
[window_ orderWindow:NSWindowAbove
relativeTo:parent_window_number];
@@ -361,8 +361,10 @@ void BridgedNativeWidget::SetCursor(NSCursor* cursor) {
}
void BridgedNativeWidget::OnWindowWillClose() {
- if (parent_)
+ if (parent_) {
parent_->RemoveChildWindow(this);
+ parent_ = nullptr;
+ }
[window_ setDelegate:nil];
[[NSNotificationCenter defaultCenter] removeObserver:window_delegate_];
native_widget_mac_->OnWindowWillClose();
@@ -464,7 +466,7 @@ void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
wants_to_be_visible_ = true;
if (parent_)
- [parent_->ns_window() addChildWindow:window_ ordered:NSWindowAbove];
+ [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove];
} else {
mouse_capture_.reset(); // Capture on hidden windows is not permitted.
@@ -472,7 +474,7 @@ void BridgedNativeWidget::OnVisibilityChangedTo(bool new_visibility) {
// list. Cocoa's childWindow management breaks down when child windows are
// hidden.
if (parent_)
- [parent_->ns_window() removeChildWindow:window_];
+ [parent_->GetNSWindow() removeChildWindow:window_];
}
// TODO(tapted): Investigate whether we want this for Mac. This is what Aura
@@ -664,18 +666,19 @@ void BridgedNativeWidget::AcceleratedWidgetHitError() {
}
////////////////////////////////////////////////////////////////////////////////
-// BridgedNativeWidget, private:
+// BridgedNativeWidget, BridgedNativeWidgetOwner:
-void BridgedNativeWidget::RemoveOrDestroyChildren() {
- // TODO(tapted): Implement unowned child windows if required.
- while (!child_windows_.empty()) {
- // The NSWindow can only be destroyed after -[NSWindow close] is complete.
- // Retain the window, otherwise the reference count can reach zero when the
- // child calls back into RemoveChildWindow() via its OnWindowWillClose().
- base::scoped_nsobject<NSWindow> child(
- [child_windows_.back()->ns_window() retain]);
- [child close];
- }
+NSWindow* BridgedNativeWidget::GetNSWindow() {
+ return window_;
+}
+
+gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const {
+ return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin();
+}
+
+bool BridgedNativeWidget::IsVisibleParent() const {
+ return parent_ ? window_visible_ && parent_->IsVisibleParent()
+ : window_visible_;
}
void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
@@ -683,7 +686,6 @@ void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
child_windows_.begin(), child_windows_.end(), child);
DCHECK(location != child_windows_.end());
child_windows_.erase(location);
- child->parent_ = nullptr;
// Note the child is sometimes removed already by AppKit. This depends on OS
// version, and possibly some unpredictable reference counting. Removing it
@@ -691,6 +693,21 @@ void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) {
[window_ removeChildWindow:child->window_];
}
+////////////////////////////////////////////////////////////////////////////////
+// BridgedNativeWidget, private:
+
+void BridgedNativeWidget::RemoveOrDestroyChildren() {
+ // TODO(tapted): Implement unowned child windows if required.
+ while (!child_windows_.empty()) {
+ // The NSWindow can only be destroyed after -[NSWindow close] is complete.
+ // Retain the window, otherwise the reference count can reach zero when the
+ // child calls back into RemoveChildWindow() via its OnWindowWillClose().
+ base::scoped_nsobject<NSWindow> child(
+ [child_windows_.back()->ns_window() retain]);
+ [child close];
+ }
+}
+
void BridgedNativeWidget::NotifyVisibilityChangeDown() {
// Child windows sometimes like to close themselves in response to visibility
// changes. That's supported, but only with the asynchronous Widget::Close().
diff --git a/ui/views/cocoa/bridged_native_widget_owner.h b/ui/views/cocoa/bridged_native_widget_owner.h
new file mode 100644
index 0000000..9869ed5
--- /dev/null
+++ b/ui/views/cocoa/bridged_native_widget_owner.h
@@ -0,0 +1,44 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_
+#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_
+
+namespace gfx {
+class Vector2d;
+}
+
+@class NSWindow;
+
+namespace views {
+
+class BridgedNativeWidget;
+
+// An abstract interface wrapping an NSWindow that ties the lifetime of one or
+// more child BridgedNativeWidgets to the lifetime of that NSWindow. This is not
+// simply an NSWindow, because the child window API provided by NSWindow
+// requires child windows to always be visible.
+class BridgedNativeWidgetOwner {
+ public:
+ // The NSWindow parent.
+ virtual NSWindow* GetNSWindow() = 0;
+
+ // The offset in screen pixels for positioning child windows owned by |this|.
+ virtual gfx::Vector2d GetChildWindowOffset() const = 0;
+
+ // Return false if |this| is hidden, or has a hidden ancestor.
+ virtual bool IsVisibleParent() const = 0;
+
+ // Removes a child window. Note |this| may be deleted after calling, so the
+ // caller should immediately null out the pointer used to make the call.
+ virtual void RemoveChildWindow(BridgedNativeWidget* child) = 0;
+
+ protected:
+ // Instances of this class may be self-deleting.
+ virtual ~BridgedNativeWidgetOwner() {}
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_
diff --git a/ui/views/cocoa/widget_owner_nswindow_adapter.h b/ui/views/cocoa/widget_owner_nswindow_adapter.h
new file mode 100644
index 0000000..6b10e39
--- /dev/null
+++ b/ui/views/cocoa/widget_owner_nswindow_adapter.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_
+#define UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_
+
+#import "base/mac/scoped_nsobject.h"
+#import "ui/views/cocoa/bridged_native_widget_owner.h"
+
+@class NSView;
+@class WidgetOwnerNSWindowAdapterBridge;
+
+namespace views {
+
+// An adapter that allows a views::Widget to be owned by an NSWindow that is not
+// backed by another BridgedNativeWidget.
+class WidgetOwnerNSWindowAdapter : public BridgedNativeWidgetOwner {
+ public:
+ // Create an adapter that will own |child|, tying its lifetime with the
+ // NSWindow containing |anchor_view|. The object is self-deleting, via a call
+ // to RemoveChildWindow() made in child->OnWindowWillClose().
+ WidgetOwnerNSWindowAdapter(BridgedNativeWidget* child, NSView* anchor_view);
+
+ // Called when the owning window is closing.
+ void OnWindowWillClose();
+
+ // Overridden from BridgedNativeWidgetOwner:
+ NSWindow* GetNSWindow() override;
+ gfx::Vector2d GetChildWindowOffset() const override;
+ bool IsVisibleParent() const override;
+ void RemoveChildWindow(BridgedNativeWidget* child) override;
+
+ private:
+ // Self-deleting.
+ ~WidgetOwnerNSWindowAdapter() override;
+
+ BridgedNativeWidget* child_; // Weak. Owned by its NativeWidgetMac.
+ base::scoped_nsobject<NSView> anchor_view_;
+ base::scoped_nsobject<WidgetOwnerNSWindowAdapterBridge> observer_bridge_;
+
+ DISALLOW_COPY_AND_ASSIGN(WidgetOwnerNSWindowAdapter);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_
diff --git a/ui/views/cocoa/widget_owner_nswindow_adapter.mm b/ui/views/cocoa/widget_owner_nswindow_adapter.mm
new file mode 100644
index 0000000..e3939fd
--- /dev/null
+++ b/ui/views/cocoa/widget_owner_nswindow_adapter.mm
@@ -0,0 +1,93 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ui/views/cocoa/widget_owner_nswindow_adapter.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+#import "ui/gfx/mac/coordinate_conversion.h"
+#import "ui/views/cocoa/bridged_native_widget.h"
+
+// Bridges an AppKit observer to observe when the (non-views) NSWindow owning a
+// views::Widget will close.
+@interface WidgetOwnerNSWindowAdapterBridge : NSObject {
+ @private
+ views::WidgetOwnerNSWindowAdapter* adapter_; // Weak. Owns us.
+}
+- (instancetype)initWithAdapter:(views::WidgetOwnerNSWindowAdapter*)adapter;
+- (void)windowWillClose:(NSNotification*)notification;
+@end
+
+@implementation WidgetOwnerNSWindowAdapterBridge
+
+- (instancetype)initWithAdapter:(views::WidgetOwnerNSWindowAdapter*)adapter {
+ if ((self = [super init]))
+ adapter_ = adapter;
+ return self;
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ adapter_->OnWindowWillClose();
+}
+
+@end
+
+namespace views {
+
+WidgetOwnerNSWindowAdapter::WidgetOwnerNSWindowAdapter(
+ BridgedNativeWidget* child,
+ NSView* anchor_view)
+ : child_(child),
+ anchor_view_([anchor_view retain]),
+ observer_bridge_(
+ [[WidgetOwnerNSWindowAdapterBridge alloc] initWithAdapter:this]) {
+ DCHECK([anchor_view_ window]);
+ [[NSNotificationCenter defaultCenter]
+ addObserver:observer_bridge_
+ selector:@selector(windowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:[anchor_view_ window]];
+}
+
+void WidgetOwnerNSWindowAdapter::OnWindowWillClose() {
+ [child_->ns_window() close];
+ // Note: |this| will be deleted here.
+}
+
+NSWindow* WidgetOwnerNSWindowAdapter::GetNSWindow() {
+ return [anchor_view_ window];
+}
+
+gfx::Vector2d WidgetOwnerNSWindowAdapter::GetChildWindowOffset() const {
+ NSWindow* window = [anchor_view_ window];
+ NSRect rect_in_window =
+ [anchor_view_ convertRect:[anchor_view_ bounds] toView:nil];
+ // Ensure we anchor off the top-left of |anchor_view_| (rect_in_window.origin
+ // is the bottom-left of the view).
+ // TODO(tapted): Use -[NSWindow convertRectToScreen:] when we ditch 10.6.
+ NSRect rect_in_screen = NSZeroRect;
+ rect_in_screen.origin =
+ [window convertBaseToScreen:NSMakePoint(NSMinX(rect_in_window),
+ NSMaxY(rect_in_window))];
+ return gfx::ScreenRectFromNSRect(rect_in_screen).OffsetFromOrigin();
+}
+
+bool WidgetOwnerNSWindowAdapter::IsVisibleParent() const {
+ return [[anchor_view_ window] isVisible];
+}
+
+void WidgetOwnerNSWindowAdapter::RemoveChildWindow(BridgedNativeWidget* child) {
+ DCHECK_EQ(child, child_);
+ [GetNSWindow() removeChildWindow:child->ns_window()];
+ delete this;
+}
+
+WidgetOwnerNSWindowAdapter::~WidgetOwnerNSWindowAdapter() {
+ [[NSNotificationCenter defaultCenter] removeObserver:observer_bridge_];
+}
+
+} // namespace views
diff --git a/ui/views/views.gyp b/ui/views/views.gyp
index 272c7a7..f8ff094 100644
--- a/ui/views/views.gyp
+++ b/ui/views/views.gyp
@@ -32,6 +32,7 @@
'cocoa/bridged_content_view.mm',
'cocoa/bridged_native_widget.h',
'cocoa/bridged_native_widget.mm',
+ 'cocoa/bridged_native_widget_owner.h',
'cocoa/cocoa_mouse_capture.h',
'cocoa/cocoa_mouse_capture.mm',
'cocoa/cocoa_mouse_capture_delegate.h',
@@ -39,6 +40,8 @@
'cocoa/native_widget_mac_nswindow.mm',
'cocoa/views_nswindow_delegate.h',
'cocoa/views_nswindow_delegate.mm',
+ 'cocoa/widget_owner_nswindow_adapter.h',
+ 'cocoa/widget_owner_nswindow_adapter.mm',
'color_chooser/color_chooser_listener.h',
'color_chooser/color_chooser_view.cc',
'color_chooser/color_chooser_view.h',
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 10a1c9f..bd6061f 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -581,13 +581,13 @@ NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
BridgedNativeWidget* bridge =
NativeWidgetMac::GetBridgeForNativeWindow([native_view window]);
if (!bridge)
- return NULL;
+ return nullptr;
- for (BridgedNativeWidget* parent;
- (parent = bridge->parent());
- bridge = parent) {
- }
- return bridge->native_widget_mac();
+ NativeWidgetPrivate* ancestor =
+ bridge->parent() ? GetTopLevelNativeWidget(
+ [bridge->parent()->GetNSWindow() contentView])
+ : nullptr;
+ return ancestor ? ancestor : bridge->native_widget_mac();
}
// static
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index ccc1e4c..c30c5aa 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -6,16 +6,19 @@
#import <Cocoa/Cocoa.h>
+#import "base/mac/scoped_nsobject.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#import "testing/gtest_mac.h"
#import "ui/events/test/cocoa_test_event_utils.h"
#include "ui/events/test/event_generator.h"
#import "ui/gfx/mac/coordinate_conversion.h"
+#import "ui/views/cocoa/bridged_native_widget.h"
#include "ui/views/controls/label.h"
#include "ui/views/native_cursor.h"
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/native_widget_private.h"
namespace views {
namespace test {
@@ -364,5 +367,64 @@ TEST_F(NativeWidgetMacTest, AccessibilityIntegration) {
EXPECT_NSEQ(title, @"Green");
}
+// Tests creating a views::Widget parented off a native NSWindow.
+TEST_F(NativeWidgetMacTest, NonWidgetParent) {
+ NSRect parent_nsrect = NSMakeRect(100, 100, 300, 200);
+ base::scoped_nsobject<NSWindow> native_parent(
+ [[NSWindow alloc] initWithContentRect:parent_nsrect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:NO]);
+ [native_parent setReleasedWhenClosed:NO]; // Owned by scoped_nsobject.
+ [native_parent makeKeyAndOrderFront:nil];
+
+ // Note: Don't use WidgetTest::CreateChildPlatformWidget because that makes
+ // windows of TYPE_CONTROL which are automatically made visible. But still
+ // mark it as a child to test window positioning.
+ Widget* child = new Widget;
+ Widget::InitParams init_params;
+ init_params.parent = [native_parent contentView];
+ init_params.child = true;
+ child->Init(init_params);
+
+ TestWidgetObserver child_observer(child);
+
+ // GetTopLevelNativeWidget() only goes as far as there exists a Widget (i.e.
+ // must stop at |child|.
+ internal::NativeWidgetPrivate* top_level_widget =
+ internal::NativeWidgetPrivate::GetTopLevelNativeWidget(
+ child->GetNativeView());
+ EXPECT_EQ(child, top_level_widget->GetWidget());
+
+ // To verify the parent, we need to use NativeWidgetMac APIs.
+ BridgedNativeWidget* bridged_native_widget =
+ NativeWidgetMac::GetBridgeForNativeWindow(child->GetNativeWindow());
+ EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow());
+
+ child->SetBounds(gfx::Rect(50, 50, 200, 100));
+ EXPECT_FALSE(child->IsVisible());
+ EXPECT_EQ(0u, [[native_parent childWindows] count]);
+
+ child->Show();
+ EXPECT_TRUE(child->IsVisible());
+ EXPECT_EQ(1u, [[native_parent childWindows] count]);
+ EXPECT_EQ(child->GetNativeWindow(),
+ [[native_parent childWindows] objectAtIndex:0]);
+ EXPECT_EQ(native_parent, [child->GetNativeWindow() parentWindow]);
+
+ // Child should be positioned on screen relative to the parent, but note we
+ // positioned the parent in Cooca coordinates, so we need to convert.
+ gfx::Point parent_origin = gfx::ScreenRectFromNSRect(parent_nsrect).origin();
+ EXPECT_EQ(gfx::Rect(150, parent_origin.y() + 50, 200, 100),
+ child->GetWindowBoundsInScreen());
+
+ // Closing the parent should close and destroy the child.
+ EXPECT_FALSE(child_observer.widget_closed());
+ [native_parent close];
+ EXPECT_TRUE(child_observer.widget_closed());
+
+ EXPECT_EQ(0u, [[native_parent childWindows] count]);
+}
+
} // namespace test
} // namespace views