blob: 5ad232d403eafe5c9277e9eed571e68f9f43975e [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();
[email protected]e626d7f2009-08-12 19:52:44183
[email protected]574a1d62009-07-17 03:23:46184 // 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
[email protected]574a1d62009-07-17 03:23:46194 if (media_element->paused()) {
[email protected]581b87eb2009-07-23 23:06:56195 media_params.player_state |= ContextMenuMediaParams::PAUSED;
[email protected]574a1d62009-07-17 03:23:46196 }
197 if (media_element->muted()) {
[email protected]581b87eb2009-07-23 23:06:56198 media_params.player_state |= ContextMenuMediaParams::MUTED;
[email protected]574a1d62009-07-17 03:23:46199 }
200 if (media_element->loop()) {
[email protected]581b87eb2009-07-23 23:06:56201 media_params.player_state |= ContextMenuMediaParams::LOOP;
[email protected]574a1d62009-07-17 03:23:46202 }
203 if (media_element->supportsSave()) {
[email protected]581b87eb2009-07-23 23:06:56204 media_params.player_state |= ContextMenuMediaParams::CAN_SAVE;
[email protected]574a1d62009-07-17 03:23:46205 }
[email protected]581b87eb2009-07-23 23:06:56206 // TODO(ajwong): Report error states in the media player.
initial.commitf5b16fe2008-07-27 00:20:51207 }
initial.commitf5b16fe2008-07-27 00:20:51208
[email protected]574a1d62009-07-17 03:23:46209 // If it's not a link, an image, a media element, or an image/media link,
210 // show a selection menu or a more generic page menu.
initial.commitf5b16fe2008-07-27 00:20:51211 std::wstring selection_text_string;
212 std::wstring misspelled_word_string;
213 GURL frame_url;
214 GURL page_url;
[email protected]6aa376b2008-09-23 18:49:52215 std::string security_info;
[email protected]f0a51fb52009-03-05 12:46:38216
[email protected]c9825a42009-05-01 22:51:50217 std::string frame_charset = WideToASCII(
218 webkit_glue::StringToStdWString(selected_frame->loader()->encoding()));
initial.commitf5b16fe2008-07-27 00:20:51219 // Send the frame and page URLs in any case.
[email protected]581b87eb2009-07-23 23:06:56220 ContextNodeType frame_node = ContextNodeType(ContextNodeType::NONE);
221 ContextNodeType page_node =
initial.commitf5b16fe2008-07-27 00:20:51222 GetTypeAndURLFromFrame(webview_->main_frame()->frame(),
223 &page_url,
[email protected]581b87eb2009-07-23 23:06:56224 ContextNodeType(ContextNodeType::PAGE));
initial.commitf5b16fe2008-07-27 00:20:51225 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56226 frame_node =
227 GetTypeAndURLFromFrame(selected_frame,
228 &frame_url,
229 ContextNodeType(ContextNodeType::FRAME));
initial.commitf5b16fe2008-07-27 00:20:51230 }
[email protected]124646932009-01-28 18:39:02231
232 if (r.isSelected()) {
[email protected]581b87eb2009-07-23 23:06:56233 node_type.type |= ContextNodeType::SELECTION;
[email protected]124646932009-01-28 18:39:02234 selection_text_string = CollapseWhitespace(
235 webkit_glue::StringToStdWString(selected_frame->selectedText()),
236 false);
237 }
238
239 if (r.isContentEditable()) {
[email protected]581b87eb2009-07-23 23:06:56240 node_type.type |= ContextNodeType::EDITABLE;
[email protected]124646932009-01-28 18:39:02241 if (webview_->GetFocusedWebCoreFrame()->editor()->
242 isContinuousSpellCheckingEnabled()) {
243 misspelled_word_string = GetMisspelledWord(default_menu,
244 selected_frame);
245 }
246 }
[email protected]f0a51fb52009-03-05 12:46:38247
[email protected]581b87eb2009-07-23 23:06:56248 if (node_type.type == ContextNodeType::NONE) {
[email protected]124646932009-01-28 18:39:02249 if (selected_frame != webview_->main_frame()->frame()) {
[email protected]581b87eb2009-07-23 23:06:56250 node_type = frame_node;
initial.commitf5b16fe2008-07-27 00:20:51251 } else {
[email protected]581b87eb2009-07-23 23:06:56252 node_type = page_node;
initial.commitf5b16fe2008-07-27 00:20:51253 }
254 }
255
[email protected]6aa376b2008-09-23 18:49:52256 // Now retrieve the security info.
257 WebCore::DocumentLoader* dl = selected_frame->loader()->documentLoader();
[email protected]2903f3b2009-03-13 16:30:50258 WebDataSource* ds = WebDataSourceImpl::FromLoader(dl);
[email protected]726985e22009-06-18 21:09:28259 if (ds)
260 security_info = ds->response().securityInfo();
[email protected]6aa376b2008-09-23 18:49:52261
[email protected]581b87eb2009-07-23 23:06:56262 int edit_flags = ContextNodeType::CAN_DO_NONE;
initial.commitf5b16fe2008-07-27 00:20:51263 if (webview_->GetFocusedWebCoreFrame()->editor()->canUndo())
[email protected]581b87eb2009-07-23 23:06:56264 edit_flags |= ContextNodeType::CAN_UNDO;
initial.commitf5b16fe2008-07-27 00:20:51265 if (webview_->GetFocusedWebCoreFrame()->editor()->canRedo())
[email protected]581b87eb2009-07-23 23:06:56266 edit_flags |= ContextNodeType::CAN_REDO;
initial.commitf5b16fe2008-07-27 00:20:51267 if (webview_->GetFocusedWebCoreFrame()->editor()->canCut())
[email protected]581b87eb2009-07-23 23:06:56268 edit_flags |= ContextNodeType::CAN_CUT;
initial.commitf5b16fe2008-07-27 00:20:51269 if (webview_->GetFocusedWebCoreFrame()->editor()->canCopy())
[email protected]581b87eb2009-07-23 23:06:56270 edit_flags |= ContextNodeType::CAN_COPY;
initial.commitf5b16fe2008-07-27 00:20:51271 if (webview_->GetFocusedWebCoreFrame()->editor()->canPaste())
[email protected]581b87eb2009-07-23 23:06:56272 edit_flags |= ContextNodeType::CAN_PASTE;
initial.commitf5b16fe2008-07-27 00:20:51273 if (webview_->GetFocusedWebCoreFrame()->editor()->canDelete())
[email protected]581b87eb2009-07-23 23:06:56274 edit_flags |= ContextNodeType::CAN_DELETE;
initial.commitf5b16fe2008-07-27 00:20:51275 // We can always select all...
[email protected]581b87eb2009-07-23 23:06:56276 edit_flags |= ContextNodeType::CAN_SELECT_ALL;
initial.commitf5b16fe2008-07-27 00:20:51277
278 WebViewDelegate* d = webview_->delegate();
279 if (d) {
280 d->ShowContextMenu(webview_,
[email protected]581b87eb2009-07-23 23:06:56281 node_type,
initial.commitf5b16fe2008-07-27 00:20:51282 menu_point.x(),
283 menu_point.y(),
284 webkit_glue::KURLToGURL(link_url),
[email protected]574a1d62009-07-17 03:23:46285 webkit_glue::KURLToGURL(src_url),
initial.commitf5b16fe2008-07-27 00:20:51286 page_url,
287 frame_url,
[email protected]574a1d62009-07-17 03:23:46288 media_params,
initial.commitf5b16fe2008-07-27 00:20:51289 selection_text_string,
290 misspelled_word_string,
[email protected]6aa376b2008-09-23 18:49:52291 edit_flags,
[email protected]c9825a42009-05-01 22:51:50292 security_info,
293 frame_charset);
initial.commitf5b16fe2008-07-27 00:20:51294 }
295 return NULL;
296}
297
298void ContextMenuClientImpl::contextMenuItemSelected(
299 WebCore::ContextMenuItem*, const WebCore::ContextMenu*) {
300}
301
302void ContextMenuClientImpl::downloadURL(const WebCore::KURL&) {
303}
304
305void ContextMenuClientImpl::copyImageToClipboard(const WebCore::HitTestResult&) {
306}
307
308void ContextMenuClientImpl::searchWithGoogle(const WebCore::Frame*) {
309}
310
311void ContextMenuClientImpl::lookUpInDictionary(WebCore::Frame*) {
312}
313
314void ContextMenuClientImpl::speak(const WebCore::String&) {
315}
316
[email protected]f3d31052009-06-29 20:49:29317bool ContextMenuClientImpl::isSpeaking() {
318 return false;
319}
320
initial.commitf5b16fe2008-07-27 00:20:51321void ContextMenuClientImpl::stopSpeaking() {
322}
323
324bool ContextMenuClientImpl::shouldIncludeInspectElementItem() {
325 return false; // TODO(jackson): Eventually include the inspector context menu item
326}
license.botbf09a502008-08-24 00:55:55327
[email protected]7284c6502008-09-09 20:27:26328#if defined(OS_MACOSX)
329void ContextMenuClientImpl::searchWithSpotlight() {
330 // TODO(pinkerton): write this
331}
332#endif