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();
if (model_)
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();
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
// found in the LICENSE file.
@ -22,6 +22,7 @@
#include "views/controls/menu/menu_controller.h"
#include "views/controls/menu/menu_item_view.h"
#include "views/controls/menu/submenu_view.h"
#include "views/views_delegate.h"
#include "views/window/window.h"
#if defined(OS_LINUX)
@ -45,6 +46,37 @@
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.
class TestingPageNavigator : public PageNavigator {
public:
@ -148,9 +180,14 @@ class BookmarkBarViewEventTestBase : public ViewEventTestBase {
views::MenuItemView::allow_task_nesting_during_run_ = false;
ViewEventTestBase::TearDown();
BookmarkBarView::testing_ = false;
views::ViewsDelegate::views_delegate = NULL;
}
protected:
void InstallViewsDelegate() {
views::ViewsDelegate::views_delegate = &views_delegate_;
}
virtual views::View* CreateContentsView() {
return bb_view_;
}
@ -200,6 +237,7 @@ class BookmarkBarViewEventTestBase : public ViewEventTestBase {
scoped_ptr<TestingProfile> profile_;
ChromeThread ui_thread_;
ChromeThread file_thread_;
ViewsDelegateImpl views_delegate_;
};
// 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_);
// Hide menu.
menu->GetMenuController()->Cancel(true);
menu->GetMenuController()->CancelAll();
Done();
}
@ -784,7 +822,7 @@ class BookmarkBarViewTest9 : public BookmarkBarViewEventTestBase {
ASSERT_NE(start_y_, menu_loc.y());
// Hide menu.
bb_view_->GetMenu()->GetMenuController()->Cancel(true);
bb_view_->GetMenu()->GetMenuController()->CancelAll();
// On linux, Cancelling menu will call Quit on the message loop,
// 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.
ASSERT_TRUE(menu->GetMenuItemByID(deleted_menu_id_) == NULL);
bb_view_->GetMenu()->GetMenuController()->Cancel(true);
bb_view_->GetMenu()->GetMenuController()->CancelAll();
Done();
}
@ -1265,3 +1303,36 @@ class BookmarkBarViewTest15 : public BookmarkBarViewEventTestBase {
};
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));
}
#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)
virtual HICON GetDefaultWindowIcon() const;
#endif
virtual void AddRef();
virtual void ReleaseRef();
private:
DISALLOW_COPY_AND_ASSIGN(ChromeViewsDelegate);

@ -49,11 +49,14 @@ MenuButton::MenuButton(ButtonListener* listener,
menu_delegate_(menu_delegate),
show_menu_marker_(show_menu_marker),
menu_marker_(ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_MENU_DROPARROW)) {
IDR_MENU_DROPARROW)),
destroyed_flag_(NULL) {
set_alignment(TextButton::ALIGN_LEFT);
}
MenuButton::~MenuButton() {
if (destroyed_flag_)
*destroyed_flag_ = true;
}
////////////////////////////////////////////////////////////////////////////////
@ -145,7 +148,17 @@ bool MenuButton::Activate() {
GetRootView()->SetMouseHandler(NULL);
menu_visible_ = true;
bool destroyed = false;
destroyed_flag_ = &destroyed;
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_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
// found in the LICENSE file.
@ -97,6 +97,10 @@ class MenuButton : public TextButton {
// text buttons.
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);
};

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

