|
1 /* |
|
2 * Copyright (C) 2009 Google Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 #include "config.h" |
|
32 #include "ContextMenuClientImpl.h" |
|
33 |
|
34 #include "CSSPropertyNames.h" |
|
35 #include "CSSStyleDeclaration.h" |
|
36 #include "ContextMenu.h" |
|
37 #include "Document.h" |
|
38 #include "DocumentLoader.h" |
|
39 #include "Editor.h" |
|
40 #include "EventHandler.h" |
|
41 #include "FrameLoader.h" |
|
42 #include "FrameView.h" |
|
43 #include "HitTestResult.h" |
|
44 #include "HTMLMediaElement.h" |
|
45 #include "HTMLNames.h" |
|
46 #include "KURL.h" |
|
47 #include "MediaError.h" |
|
48 #include "PlatformString.h" |
|
49 #include "RenderWidget.h" |
|
50 #include "TextBreakIterator.h" |
|
51 #include "Widget.h" |
|
52 |
|
53 #include "WebContextMenuData.h" |
|
54 #include "WebDataSourceImpl.h" |
|
55 #include "WebFrameImpl.h" |
|
56 #include "WebMenuItemInfo.h" |
|
57 #include "WebPlugin.h" |
|
58 #include "WebPluginContainerImpl.h" |
|
59 #include "WebPoint.h" |
|
60 #include "WebString.h" |
|
61 #include "WebURL.h" |
|
62 #include "WebURLResponse.h" |
|
63 #include "WebVector.h" |
|
64 #include "WebViewClient.h" |
|
65 #include "WebViewImpl.h" |
|
66 |
|
67 using namespace WebCore; |
|
68 |
|
69 namespace WebKit { |
|
70 |
|
71 // Figure out the URL of a page or subframe. Returns |page_type| as the type, |
|
72 // which indicates page or subframe, or ContextNodeType::NONE if the URL could not |
|
73 // be determined for some reason. |
|
74 static WebURL urlFromFrame(Frame* frame) |
|
75 { |
|
76 if (frame) { |
|
77 DocumentLoader* dl = frame->loader()->documentLoader(); |
|
78 if (dl) { |
|
79 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl); |
|
80 if (ds) |
|
81 return ds->hasUnreachableURL() ? ds->unreachableURL() : ds->request().url(); |
|
82 } |
|
83 } |
|
84 return WebURL(); |
|
85 } |
|
86 |
|
87 // Helper function to determine whether text is a single word. |
|
88 static bool isASingleWord(const String& text) |
|
89 { |
|
90 TextBreakIterator* it = wordBreakIterator(text.characters(), text.length()); |
|
91 return it && textBreakNext(it) == static_cast<int>(text.length()); |
|
92 } |
|
93 |
|
94 // Helper function to get misspelled word on which context menu |
|
95 // is to be evolked. This function also sets the word on which context menu |
|
96 // has been evoked to be the selected word, as required. This function changes |
|
97 // the selection only when there were no selected characters on OS X. |
|
98 static String selectMisspelledWord(const ContextMenu* defaultMenu, Frame* selectedFrame) |
|
99 { |
|
100 // First select from selectedText to check for multiple word selection. |
|
101 String misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); |
|
102 |
|
103 // If some texts were already selected, we don't change the selection. |
|
104 if (!misspelledWord.isEmpty()) { |
|
105 // Don't provide suggestions for multiple words. |
|
106 if (!isASingleWord(misspelledWord)) |
|
107 return String(); |
|
108 return misspelledWord; |
|
109 } |
|
110 |
|
111 // Selection is empty, so change the selection to the word under the cursor. |
|
112 HitTestResult hitTestResult = selectedFrame->eventHandler()-> |
|
113 hitTestResultAtPoint(defaultMenu->hitTestResult().point(), true); |
|
114 Node* innerNode = hitTestResult.innerNode(); |
|
115 VisiblePosition pos(innerNode->renderer()->positionForPoint( |
|
116 hitTestResult.localPoint())); |
|
117 |
|
118 if (pos.isNull()) |
|
119 return misspelledWord; // It is empty. |
|
120 |
|
121 WebFrameImpl::selectWordAroundPosition(selectedFrame, pos); |
|
122 misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); |
|
123 |
|
124 #if OS(DARWIN) |
|
125 // If misspelled word is still empty, then that portion should not be |
|
126 // selected. Set the selection to that position only, and do not expand. |
|
127 if (misspelledWord.isEmpty()) |
|
128 selectedFrame->selection()->setSelection(VisibleSelection(pos)); |
|
129 #else |
|
130 // On non-Mac, right-click should not make a range selection in any case. |
|
131 selectedFrame->selection()->setSelection(VisibleSelection(pos)); |
|
132 #endif |
|
133 return misspelledWord; |
|
134 } |
|
135 |
|
136 PlatformMenuDescription ContextMenuClientImpl::getCustomMenuFromDefaultItems( |
|
137 ContextMenu* defaultMenu) |
|
138 { |
|
139 // Displaying the context menu in this function is a big hack as we don't |
|
140 // have context, i.e. whether this is being invoked via a script or in |
|
141 // response to user input (Mouse event WM_RBUTTONDOWN, |
|
142 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked |
|
143 // in response to the above input events before popping up the context menu. |
|
144 if (!m_webView->contextMenuAllowed()) |
|
145 return 0; |
|
146 |
|
147 HitTestResult r = defaultMenu->hitTestResult(); |
|
148 Frame* selectedFrame = r.innerNonSharedNode()->document()->frame(); |
|
149 |
|
150 WebContextMenuData data; |
|
151 data.mousePosition = selectedFrame->view()->contentsToWindow(r.point()); |
|
152 |
|
153 // Compute edit flags. |
|
154 data.editFlags = WebContextMenuData::CanDoNone; |
|
155 if (m_webView->focusedWebCoreFrame()->editor()->canUndo()) |
|
156 data.editFlags |= WebContextMenuData::CanUndo; |
|
157 if (m_webView->focusedWebCoreFrame()->editor()->canRedo()) |
|
158 data.editFlags |= WebContextMenuData::CanRedo; |
|
159 if (m_webView->focusedWebCoreFrame()->editor()->canCut()) |
|
160 data.editFlags |= WebContextMenuData::CanCut; |
|
161 if (m_webView->focusedWebCoreFrame()->editor()->canCopy()) |
|
162 data.editFlags |= WebContextMenuData::CanCopy; |
|
163 if (m_webView->focusedWebCoreFrame()->editor()->canPaste()) |
|
164 data.editFlags |= WebContextMenuData::CanPaste; |
|
165 if (m_webView->focusedWebCoreFrame()->editor()->canDelete()) |
|
166 data.editFlags |= WebContextMenuData::CanDelete; |
|
167 // We can always select all... |
|
168 data.editFlags |= WebContextMenuData::CanSelectAll; |
|
169 data.editFlags |= WebContextMenuData::CanTranslate; |
|
170 |
|
171 // Links, Images, Media tags, and Image/Media-Links take preference over |
|
172 // all else. |
|
173 data.linkURL = r.absoluteLinkURL(); |
|
174 |
|
175 if (!r.absoluteImageURL().isEmpty()) { |
|
176 data.srcURL = r.absoluteImageURL(); |
|
177 data.mediaType = WebContextMenuData::MediaTypeImage; |
|
178 } else if (!r.absoluteMediaURL().isEmpty()) { |
|
179 data.srcURL = r.absoluteMediaURL(); |
|
180 |
|
181 // We know that if absoluteMediaURL() is not empty, then this |
|
182 // is a media element. |
|
183 HTMLMediaElement* mediaElement = |
|
184 static_cast<HTMLMediaElement*>(r.innerNonSharedNode()); |
|
185 if (mediaElement->hasTagName(HTMLNames::videoTag)) |
|
186 data.mediaType = WebContextMenuData::MediaTypeVideo; |
|
187 else if (mediaElement->hasTagName(HTMLNames::audioTag)) |
|
188 data.mediaType = WebContextMenuData::MediaTypeAudio; |
|
189 |
|
190 if (mediaElement->error()) |
|
191 data.mediaFlags |= WebContextMenuData::MediaInError; |
|
192 if (mediaElement->paused()) |
|
193 data.mediaFlags |= WebContextMenuData::MediaPaused; |
|
194 if (mediaElement->muted()) |
|
195 data.mediaFlags |= WebContextMenuData::MediaMuted; |
|
196 if (mediaElement->loop()) |
|
197 data.mediaFlags |= WebContextMenuData::MediaLoop; |
|
198 if (mediaElement->supportsSave()) |
|
199 data.mediaFlags |= WebContextMenuData::MediaCanSave; |
|
200 if (mediaElement->hasAudio()) |
|
201 data.mediaFlags |= WebContextMenuData::MediaHasAudio; |
|
202 if (mediaElement->hasVideo()) |
|
203 data.mediaFlags |= WebContextMenuData::MediaHasVideo; |
|
204 if (mediaElement->controls()) |
|
205 data.mediaFlags |= WebContextMenuData::MediaControls; |
|
206 } else if (r.innerNonSharedNode()->hasTagName(HTMLNames::objectTag) |
|
207 || r.innerNonSharedNode()->hasTagName(HTMLNames::embedTag)) { |
|
208 RenderObject* object = r.innerNonSharedNode()->renderer(); |
|
209 if (object && object->isWidget()) { |
|
210 Widget* widget = toRenderWidget(object)->widget(); |
|
211 if (widget) { |
|
212 WebPluginContainerImpl* plugin = static_cast<WebPluginContainerImpl*>(widget); |
|
213 WebString text = plugin->plugin()->selectionAsText(); |
|
214 if (!text.isEmpty()) { |
|
215 data.selectedText = text; |
|
216 data.editFlags |= WebContextMenuData::CanCopy; |
|
217 } |
|
218 data.editFlags &= ~WebContextMenuData::CanTranslate; |
|
219 } |
|
220 } |
|
221 } |
|
222 |
|
223 data.isImageBlocked = |
|
224 (data.mediaType == WebContextMenuData::MediaTypeImage) && !r.image(); |
|
225 |
|
226 // If it's not a link, an image, a media element, or an image/media link, |
|
227 // show a selection menu or a more generic page menu. |
|
228 data.frameEncoding = selectedFrame->loader()->writer()->encoding(); |
|
229 |
|
230 // Send the frame and page URLs in any case. |
|
231 data.pageURL = urlFromFrame(m_webView->mainFrameImpl()->frame()); |
|
232 if (selectedFrame != m_webView->mainFrameImpl()->frame()) |
|
233 data.frameURL = urlFromFrame(selectedFrame); |
|
234 |
|
235 if (r.isSelected()) |
|
236 data.selectedText = selectedFrame->selectedText().stripWhiteSpace(); |
|
237 |
|
238 if (r.isContentEditable()) { |
|
239 data.isEditable = true; |
|
240 if (m_webView->focusedWebCoreFrame()->editor()->isContinuousSpellCheckingEnabled()) { |
|
241 data.isSpellCheckingEnabled = true; |
|
242 // Spellchecking might be enabled for the field, but could be disabled on the node. |
|
243 if (m_webView->focusedWebCoreFrame()->editor()->spellCheckingEnabledInFocusedNode()) |
|
244 data.misspelledWord = selectMisspelledWord(defaultMenu, selectedFrame); |
|
245 } |
|
246 } |
|
247 |
|
248 #if OS(DARWIN) |
|
249 ExceptionCode ec = 0; |
|
250 RefPtr<CSSStyleDeclaration> style = selectedFrame->document()->createCSSStyleDeclaration(); |
|
251 style->setProperty(CSSPropertyDirection, "ltr", false, ec); |
|
252 if (selectedFrame->editor()->selectionHasStyle(style.get()) != FalseTriState) |
|
253 data.writingDirectionLeftToRight |= WebContextMenuData::CheckableMenuItemChecked; |
|
254 style->setProperty(CSSPropertyDirection, "rtl", false, ec); |
|
255 if (selectedFrame->editor()->selectionHasStyle(style.get()) != FalseTriState) |
|
256 data.writingDirectionRightToLeft |= WebContextMenuData::CheckableMenuItemChecked; |
|
257 #endif // OS(DARWIN) |
|
258 |
|
259 // Now retrieve the security info. |
|
260 DocumentLoader* dl = selectedFrame->loader()->documentLoader(); |
|
261 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl); |
|
262 if (ds) |
|
263 data.securityInfo = ds->response().securityInfo(); |
|
264 |
|
265 // Filter out custom menu elements and add them into the data. |
|
266 populateCustomMenuItems(defaultMenu, &data); |
|
267 |
|
268 WebFrame* selected_web_frame = WebFrameImpl::fromFrame(selectedFrame); |
|
269 if (m_webView->client()) |
|
270 m_webView->client()->showContextMenu(selected_web_frame, data); |
|
271 |
|
272 return 0; |
|
273 } |
|
274 |
|
275 void ContextMenuClientImpl::populateCustomMenuItems(WebCore::ContextMenu* defaultMenu, WebContextMenuData* data) |
|
276 { |
|
277 Vector<WebMenuItemInfo> customItems; |
|
278 for (size_t i = 0; i < defaultMenu->itemCount(); ++i) { |
|
279 ContextMenuItem* inputItem = defaultMenu->itemAtIndex(i, defaultMenu->platformDescription()); |
|
280 if (inputItem->action() < ContextMenuItemBaseCustomTag || inputItem->action() >= ContextMenuItemBaseApplicationTag) |
|
281 continue; |
|
282 |
|
283 WebMenuItemInfo outputItem; |
|
284 outputItem.label = inputItem->title(); |
|
285 outputItem.enabled = inputItem->enabled(); |
|
286 outputItem.checked = inputItem->checked(); |
|
287 outputItem.action = static_cast<unsigned>(inputItem->action() - ContextMenuItemBaseCustomTag); |
|
288 switch (inputItem->type()) { |
|
289 case ActionType: |
|
290 outputItem.type = WebMenuItemInfo::Option; |
|
291 break; |
|
292 case CheckableActionType: |
|
293 outputItem.type = WebMenuItemInfo::CheckableOption; |
|
294 break; |
|
295 case SeparatorType: |
|
296 outputItem.type = WebMenuItemInfo::Separator; |
|
297 break; |
|
298 case SubmenuType: |
|
299 outputItem.type = WebMenuItemInfo::Group; |
|
300 break; |
|
301 } |
|
302 customItems.append(outputItem); |
|
303 } |
|
304 |
|
305 WebVector<WebMenuItemInfo> outputItems(customItems.size()); |
|
306 for (size_t i = 0; i < customItems.size(); ++i) |
|
307 outputItems[i] = customItems[i]; |
|
308 data->customItems.swap(outputItems); |
|
309 } |
|
310 |
|
311 } // namespace WebKit |