summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortapted <tapted@chromium.org>2015-03-17 14:28:24 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-17 21:29:01 +0000
commit3de6ad4c2b4c8f52daa2119a24063cd9309460e6 (patch)
treeefabd3548f24e8ff1a066e8cd5e1add2cb7007c4
parent50442ee97f3212f38c8cbb29228293dfa1c17ce4 (diff)
downloadchromium_src-3de6ad4c2b4c8f52daa2119a24063cd9309460e6.zip
chromium_src-3de6ad4c2b4c8f52daa2119a24063cd9309460e6.tar.gz
chromium_src-3de6ad4c2b4c8f52daa2119a24063cd9309460e6.tar.bz2
MacViews: Implement Window-modal dialogs using sheets
These are used, e.g., for the Bookmark -> Edit... dialogs on Cocoa and Views. This gets WidgetTestInteractive.*Window*ModalWindowDestroyedActivationTest passing. WidgetTestInteractive.*System*ModalWindowReleasesCapture is disabled in this CL. System modal windows are not needed on Mac. Currently the implementation on MacViews assumes all modal dialogs are shown using a sheet. On Cocoa, this is not always true for WebContents-Modal dialogs (e.g. cookies and site data), but it is true for some WebContents-Modal dialogs (e.g. certificate information). A follow-up will add logic to ensure WebContents-Modal dialogs are handled appropriately, and have good test coverage. After this all tests pass on the current mac trybot configuration with toolkit_views=1. BUG=403679 Review URL: https://codereview.chromium.org/993253002 Cr-Commit-Position: refs/heads/master@{#320977}
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/interactive_ui_tests.isolate2
-rw-r--r--chrome/test/BUILD.gn1
-rw-r--r--ui/views/cocoa/bridged_native_widget.mm35
-rw-r--r--ui/views/cocoa/views_nswindow_delegate.h5
-rw-r--r--ui/views/cocoa/views_nswindow_delegate.mm7
-rw-r--r--ui/views/examples/widget_example.cc17
-rw-r--r--ui/views/examples/widget_example.h1
-rw-r--r--ui/views/widget/native_widget_mac.mm19
-rw-r--r--ui/views/widget/widget_interactive_uitest.cc25
10 files changed, 107 insertions, 6 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index eeb311b..cb1f4e3 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1470,6 +1470,7 @@
'../third_party/npapi/npapi.gyp:npapi',
'../third_party/zlib/zlib.gyp:zlib',
'../ui/base/ui_base.gyp:ui_base_test_support',
+ '../ui/resources/ui_resources.gyp:ui_test_pak',
'../ui/web_dialogs/web_dialogs.gyp:web_dialogs_test_support',
# Runtime dependencies
'../third_party/mesa/mesa.gyp:osmesa',
diff --git a/chrome/interactive_ui_tests.isolate b/chrome/interactive_ui_tests.isolate
index 6ceb6c9..01c2675 100644
--- a/chrome/interactive_ui_tests.isolate
+++ b/chrome/interactive_ui_tests.isolate
@@ -55,6 +55,7 @@
'../third_party/tlslite/',
'<(PRODUCT_DIR)/interactive_ui_tests<(EXECUTABLE_SUFFIX)',
'<(PRODUCT_DIR)/resources.pak',
+ '<(PRODUCT_DIR)/ui_test.pak',
'test/data/',
],
'read_only': 1,
@@ -66,7 +67,6 @@
'<(PRODUCT_DIR)/chrome_100_percent.pak',
'<(PRODUCT_DIR)/locales/en-US.pak',
'<(PRODUCT_DIR)/locales/fr.pak',
- '<(PRODUCT_DIR)/ui_test.pak',
],
},
}],
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index fdb7c0c..62c666c 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -250,6 +250,7 @@ if (!is_android) {
"//third_party/npapi",
"//third_party/zlib",
"//ui/base:test_support",
+ "//ui/resources:ui_test_pak",
"//ui/web_dialogs:test_support",
]
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index dfea779..74ac56af 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -87,6 +87,15 @@ BridgedNativeWidget::~BridgedNativeWidget() {
SetRootView(NULL);
DestroyCompositor();
if ([window_ delegate]) {
+ // If the delegate is still set on a modal dialog, it means it was not
+ // closed via [NSApplication endSheet:]. This is probably OK if the widget
+ // was never shown. But Cocoa ignores close() calls on open sheets. Calling
+ // endSheet: here would work, but it messes up assumptions elsewhere. E.g.
+ // DialogClientView assumes its delegate is alive when closing, which isn't
+ // true after endSheet: synchronously calls OnNativeWidgetDestroyed().
+ // So ban it. Modal dialogs should be closed via Widget::Close().
+ DCHECK(!native_widget_mac_->GetWidget()->IsModal());
+
// If the delegate is still set, it means OnWindowWillClose has not been
// called and the window is still open. Calling -[NSWindow close] will
// synchronously call OnWindowWillClose and notify NativeWidgetMac.
@@ -180,6 +189,13 @@ void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) {
// due to unpredictable OSX treatment.
DCHECK(!new_bounds.IsEmpty()) << "Zero-sized windows not supported on Mac";
+ if (native_widget_mac_->GetWidget()->IsModal()) {
+ // Modal dialogs are positioned by Cocoa. Just update the size.
+ [window_
+ setContentSize:NSMakeSize(new_bounds.width(), new_bounds.height())];
+ return;
+ }
+
gfx::Rect actual_new_bounds(new_bounds);
if (parent_ &&
@@ -239,6 +255,18 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) {
return;
}
+ if (native_widget_mac_->GetWidget()->IsModal()) {
+ NSWindow* parent_window = parent_->ns_window();
+ DCHECK(parent_window);
+
+ [NSApp beginSheet:window_
+ modalForWindow:parent_window
+ modalDelegate:[window_ delegate]
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nullptr];
+ return;
+ }
+
if (new_state == SHOW_AND_ACTIVATE_WINDOW) {
[window_ makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
@@ -450,10 +478,13 @@ void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) {
// The contentView is the BridgedContentView hosting the views::RootView. The
// focus manager will already know if a native subview has focus.
if ([window_ contentView] == [window_ firstResponder]) {
- if (is_key)
+ if (is_key) {
+ widget->OnNativeFocus();
widget->GetFocusManager()->RestoreFocusedView();
- else
+ } else {
+ widget->OnNativeBlur();
widget->GetFocusManager()->StoreFocusedView(true);
+ }
}
}
diff --git a/ui/views/cocoa/views_nswindow_delegate.h b/ui/views/cocoa/views_nswindow_delegate.h
index d5ef872..ec8e6e1 100644
--- a/ui/views/cocoa/views_nswindow_delegate.h
+++ b/ui/views/cocoa/views_nswindow_delegate.h
@@ -47,6 +47,11 @@ class BridgedNativeWidget;
// Notify when -[NSWindow display] is being called on the window.
- (void)onWindowWillDisplay;
+// Called on the delegate of a modal sheet when its modal session ends.
+- (void)sheetDidEnd:(NSWindow*)sheet
+ returnCode:(NSInteger)returnCode
+ contextInfo:(void*)contextInfo;
+
@end
#endif // UI_VIEWS_COCOA_VIEWS_NSWINDOW_DELEGATE_H_
diff --git a/ui/views/cocoa/views_nswindow_delegate.mm b/ui/views/cocoa/views_nswindow_delegate.mm
index 38a56c1..6a344a3 100644
--- a/ui/views/cocoa/views_nswindow_delegate.mm
+++ b/ui/views/cocoa/views_nswindow_delegate.mm
@@ -47,6 +47,13 @@
parent_->OnVisibilityChangedTo(true);
}
+- (void)sheetDidEnd:(NSWindow*)sheet
+ returnCode:(NSInteger)returnCode
+ contextInfo:(void*)contextInfo {
+ [sheet orderOut:nil];
+ parent_->OnWindowWillClose();
+}
+
// NSWindowDelegate implementation.
- (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
diff --git a/ui/views/examples/widget_example.cc b/ui/views/examples/widget_example.cc
index d7a1b02..3a9506f 100644
--- a/ui/views/examples/widget_example.cc
+++ b/ui/views/examples/widget_example.cc
@@ -30,6 +30,17 @@ class DialogExample : public DialogDelegateView {
View* CreateFootnoteView() override;
};
+class ModalDialogExample : public DialogExample {
+ public:
+ ModalDialogExample() {}
+
+ // WidgetDelegate:
+ ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_WINDOW; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModalDialogExample);
+};
+
DialogExample::DialogExample() {
set_background(Background::CreateSolidBackground(SK_ColorGRAY));
SetLayoutManager(new BoxLayout(BoxLayout::kVertical, 10, 10, 10));
@@ -70,6 +81,7 @@ void WidgetExample::CreateExampleView(View* container) {
container->SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0, 10));
BuildButton(container, "Popup widget", POPUP);
BuildButton(container, "Dialog widget", DIALOG);
+ BuildButton(container, "Modal Dialog", MODAL_DIALOG);
#if defined(OS_LINUX)
// Windows does not support TYPE_CONTROL top-level widgets.
BuildButton(container, "Child widget", CHILD);
@@ -116,6 +128,11 @@ void WidgetExample::ButtonPressed(Button* sender, const ui::Event& event) {
sender->GetWidget()->GetNativeView())->Show();
break;
}
+ case MODAL_DIALOG: {
+ DialogDelegate::CreateDialogWidget(new ModalDialogExample(), NULL,
+ sender->GetWidget()->GetNativeView())->Show();
+ break;
+ }
case CHILD:
ShowWidget(sender, Widget::InitParams(Widget::InitParams::TYPE_CONTROL));
break;
diff --git a/ui/views/examples/widget_example.h b/ui/views/examples/widget_example.h
index 159a4da..87065f9 100644
--- a/ui/views/examples/widget_example.h
+++ b/ui/views/examples/widget_example.h
@@ -30,6 +30,7 @@ class VIEWS_EXAMPLES_EXPORT WidgetExample : public ExampleBase,
enum Command {
POPUP, // Show a popup widget.
DIALOG, // Show a dialog widget.
+ MODAL_DIALOG, // Show a modal dialog widget.
CHILD, // Show a child widget.
CLOSE_WIDGET, // Close the sender button's widget.
};
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 20db395..3e43830 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -251,7 +251,13 @@ void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
}
void NativeWidgetMac::InitModalType(ui::ModalType modal_type) {
- NOTIMPLEMENTED();
+ if (modal_type == ui::MODAL_TYPE_NONE)
+ return;
+
+ // System modal windows not implemented (or used) on Mac.
+ DCHECK_NE(ui::MODAL_TYPE_SYSTEM, modal_type);
+ DCHECK(bridge_->parent());
+ // Everyhing happens upon show.
}
gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
@@ -299,6 +305,17 @@ void NativeWidgetMac::Close() {
if (!bridge_)
return;
+ if (delegate_->IsModal()) {
+ // Sheets can't be closed normally. This starts the sheet closing. Once the
+ // sheet has finished animating, it will call sheetDidEnd: on the parent
+ // window's delegate. Note it still needs to be asynchronous, since code
+ // calling Widget::Close() doesn't expect things to be deleted upon return.
+ [NSApp performSelector:@selector(endSheet:)
+ withObject:GetNativeWindow()
+ afterDelay:0];
+ return;
+ }
+
// Clear the view early to suppress repaints.
bridge_->SetRootView(NULL);
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 6ac9451..f55c375 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -741,7 +741,7 @@ TEST_F(WidgetTestInteractive, WindowModalWindowDestroyedActivationTest) {
init_params.native_widget =
new PlatformDesktopNativeWidget(&top_level_widget);
top_level_widget.Init(init_params);
- top_level_widget.Show();
+ ShowSync(&top_level_widget);
gfx::NativeView top_level_native_view = top_level_widget.GetNativeView();
ASSERT_FALSE(focus_listener.focus_changes().empty());
@@ -756,6 +756,9 @@ TEST_F(WidgetTestInteractive, WindowModalWindowDestroyedActivationTest) {
Widget* modal_dialog_widget = views::DialogDelegate::CreateDialogWidget(
dialog_delegate, NULL, top_level_widget.GetNativeView());
modal_dialog_widget->SetBounds(gfx::Rect(100, 100, 200, 200));
+
+ // Note the dialog widget doesn't need a ShowSync. Since it is modal, it gains
+ // active status synchronously, even on Mac.
modal_dialog_widget->Show();
gfx::NativeView modal_native_view = modal_dialog_widget->GetNativeView();
@@ -763,7 +766,15 @@ TEST_F(WidgetTestInteractive, WindowModalWindowDestroyedActivationTest) {
EXPECT_EQ(nullptr, focus_changes[1]);
EXPECT_EQ(modal_native_view, focus_changes[2]);
+#if defined(OS_MACOSX)
+ // Window modal dialogs on Mac are "sheets", which animate to close before
+ // activating their parent widget.
+ WidgetActivationWaiter waiter(&top_level_widget, true);
+ modal_dialog_widget->Close();
+ waiter.Wait();
+#else
modal_dialog_widget->CloseNow();
+#endif
EXPECT_EQ(5u, focus_changes.size());
EXPECT_EQ(nullptr, focus_changes[3]);
@@ -773,8 +784,18 @@ TEST_F(WidgetTestInteractive, WindowModalWindowDestroyedActivationTest) {
WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(&focus_listener);
}
+// Disabled on Mac. Desktop Mac doesn't have system modal windows since Carbon
+// was deprecated. It does have application modal windows, but only Ash requests
+// those.
+#if defined(OS_MACOSX) && !defined(USE_AURA)
+#define MAYBE_SystemModalWindowReleasesCapture \
+ DISABLED_SystemModalWindowReleasesCapture
+#else
+#define MAYBE_SystemModalWindowReleasesCapture SystemModalWindowReleasesCapture
+#endif
+
// Test that when opening a system-modal window, capture is released.
-TEST_F(WidgetTestInteractive, SystemModalWindowReleasesCapture) {
+TEST_F(WidgetTestInteractive, MAYBE_SystemModalWindowReleasesCapture) {
TestWidgetFocusChangeListener focus_listener;
WidgetFocusManager::GetInstance()->AddFocusChangeListener(&focus_listener);