blob: 210fb2493b74a8983c24beda59ce2954062a8461 [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"
21#include "Widget.h"
[email protected]fa419692008-10-16 21:46:1422MSVC_POP_WARNING();
initial.commitf5b16fe2008-07-27 00:20:5123#undef LOG
24
25#include "webkit/glue/context_menu_client_impl.h"
26
27#include "base/string_util.h"
[email protected]726985e22009-06-18 21:09:2828#include "webkit/api/public/WebURL.h"
29#include "webkit/api/public/WebURLResponse.h"
[email protected]e09ba552009-02-05 03:26:2930#include "webkit/glue/context_menu.h"
initial.commitf5b16fe2008-07-27 00:20:5131#include "webkit/glue/glue_util.h"
[email protected]2903f3b2009-03-13 16:30:5032#include "webkit/glue/webdatasource_impl.h"
initial.commitf5b16fe2008-07-27 00:20:5133#include "webkit/glue/webview_impl.h"
34
35#include "base/word_iterator.h"
36
[email protected]726985e22009-06-18 21:09:2837using WebKit::WebDataSource;
38
[email protected]985f0e62008-10-29 01:05:0839namespace {
40
initial.commitf5b16fe2008-07-27 00:20:5141// Helper function to determine whether text is a single word or a sentence.
[email protected]985f0e62008-10-29 01:05:0842bool IsASingleWord(const std::wstring& text) {
initial.commitf5b16fe2008-07-27 00:20:5143 WordIterator iter(text, WordIterator::BREAK_WORD);
44 int word_count = 0;
45 if (!iter.Init()) return false;
46 while (iter.Advance()) {
47 if (iter.IsWord()) {
48 word_count++;
49 if (word_count > 1) // More than one word.
50 return false;
51 }
52 }
[email protected]f0a51fb52009-03-05 12:46:3853
initial.commitf5b16fe2008-07-27 00:20:5154 // Check for 0 words.
55 if (!word_count)
56 return false;
[email protected]f0a51fb52009-03-05 12:46:3857
initial.commitf5b16fe2008-07-27 00:20:5158 // Has a single word.
59 return true;
60}
61
[email protected]f0a51fb52009-03-05 12:46:3862// Helper function to get misspelled word on which context menu
initial.commitf5b16fe2008-07-27 00:20:5163// is to be evolked. This function also sets the word on which context menu
[email protected]442a3a12009-04-23 18:14:0664// has been evoked to be the selected word, as required. This function changes
65// the selection only when there were no selected characters.
[email protected]985f0e62008-10-29 01:05:0866std::wstring GetMisspelledWord(const WebCore::ContextMenu* default_menu,
67 WebCore::Frame* selected_frame) {
initial.commitf5b16fe2008-07-27 00:20:5168 std::wstring misspelled_word_string;
69
70 // First select from selectedText to check for multiple word selection.
71 misspelled_word_string = CollapseWhitespace(
72 webkit_glue::StringToStdWString(selected_frame->selectedText()),
73 false);
74
[email protected]442a3a12009-04-23 18:14:0675 // If some texts were already selected, we don't change the selection.
76 if (!misspelled_word_string.empty()) {
77 // Don't provide suggestions for multiple words.
78 if (!IsASingleWord(misspelled_word_string))
79 return L"";
80 else
81 return misspelled_word_string;
82 }
initial.commitf5b16fe2008-07-27 00:20:5183
[email protected]3a500b352008-10-31 17:10:2184 WebCore::HitTestResult hit_test_result = selected_frame->eventHandler()->
85 hitTestResultAtPoint(default_menu->hitTestResult().point(), true);
86 WebCore::Node* inner_node = hit_test_result.innerNode();
87 WebCore::VisiblePosition pos(inner_node->renderer()->positionForPoint(
88 hit_test_result.localPoint()));
[email protected]985f0e62008-10-29 01:05:0889
[email protected]0f0981d2009-02-12 23:09:3590 WebCore::VisibleSelection selection;
initial.commitf5b16fe2008-07-27 00:20:5191 if (pos.isNotNull()) {
[email protected]0f0981d2009-02-12 23:09:3592 selection = WebCore::VisibleSelection(pos);
initial.commitf5b16fe2008-07-27 00:20:5193 selection.expandUsingGranularity(WebCore::WordGranularity);
94 }
[email protected]f0a51fb52009-03-05 12:46:3895
96 if (selection.isRange()) {
initial.commitf5b16fe2008-07-27 00:20:5197 selected_frame->setSelectionGranularity(WebCore::WordGranularity);
98 }
[email protected]f0a51fb52009-03-05 12:46:3899
initial.commitf5b16fe2008-07-27 00:20:51100 if (selected_frame->shouldChangeSelection(selection))
[email protected]de56f3782008-10-01 22:31:35101 selected_frame->selection()->setSelection(selection);
[email protected]f0a51fb52009-03-05 12:46:38102
initial.commitf5b16fe2008-07-27 00:20:51103 misspelled_word_string = CollapseWhitespace(
[email protected]f0a51fb52009-03-05 12:46:38104 webkit_glue::StringToStdWString(selected_frame->selectedText()),
initial.commitf5b16fe2008-07-27 00:20:51105 false);
[email protected]6d886b72009-02-11 23:30:11106
107 // If misspelled word is empty, then that portion should not be selected.
108 // Set the selection to that position only, and do not expand.
109 if (misspelled_word_string.empty()) {
[email protected]0f0981d2009-02-12 23:09:35110 selection = WebCore::VisibleSelection(pos);
[email protected]6d886b72009-02-11 23:30:11111 selected_frame->selection()->setSelection(selection);
112 }
113
initial.commitf5b16fe2008-07-27 00:20:51114 return misspelled_word_string;
115}
116
[email protected]985f0e62008-10-29 01:05:08117} // namespace
118
initial.commitf5b16fe2008-07-27 00:20:51119ContextMenuClientImpl::~ContextMenuClientImpl() {
120}
121
122void ContextMenuClientImpl::contextMenuDestroyed() {
123 delete this;
124}
125
126// Figure out the URL of a page or subframe. Returns |page_type| as the type,
[email protected]581b87eb2009-07-23 23:06:56127// which indicates page or subframe, or ContextNodeType::NONE if the URL could not
initial.commitf5b16fe2008-07-27 00:20:51128// be determined for some reason.
[email protected]581b87eb2009-07-23 23:06:56129static ContextNodeType GetTypeAndURLFromFrame(
130 WebCore::Frame* frame,
131 GURL* url,
132 ContextNodeType page_node_type) {
133 ContextNodeType node_type;
initial.commitf5b16fe2008-07-27 00:20:51134 if (frame) {
135 WebCore::DocumentLoader* dl = frame->loader()->documentLoader();
136 if (dl) {
[email protected]2903f3b2009-03-13 16:30:50137 WebDataSource* ds = WebDataSourceImpl::FromLoader(dl);
initial.commitf5b16fe2008-07-27 00:20:51138 if (ds) {
[email protected]581b87eb2009-07-23 23:06:56139 node_type = page_node_type;
[email protected]726985e22009-06-18 21:09:28140 *url = ds->hasUnreachableURL() ? ds->unreachableURL()
141 : ds->request().url();
initial.commitf5b16fe2008-07-27 00:20:51142 }
143 }
144 }
[email protected]581b87eb2009-07-23 23:06:56145 return node_type;
initial.commitf5b16fe2008-07-27 00:20:51146}
147
148WebCore::PlatformMenuDescription
149 ContextMenuClientImpl::getCustomMenuFromDefaultItems(
150 WebCore::ContextMenu* default_menu) {
151 // Displaying the context menu in this function is a big hack as we don't
[email protected]f0a51fb52009-03-05 12:46:38152 // have context, i.e. whether this is being invoked via a script or in
initial.commitf5b16fe2008-07-27 00:20:51153 // response to user input (Mouse event WM_RBUTTONDOWN,
154 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked
155 // in response to the above input events before popping up the context menu.
156 if (!webview_->context_menu_allowed())
157 return NULL;
158
159 WebCore::HitTestResult r = default_menu->hitTestResult();
160 WebCore::Frame* selected_frame = r.innerNonSharedNode()->document()->frame();
161
162 WebCore::IntPoint menu_point =
163 selected_frame->view()->contentsToWindow(r.point());
164
[email protected]581b87eb2009-07-23 23:06:56165 ContextNodeType node_type;
initial.commitf5b16fe2008-07-27 00:20:51166
[email protected]574a1d62009-07-17 03:23:46167 // Links, Images, Media tags, and Image/Media-Links take preference over
168 // all else.
initial.commitf5b16fe2008-07-27 00:20:51169 WebCore::KURL link_url = r.absoluteLinkURL();
initial.commitf5b16fe2008-07-27 00:20:51170 if (!link_url.isEmpty()) {
[email protected]581b87eb2009-07-23 23:06:56171 node_type.type |= ContextNodeType::LINK;
initial.commitf5b16fe2008-07-27 00:20:51172 }
[email protected]574a1d62009-07-17 03:23:46173
174 WebCore::KURL src_url;
175
176 ContextMenuMediaParams media_params;
177
178 if (!r.absoluteImageURL().isEmpty()) {
179 src_url = r.absoluteImageURL();
[email protected]581b87eb2009-07-23 23:06:56180 node_type.type |= ContextNodeType::IMAGE;
[email protected]574a1d62009-07-17 03:23:46181 } else if (!r.absoluteMediaURL().isEmpty()) {
182 src_url = r.absoluteMediaURL();
183
184 // We know that if absoluteMediaURL() is not empty, then this is a media
185 // element.
186 WebCore::HTMLMediaElement* media_element =
187 static_cast<WebCore::HTMLMediaElement*>(r.innerNonSharedNode());
188 if (media_element->hasTagName(WebCore::HTMLNames::videoTag)) {
[email protected]581b87eb2009-07-23 23:06:56189 node_type.type |= ContextNodeType::VIDEO;
[email protected]574a1d62009-07-17 03:23:46190 } else if (media_element->hasTagName(WebCore::HTMLNames::audioTag)) {
[email protected]581b87eb2009-07-23 23:06:56191 node_type.type |= ContextNodeType::AUDIO;
[email protected]574a1d62009-07-17 03:23:46192 }
193
194 media_params.playback_rate = media_element->playbackRate();
195
196 if (media_element->paused()) {
[email protected]581b87eb2009-07-23 23:06:56197 media_params.player_state |= ContextMenuMediaParams::PAUSED;
[email protected]574a1d62009-07-17 03:23:46198 }
199 if (media_element->muted()) {
[email protected]581b87eb2009-07-23 23:06:56200 media_params.player_state |= ContextMenuMediaParams::MUTED;
[email protected]574a1d62009-07-17 03:23:46201 }
202 if (media_element->loop()) {
[email protected]581b87eb2009-07-23 23:06:56203 media_params.player_state |= ContextMenuMediaParams::LOOP;
[email protected]574a1d62009-07-17 03:23:46204 }
205 if (media_element->supportsSave()) {
[email protected]581b87eb2009-07-23 23:06:56206 media_params.player_state |= ContextMenuMediaParams::CAN_SAVE;
[email protected]574a1d62009-07-17 03:23:46207 }
[email protected]581b87eb2009-07-23 23:06:56208 // TODO(ajwong): Report error states in the media player.
initial.commitf5b16fe2008-07-27 00:20:51209 }
initial.commitf5b16fe2008-07-27 00:20:51210
[email protected]574a1d62009-07-17 03:23:46211 // If it's not a link, an image, a media element, or an image/media link,
212 // show a selection menu or a more generic page menu.
initial.commitf5b16fe2008-07-27 00:20:51213 std::wstring selection_text_string;
214 std::wstring misspelled_word_string;
215 GURL frame_url;
216 GURL page_url;
[email protected]6aa376b2008-09-23 18:49:52217 std::string security_info;
[email protected]f0a51fb52009-03-05 12:46:38218
[email protected]c9825a42009-05-01 22:51:50219 std::string frame_charset = WideToASCII(
220 webkit_glue::StringToStdWString(selected_frame->loader()->encoding()));
initial.commitf5b16fe2008-07-27 00:20:51221 // Send the frame and page URLs in any case.
[email protected]581b87eb2009-07-23 23:06:56222 ContextNodeType frame_node = ContextNodeType(ContextNodeType::NONE);
223 ContextNodeType page_node =
initial.commitf5b16fe2008-07-27 00:20:51224 GetTypeAndURLFromFrame(webview_->main_frame()->frame(),
225 &page_url,
[email protected]581b87eb2009-07-23 23:06:56226 ContextNodeType(ContextNodeType::PAGE));
initial.commitf5b16fe2008-07-27 00:20:51227 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56228 frame_node =
229 GetTypeAndURLFromFrame(selected_frame,
230 &frame_url,
231 ContextNodeType(ContextNodeType::FRAME));
initial.commitf5b16fe2008-07-27 00:20:51232 }
[email protected]124646932009-01-28 18:39:02233
234 if (r.isSelected()) {
[email protected]581b87eb2009-07-23 23:06:56235 node_type.type |= ContextNodeType::SELECTION;
[email protected]124646932009-01-28 18:39:02236 selection_text_string = CollapseWhitespace(
237 webkit_glue::StringToStdWString(selected_frame->selectedText()),
238 false);
239 }
240
241 if (r.isContentEditable()) {
[email protected]581b87eb2009-07-23 23:06:56242 node_type.type |= ContextNodeType::EDITABLE;
[email protected]124646932009-01-28 18:39:02243 if (webview_->GetFocusedWebCoreFrame()->editor()->
244 isContinuousSpellCheckingEnabled()) {
245 misspelled_word_string = GetMisspelledWord(default_menu,
246 selected_frame);
247 }
248 }
[email protected]f0a51fb52009-03-05 12:46:38249
[email protected]581b87eb2009-07-23 23:06:56250 if (node_type.type == ContextNodeType::NONE) {
[email protected]124646932009-01-28 18:39:02251 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56252 node_type = frame_node;
initial.commitf5b16fe2008-07-27 00:20:51253 } else {
[email protected]581b87eb2009-07-23 23:06:56254 node_type = page_node;
initial.commitf5b16fe2008-07-27 00:20:51255 }
256 }
257
[email protected]6aa376b2008-09-23 18:49:52258 // Now retrieve the security info.
259 WebCore::DocumentLoader* dl = selected_frame->loader()->documentLoader();
[email protected]2903f3b2009-03-13 16:30:50260 WebDataSource* ds = WebDataSourceImpl::FromLoader(dl);
[email protected]726985e22009-06-18 21:09:28261 if (ds)
262 security_info = ds->response().securityInfo();
[email protected]6aa376b2008-09-23 18:49:52263
[email protected]581b87eb2009-07-23 23:06:56264 int edit_flags = ContextNodeType::CAN_DO_NONE;
initial.commitf5b16fe2008-07-27 00:20:51265 if (webview_->GetFocusedWebCoreFrame()->editor()->canUndo())
[email protected]581b87eb2009-07-23 23:06:56266 edit_flags |= ContextNodeType::CAN_UNDO;
initial.commitf5b16fe2008-07-27 00:20:51267 if (webview_->GetFocusedWebCoreFrame()->editor()->canRedo())
[email protected]581b87eb2009-07-23 23:06:56268 edit_flags |= ContextNodeType::CAN_REDO;
initial.commitf5b16fe2008-07-27 00:20:51269 if (webview_->GetFocusedWebCoreFrame()->editor()->canCut())
[email protected]581b87eb2009-07-23 23:06:56270 edit_flags |= ContextNodeType::CAN_CUT;
initial.commitf5b16fe2008-07-27 00:20:51271 if (webview_->GetFocusedWebCoreFrame()->editor()->canCopy())
[email protected]581b87eb2009-07-23 23:06:56272 edit_flags |= ContextNodeType::CAN_COPY;
initial.commitf5b16fe2008-07-27 00:20:51273 if (webview_->GetFocusedWebCoreFrame()->editor()->canPaste())
[email protected]581b87eb2009-07-23 23:06:56274 edit_flags |= ContextNodeType::CAN_PASTE;
initial.commitf5b16fe2008-07-27 00:20:51275 if (webview_->GetFocusedWebCoreFrame()->editor()->canDelete())
[email protected]581b87eb2009-07-23 23:06:56276 edit_flags |= ContextNodeType::CAN_DELETE;
initial.commitf5b16fe2008-07-27 00:20:51277 // We can always select all...
[email protected]581b87eb2009-07-23 23:06:56278 edit_flags |= ContextNodeType::CAN_SELECT_ALL;
initial.commitf5b16fe2008-07-27 00:20:51279
280 WebViewDelegate* d = webview_->delegate();
281 if (d) {
282 d->ShowContextMenu(webview_,
[email protected]581b87eb2009-07-23 23:06:56283 node_type,
initial.commitf5b16fe2008-07-27 00:20:51284 menu_point.x(),
285 menu_point.y(),
286 webkit_glue::KURLToGURL(link_url),
[email protected]574a1d62009-07-17 03:23:46287 webkit_glue::KURLToGURL(src_url),
initial.commitf5b16fe2008-07-27 00:20:51288 page_url,
289 frame_url,
[email protected]574a1d62009-07-17 03:23:46290 media_params,
initial.commitf5b16fe2008-07-27 00:20:51291 selection_text_string,
292 misspelled_word_string,
[email protected]6aa376b2008-09-23 18:49:52293 edit_flags,
[email protected]c9825a42009-05-01 22:51:50294 security_info,
295 frame_charset);
initial.commitf5b16fe2008-07-27 00:20:51296 }
297 return NULL;
298}
299
300void ContextMenuClientImpl::contextMenuItemSelected(
301 WebCore::ContextMenuItem*, const WebCore::ContextMenu*) {
302}
303
304void ContextMenuClientImpl::downloadURL(const WebCore::KURL&) {
305}
306
307void ContextMenuClientImpl::copyImageToClipboard(const WebCore::HitTestResult&) {
308}
309
310void ContextMenuClientImpl::searchWithGoogle(const WebCore::Frame*) {
311}
312
313void ContextMenuClientImpl::lookUpInDictionary(WebCore::Frame*) {
314}
315
316void ContextMenuClientImpl::speak(const WebCore::String&) {
317}
318
[email protected]f3d31052009-06-29 20:49:29319bool ContextMenuClientImpl::isSpeaking() {
320 return false;
321}
322
initial.commitf5b16fe2008-07-27 00:20:51323void ContextMenuClientImpl::stopSpeaking() {
324}
325
326bool ContextMenuClientImpl::shouldIncludeInspectElementItem() {
327 return false; // TODO(jackson): Eventually include the inspector context menu item
328}
license.botbf09a502008-08-24 00:55:55329
[email protected]7284c6502008-09-09 20:27:26330#if defined(OS_MACOSX)
331void ContextMenuClientImpl::searchWithSpotlight() {
332 // TODO(pinkerton): write this
333}
334#endif