| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2009, 2012 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 "web/ContextMenuClientImpl.h" | |
| 33 | |
| 34 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
| 35 #include "core/CSSPropertyNames.h" | |
| 36 #include "core/HTMLNames.h" | |
| 37 #include "core/css/CSSStyleDeclaration.h" | |
| 38 #include "core/dom/Document.h" | |
| 39 #include "core/dom/DocumentMarkerController.h" | |
| 40 #include "core/editing/Editor.h" | |
| 41 #include "core/editing/SpellChecker.h" | |
| 42 #include "core/frame/FrameHost.h" | |
| 43 #include "core/frame/FrameView.h" | |
| 44 #include "core/frame/PinchViewport.h" | |
| 45 #include "core/frame/Settings.h" | |
| 46 #include "core/html/HTMLAnchorElement.h" | |
| 47 #include "core/html/HTMLMediaElement.h" | |
| 48 #include "core/html/MediaError.h" | |
| 49 #include "core/page/ContextMenuController.h" | |
| 50 #include "core/page/EventHandler.h" | |
| 51 #include "core/page/Page.h" | |
| 52 #include "core/rendering/HitTestResult.h" | |
| 53 #include "core/rendering/RenderWidget.h" | |
| 54 #include "platform/ContextMenu.h" | |
| 55 #include "platform/Widget.h" | |
| 56 #include "platform/text/TextBreakIterator.h" | |
| 57 #include "platform/weborigin/KURL.h" | |
| 58 #include "public/platform/WebPoint.h" | |
| 59 #include "public/platform/WebString.h" | |
| 60 #include "public/platform/WebURL.h" | |
| 61 #include "public/platform/WebURLResponse.h" | |
| 62 #include "public/platform/WebVector.h" | |
| 63 #include "public/web/WebContextMenuData.h" | |
| 64 #include "public/web/WebFrameClient.h" | |
| 65 #include "public/web/WebMenuItemInfo.h" | |
| 66 #include "public/web/WebSpellCheckClient.h" | |
| 67 #include "public/web/WebViewClient.h" | |
| 68 #include "web/WebLocalFrameImpl.h" | |
| 69 #include "web/WebViewImpl.h" | |
| 70 #include "wtf/text/WTFString.h" | |
| 71 | |
| 72 namespace blink { | |
| 73 | |
| 74 // Figure out the URL of a page or subframe. Returns |page_type| as the type, | |
| 75 // which indicates page or subframe, or ContextNodeType::NONE if the URL could n
ot | |
| 76 // be determined for some reason. | |
| 77 static WebURL urlFromFrame(LocalFrame* frame) | |
| 78 { | |
| 79 // FIXME(sky): This used to reach through the DocumentLoader but that made n
o sense. | |
| 80 if (frame) | |
| 81 return frame->document()->url(); | |
| 82 return WebURL(); | |
| 83 } | |
| 84 | |
| 85 // Helper function to determine whether text is a single word. | |
| 86 static bool isASingleWord(const String& text) | |
| 87 { | |
| 88 TextBreakIterator* it = wordBreakIterator(text, 0, text.length()); | |
| 89 return it && it->next() == static_cast<int>(text.length()); | |
| 90 } | |
| 91 | |
| 92 // Helper function to get misspelled word on which context menu | |
| 93 // is to be invoked. This function also sets the word on which context menu | |
| 94 // has been invoked to be the selected word, as required. This function changes | |
| 95 // the selection only when there were no selected characters on OS X. | |
| 96 static String selectMisspelledWord(LocalFrame* selectedFrame) | |
| 97 { | |
| 98 // First select from selectedText to check for multiple word selection. | |
| 99 String misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); | |
| 100 | |
| 101 // If some texts were already selected, we don't change the selection. | |
| 102 if (!misspelledWord.isEmpty()) { | |
| 103 // Don't provide suggestions for multiple words. | |
| 104 if (!isASingleWord(misspelledWord)) | |
| 105 return String(); | |
| 106 return misspelledWord; | |
| 107 } | |
| 108 | |
| 109 // Selection is empty, so change the selection to the word under the cursor. | |
| 110 HitTestResult hitTestResult = selectedFrame->eventHandler(). | |
| 111 hitTestResultAtPoint(selectedFrame->page()->contextMenuController().hitT
estResult().pointInInnerNodeFrame()); | |
| 112 Node* innerNode = hitTestResult.innerNode(); | |
| 113 VisiblePosition pos(innerNode->renderer()->positionForPoint( | |
| 114 hitTestResult.localPoint())); | |
| 115 | |
| 116 if (pos.isNull()) | |
| 117 return misspelledWord; // It is empty. | |
| 118 | |
| 119 WebLocalFrameImpl::selectWordAroundPosition(selectedFrame, pos); | |
| 120 misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); | |
| 121 | |
| 122 #if OS(MACOSX) | |
| 123 // If misspelled word is still empty, then that portion should not be | |
| 124 // selected. Set the selection to that position only, and do not expand. | |
| 125 if (misspelledWord.isEmpty()) | |
| 126 selectedFrame->selection().setSelection(VisibleSelection(pos)); | |
| 127 #else | |
| 128 // On non-Mac, right-click should not make a range selection in any case. | |
| 129 selectedFrame->selection().setSelection(VisibleSelection(pos)); | |
| 130 #endif | |
| 131 return misspelledWord; | |
| 132 } | |
| 133 | |
| 134 static bool IsWhiteSpaceOrPunctuation(UChar c) | |
| 135 { | |
| 136 return isSpaceOrNewline(c) || WTF::Unicode::isPunct(c); | |
| 137 } | |
| 138 | |
| 139 static String selectMisspellingAsync(LocalFrame* selectedFrame, String& descript
ion, uint32_t& hash) | |
| 140 { | |
| 141 VisibleSelection selection = selectedFrame->selection().selection(); | |
| 142 if (!selection.isCaretOrRange()) | |
| 143 return String(); | |
| 144 | |
| 145 // Caret and range selections always return valid normalized ranges. | |
| 146 RefPtrWillBeRawPtr<Range> selectionRange = selection.toNormalizedRange(); | |
| 147 DocumentMarkerVector markers = selectedFrame->document()->markers().markersI
nRange(selectionRange.get(), DocumentMarker::MisspellingMarkers()); | |
| 148 if (markers.size() != 1) | |
| 149 return String(); | |
| 150 description = markers[0]->description(); | |
| 151 hash = markers[0]->hash(); | |
| 152 | |
| 153 // Cloning a range fails only for invalid ranges. | |
| 154 RefPtrWillBeRawPtr<Range> markerRange = selectionRange->cloneRange(); | |
| 155 markerRange->setStart(markerRange->startContainer(), markers[0]->startOffset
()); | |
| 156 markerRange->setEnd(markerRange->endContainer(), markers[0]->endOffset()); | |
| 157 | |
| 158 if (markerRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation) != selec
tionRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation)) | |
| 159 return String(); | |
| 160 | |
| 161 return markerRange->text(); | |
| 162 } | |
| 163 | |
| 164 void ContextMenuClientImpl::showContextMenu(const ContextMenu* defaultMenu) | |
| 165 { | |
| 166 // Displaying the context menu in this function is a big hack as we don't | |
| 167 // have context, i.e. whether this is being invoked via a script or in | |
| 168 // response to user input (Mouse event WM_RBUTTONDOWN, | |
| 169 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked | |
| 170 // in response to the above input events before popping up the context menu. | |
| 171 if (!m_webView->contextMenuAllowed()) | |
| 172 return; | |
| 173 | |
| 174 HitTestResult r = m_webView->page()->contextMenuController().hitTestResult()
; | |
| 175 | |
| 176 LocalFrame* selectedFrame = r.innerNodeFrame(); | |
| 177 | |
| 178 WebContextMenuData data; | |
| 179 IntPoint mousePoint = selectedFrame->view()->contentsToWindow(r.roundedPoint
InInnerNodeFrame()); | |
| 180 | |
| 181 // FIXME(bokan): crbug.com/371902 - We shouldn't be making these scale | |
| 182 // related coordinate transformatios in an ad hoc way. | |
| 183 PinchViewport& pinchViewport = selectedFrame->host()->pinchViewport(); | |
| 184 mousePoint -= flooredIntSize(pinchViewport.visibleRect().location()); | |
| 185 data.mousePosition = mousePoint; | |
| 186 | |
| 187 // Compute edit flags. | |
| 188 data.editFlags = WebContextMenuData::CanDoNone; | |
| 189 Editor& editor = m_webView->focusedCoreFrame()->editor(); | |
| 190 if (editor.canUndo()) | |
| 191 data.editFlags |= WebContextMenuData::CanUndo; | |
| 192 if (editor.canRedo()) | |
| 193 data.editFlags |= WebContextMenuData::CanRedo; | |
| 194 if (editor.canCut()) | |
| 195 data.editFlags |= WebContextMenuData::CanCut; | |
| 196 if (editor.canCopy()) | |
| 197 data.editFlags |= WebContextMenuData::CanCopy; | |
| 198 if (editor.canPaste()) | |
| 199 data.editFlags |= WebContextMenuData::CanPaste; | |
| 200 if (editor.canDelete()) | |
| 201 data.editFlags |= WebContextMenuData::CanDelete; | |
| 202 data.editFlags |= WebContextMenuData::CanTranslate; | |
| 203 | |
| 204 // Links, Images, Media tags, and Image/Media-Links take preference over | |
| 205 // all else. | |
| 206 data.linkURL = r.absoluteLinkURL(); | |
| 207 | |
| 208 if (isHTMLCanvasElement(r.innerNonSharedNode())) { | |
| 209 data.mediaType = WebContextMenuData::MediaTypeCanvas; | |
| 210 data.hasImageContents = true; | |
| 211 } else if (!r.absoluteImageURL().isEmpty()) { | |
| 212 data.srcURL = r.absoluteImageURL(); | |
| 213 data.mediaType = WebContextMenuData::MediaTypeImage; | |
| 214 data.mediaFlags |= WebContextMenuData::MediaCanPrint; | |
| 215 | |
| 216 // An image can be null for many reasons, like being blocked, no image | |
| 217 // data received from server yet. | |
| 218 data.hasImageContents = r.image() && !r.image()->isNull(); | |
| 219 } else if (!r.absoluteMediaURL().isEmpty()) { | |
| 220 data.srcURL = r.absoluteMediaURL(); | |
| 221 | |
| 222 // We know that if absoluteMediaURL() is not empty, then this | |
| 223 // is a media element. | |
| 224 HTMLMediaElement* mediaElement = toHTMLMediaElement(r.innerNonSharedNode
()); | |
| 225 if (isHTMLVideoElement(*mediaElement)) | |
| 226 data.mediaType = WebContextMenuData::MediaTypeVideo; | |
| 227 else if (isHTMLAudioElement(*mediaElement)) | |
| 228 data.mediaType = WebContextMenuData::MediaTypeAudio; | |
| 229 | |
| 230 if (mediaElement->error()) | |
| 231 data.mediaFlags |= WebContextMenuData::MediaInError; | |
| 232 if (mediaElement->paused()) | |
| 233 data.mediaFlags |= WebContextMenuData::MediaPaused; | |
| 234 if (mediaElement->muted()) | |
| 235 data.mediaFlags |= WebContextMenuData::MediaMuted; | |
| 236 if (mediaElement->loop()) | |
| 237 data.mediaFlags |= WebContextMenuData::MediaLoop; | |
| 238 if (mediaElement->supportsSave()) | |
| 239 data.mediaFlags |= WebContextMenuData::MediaCanSave; | |
| 240 if (mediaElement->hasAudio()) | |
| 241 data.mediaFlags |= WebContextMenuData::MediaHasAudio; | |
| 242 // Media controls can be toggled only for video player. If we toggle | |
| 243 // controls for audio then the player disappears, and there is no way to | |
| 244 // return it back. Don't set this bit for fullscreen video, since | |
| 245 // toggling is ignored in that case. | |
| 246 if (mediaElement->hasVideo() && !mediaElement->isFullscreen()) | |
| 247 data.mediaFlags |= WebContextMenuData::MediaCanToggleControls; | |
| 248 } | |
| 249 | |
| 250 // Send the frame and page URLs in any case. | |
| 251 data.pageURL = urlFromFrame(m_webView->mainFrameImpl()->frame()); | |
| 252 if (selectedFrame != m_webView->mainFrameImpl()->frame()) { | |
| 253 data.frameURL = urlFromFrame(selectedFrame); | |
| 254 } | |
| 255 | |
| 256 if (r.isSelected()) | |
| 257 data.selectedText = selectedFrame->selectedText().stripWhiteSpace(); | |
| 258 | |
| 259 if (r.isContentEditable()) { | |
| 260 data.isEditable = true; | |
| 261 | |
| 262 // When Chrome enables asynchronous spellchecking, its spellchecker adds
spelling markers to misspelled | |
| 263 // words and attaches suggestions to these markers in the background. Th
erefore, when a user right-clicks | |
| 264 // a mouse on a word, Chrome just needs to find a spelling marker on the
word instead of spellchecking it. | |
| 265 if (selectedFrame->settings() && selectedFrame->settings()->asynchronous
SpellCheckingEnabled()) { | |
| 266 String description; | |
| 267 uint32_t hash = 0; | |
| 268 data.misspelledWord = selectMisspellingAsync(selectedFrame, descript
ion, hash); | |
| 269 data.misspellingHash = hash; | |
| 270 if (description.length()) { | |
| 271 Vector<String> suggestions; | |
| 272 description.split('\n', suggestions); | |
| 273 data.dictionarySuggestions = suggestions; | |
| 274 } else if (m_webView->spellCheckClient()) { | |
| 275 int misspelledOffset, misspelledLength; | |
| 276 m_webView->spellCheckClient()->spellCheck(data.misspelledWord, m
isspelledOffset, misspelledLength, &data.dictionarySuggestions); | |
| 277 } | |
| 278 } else { | |
| 279 SpellChecker& spellChecker = m_webView->focusedCoreFrame()->spellChe
cker(); | |
| 280 data.isSpellCheckingEnabled = spellChecker.isContinuousSpellChecking
Enabled(); | |
| 281 // Spellchecking might be enabled for the field, but could be disabl
ed on the node. | |
| 282 if (spellChecker.isSpellCheckingEnabledInFocusedNode()) { | |
| 283 data.misspelledWord = selectMisspelledWord(selectedFrame); | |
| 284 if (m_webView->spellCheckClient()) { | |
| 285 int misspelledOffset, misspelledLength; | |
| 286 m_webView->spellCheckClient()->spellCheck( | |
| 287 data.misspelledWord, misspelledOffset, misspelledLength, | |
| 288 &data.dictionarySuggestions); | |
| 289 if (!misspelledLength) | |
| 290 data.misspelledWord.reset(); | |
| 291 } | |
| 292 } | |
| 293 } | |
| 294 } | |
| 295 | |
| 296 if (selectedFrame->editor().selectionHasStyle(CSSPropertyDirection, "ltr") !
= FalseTriState) | |
| 297 data.writingDirectionLeftToRight |= WebContextMenuData::CheckableMenuIte
mChecked; | |
| 298 if (selectedFrame->editor().selectionHasStyle(CSSPropertyDirection, "rtl") !
= FalseTriState) | |
| 299 data.writingDirectionRightToLeft |= WebContextMenuData::CheckableMenuIte
mChecked; | |
| 300 | |
| 301 data.referrerPolicy = static_cast<WebReferrerPolicy>(selectedFrame->document
()->referrerPolicy()); | |
| 302 | |
| 303 // Filter out custom menu elements and add them into the data. | |
| 304 populateCustomMenuItems(defaultMenu, &data); | |
| 305 | |
| 306 // Extract suggested filename for saving file. | |
| 307 if (isHTMLAnchorElement(r.URLElement())) { | |
| 308 HTMLAnchorElement* anchor = toHTMLAnchorElement(r.URLElement()); | |
| 309 data.suggestedFilename = anchor->getAttribute(HTMLNames::downloadAttr); | |
| 310 } | |
| 311 | |
| 312 data.node = r.innerNonSharedNode(); | |
| 313 | |
| 314 WebLocalFrameImpl* selectedWebFrame = WebLocalFrameImpl::fromFrame(selectedF
rame); | |
| 315 if (selectedWebFrame->client()) | |
| 316 selectedWebFrame->client()->showContextMenu(data); | |
| 317 } | |
| 318 | |
| 319 void ContextMenuClientImpl::clearContextMenu() | |
| 320 { | |
| 321 HitTestResult r = m_webView->page()->contextMenuController().hitTestResult()
; | |
| 322 LocalFrame* selectedFrame = r.innerNodeFrame(); | |
| 323 if (!selectedFrame) | |
| 324 return; | |
| 325 | |
| 326 WebLocalFrameImpl* selectedWebFrame = WebLocalFrameImpl::fromFrame(selectedF
rame); | |
| 327 if (selectedWebFrame->client()) | |
| 328 selectedWebFrame->client()->clearContextMenu(); | |
| 329 } | |
| 330 | |
| 331 static void populateSubMenuItems(const Vector<ContextMenuItem>& inputMenu, WebVe
ctor<WebMenuItemInfo>& subMenuItems) | |
| 332 { | |
| 333 Vector<WebMenuItemInfo> subItems; | |
| 334 for (size_t i = 0; i < inputMenu.size(); ++i) { | |
| 335 const ContextMenuItem* inputItem = &inputMenu.at(i); | |
| 336 if (inputItem->action() < ContextMenuItemBaseCustomTag || inputItem->act
ion() > ContextMenuItemLastCustomTag) | |
| 337 continue; | |
| 338 | |
| 339 WebMenuItemInfo outputItem; | |
| 340 outputItem.label = inputItem->title(); | |
| 341 outputItem.enabled = inputItem->enabled(); | |
| 342 outputItem.checked = inputItem->checked(); | |
| 343 outputItem.action = static_cast<unsigned>(inputItem->action() - ContextM
enuItemBaseCustomTag); | |
| 344 switch (inputItem->type()) { | |
| 345 case ActionType: | |
| 346 outputItem.type = WebMenuItemInfo::Option; | |
| 347 break; | |
| 348 case CheckableActionType: | |
| 349 outputItem.type = WebMenuItemInfo::CheckableOption; | |
| 350 break; | |
| 351 case SeparatorType: | |
| 352 outputItem.type = WebMenuItemInfo::Separator; | |
| 353 break; | |
| 354 case SubmenuType: | |
| 355 outputItem.type = WebMenuItemInfo::SubMenu; | |
| 356 populateSubMenuItems(inputItem->subMenuItems(), outputItem.subMenuIt
ems); | |
| 357 break; | |
| 358 } | |
| 359 subItems.append(outputItem); | |
| 360 } | |
| 361 | |
| 362 WebVector<WebMenuItemInfo> outputItems(subItems.size()); | |
| 363 for (size_t i = 0; i < subItems.size(); ++i) | |
| 364 outputItems[i] = subItems[i]; | |
| 365 subMenuItems.swap(outputItems); | |
| 366 } | |
| 367 | |
| 368 void ContextMenuClientImpl::populateCustomMenuItems(const ContextMenu* defaultMe
nu, WebContextMenuData* data) | |
| 369 { | |
| 370 populateSubMenuItems(defaultMenu->items(), data->customItems); | |
| 371 } | |
| 372 | |
| 373 } // namespace blink | |
| OLD | NEW |