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.
6 #include "core/page/CustomContextMenuProvider.h"
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"
20 using namespace HTMLNames
;
22 CustomContextMenuProvider::CustomContextMenuProvider(HTMLMenuElement
& menu
, HTMLElement
& subject
)
24 , m_subjectElement(subject
)
28 CustomContextMenuProvider::~CustomContextMenuProvider()
32 DEFINE_TRACE(CustomContextMenuProvider
)
34 visitor
->trace(m_menu
);
35 visitor
->trace(m_subjectElement
);
36 visitor
->trace(m_menuItems
);
37 ContextMenuProvider::trace(visitor
);
40 void CustomContextMenuProvider::populateContextMenu(ContextMenu
* menu
)
42 populateContextMenuItems(*m_menu
, *menu
);
45 void CustomContextMenuProvider::contextMenuItemSelected(const ContextMenuItem
* item
)
47 if (HTMLElement
* element
= menuItemAt(item
->action())) {
48 RefPtrWillBeRawPtr
<MouseEvent
> click
= MouseEvent::create(EventTypeNames::click
, m_menu
->document().domWindow(), Event::create(), SimulatedClickCreationScope::FromUserAgent
);
49 click
->setRelatedTarget(m_subjectElement
.get());
50 element
->dispatchEvent(click
.release());
54 void CustomContextMenuProvider::contextMenuCleared()
57 m_subjectElement
= nullptr;
60 void CustomContextMenuProvider::appendSeparator(ContextMenu
& contextMenu
)
62 // Avoid separators at the start of any menu and submenu.
63 if (!contextMenu
.items().size())
66 // Collapse all sequences of two or more adjacent separators in the menu or
67 // any submenus to a single separator.
68 ContextMenuItem lastItem
= contextMenu
.items().last();
69 if (lastItem
.type() == SeparatorType
)
72 contextMenu
.appendItem(ContextMenuItem(SeparatorType
, ContextMenuItemCustomTagNoAction
, String(), String()));
75 void CustomContextMenuProvider::appendMenuItem(HTMLMenuItemElement
* menuItem
, ContextMenu
& contextMenu
)
77 // Avoid menuitems with no label.
78 String labelString
= menuItem
->fastGetAttribute(labelAttr
);
79 if (labelString
.isEmpty())
82 m_menuItems
.append(menuItem
);
84 bool enabled
= !menuItem
->fastHasAttribute(disabledAttr
);
85 String icon
= menuItem
->fastGetAttribute(iconAttr
);
86 if (!icon
.isEmpty()) {
87 // To obtain the absolute URL of the icon when the attribute's value is not the empty string,
88 // the attribute's value must be resolved relative to the element.
89 KURL iconURL
= KURL(menuItem
->baseURI(), icon
);
90 icon
= iconURL
.string();
92 ContextMenuAction action
= static_cast<ContextMenuAction
>(ContextMenuItemBaseCustomTag
+ m_menuItems
.size() - 1);
93 if (equalIgnoringCase(menuItem
->fastGetAttribute(typeAttr
), "checkbox") || equalIgnoringCase(menuItem
->fastGetAttribute(typeAttr
), "radio"))
94 contextMenu
.appendItem(ContextMenuItem(CheckableActionType
, action
, labelString
, icon
, enabled
, menuItem
->fastHasAttribute(checkedAttr
)));
96 contextMenu
.appendItem(ContextMenuItem(ActionType
, action
, labelString
, icon
, enabled
, false));
99 void CustomContextMenuProvider::populateContextMenuItems(const HTMLMenuElement
& menu
, ContextMenu
& contextMenu
)
101 HTMLElement
* nextElement
= Traversal
<HTMLElement
>::firstWithin(menu
);
102 while (nextElement
) {
103 if (isHTMLHRElement(*nextElement
)) {
104 appendSeparator(contextMenu
);
105 nextElement
= Traversal
<HTMLElement
>::next(*nextElement
, &menu
);
106 } else if (isHTMLMenuElement(*nextElement
)) {
108 String labelString
= nextElement
->fastGetAttribute(labelAttr
);
109 if (labelString
.isNull()) {
110 appendSeparator(contextMenu
);
111 populateContextMenuItems(*toHTMLMenuElement(nextElement
), contextMenu
);
112 appendSeparator(contextMenu
);
113 } else if (!labelString
.isEmpty()) {
114 populateContextMenuItems(*toHTMLMenuElement(nextElement
), subMenu
);
115 contextMenu
.appendItem(ContextMenuItem(SubmenuType
, ContextMenuItemCustomTagNoAction
, labelString
, String(), &subMenu
));
117 nextElement
= Traversal
<HTMLElement
>::nextSibling(*nextElement
);
118 } else if (isHTMLMenuItemElement(*nextElement
)) {
119 appendMenuItem(toHTMLMenuItemElement(nextElement
), contextMenu
);
120 if (ContextMenuItemBaseCustomTag
+ m_menuItems
.size() >= ContextMenuItemLastCustomTag
)
122 nextElement
= Traversal
<HTMLElement
>::next(*nextElement
, &menu
);
124 nextElement
= Traversal
<HTMLElement
>::next(*nextElement
, &menu
);
128 // Remove separators at the end of the menu and any submenus.
129 while (contextMenu
.items().size() && contextMenu
.items().last().type() == SeparatorType
)
130 contextMenu
.removeLastItem();
133 HTMLElement
* CustomContextMenuProvider::menuItemAt(unsigned menuId
)
135 int itemIndex
= menuId
- ContextMenuItemBaseCustomTag
;
136 if (itemIndex
< 0 || static_cast<unsigned long>(itemIndex
) >= m_menuItems
.size())
138 return m_menuItems
[itemIndex
].get();