1/*
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Igalia S.L
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "ContextMenuController.h"
29
30#if ENABLE(CONTEXT_MENUS)
31
32#include "BackForwardController.h"
33#include "Chrome.h"
34#include "ContextMenu.h"
35#include "ContextMenuClient.h"
36#include "ContextMenuItem.h"
37#include "ContextMenuProvider.h"
38#include "Document.h"
39#include "DocumentFragment.h"
40#include "DocumentLoader.h"
41#include "Editor.h"
42#include "EditorClient.h"
43#include "Event.h"
44#include "EventHandler.h"
45#include "EventNames.h"
46#include "ExceptionCodePlaceholder.h"
47#include "FormState.h"
48#include "FrameLoadRequest.h"
49#include "FrameLoader.h"
50#include "FrameLoaderClient.h"
51#include "FrameSelection.h"
52#include "HTMLFormElement.h"
53#include "HitTestRequest.h"
54#include "HitTestResult.h"
55#include "InspectorController.h"
56#include "LocalizedStrings.h"
57#include "MainFrame.h"
58#include "MouseEvent.h"
59#include "NavigationAction.h"
60#include "Node.h"
61#include "Page.h"
62#include "PlatformEvent.h"
63#include "RenderImage.h"
64#include "ReplaceSelectionCommand.h"
65#include "ResourceRequest.h"
66#include "Settings.h"
67#include "TextIterator.h"
68#include "TypingCommand.h"
69#include "UserTypingGestureIndicator.h"
70#include "WindowFeatures.h"
71#include "markup.h"
72#include <wtf/unicode/CharacterNames.h>
73
74using namespace WTF;
75using namespace Unicode;
76
77namespace WebCore {
78
79ContextMenuController::ContextMenuController(Page& page, ContextMenuClient& client)
80    : m_page(page)
81    , m_client(client)
82{
83}
84
85ContextMenuController::~ContextMenuController()
86{
87    m_client.contextMenuDestroyed();
88}
89
90void ContextMenuController::clearContextMenu()
91{
92    m_contextMenu.clear();
93    if (m_menuProvider)
94        m_menuProvider->contextMenuCleared();
95    m_menuProvider = 0;
96}
97
98void ContextMenuController::handleContextMenuEvent(Event* event)
99{
100    m_contextMenu = maybeCreateContextMenu(event);
101    if (!m_contextMenu)
102        return;
103
104    populate();
105
106    showContextMenu(event);
107}
108
109static PassOwnPtr<ContextMenuItem> separatorItem()
110{
111    return adoptPtr(new ContextMenuItem(SeparatorType, ContextMenuItemTagNoAction, String()));
112}
113
114void ContextMenuController::showContextMenu(Event* event, PassRefPtr<ContextMenuProvider> menuProvider)
115{
116    m_menuProvider = menuProvider;
117
118    m_contextMenu = maybeCreateContextMenu(event);
119    if (!m_contextMenu) {
120        clearContextMenu();
121        return;
122    }
123
124    m_menuProvider->populateContextMenu(m_contextMenu.get());
125    if (m_context.hitTestResult().isSelected()) {
126        appendItem(*separatorItem(), m_contextMenu.get());
127        populate();
128    }
129    showContextMenu(event);
130}
131
132#if ENABLE(SERVICE_CONTROLS)
133static Image* imageFromImageElementNode(Node& node)
134{
135    RenderObject* renderer = node.renderer();
136    if (!renderer)
137        return nullptr;
138    if (!renderer->isRenderImage())
139        return nullptr;
140    CachedImage* image = toRenderImage(*renderer).cachedImage();
141    if (!image || image->errorOccurred())
142        return nullptr;
143
144    return image->imageForRenderer(renderer);
145}
146#endif
147
148PassOwnPtr<ContextMenu> ContextMenuController::maybeCreateContextMenu(Event* event)
149{
150    ASSERT(event);
151
152    if (!event->isMouseEvent())
153        return nullptr;
154
155    MouseEvent& mouseEvent = toMouseEvent(*event);
156    HitTestResult result(mouseEvent.absoluteLocation());
157
158    Node* node = event->target()->toNode();
159    if (Frame* frame = node->document().frame())
160        result = frame->eventHandler().hitTestResultAtPoint(mouseEvent.absoluteLocation());
161
162    if (!result.innerNonSharedNode())
163        return nullptr;
164
165    m_context = ContextMenuContext(result);
166
167#if ENABLE(SERVICE_CONTROLS)
168    if (node->isImageControlsButtonElement()) {
169        if (Image* image = imageFromImageElementNode(*result.innerNonSharedNode()))
170            m_context.setControlledImage(image);
171
172        // FIXME: If we couldn't get the image then we shouldn't try to show the image controls menu for it.
173        return nullptr;
174    }
175#endif
176
177    return adoptPtr(new ContextMenu);
178}
179
180void ContextMenuController::showContextMenu(Event* event)
181{
182#if ENABLE(INSPECTOR)
183    if (m_page.inspectorController().enabled())
184        addInspectElementItem();
185#endif
186
187#if USE(CROSS_PLATFORM_CONTEXT_MENUS)
188    m_contextMenu = m_client.customizeMenu(m_contextMenu.release());
189#else
190    PlatformMenuDescription customMenu = m_client.getCustomMenuFromDefaultItems(m_contextMenu.get());
191    m_contextMenu->setPlatformDescription(customMenu);
192#endif
193    event->setDefaultHandled();
194}
195
196static void openNewWindow(const URL& urlToLoad, Frame* frame)
197{
198    Page* oldPage = frame->page();
199    if (!oldPage)
200        return;
201
202    FrameLoadRequest request(frame->document()->securityOrigin(), ResourceRequest(urlToLoad, frame->loader().outgoingReferrer()));
203    Page* newPage = oldPage;
204
205    newPage = oldPage->chrome().createWindow(frame, request, WindowFeatures(), NavigationAction(request.resourceRequest()));
206    if (!newPage)
207        return;
208    newPage->chrome().show();
209    newPage->mainFrame().loader().loadFrameRequest(request, LockHistory::No, LockBackForwardList::No, 0, 0, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes);
210}
211
212#if PLATFORM(GTK)
213static void insertUnicodeCharacter(UChar character, Frame* frame)
214{
215    String text(&character, 1);
216    if (!frame->editor().shouldInsertText(text, frame->selection().toNormalizedRange().get(), EditorInsertActionTyped))
217        return;
218
219    ASSERT(frame->document());
220    TypingCommand::insertText(*frame->document(), text, 0, TypingCommand::TextCompositionNone);
221}
222#endif
223
224void ContextMenuController::contextMenuItemSelected(ContextMenuItem* item)
225{
226    ASSERT(item->type() == ActionType || item->type() == CheckableActionType);
227
228    if (item->action() >= ContextMenuItemBaseApplicationTag) {
229        m_client.contextMenuItemSelected(item, m_contextMenu.get());
230        return;
231    }
232
233    if (item->action() >= ContextMenuItemBaseCustomTag) {
234        ASSERT(m_menuProvider);
235        m_menuProvider->contextMenuItemSelected(item);
236        return;
237    }
238
239    Frame* frame = m_context.hitTestResult().innerNonSharedNode()->document().frame();
240    if (!frame)
241        return;
242
243    switch (item->action()) {
244    case ContextMenuItemTagOpenLinkInNewWindow:
245        openNewWindow(m_context.hitTestResult().absoluteLinkURL(), frame);
246        break;
247    case ContextMenuItemTagDownloadLinkToDisk:
248        // FIXME: Some day we should be able to do this from within WebCore. (Bug 117709)
249        m_client.downloadURL(m_context.hitTestResult().absoluteLinkURL());
250        break;
251    case ContextMenuItemTagCopyLinkToClipboard:
252        frame->editor().copyURL(m_context.hitTestResult().absoluteLinkURL(), m_context.hitTestResult().textContent());
253        break;
254    case ContextMenuItemTagOpenImageInNewWindow:
255        openNewWindow(m_context.hitTestResult().absoluteImageURL(), frame);
256        break;
257    case ContextMenuItemTagDownloadImageToDisk:
258        // FIXME: Some day we should be able to do this from within WebCore. (Bug 117709)
259        m_client.downloadURL(m_context.hitTestResult().absoluteImageURL());
260        break;
261    case ContextMenuItemTagCopyImageToClipboard:
262        // FIXME: The Pasteboard class is not written yet
263        // For now, call into the client. This is temporary!
264        frame->editor().copyImage(m_context.hitTestResult());
265        break;
266#if PLATFORM(GTK) || PLATFORM(EFL)
267    case ContextMenuItemTagCopyImageUrlToClipboard:
268        frame->editor().copyURL(m_context.hitTestResult().absoluteImageURL(), m_context.hitTestResult().textContent());
269        break;
270#endif
271    case ContextMenuItemTagOpenMediaInNewWindow:
272        openNewWindow(m_context.hitTestResult().absoluteMediaURL(), frame);
273        break;
274    case ContextMenuItemTagDownloadMediaToDisk:
275        // FIXME: Some day we should be able to do this from within WebCore. (Bug 117709)
276        m_client.downloadURL(m_context.hitTestResult().absoluteMediaURL());
277        break;
278    case ContextMenuItemTagCopyMediaLinkToClipboard:
279        frame->editor().copyURL(m_context.hitTestResult().absoluteMediaURL(), m_context.hitTestResult().textContent());
280        break;
281    case ContextMenuItemTagToggleMediaControls:
282        m_context.hitTestResult().toggleMediaControlsDisplay();
283        break;
284    case ContextMenuItemTagToggleMediaLoop:
285        m_context.hitTestResult().toggleMediaLoopPlayback();
286        break;
287    case ContextMenuItemTagToggleVideoFullscreen:
288        m_context.hitTestResult().toggleMediaFullscreenState();
289        break;
290    case ContextMenuItemTagEnterVideoFullscreen:
291        m_context.hitTestResult().enterFullscreenForVideo();
292        break;
293    case ContextMenuItemTagMediaPlayPause:
294        m_context.hitTestResult().toggleMediaPlayState();
295        break;
296    case ContextMenuItemTagMediaMute:
297        m_context.hitTestResult().toggleMediaMuteState();
298        break;
299    case ContextMenuItemTagOpenFrameInNewWindow: {
300        DocumentLoader* loader = frame->loader().documentLoader();
301        if (!loader->unreachableURL().isEmpty())
302            openNewWindow(loader->unreachableURL(), frame);
303        else
304            openNewWindow(loader->url(), frame);
305        break;
306    }
307    case ContextMenuItemTagCopy:
308        frame->editor().copy();
309        break;
310    case ContextMenuItemTagGoBack:
311        if (Page* page = frame->page())
312            page->backForward().goBackOrForward(-1);
313        break;
314    case ContextMenuItemTagGoForward:
315        if (Page* page = frame->page())
316            page->backForward().goBackOrForward(1);
317        break;
318    case ContextMenuItemTagStop:
319        frame->loader().stop();
320        break;
321    case ContextMenuItemTagReload:
322        frame->loader().reload();
323        break;
324    case ContextMenuItemTagCut:
325        frame->editor().command("Cut").execute();
326        break;
327    case ContextMenuItemTagPaste:
328        frame->editor().command("Paste").execute();
329        break;
330#if PLATFORM(GTK)
331    case ContextMenuItemTagDelete:
332        frame->editor().performDelete();
333        break;
334    case ContextMenuItemTagUnicodeInsertLRMMark:
335        insertUnicodeCharacter(leftToRightMark, frame);
336        break;
337    case ContextMenuItemTagUnicodeInsertRLMMark:
338        insertUnicodeCharacter(rightToLeftMark, frame);
339        break;
340    case ContextMenuItemTagUnicodeInsertLREMark:
341        insertUnicodeCharacter(leftToRightEmbed, frame);
342        break;
343    case ContextMenuItemTagUnicodeInsertRLEMark:
344        insertUnicodeCharacter(rightToLeftEmbed, frame);
345        break;
346    case ContextMenuItemTagUnicodeInsertLROMark:
347        insertUnicodeCharacter(leftToRightOverride, frame);
348        break;
349    case ContextMenuItemTagUnicodeInsertRLOMark:
350        insertUnicodeCharacter(rightToLeftOverride, frame);
351        break;
352    case ContextMenuItemTagUnicodeInsertPDFMark:
353        insertUnicodeCharacter(popDirectionalFormatting, frame);
354        break;
355    case ContextMenuItemTagUnicodeInsertZWSMark:
356        insertUnicodeCharacter(zeroWidthSpace, frame);
357        break;
358    case ContextMenuItemTagUnicodeInsertZWJMark:
359        insertUnicodeCharacter(zeroWidthJoiner, frame);
360        break;
361    case ContextMenuItemTagUnicodeInsertZWNJMark:
362        insertUnicodeCharacter(zeroWidthNonJoiner, frame);
363        break;
364#endif
365#if PLATFORM(GTK) || PLATFORM(EFL)
366    case ContextMenuItemTagSelectAll:
367        frame->editor().command("SelectAll").execute();
368        break;
369#endif
370    case ContextMenuItemTagSpellingGuess: {
371        VisibleSelection selection = frame->selection().selection();
372        if (frame->editor().shouldInsertText(item->title(), selection.toNormalizedRange().get(), EditorInsertActionPasted)) {
373            ReplaceSelectionCommand::CommandOptions replaceOptions = ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting;
374
375            if (frame->editor().behavior().shouldAllowSpellingSuggestionsWithoutSelection()) {
376                ASSERT(selection.isCaretOrRange());
377                VisibleSelection wordSelection(selection.base());
378                wordSelection.expandUsingGranularity(WordGranularity);
379                frame->selection().setSelection(wordSelection);
380            } else {
381                ASSERT(frame->editor().selectedText().length());
382                replaceOptions |= ReplaceSelectionCommand::SelectReplacement;
383            }
384
385            Document* document = frame->document();
386            ASSERT(document);
387            RefPtr<ReplaceSelectionCommand> command = ReplaceSelectionCommand::create(*document, createFragmentFromMarkup(*document, item->title(), ""), replaceOptions);
388            applyCommand(command);
389            frame->selection().revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
390        }
391        break;
392    }
393    case ContextMenuItemTagIgnoreSpelling:
394        frame->editor().ignoreSpelling();
395        break;
396    case ContextMenuItemTagLearnSpelling:
397        frame->editor().learnSpelling();
398        break;
399    case ContextMenuItemTagSearchWeb:
400        m_client.searchWithGoogle(frame);
401        break;
402    case ContextMenuItemTagLookUpInDictionary:
403        // FIXME: Some day we may be able to do this from within WebCore.
404        m_client.lookUpInDictionary(frame);
405        break;
406    case ContextMenuItemTagOpenLink:
407        if (Frame* targetFrame = m_context.hitTestResult().targetFrame())
408            targetFrame->loader().loadFrameRequest(FrameLoadRequest(frame->document()->securityOrigin(), ResourceRequest(m_context.hitTestResult().absoluteLinkURL(), frame->loader().outgoingReferrer())), LockHistory::No, LockBackForwardList::No, 0, 0, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes);
409        else
410            openNewWindow(m_context.hitTestResult().absoluteLinkURL(), frame);
411        break;
412    case ContextMenuItemTagOpenLinkInThisWindow:
413        frame->loader().loadFrameRequest(FrameLoadRequest(frame->document()->securityOrigin(), ResourceRequest(m_context.hitTestResult().absoluteLinkURL(), frame->loader().outgoingReferrer())), LockHistory::No, LockBackForwardList::No, 0, 0, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes);
414        break;
415    case ContextMenuItemTagBold:
416        frame->editor().command("ToggleBold").execute();
417        break;
418    case ContextMenuItemTagItalic:
419        frame->editor().command("ToggleItalic").execute();
420        break;
421    case ContextMenuItemTagUnderline:
422        frame->editor().toggleUnderline();
423        break;
424    case ContextMenuItemTagOutline:
425        // We actually never enable this because CSS does not have a way to specify an outline font,
426        // which may make this difficult to implement. Maybe a special case of text-shadow?
427        break;
428    case ContextMenuItemTagStartSpeaking: {
429        RefPtr<Range> selectedRange = frame->selection().toNormalizedRange();
430        if (!selectedRange || selectedRange->collapsed(IGNORE_EXCEPTION)) {
431            Document& document = m_context.hitTestResult().innerNonSharedNode()->document();
432            selectedRange = document.createRange();
433            selectedRange->selectNode(document.documentElement(), IGNORE_EXCEPTION);
434        }
435        m_client.speak(plainText(selectedRange.get()));
436        break;
437    }
438    case ContextMenuItemTagStopSpeaking:
439        m_client.stopSpeaking();
440        break;
441    case ContextMenuItemTagDefaultDirection:
442        frame->editor().setBaseWritingDirection(NaturalWritingDirection);
443        break;
444    case ContextMenuItemTagLeftToRight:
445        frame->editor().setBaseWritingDirection(LeftToRightWritingDirection);
446        break;
447    case ContextMenuItemTagRightToLeft:
448        frame->editor().setBaseWritingDirection(RightToLeftWritingDirection);
449        break;
450    case ContextMenuItemTagTextDirectionDefault:
451        frame->editor().command("MakeTextWritingDirectionNatural").execute();
452        break;
453    case ContextMenuItemTagTextDirectionLeftToRight:
454        frame->editor().command("MakeTextWritingDirectionLeftToRight").execute();
455        break;
456    case ContextMenuItemTagTextDirectionRightToLeft:
457        frame->editor().command("MakeTextWritingDirectionRightToLeft").execute();
458        break;
459#if PLATFORM(COCOA)
460    case ContextMenuItemTagSearchInSpotlight:
461        m_client.searchWithSpotlight();
462        break;
463#endif
464    case ContextMenuItemTagShowSpellingPanel:
465        frame->editor().showSpellingGuessPanel();
466        break;
467    case ContextMenuItemTagCheckSpelling:
468        frame->editor().advanceToNextMisspelling();
469        break;
470    case ContextMenuItemTagCheckSpellingWhileTyping:
471        frame->editor().toggleContinuousSpellChecking();
472        break;
473    case ContextMenuItemTagCheckGrammarWithSpelling:
474        frame->editor().toggleGrammarChecking();
475        break;
476#if PLATFORM(COCOA)
477    case ContextMenuItemTagShowFonts:
478        frame->editor().showFontPanel();
479        break;
480    case ContextMenuItemTagStyles:
481        frame->editor().showStylesPanel();
482        break;
483    case ContextMenuItemTagShowColors:
484        frame->editor().showColorPanel();
485        break;
486#endif
487#if USE(APPKIT)
488    case ContextMenuItemTagMakeUpperCase:
489        frame->editor().uppercaseWord();
490        break;
491    case ContextMenuItemTagMakeLowerCase:
492        frame->editor().lowercaseWord();
493        break;
494    case ContextMenuItemTagCapitalize:
495        frame->editor().capitalizeWord();
496        break;
497#endif
498#if PLATFORM(COCOA)
499    case ContextMenuItemTagChangeBack:
500        frame->editor().changeBackToReplacedString(m_context.hitTestResult().replacedString());
501        break;
502#endif
503#if USE(AUTOMATIC_TEXT_REPLACEMENT)
504    case ContextMenuItemTagShowSubstitutions:
505        frame->editor().showSubstitutionsPanel();
506        break;
507    case ContextMenuItemTagSmartCopyPaste:
508        frame->editor().toggleSmartInsertDelete();
509        break;
510    case ContextMenuItemTagSmartQuotes:
511        frame->editor().toggleAutomaticQuoteSubstitution();
512        break;
513    case ContextMenuItemTagSmartDashes:
514        frame->editor().toggleAutomaticDashSubstitution();
515        break;
516    case ContextMenuItemTagSmartLinks:
517        frame->editor().toggleAutomaticLinkDetection();
518        break;
519    case ContextMenuItemTagTextReplacement:
520        frame->editor().toggleAutomaticTextReplacement();
521        break;
522    case ContextMenuItemTagCorrectSpellingAutomatically:
523        frame->editor().toggleAutomaticSpellingCorrection();
524        break;
525#endif
526#if ENABLE(INSPECTOR)
527    case ContextMenuItemTagInspectElement:
528        if (Page* page = frame->page())
529            page->inspectorController().inspect(m_context.hitTestResult().innerNonSharedNode());
530        break;
531#endif
532    case ContextMenuItemTagDictationAlternative:
533        frame->editor().applyDictationAlternativelternative(item->title());
534        break;
535    default:
536        break;
537    }
538}
539
540void ContextMenuController::appendItem(ContextMenuItem& menuItem, ContextMenu* parentMenu)
541{
542    checkOrEnableIfNeeded(menuItem);
543    if (parentMenu)
544        parentMenu->appendItem(menuItem);
545}
546
547void ContextMenuController::createAndAppendFontSubMenu(ContextMenuItem& fontMenuItem)
548{
549    ContextMenu fontMenu;
550
551#if PLATFORM(COCOA)
552    ContextMenuItem showFonts(ActionType, ContextMenuItemTagShowFonts, contextMenuItemTagShowFonts());
553#endif
554    ContextMenuItem bold(CheckableActionType, ContextMenuItemTagBold, contextMenuItemTagBold());
555    ContextMenuItem italic(CheckableActionType, ContextMenuItemTagItalic, contextMenuItemTagItalic());
556    ContextMenuItem underline(CheckableActionType, ContextMenuItemTagUnderline, contextMenuItemTagUnderline());
557    ContextMenuItem outline(ActionType, ContextMenuItemTagOutline, contextMenuItemTagOutline());
558#if PLATFORM(COCOA)
559    ContextMenuItem styles(ActionType, ContextMenuItemTagStyles, contextMenuItemTagStyles());
560    ContextMenuItem showColors(ActionType, ContextMenuItemTagShowColors, contextMenuItemTagShowColors());
561#endif
562
563#if PLATFORM(COCOA)
564    appendItem(showFonts, &fontMenu);
565#endif
566    appendItem(bold, &fontMenu);
567    appendItem(italic, &fontMenu);
568    appendItem(underline, &fontMenu);
569    appendItem(outline, &fontMenu);
570#if PLATFORM(COCOA)
571    appendItem(styles, &fontMenu);
572    appendItem(*separatorItem(), &fontMenu);
573    appendItem(showColors, &fontMenu);
574#endif
575
576    fontMenuItem.setSubMenu(&fontMenu);
577}
578
579
580#if !PLATFORM(GTK)
581
582void ContextMenuController::createAndAppendSpellingAndGrammarSubMenu(ContextMenuItem& spellingAndGrammarMenuItem)
583{
584    ContextMenu spellingAndGrammarMenu;
585
586    ContextMenuItem showSpellingPanel(ActionType, ContextMenuItemTagShowSpellingPanel,
587        contextMenuItemTagShowSpellingPanel(true));
588    ContextMenuItem checkSpelling(ActionType, ContextMenuItemTagCheckSpelling,
589        contextMenuItemTagCheckSpelling());
590    ContextMenuItem checkAsYouType(CheckableActionType, ContextMenuItemTagCheckSpellingWhileTyping,
591        contextMenuItemTagCheckSpellingWhileTyping());
592    ContextMenuItem grammarWithSpelling(CheckableActionType, ContextMenuItemTagCheckGrammarWithSpelling,
593        contextMenuItemTagCheckGrammarWithSpelling());
594#if PLATFORM(COCOA)
595    ContextMenuItem correctSpelling(CheckableActionType, ContextMenuItemTagCorrectSpellingAutomatically,
596        contextMenuItemTagCorrectSpellingAutomatically());
597#endif
598
599    appendItem(showSpellingPanel, &spellingAndGrammarMenu);
600    appendItem(checkSpelling, &spellingAndGrammarMenu);
601#if PLATFORM(COCOA)
602    appendItem(*separatorItem(), &spellingAndGrammarMenu);
603#endif
604    appendItem(checkAsYouType, &spellingAndGrammarMenu);
605    appendItem(grammarWithSpelling, &spellingAndGrammarMenu);
606#if PLATFORM(COCOA)
607    appendItem(correctSpelling, &spellingAndGrammarMenu);
608#endif
609
610    spellingAndGrammarMenuItem.setSubMenu(&spellingAndGrammarMenu);
611}
612
613#endif // !PLATFORM(GTK)
614
615
616#if PLATFORM(COCOA)
617
618void ContextMenuController::createAndAppendSpeechSubMenu(ContextMenuItem& speechMenuItem)
619{
620    ContextMenu speechMenu;
621
622    ContextMenuItem start(ActionType, ContextMenuItemTagStartSpeaking, contextMenuItemTagStartSpeaking());
623    ContextMenuItem stop(ActionType, ContextMenuItemTagStopSpeaking, contextMenuItemTagStopSpeaking());
624
625    appendItem(start, &speechMenu);
626    appendItem(stop, &speechMenu);
627
628    speechMenuItem.setSubMenu(&speechMenu);
629}
630
631#endif
632
633#if PLATFORM(GTK)
634
635void ContextMenuController::createAndAppendUnicodeSubMenu(ContextMenuItem& unicodeMenuItem)
636{
637    ContextMenu unicodeMenu;
638
639    ContextMenuItem leftToRightMarkMenuItem(ActionType, ContextMenuItemTagUnicodeInsertLRMMark, contextMenuItemTagUnicodeInsertLRMMark());
640    ContextMenuItem rightToLeftMarkMenuItem(ActionType, ContextMenuItemTagUnicodeInsertRLMMark, contextMenuItemTagUnicodeInsertRLMMark());
641    ContextMenuItem leftToRightEmbedMenuItem(ActionType, ContextMenuItemTagUnicodeInsertLREMark, contextMenuItemTagUnicodeInsertLREMark());
642    ContextMenuItem rightToLeftEmbedMenuItem(ActionType, ContextMenuItemTagUnicodeInsertRLEMark, contextMenuItemTagUnicodeInsertRLEMark());
643    ContextMenuItem leftToRightOverrideMenuItem(ActionType, ContextMenuItemTagUnicodeInsertLROMark, contextMenuItemTagUnicodeInsertLROMark());
644    ContextMenuItem rightToLeftOverrideMenuItem(ActionType, ContextMenuItemTagUnicodeInsertRLOMark, contextMenuItemTagUnicodeInsertRLOMark());
645    ContextMenuItem popDirectionalFormattingMenuItem(ActionType, ContextMenuItemTagUnicodeInsertPDFMark, contextMenuItemTagUnicodeInsertPDFMark());
646    ContextMenuItem zeroWidthSpaceMenuItem(ActionType, ContextMenuItemTagUnicodeInsertZWSMark, contextMenuItemTagUnicodeInsertZWSMark());
647    ContextMenuItem zeroWidthJoinerMenuItem(ActionType, ContextMenuItemTagUnicodeInsertZWJMark, contextMenuItemTagUnicodeInsertZWJMark());
648    ContextMenuItem zeroWidthNonJoinerMenuItem(ActionType, ContextMenuItemTagUnicodeInsertZWNJMark, contextMenuItemTagUnicodeInsertZWNJMark());
649
650    appendItem(leftToRightMarkMenuItem, &unicodeMenu);
651    appendItem(rightToLeftMarkMenuItem, &unicodeMenu);
652    appendItem(leftToRightEmbedMenuItem, &unicodeMenu);
653    appendItem(rightToLeftEmbedMenuItem, &unicodeMenu);
654    appendItem(leftToRightOverrideMenuItem, &unicodeMenu);
655    appendItem(rightToLeftOverrideMenuItem, &unicodeMenu);
656    appendItem(popDirectionalFormattingMenuItem, &unicodeMenu);
657    appendItem(zeroWidthSpaceMenuItem, &unicodeMenu);
658    appendItem(zeroWidthJoinerMenuItem, &unicodeMenu);
659    appendItem(zeroWidthNonJoinerMenuItem, &unicodeMenu);
660
661    unicodeMenuItem.setSubMenu(&unicodeMenu);
662}
663
664#else
665
666void ContextMenuController::createAndAppendWritingDirectionSubMenu(ContextMenuItem& writingDirectionMenuItem)
667{
668    ContextMenu writingDirectionMenu;
669
670    ContextMenuItem defaultItem(ActionType, ContextMenuItemTagDefaultDirection,
671        contextMenuItemTagDefaultDirection());
672    ContextMenuItem ltr(CheckableActionType, ContextMenuItemTagLeftToRight, contextMenuItemTagLeftToRight());
673    ContextMenuItem rtl(CheckableActionType, ContextMenuItemTagRightToLeft, contextMenuItemTagRightToLeft());
674
675    appendItem(defaultItem, &writingDirectionMenu);
676    appendItem(ltr, &writingDirectionMenu);
677    appendItem(rtl, &writingDirectionMenu);
678
679    writingDirectionMenuItem.setSubMenu(&writingDirectionMenu);
680}
681
682void ContextMenuController::createAndAppendTextDirectionSubMenu(ContextMenuItem& textDirectionMenuItem)
683{
684    ContextMenu textDirectionMenu;
685
686    ContextMenuItem defaultItem(ActionType, ContextMenuItemTagTextDirectionDefault, contextMenuItemTagDefaultDirection());
687    ContextMenuItem ltr(CheckableActionType, ContextMenuItemTagTextDirectionLeftToRight, contextMenuItemTagLeftToRight());
688    ContextMenuItem rtl(CheckableActionType, ContextMenuItemTagTextDirectionRightToLeft, contextMenuItemTagRightToLeft());
689
690    appendItem(defaultItem, &textDirectionMenu);
691    appendItem(ltr, &textDirectionMenu);
692    appendItem(rtl, &textDirectionMenu);
693
694    textDirectionMenuItem.setSubMenu(&textDirectionMenu);
695}
696
697#endif
698
699#if PLATFORM(COCOA)
700
701void ContextMenuController::createAndAppendSubstitutionsSubMenu(ContextMenuItem& substitutionsMenuItem)
702{
703    ContextMenu substitutionsMenu;
704
705    ContextMenuItem showSubstitutions(ActionType, ContextMenuItemTagShowSubstitutions, contextMenuItemTagShowSubstitutions(true));
706    ContextMenuItem smartCopyPaste(CheckableActionType, ContextMenuItemTagSmartCopyPaste, contextMenuItemTagSmartCopyPaste());
707    ContextMenuItem smartQuotes(CheckableActionType, ContextMenuItemTagSmartQuotes, contextMenuItemTagSmartQuotes());
708    ContextMenuItem smartDashes(CheckableActionType, ContextMenuItemTagSmartDashes, contextMenuItemTagSmartDashes());
709    ContextMenuItem smartLinks(CheckableActionType, ContextMenuItemTagSmartLinks, contextMenuItemTagSmartLinks());
710    ContextMenuItem textReplacement(CheckableActionType, ContextMenuItemTagTextReplacement, contextMenuItemTagTextReplacement());
711
712    appendItem(showSubstitutions, &substitutionsMenu);
713    appendItem(*separatorItem(), &substitutionsMenu);
714    appendItem(smartCopyPaste, &substitutionsMenu);
715    appendItem(smartQuotes, &substitutionsMenu);
716    appendItem(smartDashes, &substitutionsMenu);
717    appendItem(smartLinks, &substitutionsMenu);
718    appendItem(textReplacement, &substitutionsMenu);
719
720    substitutionsMenuItem.setSubMenu(&substitutionsMenu);
721}
722
723void ContextMenuController::createAndAppendTransformationsSubMenu(ContextMenuItem& transformationsMenuItem)
724{
725    ContextMenu transformationsMenu;
726
727    ContextMenuItem makeUpperCase(ActionType, ContextMenuItemTagMakeUpperCase, contextMenuItemTagMakeUpperCase());
728    ContextMenuItem makeLowerCase(ActionType, ContextMenuItemTagMakeLowerCase, contextMenuItemTagMakeLowerCase());
729    ContextMenuItem capitalize(ActionType, ContextMenuItemTagCapitalize, contextMenuItemTagCapitalize());
730
731    appendItem(makeUpperCase, &transformationsMenu);
732    appendItem(makeLowerCase, &transformationsMenu);
733    appendItem(capitalize, &transformationsMenu);
734
735    transformationsMenuItem.setSubMenu(&transformationsMenu);
736}
737
738#endif
739
740static bool selectionContainsPossibleWord(Frame* frame)
741{
742    // Current algorithm: look for a character that's not just a separator.
743    for (TextIterator it(frame->selection().toNormalizedRange().get()); !it.atEnd(); it.advance()) {
744        int length = it.text().length();
745        for (int i = 0; i < length; ++i) {
746            if (!(U_GET_GC_MASK(it.text()[i]) & U_GC_Z_MASK))
747                return true;
748        }
749    }
750    return false;
751}
752
753#if PLATFORM(COCOA)
754#define SUPPORTS_TOGGLE_VIDEO_FULLSCREEN 1
755#else
756#define SUPPORTS_TOGGLE_VIDEO_FULLSCREEN 0
757#endif
758
759#if PLATFORM(COCOA)
760#define SUPPORTS_TOGGLE_SHOW_HIDE_MEDIA_CONTROLS 1
761#else
762#define SUPPORTS_TOGGLE_SHOW_HIDE_MEDIA_CONTROLS 0
763#endif
764
765void ContextMenuController::populate()
766{
767    ContextMenuItem OpenLinkItem(ActionType, ContextMenuItemTagOpenLink, contextMenuItemTagOpenLink());
768    ContextMenuItem OpenLinkInNewWindowItem(ActionType, ContextMenuItemTagOpenLinkInNewWindow,
769        contextMenuItemTagOpenLinkInNewWindow());
770    ContextMenuItem DownloadFileItem(ActionType, ContextMenuItemTagDownloadLinkToDisk,
771        contextMenuItemTagDownloadLinkToDisk());
772    ContextMenuItem CopyLinkItem(ActionType, ContextMenuItemTagCopyLinkToClipboard,
773        contextMenuItemTagCopyLinkToClipboard());
774    ContextMenuItem OpenImageInNewWindowItem(ActionType, ContextMenuItemTagOpenImageInNewWindow,
775        contextMenuItemTagOpenImageInNewWindow());
776    ContextMenuItem DownloadImageItem(ActionType, ContextMenuItemTagDownloadImageToDisk,
777        contextMenuItemTagDownloadImageToDisk());
778    ContextMenuItem CopyImageItem(ActionType, ContextMenuItemTagCopyImageToClipboard,
779        contextMenuItemTagCopyImageToClipboard());
780#if PLATFORM(GTK) || PLATFORM(EFL)
781    ContextMenuItem CopyImageUrlItem(ActionType, ContextMenuItemTagCopyImageUrlToClipboard,
782        contextMenuItemTagCopyImageUrlToClipboard());
783#endif
784    ContextMenuItem OpenMediaInNewWindowItem(ActionType, ContextMenuItemTagOpenMediaInNewWindow, String());
785    ContextMenuItem DownloadMediaItem(ActionType, ContextMenuItemTagDownloadMediaToDisk, String());
786    ContextMenuItem CopyMediaLinkItem(ActionType, ContextMenuItemTagCopyMediaLinkToClipboard, String());
787    ContextMenuItem MediaPlayPause(ActionType, ContextMenuItemTagMediaPlayPause,
788        contextMenuItemTagMediaPlay());
789    ContextMenuItem MediaMute(ActionType, ContextMenuItemTagMediaMute,
790        contextMenuItemTagMediaMute());
791#if SUPPORTS_TOGGLE_SHOW_HIDE_MEDIA_CONTROLS
792    ContextMenuItem ToggleMediaControls(ActionType, ContextMenuItemTagToggleMediaControls,
793        contextMenuItemTagHideMediaControls());
794#else
795    ContextMenuItem ToggleMediaControls(CheckableActionType, ContextMenuItemTagToggleMediaControls,
796        contextMenuItemTagToggleMediaControls());
797#endif
798    ContextMenuItem ToggleMediaLoop(CheckableActionType, ContextMenuItemTagToggleMediaLoop,
799        contextMenuItemTagToggleMediaLoop());
800    ContextMenuItem EnterVideoFullscreen(ActionType, ContextMenuItemTagEnterVideoFullscreen,
801        contextMenuItemTagEnterVideoFullscreen());
802    ContextMenuItem ToggleVideoFullscreen(ActionType, ContextMenuItemTagToggleVideoFullscreen,
803        contextMenuItemTagEnterVideoFullscreen());
804#if PLATFORM(COCOA)
805    ContextMenuItem SearchSpotlightItem(ActionType, ContextMenuItemTagSearchInSpotlight,
806        contextMenuItemTagSearchInSpotlight());
807#endif
808#if !PLATFORM(GTK)
809    ContextMenuItem SearchWebItem(ActionType, ContextMenuItemTagSearchWeb, contextMenuItemTagSearchWeb());
810#endif
811    ContextMenuItem CopyItem(ActionType, ContextMenuItemTagCopy, contextMenuItemTagCopy());
812    ContextMenuItem BackItem(ActionType, ContextMenuItemTagGoBack, contextMenuItemTagGoBack());
813    ContextMenuItem ForwardItem(ActionType, ContextMenuItemTagGoForward,  contextMenuItemTagGoForward());
814    ContextMenuItem StopItem(ActionType, ContextMenuItemTagStop, contextMenuItemTagStop());
815    ContextMenuItem ReloadItem(ActionType, ContextMenuItemTagReload, contextMenuItemTagReload());
816    ContextMenuItem OpenFrameItem(ActionType, ContextMenuItemTagOpenFrameInNewWindow,
817        contextMenuItemTagOpenFrameInNewWindow());
818    ContextMenuItem NoGuessesItem(ActionType, ContextMenuItemTagNoGuessesFound,
819        contextMenuItemTagNoGuessesFound());
820    ContextMenuItem IgnoreSpellingItem(ActionType, ContextMenuItemTagIgnoreSpelling,
821        contextMenuItemTagIgnoreSpelling());
822    ContextMenuItem LearnSpellingItem(ActionType, ContextMenuItemTagLearnSpelling,
823        contextMenuItemTagLearnSpelling());
824    ContextMenuItem IgnoreGrammarItem(ActionType, ContextMenuItemTagIgnoreGrammar,
825        contextMenuItemTagIgnoreGrammar());
826    ContextMenuItem CutItem(ActionType, ContextMenuItemTagCut, contextMenuItemTagCut());
827    ContextMenuItem PasteItem(ActionType, ContextMenuItemTagPaste, contextMenuItemTagPaste());
828#if PLATFORM(GTK)
829    ContextMenuItem DeleteItem(ActionType, ContextMenuItemTagDelete, contextMenuItemTagDelete());
830#endif
831#if PLATFORM(GTK) || PLATFORM(EFL)
832    ContextMenuItem SelectAllItem(ActionType, ContextMenuItemTagSelectAll, contextMenuItemTagSelectAll());
833#endif
834
835    Node* node = m_context.hitTestResult().innerNonSharedNode();
836    if (!node)
837        return;
838#if PLATFORM(GTK)
839    if (!m_context.hitTestResult().isContentEditable() && (node->isElementNode() && toElement(node)->isFormControlElement()))
840        return;
841#endif
842    Frame* frame = node->document().frame();
843    if (!frame)
844        return;
845
846#if ENABLE(SERVICE_CONTROLS)
847    // The default image control menu gets populated solely by the platform.
848    if (m_context.controlledImage())
849        return;
850#endif
851
852    if (!m_context.hitTestResult().isContentEditable()) {
853        FrameLoader& loader = frame->loader();
854        URL linkURL = m_context.hitTestResult().absoluteLinkURL();
855        if (!linkURL.isEmpty()) {
856            if (loader.client().canHandleRequest(ResourceRequest(linkURL))) {
857                appendItem(OpenLinkItem, m_contextMenu.get());
858                appendItem(OpenLinkInNewWindowItem, m_contextMenu.get());
859                appendItem(DownloadFileItem, m_contextMenu.get());
860            }
861            appendItem(CopyLinkItem, m_contextMenu.get());
862        }
863
864        URL imageURL = m_context.hitTestResult().absoluteImageURL();
865        if (!imageURL.isEmpty()) {
866            if (!linkURL.isEmpty())
867                appendItem(*separatorItem(), m_contextMenu.get());
868
869            appendItem(OpenImageInNewWindowItem, m_contextMenu.get());
870            appendItem(DownloadImageItem, m_contextMenu.get());
871            if (imageURL.isLocalFile() || m_context.hitTestResult().image())
872                appendItem(CopyImageItem, m_contextMenu.get());
873#if PLATFORM(GTK) || PLATFORM(EFL)
874            appendItem(CopyImageUrlItem, m_contextMenu.get());
875#endif
876        }
877
878        URL mediaURL = m_context.hitTestResult().absoluteMediaURL();
879        if (!mediaURL.isEmpty()) {
880            if (!linkURL.isEmpty() || !imageURL.isEmpty())
881                appendItem(*separatorItem(), m_contextMenu.get());
882
883            appendItem(MediaPlayPause, m_contextMenu.get());
884            appendItem(MediaMute, m_contextMenu.get());
885            appendItem(ToggleMediaControls, m_contextMenu.get());
886            appendItem(ToggleMediaLoop, m_contextMenu.get());
887#if SUPPORTS_TOGGLE_VIDEO_FULLSCREEN
888            appendItem(ToggleVideoFullscreen, m_contextMenu.get());
889#else
890            appendItem(EnterVideoFullscreen, m_contextMenu.get());
891#endif
892            appendItem(*separatorItem(), m_contextMenu.get());
893            appendItem(CopyMediaLinkItem, m_contextMenu.get());
894            appendItem(OpenMediaInNewWindowItem, m_contextMenu.get());
895            if (loader.client().canHandleRequest(ResourceRequest(mediaURL)))
896                appendItem(DownloadMediaItem, m_contextMenu.get());
897        }
898
899        if (imageURL.isEmpty() && linkURL.isEmpty() && mediaURL.isEmpty()) {
900            if (m_context.hitTestResult().isSelected()) {
901                if (selectionContainsPossibleWord(frame)) {
902#if PLATFORM(COCOA)
903                    String selectedString = frame->displayStringModifiedByEncoding(frame->editor().selectedText());
904                    ContextMenuItem LookUpInDictionaryItem(ActionType, ContextMenuItemTagLookUpInDictionary, contextMenuItemTagLookUpInDictionary(selectedString));
905
906                    appendItem(LookUpInDictionaryItem, m_contextMenu.get());
907#endif
908
909#if !PLATFORM(GTK)
910                    appendItem(SearchWebItem, m_contextMenu.get());
911                    appendItem(*separatorItem(), m_contextMenu.get());
912#endif
913                }
914
915                appendItem(CopyItem, m_contextMenu.get());
916#if PLATFORM(COCOA)
917                appendItem(*separatorItem(), m_contextMenu.get());
918
919                ContextMenuItem SpeechMenuItem(SubmenuType, ContextMenuItemTagSpeechMenu, contextMenuItemTagSpeechMenu());
920                createAndAppendSpeechSubMenu(SpeechMenuItem);
921                appendItem(SpeechMenuItem, m_contextMenu.get());
922#endif
923            } else {
924#if ENABLE(INSPECTOR)
925                if (!(frame->page() && (frame->page()->inspectorController().hasInspectorFrontendClient() || frame->page()->inspectorController().hasRemoteFrontend()))) {
926#endif
927
928                // In GTK+ unavailable items are not hidden but insensitive.
929#if PLATFORM(GTK)
930                appendItem(BackItem, m_contextMenu.get());
931                appendItem(ForwardItem, m_contextMenu.get());
932                appendItem(StopItem, m_contextMenu.get());
933                appendItem(ReloadItem, m_contextMenu.get());
934#else
935                if (frame->page() && frame->page()->backForward().canGoBackOrForward(-1))
936                    appendItem(BackItem, m_contextMenu.get());
937
938                if (frame->page() && frame->page()->backForward().canGoBackOrForward(1))
939                    appendItem(ForwardItem, m_contextMenu.get());
940
941                // use isLoadingInAPISense rather than isLoading because Stop/Reload are
942                // intended to match WebKit's API, not WebCore's internal notion of loading status
943                if (loader.documentLoader()->isLoadingInAPISense())
944                    appendItem(StopItem, m_contextMenu.get());
945                else
946                    appendItem(ReloadItem, m_contextMenu.get());
947#endif
948#if ENABLE(INSPECTOR)
949                }
950#endif
951
952                if (frame->page() && !frame->isMainFrame())
953                    appendItem(OpenFrameItem, m_contextMenu.get());
954            }
955        }
956    } else { // Make an editing context menu
957        bool inPasswordField = frame->selection().selection().isInPasswordField();
958        if (!inPasswordField) {
959            bool haveContextMenuItemsForMisspellingOrGrammer = false;
960            bool spellCheckingEnabled = frame->editor().isSpellCheckingEnabledFor(node);
961            if (spellCheckingEnabled) {
962                // Consider adding spelling-related or grammar-related context menu items (never both, since a single selected range
963                // is never considered a misspelling and bad grammar at the same time)
964                bool misspelling;
965                bool badGrammar;
966                Vector<String> guesses = frame->editor().guessesForMisspelledOrUngrammatical(misspelling, badGrammar);
967                if (misspelling || badGrammar) {
968                    size_t size = guesses.size();
969                    if (!size) {
970                        // If there's bad grammar but no suggestions (e.g., repeated word), just leave off the suggestions
971                        // list and trailing separator rather than adding a "No Guesses Found" item (matches AppKit)
972                        if (misspelling) {
973                            appendItem(NoGuessesItem, m_contextMenu.get());
974                            appendItem(*separatorItem(), m_contextMenu.get());
975                        }
976                    } else {
977                        for (unsigned i = 0; i < size; i++) {
978                            const String &guess = guesses[i];
979                            if (!guess.isEmpty()) {
980                                ContextMenuItem item(ActionType, ContextMenuItemTagSpellingGuess, guess);
981                                appendItem(item, m_contextMenu.get());
982                            }
983                        }
984                        appendItem(*separatorItem(), m_contextMenu.get());
985                    }
986                    if (misspelling) {
987                        appendItem(IgnoreSpellingItem, m_contextMenu.get());
988                        appendItem(LearnSpellingItem, m_contextMenu.get());
989                    } else
990                        appendItem(IgnoreGrammarItem, m_contextMenu.get());
991                    appendItem(*separatorItem(), m_contextMenu.get());
992                    haveContextMenuItemsForMisspellingOrGrammer = true;
993#if PLATFORM(COCOA)
994                } else {
995                    // If the string was autocorrected, generate a contextual menu item allowing it to be changed back.
996                    String replacedString = m_context.hitTestResult().replacedString();
997                    if (!replacedString.isEmpty()) {
998                        ContextMenuItem item(ActionType, ContextMenuItemTagChangeBack, contextMenuItemTagChangeBack(replacedString));
999                        appendItem(item, m_contextMenu.get());
1000                        appendItem(*separatorItem(), m_contextMenu.get());
1001                        haveContextMenuItemsForMisspellingOrGrammer = true;
1002                    }
1003#endif
1004                }
1005            }
1006
1007            if (!haveContextMenuItemsForMisspellingOrGrammer) {
1008                // Spelling and grammar checking is mutually exclusive with dictation alternatives.
1009                Vector<String> dictationAlternatives = m_context.hitTestResult().dictationAlternatives();
1010                if (!dictationAlternatives.isEmpty()) {
1011                    for (size_t i = 0; i < dictationAlternatives.size(); ++i) {
1012                        ContextMenuItem item(ActionType, ContextMenuItemTagDictationAlternative, dictationAlternatives[i]);
1013                        appendItem(item, m_contextMenu.get());
1014                    }
1015                    appendItem(*separatorItem(), m_contextMenu.get());
1016                }
1017            }
1018        }
1019
1020        FrameLoader& loader = frame->loader();
1021        URL linkURL = m_context.hitTestResult().absoluteLinkURL();
1022        if (!linkURL.isEmpty()) {
1023            if (loader.client().canHandleRequest(ResourceRequest(linkURL))) {
1024                appendItem(OpenLinkItem, m_contextMenu.get());
1025                appendItem(OpenLinkInNewWindowItem, m_contextMenu.get());
1026                appendItem(DownloadFileItem, m_contextMenu.get());
1027            }
1028            appendItem(CopyLinkItem, m_contextMenu.get());
1029            appendItem(*separatorItem(), m_contextMenu.get());
1030        }
1031
1032        if (m_context.hitTestResult().isSelected() && !inPasswordField && selectionContainsPossibleWord(frame)) {
1033#if PLATFORM(COCOA)
1034            String selectedString = frame->displayStringModifiedByEncoding(frame->editor().selectedText());
1035            ContextMenuItem LookUpInDictionaryItem(ActionType, ContextMenuItemTagLookUpInDictionary, contextMenuItemTagLookUpInDictionary(selectedString));
1036
1037            appendItem(LookUpInDictionaryItem, m_contextMenu.get());
1038#endif
1039
1040#if !PLATFORM(GTK)
1041            appendItem(SearchWebItem, m_contextMenu.get());
1042            appendItem(*separatorItem(), m_contextMenu.get());
1043#endif
1044        }
1045
1046        appendItem(CutItem, m_contextMenu.get());
1047        appendItem(CopyItem, m_contextMenu.get());
1048        appendItem(PasteItem, m_contextMenu.get());
1049#if PLATFORM(GTK)
1050        appendItem(DeleteItem, m_contextMenu.get());
1051        appendItem(*separatorItem(), m_contextMenu.get());
1052#endif
1053#if PLATFORM(GTK) || PLATFORM(EFL)
1054        appendItem(SelectAllItem, m_contextMenu.get());
1055#endif
1056
1057        if (!inPasswordField) {
1058#if !PLATFORM(GTK)
1059            appendItem(*separatorItem(), m_contextMenu.get());
1060            ContextMenuItem SpellingAndGrammarMenuItem(SubmenuType, ContextMenuItemTagSpellingMenu,
1061                contextMenuItemTagSpellingMenu());
1062            createAndAppendSpellingAndGrammarSubMenu(SpellingAndGrammarMenuItem);
1063            appendItem(SpellingAndGrammarMenuItem, m_contextMenu.get());
1064#endif
1065#if PLATFORM(COCOA)
1066            ContextMenuItem substitutionsMenuItem(SubmenuType, ContextMenuItemTagSubstitutionsMenu,
1067                contextMenuItemTagSubstitutionsMenu());
1068            createAndAppendSubstitutionsSubMenu(substitutionsMenuItem);
1069            appendItem(substitutionsMenuItem, m_contextMenu.get());
1070            ContextMenuItem transformationsMenuItem(SubmenuType, ContextMenuItemTagTransformationsMenu,
1071                contextMenuItemTagTransformationsMenu());
1072            createAndAppendTransformationsSubMenu(transformationsMenuItem);
1073            appendItem(transformationsMenuItem, m_contextMenu.get());
1074#endif
1075#if PLATFORM(GTK)
1076            bool shouldShowFontMenu = frame->editor().canEditRichly();
1077#else
1078            bool shouldShowFontMenu = true;
1079#endif
1080            if (shouldShowFontMenu) {
1081                ContextMenuItem FontMenuItem(SubmenuType, ContextMenuItemTagFontMenu,
1082                    contextMenuItemTagFontMenu());
1083                createAndAppendFontSubMenu(FontMenuItem);
1084                appendItem(FontMenuItem, m_contextMenu.get());
1085            }
1086#if PLATFORM(COCOA)
1087            ContextMenuItem SpeechMenuItem(SubmenuType, ContextMenuItemTagSpeechMenu, contextMenuItemTagSpeechMenu());
1088            createAndAppendSpeechSubMenu(SpeechMenuItem);
1089            appendItem(SpeechMenuItem, m_contextMenu.get());
1090#endif
1091#if PLATFORM(GTK)
1092            EditorClient* client = frame->editor().client();
1093            if (client && client->shouldShowUnicodeMenu()) {
1094                ContextMenuItem UnicodeMenuItem(SubmenuType, ContextMenuItemTagUnicode, contextMenuItemTagUnicode());
1095                createAndAppendUnicodeSubMenu(UnicodeMenuItem);
1096                appendItem(*separatorItem(), m_contextMenu.get());
1097                appendItem(UnicodeMenuItem, m_contextMenu.get());
1098            }
1099#else
1100            ContextMenuItem WritingDirectionMenuItem(SubmenuType, ContextMenuItemTagWritingDirectionMenu,
1101                contextMenuItemTagWritingDirectionMenu());
1102            createAndAppendWritingDirectionSubMenu(WritingDirectionMenuItem);
1103            appendItem(WritingDirectionMenuItem, m_contextMenu.get());
1104            if (Page* page = frame->page()) {
1105                bool includeTextDirectionSubmenu = page->settings().textDirectionSubmenuInclusionBehavior() == TextDirectionSubmenuAlwaysIncluded
1106                    || (page->settings().textDirectionSubmenuInclusionBehavior() == TextDirectionSubmenuAutomaticallyIncluded && frame->editor().hasBidiSelection());
1107                if (includeTextDirectionSubmenu) {
1108                    ContextMenuItem TextDirectionMenuItem(SubmenuType, ContextMenuItemTagTextDirectionMenu, contextMenuItemTagTextDirectionMenu());
1109                    createAndAppendTextDirectionSubMenu(TextDirectionMenuItem);
1110                    appendItem(TextDirectionMenuItem, m_contextMenu.get());
1111                }
1112            }
1113#endif
1114        }
1115    }
1116}
1117
1118#if ENABLE(INSPECTOR)
1119void ContextMenuController::addInspectElementItem()
1120{
1121    Node* node = m_context.hitTestResult().innerNonSharedNode();
1122    if (!node)
1123        return;
1124
1125    Frame* frame = node->document().frame();
1126    if (!frame)
1127        return;
1128
1129    Page* page = frame->page();
1130    if (!page)
1131        return;
1132
1133    ContextMenuItem InspectElementItem(ActionType, ContextMenuItemTagInspectElement, contextMenuItemTagInspectElement());
1134#if USE(CROSS_PLATFORM_CONTEXT_MENUS)
1135    if (m_contextMenu && !m_contextMenu->items().isEmpty())
1136#else
1137    if (m_contextMenu && m_contextMenu->itemCount())
1138#endif
1139        appendItem(*separatorItem(), m_contextMenu.get());
1140    appendItem(InspectElementItem, m_contextMenu.get());
1141}
1142#endif // ENABLE(INSPECTOR)
1143
1144void ContextMenuController::checkOrEnableIfNeeded(ContextMenuItem& item) const
1145{
1146    if (item.type() == SeparatorType)
1147        return;
1148
1149    Frame* frame = m_context.hitTestResult().innerNonSharedNode()->document().frame();
1150    if (!frame)
1151        return;
1152
1153    // Custom items already have proper checked and enabled values.
1154    if (ContextMenuItemBaseCustomTag <= item.action() && item.action() <= ContextMenuItemLastCustomTag)
1155        return;
1156
1157    bool shouldEnable = true;
1158    bool shouldCheck = false;
1159
1160    switch (item.action()) {
1161        case ContextMenuItemTagCheckSpelling:
1162            shouldEnable = frame->editor().canEdit();
1163            break;
1164        case ContextMenuItemTagDefaultDirection:
1165            shouldCheck = false;
1166            shouldEnable = false;
1167            break;
1168        case ContextMenuItemTagLeftToRight:
1169        case ContextMenuItemTagRightToLeft: {
1170            String direction = item.action() == ContextMenuItemTagLeftToRight ? "ltr" : "rtl";
1171            shouldCheck = frame->editor().selectionHasStyle(CSSPropertyDirection, direction) != FalseTriState;
1172            shouldEnable = true;
1173            break;
1174        }
1175        case ContextMenuItemTagTextDirectionDefault: {
1176            Editor::Command command = frame->editor().command("MakeTextWritingDirectionNatural");
1177            shouldCheck = command.state() == TrueTriState;
1178            shouldEnable = command.isEnabled();
1179            break;
1180        }
1181        case ContextMenuItemTagTextDirectionLeftToRight: {
1182            Editor::Command command = frame->editor().command("MakeTextWritingDirectionLeftToRight");
1183            shouldCheck = command.state() == TrueTriState;
1184            shouldEnable = command.isEnabled();
1185            break;
1186        }
1187        case ContextMenuItemTagTextDirectionRightToLeft: {
1188            Editor::Command command = frame->editor().command("MakeTextWritingDirectionRightToLeft");
1189            shouldCheck = command.state() == TrueTriState;
1190            shouldEnable = command.isEnabled();
1191            break;
1192        }
1193        case ContextMenuItemTagCopy:
1194            shouldEnable = frame->editor().canDHTMLCopy() || frame->editor().canCopy();
1195            break;
1196        case ContextMenuItemTagCut:
1197            shouldEnable = frame->editor().canDHTMLCut() || frame->editor().canCut();
1198            break;
1199        case ContextMenuItemTagIgnoreSpelling:
1200        case ContextMenuItemTagLearnSpelling:
1201            shouldEnable = frame->selection().isRange();
1202            break;
1203        case ContextMenuItemTagPaste:
1204            shouldEnable = frame->editor().canDHTMLPaste() || frame->editor().canPaste();
1205            break;
1206#if PLATFORM(GTK)
1207        case ContextMenuItemTagDelete:
1208            shouldEnable = frame->editor().canDelete();
1209            break;
1210        case ContextMenuItemTagInputMethods:
1211        case ContextMenuItemTagUnicode:
1212        case ContextMenuItemTagUnicodeInsertLRMMark:
1213        case ContextMenuItemTagUnicodeInsertRLMMark:
1214        case ContextMenuItemTagUnicodeInsertLREMark:
1215        case ContextMenuItemTagUnicodeInsertRLEMark:
1216        case ContextMenuItemTagUnicodeInsertLROMark:
1217        case ContextMenuItemTagUnicodeInsertRLOMark:
1218        case ContextMenuItemTagUnicodeInsertPDFMark:
1219        case ContextMenuItemTagUnicodeInsertZWSMark:
1220        case ContextMenuItemTagUnicodeInsertZWJMark:
1221        case ContextMenuItemTagUnicodeInsertZWNJMark:
1222            shouldEnable = true;
1223            break;
1224#endif
1225#if PLATFORM(GTK) || PLATFORM(EFL)
1226        case ContextMenuItemTagSelectAll:
1227            shouldEnable = true;
1228            break;
1229#endif
1230        case ContextMenuItemTagUnderline: {
1231            shouldCheck = frame->editor().selectionHasStyle(CSSPropertyWebkitTextDecorationsInEffect, "underline") != FalseTriState;
1232            shouldEnable = frame->editor().canEditRichly();
1233            break;
1234        }
1235        case ContextMenuItemTagLookUpInDictionary:
1236            shouldEnable = frame->selection().isRange();
1237            break;
1238        case ContextMenuItemTagCheckGrammarWithSpelling:
1239            if (frame->editor().isGrammarCheckingEnabled())
1240                shouldCheck = true;
1241            shouldEnable = true;
1242            break;
1243        case ContextMenuItemTagItalic: {
1244            shouldCheck = frame->editor().selectionHasStyle(CSSPropertyFontStyle, "italic") != FalseTriState;
1245            shouldEnable = frame->editor().canEditRichly();
1246            break;
1247        }
1248        case ContextMenuItemTagBold: {
1249            shouldCheck = frame->editor().selectionHasStyle(CSSPropertyFontWeight, "bold") != FalseTriState;
1250            shouldEnable = frame->editor().canEditRichly();
1251            break;
1252        }
1253        case ContextMenuItemTagOutline:
1254            shouldEnable = false;
1255            break;
1256        case ContextMenuItemTagShowSpellingPanel:
1257            if (frame->editor().spellingPanelIsShowing())
1258                item.setTitle(contextMenuItemTagShowSpellingPanel(false));
1259            else
1260                item.setTitle(contextMenuItemTagShowSpellingPanel(true));
1261            shouldEnable = frame->editor().canEdit();
1262            break;
1263        case ContextMenuItemTagNoGuessesFound:
1264            shouldEnable = false;
1265            break;
1266        case ContextMenuItemTagCheckSpellingWhileTyping:
1267            shouldCheck = frame->editor().isContinuousSpellCheckingEnabled();
1268            break;
1269#if PLATFORM(COCOA)
1270        case ContextMenuItemTagSubstitutionsMenu:
1271        case ContextMenuItemTagTransformationsMenu:
1272            break;
1273        case ContextMenuItemTagShowSubstitutions:
1274            if (frame->editor().substitutionsPanelIsShowing())
1275                item.setTitle(contextMenuItemTagShowSubstitutions(false));
1276            else
1277                item.setTitle(contextMenuItemTagShowSubstitutions(true));
1278            shouldEnable = frame->editor().canEdit();
1279            break;
1280        case ContextMenuItemTagMakeUpperCase:
1281        case ContextMenuItemTagMakeLowerCase:
1282        case ContextMenuItemTagCapitalize:
1283        case ContextMenuItemTagChangeBack:
1284            shouldEnable = frame->editor().canEdit();
1285            break;
1286        case ContextMenuItemTagCorrectSpellingAutomatically:
1287            shouldCheck = frame->editor().isAutomaticSpellingCorrectionEnabled();
1288            break;
1289        case ContextMenuItemTagSmartCopyPaste:
1290            shouldCheck = frame->editor().smartInsertDeleteEnabled();
1291            break;
1292        case ContextMenuItemTagSmartQuotes:
1293            shouldCheck = frame->editor().isAutomaticQuoteSubstitutionEnabled();
1294            break;
1295        case ContextMenuItemTagSmartDashes:
1296            shouldCheck = frame->editor().isAutomaticDashSubstitutionEnabled();
1297            break;
1298        case ContextMenuItemTagSmartLinks:
1299            shouldCheck = frame->editor().isAutomaticLinkDetectionEnabled();
1300            break;
1301        case ContextMenuItemTagTextReplacement:
1302            shouldCheck = frame->editor().isAutomaticTextReplacementEnabled();
1303            break;
1304        case ContextMenuItemTagStopSpeaking:
1305            shouldEnable = m_client.isSpeaking();
1306            break;
1307#else // PLATFORM(COCOA) ends here
1308        case ContextMenuItemTagStopSpeaking:
1309            break;
1310#endif
1311#if PLATFORM(GTK)
1312        case ContextMenuItemTagGoBack:
1313            shouldEnable = frame->page() && frame->page()->backForward().canGoBackOrForward(-1);
1314            break;
1315        case ContextMenuItemTagGoForward:
1316            shouldEnable = frame->page() && frame->page()->backForward().canGoBackOrForward(1);
1317            break;
1318        case ContextMenuItemTagStop:
1319            shouldEnable = frame->loader().documentLoader()->isLoadingInAPISense();
1320            break;
1321        case ContextMenuItemTagReload:
1322            shouldEnable = !frame->loader().documentLoader()->isLoadingInAPISense();
1323            break;
1324        case ContextMenuItemTagFontMenu:
1325            shouldEnable = frame->editor().canEditRichly();
1326            break;
1327#else
1328        case ContextMenuItemTagGoBack:
1329        case ContextMenuItemTagGoForward:
1330        case ContextMenuItemTagStop:
1331        case ContextMenuItemTagReload:
1332        case ContextMenuItemTagFontMenu:
1333#endif
1334        case ContextMenuItemTagNoAction:
1335        case ContextMenuItemTagOpenLinkInNewWindow:
1336        case ContextMenuItemTagOpenLinkInThisWindow:
1337        case ContextMenuItemTagDownloadLinkToDisk:
1338        case ContextMenuItemTagCopyLinkToClipboard:
1339        case ContextMenuItemTagOpenImageInNewWindow:
1340        case ContextMenuItemTagDownloadImageToDisk:
1341        case ContextMenuItemTagCopyImageToClipboard:
1342#if PLATFORM(GTK) || PLATFORM(EFL)
1343        case ContextMenuItemTagCopyImageUrlToClipboard:
1344#endif
1345            break;
1346        case ContextMenuItemTagOpenMediaInNewWindow:
1347            if (m_context.hitTestResult().mediaIsVideo())
1348                item.setTitle(contextMenuItemTagOpenVideoInNewWindow());
1349            else
1350                item.setTitle(contextMenuItemTagOpenAudioInNewWindow());
1351            break;
1352        case ContextMenuItemTagDownloadMediaToDisk:
1353            if (m_context.hitTestResult().mediaIsVideo())
1354                item.setTitle(contextMenuItemTagDownloadVideoToDisk());
1355            else
1356                item.setTitle(contextMenuItemTagDownloadAudioToDisk());
1357            break;
1358        case ContextMenuItemTagCopyMediaLinkToClipboard:
1359            if (m_context.hitTestResult().mediaIsVideo())
1360                item.setTitle(contextMenuItemTagCopyVideoLinkToClipboard());
1361            else
1362                item.setTitle(contextMenuItemTagCopyAudioLinkToClipboard());
1363            break;
1364        case ContextMenuItemTagToggleMediaControls:
1365#if SUPPORTS_TOGGLE_SHOW_HIDE_MEDIA_CONTROLS
1366            item.setTitle(m_context.hitTestResult().mediaControlsEnabled() ? contextMenuItemTagHideMediaControls() : contextMenuItemTagShowMediaControls());
1367#else
1368            shouldCheck = m_context.hitTestResult().mediaControlsEnabled();
1369#endif
1370            break;
1371        case ContextMenuItemTagToggleMediaLoop:
1372            shouldCheck = m_context.hitTestResult().mediaLoopEnabled();
1373            break;
1374        case ContextMenuItemTagToggleVideoFullscreen:
1375#if SUPPORTS_TOGGLE_VIDEO_FULLSCREEN
1376            item.setTitle(m_context.hitTestResult().mediaIsInFullscreen() ? contextMenuItemTagExitVideoFullscreen() : contextMenuItemTagEnterVideoFullscreen());
1377            break;
1378#endif
1379        case ContextMenuItemTagEnterVideoFullscreen:
1380            shouldEnable = m_context.hitTestResult().mediaSupportsFullscreen();
1381            break;
1382        case ContextMenuItemTagOpenFrameInNewWindow:
1383        case ContextMenuItemTagSpellingGuess:
1384        case ContextMenuItemTagOther:
1385        case ContextMenuItemTagSearchInSpotlight:
1386        case ContextMenuItemTagSearchWeb:
1387        case ContextMenuItemTagOpenWithDefaultApplication:
1388        case ContextMenuItemPDFActualSize:
1389        case ContextMenuItemPDFZoomIn:
1390        case ContextMenuItemPDFZoomOut:
1391        case ContextMenuItemPDFAutoSize:
1392        case ContextMenuItemPDFSinglePage:
1393        case ContextMenuItemPDFFacingPages:
1394        case ContextMenuItemPDFContinuous:
1395        case ContextMenuItemPDFNextPage:
1396        case ContextMenuItemPDFPreviousPage:
1397        case ContextMenuItemTagOpenLink:
1398        case ContextMenuItemTagIgnoreGrammar:
1399        case ContextMenuItemTagSpellingMenu:
1400        case ContextMenuItemTagShowFonts:
1401        case ContextMenuItemTagStyles:
1402        case ContextMenuItemTagShowColors:
1403        case ContextMenuItemTagSpeechMenu:
1404        case ContextMenuItemTagStartSpeaking:
1405        case ContextMenuItemTagWritingDirectionMenu:
1406        case ContextMenuItemTagTextDirectionMenu:
1407        case ContextMenuItemTagPDFSinglePageScrolling:
1408        case ContextMenuItemTagPDFFacingPagesScrolling:
1409#if ENABLE(INSPECTOR)
1410        case ContextMenuItemTagInspectElement:
1411#endif
1412        case ContextMenuItemBaseCustomTag:
1413        case ContextMenuItemCustomTagNoAction:
1414        case ContextMenuItemLastCustomTag:
1415        case ContextMenuItemBaseApplicationTag:
1416        case ContextMenuItemTagDictationAlternative:
1417            break;
1418        case ContextMenuItemTagMediaPlayPause:
1419            if (m_context.hitTestResult().mediaPlaying())
1420                item.setTitle(contextMenuItemTagMediaPause());
1421            else
1422                item.setTitle(contextMenuItemTagMediaPlay());
1423            break;
1424        case ContextMenuItemTagMediaMute:
1425            shouldEnable = m_context.hitTestResult().mediaHasAudio();
1426            shouldCheck = shouldEnable &&  m_context.hitTestResult().mediaMuted();
1427            break;
1428    }
1429
1430    item.setChecked(shouldCheck);
1431    item.setEnabled(shouldEnable);
1432}
1433
1434#if USE(ACCESSIBILITY_CONTEXT_MENUS)
1435void ContextMenuController::showContextMenuAt(Frame* frame, const IntPoint& clickPoint)
1436{
1437    clearContextMenu();
1438
1439    // Simulate a click in the middle of the accessibility object.
1440    PlatformMouseEvent mouseEvent(clickPoint, clickPoint, RightButton, PlatformEvent::MousePressed, 1, false, false, false, false, currentTime());
1441    frame->eventHandler().handleMousePressEvent(mouseEvent);
1442    bool handled = frame->eventHandler().sendContextMenuEvent(mouseEvent);
1443    if (handled)
1444        m_client.showContextMenu();
1445}
1446#endif
1447
1448#if ENABLE(SERVICE_CONTROLS)
1449void ContextMenuController::showImageControlsMenu(Event* event)
1450{
1451    clearContextMenu();
1452    handleContextMenuEvent(event);
1453    m_client.showContextMenu();
1454}
1455#endif
1456
1457} // namespace WebCore
1458
1459#endif // ENABLE(CONTEXT_MENUS)
1460