0

Fixes possible crash if the window hosting a menu was closed while the

menu was showing. When this happens the window the menu creates is
implicitly destroyed (because the parent is going away).

BUG=25563
TEST=see bug

Review URL: http://codereview.chromium.org/1664001

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44807 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
sky@chromium.org
2010-04-16 19:38:33 +00:00
parent fa8089e080
commit 365e821c6f
21 changed files with 450 additions and 211 deletions

@@ -412,6 +412,12 @@ BookmarkBarView::~BookmarkBarView() {
NotifyModelChanged(); NotifyModelChanged();
if (model_) if (model_)
model_->RemoveObserver(this); model_->RemoveObserver(this);
// It's possible for the menu to outlive us, reset the observer to make sure
// it doesn't have a reference to us.
if (bookmark_menu_)
bookmark_menu_->set_observer(NULL);
StopShowFolderDropMenuTimer(); StopShowFolderDropMenuTimer();
if (sync_service_) if (sync_service_)

@@ -1,4 +1,4 @@
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@@ -22,6 +22,7 @@
#include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_controller.h"
#include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/submenu_view.h" #include "views/controls/menu/submenu_view.h"
#include "views/views_delegate.h"
#include "views/window/window.h" #include "views/window/window.h"
#if defined(OS_LINUX) #if defined(OS_LINUX)
@@ -45,6 +46,37 @@
namespace { namespace {
class ViewsDelegateImpl : public views::ViewsDelegate {
public:
ViewsDelegateImpl() {}
virtual Clipboard* GetClipboard() const { return NULL; }
virtual void SaveWindowPlacement(const std::wstring& window_name,
const gfx::Rect& bounds,
bool maximized) {}
virtual bool GetSavedWindowBounds(const std::wstring& window_name,
gfx::Rect* bounds) const {
return false;
}
virtual bool GetSavedMaximizedState(const std::wstring& window_name,
bool* maximized) const {
return false;
}
#if defined(OS_WIN)
virtual HICON GetDefaultWindowIcon() const { return 0; }
#endif
virtual void AddRef() {
}
virtual void ReleaseRef() {
MessageLoopForUI::current()->Quit();
}
private:
DISALLOW_COPY_AND_ASSIGN(ViewsDelegateImpl);
};
// PageNavigator implementation that records the URL. // PageNavigator implementation that records the URL.
class TestingPageNavigator : public PageNavigator { class TestingPageNavigator : public PageNavigator {
public: public:
@@ -148,9 +180,14 @@ class BookmarkBarViewEventTestBase : public ViewEventTestBase {
views::MenuItemView::allow_task_nesting_during_run_ = false; views::MenuItemView::allow_task_nesting_during_run_ = false;
ViewEventTestBase::TearDown(); ViewEventTestBase::TearDown();
BookmarkBarView::testing_ = false; BookmarkBarView::testing_ = false;
views::ViewsDelegate::views_delegate = NULL;
} }
protected: protected:
void InstallViewsDelegate() {
views::ViewsDelegate::views_delegate = &views_delegate_;
}
virtual views::View* CreateContentsView() { virtual views::View* CreateContentsView() {
return bb_view_; return bb_view_;
} }
@@ -200,6 +237,7 @@ class BookmarkBarViewEventTestBase : public ViewEventTestBase {
scoped_ptr<TestingProfile> profile_; scoped_ptr<TestingProfile> profile_;
ChromeThread ui_thread_; ChromeThread ui_thread_;
ChromeThread file_thread_; ChromeThread file_thread_;
ViewsDelegateImpl views_delegate_;
}; };
// Clicks on first menu, makes sure button is depressed. Moves mouse to first // Clicks on first menu, makes sure button is depressed. Moves mouse to first
@@ -369,7 +407,7 @@ class BookmarkBarViewTest3 : public BookmarkBarViewEventTestBase {
EXPECT_EQ(GURL(), navigator_.url_); EXPECT_EQ(GURL(), navigator_.url_);
// Hide menu. // Hide menu.
menu->GetMenuController()->Cancel(true); menu->GetMenuController()->CancelAll();
Done(); Done();
} }
@@ -784,7 +822,7 @@ class BookmarkBarViewTest9 : public BookmarkBarViewEventTestBase {
ASSERT_NE(start_y_, menu_loc.y()); ASSERT_NE(start_y_, menu_loc.y());
// Hide menu. // Hide menu.
bb_view_->GetMenu()->GetMenuController()->Cancel(true); bb_view_->GetMenu()->GetMenuController()->CancelAll();
// On linux, Cancelling menu will call Quit on the message loop, // On linux, Cancelling menu will call Quit on the message loop,
// which can interfere with Done. We need to run Done in the // which can interfere with Done. We need to run Done in the
@@ -1255,7 +1293,7 @@ class BookmarkBarViewTest15 : public BookmarkBarViewEventTestBase {
// And the deleted_menu_id_ should have been removed. // And the deleted_menu_id_ should have been removed.
ASSERT_TRUE(menu->GetMenuItemByID(deleted_menu_id_) == NULL); ASSERT_TRUE(menu->GetMenuItemByID(deleted_menu_id_) == NULL);
bb_view_->GetMenu()->GetMenuController()->Cancel(true); bb_view_->GetMenu()->GetMenuController()->CancelAll();
Done(); Done();
} }
@@ -1265,3 +1303,36 @@ class BookmarkBarViewTest15 : public BookmarkBarViewEventTestBase {
}; };
VIEW_TEST(BookmarkBarViewTest15, MenuStaysVisibleAfterDelete) VIEW_TEST(BookmarkBarViewTest15, MenuStaysVisibleAfterDelete)
// Tests that we don't crash or get stuck if the parent of a menu is closed.
class BookmarkBarViewTest16 : public BookmarkBarViewEventTestBase {
protected:
virtual void DoTestOnMessageLoop() {
InstallViewsDelegate();
// Move the mouse to the first folder on the bookmark bar and press the
// mouse.
views::TextButton* button = bb_view_->GetBookmarkButton(0);
ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP,
CreateEventTask(this, &BookmarkBarViewTest16::Step2));
}
private:
void Step2() {
// Menu should be showing.
views::MenuItemView* menu = bb_view_->GetMenu();
ASSERT_TRUE(menu != NULL);
ASSERT_TRUE(menu->GetSubmenu()->IsShowing());
// Button should be depressed.
views::TextButton* button = bb_view_->GetBookmarkButton(0);
ASSERT_TRUE(button->state() == views::CustomButton::BS_PUSHED);
// Close the window.
window_->Close();
window_ = NULL;
}
};
VIEW_TEST(BookmarkBarViewTest16, DeleteMenu)

@@ -81,3 +81,11 @@ HICON ChromeViewsDelegate::GetDefaultWindowIcon() const {
MAKEINTRESOURCE(IDR_MAINFRAME)); MAKEINTRESOURCE(IDR_MAINFRAME));
} }
#endif #endif
void ChromeViewsDelegate::AddRef() {
g_browser_process->AddRefModule();
}
void ChromeViewsDelegate::ReleaseRef() {
g_browser_process->ReleaseModule();
}

@@ -25,6 +25,8 @@ class ChromeViewsDelegate : public views::ViewsDelegate {
#if defined(OS_WIN) #if defined(OS_WIN)
virtual HICON GetDefaultWindowIcon() const; virtual HICON GetDefaultWindowIcon() const;
#endif #endif
virtual void AddRef();
virtual void ReleaseRef();
private: private:
DISALLOW_COPY_AND_ASSIGN(ChromeViewsDelegate); DISALLOW_COPY_AND_ASSIGN(ChromeViewsDelegate);

@@ -49,11 +49,14 @@ MenuButton::MenuButton(ButtonListener* listener,
menu_delegate_(menu_delegate), menu_delegate_(menu_delegate),
show_menu_marker_(show_menu_marker), show_menu_marker_(show_menu_marker),
menu_marker_(ResourceBundle::GetSharedInstance().GetBitmapNamed( menu_marker_(ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_MENU_DROPARROW)) { IDR_MENU_DROPARROW)),
destroyed_flag_(NULL) {
set_alignment(TextButton::ALIGN_LEFT); set_alignment(TextButton::ALIGN_LEFT);
} }
MenuButton::~MenuButton() { MenuButton::~MenuButton() {
if (destroyed_flag_)
*destroyed_flag_ = true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -145,7 +148,17 @@ bool MenuButton::Activate() {
GetRootView()->SetMouseHandler(NULL); GetRootView()->SetMouseHandler(NULL);
menu_visible_ = true; menu_visible_ = true;
bool destroyed = false;
destroyed_flag_ = &destroyed;
menu_delegate_->RunMenu(this, menu_position); menu_delegate_->RunMenu(this, menu_position);
if (destroyed) {
// The menu was deleted while showing. Don't attempt any processing.
return false;
}
menu_visible_ = false; menu_visible_ = false;
menu_closed_time_ = Time::Now(); menu_closed_time_ = Time::Now();

@@ -1,4 +1,4 @@
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@@ -97,6 +97,10 @@ class MenuButton : public TextButton {
// text buttons. // text buttons.
const SkBitmap* menu_marker_; const SkBitmap* menu_marker_;
// If non-null the destuctor sets this to true. This is set while the menu is
// showing and used to detect if the menu was deleted while running.
bool* destroyed_flag_;
DISALLOW_COPY_AND_ASSIGN(MenuButton); DISALLOW_COPY_AND_ASSIGN(MenuButton);
}; };

@@ -16,8 +16,10 @@
#include "views/drag_utils.h" #include "views/drag_utils.h"
#include "views/screen.h" #include "views/screen.h"
#include "views/view_constants.h" #include "views/view_constants.h"
#include "views/views_delegate.h"
#include "views/widget/root_view.h" #include "views/widget/root_view.h"
#include "views/widget/widget.h" #include "views/widget/widget.h"
#if defined(OS_LINUX) #if defined(OS_LINUX)
#include "base/keyboard_code_conversion_gtk.h" #include "base/keyboard_code_conversion_gtk.h"
#endif #endif
@@ -191,6 +193,10 @@ MenuItemView* MenuController::Run(gfx::NativeWindow parent,
DLOG(INFO) << " entering nested loop, depth=" << nested_depth; DLOG(INFO) << " entering nested loop, depth=" << nested_depth;
#endif #endif
// Make sure Chrome doesn't attempt to shut down while the menu is showing.
if (ViewsDelegate::views_delegate)
ViewsDelegate::views_delegate->AddRef();
MessageLoopForUI* loop = MessageLoopForUI::current(); MessageLoopForUI* loop = MessageLoopForUI::current();
if (MenuItemView::allow_task_nesting_during_run_) { if (MenuItemView::allow_task_nesting_during_run_) {
bool did_allow_task_nesting = loop->NestableTasksAllowed(); bool did_allow_task_nesting = loop->NestableTasksAllowed();
@@ -201,6 +207,9 @@ MenuItemView* MenuController::Run(gfx::NativeWindow parent,
loop->Run(this); loop->Run(this);
} }
if (ViewsDelegate::views_delegate)
ViewsDelegate::views_delegate->ReleaseRef();
#ifdef DEBUG_MENU #ifdef DEBUG_MENU
nested_depth--; nested_depth--;
DLOG(INFO) << " exiting nested loop, depth=" << nested_depth; DLOG(INFO) << " exiting nested loop, depth=" << nested_depth;
@@ -238,11 +247,14 @@ MenuItemView* MenuController::Run(gfx::NativeWindow parent,
CloseAllNestedMenus(); CloseAllNestedMenus();
// Set exit_all_, which makes sure all nested loops exit immediately. // Set exit_all_, which makes sure all nested loops exit immediately.
exit_type_ = EXIT_ALL; if (exit_type_ != EXIT_DESTROYED)
exit_type_ = EXIT_ALL;
} }
} }
if (menu_button_) { // If we stopped running because one of the menus was destroyed chances are
// the button was also destroyed.
if (exit_type_ != EXIT_DESTROYED && menu_button_) {
menu_button_->SetState(CustomButton::BS_NORMAL); menu_button_->SetState(CustomButton::BS_NORMAL);
menu_button_->SchedulePaint(); menu_button_->SchedulePaint();
} }
@@ -286,7 +298,7 @@ void MenuController::SetSelection(MenuItemView* menu_item,
StartShowTimer(); StartShowTimer();
} }
void MenuController::Cancel(bool all) { void MenuController::Cancel(ExitType type) {
if (!showing_) { if (!showing_) {
// This occurs if we're in the process of notifying the delegate for a drop // This occurs if we're in the process of notifying the delegate for a drop
// and the delegate cancels us. // and the delegate cancels us.
@@ -294,7 +306,7 @@ void MenuController::Cancel(bool all) {
} }
MenuItemView* selected = state_.item; MenuItemView* selected = state_.item;
exit_type_ = all ? EXIT_ALL : EXIT_OUTERMOST; exit_type_ = type;
// Hide windows immediately. // Hide windows immediately.
SetSelection(NULL, false, true); SetSelection(NULL, false, true);
@@ -336,7 +348,7 @@ void MenuController::OnMousePressed(SubmenuView* source,
#endif #endif
// And close. // And close.
Cancel(true); Cancel(EXIT_ALL);
return; return;
} }
@@ -394,7 +406,7 @@ void MenuController::OnMouseDragged(SubmenuView* source,
if (showing_) { if (showing_) {
// We're still showing, close all menus. // We're still showing, close all menus.
CloseAllNestedMenus(); CloseAllNestedMenus();
Cancel(true); Cancel(EXIT_ALL);
} // else case, drop was on us. } // else case, drop was on us.
} // else case, someone canceled us, don't do anything } // else case, someone canceled us, don't do anything
} }
@@ -637,7 +649,7 @@ void MenuController::SetActiveInstance(MenuController* controller) {
bool MenuController::Dispatch(const MSG& msg) { bool MenuController::Dispatch(const MSG& msg) {
DCHECK(blocking_run_); DCHECK(blocking_run_);
if (exit_type_ == EXIT_ALL) { if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED) {
// We must translate/dispatch the message here, otherwise we would drop // We must translate/dispatch the message here, otherwise we would drop
// the message on the floor. // the message on the floor.
TranslateMessage(&msg); TranslateMessage(&msg);
@@ -678,7 +690,7 @@ bool MenuController::Dispatch(const MSG& msg) {
case WM_CANCELMODE: case WM_CANCELMODE:
case WM_SYSKEYDOWN: case WM_SYSKEYDOWN:
// Exit immediately on system keys. // Exit immediately on system keys.
Cancel(true); Cancel(EXIT_ALL);
return false; return false;
default: default:
@@ -693,7 +705,7 @@ bool MenuController::Dispatch(const MSG& msg) {
bool MenuController::Dispatch(GdkEvent* event) { bool MenuController::Dispatch(GdkEvent* event) {
gtk_main_do_event(event); gtk_main_do_event(event);
if (exit_type_ == EXIT_ALL) if (exit_type_ == EXIT_ALL || exit_type_ == EXIT_DESTROYED)
return false; return false;
switch (event->type) { switch (event->type) {
@@ -766,7 +778,7 @@ bool MenuController::OnKeyDown(int key_code
(!state_.item->HasSubmenu() || (!state_.item->HasSubmenu() ||
!state_.item->GetSubmenu()->IsShowing()))) { !state_.item->GetSubmenu()->IsShowing()))) {
// User pressed escape and only one menu is shown, cancel it. // User pressed escape and only one menu is shown, cancel it.
Cancel(false); Cancel(EXIT_OUTERMOST);
return false; return false;
} else { } else {
CloseSubmenu(); CloseSubmenu();

@@ -38,6 +38,22 @@ class MenuController : public MessageLoopForUI::Dispatcher {
friend class MenuHostRootView; friend class MenuHostRootView;
friend class MenuItemView; friend class MenuItemView;
// Enumeration of how the menu should exit.
enum ExitType {
// Don't exit.
EXIT_NONE,
// All menus, including nested, should be exited.
EXIT_ALL,
// Only the outermost menu should be exited.
EXIT_OUTERMOST,
// This is set if the menu is being closed as the result of one of the menus
// being destroyed.
EXIT_DESTROYED
};
// If a menu is currently active, this returns the controller for it. // If a menu is currently active, this returns the controller for it.
static MenuController* GetActiveInstance(); static MenuController* GetActiveInstance();
@@ -66,12 +82,12 @@ class MenuController : public MessageLoopForUI::Dispatcher {
bool open_submenu, bool open_submenu,
bool update_immediately); bool update_immediately);
// Cancels the current Run. If all is true, any nested loops are canceled // Cancels the current Run. See ExitType for a description of what happens
// as well. This immediately hides all menus. // with the various parameters.
void Cancel(bool all); void Cancel(ExitType type);
// An alternative to Cancel(true) that can be used with a OneShotTimer. // An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
void CancelAll() { return Cancel(true); } void CancelAll() { Cancel(EXIT_ALL); }
// Various events, forwarded from the submenu. // Various events, forwarded from the submenu.
// //
@@ -98,18 +114,6 @@ class MenuController : public MessageLoopForUI::Dispatcher {
void OnDragExitedScrollButton(SubmenuView* source); void OnDragExitedScrollButton(SubmenuView* source);
private: private:
// Enumeration of how the menu should exit.
enum ExitType {
// Don't exit.
EXIT_NONE,
// All menus, including nested, should be exited.
EXIT_ALL,
// Only the outermost menu should be exited.
EXIT_OUTERMOST
};
class MenuScrollTask; class MenuScrollTask;
// Tracks selection information. // Tracks selection information.

64
views/controls/menu/menu_host.h Executable file

@@ -0,0 +1,64 @@
// Copyright (c) 2010 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 VIEWS_CONTROLS_MENU_MENU_HOST_H_
#define VIEWS_CONTROLS_MENU_MENU_HOST_H_
#include "gfx/native_widget_types.h"
#include "gfx/rect.h"
namespace views {
class SubmenuView;
class View;
// SubmenuView uses a MenuHost to house the SubmenuView. MenuHost typically
// extends the native Widget type, but is defined here for clarity of what
// methods SubmenuView uses.
//
// SubmenuView owns the MenuHost. When SubmenuView is done with the MenuHost
// |DestroyMenuHost| is invoked. The one exception to this is if the native
// OS destroys the widget out from under us, in which case |MenuHostDestroyed|
// is invoked back on the SubmenuView and the SubmenuView then drops references
// to the MenuHost.
class MenuHost {
public:
// Creates the platform specific MenuHost. Ownership passes to the caller.
static MenuHost* Create(SubmenuView* submenu_view);
// Initializes and shows the MenuHost.
virtual void Init(gfx::NativeWindow parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture) = 0;
// Returns true if the menu host is visible.
virtual bool IsMenuHostVisible() = 0;
// Shows the menu host. If |do_capture| is true the menu host should do a
// mouse grab.
virtual void ShowMenuHost(bool do_capture) = 0;
// Hides the menu host.
virtual void HideMenuHost() = 0;
// Destroys and deletes the menu host.
virtual void DestroyMenuHost() = 0;
// Sets the bounds of the menu host.
virtual void SetMenuHostBounds(const gfx::Rect& bounds) = 0;
// Releases a mouse grab installed by |ShowMenuHost|.
virtual void ReleaseMenuHostCapture() = 0;
// Returns the native window of the MenuHost.
virtual gfx::NativeWindow GetMenuHostWindow() = 0;
protected:
virtual ~MenuHost() {}
};
} // namespace views
#endif // VIEWS_CONTROLS_MENU_MENU_HOST_H_

@@ -1,8 +1,7 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "views/controls/menu/menu_host_gtk.h" #include "views/controls/menu/menu_host_gtk.h"
#include <gdk/gdk.h> #include <gdk/gdk.h>
@@ -14,9 +13,14 @@
namespace views { namespace views {
MenuHost::MenuHost(SubmenuView* submenu) // static
MenuHost* MenuHost::Create(SubmenuView* submenu_view) {
return new MenuHostGtk(submenu_view);
}
MenuHostGtk::MenuHostGtk(SubmenuView* submenu)
: WidgetGtk(WidgetGtk::TYPE_POPUP), : WidgetGtk(WidgetGtk::TYPE_POPUP),
closed_(false), destroying_(false),
submenu_(submenu), submenu_(submenu),
did_pointer_grab_(false) { did_pointer_grab_(false) {
GdkEvent* event = gtk_get_current_event(); GdkEvent* event = gtk_get_current_event();
@@ -29,50 +33,92 @@ MenuHost::MenuHost(SubmenuView* submenu)
} }
} }
void MenuHost::Init(gfx::NativeWindow parent, MenuHostGtk::~MenuHostGtk() {
}
void MenuHostGtk::Init(gfx::NativeWindow parent,
const gfx::Rect& bounds, const gfx::Rect& bounds,
View* contents_view, View* contents_view,
bool do_capture) { bool do_capture) {
make_transient_to_parent();
WidgetGtk::Init(GTK_WIDGET(parent), bounds); WidgetGtk::Init(GTK_WIDGET(parent), bounds);
// Make sure we get destroyed when the parent is destroyed.
gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE);
gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()), gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()),
GDK_WINDOW_TYPE_HINT_MENU); GDK_WINDOW_TYPE_HINT_MENU);
SetContentsView(contents_view); SetContentsView(contents_view);
Show(); ShowMenuHost(do_capture);
}
bool MenuHostGtk::IsMenuHostVisible() {
return IsVisible();
}
void MenuHostGtk::ShowMenuHost(bool do_capture) {
WidgetGtk::Show();
if (do_capture) if (do_capture)
DoCapture(); DoCapture();
} }
gfx::NativeWindow MenuHost::GetNativeWindow() { void MenuHostGtk::HideMenuHost() {
// Make sure we release capture before hiding.
ReleaseMenuHostCapture();
WidgetGtk::Hide();
}
void MenuHostGtk::DestroyMenuHost() {
HideMenuHost();
destroying_ = true;
CloseNow();
}
void MenuHostGtk::SetMenuHostBounds(const gfx::Rect& bounds) {
SetBounds(bounds);
}
void MenuHostGtk::ReleaseMenuHostCapture() {
ReleaseGrab();
}
gfx::NativeWindow MenuHostGtk::GetMenuHostWindow() {
return GTK_WINDOW(GetNativeView()); return GTK_WINDOW(GetNativeView());
} }
void MenuHost::Show() { RootView* MenuHostGtk::CreateRootView() {
WidgetGtk::Show(); return new MenuHostRootView(this, submenu_);
} }
void MenuHost::Hide() { bool MenuHostGtk::ReleaseCaptureOnMouseReleased() {
if (closed_) { return false;
// We're already closed, nothing to do. }
// This is invoked twice if the first time just hid us, and the second
// time deleted Closed (deleted) us. void MenuHostGtk::ReleaseGrab() {
return; WidgetGtk::ReleaseGrab();
if (did_pointer_grab_) {
did_pointer_grab_ = false;
gdk_pointer_ungrab(GDK_CURRENT_TIME);
} }
// The menus are freed separately, and possibly before the window is closed,
// remove them so that View doesn't try to access deleted objects.
static_cast<MenuHostRootView*>(GetRootView())->suspend_events();
GetRootView()->RemoveAllChildViews(false);
ReleaseGrab();
closed_ = true;
WidgetGtk::Hide();
} }
void MenuHost::HideWindow() { void MenuHostGtk::OnDestroy(GtkWidget* object) {
// Make sure we release capture before hiding. if (!destroying_) {
ReleaseGrab(); // We weren't explicitly told to destroy ourselves, which means the menu was
WidgetGtk::Hide(); // deleted out from under us (the window we're parented to was closed). Tell
// the SubmenuView to drop references to us.
submenu_->MenuHostDestroyed();
}
WidgetGtk::OnDestroy(object);
} }
void MenuHost::DoCapture() { gboolean MenuHostGtk::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) {
// Grab breaking only happens when drag and drop starts. So, we don't try
// and ungrab or cancel the menu.
did_pointer_grab_ = false;
return WidgetGtk::OnGrabBrokeEvent(widget, event);
}
void MenuHostGtk::DoCapture() {
// Release the current grab. // Release the current grab.
GtkWidget* current_grab_window = gtk_grab_get_current(); GtkWidget* current_grab_window = gtk_grab_get_current();
if (current_grab_window) if (current_grab_window)
@@ -93,38 +139,6 @@ void MenuHost::DoCapture() {
did_pointer_grab_ = (grab_status == GDK_GRAB_SUCCESS); did_pointer_grab_ = (grab_status == GDK_GRAB_SUCCESS);
DCHECK(did_pointer_grab_); DCHECK(did_pointer_grab_);
// need keyboard grab. // need keyboard grab.
#ifdef DEBUG_MENU
DLOG(INFO) << "Doing capture";
#endif
}
void MenuHost::ReleaseCapture() {
ReleaseGrab();
}
RootView* MenuHost::CreateRootView() {
return new MenuHostRootView(this, submenu_);
}
gboolean MenuHost::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) {
// Grab breaking only happens when drag and drop starts. So, we don't try
// and ungrab or cancel the menu.
did_pointer_grab_ = false;
return WidgetGtk::OnGrabBrokeEvent(widget, event);
}
// Overriden to return false, we do NOT want to release capture on mouse
// release.
bool MenuHost::ReleaseCaptureOnMouseReleased() {
return false;
}
void MenuHost::ReleaseGrab() {
WidgetGtk::ReleaseGrab();
if (did_pointer_grab_) {
did_pointer_grab_ = false;
gdk_pointer_ungrab(GDK_CURRENT_TIME);
}
} }
} // namespace views } // namespace views

@@ -1,4 +1,4 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@@ -6,6 +6,7 @@
#ifndef VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_ #ifndef VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_
#define VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_ #define VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_
#include "views/controls/menu/menu_host.h"
#include "views/widget/widget_gtk.h" #include "views/widget/widget_gtk.h"
namespace views { namespace views {
@@ -13,28 +14,27 @@ namespace views {
class SubmenuView; class SubmenuView;
// MenuHost implementation for Gtk. // MenuHost implementation for Gtk.
class MenuHost : public WidgetGtk { class MenuHostGtk : public WidgetGtk, public MenuHost {
public: public:
explicit MenuHost(SubmenuView* submenu); explicit MenuHostGtk(SubmenuView* submenu);
virtual ~MenuHostGtk();
// MenuHost overrides.
void Init(gfx::NativeWindow parent, void Init(gfx::NativeWindow parent,
const gfx::Rect& bounds, const gfx::Rect& bounds,
View* contents_view, View* contents_view,
bool do_capture); bool do_capture);
virtual bool IsMenuHostVisible();
gfx::NativeWindow GetNativeWindow(); virtual void ShowMenuHost(bool do_capture);
virtual void HideMenuHost();
void Show(); virtual void DestroyMenuHost();
virtual void Hide(); virtual void SetMenuHostBounds(const gfx::Rect& bounds);
virtual void HideWindow(); virtual void ReleaseMenuHostCapture();
void DoCapture(); virtual gfx::NativeWindow GetMenuHostWindow();
void ReleaseCapture();
protected: protected:
virtual RootView* CreateRootView(); virtual RootView* CreateRootView();
virtual gboolean OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event);
// Overriden to return false, we do NOT want to release capture on mouse // Overriden to return false, we do NOT want to release capture on mouse
// release. // release.
virtual bool ReleaseCaptureOnMouseReleased(); virtual bool ReleaseCaptureOnMouseReleased();
@@ -42,9 +42,14 @@ class MenuHost : public WidgetGtk {
// Overriden to also release pointer grab. // Overriden to also release pointer grab.
virtual void ReleaseGrab(); virtual void ReleaseGrab();
virtual void OnDestroy(GtkWidget* object);
virtual gboolean OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event);
private: private:
// If true, we've been closed. void DoCapture();
bool closed_;
// If true, DestroyMenuHost has been invoked.
bool destroying_;
// The view we contain. // The view we contain.
SubmenuView* submenu_; SubmenuView* submenu_;
@@ -52,7 +57,7 @@ class MenuHost : public WidgetGtk {
// Have we done a pointer grab? // Have we done a pointer grab?
bool did_pointer_grab_; bool did_pointer_grab_;
DISALLOW_COPY_AND_ASSIGN(MenuHost); DISALLOW_COPY_AND_ASSIGN(MenuHostGtk);
}; };
} // namespace views } // namespace views

@@ -1,4 +1,4 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@@ -56,7 +56,7 @@ void MenuHostRootView::OnMouseReleased(const MouseEvent& event,
if (forward_drag_to_menu_controller_) { if (forward_drag_to_menu_controller_) {
forward_drag_to_menu_controller_ = false; forward_drag_to_menu_controller_ = false;
if (canceled) { if (canceled) {
GetMenuController()->Cancel(true); GetMenuController()->Cancel(MenuController::EXIT_ALL);
} else { } else {
GetMenuController()->OnMouseReleased(submenu_, event); GetMenuController()->OnMouseReleased(submenu_, event);
} }

@@ -12,8 +12,13 @@
namespace views { namespace views {
MenuHost::MenuHost(SubmenuView* submenu) // static
: closed_(false), MenuHost* MenuHost::Create(SubmenuView* submenu_view) {
return new MenuHostWin(submenu_view);
}
MenuHostWin::MenuHostWin(SubmenuView* submenu)
: destroying_(false),
submenu_(submenu), submenu_(submenu),
owns_capture_(false) { owns_capture_(false) {
set_window_style(WS_POPUP); set_window_style(WS_POPUP);
@@ -30,45 +35,69 @@ MenuHost::MenuHost(SubmenuView* submenu)
set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE); set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE);
} }
void MenuHost::Init(HWND parent, MenuHostWin::~MenuHostWin() {
const gfx::Rect& bounds, }
View* contents_view,
bool do_capture) { void MenuHostWin::Init(HWND parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture) {
WidgetWin::Init(parent, bounds); WidgetWin::Init(parent, bounds);
SetContentsView(contents_view); SetContentsView(contents_view);
Show(); ShowMenuHost(do_capture);
}
bool MenuHostWin::IsMenuHostVisible() {
return IsVisible();
}
void MenuHostWin::ShowMenuHost(bool do_capture) {
// We don't want to take focus away from the hosting window.
ShowWindow(SW_SHOWNA);
if (do_capture) if (do_capture)
DoCapture(); DoCapture();
} }
void MenuHost::Show() { void MenuHostWin::HideMenuHost() {
// We don't want to take focus away from the hosting window.
ShowWindow(SW_SHOWNA);
}
void MenuHost::Hide() {
if (closed_) {
// We're already closed, nothing to do.
// This is invoked twice if the first time just hid us, and the second
// time deleted Closed (deleted) us.
return;
}
// The menus are freed separately, and possibly before the window is closed,
// remove them so that View doesn't try to access deleted objects.
static_cast<MenuHostRootView*>(GetRootView())->suspend_events();
GetRootView()->RemoveAllChildViews(false);
closed_ = true;
ReleaseCapture();
WidgetWin::Hide();
}
void MenuHost::HideWindow() {
// Make sure we release capture before hiding. // Make sure we release capture before hiding.
ReleaseCapture(); ReleaseMenuHostCapture();
WidgetWin::Hide(); WidgetWin::Hide();
} }
void MenuHost::OnCaptureChanged(HWND hwnd) { void MenuHostWin::DestroyMenuHost() {
HideMenuHost();
destroying_ = true;
CloseNow();
}
void MenuHostWin::SetMenuHostBounds(const gfx::Rect& bounds) {
SetBounds(bounds);
}
void MenuHostWin::ReleaseMenuHostCapture() {
if (owns_capture_) {
owns_capture_ = false;
::ReleaseCapture();
}
}
gfx::NativeWindow MenuHostWin::GetMenuHostWindow() {
return GetNativeView();
}
void MenuHostWin::OnDestroy() {
if (!destroying_) {
// We weren't explicitly told to destroy ourselves, which means the menu was
// deleted out from under us (the window we're parented to was closed). Tell
// the SubmenuView to drop references to us.
submenu_->MenuHostDestroyed();
}
WidgetWin::OnDestroy();
}
void MenuHostWin::OnCaptureChanged(HWND hwnd) {
WidgetWin::OnCaptureChanged(hwnd); WidgetWin::OnCaptureChanged(hwnd);
owns_capture_ = false; owns_capture_ = false;
#ifdef DEBUG_MENU #ifdef DEBUG_MENU
@@ -76,7 +105,20 @@ void MenuHost::OnCaptureChanged(HWND hwnd) {
#endif #endif
} }
void MenuHost::DoCapture() { void MenuHostWin::OnCancelMode() {
submenu_->GetMenuItem()->GetMenuController()->Cancel(
MenuController::EXIT_ALL);
}
RootView* MenuHostWin::CreateRootView() {
return new MenuHostRootView(this, submenu_);
}
bool MenuHostWin::ReleaseCaptureOnMouseReleased() {
return false;
}
void MenuHostWin::DoCapture() {
owns_capture_ = true; owns_capture_ = true;
SetCapture(); SetCapture();
has_capture_ = true; has_capture_ = true;
@@ -85,33 +127,4 @@ void MenuHost::DoCapture() {
#endif #endif
} }
void MenuHost::ReleaseCapture() {
if (owns_capture_) {
#ifdef DEBUG_MENU
DLOG(INFO) << "released capture";
#endif
owns_capture_ = false;
::ReleaseCapture();
}
}
RootView* MenuHost::CreateRootView() {
return new MenuHostRootView(this, submenu_);
}
void MenuHost::OnCancelMode() {
if (!closed_) {
#ifdef DEBUG_MENU
DLOG(INFO) << "OnCanceMode, closing menu";
#endif
submenu_->GetMenuItem()->GetMenuController()->Cancel(true);
}
}
// Overriden to return false, we do NOT want to release capture on mouse
// release.
bool MenuHost::ReleaseCaptureOnMouseReleased() {
return false;
}
} // namespace views } // namespace views

@@ -6,6 +6,7 @@
#ifndef VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_ #ifndef VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_
#define VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_ #define VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_
#include "views/controls/menu/menu_host.h"
#include "views/widget/widget_win.h" #include "views/widget/widget_win.h"
namespace views { namespace views {
@@ -13,36 +14,41 @@ namespace views {
class SubmenuView; class SubmenuView;
// MenuHost implementation for windows. // MenuHost implementation for windows.
class MenuHost : public WidgetWin { class MenuHostWin : public WidgetWin, public MenuHost {
public: public:
explicit MenuHost(SubmenuView* submenu); explicit MenuHostWin(SubmenuView* submenu);
virtual ~MenuHostWin();
void Init(HWND parent, // MenuHost overrides:
const gfx::Rect& bounds, virtual void Init(HWND parent,
View* contents_view, const gfx::Rect& bounds,
bool do_capture); View* contents_view,
bool do_capture);
virtual bool IsMenuHostVisible();
virtual void ShowMenuHost(bool do_capture);
virtual void HideMenuHost();
virtual void DestroyMenuHost();
virtual void SetMenuHostBounds(const gfx::Rect& bounds);
virtual void ReleaseMenuHostCapture();
virtual gfx::NativeWindow GetMenuHostWindow();
gfx::NativeWindow GetNativeWindow() { return GetNativeView(); } // WidgetWin overrides:
virtual void OnDestroy();
void Show();
virtual void Hide();
virtual void HideWindow();
virtual void OnCaptureChanged(HWND hwnd); virtual void OnCaptureChanged(HWND hwnd);
void DoCapture(); virtual void OnCancelMode();
void ReleaseCapture();
protected: protected:
virtual RootView* CreateRootView(); virtual RootView* CreateRootView();
virtual void OnCancelMode();
// Overriden to return false, we do NOT want to release capture on mouse // Overriden to return false, we do NOT want to release capture on mouse
// release. // release.
virtual bool ReleaseCaptureOnMouseReleased(); virtual bool ReleaseCaptureOnMouseReleased();
private: private:
// If true, we've been closed. void DoCapture();
bool closed_;
// If true, DestroyMenuHost has been invoked.
bool destroying_;
// If true, we own the capture and need to release it. // If true, we own the capture and need to release it.
bool owns_capture_; bool owns_capture_;
@@ -50,7 +56,7 @@ class MenuHost : public WidgetWin {
// The view we contain. // The view we contain.
SubmenuView* submenu_; SubmenuView* submenu_;
DISALLOW_COPY_AND_ASSIGN(MenuHost); DISALLOW_COPY_AND_ASSIGN(MenuHostWin);
}; };
} // namespace views } // namespace views

@@ -98,7 +98,7 @@ void MenuItemView::RunMenuAt(gfx::NativeWindow parent,
// A menu is already showing, but it isn't a blocking menu. Cancel it. // A menu is already showing, but it isn't a blocking menu. Cancel it.
// We can get here during drag and drop if the user right clicks on the // We can get here during drag and drop if the user right clicks on the
// menu quickly after the drop. // menu quickly after the drop.
controller->Cancel(true); controller->Cancel(MenuController::EXIT_ALL);
controller = NULL; controller = NULL;
} }
bool owns_controller = false; bool owns_controller = false;
@@ -145,7 +145,7 @@ void MenuItemView::RunMenuForDropAt(gfx::NativeWindow parent,
// If there is a menu, hide it so that only one menu is shown during dnd. // If there is a menu, hide it so that only one menu is shown during dnd.
MenuController* current_controller = MenuController::GetActiveInstance(); MenuController* current_controller = MenuController::GetActiveInstance();
if (current_controller) { if (current_controller) {
current_controller->Cancel(true); current_controller->Cancel(MenuController::EXIT_ALL);
} }
// Always create a new controller for non-blocking. // Always create a new controller for non-blocking.
@@ -160,7 +160,7 @@ void MenuItemView::RunMenuForDropAt(gfx::NativeWindow parent,
void MenuItemView::Cancel() { void MenuItemView::Cancel() {
if (controller_ && !canceled_) { if (controller_ && !canceled_) {
canceled_ = true; canceled_ = true;
controller_->Cancel(true); controller_->Cancel(MenuController::EXIT_ALL);
} }
} }

@@ -6,15 +6,10 @@
#include "gfx/canvas.h" #include "gfx/canvas.h"
#include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_controller.h"
#include "views/controls/menu/menu_host.h"
#include "views/controls/menu/menu_scroll_view_container.h" #include "views/controls/menu/menu_scroll_view_container.h"
#include "views/widget/root_view.h" #include "views/widget/root_view.h"
#if defined(OS_WIN)
#include "views/controls/menu/menu_host_win.h"
#elif defined(OS_LINUX)
#include "views/controls/menu/menu_host_gtk.h"
#endif
// Height of the drop indicator. This should be an even number. // Height of the drop indicator. This should be an even number.
static const int kDropIndicatorHeight = 2; static const int kDropIndicatorHeight = 2;
@@ -208,20 +203,18 @@ bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) {
} }
bool SubmenuView::IsShowing() { bool SubmenuView::IsShowing() {
return host_ && host_->IsVisible(); return host_ && host_->IsMenuHostVisible();
} }
void SubmenuView::ShowAt(gfx::NativeWindow parent, void SubmenuView::ShowAt(gfx::NativeWindow parent,
const gfx::Rect& bounds, const gfx::Rect& bounds,
bool do_capture) { bool do_capture) {
if (host_) { if (host_) {
host_->Show(); host_->ShowMenuHost(do_capture);
if (do_capture)
host_->DoCapture();
return; return;
} }
host_ = new MenuHost(this); host_ = MenuHost::Create(this);
// Force construction of the scroll view container. // Force construction of the scroll view container.
GetScrollViewContainer(); GetScrollViewContainer();
// Make sure the first row is visible. // Make sure the first row is visible.
@@ -230,23 +223,25 @@ void SubmenuView::ShowAt(gfx::NativeWindow parent,
} }
void SubmenuView::Reposition(const gfx::Rect& bounds) { void SubmenuView::Reposition(const gfx::Rect& bounds) {
host_->SetBounds(bounds); if (host_)
host_->SetMenuHostBounds(bounds);
} }
void SubmenuView::Close() { void SubmenuView::Close() {
if (host_) { if (host_) {
host_->Close(); host_->DestroyMenuHost();
host_ = NULL; host_ = NULL;
} }
} }
void SubmenuView::Hide() { void SubmenuView::Hide() {
if (host_) if (host_)
host_->HideWindow(); host_->HideMenuHost();
} }
void SubmenuView::ReleaseCapture() { void SubmenuView::ReleaseCapture() {
host_->ReleaseCapture(); if (host_)
host_->ReleaseMenuHostCapture();
} }
bool SubmenuView::SkipDefaultKeyEventProcessing(const views::KeyEvent& e) { bool SubmenuView::SkipDefaultKeyEventProcessing(const views::KeyEvent& e) {
@@ -282,7 +277,12 @@ MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() {
} }
gfx::NativeWindow SubmenuView::native_window() const { gfx::NativeWindow SubmenuView::native_window() const {
return host_ ? host_->GetNativeWindow() : NULL; return host_ ? host_->GetMenuHostWindow() : NULL;
}
void SubmenuView::MenuHostDestroyed() {
host_ = NULL;
GetMenuItem()->GetMenuController()->Cancel(MenuController::EXIT_DESTROYED);
} }
void SubmenuView::PaintDropIndicator(gfx::Canvas* canvas, void SubmenuView::PaintDropIndicator(gfx::Canvas* canvas,

@@ -1,4 +1,4 @@
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@@ -118,6 +118,11 @@ class SubmenuView : public View {
// Returns the NativeWindow host of the menu, or NULL if not showing. // Returns the NativeWindow host of the menu, or NULL if not showing.
gfx::NativeWindow native_window() const; gfx::NativeWindow native_window() const;
// Invoked if the menu is prematurely destroyed. This can happen if the window
// closes while the menu is shown. If invoked the SubmenuView must drop all
// references to the MenuHost as the MenuHost is about to be deleted.
void MenuHostDestroyed();
// Padding around the edges of the submenu. // Padding around the edges of the submenu.
static const int kSubmenuBorderSize; static const int kSubmenuBorderSize;
@@ -138,7 +143,8 @@ class SubmenuView : public View {
// Parent menu item. // Parent menu item.
MenuItemView* parent_menu_item_; MenuItemView* parent_menu_item_;
// Widget subclass used to show the children. // Widget subclass used to show the children. This is deleted when we invoke
// |DestroyMenuHost|, or |MenuHostDestroyed| is invoked back on us.
MenuHost* host_; MenuHost* host_;
// If non-null, indicates a drop is in progress and drop_item is the item // If non-null, indicates a drop is in progress and drop_item is the item

@@ -687,6 +687,8 @@ class TestViewsDelegate : public views::ViewsDelegate {
virtual HICON GetDefaultWindowIcon() const { virtual HICON GetDefaultWindowIcon() const {
return NULL; return NULL;
} }
virtual void AddRef() {}
virtual void ReleaseRef() {}
private: private:
mutable scoped_ptr<Clipboard> clipboard_; mutable scoped_ptr<Clipboard> clipboard_;

@@ -111,6 +111,7 @@
'controls/menu/menu_delegate.h', 'controls/menu/menu_delegate.h',
'controls/menu/menu_gtk.cc', 'controls/menu/menu_gtk.cc',
'controls/menu/menu_gtk.h', 'controls/menu/menu_gtk.h',
'controls/menu/menu_host.h',
'controls/menu/menu_host_root_view.cc', 'controls/menu/menu_host_root_view.cc',
'controls/menu/menu_host_root_view.h', 'controls/menu/menu_host_root_view.h',
'controls/menu/menu_host_win.cc', 'controls/menu/menu_host_win.cc',

@@ -1,4 +1,4 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this // Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this
// source code is governed by a BSD-style license that can be found in the // source code is governed by a BSD-style license that can be found in the
// LICENSE file. // LICENSE file.
@@ -49,6 +49,11 @@ class ViewsDelegate {
virtual HICON GetDefaultWindowIcon() const = 0; virtual HICON GetDefaultWindowIcon() const = 0;
#endif #endif
// AddRef/ReleaseRef are invoked while a menu is visible. They are used to
// ensure we don't attempt to exit while a menu is showing.
virtual void AddRef() = 0;
virtual void ReleaseRef() = 0;
// The active ViewsDelegate used by the views system. // The active ViewsDelegate used by the views system.
static ViewsDelegate* views_delegate; static ViewsDelegate* views_delegate;
}; };

@@ -989,6 +989,9 @@ gboolean WidgetGtk::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) {
} }
void WidgetGtk::OnGrabNotify(GtkWidget* widget, gboolean was_grabbed) { void WidgetGtk::OnGrabNotify(GtkWidget* widget, gboolean was_grabbed) {
if (!window_contents_)
return; // Grab broke after window destroyed, don't try processing it.
gtk_grab_remove(window_contents_); gtk_grab_remove(window_contents_);
HandleGrabBroke(); HandleGrabBroke();
} }