Home | History | Annotate | Download | only in page
      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