Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / ui / views / bookmarks / bookmark_menu_delegate.cc
bloba734ada300bbcbf0a359fab4b9223c983fe0e052
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h"
7 #include "base/prefs/pref_service.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
10 #include "chrome/browser/bookmarks/chrome_bookmark_client.h"
11 #include "chrome/browser/bookmarks/chrome_bookmark_client_factory.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
14 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
17 #include "chrome/browser/ui/views/bookmarks/bookmark_drag_drop_views.h"
18 #include "chrome/browser/ui/views/event_utils.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/bookmarks/browser/bookmark_model.h"
21 #include "content/public/browser/page_navigator.h"
22 #include "content/public/browser/user_metrics.h"
23 #include "grit/theme_resources.h"
24 #include "ui/base/dragdrop/os_exchange_data.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/base/window_open_disposition.h"
27 #include "ui/resources/grit/ui_resources.h"
28 #include "ui/views/controls/button/menu_button.h"
29 #include "ui/views/controls/menu/menu_item_view.h"
30 #include "ui/views/controls/menu/submenu_view.h"
31 #include "ui/views/widget/widget.h"
33 using base::UserMetricsAction;
34 using bookmarks::BookmarkNodeData;
35 using content::PageNavigator;
36 using views::MenuItemView;
38 // Max width of a menu. There does not appear to be an OS value for this, yet
39 // both IE and FF restrict the max width of a menu.
40 static const int kMaxMenuWidth = 400;
42 BookmarkMenuDelegate::BookmarkMenuDelegate(Browser* browser,
43 PageNavigator* navigator,
44 views::Widget* parent,
45 int first_menu_id,
46 int max_menu_id)
47 : browser_(browser),
48 profile_(browser->profile()),
49 page_navigator_(navigator),
50 parent_(parent),
51 menu_(NULL),
52 parent_menu_item_(NULL),
53 next_menu_id_(first_menu_id),
54 min_menu_id_(first_menu_id),
55 max_menu_id_(max_menu_id),
56 real_delegate_(NULL),
57 is_mutating_model_(false),
58 location_(BOOKMARK_LAUNCH_LOCATION_NONE) {}
60 BookmarkMenuDelegate::~BookmarkMenuDelegate() {
61 GetBookmarkModel()->RemoveObserver(this);
64 void BookmarkMenuDelegate::Init(views::MenuDelegate* real_delegate,
65 MenuItemView* parent,
66 const BookmarkNode* node,
67 int start_child_index,
68 ShowOptions show_options,
69 BookmarkLaunchLocation location) {
70 GetBookmarkModel()->AddObserver(this);
71 real_delegate_ = real_delegate;
72 location_ = location;
73 if (parent) {
74 parent_menu_item_ = parent;
76 // Add a separator if there are existing items in the menu, and if the
77 // current node has children. If |node| is the bookmark bar then the
78 // managed node is shown as its first child, if it's not empty.
79 BookmarkModel* model = GetBookmarkModel();
80 ChromeBookmarkClient* client = GetChromeBookmarkClient();
81 bool show_managed = show_options == SHOW_PERMANENT_FOLDERS &&
82 node == model->bookmark_bar_node() &&
83 !client->managed_node()->empty();
84 bool has_children =
85 (start_child_index < node->child_count()) || show_managed;
86 int initial_count = parent->GetSubmenu() ?
87 parent->GetSubmenu()->GetMenuItemCount() : 0;
88 if (has_children && initial_count > 0)
89 parent->AppendSeparator();
90 if (show_managed)
91 BuildMenuForManagedNode(parent, &next_menu_id_);
92 BuildMenu(node, start_child_index, parent, &next_menu_id_);
93 if (show_options == SHOW_PERMANENT_FOLDERS)
94 BuildMenusForPermanentNodes(parent, &next_menu_id_);
95 } else {
96 menu_ = CreateMenu(node, start_child_index, show_options);
100 void BookmarkMenuDelegate::SetPageNavigator(PageNavigator* navigator) {
101 page_navigator_ = navigator;
102 if (context_menu_.get())
103 context_menu_->SetPageNavigator(navigator);
106 BookmarkModel* BookmarkMenuDelegate::GetBookmarkModel() {
107 return BookmarkModelFactory::GetForProfile(profile_);
110 ChromeBookmarkClient* BookmarkMenuDelegate::GetChromeBookmarkClient() {
111 return ChromeBookmarkClientFactory::GetForProfile(profile_);
114 void BookmarkMenuDelegate::SetActiveMenu(const BookmarkNode* node,
115 int start_index) {
116 DCHECK(!parent_menu_item_);
117 if (!node_to_menu_map_[node])
118 CreateMenu(node, start_index, HIDE_PERMANENT_FOLDERS);
119 menu_ = node_to_menu_map_[node];
122 base::string16 BookmarkMenuDelegate::GetTooltipText(
123 int id,
124 const gfx::Point& screen_loc) const {
125 MenuIDToNodeMap::const_iterator i = menu_id_to_node_map_.find(id);
126 // When removing bookmarks it may be possible to end up here without a node.
127 if (i == menu_id_to_node_map_.end()) {
128 DCHECK(is_mutating_model_);
129 return base::string16();
132 const BookmarkNode* node = i->second;
133 if (node->is_url()) {
134 return BookmarkBarView::CreateToolTipForURLAndTitle(
135 parent_, screen_loc, node->url(), node->GetTitle(), profile_);
137 return base::string16();
140 bool BookmarkMenuDelegate::IsTriggerableEvent(views::MenuItemView* menu,
141 const ui::Event& e) {
142 return e.type() == ui::ET_GESTURE_TAP ||
143 e.type() == ui::ET_GESTURE_TAP_DOWN ||
144 event_utils::IsPossibleDispositionEvent(e);
147 void BookmarkMenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
148 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
150 const BookmarkNode* node = menu_id_to_node_map_[id];
151 std::vector<const BookmarkNode*> selection;
152 selection.push_back(node);
154 chrome::OpenAll(parent_->GetNativeWindow(), page_navigator_, selection,
155 ui::DispositionFromEventFlags(mouse_event_flags),
156 profile_);
157 RecordBookmarkLaunch(node, location_);
160 bool BookmarkMenuDelegate::ShouldExecuteCommandWithoutClosingMenu(
161 int id,
162 const ui::Event& event) {
163 return (event.flags() & ui::EF_LEFT_MOUSE_BUTTON) &&
164 ui::DispositionFromEventFlags(event.flags()) == NEW_BACKGROUND_TAB;
167 bool BookmarkMenuDelegate::GetDropFormats(
168 MenuItemView* menu,
169 int* formats,
170 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
171 *formats = ui::OSExchangeData::URL;
172 custom_formats->insert(BookmarkNodeData::GetBookmarkCustomFormat());
173 return true;
176 bool BookmarkMenuDelegate::AreDropTypesRequired(MenuItemView* menu) {
177 return true;
180 bool BookmarkMenuDelegate::CanDrop(MenuItemView* menu,
181 const ui::OSExchangeData& data) {
182 // Only accept drops of 1 node, which is the case for all data dragged from
183 // bookmark bar and menus.
185 if (!drop_data_.Read(data) || drop_data_.elements.size() != 1 ||
186 !profile_->GetPrefs()->GetBoolean(
187 bookmarks::prefs::kEditBookmarksEnabled))
188 return false;
190 if (drop_data_.has_single_url())
191 return true;
193 const BookmarkNode* drag_node =
194 drop_data_.GetFirstNode(GetBookmarkModel(), profile_->GetPath());
195 if (!drag_node) {
196 // Dragging a folder from another profile, always accept.
197 return true;
200 // Drag originated from same profile and is not a URL. Only accept it if
201 // the dragged node is not a parent of the node menu represents.
202 if (menu_id_to_node_map_.find(menu->GetCommand()) ==
203 menu_id_to_node_map_.end()) {
204 // If we don't know the menu assume its because we're embedded. We'll
205 // figure out the real operation when GetDropOperation is invoked.
206 return true;
208 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
209 DCHECK(drop_node);
210 while (drop_node && drop_node != drag_node)
211 drop_node = drop_node->parent();
212 return (drop_node == NULL);
215 int BookmarkMenuDelegate::GetDropOperation(
216 MenuItemView* item,
217 const ui::DropTargetEvent& event,
218 views::MenuDelegate::DropPosition* position) {
219 // Should only get here if we have drop data.
220 DCHECK(drop_data_.is_valid());
222 const BookmarkNode* node = menu_id_to_node_map_[item->GetCommand()];
223 const BookmarkNode* drop_parent = node->parent();
224 int index_to_drop_at = drop_parent->GetIndexOf(node);
225 BookmarkModel* model = GetBookmarkModel();
226 switch (*position) {
227 case views::MenuDelegate::DROP_AFTER:
228 if (node == model->other_node() || node == model->mobile_node()) {
229 // Dropping after these nodes makes no sense.
230 *position = views::MenuDelegate::DROP_NONE;
232 index_to_drop_at++;
233 break;
235 case views::MenuDelegate::DROP_BEFORE:
236 if (node == model->mobile_node()) {
237 // Dropping before this node makes no sense.
238 *position = views::MenuDelegate::DROP_NONE;
240 break;
242 case views::MenuDelegate::DROP_ON:
243 drop_parent = node;
244 index_to_drop_at = node->child_count();
245 break;
247 default:
248 break;
250 DCHECK(drop_parent);
251 return chrome::GetBookmarkDropOperation(
252 profile_, event, drop_data_, drop_parent, index_to_drop_at);
255 int BookmarkMenuDelegate::OnPerformDrop(
256 MenuItemView* menu,
257 views::MenuDelegate::DropPosition position,
258 const ui::DropTargetEvent& event) {
259 const BookmarkNode* drop_node = menu_id_to_node_map_[menu->GetCommand()];
260 DCHECK(drop_node);
261 BookmarkModel* model = GetBookmarkModel();
262 DCHECK(model);
263 const BookmarkNode* drop_parent = drop_node->parent();
264 DCHECK(drop_parent);
265 int index_to_drop_at = drop_parent->GetIndexOf(drop_node);
266 switch (position) {
267 case views::MenuDelegate::DROP_AFTER:
268 index_to_drop_at++;
269 break;
271 case views::MenuDelegate::DROP_ON:
272 DCHECK(drop_node->is_folder());
273 drop_parent = drop_node;
274 index_to_drop_at = drop_node->child_count();
275 break;
277 case views::MenuDelegate::DROP_BEFORE:
278 if (drop_node == model->other_node() ||
279 drop_node == model->mobile_node()) {
280 // This can happen with SHOW_PERMANENT_FOLDERS.
281 drop_parent = model->bookmark_bar_node();
282 index_to_drop_at = drop_parent->child_count();
284 break;
286 default:
287 break;
290 bool copy = event.source_operations() == ui::DragDropTypes::DRAG_COPY;
291 return chrome::DropBookmarks(profile_, drop_data_,
292 drop_parent, index_to_drop_at, copy);
295 bool BookmarkMenuDelegate::ShowContextMenu(MenuItemView* source,
296 int id,
297 const gfx::Point& p,
298 ui::MenuSourceType source_type) {
299 DCHECK(menu_id_to_node_map_.find(id) != menu_id_to_node_map_.end());
300 std::vector<const BookmarkNode*> nodes;
301 nodes.push_back(menu_id_to_node_map_[id]);
302 bool close_on_delete = !parent_menu_item_ &&
303 (nodes[0]->parent() == GetBookmarkModel()->other_node() &&
304 nodes[0]->parent()->child_count() == 1);
305 context_menu_.reset(
306 new BookmarkContextMenu(
307 parent_,
308 browser_,
309 profile_,
310 page_navigator_,
311 nodes[0]->parent(),
312 nodes,
313 close_on_delete));
314 context_menu_->set_observer(this);
315 context_menu_->RunMenuAt(p, source_type);
316 context_menu_.reset(NULL);
317 return true;
320 bool BookmarkMenuDelegate::CanDrag(MenuItemView* menu) {
321 const BookmarkNode* node = menu_id_to_node_map_[menu->GetCommand()];
322 // Don't let users drag the other folder.
323 return node->parent() != GetBookmarkModel()->root_node();
326 void BookmarkMenuDelegate::WriteDragData(MenuItemView* sender,
327 ui::OSExchangeData* data) {
328 DCHECK(sender && data);
330 content::RecordAction(UserMetricsAction("BookmarkBar_DragFromFolder"));
332 BookmarkNodeData drag_data(menu_id_to_node_map_[sender->GetCommand()]);
333 drag_data.Write(profile_->GetPath(), data);
336 int BookmarkMenuDelegate::GetDragOperations(MenuItemView* sender) {
337 return chrome::GetBookmarkDragOperation(
338 profile_, menu_id_to_node_map_[sender->GetCommand()]);
341 int BookmarkMenuDelegate::GetMaxWidthForMenu(MenuItemView* menu) {
342 return kMaxMenuWidth;
345 void BookmarkMenuDelegate::BookmarkModelChanged() {
348 void BookmarkMenuDelegate::BookmarkNodeFaviconChanged(
349 BookmarkModel* model,
350 const BookmarkNode* node) {
351 NodeToMenuMap::iterator menu_pair = node_to_menu_map_.find(node);
352 if (menu_pair == node_to_menu_map_.end())
353 return; // We're not showing a menu item for the node.
355 menu_pair->second->SetIcon(model->GetFavicon(node).AsImageSkia());
358 void BookmarkMenuDelegate::WillRemoveBookmarks(
359 const std::vector<const BookmarkNode*>& bookmarks) {
360 DCHECK(!is_mutating_model_);
361 is_mutating_model_ = true; // Set to false in DidRemoveBookmarks().
363 // Remove the observer so that when the remove happens we don't prematurely
364 // cancel the menu. The observer is added back in DidRemoveBookmarks().
365 GetBookmarkModel()->RemoveObserver(this);
367 // Remove the menu items.
368 std::set<MenuItemView*> changed_parent_menus;
369 for (std::vector<const BookmarkNode*>::const_iterator i(bookmarks.begin());
370 i != bookmarks.end(); ++i) {
371 NodeToMenuMap::iterator node_to_menu = node_to_menu_map_.find(*i);
372 if (node_to_menu != node_to_menu_map_.end()) {
373 MenuItemView* menu = node_to_menu->second;
374 MenuItemView* parent = menu->GetParentMenuItem();
375 // |parent| is NULL when removing a root. This happens when right clicking
376 // to delete an empty folder.
377 if (parent) {
378 changed_parent_menus.insert(parent);
379 parent->RemoveMenuItemAt(menu->parent()->GetIndexOf(menu));
381 node_to_menu_map_.erase(node_to_menu);
382 menu_id_to_node_map_.erase(menu->GetCommand());
386 // All the bookmarks in |bookmarks| should have the same parent. It's possible
387 // to support different parents, but this would need to prune any nodes whose
388 // parent has been removed. As all nodes currently have the same parent, there
389 // is the DCHECK.
390 DCHECK(changed_parent_menus.size() <= 1);
392 // Remove any descendants of the removed nodes in |node_to_menu_map_|.
393 for (NodeToMenuMap::iterator i(node_to_menu_map_.begin());
394 i != node_to_menu_map_.end(); ) {
395 bool ancestor_removed = false;
396 for (std::vector<const BookmarkNode*>::const_iterator j(bookmarks.begin());
397 j != bookmarks.end(); ++j) {
398 if (i->first->HasAncestor(*j)) {
399 ancestor_removed = true;
400 break;
403 if (ancestor_removed) {
404 menu_id_to_node_map_.erase(i->second->GetCommand());
405 node_to_menu_map_.erase(i++);
406 } else {
407 ++i;
411 for (std::set<MenuItemView*>::const_iterator i(changed_parent_menus.begin());
412 i != changed_parent_menus.end(); ++i)
413 (*i)->ChildrenChanged();
416 void BookmarkMenuDelegate::DidRemoveBookmarks() {
417 // Balances remove in WillRemoveBookmarksImpl.
418 GetBookmarkModel()->AddObserver(this);
419 DCHECK(is_mutating_model_);
420 is_mutating_model_ = false;
423 MenuItemView* BookmarkMenuDelegate::CreateMenu(const BookmarkNode* parent,
424 int start_child_index,
425 ShowOptions show_options) {
426 MenuItemView* menu = new MenuItemView(real_delegate_);
427 menu->SetCommand(next_menu_id_++);
428 menu_id_to_node_map_[menu->GetCommand()] = parent;
429 menu->set_has_icons(true);
430 bool show_permanent = show_options == SHOW_PERMANENT_FOLDERS;
431 if (show_permanent && parent == GetBookmarkModel()->bookmark_bar_node())
432 BuildMenuForManagedNode(menu, &next_menu_id_);
433 BuildMenu(parent, start_child_index, menu, &next_menu_id_);
434 if (show_permanent)
435 BuildMenusForPermanentNodes(menu, &next_menu_id_);
436 return menu;
439 void BookmarkMenuDelegate::BuildMenusForPermanentNodes(
440 views::MenuItemView* menu,
441 int* next_menu_id) {
442 BookmarkModel* model = GetBookmarkModel();
443 bool added_separator = false;
444 BuildMenuForPermanentNode(model->other_node(), IDR_BOOKMARK_BAR_FOLDER, menu,
445 next_menu_id, &added_separator);
446 BuildMenuForPermanentNode(model->mobile_node(), IDR_BOOKMARK_BAR_FOLDER, menu,
447 next_menu_id, &added_separator);
450 void BookmarkMenuDelegate::BuildMenuForPermanentNode(
451 const BookmarkNode* node,
452 int icon_resource_id,
453 MenuItemView* menu,
454 int* next_menu_id,
455 bool* added_separator) {
456 if (!node->IsVisible() || node->GetTotalNodeCount() == 1)
457 return; // No children, don't create a menu.
459 int id = *next_menu_id;
460 // Don't create the submenu if its menu ID will be outside the range allowed.
461 if (IsOutsideMenuIdRange(id))
462 return;
463 (*next_menu_id)++;
465 if (!*added_separator) {
466 *added_separator = true;
467 menu->AppendSeparator();
470 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
471 gfx::ImageSkia* folder_icon = rb->GetImageSkiaNamed(icon_resource_id);
472 MenuItemView* submenu = menu->AppendSubMenuWithIcon(
473 id, node->GetTitle(), *folder_icon);
474 BuildMenu(node, 0, submenu, next_menu_id);
475 menu_id_to_node_map_[id] = node;
478 void BookmarkMenuDelegate::BuildMenuForManagedNode(
479 MenuItemView* menu,
480 int* next_menu_id) {
481 // Don't add a separator for this menu.
482 bool added_separator = true;
483 const BookmarkNode* node = GetChromeBookmarkClient()->managed_node();
484 BuildMenuForPermanentNode(node, IDR_BOOKMARK_BAR_FOLDER_MANAGED, menu,
485 next_menu_id, &added_separator);
488 void BookmarkMenuDelegate::BuildMenu(const BookmarkNode* parent,
489 int start_child_index,
490 MenuItemView* menu,
491 int* next_menu_id) {
492 node_to_menu_map_[parent] = menu;
493 DCHECK(parent->empty() || start_child_index < parent->child_count());
494 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
495 for (int i = start_child_index; i < parent->child_count(); ++i) {
496 const BookmarkNode* node = parent->GetChild(i);
497 const int id = *next_menu_id;
498 // Don't create the item if its menu ID will be outside the range allowed.
499 if (IsOutsideMenuIdRange(id))
500 break;
502 (*next_menu_id)++;
504 menu_id_to_node_map_[id] = node;
505 if (node->is_url()) {
506 const gfx::Image& image = GetBookmarkModel()->GetFavicon(node);
507 const gfx::ImageSkia* icon = image.IsEmpty() ?
508 rb->GetImageSkiaNamed(IDR_DEFAULT_FAVICON) : image.ToImageSkia();
509 node_to_menu_map_[node] =
510 menu->AppendMenuItemWithIcon(id, node->GetTitle(), *icon);
511 } else if (node->is_folder()) {
512 gfx::ImageSkia* folder_icon =
513 rb->GetImageSkiaNamed(IDR_BOOKMARK_BAR_FOLDER);
514 MenuItemView* submenu = menu->AppendSubMenuWithIcon(
515 id, node->GetTitle(), *folder_icon);
516 BuildMenu(node, 0, submenu, next_menu_id);
517 } else {
518 NOTREACHED();
523 bool BookmarkMenuDelegate::IsOutsideMenuIdRange(int menu_id) const {
524 return menu_id < min_menu_id_ || menu_id > max_menu_id_;