diff options
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/automation/ui_controls.cc | 228 | ||||
-rw-r--r-- | chrome/browser/automation/ui_controls.h | 28 | ||||
-rw-r--r-- | chrome/browser/bookmark_bar_model.cc | 25 | ||||
-rw-r--r-- | chrome/browser/bookmark_bar_model.h | 6 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.cc | 65 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view.h | 33 | ||||
-rw-r--r-- | chrome/browser/views/bookmark_bar_view_test.cc | 857 | ||||
-rw-r--r-- | chrome/test/testing_profile.cc | 4 | ||||
-rw-r--r-- | chrome/test/testing_profile.h | 23 | ||||
-rw-r--r-- | chrome/test/ui/view_event_test_base.cc | 135 | ||||
-rw-r--r-- | chrome/test/ui/view_event_test_base.h | 160 | ||||
-rw-r--r-- | chrome/test/unit/unittests.vcproj | 16 | ||||
-rw-r--r-- | chrome/views/chrome_menu.cc | 13 | ||||
-rw-r--r-- | chrome/views/chrome_menu.h | 6 | ||||
-rw-r--r-- | chrome/views/menu_button.cc | 2 |
15 files changed, 1536 insertions, 65 deletions
diff --git a/chrome/browser/automation/ui_controls.cc b/chrome/browser/automation/ui_controls.cc index 811bd89..f7c7806 100644 --- a/chrome/browser/automation/ui_controls.cc +++ b/chrome/browser/automation/ui_controls.cc @@ -30,8 +30,131 @@ #include "chrome/browser/automation/ui_controls.h" #include "base/logging.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "chrome/views/view.h" -// Methods private to this file +namespace ui_controls { + +namespace { + +// InputDispatcher ------------------------------------------------------------ + +// InputDispatcher is used to listen for a mouse/keyboard event. When the +// appropriate event is received the task is notified. +class InputDispatcher : public base::RefCounted<InputDispatcher> { + public: + InputDispatcher(Task* task, WPARAM message_waiting_for); + + ~InputDispatcher(); + + // Invoked from the hook. If mouse_message matches message_waiting_for_ + // MatchingMessageFound is invoked. + void DispatchedMessage(WPARAM mouse_message); + + // Invoked when a matching event is found. Uninstalls the hook and schedules + // an event that notifies the task. + void MatchingMessageFound(); + + private: + // Notifies the task and release this (which should delete it). + void NotifyTask(); + + // The task we notify. + scoped_ptr<Task> task_; + + // Message we're waiting for. Not used for keyboard events. + const WPARAM message_waiting_for_; + + DISALLOW_COPY_AND_ASSIGN(InputDispatcher); +}; + +// Have we installed the hook? +bool installed_hook_ = false; + +// Return value from SetWindowsHookEx. +HHOOK next_hook_ = NULL; + +// If a hook is installed, this is the dispatcher. +InputDispatcher* current_dispatcher_ = NULL; + +// Callback from hook when a mouse message is received. +LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param) { + HHOOK next_hook = next_hook_; + if (n_code == HC_ACTION) { + DCHECK(current_dispatcher_); + current_dispatcher_->DispatchedMessage(w_param); + } + return CallNextHookEx(next_hook, n_code, w_param, l_param); +} + +// Callback from hook when a key message is received. +LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param) { + HHOOK next_hook = next_hook_; + if (n_code == HC_ACTION) { + DCHECK(current_dispatcher_); + if (l_param & (1 << 30)) // Only send on key up. + current_dispatcher_->MatchingMessageFound(); + } + return CallNextHookEx(next_hook, n_code, w_param, l_param); +} + +// Installs dispatcher as the current hook. +void InstallHook(InputDispatcher* dispatcher, bool key_hook) { + DCHECK(!installed_hook_); + current_dispatcher_ = dispatcher; + installed_hook_ = true; + if (key_hook) { + next_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyHook, NULL, + GetCurrentThreadId()); + } else { + // NOTE: I originally tried WH_CALLWNDPROCRET, but for some reason I + // didn't get a mouse message like I do with MouseHook. + next_hook_ = SetWindowsHookEx(WH_MOUSE, &MouseHook, NULL, + GetCurrentThreadId()); + } + DCHECK(next_hook_); +} + +// Uninstalls the hook set in InstallHook. +void UninstallHook(InputDispatcher* dispatcher) { + if (current_dispatcher_ == dispatcher) { + installed_hook_ = false; + current_dispatcher_ = NULL; + UnhookWindowsHookEx(next_hook_); + } +} + +InputDispatcher::InputDispatcher(Task* task, UINT message_waiting_for) + : task_(task), message_waiting_for_(message_waiting_for) { + InstallHook(this, message_waiting_for == WM_KEYUP); +} + +InputDispatcher::~InputDispatcher() { + // Make sure the hook isn't installed. + UninstallHook(this); +} + +void InputDispatcher::DispatchedMessage(WPARAM message) { + if (message == message_waiting_for_) + MatchingMessageFound(); +} + +void InputDispatcher::MatchingMessageFound() { + UninstallHook(this); + // At the time we're invoked the event has not actually been processed. + // Use PostTask to make sure the event has been processed before notifying. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, NewRunnableMethod(this, &InputDispatcher::NotifyTask), 0); +} + +void InputDispatcher::NotifyTask() { + task_->Run(); + Release(); +} + +// Private functions ---------------------------------------------------------- // Populate the INPUT structure with the appropriate keyboard event // parameters required by SendInput @@ -58,9 +181,11 @@ bool SendKeyEvent(wchar_t key, bool up) { return true; } -namespace ui_controls { +bool SendKeyPressImpl(wchar_t key, bool control, bool shift, bool alt, + Task* task) { + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, WM_KEYUP) : NULL); -bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated int i = 0; @@ -108,21 +233,17 @@ bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { i++; } - unsigned int rv = ::SendInput(i, input, sizeof(INPUT)); - return rv == i; -} - -bool SendKeyDown(wchar_t key) { - return SendKeyEvent(key, false); -} + if (rv != i) + return false; -bool SendKeyUp(wchar_t key) { - return SendKeyEvent(key, true); + if (dispatcher.get()) + dispatcher->AddRef(); + return true; } -bool SendMouseMove(long x, long y) { +bool SendMouseMoveImpl(long x, long y, Task* task) { INPUT input = { 0 }; int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1; @@ -135,48 +256,115 @@ bool SendMouseMove(long x, long y) { input.mi.dx = pixel_x; input.mi.dy = pixel_y; + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL); + if (!::SendInput(1, &input, sizeof(INPUT))) return false; + + if (dispatcher.get()) + dispatcher->AddRef(); + return true; } -bool SendMouseClick(MouseButton type) { - +bool SendMouseEventsImpl(MouseButton type, int state, Task* task) { DWORD down_flags = MOUSEEVENTF_ABSOLUTE; DWORD up_flags = MOUSEEVENTF_ABSOLUTE; + UINT last_event; switch(type) { case LEFT: down_flags |= MOUSEEVENTF_LEFTDOWN; up_flags |= MOUSEEVENTF_LEFTUP; - + last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN; break; + case MIDDLE: down_flags |= MOUSEEVENTF_MIDDLEDOWN; up_flags |= MOUSEEVENTF_MIDDLEUP; - + last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN; break; + case RIGHT: down_flags |= MOUSEEVENTF_RIGHTDOWN; up_flags |= MOUSEEVENTF_RIGHTUP; - + last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN; break; + default: NOTREACHED(); return false; } + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, last_event) : NULL); + INPUT input = { 0 }; input.type = INPUT_MOUSE; input.mi.dwFlags = down_flags; - if (!::SendInput(1, &input, sizeof(INPUT))) + if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT))) return false; input.mi.dwFlags = up_flags; - if (!::SendInput(1, &input, sizeof(INPUT))) + if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT))) return false; + if (dispatcher.get()) + dispatcher->AddRef(); + return true; } +} // namespace + +// public functions ----------------------------------------------------------- + +bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt) { + return SendKeyPressImpl(key, control, shift, alt, NULL); +} + +bool SendKeyPressNotifyWhenDone(wchar_t key, bool control, bool shift, + bool alt, Task* task) { + return SendKeyPressImpl(key, control, shift, alt, task); +} + +bool SendKeyDown(wchar_t key) { + return SendKeyEvent(key, false); +} + +bool SendKeyUp(wchar_t key) { + return SendKeyEvent(key, true); +} + +bool SendMouseMove(long x, long y) { + return SendMouseMoveImpl(x, y, NULL); +} + +void SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + SendMouseMoveImpl(x, y, task); +} + +bool SendMouseEvents(MouseButton type, int state) { + return SendMouseEventsImpl(type, state, NULL); +} + +void SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + SendMouseEventsImpl(type, state, task); +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEventsImpl(type, UP | DOWN, NULL); +} + +void MoveMouseToCenterAndPress( + ChromeViews::View* view, MouseButton button, int state, Task* task) { + DCHECK(view); + DCHECK(view->GetViewContainer()); + CPoint view_center(view->GetWidth() / 2, view->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(view, &view_center); + SendMouseMove(view_center.x, view_center.y); + SendMouseEventsNotifyWhenDone(button, state, task); +} + } // ui_controls diff --git a/chrome/browser/automation/ui_controls.h b/chrome/browser/automation/ui_controls.h index 4a655f5..35c0540 100644 --- a/chrome/browser/automation/ui_controls.h +++ b/chrome/browser/automation/ui_controls.h @@ -33,10 +33,22 @@ #include <string> #include <wtypes.h> +namespace ChromeViews { +class View; +} + +class Task; + namespace ui_controls { +// Many of the functions in this class include a variant that takes a Task. +// The version that takes a Task waits until the generated event is processed. +// Once the generated event is processed the Task is Run (and deleted). + // Send a key press with/without modifier keys. bool SendKeyPress(wchar_t key, bool control, bool shift, bool alt); +bool SendKeyPressNotifyWhenDone(wchar_t key, bool control, bool shift, + bool alt, Task* task); // Send a key down event. Use VK_CONTROL for ctrl key, // VK_MENU for alt key and VK_SHIFT for shift key. @@ -47,6 +59,7 @@ bool SendKeyUp(wchar_t key); // Simulate a mouse move. (x,y) are absolute // screen coordinates. bool SendMouseMove(long x, long y); +void SendMouseMoveNotifyWhenDone(long x, long y, Task* task); enum MouseButton { LEFT = 0, @@ -54,9 +67,24 @@ enum MouseButton { RIGHT, }; +// Used to indicate the state of the button when generating events. +enum MouseButtonState { + UP = 1, + DOWN = 2 +}; + +// Sends a mouse down and or up message. +bool SendMouseEvents(MouseButton type, int state); +void SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task); + // Simulate a single mouse click with given button type. bool SendMouseClick(MouseButton type); +// A combination of SendMouseMove to the middle of the view followed by +// SendMouseEvents. +void MoveMouseToCenterAndPress( + ChromeViews::View* view, MouseButton button, int state, Task* task); + } // ui_controls #endif // CHROME_BROWSER_AUTOMATION_UI_CONTROLS_H__
\ No newline at end of file diff --git a/chrome/browser/bookmark_bar_model.cc b/chrome/browser/bookmark_bar_model.cc index 661f7c5..c6c9312 100644 --- a/chrome/browser/bookmark_bar_model.cc +++ b/chrome/browser/bookmark_bar_model.cc @@ -107,8 +107,13 @@ BookmarkBarModel::BookmarkBarModel(Profile* profile) next_group_id_(HistoryService::kBookmarkBarID + 1), bookmark_bar_node_(NULL), other_node_(NULL) { - if (!profile) { - // Profile is NULL during testing. + // Notifications we want. + if (profile_) + NotificationService::current()->AddObserver( + this, NOTIFY_STARRED_FAVICON_CHANGED, Source<Profile>(profile_)); + + if (!profile || !profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)) { + // Profile/HistoryService is NULL during testing. CreateBookmarkBarNode(); CreateOtherBookmarksNode(); AddRootChildren(NULL); @@ -116,19 +121,11 @@ BookmarkBarModel::BookmarkBarModel(Profile* profile) return; } - // Notifications we want. - NotificationService::current()->AddObserver( - this, NOTIFY_STARRED_FAVICON_CHANGED, Source<Profile>(profile_)); - - // Request the entries from the database. - HistoryService* history_service = - profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); - if (!history_service) - return; - // Request the entries on the bookmark bar. - history_service->GetAllStarredEntries(&load_consumer_, - NewCallback(this, &BookmarkBarModel::OnGotStarredEntries)); + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)-> + GetAllStarredEntries(&load_consumer_, + NewCallback(this, + &BookmarkBarModel::OnGotStarredEntries)); } BookmarkBarModel::~BookmarkBarModel() { diff --git a/chrome/browser/bookmark_bar_model.h b/chrome/browser/bookmark_bar_model.h index b7e75f8..f7ecf71 100644 --- a/chrome/browser/bookmark_bar_model.h +++ b/chrome/browser/bookmark_bar_model.h @@ -27,8 +27,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#ifndef CHROME_BROWSER_BOOKMARK_BAR_H__ -#define CHROME_BROWSER_BOOKMARK_BAR_H__ +#ifndef CHROME_BROWSER_BOOKMARK_BAR_MODEL_H_ +#define CHROME_BROWSER_BOOKMARK_BAR_MODEL_H_ #include "base/observer_list.h" #include "chrome/browser/cancelable_request.h" @@ -427,4 +427,4 @@ class BookmarkBarModel : public NotificationObserver { DISALLOW_EVIL_CONSTRUCTORS(BookmarkBarModel); }; -#endif // CHROME_BROWSER_BOOKMARK_BAR_H__ +#endif // CHROME_BROWSER_BOOKMARK_BAR_MODEL_H_ diff --git a/chrome/browser/views/bookmark_bar_view.cc b/chrome/browser/views/bookmark_bar_view.cc index e9dbb11..6579a0f 100644 --- a/chrome/browser/views/bookmark_bar_view.cc +++ b/chrome/browser/views/bookmark_bar_view.cc @@ -235,7 +235,13 @@ class BookmarkButton : public ChromeViews::TextButton { url_(url), profile_(profile) { show_animation_.reset(new SlideAnimation(this)); - show_animation_->Show(); + if (BookmarkBarView::testing_) { + // For some reason during testing the events generated by animating + // throw off the test. So, don't animate while testing. + show_animation_->Reset(1); + } else { + show_animation_->Show(); + } } bool GetTooltipText(int x, int y, std::wstring* tooltip) { @@ -507,6 +513,8 @@ class BookmarkNodeMenuController : public ChromeViews::MenuDelegate, menu_.Cancel(); } + MenuItemView* menu() { return &menu_; } + private: // Menu::Delegate method. Does the appropriate operation based on chosen // menu item. @@ -750,6 +758,12 @@ class MenuRunner : public ChromeViews::MenuDelegate, menu_.Cancel(); } + MenuItemView* menu() { return &menu_; } + + MenuItemView* context_menu() { + return context_menu_.get() ? context_menu_->menu() : NULL; + } + private: // Creates an entry in menu for each child node of parent starting at // start_child_index, recursively invoking this for any star groups. @@ -980,6 +994,9 @@ class ButtonSeparatorView : public ChromeViews::View { // static const int BookmarkBarView::kMaxButtonWidth = 150; +// static +bool BookmarkBarView::testing_ = false; + // Returns the bitmap to use for starred groups. static const SkBitmap& GetGroupIcon() { if (!kFolderIcon) { @@ -1444,24 +1461,38 @@ bool BookmarkBarView::IsAlwaysShown() { } bool BookmarkBarView::IsNewTabPage() { - DCHECK(browser_); - - if (browser_->GetSelectedTabContents()) { - return browser_->GetSelectedTabContents()->IsBookmarkBarAlwaysVisible(); - } - - return false; + return (browser_ && browser_->GetSelectedTabContents() && + browser_->GetSelectedTabContents()->IsBookmarkBarAlwaysVisible()); } void BookmarkBarView::AnimationProgressed(const Animation* animation) { - browser_->ToolbarSizeChanged(NULL, true); + if (browser_) + browser_->ToolbarSizeChanged(NULL, true); } void BookmarkBarView::AnimationEnded(const Animation* animation) { - browser_->ToolbarSizeChanged(NULL, false); + if (browser_) + browser_->ToolbarSizeChanged(NULL, false); SchedulePaint(); } +ChromeViews::TextButton* BookmarkBarView::GetBookmarkButton(int index) { + DCHECK(index >= 0 && index < GetBookmarkButtonCount()); + return static_cast<ChromeViews::TextButton*>(GetChildViewAt(index)); +} + +ChromeViews::MenuItemView* BookmarkBarView::GetMenu() { + return menu_runner_.get() ? menu_runner_->menu() : NULL; +} + +ChromeViews::MenuItemView* BookmarkBarView::GetContextMenu() { + return menu_runner_.get() ? menu_runner_->context_menu() : NULL; +} + +ChromeViews::MenuItemView* BookmarkBarView::GetDropMenu() { + return drop_menu_runner_.get() ? drop_menu_runner_->menu() : NULL; +} + void BookmarkBarView::Init() { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); @@ -1525,11 +1556,6 @@ int BookmarkBarView::GetBookmarkButtonCount() { return GetChildViewCount() - 4; } -ChromeViews::TextButton* BookmarkBarView::GetBookmarkButton(int index) { - DCHECK(index >= 0 && index < GetBookmarkButtonCount()); - return static_cast<ChromeViews::TextButton*>(GetChildViewAt(index)); -} - void BookmarkBarView::Loaded(BookmarkBarModel* model) { BookmarkBarNode* node = model_->GetBookmarkBarNode(); DCHECK(node && model_->other_node()); @@ -1722,8 +1748,7 @@ void BookmarkBarView::RunMenu(ChromeViews::View* view, CPoint screen_loc(x, 0); View::ConvertPointToScreen(this, &screen_loc); menu_runner_.reset(new MenuRunner(this, node, start_index)); - HWND parent_hwnd = reinterpret_cast<HWND>( - browser_->window()->GetPlatformID()); + HWND parent_hwnd = GetViewContainer()->GetHWND(); menu_runner_->RunMenuAt(parent_hwnd, gfx::Rect(screen_loc.x, screen_loc.y, view->GetWidth(), height), @@ -1914,6 +1939,12 @@ void BookmarkBarView::StopShowFolderDropMenuTimer() { } void BookmarkBarView::StartShowFolderDropMenuTimer(BookmarkBarNode* node) { + if (testing_) { + // So that tests can run as fast as possible disable the delay during + // testing. + ShowDropFolderForNode(node); + return; + } DCHECK(!show_folder_drop_menu_task_); show_folder_drop_menu_task_ = new ShowFolderDropMenuTask(this, node); static DWORD delay = 0; diff --git a/chrome/browser/views/bookmark_bar_view.h b/chrome/browser/views/bookmark_bar_view.h index b8ffa37..287f8d7 100644 --- a/chrome/browser/views/bookmark_bar_view.h +++ b/chrome/browser/views/bookmark_bar_view.h @@ -52,6 +52,10 @@ class ButtonSeparatorView; struct DropInfo; } +namespace ChromeViews { +class MenuItemView; +} + // BookmarkBarView renders the BookmarkBarModel. Each starred entry // on the BookmarkBar is rendered as a MenuButton. An additional // MenuButton aligned to the right allows the user to quickly see @@ -143,9 +147,33 @@ class BookmarkBarView : public ChromeViews::View, void AnimationProgressed(const Animation* animation); void AnimationEnded(const Animation* animation); + // Returns the button at the specified index. + ChromeViews::TextButton* GetBookmarkButton(int index); + + // Returns the button responsible for showing bookmarks in the other bookmark + // folder. + ChromeViews::TextButton* other_bookmarked_button() const { + return other_bookmarked_button_; + } + + // Returns the active MenuItemView, or NULL if a menu isn't showing. + ChromeViews::MenuItemView* GetMenu(); + + // Returns the drop MenuItemView, or NULL if a menu isn't showing. + ChromeViews::MenuItemView* GetDropMenu(); + + // Returns the context menu, or null if one isn't showing. + ChromeViews::MenuItemView* GetContextMenu(); + + // Returns the button used when not all the items on the bookmark bar fit. + ChromeViews::TextButton* overflow_button() const { return overflow_button_; } + // Maximum size of buttons on the bookmark bar. static const int kMaxButtonWidth; + // If true we're running tests. This short circuits a couple of animations. + static bool testing_; + private: // Task that invokes ShowDropFolderForNode when run. ShowFolderDropMenuTask // deletes itself once run. @@ -192,9 +220,6 @@ class BookmarkBarView : public ChromeViews::View, // bookmark bar model has. int GetBookmarkButtonCount(); - // Returns the button at the specified index. - ChromeViews::TextButton* GetBookmarkButton(int index); - // Invoked when the bookmark bar model has finished loading. Creates a button // for each of the children of the root node from the model. virtual void Loaded(BookmarkBarModel* model); @@ -404,7 +429,7 @@ class BookmarkBarView : public ChromeViews::View, ButtonSeparatorView* bookmarks_separator_view_; - // Owning browser. + // Owning browser. This is NULL duing testing. Browser* browser_; // Animation controlling showing and hiding of the bar. diff --git a/chrome/browser/views/bookmark_bar_view_test.cc b/chrome/browser/views/bookmark_bar_view_test.cc new file mode 100644 index 0000000..a2a84e7 --- /dev/null +++ b/chrome/browser/views/bookmark_bar_view_test.cc @@ -0,0 +1,857 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/string_util.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/bookmark_bar_model.h" +#include "chrome/browser/page_navigator.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/test/testing_profile.h" +#include "chrome/test/ui/view_event_test_base.h" +#include "chrome/views/chrome_menu.h" +#include "chrome/views/text_button.h" + +namespace { + +// PageNavigator implementation that records the URL. +class TestingPageNavigator : public PageNavigator { + public: + virtual void OpenURL(const GURL& url, + WindowOpenDisposition disposition, + PageTransition::Type transition) { + url_ = url; + } + + GURL url_; +}; + +} // namespace + +// Base class for event generating bookmark view tests. These test are intended +// to exercise ChromeMenus, but that's easier done with BookmarkBarView rather +// than ChromeMenu itself. +// +// SetUp creates a bookmark model with the following structure. +// All folders are in upper case, all URLs in lower case. +// F1 +// f1a +// F11 +// f11a +// * +// a +// b +// c +// d +// OTHER +// oa +// OF +// ofa +// ofb +// OF2 +// of2a +// of2b +// +// * if CreateBigMenu returns return true, 100 menu items are created here with +// the names f1-f100. +// +// Subclasses should be sure and invoke super's implementation of SetUp and +// TearDown. +class BookmarkBarViewEventTestBase : public ViewEventTestBase { + public: + BookmarkBarViewEventTestBase() + : ViewEventTestBase(), bb_view_(NULL), model_(NULL) { + } + + virtual void SetUp() { + ChromeViews::MenuItemView::allow_task_nesting_during_run_ = true; + BookmarkBarView::testing_ = true; + + profile_.reset(new TestingProfile()); + profile_->set_has_history_service(true); + profile_->CreateBookmarkBarModel(); + profile_->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true); + + model_ = profile_->GetBookmarkBarModel(); + + bb_view_ = new BookmarkBarView(profile_.get(), NULL); + bb_view_->SetPageNavigator(&navigator_); + + AddTestData(CreateBigMenu()); + + // Calculate the preferred size so that one button doesn't fit, which + // triggers the overflow button to appear. + // + // BookmarkBarView::Layout does nothing if the parent is NULL and + // GetPreferredSize hard codes a width of 1. For that reason we add the + // BookmarkBarView to a dumby view as the parent. + // + // This code looks a bit hacky, but I've written it so that it shouldn't + // be dependant upon any of the layout code in BookmarkBarView. Instead + // we brute force search for a size that triggers the overflow button. + ChromeViews::View tmp_parent; + + tmp_parent.AddChildView(bb_view_); + + CSize bb_view_pref; + bb_view_->GetPreferredSize(&bb_view_pref); + bb_view_pref_.set_width(1000); + bb_view_pref_.set_height(bb_view_pref.cy); + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(4); + while (button->IsVisible()) { + bb_view_pref_.set_width(bb_view_pref_.width() - 25); + bb_view_->SetBounds(0, 0, bb_view_pref_.width(), bb_view_pref_.height()); + bb_view_->Layout(); + } + + tmp_parent.RemoveChildView(bb_view_); + + ViewEventTestBase::SetUp(); + } + + virtual void TearDown() { + BookmarkBarView::testing_ = false; + ChromeViews::MenuItemView::allow_task_nesting_during_run_ = false; + ViewEventTestBase::TearDown(); + } + + protected: + virtual ChromeViews::View* CreateContentsView() { + return bb_view_; + } + + virtual gfx::Size GetPreferredSize() { return bb_view_pref_; } + + // See comment above class description for what this does. + virtual bool CreateBigMenu() { return false; } + + BookmarkBarModel* model_; + BookmarkBarView* bb_view_; + TestingPageNavigator navigator_; + + private: + void AddTestData(bool big_menu) { + std::string test_base = "file:///c:/tmp/"; + + BookmarkBarNode* f1 = + model_->AddGroup(model_->GetBookmarkBarNode(), 0, L"F1"); + model_->AddURL(f1, 0, L"f1a", GURL(test_base + "f1a")); + BookmarkBarNode* f11 = model_->AddGroup(f1, 1, L"F11"); + model_->AddURL(f11, 0, L"f11a", GURL(test_base + "f11a")); + if (big_menu) { + for (int i = 1; i <= 100; ++i) { + model_->AddURL(f1, i + 1, L"f" + IntToWString(i), + GURL(test_base + "f" + IntToString(i))); + } + } + model_->AddURL(model_->GetBookmarkBarNode(), 1, L"a", + GURL(test_base + "a")); + model_->AddURL(model_->GetBookmarkBarNode(), 2, L"b", + GURL(test_base + "b")); + model_->AddURL(model_->GetBookmarkBarNode(), 3, L"c", + GURL(test_base + "c")); + model_->AddURL(model_->GetBookmarkBarNode(), 4, L"d", + GURL(test_base + "d")); + model_->AddURL(model_->other_node(), 0, L"oa", GURL(test_base + "oa")); + BookmarkBarNode* of = model_->AddGroup(model_->other_node(), 1, L"OF"); + model_->AddURL(of, 0, L"ofa", GURL(test_base + "ofa")); + model_->AddURL(of, 1, L"ofb", GURL(test_base + "ofb")); + BookmarkBarNode* of2 = model_->AddGroup(model_->other_node(), 2, L"OF2"); + model_->AddURL(of2, 0, L"of2a", GURL(test_base + "of2a")); + model_->AddURL(of2, 1, L"of2b", GURL(test_base + "of2b")); + } + + gfx::Size bb_view_pref_; + scoped_ptr<TestingProfile> profile_; +}; + +// Clicks on first menu, makes sure button is depressed. Moves mouse to first +// child, clicks it and makes sure a navigation occurs. +class BookmarkBarViewTest1 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest1::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + // Button should be depressed. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ASSERT_TRUE(button->GetState() == ChromeViews::BaseButton::BS_PUSHED); + + // Click on the 2nd menu item (A URL). + ASSERT_TRUE(menu->GetSubmenu()); + + ChromeViews::MenuItemView* menu_to_select = + menu->GetSubmenu()->GetMenuItemAt(0); + ui_controls::MoveMouseToCenterAndPress(menu_to_select, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest1::Step3)); + } + + void Step3() { + // We should have navigated to URL f1a. + ASSERT_TRUE(navigator_.url_ == + model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)-> + GetURL()); + + // Make sure button is no longer pushed. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ASSERT_TRUE(button->GetState() == ChromeViews::BaseButton::BS_NORMAL); + + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu == NULL || !menu->GetSubmenu()->IsShowing()); + + Done(); + } +}; + +VIEW_TEST(BookmarkBarViewTest1, Basic) + +// Brings up menu, clicks on empty space and make sure menu hides. +class BookmarkBarViewTest2 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest2::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL && menu->GetSubmenu()->IsShowing()); + + // Click on 0x0, which should trigger closing menu. + // NOTE: this code assume there is a left margin, which is currently + // true. If that changes, this code will need to find another empty space + // to press the mouse on. + CPoint mouse_loc(0, 0); + ChromeViews::View::ConvertPointToScreen(bb_view_, &mouse_loc); + ui_controls::SendMouseMove(0, 0); + ui_controls::SendMouseEventsNotifyWhenDone( + ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest2::Step3)); + } + + void Step3() { + // The menu shouldn't be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu == NULL || !menu->GetSubmenu()->IsShowing()); + + // Make sure button is no longer pushed. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ASSERT_TRUE(button->GetState() == ChromeViews::BaseButton::BS_NORMAL); + + window_->Activate(); + + Done(); + } +}; + +VIEW_TEST(BookmarkBarViewTest2, HideOnDesktopClick) + +// Brings up menu. Moves over child to make sure submenu appears, moves over +// another child and make sure next menu appears. +class BookmarkBarViewTest3 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->other_bookmarked_button(); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest3::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(1); + ASSERT_TRUE(child_menu != NULL); + + // Click on second child, which has a submenu. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest3::Step3)); + } + + void Step3() { + // Make sure sub menu is showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(1); + ASSERT_TRUE(child_menu->GetSubmenu() != NULL); + ASSERT_TRUE(child_menu->GetSubmenu()->IsShowing()); + + // Click on third child, which has a submenu too. + child_menu = menu->GetSubmenu()->GetMenuItemAt(2); + ASSERT_TRUE(child_menu != NULL); + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest3::Step4)); + } + + void Step4() { + // Make sure sub menu we first clicked isn't showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(1); + ASSERT_TRUE(child_menu->GetSubmenu() != NULL); + ASSERT_FALSE(child_menu->GetSubmenu()->IsShowing()); + + // And submenu we last clicked is showing. + child_menu = menu->GetSubmenu()->GetMenuItemAt(2); + ASSERT_TRUE(child_menu != NULL); + ASSERT_TRUE(child_menu->GetSubmenu()->IsShowing()); + + // Nothing should have been selected. + ASSERT_TRUE(navigator_.url_ == GURL()); + + // Hide menu. + menu->GetMenuController()->Cancel(true); + + // Because of the nested loop run by the menu we need to invoke done twice. + Done(); + Done(); + } +}; + +VIEW_TEST(BookmarkBarViewTest3, Submenus) + +// Tests context menus by way of opening a context menu for a bookmark, +// then right clicking to get context menu and selecting the first menu item +// (open). +class BookmarkBarViewTest4 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->other_bookmarked_button(); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest4::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(0); + ASSERT_TRUE(child_menu != NULL); + + // Right click on the first child to get its context menu. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::RIGHT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest4::Step3)); + } + + void Step3() { + // Make sure the context menu is showing. + ChromeViews::MenuItemView* menu = bb_view_->GetContextMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + // Select the first menu item (open). + ui_controls::MoveMouseToCenterAndPress(menu->GetSubmenu()->GetMenuItemAt(0), + ui_controls::LEFT, ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest4::Step4)); + } + + void Step4() { + ASSERT_TRUE(navigator_.url_ == + model_->other_node()->GetChild(0)->GetURL()); + + // Because of the nested loop we invoke done twice here. + Done(); + Done(); + } +}; + +VIEW_TEST(BookmarkBarViewTest4, ContextMenus) + +// Tests drag and drop within the same menu. +class BookmarkBarViewTest5 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + url_dragging_ = + model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)->GetURL(); + + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest5::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(0); + ASSERT_TRUE(child_menu != NULL); + + // Move mouse to center of menu and press button. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::DOWN, + CreateEventTask(this, &BookmarkBarViewTest5::Step3)); + } + + void Step3() { + ChromeViews::MenuItemView* target_menu = + bb_view_->GetMenu()->GetSubmenu()->GetMenuItemAt(1); + CPoint loc(1, target_menu->GetHeight() - 1); + ChromeViews::View::ConvertPointToScreen(target_menu, &loc); + + // Start a drag. + ui_controls::SendMouseMoveNotifyWhenDone(loc.x + 10, loc.y, + CreateEventTask(this, &BookmarkBarViewTest5::Step4)); + + // See comment above this method as to why we do this. + ScheduleMouseMoveInBackground(loc.x, loc.y); + } + + void Step4() { + // Drop the item so that it's now the second item. + ChromeViews::MenuItemView* target_menu = + bb_view_->GetMenu()->GetSubmenu()->GetMenuItemAt(1); + CPoint loc(1, target_menu->GetHeight() - 1); + ChromeViews::View::ConvertPointToScreen(target_menu, &loc); + ui_controls::SendMouseMove(loc.x, loc.y); + + ui_controls::SendMouseEventsNotifyWhenDone(ui_controls::LEFT, + ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest5::Step5)); + } + + void Step5() { + GURL url = model_->GetBookmarkBarNode()->GetChild(0)->GetChild(1)->GetURL(); + ASSERT_TRUE(url == url_dragging_); + Done(); + } + + GURL url_dragging_; +}; + +VIEW_TEST(BookmarkBarViewTest5, DND) + +// Tests holding mouse down on overflow button, dragging such that menu pops up +// then selecting an item. +class BookmarkBarViewTest6 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Press the mouse button on the overflow button. Don't release it though. + ChromeViews::TextButton* button = bb_view_->overflow_button(); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN, CreateEventTask(this, &BookmarkBarViewTest6::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(0); + ASSERT_TRUE(child_menu != NULL); + + // Move mouse to center of menu and release mouse. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::UP, CreateEventTask(this, &BookmarkBarViewTest6::Step3)); + } + + void Step3() { + ASSERT_TRUE(navigator_.url_ == + model_->GetBookmarkBarNode()->GetChild(4)->GetURL()); + Done(); + } + + GURL url_dragging_; +}; + +VIEW_TEST(BookmarkBarViewTest6, OpenMenuOnClickAndHold) + +// Tests drag and drop to different menu. +class BookmarkBarViewTest7 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + url_dragging_ = + model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)->GetURL(); + + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest7::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(0); + ASSERT_TRUE(child_menu != NULL); + + // Move mouse to center of menu and press button. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::DOWN, + CreateEventTask(this, &BookmarkBarViewTest7::Step3)); + } + + void Step3() { + // Drag over other button. + ChromeViews::TextButton* other_button = + bb_view_->other_bookmarked_button(); + CPoint loc(other_button->GetWidth() / 2, other_button->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(other_button, &loc); + + // Start a drag. + ui_controls::SendMouseMoveNotifyWhenDone(loc.x + 10, loc.y, + NewRunnableMethod(this, &BookmarkBarViewTest7::Step4)); + + // See comment above this method as to why we do this. + ScheduleMouseMoveInBackground(loc.x, loc.y); + } + + void Step4() { + ChromeViews::MenuItemView* drop_menu = bb_view_->GetDropMenu(); + ASSERT_TRUE(drop_menu != NULL); + ASSERT_TRUE(drop_menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* target_menu = + drop_menu->GetSubmenu()->GetMenuItemAt(0); + CPoint loc(1, 1); + ChromeViews::View::ConvertPointToScreen(target_menu, &loc); + ui_controls::SendMouseMove(loc.x, loc.y); + ui_controls::SendMouseEventsNotifyWhenDone( + ui_controls::LEFT, ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest7::Step5)); + } + + void Step5() { + ASSERT_TRUE(model_->other_node()->GetChild(0)->GetURL() == url_dragging_); + Done(); + } + + GURL url_dragging_; +}; + +VIEW_TEST(BookmarkBarViewTest7, DNDToDifferentMenu) + +// Drags from one menu to next so that original menu closes, then back to +// original menu. +class BookmarkBarViewTest8 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + url_dragging_ = + model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)->GetURL(); + + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest8::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* child_menu = + menu->GetSubmenu()->GetMenuItemAt(0); + ASSERT_TRUE(child_menu != NULL); + + // Move mouse to center of menu and press button. + ui_controls::MoveMouseToCenterAndPress(child_menu, ui_controls::LEFT, + ui_controls::DOWN, + CreateEventTask(this, &BookmarkBarViewTest8::Step3)); + } + + void Step3() { + // Drag over other button. + ChromeViews::TextButton* other_button = + bb_view_->other_bookmarked_button(); + CPoint loc(other_button->GetWidth() / 2, other_button->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(other_button, &loc); + + // Start a drag. + ui_controls::SendMouseMoveNotifyWhenDone(loc.x + 10, loc.y, + NewRunnableMethod(this, &BookmarkBarViewTest8::Step4)); + + // See comment above this method as to why we do this. + ScheduleMouseMoveInBackground(loc.x, loc.y); + } + + void Step4() { + ChromeViews::MenuItemView* drop_menu = bb_view_->GetDropMenu(); + ASSERT_TRUE(drop_menu != NULL); + ASSERT_TRUE(drop_menu->GetSubmenu()->IsShowing()); + + // Now drag back over first menu. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + CPoint loc(button->GetWidth() / 2, button->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(button, &loc); + ui_controls::SendMouseMoveNotifyWhenDone(loc.x, loc.y, + NewRunnableMethod(this, &BookmarkBarViewTest8::Step5)); + } + + void Step5() { + // Drop on folder F11. + ChromeViews::MenuItemView* drop_menu = bb_view_->GetDropMenu(); + ASSERT_TRUE(drop_menu != NULL); + ASSERT_TRUE(drop_menu->GetSubmenu()->IsShowing()); + + ChromeViews::MenuItemView* target_menu = + drop_menu->GetSubmenu()->GetMenuItemAt(1); + ui_controls::MoveMouseToCenterAndPress( + target_menu, ui_controls::LEFT, ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest8::Step6)); + } + + void Step6() { + // Make sure drop was processed. + GURL final_url = model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)-> + GetChild(1)->GetURL(); + ASSERT_TRUE(final_url == url_dragging_); + Done(); + } + + GURL url_dragging_; +}; + +VIEW_TEST(BookmarkBarViewTest8, DNDBackToOriginatingMenu) + +// Moves the mouse over the scroll button and makes sure we get scrolling. +class BookmarkBarViewTest9 : public BookmarkBarViewEventTestBase { + protected: + virtual bool CreateBigMenu() { return true; } + + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest9::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + first_menu_ = menu->GetSubmenu()->GetMenuItemAt(0); + CPoint menu_loc; + ChromeViews::View::ConvertPointToScreen(first_menu_, &menu_loc); + start_y_ = menu_loc.y; + + // Move the mouse over the scroll button. + ChromeViews::View* scroll_container = menu->GetSubmenu()->GetParent(); + ASSERT_TRUE(scroll_container != NULL); + scroll_container = scroll_container->GetParent(); + ASSERT_TRUE(scroll_container != NULL); + ChromeViews::View* scroll_down_button = scroll_container->GetChildViewAt(1); + ASSERT_TRUE(scroll_down_button); + CPoint loc(scroll_down_button->GetWidth() / 2, + scroll_down_button->GetHeight() / 2); + ChromeViews::View::ConvertPointToScreen(scroll_down_button, &loc); + ui_controls::SendMouseMoveNotifyWhenDone( + loc.x, loc.y, CreateEventTask(this, &BookmarkBarViewTest9::Step3)); + } + + void Step3() { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + NewRunnableMethod(this, &BookmarkBarViewTest9::Step4), 200); + } + + void Step4() { + CPoint menu_loc; + ChromeViews::View::ConvertPointToScreen(first_menu_, &menu_loc); + ASSERT_NE(start_y_, menu_loc.y); + + // Hide menu. + bb_view_->GetMenu()->GetMenuController()->Cancel(true); + + Done(); + } + + int start_y_; + ChromeViews::MenuItemView* first_menu_; +}; + +VIEW_TEST(BookmarkBarViewTest9, ScrollButtonScrolls) + +// Tests up/down/left/enter key messages. +class BookmarkBarViewTest10 : public BookmarkBarViewEventTestBase { + protected: + virtual void DoTestOnMessageLoop() { + // Move the mouse to the first folder on the bookmark bar and press the + // mouse. + ChromeViews::TextButton* button = bb_view_->GetBookmarkButton(0); + ui_controls::MoveMouseToCenterAndPress(button, ui_controls::LEFT, + ui_controls::DOWN | ui_controls::UP, + CreateEventTask(this, &BookmarkBarViewTest10::Step2)); + } + + private: + void Step2() { + // Menu should be showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + + // Send a down event, which should select the first item. + ui_controls::SendKeyPressNotifyWhenDone( + VK_DOWN, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step3)); + } + + void Step3() { + // Make sure menu is showing and item is selected. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + ASSERT_TRUE(menu->GetSubmenu()->GetMenuItemAt(0)->IsSelected()); + + // Send a key down event, which should select the next item. + ui_controls::SendKeyPressNotifyWhenDone( + VK_DOWN, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step4)); + } + + void Step4() { + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + ASSERT_FALSE(menu->GetSubmenu()->GetMenuItemAt(0)->IsSelected()); + ASSERT_TRUE(menu->GetSubmenu()->GetMenuItemAt(1)->IsSelected()); + + // Send a right arrow to force the menu to open. + ui_controls::SendKeyPressNotifyWhenDone( + VK_RIGHT, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step5)); + } + + void Step5() { + // Make sure the submenu is showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + ChromeViews::MenuItemView* submenu = menu->GetSubmenu()->GetMenuItemAt(1); + ASSERT_TRUE(submenu->IsSelected()); + ASSERT_TRUE(submenu->GetSubmenu()); + ASSERT_TRUE(submenu->GetSubmenu()->IsShowing()); + + // Send a left arrow to close the submenu. + ui_controls::SendKeyPressNotifyWhenDone( + VK_LEFT, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step6)); + } + + void Step6() { + // Make sure the submenu is showing. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + ChromeViews::MenuItemView* submenu = menu->GetSubmenu()->GetMenuItemAt(1); + ASSERT_TRUE(submenu->IsSelected()); + ASSERT_TRUE(!submenu->GetSubmenu() || !submenu->GetSubmenu()->IsShowing()); + + // Send a down arrow to wrap back to f1a + ui_controls::SendKeyPressNotifyWhenDone( + VK_DOWN, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step7)); + } + + void Step7() { + // Make sure menu is showing and item is selected. + ChromeViews::MenuItemView* menu = bb_view_->GetMenu(); + ASSERT_TRUE(menu != NULL); + ASSERT_TRUE(menu->GetSubmenu()->IsShowing()); + ASSERT_TRUE(menu->GetSubmenu()->GetMenuItemAt(0)->IsSelected()); + + // Send enter, which should select the item. + ui_controls::SendKeyPressNotifyWhenDone( + VK_RETURN, false, false, false, + CreateEventTask(this, &BookmarkBarViewTest10::Step8)); + } + + void Step8() { + ASSERT_TRUE( + model_->GetBookmarkBarNode()->GetChild(0)->GetChild(0)->GetURL() == + navigator_.url_); + Done(); + } +}; + +VIEW_TEST(BookmarkBarViewTest10, KeyEvents) diff --git a/chrome/test/testing_profile.cc b/chrome/test/testing_profile.cc index ac9e9f7..c1c3fe8 100644 --- a/chrome/test/testing_profile.cc +++ b/chrome/test/testing_profile.cc @@ -67,3 +67,7 @@ void TestingProfile::DestroyHistoryService() { MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask); MessageLoop::current()->Run(); } + +void TestingProfile::CreateBookmarkBarModel() { + bookmark_bar_model_.reset(new BookmarkBarModel(this)); +} diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index ae6c356..f6b01b5 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -33,6 +33,7 @@ #include "base/base_paths.h" #include "base/path_service.h" #include "base/file_util.h" +#include "chrome/browser/bookmark_bar_model.h" #include "chrome/browser/browser_prefs.h" #include "chrome/browser/history/history.h" #include "chrome/browser/profile.h" @@ -40,12 +41,16 @@ class TestingProfile : public Profile { public: - TestingProfile() : start_time_(Time::Now()) {} + TestingProfile() : start_time_(Time::Now()), has_history_service_(false) {} virtual ~TestingProfile(); // Creates the HistoryService. Normally there is no HistoryService. void CreateHistoryService(); + // Creates the BookmkarBarModel. If not invoked the bookmark bar model is + // NULL. + void CreateBookmarkBarModel(); + virtual std::wstring GetPath() { return std::wstring(); } @@ -64,8 +69,11 @@ class TestingProfile : public Profile { virtual HistoryService* GetHistoryService(ServiceAccessType access) { return history_service_.get(); } + void set_has_history_service(bool has_history_service) { + has_history_service_ = has_history_service; + } virtual bool HasHistoryService() const { - return (history_service_.get() != NULL); + return (history_service_.get() != NULL || has_history_service_); } virtual WebDataService* GetWebDataService(ServiceAccessType access) { return NULL; @@ -134,10 +142,10 @@ class TestingProfile : public Profile { virtual void MergeResourceBoolean(int message_id, bool* output_value) { } virtual bool HasBookmarkBarModel() { - return false; + return (bookmark_bar_model_.get() != NULL); } virtual BookmarkBarModel* GetBookmarkBarModel() { - return NULL; + return bookmark_bar_model_.get(); } virtual bool Profile::IsSameProfile(Profile *p) { return this == p; @@ -171,6 +179,13 @@ class TestingProfile : public Profile { // The history service. Only created if CreateHistoryService is invoked. scoped_refptr<HistoryService> history_service_; + + // The BookmarkBarModel. Only created if CreateBookmarkBarModel is invoked. + scoped_ptr<BookmarkBarModel> bookmark_bar_model_; + + // Do we have a history service? This defaults to the value of + // history_service, but can be explicitly set. + bool has_history_service_; }; #endif // CHROME_TEST_TESTING_PROFILE_H__ diff --git a/chrome/test/ui/view_event_test_base.cc b/chrome/test/ui/view_event_test_base.cc new file mode 100644 index 0000000..bf0aa84 --- /dev/null +++ b/chrome/test/ui/view_event_test_base.cc @@ -0,0 +1,135 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/test/ui/view_event_test_base.h" + +#include "base/message_loop.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/views/window.h" + +namespace { + +// View subclass that allows you to specify the preferred size. +class TestView : public ChromeViews::View { + public: + TestView() {} + + void set_preferred_size(const gfx::Size& size) { preferred_size_ = size; } + void GetPreferredSize(CSize* out) { + if (!preferred_size_.IsEmpty()) + *out = preferred_size_.ToSIZE(); + else + View::GetPreferredSize(out); + } + + private: + gfx::Size preferred_size_; + + DISALLOW_COPY_AND_ASSIGN(TestView); +}; + +// Delay in background thread before posting mouse move. +const int kMouseMoveDelayMS = 200; + +} // namespace + +// static +void ViewEventTestBase::Done() { + MessageLoop::current()->Quit(); + MessageLoop::current()->Quit(); +} + +ViewEventTestBase::ViewEventTestBase() : window_(NULL), content_view_(NULL) { } + +void ViewEventTestBase::SetUp() { + OleInitialize(NULL); + window_ = ChromeViews::Window::CreateChromeWindow(NULL, gfx::Rect(), this); +} + +void ViewEventTestBase::TearDown() { + if (window_) { + DestroyWindow(window_->GetHWND()); + window_ = NULL; + } + OleUninitialize(); +} + +ChromeViews::View* ViewEventTestBase::GetContentsView() { + if (!content_view_) { + // Wrap the real view (as returned by CreateContentsView) in a View so + // that we can customize the preferred size. + TestView* test_view = new TestView(); + test_view->SetLayoutManager(new ChromeViews::FillLayout()); + test_view->set_preferred_size(GetPreferredSize()); + test_view->AddChildView(CreateContentsView()); + content_view_ = test_view; + } + return content_view_; +} + +void ViewEventTestBase::StartMessageLoopAndRunTest() { + window_->Show(); + // Make sure the window is the foreground window, otherwise none of the + // mouse events are going to be targeted correctly. + SetForegroundWindow(window_->GetHWND()); + + // Flush any pending events to make sure we start with a clean slate. + MessageLoop::current()->RunAllPending(); + + // Schedule a task that starts the test. Need to do this as we're going to + // run the message loop. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod(this, &ViewEventTestBase::DoTestOnMessageLoop), 0); + + MessageLoop::current()->Run(); +} + +void ViewEventTestBase::ScheduleMouseMoveInBackground(int x, int y) { + if (!dnd_thread_.get()) { + dnd_thread_.reset(new Thread("mouse-move-thread")); + dnd_thread_->Start(); + } + dnd_thread_->message_loop()->PostDelayedTask( + FROM_HERE, NewRunnableFunction(&ui_controls::SendMouseMove, x, y), + kMouseMoveDelayMS); +} + +void ViewEventTestBase::StopBackgroundThread() { + dnd_thread_.reset(NULL); +} + +void ViewEventTestBase::RunTestMethod(Task* task) { + StopBackgroundThread(); + + scoped_ptr<Task> task_deleter(task); + task->Run(); + if (HasFatalFailure()) + Done(); +} diff --git a/chrome/test/ui/view_event_test_base.h b/chrome/test/ui/view_event_test_base.h new file mode 100644 index 0000000..4582f8a --- /dev/null +++ b/chrome/test/ui/view_event_test_base.h @@ -0,0 +1,160 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_TEST_UI_VIEW_EVENT_TEST_BASE_H_ +#define CHROME_TEST_UI_VIEW_EVENT_TEST_BASE_H_ + +#include "base/thread.h" +#include "chrome/views/window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +class Task; + +// Base class for Views based tests that dispatch events. +// +// As views based event test involves waiting for events to be processed, +// writing a views based test is slightly different than that of writing +// other unit tests. In particular when the test fails or is done you need +// to stop the message loop. This can be done by way of invoking the Done +// method. +// +// Any delayed callbacks should be done by way of CreateEventTask. +// CreateEventTask checks to see if ASSERT_XXX has been invoked after invoking +// the task. If there was a failure Done is invoked and the test stops. +// +// ViewEventTestBase creates a Window with the View returned from +// CreateContentsView. The preferred size for the view can be customized by +// overriding GetPreferredSize. If you do not override GetPreferredSize the +// preferred size of the view returned from CreateContentsView is used. +// +// Subclasses of ViewEventTestBase must implement two methods: +// . DoTestOnMessageLoop: invoked when the message loop is running. Run your +// test here, invoke Done when done. +// . CreateContentsView: returns the view to place in the window. +// +// Once you have created a ViewEventTestBase use the macro VIEW_TEST to define +// the fixture. +// +// I encountered weird timing problems in initiating dragging and drop that +// necessitated ugly hacks. In particular when the hook installed by +// ui_controls received the mouse event and posted a task that task was not +// processed. To work around this use the following pattern when initiating +// dnd: +// // Schedule the mouse move at a location slightly different from where +// // you really want to move to. +// ui_controls::SendMouseMoveNotifyWhenDone(loc.x + 10, loc.y, +// NewRunnableMethod(this, YYY)); +// // Then use this to schedule another mouse move. +// ScheduleMouseMoveInBackground(loc.x, loc.y); + +class ViewEventTestBase : public ChromeViews::WindowDelegate, + public testing::Test { + public: + // Invoke when done either because of failure or success. Quits the message + // loop. + static void Done(); + + ViewEventTestBase(); + + // Creates a window. + virtual void SetUp(); + + // Destroys the window. + virtual void TearDown(); + + virtual bool CanResize() const { + return true; + } + + // WindowDelegate method. Calls into CreateContentsView to get the actual + // view. + virtual ChromeViews::View* GetContentsView(); + + // Overriden to do nothing so that this class can be used in runnable tasks. + void AddRef() {} + void Release() {} + + protected: + // Returns the view that is added to the window. + virtual ChromeViews::View* CreateContentsView() = 0; + + // Called once the message loop is running. + virtual void DoTestOnMessageLoop() = 0; + + // Invoke from test main. Shows the window, starts the message loop and + // schedules a task that invokes DoTestOnMessageLoop. + void StartMessageLoopAndRunTest(); + + // Returns an empty Size. Subclasses that want a preferred size other than + // that of the View returned by CreateContentsView should override this + // appropriately. + virtual gfx::Size GetPreferredSize() { return gfx::Size(); } + + // Creates a task that calls the specified method back. The specified + // method is called in such a way that if there are any test failures + // Done is invoked. + template <class T, class Method> + Task* CreateEventTask(T* target, Method method) { + return NewRunnableMethod(this, &ViewEventTestBase::RunTestMethod, + NewRunnableMethod(target, method)); + } + + // Spawns a new thread posts a MouseMove in the background. + void ScheduleMouseMoveInBackground(int x, int y); + + ChromeViews::Window* window_; + + private: + // Stops the thread started by ScheduleMouseMoveInBackground. + void StopBackgroundThread(); + + // Callback from CreateEventTask. Stops the background thread, runs the + // supplied task and if there are failures invokes Done. + void RunTestMethod(Task* task); + + // The content of the Window. + ChromeViews::View* content_view_; + + // Thread for posting background MouseMoves. + scoped_ptr<Thread> dnd_thread_; + + DISALLOW_COPY_AND_ASSIGN(ViewEventTestBase); +}; + +// Convenience macro for defining a ViewEventTestBase. See class description +// of ViewEventTestBase for details. +// +// NOTE: These tests are disabled until we get a buildbot that is always logged +// in and can run them. +#define VIEW_TEST(test_class, name) \ + TEST_F(test_class, DISABLED_name) {\ + StartMessageLoopAndRunTest();\ + } + +#endif // CHROME_TEST_UI_VIEW_EVENT_TEST_BASE_H_ diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index dfea76e..d69d0fc 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -194,6 +194,14 @@ > </File> <File + RelativePath="..\ui\view_event_test_base.cc" + > + </File> + <File + RelativePath="..\ui\view_event_test_base.h" + > + </File> + <File RelativePath="..\..\..\net\url_request\url_request_test_job.cc" > </File> @@ -203,6 +211,14 @@ </File> </Filter> <Filter + Name="TestBookmarkBarView" + > + <File + RelativePath="..\..\browser\views\bookmark_bar_view_test.cc" + > + </File> + </Filter> + <Filter Name="TestGoogleURLTracker" > <File diff --git a/chrome/views/chrome_menu.cc b/chrome/views/chrome_menu.cc index 271c19b..a0c6abb 100644 --- a/chrome/views/chrome_menu.cc +++ b/chrome/views/chrome_menu.cc @@ -1072,6 +1072,9 @@ const int MenuItemView::kMenuItemViewID = 1001; // static const int MenuItemView::kDropBetweenPixels = 5; +// static +bool MenuItemView::allow_task_nesting_during_run_ = false; + MenuItemView::MenuItemView(MenuDelegate* delegate) { DCHECK(delegate_); Init(NULL, 0, SUBMENU, delegate); @@ -1571,7 +1574,15 @@ MenuItemView* MenuController::Run(HWND parent, DLOG(INFO) << " entering nested loop, depth=" << nested_depth; #endif - MessageLoop::current()->Run(this); + if (MenuItemView::allow_task_nesting_during_run_) { + bool did_allow_task_nesting = + MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(this); + MessageLoop::current()->SetNestableTasksAllowed(did_allow_task_nesting); + } else { + MessageLoop::current()->Run(this); + } #ifdef DEBUG_MENU nested_depth--; diff --git a/chrome/views/chrome_menu.h b/chrome/views/chrome_menu.h index 0c478aa..03f3c6c 100644 --- a/chrome/views/chrome_menu.h +++ b/chrome/views/chrome_menu.h @@ -237,6 +237,10 @@ class MenuItemView : public View { // before/after the menu item, otherwise it is on the item. static const int kDropBetweenPixels; + // If true SetNestableTasksAllowed(true) is invoked before MessageLoop::Run + // is invoked. This is only useful for testing and defaults to false. + static bool allow_task_nesting_during_run_; + // Different types of menu items. enum Type { NORMAL, @@ -388,7 +392,7 @@ class MenuItemView : public View { // Returns the root parent, or this if this has no parent. MenuItemView* GetRootMenuItem(); - // Returs the mnemonic for this MenuItemView, or 0 if this MenuItemView + // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView // doesn't have a mnemonic. wchar_t GetMnemonic(); diff --git a/chrome/views/menu_button.cc b/chrome/views/menu_button.cc index 1ebaba4..452fbac 100644 --- a/chrome/views/menu_button.cc +++ b/chrome/views/menu_button.cc @@ -70,7 +70,7 @@ MenuButton::MenuButton(const std::wstring& text, bool show_menu_marker) : TextButton(text), menu_visible_(false), - menu_closed_time_(Time::Now()), + menu_closed_time_(), menu_delegate_(menu_delegate), show_menu_marker_(show_menu_marker) { if (kMenuMarker == NULL) { |