diff options
author | tapted <tapted@chromium.org> | 2015-03-17 14:28:24 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-17 21:29:01 +0000 |
commit | 3de6ad4c2b4c8f52daa2119a24063cd9309460e6 (patch) | |
tree | efabd3548f24e8ff1a066e8cd5e1add2cb7007c4 | |
parent | 50442ee97f3212f38c8cbb29228293dfa1c17ce4 (diff) | |
download | chromium_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.gypi | 1 | ||||
-rw-r--r-- | chrome/interactive_ui_tests.isolate | 2 | ||||
-rw-r--r-- | chrome/test/BUILD.gn | 1 | ||||
-rw-r--r-- | ui/views/cocoa/bridged_native_widget.mm | 35 | ||||
-rw-r--r-- | ui/views/cocoa/views_nswindow_delegate.h | 5 | ||||
-rw-r--r-- | ui/views/cocoa/views_nswindow_delegate.mm | 7 | ||||
-rw-r--r-- | ui/views/examples/widget_example.cc | 17 | ||||
-rw-r--r-- | ui/views/examples/widget_example.h | 1 | ||||
-rw-r--r-- | ui/views/widget/native_widget_mac.mm | 19 | ||||
-rw-r--r-- | ui/views/widget/widget_interactive_uitest.cc | 25 |
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); |