@ -38,6 +38,22 @@ class MenuController : public MessageLoopForUI::Dispatcher {
friend class MenuHostRootView;
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.
static MenuController* GetActiveInstance();
@ -66,12 +82,12 @@ class MenuController : public MessageLoopForUI::Dispatcher {
bool open_submenu,
bool update_immediately);
// Cancels the current Run. If all is true, any nested loops are canceled
// as well. This immediately hides all menus.
void Cancel(bool all);
// Cancels the current Run. See ExitType for a description of what happens
// with the various parameters.
void Cancel(ExitType type);
// An alternative to Cancel(true) that can be used with a OneShotTimer.
void CancelAll() { return Cancel(true); }
// An alternative to Cancel(EXIT_ALL) that can be used with a OneShotTimer.
void CancelAll() { Cancel(EXIT_ALL); }
// Various events, forwarded from the submenu.
//
@ -98,18 +114,6 @@ class MenuController : public MessageLoopForUI::Dispatcher {
void OnDragExitedScrollButton(SubmenuView* source);
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;
// 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
// found in the LICENSE file.
#include "views/controls/menu/menu_host_gtk.h"
#include <gdk/gdk.h>
@ -14,9 +13,14 @@
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),
closed_(false),
destroying_(false),
submenu_(submenu),
did_pointer_grab_(false) {
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,
View* contents_view,
bool do_capture) {
make_transient_to_parent();
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()),
GDK_WINDOW_TYPE_HINT_MENU);
SetContentsView(contents_view);
Show();
ShowMenuHost(do_capture);
}
bool MenuHostGtk::IsMenuHostVisible() {
return IsVisible();
}
void MenuHostGtk::ShowMenuHost(bool do_capture) {
WidgetGtk::Show();
if (do_capture)
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());
}
void MenuHost::Show() {
WidgetGtk::Show();
RootView* MenuHostGtk::CreateRootView() {
return new MenuHostRootView(this, submenu_);
}
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;
bool MenuHostGtk::ReleaseCaptureOnMouseReleased() {
return false;
}
void MenuHostGtk::ReleaseGrab() {
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() {
// Make sure we release capture before hiding.
ReleaseGrab();
WidgetGtk::Hide();
void MenuHostGtk::OnDestroy(GtkWidget* object) {
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();
}
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.
GtkWidget* current_grab_window = gtk_grab_get_current();
if (current_grab_window)
@ -93,38 +139,6 @@ void MenuHost::DoCapture() {
did_pointer_grab_ = (grab_status == GDK_GRAB_SUCCESS);
DCHECK(did_pointer_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

@ -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
// found in the LICENSE file.
@ -6,6 +6,7 @@
#ifndef 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"
namespace views {
@ -13,28 +14,27 @@ namespace views {
class SubmenuView;
// MenuHost implementation for Gtk.
class MenuHost : public WidgetGtk {
class MenuHostGtk : public WidgetGtk, public MenuHost {
public:
explicit MenuHost(SubmenuView* submenu);
explicit MenuHostGtk(SubmenuView* submenu);
virtual ~MenuHostGtk();
// MenuHost overrides.
void Init(gfx::NativeWindow parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture);
gfx::NativeWindow GetNativeWindow();
void Show();
virtual void Hide();
virtual void HideWindow();
void DoCapture();
void ReleaseCapture();
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();
protected:
virtual RootView* CreateRootView();
virtual gboolean OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event);
// Overriden to return false, we do NOT want to release capture on mouse
// release.
virtual bool ReleaseCaptureOnMouseReleased();
@ -42,9 +42,14 @@ class MenuHost : public WidgetGtk {
// Overriden to also release pointer grab.
virtual void ReleaseGrab();
virtual void OnDestroy(GtkWidget* object);
virtual gboolean OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event);
private:
// If true, we've been closed.
bool closed_;
void DoCapture();
// If true, DestroyMenuHost has been invoked.
bool destroying_;
// The view we contain.
SubmenuView* submenu_;
@ -52,7 +57,7 @@ class MenuHost : public WidgetGtk {
// Have we done a pointer grab?
bool did_pointer_grab_;
DISALLOW_COPY_AND_ASSIGN(MenuHost);
DISALLOW_COPY_AND_ASSIGN(MenuHostGtk);
};
} // 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
// found in the LICENSE file.
@ -56,7 +56,7 @@ void MenuHostRootView::OnMouseReleased(const MouseEvent& event,
if (forward_drag_to_menu_controller_) {
forward_drag_to_menu_controller_ = false;
if (canceled) {
GetMenuController()->Cancel(true);
GetMenuController()->Cancel(MenuController::EXIT_ALL);
} else {
GetMenuController()->OnMouseReleased(submenu_, event);
}

@ -12,8 +12,13 @@
namespace views {
MenuHost::MenuHost(SubmenuView* submenu)
: closed_(false),
// static
MenuHost* MenuHost::Create(SubmenuView* submenu_view) {
return new MenuHostWin(submenu_view);
}
MenuHostWin::MenuHostWin(SubmenuView* submenu)
: destroying_(false),
submenu_(submenu),
owns_capture_(false) {
set_window_style(WS_POPUP);
@ -30,45 +35,69 @@ MenuHost::MenuHost(SubmenuView* submenu)
set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE);
}
void MenuHost::Init(HWND parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture) {
MenuHostWin::~MenuHostWin() {
}
void MenuHostWin::Init(HWND parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture) {
WidgetWin::Init(parent, bounds);
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)
DoCapture();
}
void MenuHost::Show() {
// 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() {
void MenuHostWin::HideMenuHost() {
// Make sure we release capture before hiding.
ReleaseCapture();
ReleaseMenuHostCapture();
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);
owns_capture_ = false;
#ifdef DEBUG_MENU
@ -76,7 +105,20 @@ void MenuHost::OnCaptureChanged(HWND hwnd) {
#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;
SetCapture();
has_capture_ = true;
@ -85,33 +127,4 @@ void MenuHost::DoCapture() {
#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

@ -6,6 +6,7 @@
#ifndef 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"
namespace views {
@ -13,36 +14,41 @@ namespace views {
class SubmenuView;
// MenuHost implementation for windows.
class MenuHost : public WidgetWin {
class MenuHostWin : public WidgetWin, public MenuHost {
public:
explicit MenuHost(SubmenuView* submenu);
explicit MenuHostWin(SubmenuView* submenu);
virtual ~MenuHostWin();
void Init(HWND parent,
const gfx::Rect& bounds,
View* contents_view,
bool do_capture);
// MenuHost overrides:
virtual void Init(HWND parent,
const gfx::Rect& bounds,
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(); }
void Show();
virtual void Hide();
virtual void HideWindow();
// WidgetWin overrides:
virtual void OnDestroy();
virtual void OnCaptureChanged(HWND hwnd);
void DoCapture();
void ReleaseCapture();
virtual void OnCancelMode();
protected:
virtual RootView* CreateRootView();
virtual void OnCancelMode();
// Overriden to return false, we do NOT want to release capture on mouse
// release.
virtual bool ReleaseCaptureOnMouseReleased();
private:
// If true, we've been closed.
bool closed_;
void DoCapture();
// If true, DestroyMenuHost has been invoked.
bool destroying_;
// If true, we own the capture and need to release it.
bool owns_capture_;
@ -50,7 +56,7 @@ class MenuHost : public WidgetWin {
// The view we contain.
SubmenuView* submenu_;
DISALLOW_COPY_AND_ASSIGN(MenuHost);
DISALLOW_COPY_AND_ASSIGN(MenuHostWin);
};
} // 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.
// We can get here during drag and drop if the user right clicks on the
// menu quickly after the drop.
controller->Cancel(true);
controller->Cancel(MenuController::EXIT_ALL);
controller = NULL;
}
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.
MenuController* current_controller = MenuController::GetActiveInstance();
if (current_controller) {
current_controller->Cancel(true);
current_controller->Cancel(MenuController::EXIT_ALL);
}
// Always create a new controller for non-blocking.
@ -160,7 +160,7 @@ void MenuItemView::RunMenuForDropAt(gfx::NativeWindow parent,
void MenuItemView::Cancel() {
if (controller_ && !canceled_) {
canceled_ = true;
controller_->Cancel(true);
controller_->Cancel(MenuController::EXIT_ALL);
}
}

@ -6,15 +6,10 @@
#include "gfx/canvas.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/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.
static const int kDropIndicatorHeight = 2;
@ -208,20 +203,18 @@ bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) {
}
bool SubmenuView::IsShowing() {
return host_ && host_->IsVisible();
return host_ && host_->IsMenuHostVisible();
}
void SubmenuView::ShowAt(gfx::NativeWindow parent,
const gfx::Rect& bounds,
bool do_capture) {
if (host_) {
host_->Show();
if (do_capture)
host_->DoCapture();
host_->ShowMenuHost(do_capture);
return;
}
host_ = new MenuHost(this);
host_ = MenuHost::Create(this);
// Force construction of the scroll view container.
GetScrollViewContainer();
// Make sure the first row is visible.
@ -230,23 +223,25 @@ void SubmenuView::ShowAt(gfx::NativeWindow parent,
}
void SubmenuView::Reposition(const gfx::Rect& bounds) {
host_->SetBounds(bounds);
if (host_)
host_->SetMenuHostBounds(bounds);
}
void SubmenuView::Close() {
if (host_) {
host_->Close();
host_->DestroyMenuHost();
host_ = NULL;
}
}
void SubmenuView::Hide() {
if (host_)
host_->HideWindow();
host_->HideMenuHost();
}
void SubmenuView::ReleaseCapture() {
host_->ReleaseCapture();
if (host_)
host_->ReleaseMenuHostCapture();
}
bool SubmenuView::SkipDefaultKeyEventProcessing(const views::KeyEvent& e) {
@ -282,7 +277,12 @@ MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() {
}
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,

@ -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
// 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.
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.
static const int kSubmenuBorderSize;
@ -138,7 +143,8 @@ class SubmenuView : public View {
// 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_;
// 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 {
return NULL;
}
virtual void AddRef() {}
virtual void ReleaseRef() {}
private:
mutable scoped_ptr<Clipboard> clipboard_;

@ -111,6 +111,7 @@
'controls/menu/menu_delegate.h',
'controls/menu/menu_gtk.cc',
'controls/menu/menu_gtk.h',
'controls/menu/menu_host.h',
'controls/menu/menu_host_root_view.cc',
'controls/menu/menu_host_root_view.h',
'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
// LICENSE file.
@ -49,6 +49,11 @@ class ViewsDelegate {
virtual HICON GetDefaultWindowIcon() const = 0;
#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.
static ViewsDelegate* views_delegate;
};

@ -989,6 +989,9 @@ gboolean WidgetGtk::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) {
}
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_);
HandleGrabBroke();
}