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/InputTypeNames.h" 38 #include "core/css/CSSStyleDeclaration.h" 39 #include "core/dom/Document.h" 40 #include "core/dom/DocumentMarkerController.h" 41 #include "core/editing/Editor.h" 42 #include "core/editing/SpellChecker.h" 43 #include "core/frame/FrameHost.h" 44 #include "core/frame/FrameView.h" 45 #include "core/frame/PinchViewport.h" 46 #include "core/frame/Settings.h" 47 #include "core/html/HTMLAnchorElement.h" 48 #include "core/html/HTMLFormElement.h" 49 #include "core/html/HTMLInputElement.h" 50 #include "core/html/HTMLMediaElement.h" 51 #include "core/html/HTMLPlugInElement.h" 52 #include "core/html/MediaError.h" 53 #include "core/loader/DocumentLoader.h" 54 #include "core/loader/FrameLoader.h" 55 #include "core/loader/HistoryItem.h" 56 #include "core/page/ContextMenuController.h" 57 #include "core/page/EventHandler.h" 58 #include "core/page/Page.h" 59 #include "core/rendering/HitTestResult.h" 60 #include "core/rendering/RenderWidget.h" 61 #include "platform/ContextMenu.h" 62 #include "platform/Widget.h" 63 #include "platform/text/TextBreakIterator.h" 64 #include "platform/weborigin/KURL.h" 65 #include "public/platform/WebPoint.h" 66 #include "public/platform/WebString.h" 67 #include "public/platform/WebURL.h" 68 #include "public/platform/WebURLResponse.h" 69 #include "public/platform/WebVector.h" 70 #include "public/web/WebContextMenuData.h" 71 #include "public/web/WebFormElement.h" 72 #include "public/web/WebFrameClient.h" 73 #include "public/web/WebMenuItemInfo.h" 74 #include "public/web/WebPlugin.h" 75 #include "public/web/WebSearchableFormData.h" 76 #include "public/web/WebSpellCheckClient.h" 77 #include "public/web/WebViewClient.h" 78 #include "web/WebDataSourceImpl.h" 79 #include "web/WebLocalFrameImpl.h" 80 #include "web/WebPluginContainerImpl.h" 81 #include "web/WebViewImpl.h" 82 #include "wtf/text/WTFString.h" 83 84 namespace blink { 85 86 // Figure out the URL of a page or subframe. Returns |page_type| as the type, 87 // which indicates page or subframe, or ContextNodeType::NONE if the URL could not 88 // be determined for some reason. 89 static WebURL urlFromFrame(LocalFrame* frame) 90 { 91 if (frame) { 92 DocumentLoader* dl = frame->loader().documentLoader(); 93 if (dl) { 94 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl); 95 if (ds) 96 return ds->hasUnreachableURL() ? ds->unreachableURL() : ds->request().url(); 97 } 98 } 99 return WebURL(); 100 } 101 102 // Helper function to determine whether text is a single word. 103 static bool isASingleWord(const String& text) 104 { 105 TextBreakIterator* it = wordBreakIterator(text, 0, text.length()); 106 return it && it->next() == static_cast<int>(text.length()); 107 } 108 109 // Helper function to get misspelled word on which context menu 110 // is to be invoked. This function also sets the word on which context menu 111 // has been invoked to be the selected word, as required. This function changes 112 // the selection only when there were no selected characters on OS X. 113 static String selectMisspelledWord(LocalFrame* selectedFrame) 114 { 115 // First select from selectedText to check for multiple word selection. 116 String misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); 117 118 // If some texts were already selected, we don't change the selection. 119 if (!misspelledWord.isEmpty()) { 120 // Don't provide suggestions for multiple words. 121 if (!isASingleWord(misspelledWord)) 122 return String(); 123 return misspelledWord; 124 } 125 126 // Selection is empty, so change the selection to the word under the cursor. 127 HitTestResult hitTestResult = selectedFrame->eventHandler(). 128 hitTestResultAtPoint(selectedFrame->page()->contextMenuController().hitTestResult().pointInInnerNodeFrame()); 129 Node* innerNode = hitTestResult.innerNode(); 130 VisiblePosition pos(innerNode->renderer()->positionForPoint( 131 hitTestResult.localPoint())); 132 133 if (pos.isNull()) 134 return misspelledWord; // It is empty. 135 136 WebLocalFrameImpl::selectWordAroundPosition(selectedFrame, pos); 137 misspelledWord = selectedFrame->selectedText().stripWhiteSpace(); 138 139 #if OS(MACOSX) 140 // If misspelled word is still empty, then that portion should not be 141 // selected. Set the selection to that position only, and do not expand. 142 if (misspelledWord.isEmpty()) 143 selectedFrame->selection().setSelection(VisibleSelection(pos)); 144 #else 145 // On non-Mac, right-click should not make a range selection in any case. 146 selectedFrame->selection().setSelection(VisibleSelection(pos)); 147 #endif 148 return misspelledWord; 149 } 150 151 static bool IsWhiteSpaceOrPunctuation(UChar c) 152 { 153 return isSpaceOrNewline(c) || WTF::Unicode::isPunct(c); 154 } 155 156 static String selectMisspellingAsync(LocalFrame* selectedFrame, String& description, uint32_t& hash) 157 { 158 VisibleSelection selection = selectedFrame->selection().selection(); 159 if (!selection.isCaretOrRange()) 160 return String(); 161 162 // Caret and range selections always return valid normalized ranges. 163 RefPtrWillBeRawPtr<Range> selectionRange = selection.toNormalizedRange(); 164 DocumentMarkerVector markers = selectedFrame->document()->markers().markersInRange(selectionRange.get(), DocumentMarker::MisspellingMarkers()); 165 if (markers.size() != 1) 166 return String(); 167 description = markers[0]->description(); 168 hash = markers[0]->hash(); 169 170 // Cloning a range fails only for invalid ranges. 171 RefPtrWillBeRawPtr<Range> markerRange = selectionRange->cloneRange(); 172 markerRange->setStart(markerRange->startContainer(), markers[0]->startOffset()); 173 markerRange->setEnd(markerRange->endContainer(), markers[0]->endOffset()); 174 175 if (markerRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation) != selectionRange->text().stripWhiteSpace(&IsWhiteSpaceOrPunctuation)) 176 return String(); 177 178 return markerRange->text(); 179 } 180 181 void ContextMenuClientImpl::showContextMenu(const ContextMenu* defaultMenu) 182 { 183 // Displaying the context menu in this function is a big hack as we don't 184 // have context, i.e. whether this is being invoked via a script or in 185 // response to user input (Mouse event WM_RBUTTONDOWN, 186 // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked 187 // in response to the above input events before popping up the context menu. 188 if (!m_webView->contextMenuAllowed()) 189 return; 190 191 HitTestResult r = m_webView->page()->contextMenuController().hitTestResult(); 192 193 r.setToShadowHostIfInUserAgentShadowRoot(); 194 195 LocalFrame* selectedFrame = r.innerNodeFrame(); 196 197 WebContextMenuData data; 198 IntPoint mousePoint = selectedFrame->view()->contentsToWindow(r.roundedPointInInnerNodeFrame()); 199 200 // FIXME(bokan): crbug.com/371902 - We shouldn't be making these scale 201 // related coordinate transformatios in an ad hoc way. 202 PinchViewport& pinchViewport = selectedFrame->host()->pinchViewport(); 203 mousePoint -= flooredIntSize(pinchViewport.visibleRect().location()); 204 mousePoint.scale(m_webView->pageScaleFactor(), m_webView->pageScaleFactor()); 205 data.mousePosition = mousePoint; 206 207 // Compute edit flags. 208 data.editFlags = WebContextMenuData::CanDoNone; 209 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canUndo()) 210 data.editFlags |= WebContextMenuData::CanUndo; 211 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canRedo()) 212 data.editFlags |= WebContextMenuData::CanRedo; 213 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canCut()) 214 data.editFlags |= WebContextMenuData::CanCut; 215 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canCopy()) 216 data.editFlags |= WebContextMenuData::CanCopy; 217 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canPaste()) 218 data.editFlags |= WebContextMenuData::CanPaste; 219 if (toLocalFrame(m_webView->focusedCoreFrame())->editor().canDelete()) 220 data.editFlags |= WebContextMenuData::CanDelete; 221 if (isHTMLTextFormControlElement(r.innerNonSharedNode())) { 222 if (!toHTMLTextFormControlElement(r.innerNonSharedNode())->value().isEmpty()) 223 data.editFlags |= WebContextMenuData::CanSelectAll; 224 } 225 data.editFlags |= WebContextMenuData::CanTranslate; 226 227 // Links, Images, Media tags, and Image/Media-Links take preference over 228 // all else. 229 data.linkURL = r.absoluteLinkURL(); 230 231 if (isHTMLCanvasElement(r.innerNonSharedNode())) { 232 data.mediaType = WebContextMenuData::MediaTypeCanvas; 233 data.hasImageContents = true; 234 } else if (!r.absoluteImageURL().isEmpty()) { 235 data.srcURL = r.absoluteImageURL(); 236 data.mediaType = WebContextMenuData::MediaTypeImage; 237 data.mediaFlags |= WebContextMenuData::MediaCanPrint; 238 239 // An image can be null for many reasons, like being blocked, no image 240 // data received from server yet. 241 data.hasImageContents = r.image() && !r.image()->isNull(); 242 } else if (!r.absoluteMediaURL().isEmpty()) { 243 data.srcURL = r.absoluteMediaURL(); 244 245 // We know that if absoluteMediaURL() is not empty, then this 246 // is a media element. 247 HTMLMediaElement* mediaElement = toHTMLMediaElement(r.innerNonSharedNode()); 248 if (isHTMLVideoElement(*mediaElement)) 249 data.mediaType = WebContextMenuData::MediaTypeVideo; 250 else if (isHTMLAudioElement(*mediaElement)) 251 data.mediaType = WebContextMenuData::MediaTypeAudio; 252 253 if (mediaElement->error()) 254 data.mediaFlags |= WebContextMenuData::MediaInError; 255 if (mediaElement->paused()) 256 data.mediaFlags |= WebContextMenuData::MediaPaused; 257 if (mediaElement->muted()) 258 data.mediaFlags |= WebContextMenuData::MediaMuted; 259 if (mediaElement->loop()) 260 data.mediaFlags |= WebContextMenuData::MediaLoop; 261 if (mediaElement->supportsSave()) 262 data.mediaFlags |= WebContextMenuData::MediaCanSave; 263 if (mediaElement->hasAudio()) 264 data.mediaFlags |= WebContextMenuData::MediaHasAudio; 265 // Media controls can be toggled only for video player. If we toggle 266 // controls for audio then the player disappears, and there is no way to 267 // return it back. Don't set this bit for fullscreen video, since 268 // toggling is ignored in that case. 269 if (mediaElement->hasVideo() && !mediaElement->isFullscreen()) 270 data.mediaFlags |= WebContextMenuData::MediaCanToggleControls; 271 if (mediaElement->shouldShowControls()) 272 data.mediaFlags |= WebContextMenuData::MediaControls; 273 } else if (isHTMLObjectElement(*r.innerNonSharedNode()) || isHTMLEmbedElement(*r.innerNonSharedNode())) { 274 RenderObject* object = r.innerNonSharedNode()->renderer(); 275 if (object && object->isWidget()) { 276 Widget* widget = toRenderWidget(object)->widget(); 277 if (widget && widget->isPluginContainer()) { 278 data.mediaType = WebContextMenuData::MediaTypePlugin; 279 WebPluginContainerImpl* plugin = toWebPluginContainerImpl(widget); 280 WebString text = plugin->plugin()->selectionAsText(); 281 if (!text.isEmpty()) { 282 data.selectedText = text; 283 data.editFlags |= WebContextMenuData::CanCopy; 284 } 285 data.editFlags &= ~WebContextMenuData::CanTranslate; 286 data.linkURL = plugin->plugin()->linkAtPosition(data.mousePosition); 287 if (plugin->plugin()->supportsPaginatedPrint()) 288 data.mediaFlags |= WebContextMenuData::MediaCanPrint; 289 290 HTMLPlugInElement* pluginElement = toHTMLPlugInElement(r.innerNonSharedNode()); 291 data.srcURL = pluginElement->document().completeURL(pluginElement->url()); 292 data.mediaFlags |= WebContextMenuData::MediaCanSave; 293 294 // Add context menu commands that are supported by the plugin. 295 if (plugin->plugin()->canRotateView()) 296 data.mediaFlags |= WebContextMenuData::MediaCanRotate; 297 } 298 } 299 } 300 301 // If it's not a link, an image, a media element, or an image/media link, 302 // show a selection menu or a more generic page menu. 303 if (selectedFrame->document()->loader()) 304 data.frameEncoding = selectedFrame->document()->encodingName(); 305 306 // Send the frame and page URLs in any case. 307 data.pageURL = urlFromFrame(m_webView->mainFrameImpl()->frame()); 308 if (selectedFrame != m_webView->mainFrameImpl()->frame()) { 309 data.frameURL = urlFromFrame(selectedFrame); 310 RefPtr<HistoryItem> historyItem = selectedFrame->loader().currentItem(); 311 if (historyItem) 312 data.frameHistoryItem = WebHistoryItem(historyItem); 313 } 314 315 if (r.isSelected()) { 316 if (!isHTMLInputElement(*r.innerNonSharedNode()) || toHTMLInputElement(r.innerNonSharedNode())->type() != InputTypeNames::password) 317 data.selectedText = selectedFrame->selectedText().stripWhiteSpace(); 318 } 319 320 if (r.isContentEditable()) { 321 data.isEditable = true; 322 323 // When Chrome enables asynchronous spellchecking, its spellchecker adds spelling markers to misspelled 324 // words and attaches suggestions to these markers in the background. Therefore, when a user right-clicks 325 // a mouse on a word, Chrome just needs to find a spelling marker on the word instead of spellchecking it. 326 if (selectedFrame->settings() && selectedFrame->settings()->asynchronousSpellCheckingEnabled()) { 327 String description; 328 uint32_t hash = 0; 329 data.misspelledWord = selectMisspellingAsync(selectedFrame, description, hash); 330 data.misspellingHash = hash; 331 if (description.length()) { 332 Vector<String> suggestions; 333 description.split('\n', suggestions); 334 data.dictionarySuggestions = suggestions; 335 } else if (m_webView->spellCheckClient()) { 336 int misspelledOffset, misspelledLength; 337 m_webView->spellCheckClient()->spellCheck(data.misspelledWord, misspelledOffset, misspelledLength, &data.dictionarySuggestions); 338 } 339 } else { 340 data.isSpellCheckingEnabled = 341 toLocalFrame(m_webView->focusedCoreFrame())->spellChecker().isContinuousSpellCheckingEnabled(); 342 // Spellchecking might be enabled for the field, but could be disabled on the node. 343 if (toLocalFrame(m_webView->focusedCoreFrame())->spellChecker().isSpellCheckingEnabledInFocusedNode()) { 344 data.misspelledWord = selectMisspelledWord(selectedFrame); 345 if (m_webView->spellCheckClient()) { 346 int misspelledOffset, misspelledLength; 347 m_webView->spellCheckClient()->spellCheck( 348 data.misspelledWord, misspelledOffset, misspelledLength, 349 &data.dictionarySuggestions); 350 if (!misspelledLength) 351 data.misspelledWord.reset(); 352 } 353 } 354 } 355 HTMLFormElement* form = selectedFrame->selection().currentForm(); 356 if (form && isHTMLInputElement(*r.innerNonSharedNode())) { 357 HTMLInputElement& selectedElement = toHTMLInputElement(*r.innerNonSharedNode()); 358 WebSearchableFormData ws = WebSearchableFormData(WebFormElement(form), WebInputElement(&selectedElement)); 359 if (ws.url().isValid()) 360 data.keywordURL = ws.url(); 361 } 362 } 363 364 if (selectedFrame->editor().selectionHasStyle(CSSPropertyDirection, "ltr") != FalseTriState) 365 data.writingDirectionLeftToRight |= WebContextMenuData::CheckableMenuItemChecked; 366 if (selectedFrame->editor().selectionHasStyle(CSSPropertyDirection, "rtl") != FalseTriState) 367 data.writingDirectionRightToLeft |= WebContextMenuData::CheckableMenuItemChecked; 368 369 // Now retrieve the security info. 370 DocumentLoader* dl = selectedFrame->loader().documentLoader(); 371 WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl); 372 if (ds) 373 data.securityInfo = ds->response().securityInfo(); 374 375 data.referrerPolicy = static_cast<WebReferrerPolicy>(selectedFrame->document()->referrerPolicy()); 376 377 // Filter out custom menu elements and add them into the data. 378 populateCustomMenuItems(defaultMenu, &data); 379 380 // Extract suggested filename for saving file. 381 if (isHTMLAnchorElement(r.URLElement())) { 382 HTMLAnchorElement* anchor = toHTMLAnchorElement(r.URLElement()); 383 data.suggestedFilename = anchor->fastGetAttribute(HTMLNames::downloadAttr); 384 } 385 386 data.node = r.innerNonSharedNode(); 387 388 WebLocalFrameImpl* selectedWebFrame = WebLocalFrameImpl::fromFrame(selectedFrame); 389 if (selectedWebFrame->client()) 390 selectedWebFrame->client()->showContextMenu(data); 391 } 392 393 void ContextMenuClientImpl::clearContextMenu() 394 { 395 HitTestResult r = m_webView->page()->contextMenuController().hitTestResult(); 396 LocalFrame* selectedFrame = r.innerNodeFrame(); 397 if (!selectedFrame) 398 return; 399 400 WebLocalFrameImpl* selectedWebFrame = WebLocalFrameImpl::fromFrame(selectedFrame); 401 if (selectedWebFrame->client()) 402 selectedWebFrame->client()->clearContextMenu(); 403 } 404 405 static void populateSubMenuItems(const Vector<ContextMenuItem>& inputMenu, WebVector<WebMenuItemInfo>& subMenuItems) 406 { 407 Vector<WebMenuItemInfo> subItems; 408 for (size_t i = 0; i < inputMenu.size(); ++i) { 409 const ContextMenuItem* inputItem = &inputMenu.at(i); 410 if (inputItem->action() < ContextMenuItemBaseCustomTag || inputItem->action() > ContextMenuItemLastCustomTag) 411 continue; 412 413 WebMenuItemInfo outputItem; 414 outputItem.label = inputItem->title(); 415 outputItem.enabled = inputItem->enabled(); 416 outputItem.checked = inputItem->checked(); 417 outputItem.action = static_cast<unsigned>(inputItem->action() - ContextMenuItemBaseCustomTag); 418 switch (inputItem->type()) { 419 case ActionType: 420 outputItem.type = WebMenuItemInfo::Option; 421 break; 422 case CheckableActionType: 423 outputItem.type = WebMenuItemInfo::CheckableOption; 424 break; 425 case SeparatorType: 426 outputItem.type = WebMenuItemInfo::Separator; 427 break; 428 case SubmenuType: 429 outputItem.type = WebMenuItemInfo::SubMenu; 430 populateSubMenuItems(inputItem->subMenuItems(), outputItem.subMenuItems); 431 break; 432 } 433 subItems.append(outputItem); 434 } 435 436 WebVector<WebMenuItemInfo> outputItems(subItems.size()); 437 for (size_t i = 0; i < subItems.size(); ++i) 438 outputItems[i] = subItems[i]; 439 subMenuItems.swap(outputItems); 440 } 441 442 void ContextMenuClientImpl::populateCustomMenuItems(const ContextMenu* defaultMenu, WebContextMenuData* data) 443 { 444 populateSubMenuItems(defaultMenu->items(), data->customItems); 445 } 446 447 } // namespace blink 448