- Revision
- 275270
- Author
- wenson_hs...@apple.com
- Date
- 2021-03-30 22:52:41 -0700 (Tue, 30 Mar 2021)
Log Message
[iOS] Fall back to context menu presentation after long pressing image overlay text
https://bugs.webkit.org/show_bug.cgi?id=223967
<rdar://problem/76028620>
Reviewed by Tim Horton.
Add support for this behavior; see <rdar://problem/76028620> for more details.
* Shared/ios/InteractionInformationAtPosition.h:
* Shared/ios/InteractionInformationAtPosition.mm:
(WebKit::InteractionInformationAtPosition::encode const):
(WebKit::InteractionInformationAtPosition::decode):
Add a couple of new flags (`isSelected` and `isImageOverlayText`) to indicate whether the interaction is over
selected text, and also whether the interaction is over text in an image overlay, respectively.
* Shared/ios/InteractionInformationRequest.cpp:
(WebKit::InteractionInformationRequest::encode const):
(WebKit::InteractionInformationRequest::decode):
(WebKit::InteractionInformationRequest::isValidForRequest const):
Add a new position information request option to ignore user agent shadow root content.
* Shared/ios/InteractionInformationRequest.h:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
Add a flag that's set when `_imageExtractionTimeoutGestureRecognizer` is recognized, and is about to present a
context menu, and is unset upon starting the context menu presentation.
(-[WKContentView setUpInteraction]):
(-[WKContentView cleanUpInteraction]):
(-[WKContentView gestureRecognizer:shouldRequireFailureOfGestureRecognizer:]):
(-[WKContentView gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]):
Make the timeout gesture (`_imageExtractionTimeoutGestureRecognizer`) require the failure of the context menu
initiation gesture, such that this image extraction timeout gesture won't trigger if the context menu has
already been triggered.
(-[WKContentView _invalidateCurrentPositionInformation]):
(-[WKContentView _didCommitLoadForMainFrame]):
Pull logic for resetting cached position information into a helper, and call it inside
`-_didCommitLoadForMainFrame`.
(-[WKContentView _contextMenuInteraction:configurationForMenuAtLocation:completion:]):
Reset the new flag (`_contextMenuWasTriggeredByImageExtractionTimeout`) if necessary, and use its existing value
to determine whether we should ignore UA shadow root content when performing a hit-test for the context menu
configuration.
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::imageRendererAndImage):
(WebKit::imagePositionInformation):
(WebKit::elementPositionInformation):
Additionally supply the image URL and image data if `includeImageData` is set, even if the hit-tested node is
just a text node underneath an image element's overlay.
(WebKit::selectionPositionInformation):
(WebKit::WebPage::positionInformation):
Add support for the new position information flags.
Modified Paths
Diff
Modified: trunk/Source/WebKit/ChangeLog (275269 => 275270)
--- trunk/Source/WebKit/ChangeLog 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/ChangeLog 2021-03-31 05:52:41 UTC (rev 275270)
@@ -1,3 +1,69 @@
+2021-03-30 Wenson Hsieh <wenson_hs...@apple.com>
+
+ [iOS] Fall back to context menu presentation after long pressing image overlay text
+ https://bugs.webkit.org/show_bug.cgi?id=223967
+ <rdar://problem/76028620>
+
+ Reviewed by Tim Horton.
+
+ Add support for this behavior; see <rdar://problem/76028620> for more details.
+
+ * Shared/ios/InteractionInformationAtPosition.h:
+ * Shared/ios/InteractionInformationAtPosition.mm:
+ (WebKit::InteractionInformationAtPosition::encode const):
+ (WebKit::InteractionInformationAtPosition::decode):
+
+ Add a couple of new flags (`isSelected` and `isImageOverlayText`) to indicate whether the interaction is over
+ selected text, and also whether the interaction is over text in an image overlay, respectively.
+
+ * Shared/ios/InteractionInformationRequest.cpp:
+ (WebKit::InteractionInformationRequest::encode const):
+ (WebKit::InteractionInformationRequest::decode):
+ (WebKit::InteractionInformationRequest::isValidForRequest const):
+
+ Add a new position information request option to ignore user agent shadow root content.
+
+ * Shared/ios/InteractionInformationRequest.h:
+ * UIProcess/ios/WKContentViewInteraction.h:
+ * UIProcess/ios/WKContentViewInteraction.mm:
+
+ Add a flag that's set when `_imageExtractionTimeoutGestureRecognizer` is recognized, and is about to present a
+ context menu, and is unset upon starting the context menu presentation.
+
+ (-[WKContentView setUpInteraction]):
+ (-[WKContentView cleanUpInteraction]):
+ (-[WKContentView gestureRecognizer:shouldRequireFailureOfGestureRecognizer:]):
+ (-[WKContentView gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:]):
+
+ Make the timeout gesture (`_imageExtractionTimeoutGestureRecognizer`) require the failure of the context menu
+ initiation gesture, such that this image extraction timeout gesture won't trigger if the context menu has
+ already been triggered.
+
+ (-[WKContentView _invalidateCurrentPositionInformation]):
+ (-[WKContentView _didCommitLoadForMainFrame]):
+
+ Pull logic for resetting cached position information into a helper, and call it inside
+ `-_didCommitLoadForMainFrame`.
+
+ (-[WKContentView _contextMenuInteraction:configurationForMenuAtLocation:completion:]):
+
+ Reset the new flag (`_contextMenuWasTriggeredByImageExtractionTimeout`) if necessary, and use its existing value
+ to determine whether we should ignore UA shadow root content when performing a hit-test for the context menu
+ configuration.
+
+ * WebProcess/WebPage/ios/WebPageIOS.mm:
+ (WebKit::imageRendererAndImage):
+ (WebKit::imagePositionInformation):
+ (WebKit::elementPositionInformation):
+
+ Additionally supply the image URL and image data if `includeImageData` is set, even if the hit-tested node is
+ just a text node underneath an image element's overlay.
+
+ (WebKit::selectionPositionInformation):
+ (WebKit::WebPage::positionInformation):
+
+ Add support for the new position information flags.
+
2021-03-30 Chris Dumez <cdu...@apple.com>
ASSERT(m_sendPort) in IPC::Connection::open() when running some iOS unit tests
Modified: trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.h (275269 => 275270)
--- trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.h 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.h 2021-03-31 05:52:41 UTC (rev 275270)
@@ -54,6 +54,7 @@
bool canBeValid { true };
Optional<bool> nodeAtPositionHasDoubleClickHandler;
bool isSelectable { false };
+ bool isSelected { false };
bool prefersDraggingOverTextSelection { false };
bool isNearMarkedText { false };
bool touchCalloutEnabled { true };
@@ -70,6 +71,7 @@
bool preventTextInteraction { false };
#endif
bool shouldNotUseIBeamInEditableContent { false };
+ bool isImageOverlayText { false };
WebCore::FloatPoint adjustedPointForNodeRespondingToClickEvents;
URL url;
URL imageURL;
Modified: trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.mm (275269 => 275270)
--- trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.mm 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/Shared/ios/InteractionInformationAtPosition.mm 2021-03-31 05:52:41 UTC (rev 275270)
@@ -45,6 +45,7 @@
encoder << canBeValid;
encoder << nodeAtPositionHasDoubleClickHandler;
encoder << isSelectable;
+ encoder << isSelected;
encoder << prefersDraggingOverTextSelection;
encoder << isNearMarkedText;
encoder << touchCalloutEnabled;
@@ -85,6 +86,7 @@
encoder << preventTextInteraction;
#endif
encoder << shouldNotUseIBeamInEditableContent;
+ encoder << isImageOverlayText;
encoder << elementContext;
}
@@ -102,6 +104,9 @@
if (!decoder.decode(result.isSelectable))
return false;
+ if (!decoder.decode(result.isSelected))
+ return false;
+
if (!decoder.decode(result.prefersDraggingOverTextSelection))
return false;
@@ -204,6 +209,9 @@
if (!decoder.decode(result.shouldNotUseIBeamInEditableContent))
return false;
+ if (!decoder.decode(result.isImageOverlayText))
+ return false;
+
if (!decoder.decode(result.elementContext))
return false;
Modified: trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.cpp (275269 => 275270)
--- trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.cpp 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.cpp 2021-03-31 05:52:41 UTC (rev 275270)
@@ -42,6 +42,7 @@
encoder << includeHasDoubleClickHandler;
encoder << includeImageData;
encoder << linkIndicatorShouldHaveLegacyMargins;
+ encoder << disallowUserAgentShadowContent;
}
bool InteractionInformationRequest::decode(IPC::Decoder& decoder, InteractionInformationRequest& result)
@@ -67,6 +68,9 @@
if (!decoder.decode(result.linkIndicatorShouldHaveLegacyMargins))
return false;
+ if (!decoder.decode(result.disallowUserAgentShadowContent))
+ return false;
+
return true;
}
@@ -91,6 +95,9 @@
if (other.linkIndicatorShouldHaveLegacyMargins != linkIndicatorShouldHaveLegacyMargins)
return false;
+ if (other.disallowUserAgentShadowContent != disallowUserAgentShadowContent)
+ return false;
+
return (other.point - point).diagonalLengthSquared() <= radius * radius;
}
Modified: trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.h (275269 => 275270)
--- trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.h 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/Shared/ios/InteractionInformationRequest.h 2021-03-31 05:52:41 UTC (rev 275270)
@@ -46,6 +46,7 @@
bool includeImageData { false };
bool linkIndicatorShouldHaveLegacyMargins { false };
+ bool disallowUserAgentShadowContent { false };
InteractionInformationRequest() { }
explicit InteractionInformationRequest(WebCore::IntPoint point)
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (275269 => 275270)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h 2021-03-31 05:52:41 UTC (rev 275270)
@@ -475,7 +475,9 @@
Vector<BlockPtr<void(WebKit::ProceedWithImageExtraction)>> _actionsToPerformAfterPendingImageExtraction;
#if USE(UICONTEXTMENU)
RetainPtr<UIMenu> _imageExtractionContextMenu;
+ BOOL _contextMenuWasTriggeredByImageExtractionTimeout;
#endif // USE(UICONTEXTMENU)
+ BOOL _isProceedingWithImageExtraction;
#endif // ENABLE(IMAGE_EXTRACTION)
#if USE(APPLE_INTERNAL_SDK)
Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (275269 => 275270)
--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm 2021-03-31 05:52:41 UTC (rev 275270)
@@ -908,6 +908,10 @@
_isChangingFocus = NO;
_isBlurringFocusedElement = NO;
+#if USE(UICONTEXTMENU) && ENABLE(IMAGE_EXTRACTION)
+ _contextMenuWasTriggeredByImageExtractionTimeout = NO;
+#endif
+
#if ENABLE(DATALIST_ELEMENT)
_dataListTextSuggestionsInputView = nil;
_dataListTextSuggestions = nil;
@@ -964,6 +968,10 @@
_treatAsContentEditableUntilNextEditorStateUpdate = NO;
+#if USE(UICONTEXTMENU) && ENABLE(IMAGE_EXTRACTION)
+ _contextMenuWasTriggeredByImageExtractionTimeout = NO;
+#endif
+
if (_interactionViewsContainerView) {
[self.layer removeObserver:self forKeyPath:@"transform" context:WKContentViewKVOTransformContext];
[_interactionViewsContainerView removeFromSuperview];
@@ -2307,6 +2315,11 @@
if ([otherGestureRecognizer isKindOfClass:WKDeferringGestureRecognizer.class])
return [(WKDeferringGestureRecognizer *)otherGestureRecognizer shouldDeferGestureRecognizer:gestureRecognizer];
+#if USE(UICONTEXTMENU) && ENABLE(IMAGE_EXTRACTION)
+ if (gestureRecognizer == _imageExtractionTimeoutGestureRecognizer && otherGestureRecognizer == [_contextMenuInteraction gestureRecognizerForFailureRelationships])
+ return YES;
+#endif
+
return NO;
}
@@ -2315,6 +2328,11 @@
if ([gestureRecognizer isKindOfClass:WKDeferringGestureRecognizer.class])
return [(WKDeferringGestureRecognizer *)gestureRecognizer shouldDeferGestureRecognizer:otherGestureRecognizer];
+#if USE(UICONTEXTMENU) && ENABLE(IMAGE_EXTRACTION)
+ if (gestureRecognizer == [_contextMenuInteraction gestureRecognizerForFailureRelationships] && otherGestureRecognizer == _imageExtractionTimeoutGestureRecognizer)
+ return YES;
+#endif
+
return NO;
}
@@ -3048,6 +3066,12 @@
_page->clearSelection();
}
+- (void)_invalidateCurrentPositionInformation
+{
+ _hasValidPositionInformation = NO;
+ _positionInformation = { };
+}
+
- (void)_positionInformationDidChange:(const WebKit::InteractionInformationAtPosition&)info
{
if (_lastOutstandingPositionInformationRequest && info.request.isValidForRequest(*_lastOutstandingPositionInformationRequest))
@@ -4493,8 +4517,7 @@
_textInteractionDidChangeFocusedElement = NO;
_activeTextInteractionCount = 0;
_treatAsContentEditableUntilNextEditorStateUpdate = NO;
- _hasValidPositionInformation = NO;
- _positionInformation = { };
+ [self _invalidateCurrentPositionInformation];
}
#if !USE(UIKIT_KEYBOARD_ADDITIONS)
@@ -9954,6 +9977,12 @@
- (void)_contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location completion:(void(^)(UIContextMenuConfiguration *))completion
{
+#if ENABLE(IMAGE_EXTRACTION)
+ BOOL triggeredByImageExtractionTimeout = std::exchange(_contextMenuWasTriggeredByImageExtractionTimeout, NO);
+#else
+ BOOL triggeredByImageExtractionTimeout = NO;
+#endif
+
if (!_webView)
return completion(nil);
@@ -9960,7 +9989,7 @@
if (!self.webView.configuration._longPressActionsEnabled)
return completion(nil);
- auto getConfigurationAndContinue = [weakSelf = WeakObjCPtr<WKContentView>(self), interaction = retainPtr(interaction), completion = makeBlockPtr(completion)] (WebKit::ProceedWithImageExtraction proceedWithImageExtraction) {
+ auto getConfigurationAndContinue = [weakSelf = WeakObjCPtr<WKContentView>(self), interaction = retainPtr(interaction), completion = makeBlockPtr(completion), triggeredByImageExtractionTimeout] (WebKit::ProceedWithImageExtraction proceedWithImageExtraction) {
auto strongSelf = weakSelf.get();
if (!strongSelf || proceedWithImageExtraction == WebKit::ProceedWithImageExtraction::Yes) {
completion(nil);
@@ -9976,6 +10005,7 @@
WebKit::InteractionInformationRequest request { WebCore::roundedIntPoint([interaction locationInView:strongSelf.get()]) };
request.includeSnapshot = true;
request.includeLinkIndicator = true;
+ request.disallowUserAgentShadowContent = triggeredByImageExtractionTimeout;
request.linkIndicatorShouldHaveLegacyMargins = ![strongSelf _shouldUseContextMenus];
[strongSelf doAfterPositionInformationUpdate:[weakSelf = WeakObjCPtr<WKContentView>(strongSelf.get()), completion] (WebKit::InteractionInformationAtPosition) {
Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (275269 => 275270)
--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2021-03-31 05:48:55 UTC (rev 275269)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm 2021-03-31 05:52:41 UTC (rev 275270)
@@ -2665,19 +2665,32 @@
#endif
-static void imagePositionInformation(WebPage& page, Element& element, const InteractionInformationRequest& request, InteractionInformationAtPosition& info)
+static Optional<std::pair<RenderImage&, Image&>> imageRendererAndImage(Element& element)
{
- auto& renderImage = downcast<RenderImage>(*(element.renderer()));
+ if (!is<RenderImage>(element.renderer()))
+ return WTF::nullopt;
+
+ auto& renderImage = downcast<RenderImage>(*element.renderer());
if (!renderImage.cachedImage() || renderImage.cachedImage()->errorOccurred())
- return;
+ return WTF::nullopt;
auto* image = renderImage.cachedImage()->imageForRenderer(&renderImage);
if (!image || image->width() <= 1 || image->height() <= 1)
+ return WTF::nullopt;
+
+ return {{ renderImage, *image }};
+}
+
+static void imagePositionInformation(WebPage& page, Element& element, const InteractionInformationRequest& request, InteractionInformationAtPosition& info)
+{
+ auto rendererAndImage = imageRendererAndImage(element);
+ if (!rendererAndImage)
return;
+ auto& [renderImage, image] = *rendererAndImage;
info.isImage = true;
info.imageURL = element.document().completeURL(renderImage.cachedImage()->url().string());
- info.isAnimatedImage = image->isAnimated();
+ info.isAnimatedImage = image.isAnimated();
if (request.includeSnapshot || request.includeImageData)
info.image = createShareableBitmap(renderImage, screenSize() * page.corePage()->deviceScaleFactor());
@@ -2733,12 +2746,17 @@
if (auto* renderer = element.renderer()) {
bool shouldCollectImagePositionInformation = renderer->isRenderImage();
-#if ENABLE(IMAGE_EXTRACTION)
- if (innerNonSharedNode && HTMLElement::isImageOverlayText(*innerNonSharedNode))
+ if (shouldCollectImagePositionInformation && innerNonSharedNode && HTMLElement::isImageOverlayText(*innerNonSharedNode)) {
shouldCollectImagePositionInformation = false;
-#else
- UNUSED_PARAM(innerNonSharedNode);
-#endif
+ info.isImageOverlayText = true;
+ if (request.includeImageData) {
+ if (auto rendererAndImage = imageRendererAndImage(element)) {
+ auto& [renderImage, image] = *rendererAndImage;
+ info.imageURL = element.document().completeURL(renderImage.cachedImage()->url().string());
+ info.image = createShareableBitmap(renderImage, screenSize() * page.corePage()->deviceScaleFactor());
+ }
+ }
+ }
if (shouldCollectImagePositionInformation)
imagePositionInformation(page, element, request, info);
boundsPositionInformation(*renderer, info);
@@ -2759,6 +2777,8 @@
RenderObject* renderer = hitNode->renderer();
boundsPositionInformation(*renderer, info);
+
+ info.isSelected = result.isSelected();
if (is<Element>(*hitNode)) {
Element& element = downcast<Element>(*hitNode);
@@ -2921,8 +2941,16 @@
info.nodeAtPositionHasDoubleClickHandler = m_page->mainFrame().nodeRespondingToDoubleClickEvent(request.point, adjustedPoint);
auto& eventHandler = m_page->mainFrame().eventHandler();
- constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::ReadOnly, HitTestRequest::AllowFrameScrollbars, HitTestRequest::AllowVisibleChildFrameContentOnly };
- auto hitTestResult = eventHandler.hitTestResultAtPoint(request.point, hitType);
+ auto hitTestRequestTypes = OptionSet<HitTestRequest::RequestType> {
+ HitTestRequest::ReadOnly,
+ HitTestRequest::AllowFrameScrollbars,
+ HitTestRequest::AllowVisibleChildFrameContentOnly,
+ };
+
+ if (request.disallowUserAgentShadowContent)
+ hitTestRequestTypes.add(HitTestRequest::DisallowUserAgentShadowContent);
+
+ auto hitTestResult = eventHandler.hitTestResultAtPoint(request.point, hitTestRequestTypes);
if (auto* hitFrame = hitTestResult.innerNodeFrame()) {
info.cursor = hitFrame->eventHandler().selectCursor(hitTestResult, false);
if (request.includeCaretContext)