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