blob: c3c6ff2fea846234d5a683de6d79e9f80f10216c [file] [log] [blame]
[email protected]574a1d62009-07-17 03:23:461// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commitf5b16fe2008-07-27 00:20:514
5#include "config.h"
6
[email protected]fa419692008-10-16 21:46:147#include "base/compiler_specific.h"
8
9MSVC_PUSH_WARNING_LEVEL(0);
initial.commitf5b16fe2008-07-27 00:20:5110#include "ContextMenu.h"
11#include "Document.h"
12#include "DocumentLoader.h"
13#include "Editor.h"
[email protected]985f0e62008-10-29 01:05:0814#include "EventHandler.h"
initial.commitf5b16fe2008-07-27 00:20:5115#include "FrameLoader.h"
16#include "FrameView.h"
17#include "HitTestResult.h"
[email protected]574a1d62009-07-17 03:23:4618#include "HTMLMediaElement.h"
19#include "HTMLNames.h"
initial.commitf5b16fe2008-07-27 00:20:5120#include "KURL.h"
[email protected]c67b2c02009-08-20 19:23:3621#include "MediaError.h"
initial.commitf5b16fe2008-07-27 00:20:5122#include "Widget.h"
[email protected]fa419692008-10-16 21:46:1423MSVC_POP_WARNING();
initial.commitf5b16fe2008-07-27 00:20:5124#undef LOG
25
26#include "webkit/glue/context_menu_client_impl.h"
27
28#include "base/string_util.h"
[email protected]726985e22009-06-18 21:09:2829#include "webkit/api/public/WebURL.h"
30#include "webkit/api/public/WebURLResponse.h"
[email protected]31f4c7e2009-08-27 20:43:2931#include "webkit/api/src/WebDataSourceImpl.h"
[email protected]e09ba552009-02-05 03:26:2932#include "webkit/glue/context_menu.h"
initial.commitf5b16fe2008-07-27 00:20:5133#include "webkit/glue/glue_util.h"
initial.commitf5b16fe2008-07-27 00:20:5134#include "webkit/glue/webview_impl.h"
35
36#include "base/word_iterator.h"
37
[email protected]726985e22009-06-18 21:09:2838using WebKit::WebDataSource;
[email protected]31f4c7e2009-08-27 20:43:2939using WebKit::WebDataSourceImpl;
[email protected]726985e22009-06-18 21:09:2840
[email protected]985f0e62008-10-29 01:05:0841namespace {
42
initial.commitf5b16fe2008-07-27 00:20:5143// Helper function to determine whether text is a single word or a sentence.
[email protected]985f0e62008-10-29 01:05:0844bool IsASingleWord(const std::wstring& text) {
initial.commitf5b16fe2008-07-27 00:20:5145 WordIterator iter(text, WordIterator::BREAK_WORD);
46 int word_count = 0;
47 if (!iter.Init()) return false;
48 while (iter.Advance()) {
49 if (iter.IsWord()) {
50 word_count++;
51 if (word_count > 1) // More than one word.
52 return false;
53 }
54 }
[email protected]f0a51fb52009-03-05 12:46:3855
initial.commitf5b16fe2008-07-27 00:20:5156 // Check for 0 words.
57 if (!word_count)
58 return false;
[email protected]f0a51fb52009-03-05 12:46:3859
initial.commitf5b16fe2008-07-27 00:20:5160 // Has a single word.
61 return true;
62}
63
[email protected]f0a51fb52009-03-05 12:46:3864// Helper function to get misspelled word on which context menu
initial.commitf5b16fe2008-07-27 00:20:5165// is to be evolked. This function also sets the word on which context menu
[email protected]442a3a12009-04-23 18:14:0666// has been evoked to be the selected word, as required. This function changes
67// the selection only when there were no selected characters.
[email protected]985f0e62008-10-29 01:05:0868std::wstring GetMisspelledWord(const WebCore::ContextMenu* default_menu,
69 WebCore::Frame* selected_frame) {
initial.commitf5b16fe2008-07-27 00:20:5170 std::wstring misspelled_word_string;
71
72 // First select from selectedText to check for multiple word selection.
73 misspelled_word_string = CollapseWhitespace(
74 webkit_glue::StringToStdWString(selected_frame->selectedText()),
75 false);
76
[email protected]442a3a12009-04-23 18:14:0677 // If some texts were already selected, we don't change the selection.
78 if (!misspelled_word_string.empty()) {
79 // Don't provide suggestions for multiple words.
80 if (!IsASingleWord(misspelled_word_string))
81 return L"";
82 else
83 return misspelled_word_string;
84 }
initial.commitf5b16fe2008-07-27 00:20:5185
[email protected]3a500b352008-10-31 17:10:2186 WebCore::HitTestResult hit_test_result = selected_frame->eventHandler()->
87 hitTestResultAtPoint(default_menu->hitTestResult().point(), true);
88 WebCore::Node* inner_node = hit_test_result.innerNode();
89 WebCore::VisiblePosition pos(inner_node->renderer()->positionForPoint(
90 hit_test_result.localPoint()));
[email protected]985f0e62008-10-29 01:05:0891
[email protected]0f0981d2009-02-12 23:09:3592 WebCore::VisibleSelection selection;
initial.commitf5b16fe2008-07-27 00:20:5193 if (pos.isNotNull()) {
[email protected]0f0981d2009-02-12 23:09:3594 selection = WebCore::VisibleSelection(pos);
initial.commitf5b16fe2008-07-27 00:20:5195 selection.expandUsingGranularity(WebCore::WordGranularity);
96 }
[email protected]f0a51fb52009-03-05 12:46:3897
98 if (selection.isRange()) {
initial.commitf5b16fe2008-07-27 00:20:5199 selected_frame->setSelectionGranularity(WebCore::WordGranularity);
100 }
[email protected]f0a51fb52009-03-05 12:46:38101
initial.commitf5b16fe2008-07-27 00:20:51102 if (selected_frame->shouldChangeSelection(selection))
[email protected]de56f3782008-10-01 22:31:35103 selected_frame->selection()->setSelection(selection);
[email protected]f0a51fb52009-03-05 12:46:38104
initial.commitf5b16fe2008-07-27 00:20:51105 misspelled_word_string = CollapseWhitespace(
[email protected]f0a51fb52009-03-05 12:46:38106 webkit_glue::StringToStdWString(selected_frame->selectedText()),
initial.commitf5b16fe2008-07-27 00:20:51107 false);
[email protected]6d886b72009-02-11 23:30:11108
109 // If misspelled word is empty, then that portion should not be selected.
110 // Set the selection to that position only, and do not expand.
111 if (misspelled_word_string.empty()) {
[email protected]0f0981d2009-02-12 23:09:35112 selection = WebCore::VisibleSelection(pos);
[email protected]6d886b72009-02-11 23:30:11113 selected_frame->selection()->setSelection(selection);
114 }
115
initial.commitf5b16fe2008-07-27 00:20:51116 return misspelled_word_string;
117}
118
[email protected]985f0e62008-10-29 01:05:08119} // namespace
120
initial.commitf5b16fe2008-07-27 00:20:51121ContextMenuClientImpl::~ContextMenuClientImpl() {
122}
123
124void ContextMenuClientImpl::contextMenuDestroyed() {
125 delete this;
126}
127
128// Figure out the URL of a page or subframe. Returns |page_type| as the type,
[email protected]581b87eb2009-07-23 23:06:56129// which indicates page or subframe, or ContextNodeType::NONE if the URL could not
initial.commitf5b16fe2008-07-27 00:20:51130// be determined for some reason.
[email protected]581b87eb2009-07-23 23:06:56131static ContextNodeType GetTypeAndURLFromFrame(
132 WebCore::Frame* frame,
133 GURL* url,
134 ContextNodeType page_node_type) {
135 ContextNodeType node_type;
initial.commitf5b16fe2008-07-27 00:20:51136 if (frame) {
137 WebCore::DocumentLoader* dl = frame->loader()->documentLoader();
138 if (dl) {
[email protected]31f4c7e2009-08-27 20:43:29139 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
initial.commitf5b16fe2008-07-27 00:20:51140 if (ds) {
[email protected]581b87eb2009-07-23 23:06:56141 node_type = page_node_type;
[email protected]726985e22009-06-18 21:09:28142 *url = ds->hasUnreachableURL() ? ds->unreachableURL()
143 : ds->request().url();
initial.commitf5b16fe2008-07-27 00:20:51144 }
145 }
146 }
[email protected]581b87eb2009-07-23 23:06:56147 return node_type;
initial.commitf5b16fe2008-07-27 00:20:51148}
149
150WebCore::PlatformMenuDescription
151 ContextMenuClientImpl::getCustomMenuFromDefaultItems(
152 WebCore::ContextMenu* default_menu) {
153 // Displaying the context menu in this function is a big hack as we don't
[email protected]f0a51fb52009-03-05 12:46:38154 // have context, i.e. whether this is being invoked via a script or in
initial.commitf5b16fe2008-07-27 00:20:51155 // response to user input (Mouse event WM_RBUTTONDOWN,
156 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked
157 // in response to the above input events before popping up the context menu.
158 if (!webview_->context_menu_allowed())
159 return NULL;
160
161 WebCore::HitTestResult r = default_menu->hitTestResult();
162 WebCore::Frame* selected_frame = r.innerNonSharedNode()->document()->frame();
163
164 WebCore::IntPoint menu_point =
165 selected_frame->view()->contentsToWindow(r.point());
166
[email protected]581b87eb2009-07-23 23:06:56167 ContextNodeType node_type;
initial.commitf5b16fe2008-07-27 00:20:51168
[email protected]574a1d62009-07-17 03:23:46169 // Links, Images, Media tags, and Image/Media-Links take preference over
170 // all else.
initial.commitf5b16fe2008-07-27 00:20:51171 WebCore::KURL link_url = r.absoluteLinkURL();
initial.commitf5b16fe2008-07-27 00:20:51172 if (!link_url.isEmpty()) {
[email protected]581b87eb2009-07-23 23:06:56173 node_type.type |= ContextNodeType::LINK;
initial.commitf5b16fe2008-07-27 00:20:51174 }
[email protected]574a1d62009-07-17 03:23:46175
176 WebCore::KURL src_url;
177
178 ContextMenuMediaParams media_params;
179
180 if (!r.absoluteImageURL().isEmpty()) {
181 src_url = r.absoluteImageURL();
[email protected]581b87eb2009-07-23 23:06:56182 node_type.type |= ContextNodeType::IMAGE;
[email protected]574a1d62009-07-17 03:23:46183 } else if (!r.absoluteMediaURL().isEmpty()) {
184 src_url = r.absoluteMediaURL();
[email protected]e626d7f2009-08-12 19:52:44185
[email protected]574a1d62009-07-17 03:23:46186 // We know that if absoluteMediaURL() is not empty, then this is a media
187 // element.
188 WebCore::HTMLMediaElement* media_element =
189 static_cast<WebCore::HTMLMediaElement*>(r.innerNonSharedNode());
190 if (media_element->hasTagName(WebCore::HTMLNames::videoTag)) {
[email protected]581b87eb2009-07-23 23:06:56191 node_type.type |= ContextNodeType::VIDEO;
[email protected]574a1d62009-07-17 03:23:46192 } else if (media_element->hasTagName(WebCore::HTMLNames::audioTag)) {
[email protected]581b87eb2009-07-23 23:06:56193 node_type.type |= ContextNodeType::AUDIO;
[email protected]574a1d62009-07-17 03:23:46194 }
195
[email protected]c67b2c02009-08-20 19:23:36196 if (media_element->error()) {
197 media_params.player_state |= ContextMenuMediaParams::IN_ERROR;
198 }
[email protected]574a1d62009-07-17 03:23:46199 if (media_element->paused()) {
[email protected]581b87eb2009-07-23 23:06:56200 media_params.player_state |= ContextMenuMediaParams::PAUSED;
[email protected]574a1d62009-07-17 03:23:46201 }
202 if (media_element->muted()) {
[email protected]581b87eb2009-07-23 23:06:56203 media_params.player_state |= ContextMenuMediaParams::MUTED;
[email protected]574a1d62009-07-17 03:23:46204 }
205 if (media_element->loop()) {
[email protected]581b87eb2009-07-23 23:06:56206 media_params.player_state |= ContextMenuMediaParams::LOOP;
[email protected]574a1d62009-07-17 03:23:46207 }
208 if (media_element->supportsSave()) {
[email protected]581b87eb2009-07-23 23:06:56209 media_params.player_state |= ContextMenuMediaParams::CAN_SAVE;
[email protected]574a1d62009-07-17 03:23:46210 }
[email protected]bdaa1b752009-08-20 21:28:17211
212 media_params.has_audio = media_element->hasAudio();
initial.commitf5b16fe2008-07-27 00:20:51213 }
initial.commitf5b16fe2008-07-27 00:20:51214
[email protected]574a1d62009-07-17 03:23:46215 // If it's not a link, an image, a media element, or an image/media link,
216 // show a selection menu or a more generic page menu.
initial.commitf5b16fe2008-07-27 00:20:51217 std::wstring selection_text_string;
218 std::wstring misspelled_word_string;
219 GURL frame_url;
220 GURL page_url;
[email protected]6aa376b2008-09-23 18:49:52221 std::string security_info;
[email protected]f0a51fb52009-03-05 12:46:38222
[email protected]c9825a42009-05-01 22:51:50223 std::string frame_charset = WideToASCII(
224 webkit_glue::StringToStdWString(selected_frame->loader()->encoding()));
initial.commitf5b16fe2008-07-27 00:20:51225 // Send the frame and page URLs in any case.
[email protected]581b87eb2009-07-23 23:06:56226 ContextNodeType frame_node = ContextNodeType(ContextNodeType::NONE);
227 ContextNodeType page_node =
initial.commitf5b16fe2008-07-27 00:20:51228 GetTypeAndURLFromFrame(webview_->main_frame()->frame(),
229 &page_url,
[email protected]581b87eb2009-07-23 23:06:56230 ContextNodeType(ContextNodeType::PAGE));
initial.commitf5b16fe2008-07-27 00:20:51231 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56232 frame_node =
233 GetTypeAndURLFromFrame(selected_frame,
234 &frame_url,
235 ContextNodeType(ContextNodeType::FRAME));
initial.commitf5b16fe2008-07-27 00:20:51236 }
[email protected]124646932009-01-28 18:39:02237
238 if (r.isSelected()) {
[email protected]581b87eb2009-07-23 23:06:56239 node_type.type |= ContextNodeType::SELECTION;
[email protected]124646932009-01-28 18:39:02240 selection_text_string = CollapseWhitespace(
241 webkit_glue::StringToStdWString(selected_frame->selectedText()),
242 false);
243 }
244
245 if (r.isContentEditable()) {
[email protected]581b87eb2009-07-23 23:06:56246 node_type.type |= ContextNodeType::EDITABLE;
[email protected]124646932009-01-28 18:39:02247 if (webview_->GetFocusedWebCoreFrame()->editor()->
248 isContinuousSpellCheckingEnabled()) {
249 misspelled_word_string = GetMisspelledWord(default_menu,
250 selected_frame);
251 }
252 }
[email protected]f0a51fb52009-03-05 12:46:38253
[email protected]581b87eb2009-07-23 23:06:56254 if (node_type.type == ContextNodeType::NONE) {
[email protected]124646932009-01-28 18:39:02255 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56256 node_type = frame_node;
initial.commitf5b16fe2008-07-27 00:20:51257 } else {
[email protected]581b87eb2009-07-23 23:06:56258 node_type = page_node;
initial.commitf5b16fe2008-07-27 00:20:51259 }
260 }
261
[email protected]6aa376b2008-09-23 18:49:52262 // Now retrieve the security info.
263 WebCore::DocumentLoader* dl = selected_frame->loader()->documentLoader();
[email protected]31f4c7e2009-08-27 20:43:29264 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
[email protected]726985e22009-06-18 21:09:28265 if (ds)
266 security_info = ds->response().securityInfo();
[email protected]6aa376b2008-09-23 18:49:52267
[email protected]581b87eb2009-07-23 23:06:56268 int edit_flags = ContextNodeType::CAN_DO_NONE;
initial.commitf5b16fe2008-07-27 00:20:51269 if (webview_->GetFocusedWebCoreFrame()->editor()->canUndo())
[email protected]581b87eb2009-07-23 23:06:56270 edit_flags |= ContextNodeType::CAN_UNDO;
initial.commitf5b16fe2008-07-27 00:20:51271 if (webview_->GetFocusedWebCoreFrame()->editor()->canRedo())
[email protected]581b87eb2009-07-23 23:06:56272 edit_flags |= ContextNodeType::CAN_REDO;
initial.commitf5b16fe2008-07-27 00:20:51273 if (webview_->GetFocusedWebCoreFrame()->editor()->canCut())
[email protected]581b87eb2009-07-23 23:06:56274 edit_flags |= ContextNodeType::CAN_CUT;
initial.commitf5b16fe2008-07-27 00:20:51275 if (webview_->GetFocusedWebCoreFrame()->editor()->canCopy())
[email protected]581b87eb2009-07-23 23:06:56276 edit_flags |= ContextNodeType::CAN_COPY;
initial.commitf5b16fe2008-07-27 00:20:51277 if (webview_->GetFocusedWebCoreFrame()->editor()->canPaste())
[email protected]581b87eb2009-07-23 23:06:56278 edit_flags |= ContextNodeType::CAN_PASTE;
initial.commitf5b16fe2008-07-27 00:20:51279 if (webview_->GetFocusedWebCoreFrame()->editor()->canDelete())
[email protected]581b87eb2009-07-23 23:06:56280 edit_flags |= ContextNodeType::CAN_DELETE;
initial.commitf5b16fe2008-07-27 00:20:51281 // We can always select all...
[email protected]581b87eb2009-07-23 23:06:56282 edit_flags |= ContextNodeType::CAN_SELECT_ALL;
initial.commitf5b16fe2008-07-27 00:20:51283
284 WebViewDelegate* d = webview_->delegate();
285 if (d) {
286 d->ShowContextMenu(webview_,
[email protected]581b87eb2009-07-23 23:06:56287 node_type,
initial.commitf5b16fe2008-07-27 00:20:51288 menu_point.x(),
289 menu_point.y(),
290 webkit_glue::KURLToGURL(link_url),
[email protected]574a1d62009-07-17 03:23:46291 webkit_glue::KURLToGURL(src_url),
initial.commitf5b16fe2008-07-27 00:20:51292 page_url,
293 frame_url,
[email protected]574a1d62009-07-17 03:23:46294 media_params,
initial.commitf5b16fe2008-07-27 00:20:51295 selection_text_string,
296 misspelled_word_string,
[email protected]6aa376b2008-09-23 18:49:52297 edit_flags,
[email protected]c9825a42009-05-01 22:51:50298 security_info,
299 frame_charset);
initial.commitf5b16fe2008-07-27 00:20:51300 }
301 return NULL;
302}
303
304void ContextMenuClientImpl::contextMenuItemSelected(
305 WebCore::ContextMenuItem*, const WebCore::ContextMenu*) {
306}
307
308void ContextMenuClientImpl::downloadURL(const WebCore::KURL&) {
309}
310
311void ContextMenuClientImpl::copyImageToClipboard(const WebCore::HitTestResult&) {
312}
313
314void ContextMenuClientImpl::searchWithGoogle(const WebCore::Frame*) {
315}
316
317void ContextMenuClientImpl::lookUpInDictionary(WebCore::Frame*) {
318}
319
320void ContextMenuClientImpl::speak(const WebCore::String&) {
321}
322
[email protected]f3d31052009-06-29 20:49:29323bool ContextMenuClientImpl::isSpeaking() {
324 return false;
325}
326
initial.commitf5b16fe2008-07-27 00:20:51327void ContextMenuClientImpl::stopSpeaking() {
328}
329
330bool ContextMenuClientImpl::shouldIncludeInspectElementItem() {
331 return false; // TODO(jackson): Eventually include the inspector context menu item
332}
license.botbf09a502008-08-24 00:55:55333
[email protected]7284c6502008-09-09 20:27:26334#if defined(OS_MACOSX)
335void ContextMenuClientImpl::searchWithSpotlight() {
336 // TODO(pinkerton): write this
337}
338#endif