blob: e3783a32082fae7659a3fe931c2264ac5dea9a16 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/page/CustomContextMenuProvider.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/events/EventDispatcher.h"
#include "core/events/MouseEvent.h"
#include "core/html/HTMLMenuElement.h"
#include "core/html/HTMLMenuItemElement.h"
#include "core/page/ContextMenuController.h"
#include "core/page/Page.h"
#include "platform/ContextMenu.h"
namespace blink {
using namespace HTMLNames;
CustomContextMenuProvider::CustomContextMenuProvider(HTMLMenuElement& menu,
HTMLElement& subject)
: m_menu(menu), m_subjectElement(subject) {}
CustomContextMenuProvider::~CustomContextMenuProvider() {}
DEFINE_TRACE(CustomContextMenuProvider) {
visitor->trace(m_menu);
visitor->trace(m_subjectElement);
visitor->trace(m_menuItems);
ContextMenuProvider::trace(visitor);
}
void CustomContextMenuProvider::populateContextMenu(ContextMenu* menu) {
populateContextMenuItems(*m_menu, *menu);
}
void CustomContextMenuProvider::contextMenuItemSelected(
const ContextMenuItem* item) {
if (HTMLElement* element = menuItemAt(item->action())) {
MouseEvent* click = MouseEvent::create(
EventTypeNames::click, m_menu->document().domWindow(), Event::create(),
SimulatedClickCreationScope::FromUserAgent);
click->setRelatedTarget(m_subjectElement.get());
element->dispatchEvent(click);
}
}
void CustomContextMenuProvider::contextMenuCleared() {
m_menuItems.clear();
m_subjectElement = nullptr;
}
void CustomContextMenuProvider::appendSeparator(ContextMenu& contextMenu) {
// Avoid separators at the start of any menu and submenu.
if (!contextMenu.items().size())
return;
// Collapse all sequences of two or more adjacent separators in the menu or
// any submenus to a single separator.
ContextMenuItem lastItem = contextMenu.items().last();
if (lastItem.type() == SeparatorType)
return;
contextMenu.appendItem(ContextMenuItem(
SeparatorType, ContextMenuItemCustomTagNoAction, String(), String()));
}
void CustomContextMenuProvider::appendMenuItem(HTMLMenuItemElement* menuItem,
ContextMenu& contextMenu) {
// Avoid menuitems with no label.
String labelString = menuItem->fastGetAttribute(labelAttr);
if (labelString.isEmpty())
return;
m_menuItems.append(menuItem);
bool enabled = !menuItem->fastHasAttribute(disabledAttr);
String icon = menuItem->fastGetAttribute(iconAttr);
if (!icon.isEmpty()) {
// To obtain the absolute URL of the icon when the attribute's value is not
// the empty string, the attribute's value must be resolved relative to the
// element.
KURL iconURL = KURL(menuItem->baseURI(), icon);
icon = iconURL.getString();
}
ContextMenuAction action = static_cast<ContextMenuAction>(
ContextMenuItemBaseCustomTag + m_menuItems.size() - 1);
if (equalIgnoringCase(menuItem->fastGetAttribute(typeAttr), "checkbox") ||
equalIgnoringCase(menuItem->fastGetAttribute(typeAttr), "radio"))
contextMenu.appendItem(
ContextMenuItem(CheckableActionType, action, labelString, icon, enabled,
menuItem->fastHasAttribute(checkedAttr)));
else
contextMenu.appendItem(
ContextMenuItem(ActionType, action, labelString, icon, enabled, false));
}
void CustomContextMenuProvider::populateContextMenuItems(
const HTMLMenuElement& menu,
ContextMenu& contextMenu) {
HTMLElement* nextElement = Traversal<HTMLElement>::firstWithin(menu);
while (nextElement) {
if (isHTMLHRElement(*nextElement)) {
appendSeparator(contextMenu);
nextElement = Traversal<HTMLElement>::next(*nextElement, &menu);
} else if (isHTMLMenuElement(*nextElement)) {
ContextMenu subMenu;
String labelString = nextElement->fastGetAttribute(labelAttr);
if (labelString.isNull()) {
appendSeparator(contextMenu);
populateContextMenuItems(*toHTMLMenuElement(nextElement), contextMenu);
appendSeparator(contextMenu);
} else if (!labelString.isEmpty()) {
populateContextMenuItems(*toHTMLMenuElement(nextElement), subMenu);
contextMenu.appendItem(
ContextMenuItem(SubmenuType, ContextMenuItemCustomTagNoAction,
labelString, String(), &subMenu));
}
nextElement = Traversal<HTMLElement>::nextSibling(*nextElement);
} else if (isHTMLMenuItemElement(*nextElement)) {
appendMenuItem(toHTMLMenuItemElement(nextElement), contextMenu);
if (ContextMenuItemBaseCustomTag + m_menuItems.size() >=
ContextMenuItemLastCustomTag)
break;
nextElement = Traversal<HTMLElement>::next(*nextElement, &menu);
} else {
nextElement = Traversal<HTMLElement>::next(*nextElement, &menu);
}
}
// Remove separators at the end of the menu and any submenus.
while (contextMenu.items().size() &&
contextMenu.items().last().type() == SeparatorType)
contextMenu.removeLastItem();
}
HTMLElement* CustomContextMenuProvider::menuItemAt(unsigned menuId) {
int itemIndex = menuId - ContextMenuItemBaseCustomTag;
if (itemIndex < 0 ||
static_cast<unsigned long>(itemIndex) >= m_menuItems.size())
return nullptr;
return m_menuItems[itemIndex].get();
}
} // namespace blink