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