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/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