Skip to content

Commit

Permalink
[UnifiedPDF] Context menu should have selection-activated items (copy…
Browse files Browse the repository at this point in the history
… link, lookup, search-the-web)

https://bugs.webkit.org/show_bug.cgi?id=269330
rdar://122918090

Reviewed by Tim Horton.

We track text selections as of 274032@main, making it possible to perform
actions on said selections. This patch introduces the context menu items
that are modulated by the current selection context. Namely, we add
"Copy Link", "Dictionary Lookup", and "Search the Web". The last of
these items has a concrete action courtesy of 274583@main, and the other
two will be implemented in upcoming commits.

In doing so, we slightly refactor how we construct the context menu by
breaking it up into sections corresponding to selections, scale
adjustment, and display mode.

* Source/WebCore/platform/LocalizedStrings.cpp:
(WebCore::contextMenuItemPDFOpenWithPreview):
(WebCore::contextMenuItemPDFCopy): Deleted.
Remove the redundant definition since contextMenuItemTagCopy() returns
the same data.

* Source/WebCore/platform/LocalizedStrings.h:
WEBCORE_EXPORT some more methods since they're being called from WebKit.

* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.h:
* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.mm:
(WebKit::UnifiedPDFPlugin::handleContextMenuEvent):
(WebKit::UnifiedPDFPlugin::toContextMenuItemTag const):
(WebKit::UnifiedPDFPlugin::createContextMenu const):
(WebKit::UnifiedPDFPlugin::isDisplayModeContextMenuItemTag const):
(WebKit::UnifiedPDFPlugin::titleForContextMenuItemTag const):
(WebKit::UnifiedPDFPlugin::contextMenuItem const):
(WebKit::UnifiedPDFPlugin::separatorContextMenuItem const):
(WebKit::UnifiedPDFPlugin::selectionContextMenuItems const):
(WebKit::UnifiedPDFPlugin::displayModeContextMenuItems const):
(WebKit::UnifiedPDFPlugin::scaleContextMenuItems const):
(WebKit::UnifiedPDFPlugin::performContextMenuAction):

Canonical link: https://commits.webkit.org/274611@main
  • Loading branch information
aprotyas committed Feb 14, 2024
1 parent 46a7bd3 commit 547b742
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 42 deletions.
5 changes: 0 additions & 5 deletions Source/WebCore/platform/LocalizedStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,6 @@ String contextMenuItemPDFOpenWithPreview()
{
return WEB_UI_STRING("Open with Preview", "Open with Preview context menu item");
}

String contextMenuItemPDFCopy()
{
return WEB_UI_STRING("Copy", "Copy context menu item");
}
#endif

