blob: 561c647a807eea825b3597223e1891ad4fca6a77 [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)
: menu_(menu), subject_element_(subject) {}
CustomContextMenuProvider::~CustomContextMenuProvider() {}
DEFINE_TRACE(CustomContextMenuProvider) {
visitor->Trace(menu_);
visitor->Trace(subject_element_);
visitor->Trace(menu_items_);
ContextMenuProvider::Trace(visitor);
}
void CustomContextMenuProvider::PopulateContextMenu(ContextMenu* menu) {
PopulateContextMenuItems(*menu_, *menu);
}
void CustomContextMenuProvider::ContextMenuItemSelected(
const ContextMenuItem* item) {
if (HTMLElement* element = MenuItemAt(item->Action())) {
MouseEvent* click = MouseEvent::Create(
EventTypeNames::click, menu_->GetDocument().domWindow(),
Event::Create(), SimulatedClickCreationScope::kFromUserAgent);
click->SetRelatedTarget(subject_element_.Get());
element->DispatchEvent(click);
}
}
void CustomContextMenuProvider::ContextMenuCleared() {
menu_items_.clear();
subject_element_ = nullptr;
}
void CustomContextMenuProvider::AppendSeparator(ContextMenu& context_menu) {
// Avoid separators at the start of any menu and submenu.
if (!context_menu.Items().size())
return;
// Collapse all sequences of two or more adjacent separators in the menu or
// any submenus to a single separator.
ContextMenuItem last_item = context_menu.Items().back();
if (last_item.GetType() == kSeparatorType)
return;
context_menu.AppendItem(ContextMenuItem(
kSeparatorType, kContextMenuItemCustomTagNoAction, String(), String()));
}
void CustomContextMenuProvider::AppendMenuItem(HTMLMenuItemElement* menu_item,
ContextMenu& context_menu) {
// Avoid menuitems with no label.
String label_string = menu_item->label();
if (label_string.IsEmpty())
return;
menu_items_.push_back(menu_item);
bool enabled = !menu_item->FastHasAttribute(disabledAttr);
String icon = menu_item->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 icon_url = KURL(menu_item->baseURI(), icon);
icon = icon_url.GetString();
}
ContextMenuAction action = static_cast<ContextMenuAction>(
kContextMenuItemBaseCustomTag + menu_items_.size() - 1);
if (DeprecatedEqualIgnoringCase(menu_item->FastGetAttribute(typeAttr),
"checkbox") ||
DeprecatedEqualIgnoringCase(menu_item->FastGetAttribute(typeAttr),
"radio"))
context_menu.AppendItem(
ContextMenuItem(kCheckableActionType, action, label_string, icon,
enabled, menu_item->FastHasAttribute(checkedAttr)));
else
context_menu.AppendItem(ContextMenuItem(kActionType, action, label_string,
icon, enabled, false));
}
void CustomContextMenuProvider::PopulateContextMenuItems(
const HTMLMenuElement& menu,
ContextMenu& context_menu) {
HTMLElement* next_element = Traversal<HTMLElement>::FirstWithin(menu);
while (next_element) {
if (isHTMLHRElement(*next_element)) {
AppendSeparator(context_menu);
next_element = Traversal<HTMLElement>::Next(*next_element, &menu);
} else if (isHTMLMenuElement(*next_element)) {
ContextMenu sub_menu;
String label_string = next_element->FastGetAttribute(labelAttr);
if (label_string.IsNull()) {
AppendSeparator(context_menu);
PopulateContextMenuItems(*toHTMLMenuElement(next_element),
context_menu);
AppendSeparator(context_menu);
} else if (!label_string.IsEmpty()) {
PopulateContextMenuItems(*toHTMLMenuElement(next_element), sub_menu);
context_menu.AppendItem(
ContextMenuItem(kSubmenuType, kContextMenuItemCustomTagNoAction,
label_string, String(), &sub_menu));
}
next_element = Traversal<HTMLElement>::NextSibling(*next_element);
} else if (isHTMLMenuItemElement(*next_element)) {
AppendMenuItem(toHTMLMenuItemElement(next_element), context_menu);
if (kContextMenuItemBaseCustomTag + menu_items_.size() >=
kContextMenuItemLastCustomTag)
break;
next_element = Traversal<HTMLElement>::Next(*next_element, &menu);
} else {
next_element = Traversal<HTMLElement>::Next(*next_element, &menu);
}
}
// Remove separators at the end of the menu and any submenus.
while (context_menu.Items().size() &&
context_menu.Items().back().GetType() == kSeparatorType)
context_menu.RemoveLastItem();
}
HTMLElement* CustomContextMenuProvider::MenuItemAt(unsigned menu_id) {
int item_index = menu_id - kContextMenuItemBaseCustomTag;
if (item_index < 0 ||
static_cast<unsigned long>(item_index) >= menu_items_.size())
return nullptr;
return menu_items_[item_index].Get();
}
} // namespace blink