2 * Copyright (C) 2009, 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "web/ContextMenuClientImpl.h"
34 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
35 #include "core/CSSPropertyNames.h"
36 #include "core/HTMLNames.h"
37 #include "core/InputTypeNames.h"
38 #include "core/css/CSSStyleDeclaration.h"
39 #include "core/dom/Document.h"
40 #include "core/editing/Editor.h"
41 #include "core/editing/markers/DocumentMarkerController.h"
42 #include "core/editing/spellcheck/SpellChecker.h"
43 #include "core/frame/FrameHost.h"
44 #include "core/frame/FrameView.h"
45 #include "core/frame/Settings.h"
46 #include "core/frame/VisualViewport.h"
47 #include "core/html/HTMLAnchorElement.h"
48 #include "core/html/HTMLFormElement.h"
49 #include "core/html/HTMLImageElement.h"
50 #include "core/html/HTMLInputElement.h"
51 #include "core/html/HTMLMediaElement.h"
52 #include "core/html/HTMLPlugInElement.h"
53 #include "core/html/MediaError.h"
54 #include "core/input/EventHandler.h"
55 #include "core/layout/HitTestResult.h"
56 #include "core/layout/LayoutPart.h"
57 #include "core/loader/DocumentLoader.h"
58 #include "core/loader/FrameLoader.h"
59 #include "core/loader/HistoryItem.h"
60 #include "core/page/ContextMenuController.h"
61 #include "core/page/Page.h"
62 #include "platform/ContextMenu.h"
63 #include "platform/Widget.h"
64 #include "platform/exported/WrappedResourceResponse.h"
65 #include "platform/text/TextBreakIterator.h"
66 #include "platform/weborigin/KURL.h"
67 #include "public/platform/WebPoint.h"
68 #include "public/platform/WebString.h"
69 #include "public/platform/WebURL.h"
70 #include "public/platform/WebURLResponse.h"
71 #include "public/platform/WebVector.h"
72 #include "public/web/WebContextMenuData.h"
73 #include "public/web/WebFormElement.h"
74 #include "public/web/WebFrameClient.h"
75 #include "public/web/WebMenuItemInfo.h"
76 #include "public/web/WebPlugin.h"
77 #include "public/web/WebSearchableFormData.h"
78 #include "public/web/WebSpellCheckClient.h"
79 #include "public/web/WebViewClient.h"
80 #include "web/ContextMenuAllowedScope.h"
81 #include "web/WebDataSourceImpl.h"
82 #include "web/WebLocalFrameImpl.h"
83 #include "web/WebPluginContainerImpl.h"
84 #include "web/WebViewImpl.h"
85 #include "wtf/text/WTFString.h"
89 // Figure out the URL of a page or subframe. Returns |page_type| as the type,
90 // which indicates page or subframe, or ContextNodeType::NONE if the URL could not
91 // be determined for some reason.
92 static WebURL
urlFromFrame(LocalFrame
* frame
)
95 DocumentLoader
* dl
= frame
->loader().documentLoader();
97 WebDataSource
* ds
= WebDataSourceImpl::fromDocumentLoader(dl
);
99 return ds
->hasUnreachableURL() ? ds
->unreachableURL() : ds
->request().url();
105 // Helper function to determine whether text is a single word.
106 static bool isASingleWord(const String
& text
)
108 TextBreakIterator
* it
= wordBreakIterator(text
, 0, text
.length());
109 return it
&& it
->next() == static_cast<int>(text
.length());
112 // Helper function to get misspelled word on which context menu
113 // is to be invoked. This function also sets the word on which context menu
114 // has been invoked to be the selected word, as required. This function changes
115 // the selection only when there were no selected characters on OS X.
116 static String
selectMisspelledWord(LocalFrame
* selectedFrame
)
118 // First select from selectedText to check for multiple word selection.
119 String misspelledWord
= selectedFrame
->selectedText().stripWhiteSpace();
121 // If some texts were already selected, we don't change the selection.
122 if (!misspelledWord
.isEmpty()) {
123 // Don't provide suggestions for multiple words.
124 if (!isASingleWord(misspelledWord
))
126 return misspelledWord
;
129 // Selection is empty, so change the selection to the word under the cursor.
130 HitTestResult hitTestResult
= selectedFrame
->eventHandler().
131 hitTestResultAtPoint(selectedFrame
->page()->contextMenuController().hitTestResult().pointInInnerNodeFrame());
132 Node
* innerNode
= hitTestResult
.innerNode();
133 VisiblePosition pos
= createVisiblePosition(innerNode
->layoutObject()->positionForPoint(
134 hitTestResult
.localPoint()));
137 return misspelledWord
; // It is empty.
139 WebLocalFrameImpl::selectWordAroundPosition(selectedFrame
, pos
);
140 misspelledWord
= selectedFrame
->selectedText().stripWhiteSpace();
143 // If misspelled word is still empty, then that portion should not be
144 // selected. Set the selection to that position only, and do not expand.
145 if (misspelledWord
.isEmpty())
146 selectedFrame
->selection().setSelection(VisibleSelection(pos
));
148 // On non-Mac, right-click should not make a range selection in any case.
149 selectedFrame
->selection().setSelection(VisibleSelection(pos
));
151 return misspelledWord
;
154 static bool IsWhiteSpaceOrPunctuation(UChar c
)
156 return isSpaceOrNewline(c
) || WTF::Unicode::isPunct(c
);
159 static String
selectMisspellingAsync(LocalFrame
* selectedFrame
, String
& description
, uint32_t& hash
)
161 VisibleSelection selection
= selectedFrame
->selection().selection();
162 if (!selection
.isCaretOrRange())
165 // Caret and range selections always return valid normalized ranges.
166 RefPtrWillBeRawPtr
<Range
> selectionRange
= createRange(selection
.toNormalizedEphemeralRange());
167 DocumentMarkerVector markers
= selectedFrame
->document()->markers().markersInRange(EphemeralRange(selectionRange
.get()), DocumentMarker::MisspellingMarkers());
168 if (markers
.size() != 1)
170 description
= markers
[0]->description();
171 hash
= markers
[0]->hash();
173 // Cloning a range fails only for invalid ranges.
174 RefPtrWillBeRawPtr
<Range
> markerRange
= selectionRange
->cloneRange();
175 markerRange
->setStart(markerRange
->startContainer(), markers
[0]->startOffset());
176 markerRange
->setEnd(markerRange
->endContainer(), markers
[0]->endOffset());
178 if (markerRange
->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation
) != selectionRange
->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation
))
181 return markerRange
->text();
184 void ContextMenuClientImpl::showContextMenu(const ContextMenu
* defaultMenu
)
186 // Displaying the context menu in this function is a big hack as we don't
187 // have context, i.e. whether this is being invoked via a script or in
188 // response to user input (Mouse event WM_RBUTTONDOWN,
189 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked
190 // in response to the above input events before popping up the context menu.
191 if (!ContextMenuAllowedScope::isContextMenuAllowed())
194 HitTestResult r
= m_webView
->page()->contextMenuController().hitTestResult();
196 r
.setToShadowHostIfInUserAgentShadowRoot();
198 LocalFrame
* selectedFrame
= r
.innerNodeFrame();
200 WebContextMenuData data
;
201 data
.mousePosition
= selectedFrame
->view()->contentsToViewport(r
.roundedPointInInnerNodeFrame());
203 // Compute edit flags.
204 data
.editFlags
= WebContextMenuData::CanDoNone
;
205 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canUndo())
206 data
.editFlags
|= WebContextMenuData::CanUndo
;
207 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canRedo())
208 data
.editFlags
|= WebContextMenuData::CanRedo
;
209 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canCut())
210 data
.editFlags
|= WebContextMenuData::CanCut
;
211 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canCopy())
212 data
.editFlags
|= WebContextMenuData::CanCopy
;
213 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canPaste())
214 data
.editFlags
|= WebContextMenuData::CanPaste
;
215 if (toLocalFrame(m_webView
->focusedCoreFrame())->editor().canDelete())
216 data
.editFlags
|= WebContextMenuData::CanDelete
;
217 // We can always select all...
218 data
.editFlags
|= WebContextMenuData::CanSelectAll
;
219 data
.editFlags
|= WebContextMenuData::CanTranslate
;
221 // Links, Images, Media tags, and Image/Media-Links take preference over
223 data
.linkURL
= r
.absoluteLinkURL();
225 if (r
.innerNode()->isHTMLElement()) {
226 HTMLElement
* htmlElement
= toHTMLElement(r
.innerNode());
227 if (!htmlElement
->title().isEmpty()) {
228 data
.titleText
= htmlElement
->title();
230 data
.titleText
= htmlElement
->altText();
234 if (isHTMLCanvasElement(r
.innerNode())) {
235 data
.mediaType
= WebContextMenuData::MediaTypeCanvas
;
236 data
.hasImageContents
= true;
237 } else if (!r
.absoluteImageURL().isEmpty()) {
238 data
.srcURL
= r
.absoluteImageURL();
239 data
.mediaType
= WebContextMenuData::MediaTypeImage
;
240 data
.mediaFlags
|= WebContextMenuData::MediaCanPrint
;
242 // An image can be null for many reasons, like being blocked, no image
243 // data received from server yet.
244 data
.hasImageContents
= r
.image() && !r
.image()->isNull();
245 if (data
.hasImageContents
&& isHTMLImageElement(r
.innerNodeOrImageMapImage())) {
246 HTMLImageElement
* imageElement
= toHTMLImageElement(r
.innerNodeOrImageMapImage());
247 if (imageElement
&& imageElement
->cachedImage())
248 data
.imageResponse
= WrappedResourceResponse(imageElement
->cachedImage()->response());
250 } else if (!r
.absoluteMediaURL().isEmpty()) {
251 data
.srcURL
= r
.absoluteMediaURL();
253 // We know that if absoluteMediaURL() is not empty, then this
254 // is a media element.
255 HTMLMediaElement
* mediaElement
= toHTMLMediaElement(r
.innerNode());
256 if (isHTMLVideoElement(*mediaElement
))
257 data
.mediaType
= WebContextMenuData::MediaTypeVideo
;
258 else if (isHTMLAudioElement(*mediaElement
))
259 data
.mediaType
= WebContextMenuData::MediaTypeAudio
;
261 if (mediaElement
->error())
262 data
.mediaFlags
|= WebContextMenuData::MediaInError
;
263 if (mediaElement
->paused())
264 data
.mediaFlags
|= WebContextMenuData::MediaPaused
;
265 if (mediaElement
->muted())
266 data
.mediaFlags
|= WebContextMenuData::MediaMuted
;
267 if (mediaElement
->loop())
268 data
.mediaFlags
|= WebContextMenuData::MediaLoop
;
269 if (mediaElement
->supportsSave())
270 data
.mediaFlags
|= WebContextMenuData::MediaCanSave
;
271 if (mediaElement
->hasAudio())
272 data
.mediaFlags
|= WebContextMenuData::MediaHasAudio
;
273 // Media controls can be toggled only for video player. If we toggle
274 // controls for audio then the player disappears, and there is no way to
275 // return it back. Don't set this bit for fullscreen video, since
276 // toggling is ignored in that case.
277 if (mediaElement
->hasVideo() && !mediaElement
->isFullscreen())
278 data
.mediaFlags
|= WebContextMenuData::MediaCanToggleControls
;
279 if (mediaElement
->shouldShowControls())
280 data
.mediaFlags
|= WebContextMenuData::MediaControls
;
281 } else if (isHTMLObjectElement(*r
.innerNode()) || isHTMLEmbedElement(*r
.innerNode())) {
282 LayoutObject
* object
= r
.innerNode()->layoutObject();
283 if (object
&& object
->isLayoutPart()) {
284 Widget
* widget
= toLayoutPart(object
)->widget();
285 if (widget
&& widget
->isPluginContainer()) {
286 data
.mediaType
= WebContextMenuData::MediaTypePlugin
;
287 WebPluginContainerImpl
* plugin
= toWebPluginContainerImpl(widget
);
288 WebString text
= plugin
->plugin()->selectionAsText();
289 if (!text
.isEmpty()) {
290 data
.selectedText
= text
;
291 data
.editFlags
|= WebContextMenuData::CanCopy
;
293 data
.editFlags
&= ~WebContextMenuData::CanTranslate
;
294 data
.linkURL
= plugin
->plugin()->linkAtPosition(data
.mousePosition
);
295 if (plugin
->plugin()->supportsPaginatedPrint())
296 data
.mediaFlags
|= WebContextMenuData::MediaCanPrint
;
298 HTMLPlugInElement
* pluginElement
= toHTMLPlugInElement(r
.innerNode());
299 data
.srcURL
= pluginElement
->document().completeURL(pluginElement
->url());
300 data
.mediaFlags
|= WebContextMenuData::MediaCanSave
;
302 // Add context menu commands that are supported by the plugin.
303 if (plugin
->plugin()->canRotateView())
304 data
.mediaFlags
|= WebContextMenuData::MediaCanRotate
;
309 // If it's not a link, an image, a media element, or an image/media link,
310 // show a selection menu or a more generic page menu.
311 if (selectedFrame
->document()->loader())
312 data
.frameEncoding
= selectedFrame
->document()->encodingName();
314 // Send the frame and page URLs in any case.
315 if (selectedFrame
!= m_webView
->page()->mainFrame()) {
316 data
.frameURL
= urlFromFrame(selectedFrame
);
317 RefPtrWillBeRawPtr
<HistoryItem
> historyItem
= selectedFrame
->loader().currentItem();
319 data
.frameHistoryItem
= WebHistoryItem(historyItem
);
321 data
.pageURL
= urlFromFrame(m_webView
->mainFrameImpl()->frame());
324 if (r
.isSelected()) {
325 if (!isHTMLInputElement(*r
.innerNode()) || toHTMLInputElement(r
.innerNode())->type() != InputTypeNames::password
)
326 data
.selectedText
= selectedFrame
->selectedText().stripWhiteSpace();
329 if (r
.isContentEditable()) {
330 data
.isEditable
= true;
332 // When Chrome enables asynchronous spellchecking, its spellchecker adds spelling markers to misspelled
333 // words and attaches suggestions to these markers in the background. Therefore, when a user right-clicks
334 // a mouse on a word, Chrome just needs to find a spelling marker on the word instead of spellchecking it.
335 if (selectedFrame
->settings() && selectedFrame
->settings()->asynchronousSpellCheckingEnabled()) {
338 data
.misspelledWord
= selectMisspellingAsync(selectedFrame
, description
, hash
);
339 data
.misspellingHash
= hash
;
340 if (description
.length()) {
341 Vector
<String
> suggestions
;
342 description
.split('\n', suggestions
);
343 data
.dictionarySuggestions
= suggestions
;
344 } else if (m_webView
->spellCheckClient()) {
345 int misspelledOffset
, misspelledLength
;
346 m_webView
->spellCheckClient()->spellCheck(data
.misspelledWord
, misspelledOffset
, misspelledLength
, &data
.dictionarySuggestions
);
349 data
.isSpellCheckingEnabled
=
350 toLocalFrame(m_webView
->focusedCoreFrame())->spellChecker().isContinuousSpellCheckingEnabled();
351 // Spellchecking might be enabled for the field, but could be disabled on the node.
352 if (toLocalFrame(m_webView
->focusedCoreFrame())->spellChecker().isSpellCheckingEnabledInFocusedNode()) {
353 data
.misspelledWord
= selectMisspelledWord(selectedFrame
);
354 if (m_webView
->spellCheckClient()) {
355 int misspelledOffset
, misspelledLength
;
356 m_webView
->spellCheckClient()->spellCheck(
357 data
.misspelledWord
, misspelledOffset
, misspelledLength
,
358 &data
.dictionarySuggestions
);
359 if (!misspelledLength
)
360 data
.misspelledWord
.reset();
364 HTMLFormElement
* form
= selectedFrame
->selection().currentForm();
365 if (form
&& isHTMLInputElement(*r
.innerNode())) {
366 HTMLInputElement
& selectedElement
= toHTMLInputElement(*r
.innerNode());
367 WebSearchableFormData ws
= WebSearchableFormData(WebFormElement(form
), WebInputElement(&selectedElement
));
368 if (ws
.url().isValid())
369 data
.keywordURL
= ws
.url();
373 if (selectedFrame
->editor().selectionHasStyle(CSSPropertyDirection
, "ltr") != FalseTriState
)
374 data
.writingDirectionLeftToRight
|= WebContextMenuData::CheckableMenuItemChecked
;
375 if (selectedFrame
->editor().selectionHasStyle(CSSPropertyDirection
, "rtl") != FalseTriState
)
376 data
.writingDirectionRightToLeft
|= WebContextMenuData::CheckableMenuItemChecked
;
378 // Now retrieve the security info.
379 DocumentLoader
* dl
= selectedFrame
->loader().documentLoader();
380 WebDataSource
* ds
= WebDataSourceImpl::fromDocumentLoader(dl
);
382 data
.securityInfo
= ds
->response().securityInfo();
384 data
.referrerPolicy
= static_cast<WebReferrerPolicy
>(selectedFrame
->document()->referrerPolicy());
386 // Filter out custom menu elements and add them into the data.
387 populateCustomMenuItems(defaultMenu
, &data
);
389 if (isHTMLAnchorElement(r
.URLElement())) {
390 HTMLAnchorElement
* anchor
= toHTMLAnchorElement(r
.URLElement());
392 // Extract suggested filename for saving file.
393 data
.suggestedFilename
= anchor
->fastGetAttribute(HTMLNames::downloadAttr
);
395 // If the anchor wants to suppress the referrer, update the referrerPolicy accordingly.
396 if (anchor
->hasRel(RelationNoReferrer
))
397 data
.referrerPolicy
= WebReferrerPolicyNever
;
399 data
.linkText
= anchor
->innerText();
402 // Find the input field type.
403 if (isHTMLInputElement(r
.innerNode())) {
404 HTMLInputElement
* element
= toHTMLInputElement(r
.innerNode());
405 if (element
->type() == InputTypeNames::password
)
406 data
.inputFieldType
= WebContextMenuData::InputFieldTypePassword
;
407 else if (element
->isTextField())
408 data
.inputFieldType
= WebContextMenuData::InputFieldTypePlainText
;
410 data
.inputFieldType
= WebContextMenuData::InputFieldTypeOther
;
412 data
.inputFieldType
= WebContextMenuData::InputFieldTypeNone
;
415 data
.node
= r
.innerNodeOrImageMapImage();
417 WebLocalFrameImpl
* selectedWebFrame
= WebLocalFrameImpl::fromFrame(selectedFrame
);
418 if (selectedWebFrame
->client())
419 selectedWebFrame
->client()->showContextMenu(data
);
422 void ContextMenuClientImpl::clearContextMenu()
424 HitTestResult r
= m_webView
->page()->contextMenuController().hitTestResult();
425 LocalFrame
* selectedFrame
= r
.innerNodeFrame();
429 WebLocalFrameImpl
* selectedWebFrame
= WebLocalFrameImpl::fromFrame(selectedFrame
);
430 if (selectedWebFrame
->client())
431 selectedWebFrame
->client()->clearContextMenu();
434 static void populateSubMenuItems(const Vector
<ContextMenuItem
>& inputMenu
, WebVector
<WebMenuItemInfo
>& subMenuItems
)
436 Vector
<WebMenuItemInfo
> subItems
;
437 for (size_t i
= 0; i
< inputMenu
.size(); ++i
) {
438 const ContextMenuItem
* inputItem
= &inputMenu
.at(i
);
439 if (inputItem
->action() < ContextMenuItemBaseCustomTag
|| inputItem
->action() > ContextMenuItemLastCustomTag
)
442 WebMenuItemInfo outputItem
;
443 outputItem
.label
= inputItem
->title();
444 outputItem
.icon
= inputItem
->icon();
445 outputItem
.enabled
= inputItem
->enabled();
446 outputItem
.checked
= inputItem
->checked();
447 outputItem
.action
= static_cast<unsigned>(inputItem
->action() - ContextMenuItemBaseCustomTag
);
448 switch (inputItem
->type()) {
450 outputItem
.type
= WebMenuItemInfo::Option
;
452 case CheckableActionType
:
453 outputItem
.type
= WebMenuItemInfo::CheckableOption
;
456 outputItem
.type
= WebMenuItemInfo::Separator
;
459 outputItem
.type
= WebMenuItemInfo::SubMenu
;
460 populateSubMenuItems(inputItem
->subMenuItems(), outputItem
.subMenuItems
);
463 subItems
.append(outputItem
);
466 WebVector
<WebMenuItemInfo
> outputItems(subItems
.size());
467 for (size_t i
= 0; i
< subItems
.size(); ++i
)
468 outputItems
[i
] = subItems
[i
];
469 subMenuItems
.swap(outputItems
);
472 void ContextMenuClientImpl::populateCustomMenuItems(const ContextMenu
* defaultMenu
, WebContextMenuData
* data
)
474 populateSubMenuItems(defaultMenu
->items(), data
->customItems
);