#if ENABLE(PDFJS) || ENABLE(UNIFIED_PDF)
Expand Down
9 changes: 4 additions & 5 deletions Source/WebCore/platform/LocalizedStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ namespace WebCore {
#if ENABLE(CONTEXT_MENUS)
WEBCORE_EXPORT String contextMenuItemTagOpenLinkInNewWindow();
String contextMenuItemTagDownloadLinkToDisk();
String contextMenuItemTagCopyLinkToClipboard();
WEBCORE_EXPORT String contextMenuItemTagCopyLinkToClipboard();
String contextMenuItemTagOpenImageInNewWindow();
String contextMenuItemTagDownloadImageToDisk();
String contextMenuItemTagCopyImageToClipboard();
#if PLATFORM(GTK)
String contextMenuItemTagCopyImageURLToClipboard();
#endif
String contextMenuItemTagOpenFrameInNewWindow();
String contextMenuItemTagCopy();
WEBCORE_EXPORT String contextMenuItemTagCopy();
String contextMenuItemTagGoBack();
String contextMenuItemTagGoForward();
String contextMenuItemTagStop();
Expand Down Expand Up @@ -102,9 +102,9 @@ namespace WebCore {
String contextMenuItemTagNoGuessesFound();
String contextMenuItemTagIgnoreSpelling();
String contextMenuItemTagLearnSpelling();
String contextMenuItemTagSearchWeb();
WEBCORE_EXPORT String contextMenuItemTagSearchWeb();
#if PLATFORM(COCOA)
String contextMenuItemTagLookUpInDictionary(const String& selectedString);
WEBCORE_EXPORT String contextMenuItemTagLookUpInDictionary(const String& selectedString);
#endif
WEBCORE_EXPORT String contextMenuItemTagOpenLink();
WEBCORE_EXPORT String contextMenuItemTagIgnoreGrammar();
Expand Down Expand Up @@ -179,7 +179,6 @@ namespace WebCore {
#endif
#if ENABLE(UNIFIED_PDF)
WEBCORE_EXPORT String contextMenuItemPDFOpenWithPreview();
WEBCORE_EXPORT String contextMenuItemPDFCopy();
#endif
#if ENABLE(PDFJS) || ENABLE(UNIFIED_PDF)
WEBCORE_EXPORT String contextMenuItemPDFSinglePage();
Expand Down
15 changes: 13 additions & 2 deletions Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ enum class DelegatedScrollingMode : uint8_t;
namespace WebKit {

struct PDFContextMenu;
struct PDFContextMenuItem;
class PDFPluginPasswordField;
class PDFPluginPasswordForm;
class WebFrame;
Expand Down Expand Up @@ -191,9 +192,13 @@ class UnifiedPDFPlugin final : public PDFPluginBase, public WebCore::GraphicsLay
[[maybe_unused]] bool performCopyEditingOperation() const;

// Context Menu
#if ENABLE(CONTEXT_MENUS)
enum class ContextMenuItemTag : int8_t {
Invalid = -1,
WebSearch,
DictionaryLookup,
Copy,
CopyLink,
OpenWithPreview,
SinglePage,
SinglePageContinuous,
Expand All @@ -205,8 +210,14 @@ class UnifiedPDFPlugin final : public PDFPluginBase, public WebCore::GraphicsLay
Unknown,
};

#if PLATFORM(MAC)
PDFContextMenu createContextMenu(const WebCore::IntPoint& contextMenuPoint) const;
std::optional<PDFContextMenu> createContextMenu(const WebMouseEvent&) const;
PDFContextMenuItem contextMenuItem(ContextMenuItemTag) const;
String titleForContextMenuItemTag(ContextMenuItemTag) const;
bool isDisplayModeContextMenuItemTag(ContextMenuItemTag) const;
PDFContextMenuItem separatorContextMenuItem() const;
Vector<PDFContextMenuItem> selectionContextMenuItems(const WebCore::IntPoint& contextMenuPoint) const;
Vector<PDFContextMenuItem> displayModeContextMenuItems() const;
Vector<PDFContextMenuItem> scaleContextMenuItems() const;
ContextMenuItemTag toContextMenuItemTag(int tagValue) const;
void performContextMenuAction(ContextMenuItemTag);

Expand Down
166 changes: 136 additions & 30 deletions Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1490,19 +1490,16 @@ static bool isContextMenuEvent(const WebMouseEvent& event)

bool UnifiedPDFPlugin::handleContextMenuEvent(const WebMouseEvent& event)
{
#if PLATFORM(MAC)
if (!m_frame || !m_frame->coreLocalFrame())
return false;
#if ENABLE(CONTEXT_MENUS)
RefPtr webPage = m_frame->page();
if (!webPage)
return false;
RefPtr frameView = m_frame->coreLocalFrame()->view();
if (!frameView)
return false;

auto contextMenu = createContextMenu(frameView->contentsToScreen(IntRect(frameView->windowToContents(event.position()), IntSize())).location());
auto contextMenu = createContextMenu(event);
if (!contextMenu)
return false;

webPage->sendWithAsyncReply(Messages::WebPageProxy::ShowPDFContextMenu { contextMenu, m_identifier }, [this, weakThis = WeakPtr { *this }](std::optional<int32_t>&& selectedItemTag) {
webPage->sendWithAsyncReply(Messages::WebPageProxy::ShowPDFContextMenu { *contextMenu, m_identifier }, [this, weakThis = WeakPtr { *this }](std::optional<int32_t>&& selectedItemTag) {
RefPtr protectedThis = weakThis.get();
if (!protectedThis)
return;
Expand All @@ -1515,7 +1512,7 @@ static bool isContextMenuEvent(const WebMouseEvent& event)
return true;
#else
return false;
#endif // PLATFORM(MAC)
#endif // ENABLE(CONTEXT_MENUS)
}

bool UnifiedPDFPlugin::handleKeyboardEvent(const WebKeyboardEvent&)
Expand Down Expand Up @@ -1610,7 +1607,7 @@ static bool isContextMenuEvent(const WebMouseEvent& event)

#pragma mark Context Menu

#if PLATFORM(MAC)
#if ENABLE(CONTEXT_MENUS)
UnifiedPDFPlugin::ContextMenuItemTag UnifiedPDFPlugin::contextMenuItemTagFromDisplayMode(const PDFDocumentLayout::DisplayMode& displayMode) const
{
switch (displayMode) {
Expand All @@ -1637,7 +1634,10 @@ static bool isContextMenuEvent(const WebMouseEvent& event)
auto UnifiedPDFPlugin::toContextMenuItemTag(int tagValue) const -> ContextMenuItemTag
{
static constexpr std::array regularContextMenuItemTags {
ContextMenuItemTag::WebSearch,
ContextMenuItemTag::DictionaryLookup,
ContextMenuItemTag::Copy,
ContextMenuItemTag::CopyLink,
ContextMenuItemTag::OpenWithPreview,
ContextMenuItemTag::SinglePage,
ContextMenuItemTag::SinglePageContinuous,
Expand All @@ -1653,49 +1653,155 @@ static bool isContextMenuEvent(const WebMouseEvent& event)
return isKnownContextMenuItemTag ? static_cast<ContextMenuItemTag>(tagValue) : ContextMenuItemTag::Unknown;
}

PDFContextMenu UnifiedPDFPlugin::createContextMenu(const IntPoint& contextMenuPoint) const
std::optional<PDFContextMenu> UnifiedPDFPlugin::createContextMenu(const WebMouseEvent& contextMenuEvent) const
{
ASSERT(isContextMenuEvent(contextMenuEvent));

if (!m_frame || !m_frame->coreLocalFrame())
return std::nullopt;

RefPtr frameView = m_frame->coreLocalFrame()->view();
if (!frameView)
return std::nullopt;

Vector<PDFContextMenuItem> menuItems;

auto addSeparator = [&] {
menuItems.append({ String(), 0, enumToUnderlyingType(ContextMenuItemTag::Invalid), ContextMenuItemEnablement::Disabled, ContextMenuItemHasAction::No, ContextMenuItemIsSeparator::Yes });
auto addSeparator = [item = separatorContextMenuItem(), &menuItems] {
menuItems.append(item);
};

if ([m_pdfDocument allowsCopying] && m_currentSelection) {
menuItems.append({ WebCore::contextMenuItemPDFCopy(), 0, enumToUnderlyingType(ContextMenuItemTag::Copy), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.appendVector(selectionContextMenuItems(convertFromRootViewToPlugin(contextMenuEvent.position())));
addSeparator();
}

menuItems.append({ WebCore::contextMenuItemPDFOpenWithPreview(), 0,
enumToUnderlyingType(ContextMenuItemTag::OpenWithPreview),
ContextMenuItemEnablement::Enabled,
ContextMenuItemHasAction::Yes,
ContextMenuItemIsSeparator::No
});
menuItems.append(contextMenuItem(ContextMenuItemTag::OpenWithPreview));

addSeparator();

auto currentDisplayMode = contextMenuItemTagFromDisplayMode(m_documentLayout.displayMode());
menuItems.append({ WebCore::contextMenuItemPDFSinglePage(), currentDisplayMode == ContextMenuItemTag::SinglePage, enumToUnderlyingType(ContextMenuItemTag::SinglePage), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.append({ WebCore::contextMenuItemPDFSinglePageContinuous(), currentDisplayMode == ContextMenuItemTag::SinglePageContinuous, enumToUnderlyingType(ContextMenuItemTag::SinglePageContinuous), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.append({ WebCore::contextMenuItemPDFTwoPages(), currentDisplayMode == ContextMenuItemTag::TwoPages, enumToUnderlyingType(ContextMenuItemTag::TwoPages), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.append({ WebCore::contextMenuItemPDFTwoPagesContinuous(), currentDisplayMode == ContextMenuItemTag::TwoPagesContinuous, enumToUnderlyingType(ContextMenuItemTag::TwoPagesContinuous), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.appendVector(displayModeContextMenuItems());

addSeparator();

menuItems.append({ WebCore::contextMenuItemPDFZoomIn(), 0, enumToUnderlyingType(ContextMenuItemTag::ZoomIn), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.append({ WebCore::contextMenuItemPDFZoomOut(), 0, enumToUnderlyingType(ContextMenuItemTag::ZoomOut), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.append({ WebCore::contextMenuItemPDFActualSize(), 0, enumToUnderlyingType(ContextMenuItemTag::ActualSize), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No });
menuItems.appendVector(scaleContextMenuItems());

auto contextMenuPoint = frameView->contentsToScreen(IntRect(frameView->windowToContents(contextMenuEvent.position()), IntSize())).location();

return PDFContextMenu { contextMenuPoint, WTFMove(menuItems), { enumToUnderlyingType(ContextMenuItemTag::OpenWithPreview) } };
}

bool UnifiedPDFPlugin::isDisplayModeContextMenuItemTag(ContextMenuItemTag tag) const
{
return tag == ContextMenuItemTag::SinglePage || tag == ContextMenuItemTag::SinglePageContinuous || tag == ContextMenuItemTag::TwoPages || tag == ContextMenuItemTag::TwoPagesContinuous;
}

String UnifiedPDFPlugin::titleForContextMenuItemTag(ContextMenuItemTag tag) const
{
switch (tag) {
case ContextMenuItemTag::Invalid:
return { };
case ContextMenuItemTag::WebSearch:
return contextMenuItemTagSearchWeb();
case ContextMenuItemTag::DictionaryLookup:
return contextMenuItemTagLookUpInDictionary(selectionString());
case ContextMenuItemTag::Copy:
return contextMenuItemTagCopy();
case ContextMenuItemTag::CopyLink:
return contextMenuItemTagCopyLinkToClipboard();
case ContextMenuItemTag::OpenWithPreview:
return contextMenuItemPDFOpenWithPreview();
case ContextMenuItemTag::SinglePage:
return contextMenuItemPDFSinglePage();
case ContextMenuItemTag::SinglePageContinuous:
return contextMenuItemPDFSinglePageContinuous();
case ContextMenuItemTag::TwoPages:
return contextMenuItemPDFTwoPages();
case ContextMenuItemTag::TwoPagesContinuous:
return contextMenuItemPDFTwoPagesContinuous();
case ContextMenuItemTag::ZoomIn:
return contextMenuItemPDFZoomIn();
case ContextMenuItemTag::ZoomOut:
return contextMenuItemPDFZoomOut();
case ContextMenuItemTag::ActualSize:
return contextMenuItemPDFActualSize();
default:
ASSERT_NOT_REACHED();
return { };
}
}

PDFContextMenuItem UnifiedPDFPlugin::contextMenuItem(ContextMenuItemTag tag) const
{
switch (tag) {
case ContextMenuItemTag::Unknown:
case ContextMenuItemTag::Invalid:
return separatorContextMenuItem();
default: {
auto currentDisplayMode = contextMenuItemTagFromDisplayMode(m_documentLayout.displayMode());
int state = isDisplayModeContextMenuItemTag(tag) ? currentDisplayMode == tag : 0;
return { titleForContextMenuItemTag(tag), state, enumToUnderlyingType(tag), ContextMenuItemEnablement::Enabled, ContextMenuItemHasAction::Yes, ContextMenuItemIsSeparator::No };
}
}
}

PDFContextMenuItem UnifiedPDFPlugin::separatorContextMenuItem() const
{
return { { }, 0, enumToUnderlyingType(ContextMenuItemTag::Invalid), ContextMenuItemEnablement::Disabled, ContextMenuItemHasAction::No, ContextMenuItemIsSeparator::Yes };
}

Vector<PDFContextMenuItem> UnifiedPDFPlugin::selectionContextMenuItems(const IntPoint& contextMenuPointInPluginSpace) const
{
if (![m_pdfDocument allowsCopying] || !m_currentSelection)
return { };

Vector<PDFContextMenuItem> items {
contextMenuItem(ContextMenuItemTag::WebSearch),
separatorContextMenuItem(),
contextMenuItem(ContextMenuItemTag::DictionaryLookup),
separatorContextMenuItem(),
contextMenuItem(ContextMenuItemTag::Copy),
};

return { contextMenuPoint, WTFMove(menuItems), { enumToUnderlyingType(ContextMenuItemTag::OpenWithPreview) } };
if (pdfElementTypesForPluginPoint(contextMenuPointInPluginSpace).contains(PDFElementType::Link))
items.append(contextMenuItem(ContextMenuItemTag::CopyLink));

return items;
}

Vector<PDFContextMenuItem> UnifiedPDFPlugin::displayModeContextMenuItems() const
{
return {
contextMenuItem(ContextMenuItemTag::SinglePage),
contextMenuItem(ContextMenuItemTag::SinglePageContinuous),
contextMenuItem(ContextMenuItemTag::TwoPages),
contextMenuItem(ContextMenuItemTag::TwoPagesContinuous),
};
}

Vector<PDFContextMenuItem> UnifiedPDFPlugin::scaleContextMenuItems() const
{
return {
contextMenuItem(ContextMenuItemTag::ZoomIn),
contextMenuItem(ContextMenuItemTag::ZoomOut),
contextMenuItem(ContextMenuItemTag::ActualSize),
};
}

void UnifiedPDFPlugin::performContextMenuAction(ContextMenuItemTag tag)
{
switch (tag) {
case ContextMenuItemTag::WebSearch:
performWebSearch(selectionString());
break;
case ContextMenuItemTag::DictionaryLookup:
notImplemented();
break;
case ContextMenuItemTag::Copy:
performCopyEditingOperation();
break;
case ContextMenuItemTag::CopyLink:
notImplemented();
break;
// The OpenWithPreview action is handled in the UI Process.
case ContextMenuItemTag::OpenWithPreview: return;
case ContextMenuItemTag::SinglePage:
Expand All @@ -1720,7 +1826,7 @@ static bool isContextMenuEvent(const WebMouseEvent& event)
RELEASE_ASSERT_NOT_REACHED();
}
}
#endif // PLATFORM(MAC)
#endif // ENABLE(CONTEXT_MENUS)

#pragma mark Editing Commands

Expand Down

0 comments on commit 547b742

Please sign in to comment.