Commit 12c05943 authored by mukai@chromium.org's avatar mukai@chromium.org

Add EventFilter for LauncherTooltip (2nd try).

Now it monitors mouse/touch/gesture events and hides the tooltip if necessary.
Previous CL (crrev.com/148079) was reverted since it causes use-after-free error.
It happens when the 'LauncherTooltipBubble' widget exists but LauncherTooltipManager
is already released.  In that case, the widget's release will cause WindowClosing()
which calls host_->OnBubbleClosed(), but host_ is already released.
Thus I'd make sure to introduce a method to set host_ to NULL in case that
LauncherTooltipManager explicitly clean-up the Bubble.

R=derat@chromium.org
BUG=137678
TEST=manually checked on linux, aura_shell_unittests passed with asan


Review URL: https://chromiumcodereview.appspot.com/10808102

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148308 0039d316-1c4b-4281-b951-d872f2087c98
parent de6bb903
......@@ -4,6 +4,7 @@
#include "ash/launcher/launcher_tooltip_manager.h"
#include "ash/launcher/launcher_view.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
......@@ -11,7 +12,10 @@
#include "base/message_loop.h"
#include "base/time.h"
#include "base/timer.h"
#include "ui/aura/event.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/base/events.h"
#include "ui/gfx/insets.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/controls/label.h"
......@@ -51,11 +55,9 @@ class LauncherTooltipManager::LauncherTooltipBubble
LauncherTooltipManager* host);
void SetText(const string16& text);
void Close();
private:
// views::View overrides:
virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE;
// views::WidgetDelegate overrides;
virtual void WindowClosing() OVERRIDE;
......@@ -95,31 +97,41 @@ void LauncherTooltipManager::LauncherTooltipBubble::SetText(
SizeToContents();
}
void LauncherTooltipManager::LauncherTooltipBubble::OnMouseExited(
const views::MouseEvent& event) {
GetWidget()->Close();
host_->OnBubbleClosed(this);
void LauncherTooltipManager::LauncherTooltipBubble::Close() {
if (GetWidget()) {
host_ = NULL;
GetWidget()->Close();
}
}
void LauncherTooltipManager::LauncherTooltipBubble::WindowClosing() {
views::BubbleDelegateView::WindowClosing();
host_->OnBubbleClosed(this);
if (host_)
host_->OnBubbleClosed(this);
}
LauncherTooltipManager::LauncherTooltipManager(
ShelfAlignment alignment, ShelfLayoutManager* shelf_layout_manager)
ShelfAlignment alignment,
ShelfLayoutManager* shelf_layout_manager,
LauncherView* launcher_view)
: view_(NULL),
widget_(NULL),
anchor_(NULL),
alignment_(alignment),
shelf_layout_manager_(shelf_layout_manager) {
shelf_layout_manager_(shelf_layout_manager),
launcher_view_(launcher_view) {
if (shelf_layout_manager)
shelf_layout_manager->AddObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->AddEnvEventFilter(this);
}
LauncherTooltipManager::~LauncherTooltipManager() {
Close();
if (shelf_layout_manager_)
shelf_layout_manager_->RemoveObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->RemoveEnvEventFilter(this);
}
void LauncherTooltipManager::ShowDelayed(views::View* anchor,
......@@ -135,7 +147,7 @@ void LauncherTooltipManager::ShowDelayed(views::View* anchor,
return;
CreateBubble(anchor, text);
gfx::NativeView native_view = view_->GetWidget()->GetNativeView();
gfx::NativeView native_view = widget_->GetNativeView();
SetWindowVisibilityAnimationType(
native_view, WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
SetWindowVisibilityAnimationTransition(native_view, ANIMATE_SHOW);
......@@ -154,22 +166,24 @@ void LauncherTooltipManager::ShowImmediately(views::View* anchor,
return;
CreateBubble(anchor, text);
gfx::NativeView native_view = view_->GetWidget()->GetNativeView();
gfx::NativeView native_view = widget_->GetNativeView();
SetWindowVisibilityAnimationTransition(native_view, ANIMATE_NONE);
ShowInternal();
}
void LauncherTooltipManager::Close() {
if (view_) {
view_->GetWidget()->Close();
view_->Close();
view_ = NULL;
widget_ = NULL;
}
}
void LauncherTooltipManager::OnBubbleClosed(
views::BubbleDelegateView* view) {
if (view == view_)
void LauncherTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
if (view == view_) {
view_ = NULL;
widget_ = NULL;
}
}
void LauncherTooltipManager::SetArrowLocation(ShelfAlignment alignment) {
......@@ -211,7 +225,66 @@ bool LauncherTooltipManager::IsVisible() {
if (timer_.get() && timer_->IsRunning())
return false;
return view_ && view_->GetWidget() && view_->GetWidget()->IsVisible();
return widget_ && widget_->IsVisible();
}
bool LauncherTooltipManager::PreHandleKeyEvent(aura::Window* target,
aura::KeyEvent* event) {
// Not handled.
return false;
}
bool LauncherTooltipManager::PreHandleMouseEvent(aura::Window* target,
aura::MouseEvent* event) {
DCHECK(target);
DCHECK(event);
if (!widget_ || !widget_->IsVisible())
return false;
DCHECK(view_);
DCHECK(launcher_view_);
if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) {
CloseSoon();
return false;
}
gfx::Point location_in_launcher_view = event->location();
aura::Window::ConvertPointToWindow(
target, launcher_view_->GetWidget()->GetNativeWindow(),
&location_in_launcher_view);
gfx::Point location_on_screen = event->location();
aura::Window::ConvertPointToWindow(
target, target->GetRootWindow(), &location_on_screen);
gfx::Rect bubble_rect = widget_->GetWindowBoundsInScreen();
if (launcher_view_->ShouldHideTooltip(location_in_launcher_view) &&
!bubble_rect.Contains(location_on_screen)) {
// Because this mouse event may arrive to |view_|, here we just schedule
// the closing event rather than directly calling Close().
CloseSoon();
}
return false;
}
ui::TouchStatus LauncherTooltipManager::PreHandleTouchEvent(
aura::Window* target, aura::TouchEvent* event) {
if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target)
Close();
return ui::TOUCH_STATUS_UNKNOWN;
}
ui::GestureStatus LauncherTooltipManager::PreHandleGestureEvent(
aura::Window* target, aura::GestureEvent* event) {
if (widget_ && widget_->IsVisible()) {
// Because this mouse event may arrive to |view_|, here we just schedule
// the closing event rather than directly calling Close().
CloseSoon();
}
return ui::GESTURE_STATUS_UNKNOWN;
}
void LauncherTooltipManager::WillDeleteShelf() {
......@@ -233,12 +306,16 @@ void LauncherTooltipManager::OnAutoHideStateChanged(
// AutoHide state change happens during an event filter, so immediate close
// may cause a crash in the HandleMouseEvent() after the filter. So we just
// schedule the Close here.
MessageLoopForUI::current()->PostTask(
FROM_HERE,
base::Bind(&LauncherTooltipManager::Close, base::Unretained(this)));
CloseSoon();
}
}
void LauncherTooltipManager::CloseSoon() {
MessageLoopForUI::current()->PostTask(
FROM_HERE,
base::Bind(&LauncherTooltipManager::Close, base::Unretained(this)));
}
void LauncherTooltipManager::ShowInternal() {
if (view_)
view_->Show();
......@@ -255,6 +332,7 @@ void LauncherTooltipManager::CreateBubble(views::View* anchor,
view_ = new LauncherTooltipBubble(
anchor, GetArrowLocation(alignment_), this);
views::BubbleDelegateView::CreateBubble(view_);
widget_ = view_->GetWidget();
view_->SetText(text_);
}
......
......@@ -10,6 +10,7 @@
#include "ash/wm/shelf_types.h"
#include "base/basictypes.h"
#include "base/string16.h"
#include "ui/aura/event_filter.h"
#include "ui/gfx/rect.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_delegate.h"
......@@ -30,13 +31,16 @@ class LauncherViewTest;
}
namespace internal {
class LauncherView;
// LauncherTooltipManager manages the tooltip balloon poping up on launcher
// items.
class ASH_EXPORT LauncherTooltipManager : public ShelfLayoutManager::Observer {
class ASH_EXPORT LauncherTooltipManager : public aura::EventFilter,
public ShelfLayoutManager::Observer {
public:
LauncherTooltipManager(ShelfAlignment alignment,
ShelfLayoutManager* shelf_layout_manager);
ShelfLayoutManager* shelf_layout_manager,
LauncherView* launcher_view);
virtual ~LauncherTooltipManager();
// Called when the bubble is closed.
......@@ -66,6 +70,17 @@ class ASH_EXPORT LauncherTooltipManager : public ShelfLayoutManager::Observer {
bool IsVisible();
protected:
// aura::EventFilter overrides:
virtual bool PreHandleKeyEvent(aura::Window* target,
aura::KeyEvent* event) OVERRIDE;
virtual bool PreHandleMouseEvent(aura::Window* target,
aura::MouseEvent* event) OVERRIDE;
virtual ui::TouchStatus PreHandleTouchEvent(aura::Window* target,
aura::TouchEvent* event) OVERRIDE;
virtual ui::GestureStatus PreHandleGestureEvent(
aura::Window* target,
aura::GestureEvent* event) OVERRIDE;
// ShelfLayoutManager::Observer overrides:
virtual void WillDeleteShelf() OVERRIDE;
virtual void WillChangeVisibilityState(
......@@ -78,16 +93,19 @@ protected:
friend class test::LauncherViewTest;
friend class test::LauncherTooltipManagerTest;
void CloseSoon();
void ShowInternal();
void CreateBubble(views::View* anchor, const string16& text);
LauncherTooltipBubble* view_;
views::Widget* widget_;
views::View* anchor_;
string16 text_;
ShelfAlignment alignment_;
scoped_ptr<base::Timer> timer_;
ShelfLayoutManager* shelf_layout_manager_;
LauncherView* launcher_view_;
DISALLOW_COPY_AND_ASSIGN(LauncherTooltipManager);
};
......
......@@ -8,6 +8,12 @@
#include "ash/wm/shelf_layout_manager.h"
#include "ash/wm/window_util.h"
#include "base/string16.h"
#include "base/time.h"
#include "ui/aura/event.h"
#include "ui/aura/event_filter.h"
#include "ui/aura/root_window.h"
#include "ui/base/events.h"
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/views/widget/widget.h"
namespace ash {
......@@ -19,10 +25,17 @@ class LauncherTooltipManagerTest : public AshTestBase {
virtual ~LauncherTooltipManagerTest() {}
virtual void SetUp() OVERRIDE {
ash::test::AshTestBase::SetUp();
AshTestBase::SetUp();
tooltip_manager_.reset(new internal::LauncherTooltipManager(
SHELF_ALIGNMENT_BOTTOM, Shell::GetInstance()->shelf()));
SHELF_ALIGNMENT_BOTTOM,
Shell::GetInstance()->shelf(),
Shell::GetInstance()->launcher()->GetLauncherViewForTest()));
}
virtual void TearDown() OVERRIDE {
tooltip_manager_.reset();
AshTestBase::TearDown();
}
void ShowDelayed() {
......@@ -43,6 +56,14 @@ class LauncherTooltipManagerTest : public AshTestBase {
return tooltip_manager_->timer_.get() != NULL;
}
aura::EventFilter* GetEventFilter() {
return tooltip_manager_.get();
}
views::Widget* GetTooltipWidget() {
return tooltip_manager_->widget_;
}
protected:
scoped_ptr<views::View> dummy_anchor_;
scoped_ptr<internal::LauncherTooltipManager> tooltip_manager_;
......@@ -112,5 +133,67 @@ TEST_F(LauncherTooltipManagerTest, HideWhenShelfIsAutoHide) {
EXPECT_FALSE(IsTimerRunning());
}
TEST_F(LauncherTooltipManagerTest, ShouldHideForEvents) {
ShowImmediately();
ASSERT_TRUE(TooltipIsVisible());
aura::RootWindow* root_window = Shell::GetInstance()->GetPrimaryRootWindow();
aura::EventFilter* event_filter = GetEventFilter();
// Should not hide for key events.
aura::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(event_filter->PreHandleKeyEvent(root_window, &key_event));
EXPECT_TRUE(TooltipIsVisible());
// Should hide for touch events.
aura::TouchEvent touch_event(
ui::ET_TOUCH_PRESSED, gfx::Point(), 0, base::TimeDelta());
EXPECT_EQ(ui::TOUCH_STATUS_UNKNOWN,
event_filter->PreHandleTouchEvent(root_window, &touch_event));
EXPECT_FALSE(TooltipIsVisible());
// Shouldn't hide if the touch happens on the tooltip.
ShowImmediately();
views::Widget* tooltip_widget = GetTooltipWidget();
EXPECT_EQ(ui::TOUCH_STATUS_UNKNOWN,
event_filter->PreHandleTouchEvent(
tooltip_widget->GetNativeWindow(), &touch_event));
EXPECT_TRUE(TooltipIsVisible());
// Should hide for gesture events.
aura::GestureEvent gesture_event(
ui::ET_GESTURE_BEGIN, 0, 0, ui::EF_NONE, base::Time(),
ui::GestureEventDetails(ui::ET_GESTURE_BEGIN, 0.0f, 0.0f), 0);
EXPECT_EQ(ui::GESTURE_STATUS_UNKNOWN,
event_filter->PreHandleGestureEvent(root_window, &gesture_event));
RunAllPendingInMessageLoop();
EXPECT_FALSE(TooltipIsVisible());
}
TEST_F(LauncherTooltipManagerTest, HideForMouseEvent) {
ShowImmediately();
ASSERT_TRUE(TooltipIsVisible());
aura::RootWindow* root_window = Shell::GetInstance()->GetPrimaryRootWindow();
aura::EventFilter* event_filter = GetEventFilter();
gfx::Rect tooltip_rect = GetTooltipWidget()->GetNativeWindow()->bounds();
ASSERT_FALSE(tooltip_rect.IsEmpty());
// Shouldn't hide if the mouse is in the tooltip.
aura::MouseEvent mouse_event(ui::ET_MOUSE_MOVED, tooltip_rect.CenterPoint(),
tooltip_rect.CenterPoint(), ui::EF_NONE);
aura::LocatedEvent::TestApi test_api(&mouse_event);
EXPECT_FALSE(event_filter->PreHandleMouseEvent(root_window, &mouse_event));
EXPECT_TRUE(TooltipIsVisible());
// Should hide if the mouse is out of the tooltip.
test_api.set_location(tooltip_rect.origin().Add(gfx::Point(-1, -1)));
EXPECT_FALSE(event_filter->PreHandleMouseEvent(root_window, &mouse_event));
RunAllPendingInMessageLoop();
EXPECT_FALSE(TooltipIsVisible());
}
} // namespace test
} // namespace ash
......@@ -283,7 +283,8 @@ LauncherView::LauncherView(LauncherModel* model,
bounds_animator_->AddObserver(this);
set_context_menu_controller(this);
focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
tooltip_.reset(new LauncherTooltipManager(alignment_, shelf_layout_manager));
tooltip_.reset(new LauncherTooltipManager(
alignment_, shelf_layout_manager, this));
}
LauncherView::~LauncherView() {
......@@ -814,20 +815,6 @@ views::FocusTraversable* LauncherView::GetPaneFocusTraversable() {
return this;
}
void LauncherView::OnMouseMoved(const views::MouseEvent& event) {
if (ShouldHideTooltip(event.location()) && tooltip_->IsVisible())
tooltip_->Close();
}
void LauncherView::OnMouseExited(const views::MouseEvent& event) {
// Mouse exit events are fired for entering to a launcher button from
// the launcher view, so it checks the location by ShouldHideTooltip().
gfx::Point point = event.location();
views::View::ConvertPointToView(parent(), this, &point);
if (ShouldHideTooltip(point) && tooltip_->IsVisible())
tooltip_->Close();
}
void LauncherView::LauncherItemAdded(int model_index) {
model_index = CancelDrag(model_index);
views::View* view = CreateViewForItem(model_->items()[model_index]);
......
......@@ -178,8 +178,6 @@ class ASH_EXPORT LauncherView : public views::View,
virtual gfx::Size GetPreferredSize() OVERRIDE;
virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE;
virtual void OnMouseMoved(const views::MouseEvent& event) OVERRIDE;
virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE;
// Overridden from LauncherModelObserver:
virtual void LauncherItemAdded(int model_index) OVERRIDE;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment