1/*
2 * Copyright (C) 2009 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 "CSSStyleDeclaration.h"
36#include "ContextMenu.h"
37#include "Document.h"
38#include "DocumentLoader.h"
39#include "Editor.h"
40#include "EventHandler.h"
41#include "FrameLoader.h"
42#include "FrameView.h"
43#include "HitTestResult.h"
44#include "HTMLMediaElement.h"
45#include "HTMLNames.h"
46#include "KURL.h"
47#include "MediaError.h"
48#include "PlatformString.h"
49#include "TextBreakIterator.h"
50#include "Widget.h"
51
52#include "WebContextMenuData.h"
53#include "WebDataSourceImpl.h"
54#include "WebFrameImpl.h"
55#include "WebMenuItemInfo.h"
56#include "WebPoint.h"
57#include "WebString.h"
58#include "WebURL.h"
59#include "WebURLResponse.h"
60#include "WebVector.h"
61#include "WebViewClient.h"
62#include "WebViewImpl.h"
63
64using namespace WebCore;
65
66namespace WebKit {
67
68// Figure out the URL of a page or subframe. Returns |page_type| as the type,
69// which indicates page or subframe, or ContextNodeType::NONE if the URL could not
70// be determined for some reason.
71static WebURL urlFromFrame(Frame* frame)
72{
73    if (frame) {
74        DocumentLoader* dl = frame->loader()->documentLoader();
75        if (dl) {
76            WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
77            if (ds)
78                return ds->hasUnreachableURL() ? ds->unreachableURL() : ds->request().url();
79        }
80    }
81    return WebURL();
82}
83
84// Helper function to determine whether text is a single word.
85static bool isASingleWord(const String& text)
86{
87    TextBreakIterator* it = wordBreakIterator(text.characters(), text.length());
88    return it && textBreakNext(it) == static_cast<int>(text.length());
89}
90
91// Helper function to get misspelled word on which context menu
92// is to be evolked. This function also sets the word on which context menu
93// has been evoked to be the selected word, as required. This function changes
94// the selection only when there were no selected characters on OS X.
95static String selectMisspelledWord(const ContextMenu* defaultMenu, Frame* selectedFrame)
96{
97    // First select from selectedText to check for multiple word selection.
98    String misspelledWord = selectedFrame->selectedText().stripWhiteSpace();
99
100    // If some texts were already selected, we don't change the selection.
101    if (!misspelledWord.isEmpty()) {
102        // Don't provide suggestions for multiple words.
103        if (!isASingleWord(misspelledWord))
104            return String();
105        return misspelledWord;
106    }
107
108    // Selection is empty, so change the selection to the word under the cursor.
109    HitTestResult hitTestResult = selectedFrame->eventHandler()->
110        hitTestResultAtPoint(defaultMenu->hitTestResult().point(), true);
111    Node* innerNode = hitTestResult.innerNode();
112    VisiblePosition pos(innerNode->renderer()->positionForPoint(
113        hitTestResult.localPoint()));
114
115    if (pos.isNull())
116        return misspelledWord; // It is empty.
117
118    WebFrameImpl::selectWordAroundPosition(selectedFrame, pos);
119    misspelledWord = selectedFrame->selectedText().stripWhiteSpace();
120
121#if OS(DARWIN)
122    // If misspelled word is still empty, then that portion should not be
123    // selected. Set the selection to that position only, and do not expand.
124    if (misspelledWord.isEmpty())
125        selectedFrame->selection()->setSelection(VisibleSelection(pos));
126#else
127    // On non-Mac, right-click should not make a range selection in any case.
128    selectedFrame->selection()->setSelection(VisibleSelection(pos));
129#endif
130    return misspelledWord;
131}
132
133PlatformMenuDescription ContextMenuClientImpl::getCustomMenuFromDefaultItems(
134    ContextMenu* defaultMenu)
135{
136    // Displaying the context menu in this function is a big hack as we don't
137    // have context, i.e. whether this is being invoked via a script or in
138    // response to user input (Mouse event WM_RBUTTONDOWN,
139    // Keyboard events KeyVK_APPS, Shift+F10). Check if this is being invoked
140    // in response to the above input events before popping up the context menu.
141    if (!m_webView->contextMenuAllowed())
142        return 0;
143
144    HitTestResult r = defaultMenu->hitTestResult();
145    Frame* selectedFrame = r.innerNonSharedNode()->document()->frame();
146
147    WebContextMenuData data;
148    data.mousePosition = selectedFrame->view()->contentsToWindow(r.point());
149
150    // Links, Images, Media tags, and Image/Media-Links take preference over
151    // all else.
152    data.linkURL = r.absoluteLinkURL();
153
154    data.mediaType = WebContextMenuData::MediaTypeNone;
155    data.mediaFlags = WebContextMenuData::MediaNone;
156
157    if (!r.absoluteImageURL().isEmpty()) {
158        data.srcURL = r.absoluteImageURL();
159        data.mediaType = WebContextMenuData::MediaTypeImage;
160    } else if (!r.absoluteMediaURL().isEmpty()) {
161        data.srcURL = r.absoluteMediaURL();
162
163        // We know that if absoluteMediaURL() is not empty, then this
164        // is a media element.
165        HTMLMediaElement* mediaElement =
166            static_cast<HTMLMediaElement*>(r.innerNonSharedNode());
167        if (mediaElement->hasTagName(HTMLNames::videoTag))
168            data.mediaType = WebContextMenuData::MediaTypeVideo;
169        else if (mediaElement->hasTagName(HTMLNames::audioTag))
170            data.mediaType = WebContextMenuData::MediaTypeAudio;
171
172        if (mediaElement->error())
173            data.mediaFlags |= WebContextMenuData::MediaInError;
174        if (mediaElement->paused())
175            data.mediaFlags |= WebContextMenuData::MediaPaused;
176        if (mediaElement->muted())
177            data.mediaFlags |= WebContextMenuData::MediaMuted;
178        if (mediaElement->loop())
179            data.mediaFlags |= WebContextMenuData::MediaLoop;
180        if (mediaElement->supportsSave())
181            data.mediaFlags |= WebContextMenuData::MediaCanSave;
182        if (mediaElement->hasAudio())
183            data.mediaFlags |= WebContextMenuData::MediaHasAudio;
184    }
185    // If it's not a link, an image, a media element, or an image/media link,
186    // show a selection menu or a more generic page menu.
187    data.frameEncoding = selectedFrame->loader()->encoding();
188
189    // Send the frame and page URLs in any case.
190    data.pageURL = urlFromFrame(m_webView->mainFrameImpl()->frame());
191    if (selectedFrame != m_webView->mainFrameImpl()->frame())
192        data.frameURL = urlFromFrame(selectedFrame);
193
194    if (r.isSelected())
195        data.selectedText = selectedFrame->selectedText().stripWhiteSpace();
196
197    data.isEditable = false;
198    if (r.isContentEditable()) {
199        data.isEditable = true;
200        if (m_webView->focusedWebCoreFrame()->editor()->isContinuousSpellCheckingEnabled()) {
201            data.isSpellCheckingEnabled = true;
202            data.misspelledWord = selectMisspelledWord(defaultMenu, selectedFrame);
203        }
204    }
205
206#if OS(DARWIN)
207    // Writing direction context menu.
208    data.writingDirectionDefault = WebContextMenuData::CheckableMenuItemDisabled;
209    data.writingDirectionLeftToRight = WebContextMenuData::CheckableMenuItemEnabled;
210    data.writingDirectionRightToLeft = WebContextMenuData::CheckableMenuItemEnabled;
211
212    ExceptionCode ec = 0;
213    RefPtr<CSSStyleDeclaration> style = selectedFrame->document()->createCSSStyleDeclaration();
214    style->setProperty(CSSPropertyDirection, "ltr", false, ec);
215    if (selectedFrame->editor()->selectionHasStyle(style.get()) != FalseTriState)
216        data.writingDirectionLeftToRight |= WebContextMenuData::CheckableMenuItemChecked;
217    style->setProperty(CSSPropertyDirection, "rtl", false, ec);
218    if (selectedFrame->editor()->selectionHasStyle(style.get()) != FalseTriState)
219        data.writingDirectionRightToLeft |= WebContextMenuData::CheckableMenuItemChecked;
220#endif // OS(DARWIN)
221
222    // Now retrieve the security info.
223    DocumentLoader* dl = selectedFrame->loader()->documentLoader();
224    WebDataSource* ds = WebDataSourceImpl::fromDocumentLoader(dl);
225    if (ds)
226        data.securityInfo = ds->response().securityInfo();
227
228    // Compute edit flags.
229    data.editFlags = WebContextMenuData::CanDoNone;
230    if (m_webView->focusedWebCoreFrame()->editor()->canUndo())
231        data.editFlags |= WebContextMenuData::CanUndo;
232    if (m_webView->focusedWebCoreFrame()->editor()->canRedo())
233        data.editFlags |= WebContextMenuData::CanRedo;
234    if (m_webView->focusedWebCoreFrame()->editor()->canCut())
235        data.editFlags |= WebContextMenuData::CanCut;
236    if (m_webView->focusedWebCoreFrame()->editor()->canCopy())
237        data.editFlags |= WebContextMenuData::CanCopy;
238    if (m_webView->focusedWebCoreFrame()->editor()->canPaste())
239        data.editFlags |= WebContextMenuData::CanPaste;
240    if (m_webView->focusedWebCoreFrame()->editor()->canDelete())
241        data.editFlags |= WebContextMenuData::CanDelete;
242    // We can always select all...
243    data.editFlags |= WebContextMenuData::CanSelectAll;
244
245    // Filter out custom menu elements and add them into the data.
246    populateCustomMenuItems(defaultMenu, &data);
247
248    WebFrame* selected_web_frame = WebFrameImpl::fromFrame(selectedFrame);
249    if (m_webView->client())
250        m_webView->client()->showContextMenu(selected_web_frame, data);
251
252    return 0;
253}
254
255void ContextMenuClientImpl::populateCustomMenuItems(WebCore::ContextMenu* defaultMenu, WebContextMenuData* data)
256{
257    Vector<WebMenuItemInfo> customItems;
258    for (size_t i = 0; i < defaultMenu->itemCount(); ++i) {
259        ContextMenuItem* inputItem = defaultMenu->itemAtIndex(i, defaultMenu->platformDescription());
260        if (inputItem->action() < ContextMenuItemBaseCustomTag || inputItem->action() >=  ContextMenuItemBaseApplicationTag)
261            continue;
262
263        WebMenuItemInfo outputItem;
264        outputItem.label = inputItem->title();
265        outputItem.enabled = inputItem->enabled();
266        outputItem.checked = inputItem->checked();
267        outputItem.action = static_cast<unsigned>(inputItem->action() - ContextMenuItemBaseCustomTag);
268        switch (inputItem->type()) {
269        case ActionType:
270            outputItem.type = WebMenuItemInfo::Option;
271            break;
272        case CheckableActionType:
273            outputItem.type = WebMenuItemInfo::CheckableOption;
274            break;
275        case SeparatorType:
276            outputItem.type = WebMenuItemInfo::Separator;
277            break;
278        case SubmenuType:
279            outputItem.type = WebMenuItemInfo::Group;
280            break;
281        }
282        customItems.append(outputItem);
283    }
284
285    WebVector<WebMenuItemInfo> outputItems(customItems.size());
286    for (size_t i = 0; i < customItems.size(); ++i)
287        outputItems[i] = customItems[i];
288    data->customItems.swap(outputItems);
289}
290
291} // namespace WebKit
292