Index: ui/views/widget/native_widget_mac_unittest.mm |
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm |
index 5a48f0257223f75064732e12b1a76061c18a7cd2..453312e0e10b776582086a40d0e523306071e82b 100644 |
--- a/ui/views/widget/native_widget_mac_unittest.mm |
+++ b/ui/views/widget/native_widget_mac_unittest.mm |
@@ -22,6 +22,7 @@ |
#import "ui/events/test/cocoa_test_event_utils.h" |
#include "ui/events/test/event_generator.h" |
#import "ui/gfx/mac/coordinate_conversion.h" |
+#include "ui/views/bubble/bubble_delegate.h" |
#import "ui/views/cocoa/bridged_native_widget.h" |
#import "ui/views/cocoa/native_widget_mac_nswindow.h" |
#include "ui/views/controls/button/label_button.h" |
@@ -918,6 +919,94 @@ TEST_F(NativeWidgetMacTest, NoopReparentNativeView) { |
parent_widget->CloseNow(); |
} |
+// Attaches a child window to |parent| that checks its parent's delegate is |
+// cleared when the child is destroyed. This assumes the child is destroyed via |
+// destruction of its parent. |
+class ParentCloseMonitor : public WidgetObserver { |
+ public: |
+ explicit ParentCloseMonitor(Widget* parent) { |
+ Widget* child = new Widget(); |
+ child->AddObserver(this); |
+ Widget::InitParams init_params(Widget::InitParams::TYPE_WINDOW_FRAMELESS); |
+ init_params.parent = parent->GetNativeView(); |
+ init_params.bounds = gfx::Rect(100, 100, 100, 100); |
+ init_params.native_widget = new NativeWidgetCapture(child); |
+ child->Init(init_params); |
+ child->Show(); |
+ |
+ // NSWindow parent/child relationship should be established on Show() and |
+ // the parent should have a delegate. Retain the parent since it can't be |
+ // retrieved from the child while it is being destroyed. |
+ parent_nswindow_.reset([[child->GetNativeWindow() parentWindow] retain]); |
+ EXPECT_TRUE(parent_nswindow_); |
+ EXPECT_TRUE([parent_nswindow_ delegate]); |
+ } |
+ |
+ ~ParentCloseMonitor() override { |
+ EXPECT_TRUE(child_closed_); // Otherwise the observer wasn't removed. |
+ } |
+ |
+ void OnWidgetDestroying(Widget* child) override { |
+ // Upon a parent-triggered close, the NSWindow relationship will already be |
+ // removed. The parent should still be open (children are always closed |
+ // first), but not have a delegate (since it is being torn down). |
+ EXPECT_FALSE([child->GetNativeWindow() parentWindow]); |
+ EXPECT_TRUE([parent_nswindow_ isVisible]); |
+ EXPECT_FALSE([parent_nswindow_ delegate]); |
+ |
+ EXPECT_FALSE(child_closed_); |
+ child->RemoveObserver(this); |
+ child_closed_ = true; |
+ } |
+ |
+ bool child_closed() const { return child_closed_; } |
+ |
+ private: |
+ base::scoped_nsobject<NSWindow> parent_nswindow_; |
+ bool child_closed_ = false; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ParentCloseMonitor); |
+}; |
+ |
+// Ensures when a parent window is destroyed, and triggers its child windows to |
+// be closed, that the child windows (via AppKit) do not attempt to call back |
+// into the parent, whilst it's in the process of being destroyed. |
+TEST_F(NativeWidgetMacTest, NoParentDelegateDuringTeardown) { |
+ // First test "normal" windows and AppKit close. |
+ { |
+ Widget* parent = CreateTopLevelPlatformWidget(); |
+ parent->SetBounds(gfx::Rect(100, 100, 300, 200)); |
+ parent->Show(); |
+ ParentCloseMonitor monitor(parent); |
+ [parent->GetNativeWindow() close]; |
+ EXPECT_TRUE(monitor.child_closed()); |
+ } |
+ |
+ // Test the Widget::CloseNow() flow. |
+ { |
+ Widget* parent = CreateTopLevelPlatformWidget(); |
+ parent->SetBounds(gfx::Rect(100, 100, 300, 200)); |
+ parent->Show(); |
+ ParentCloseMonitor monitor(parent); |
+ parent->CloseNow(); |
+ EXPECT_TRUE(monitor.child_closed()); |
+ } |
+ |
+ // Test the WIDGET_OWNS_NATIVE_WIDGET flow. |
+ { |
+ scoped_ptr<Widget> parent(new Widget); |
+ Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); |
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
+ params.bounds = gfx::Rect(100, 100, 300, 200); |
+ parent->Init(params); |
+ parent->Show(); |
+ |
+ ParentCloseMonitor monitor(parent.get()); |
+ parent.reset(); |
+ EXPECT_TRUE(monitor.child_closed()); |
+ } |
+} |
+ |
// Tests Cocoa properties that should be given to particular widget types. |
TEST_F(NativeWidgetMacTest, NativeProperties) { |
// Create a regular widget (TYPE_WINDOW). |
@@ -938,6 +1027,20 @@ TEST_F(NativeWidgetMacTest, NativeProperties) { |
// Dialogs shouldn't take main status away from their parent. |
EXPECT_FALSE([dialog_widget->GetNativeWindow() canBecomeMainWindow]); |
+ // Create a bubble widget with a parent: also shouldn't get main. |
+ BubbleDelegateView* bubble_view = new BubbleDelegateView(); |
+ bubble_view->set_parent_window(regular_widget->GetNativeView()); |
+ Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_view); |
+ EXPECT_TRUE([bubble_widget->GetNativeWindow() canBecomeKeyWindow]); |
+ EXPECT_FALSE([bubble_widget->GetNativeWindow() canBecomeMainWindow]); |
+ |
+ // But a bubble without a parent should still be able to become main. |
+ Widget* toplevel_bubble_widget = |
+ BubbleDelegateView::CreateBubble(new BubbleDelegateView()); |
+ EXPECT_TRUE([toplevel_bubble_widget->GetNativeWindow() canBecomeKeyWindow]); |
+ EXPECT_TRUE([toplevel_bubble_widget->GetNativeWindow() canBecomeMainWindow]); |
+ |
+ toplevel_bubble_widget->CloseNow(); |
regular_widget->CloseNow(); |
} |