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