blob: c3c6ff2fea846234d5a683de6d79e9f80f10216c [file] [log] [blame]
// Copyright (c) 2006-2009 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 "config.h"
#include "base/compiler_specific.h"
MSVC_PUSH_WARNING_LEVEL(0);
#include "ContextMenu.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "Editor.h"
#include "EventHandler.h"
#include "FrameLoader.h"
#include "FrameView.h"
#include "HitTestResult.h"
#include "HTMLMediaElement.h"
#include "HTMLNames.h"
#include "KURL.h"
#include "MediaError.h"
#include "Widget.h"
MSVC_POP_WARNING();
#undef LOG
#include "webkit/glue/context_menu_client_impl.h"
#include "base/string_util.h"
#include "webkit/api/public/WebURL.h"
#include "webkit/api/public/WebURLResponse.h"
#include "webkit/api/src/WebDataSourceImpl.h"
#include "webkit/glue/context_menu.h"
#include "webkit/glue/glue_util.h"
#include "webkit/glue/webview_impl.h"
#include "base/word_iterator.h"
using WebKit::WebDataSource;
using WebKit::WebDataSourceImpl;
namespace {
// Helper function to determine whether text is a single word or a sentence.
bool IsASingleWord(const std::wstring& text) {
WordIterator iter(text, WordIterator::BREAK_WORD);
int word_count = 0;
if (!iter.Init()) return false;
while (iter.Advance()) {
if (iter.IsWord()) {
word_count++;
if (word_count > 1) // More than one word.
return false;
}
}
// Check for 0 words.
if (!word_count)
return false;
// Has a single word.
return true;
}
// Helper function to get misspelled word on which context menu
// is to be evolked. This function also sets the word on which context menu
// has been evoked to be the selected word, as required. This function changes
// the selection only when there were no selected characters.
std::wstring GetMisspelledWord(const WebCore::ContextMenu* default_menu,
WebCore::Frame* selected_frame) {
std::wstring misspelled_word_string;
// First select from selectedText to check for multiple word selection.
misspelled_word_string = CollapseWhitespace(
webkit_glue::StringToStdWString(selected_frame->selectedText()),
false);
// If some texts were already selected, we don't change the selection.
if (!misspelled_word_string.empty()) {
// Don't provide suggestions for multiple words.
if (!IsASingleWord(misspelled_word_string))
return L"";
else
return misspelled_word_string;
}
WebCore::HitTestResult hit_test_result = selected_frame->eventHandler()->
hitTestResultAtPoint(default_menu->hitTestResult().point(), true);
WebCore::Node* inner_node = hit_test_result.innerNode();
WebCore::VisiblePosition pos(inner_node->renderer()->positionForPoint(
hit_test_result.localPoint()));
WebCore::VisibleSelection selection;
if (pos.isNotNull()) {
selection = WebCore::VisibleSelection(pos);
selection.expandUsingGranularity(WebCore::WordGranularity);
}
if (selection.isRange()) {
selected_frame->setSelectionGranularity(WebCore::WordGranularity);
}
if (selected_frame->shouldChangeSelection(selection))
selected_frame->selection()->setSelection(selection);
misspelled_word_string = CollapseWhitespace(
webkit_glue::StringToStdWString(selected_frame->selectedText()),
false);
// If misspelled word is empty, then that portion should not be selected.
// Set the selection to that position only, and do not expand.
if (misspelled_word_string.empty()) {
selection = WebCore::VisibleSelection(pos);
selected_frame->selection()->setSelection(selection);
}
return misspelled_word_string;
}
} // namespace
ContextMenuClientImpl::~ContextMenuClientImpl() {
}
void ContextMenuClientImpl::contextMenuDestroyed() {
delete this;
}
// Figure out the URL of a page or subframe. Returns |page_type| as the type,
// which indicates page or subframe, or ContextNodeType::NONE if the URL could not
// be determined for some reason.
static ContextNodeType GetTypeAndURLFromFrame(
WebCore::Frame* frame,
GURL* url,
ContextNodeType page_node_type) {
ContextNodeType node_type;
if (frame) {
WebCore::DocumentLoader* dl = frame->loader()->documentLoader();
if (dl) {
WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
if (ds) {
node_type = page_node_type;
*url = ds->hasUnreachableURL() ? ds->unreachableURL()
: ds->request().url();
}
}
}
return node_type;
}
WebCore::PlatformMenuDescription
ContextMenuClientImpl::getCustomMenuFromDefaultItems(
WebCore::ContextMenu* default_menu) {
// Displaying the context menu in this function is a big hack as we don't
// have context, i.e. whether this is being invoked via a script or in
// response to user input (Mouse event WM_RBUTTONDOWN,
// Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked
// in response to the above input events before popping up the context menu.
if (!webview_->context_menu_allowed())
return NULL;
WebCore::HitTestResult r = default_menu->hitTestResult();
WebCore::Frame* selected_frame = r.innerNonSharedNode()->document()->frame();
WebCore::IntPoint menu_point =
selected_frame->view()->contentsToWindow(r.point());
ContextNodeType node_type;
// Links, Images, Media tags, and Image/Media-Links take preference over
// all else.
WebCore::KURL link_url = r.absoluteLinkURL();
if (!link_url.isEmpty()) {
node_type.type |= ContextNodeType::LINK;
}
WebCore::KURL src_url;
ContextMenuMediaParams media_params;
if (!r.absoluteImageURL().isEmpty()) {
src_url = r.absoluteImageURL();
node_type.type |= ContextNodeType::IMAGE;
} else if (!r.absoluteMediaURL().isEmpty()) {
src_url = r.absoluteMediaURL();
// We know that if absoluteMediaURL() is not empty, then this is a media
// element.
WebCore::HTMLMediaElement* media_element =
static_cast<WebCore::HTMLMediaElement*>(r.innerNonSharedNode());
if (media_element->hasTagName(WebCore::HTMLNames::videoTag)) {
node_type.type |= ContextNodeType::VIDEO;
} else if (media_element->hasTagName(WebCore::HTMLNames::audioTag)) {
node_type.type |= ContextNodeType::AUDIO;
}
if (media_element->error()) {
media_params.player_state |= ContextMenuMediaParams::IN_ERROR;
}
if (media_element->paused()) {
media_params.player_state |= ContextMenuMediaParams::PAUSED;
}
if (media_element->muted()) {
media_params.player_state |= ContextMenuMediaParams::MUTED;
}
if (media_element->loop()) {
media_params.player_state |= ContextMenuMediaParams::LOOP;
}
if (media_element->supportsSave()) {
media_params.player_state |= ContextMenuMediaParams::CAN_SAVE;
}
media_params.has_audio = media_element->hasAudio();
}
// If it's not a link, an image, a media element, or an image/media link,
// show a selection menu or a more generic page menu.
std::wstring selection_text_string;
std::wstring misspelled_word_string;
GURL frame_url;
GURL page_url;
std::string security_info;
std::string frame_charset = WideToASCII(
webkit_glue::StringToStdWString(selected_frame->loader()->encoding()));
// Send the frame and page URLs in any case.
ContextNodeType frame_node = ContextNodeType(ContextNodeType::NONE);
ContextNodeType page_node =
GetTypeAndURLFromFrame(webview_->main_frame()->frame(),
&page_url,
ContextNodeType(ContextNodeType::PAGE));
if (selected_frame != webview_->main_frame()->frame()) {
frame_node =
GetTypeAndURLFromFrame(selected_frame,
&frame_url,
ContextNodeType(ContextNodeType::FRAME));
}
if (r.isSelected()) {
node_type.type |= ContextNodeType::SELECTION;
selection_text_string = CollapseWhitespace(
webkit_glue::StringToStdWString(selected_frame->selectedText()),
false);
}
if (r.isContentEditable()) {
node_type.type |= ContextNodeType::EDITABLE;
if (webview_->GetFocusedWebCoreFrame()->editor()->
isContinuousSpellCheckingEnabled()) {
misspelled_word_string = GetMisspelledWord(default_menu,
selected_frame);
}
}
if (node_type.type == ContextNodeType::NONE) {
if (selected_frame != webview_->main_frame()->frame()) {
node_type = frame_node;
} else {
node_type = page_node;
}
}
// Now retrieve the security info.
WebCore::DocumentLoader* dl = selected_frame->loader()->documentLoader();
WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
if (ds)
security_info = ds->response().securityInfo();
int edit_flags = ContextNodeType::CAN_DO_NONE;
if (webview_->GetFocusedWebCoreFrame()->editor()->canUndo())
edit_flags |= ContextNodeType::CAN_UNDO;
if (webview_->GetFocusedWebCoreFrame()->editor()->canRedo())
edit_flags |= ContextNodeType::CAN_REDO;
if (webview_->GetFocusedWebCoreFrame()->editor()->canCut())
edit_flags |= ContextNodeType::CAN_CUT;
if (webview_->GetFocusedWebCoreFrame()->editor()->canCopy())
edit_flags |= ContextNodeType::CAN_COPY;
if (webview_->GetFocusedWebCoreFrame()->editor()->canPaste())
edit_flags |= ContextNodeType::CAN_PASTE;
if (webview_->GetFocusedWebCoreFrame()->editor()->canDelete())
edit_flags |= ContextNodeType::CAN_DELETE;
// We can always select all...
edit_flags |= ContextNodeType::CAN_SELECT_ALL;
WebViewDelegate* d = webview_->delegate();
if (d) {
d->ShowContextMenu(webview_,
node_type,
menu_point.x(),
menu_point.y(),
webkit_glue::KURLToGURL(link_url),
webkit_glue::KURLToGURL(src_url),
page_url,
frame_url,
media_params,
selection_text_string,
misspelled_word_string,
edit_flags,
security_info,
frame_charset);
}
return NULL;
}
void ContextMenuClientImpl::contextMenuItemSelected(
WebCore::ContextMenuItem*, const WebCore::ContextMenu*) {
}
void ContextMenuClientImpl::downloadURL(const WebCore::KURL&) {
}
void ContextMenuClientImpl::copyImageToClipboard(const WebCore::HitTestResult&) {
}
void ContextMenuClientImpl::searchWithGoogle(const WebCore::Frame*) {
}
void ContextMenuClientImpl::lookUpInDictionary(WebCore::Frame*) {
}
void ContextMenuClientImpl::speak(const WebCore::String&) {
}
bool ContextMenuClientImpl::isSpeaking() {
return false;
}
void ContextMenuClientImpl::stopSpeaking() {
}
bool ContextMenuClientImpl::shouldIncludeInspectElementItem() {
return false; // TODO(jackson): Eventually include the inspector context menu item
}
#if defined(OS_MACOSX)
void ContextMenuClientImpl::searchWithSpotlight() {
// TODO(pinkerton): write this
}
#endif