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