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
18namespace blink {
19
20using namespace HTMLNames;
21
22CustomContextMenuProvider::CustomContextMenuProvider(HTMLMenuElement& menu, HTMLElement& subject)
23    : m_menu(menu)
24    , m_subjectElement(subject)
25{
26}
27
28CustomContextMenuProvider::~CustomContextMenuProvider()
29{
30}
31
32void CustomContextMenuProvider::populateContextMenu(ContextMenu* menu)
33{
34    populateContextMenuItems(*m_menu, *menu);
35}
36
37void 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
46void CustomContextMenuProvider::contextMenuCleared()
47{
48    m_menuItems.clear();
49    m_subjectElement = nullptr;
50}
51
52void 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
67void 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
78void 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
112HTMLElement* 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