1 // Copyright 2014 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. 4 5 #include "config.h" 6 #include "core/page/CustomContextMenuProvider.h" 7 8 #include "core/dom/Document.h" 9 #include "core/dom/ElementTraversal.h" 10 #include "core/events/EventDispatcher.h" 11 #include "core/events/MouseEvent.h" 12 #include "core/html/HTMLMenuElement.h" 13 #include "core/html/HTMLMenuItemElement.h" 14 #include "core/page/ContextMenuController.h" 15 #include "core/page/Page.h" 16 #include "platform/ContextMenu.h" 17 18 namespace blink { 19 20 using namespace HTMLNames; 21 22 CustomContextMenuProvider::CustomContextMenuProvider(HTMLMenuElement& menu, HTMLElement& subject) 23 : m_menu(menu) 24 , m_subjectElement(subject) 25 { 26 } 27 28 CustomContextMenuProvider::~CustomContextMenuProvider() 29 { 30 } 31 32 void CustomContextMenuProvider::populateContextMenu(ContextMenu* menu) 33 { 34 populateContextMenuItems(*m_menu, *menu); 35 } 36 37 void CustomContextMenuProvider::contextMenuItemSelected(const ContextMenuItem* item) 38 { 39 if (HTMLElement* element = menuItemAt(item->action())) { 40 RefPtrWillBeRawPtr<SimulatedMouseEvent> click = SimulatedMouseEvent::create(EventTypeNames::click, m_menu->document().domWindow(), Event::create()); 41 click->setRelatedTarget(m_subjectElement.get()); 42 element->dispatchEvent(click.release()); 43 } 44 } 45 46 void CustomContextMenuProvider::contextMenuCleared() 47 { 48 m_menuItems.clear(); 49 m_subjectElement = nullptr; 50 } 51 52 void CustomContextMenuProvider::appendSeparator(ContextMenu& contextMenu) 53 { 54 // Avoid separators at the start of any menu and submenu. 55 if (!contextMenu.items().size()) 56 return; 57 58 // Collapse all sequences of two or more adjacent separators in the menu or 59 // any submenus to a single separator. 60 ContextMenuItem lastItem = contextMenu.items().last(); 61 if (lastItem.type() == SeparatorType) 62 return; 63 64 contextMenu.appendItem(ContextMenuItem(SeparatorType, ContextMenuItemCustomTagNoAction, String())); 65 } 66 67 void CustomContextMenuProvider::appendMenuItem(HTMLMenuItemElement* menuItem, ContextMenu& contextMenu) 68 { 69 // Avoid menuitems with no label. 70 String labelString = menuItem->fastGetAttribute(labelAttr); 71 if (labelString.isEmpty()) 72 return; 73 74 m_menuItems.append(menuItem); 75 contextMenu.appendItem(ContextMenuItem(ActionType, static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + m_menuItems.size() - 1), labelString)); 76 } 77 78 void CustomContextMenuProvider::populateContextMenuItems(const HTMLMenuElement& menu, ContextMenu& contextMenu) 79 { 80 HTMLElement* nextElement = Traversal<HTMLElement>::firstWithin(menu); 81 while (nextElement) { 82 if (isHTMLHRElement(*nextElement)) { 83 appendSeparator(contextMenu); 84 nextElement = Traversal<HTMLElement>::next(*nextElement, &menu); 85 } else if (isHTMLMenuElement(*nextElement)) { 86 ContextMenu subMenu; 87 String labelString = nextElement->fastGetAttribute(labelAttr); 88 if (labelString.isNull()) { 89 appendSeparator(contextMenu); 90 populateContextMenuItems(*toHTMLMenuElement(nextElement), contextMenu); 91 appendSeparator(contextMenu); 92 } else if (!labelString.isEmpty()) { 93 populateContextMenuItems(*toHTMLMenuElement(nextElement), subMenu); 94 contextMenu.appendItem(ContextMenuItem(SubmenuType, ContextMenuItemCustomTagNoAction, labelString, &subMenu)); 95 } 96 nextElement = Traversal<HTMLElement>::nextSibling(*nextElement); 97 } else if (isHTMLMenuItemElement(*nextElement)) { 98 appendMenuItem(toHTMLMenuItemElement(nextElement), contextMenu); 99 if (ContextMenuItemBaseCustomTag + m_menuItems.size() >= ContextMenuItemLastCustomTag) 100 break; 101 nextElement = Traversal<HTMLElement>::next(*nextElement, &menu); 102 } else { 103 nextElement = Traversal<HTMLElement>::next(*nextElement, &menu); 104 } 105 } 106 107 // Remove separators at the end of the menu and any submenus. 108 while (contextMenu.items().size() && contextMenu.items().last().type() == SeparatorType) 109 contextMenu.removeLastItem(); 110 } 111 112 HTMLElement* CustomContextMenuProvider::menuItemAt(unsigned menuId) 113 { 114 int itemIndex = menuId - ContextMenuItemBaseCustomTag; 115 if (itemIndex < 0 || static_cast<unsigned long>(itemIndex) >= m_menuItems.size()) 116 return 0; 117 return m_menuItems[itemIndex].get(); 118 } 119 120 } // namespace blink 121