[WK2] -[WKContentView doAfterPositionInformationUpdate:atPosition:] should be robust...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Apr 2017 19:59:46 +0000 (19:59 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 20 Apr 2017 19:59:46 +0000 (19:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170922
<rdar://problem/31634990>

Reviewed by Tim Horton.

Source/WebKit2:

Refactors part of the asynchronous position information mechanism introduced in
<https://trac.webkit.org/changeset/215171>, and introduces infrastructure for unit testing UI-side position
information requests.

_invokeAndRemovePendingHandlersValidForCurrentPositionInformation is a helper method on WKContentView
responsible for invoking queued position information handlers after receiving a position information response
from the web process. Previously, this method would iterate over the list of pending callbacks and invoke all
callbacks whose requests are satisfied by the incoming position information update, saving the indices of these
handled callbacks in the process. At the end, it would then remove callbacks at these indices from the array of
pending callbacks. This is problematic when a synchronous position update (via
ensurePositionInformationIsUpToDate:) is triggered from within one of these callbacks, since
_invokeAndRemovePendingHandlersValidForCurrentPositionInformation will be called from within itself, and then we
will attempt to remove a callback at the same index twice.

To fix this, we change the array of pending position information handlers to be an array of optionals instead.
When invoking and removing pending handlers after a position information response arrives, we now mark callbacks
as handled by setting them to std::nullopt. Then, when the top-level invocation to
_invokeAndRemovePendingHandlersValidForCurrentPositionInformation is finished, we remove all marked handlers
from the pending vector.

Covered by 6 new unit tests:
- PositionInformationTests.FindDraggableLinkAtPosition
- PositionInformationTests.RequestDraggableLinkAtPosition
- PositionInformationTests.FindDraggableLinkAtDifferentPositionWithinRequestBlock
- PositionInformationTests.FindDraggableLinkAtSamePositionWithinRequestBlock
- PositionInformationTests.RequestDraggableLinkAtSamePositionWithinRequestBlock
- PositionInformationTests.RequestDraggableLinkAtDifferentPositionWithinRequestBlock

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView draggableElementAtPosition:]):
(-[WKWebView requestDraggableElementAtPosition:completionBlock:]):

Uses WKContentView's position information request helpers to search for draggable elements on the page. There
are both synchronous and asynchronous versions of this, both of which retrieve a _WKDraggableElementInfo.

* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/API/Cocoa/_WKDraggableElementInfo.h: Added.
* UIProcess/API/Cocoa/_WKDraggableElementInfo.mm: Added.

Exposes what elements are draggable at a given position as SPI (only for use in testing code, at the moment).

(-[_WKDraggableElementInfo initWithInteractionInformationAtPosition:]):
(-[_WKDraggableElementInfo copyWithZone:]):
* UIProcess/API/Cocoa/_WKDraggableElementInfoInternal.h: Added.
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView currentPositionInformation]):
(-[WKContentView doAfterPositionInformationUpdate:forRequest:]):
(-[WKContentView _invokeAndRemovePendingHandlersValidForCurrentPositionInformation]):
* WebKit2.xcodeproj/project.pbxproj:

Tools:

Adds six new unit tests for retrieving interaction information at a given position in the UI process. See
WebKit2 ChangeLog for more details.

* TestWebKitAPI/Tests/ios/PositionInformationTests.mm:
(-[_WKDraggableElementInfo expectToBeLink:image:atPoint:]):
(TestWebKitAPI::TEST):
(TestWebKitAPI::expectCGPointsToBeEqual): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215573 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.h [new file with mode: 0644]
Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.mm [new file with mode: 0644]
Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfoInternal.h [new file with mode: 0644]
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit2/WebKit2.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/ios/PositionInformationTests.mm [new file with mode: 0644]

index d36aeaf42f04dafed37a65534fba6fa4960224e0..56159bdf27a83fb7cb7268ad948d26061f859288 100644 (file)
@@ -1,3 +1,62 @@
+2017-04-20  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] -[WKContentView doAfterPositionInformationUpdate:atPosition:] should be robust against synchronous reentrancy
+        https://bugs.webkit.org/show_bug.cgi?id=170922
+        <rdar://problem/31634990>
+
+        Reviewed by Tim Horton.
+
+        Refactors part of the asynchronous position information mechanism introduced in
+        <https://trac.webkit.org/changeset/215171>, and introduces infrastructure for unit testing UI-side position
+        information requests.
+
+        _invokeAndRemovePendingHandlersValidForCurrentPositionInformation is a helper method on WKContentView
+        responsible for invoking queued position information handlers after receiving a position information response
+        from the web process. Previously, this method would iterate over the list of pending callbacks and invoke all
+        callbacks whose requests are satisfied by the incoming position information update, saving the indices of these
+        handled callbacks in the process. At the end, it would then remove callbacks at these indices from the array of
+        pending callbacks. This is problematic when a synchronous position update (via
+        ensurePositionInformationIsUpToDate:) is triggered from within one of these callbacks, since
+        _invokeAndRemovePendingHandlersValidForCurrentPositionInformation will be called from within itself, and then we
+        will attempt to remove a callback at the same index twice.
+
+        To fix this, we change the array of pending position information handlers to be an array of optionals instead.
+        When invoking and removing pending handlers after a position information response arrives, we now mark callbacks
+        as handled by setting them to std::nullopt. Then, when the top-level invocation to
+        _invokeAndRemovePendingHandlersValidForCurrentPositionInformation is finished, we remove all marked handlers
+        from the pending vector.
+
+        Covered by 6 new unit tests:
+        - PositionInformationTests.FindDraggableLinkAtPosition
+        - PositionInformationTests.RequestDraggableLinkAtPosition
+        - PositionInformationTests.FindDraggableLinkAtDifferentPositionWithinRequestBlock
+        - PositionInformationTests.FindDraggableLinkAtSamePositionWithinRequestBlock
+        - PositionInformationTests.RequestDraggableLinkAtSamePositionWithinRequestBlock
+        - PositionInformationTests.RequestDraggableLinkAtDifferentPositionWithinRequestBlock
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView draggableElementAtPosition:]):
+        (-[WKWebView requestDraggableElementAtPosition:completionBlock:]):
+
+        Uses WKContentView's position information request helpers to search for draggable elements on the page. There
+        are both synchronous and asynchronous versions of this, both of which retrieve a _WKDraggableElementInfo.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/API/Cocoa/_WKDraggableElementInfo.h: Added.
+        * UIProcess/API/Cocoa/_WKDraggableElementInfo.mm: Added.
+
+        Exposes what elements are draggable at a given position as SPI (only for use in testing code, at the moment).
+
+        (-[_WKDraggableElementInfo initWithInteractionInformationAtPosition:]):
+        (-[_WKDraggableElementInfo copyWithZone:]):
+        * UIProcess/API/Cocoa/_WKDraggableElementInfoInternal.h: Added.
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView currentPositionInformation]):
+        (-[WKContentView doAfterPositionInformationUpdate:forRequest:]):
+        (-[WKContentView _invokeAndRemovePendingHandlersValidForCurrentPositionInformation]):
+        * WebKit2.xcodeproj/project.pbxproj:
+
 2017-04-20  Anders Carlsson  <andersca@apple.com>
 
         Fix a PassKit function declaration typo
index d3f514bfad57136e507a7460548cf6dd5767df14..17d39bc8b09605f512d060833e95eaaff0947dce 100644 (file)
@@ -36,6 +36,8 @@
 #import "FindClient.h"
 #import "FullscreenClient.h"
 #import "IconLoadingDelegate.h"
+#import "InteractionInformationAtPosition.h"
+#import "InteractionInformationRequest.h"
 #import "LegacySessionStateCoding.h"
 #import "Logging.h"
 #import "NavigationState.h"
@@ -83,6 +85,7 @@
 #import "WebURLSchemeHandlerCocoa.h"
 #import "WebViewImpl.h"
 #import "_WKDiagnosticLoggingDelegate.h"
+#import "_WKDraggableElementInfoInternal.h"
 #import "_WKFindDelegate.h"
 #import "_WKFrameHandleInternal.h"
 #import "_WKFullscreenDelegate.h"
@@ -5088,6 +5091,18 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
 }
 
 #if PLATFORM(IOS)
+- (_WKDraggableElementInfo *)draggableElementAtPosition:(CGPoint)position
+{
+    [_contentView ensurePositionInformationIsUpToDate:WebKit::InteractionInformationRequest(WebCore::roundedIntPoint(position))];
+    return [_WKDraggableElementInfo infoWithInteractionInformationAtPosition:[_contentView currentPositionInformation]];
+}
+
+- (void)requestDraggableElementAtPosition:(CGPoint)position completionBlock:(void (^)(_WKDraggableElementInfo *))block
+{
+    [_contentView doAfterPositionInformationUpdate:[capturedBlock = makeBlockPtr(block)] (WebKit::InteractionInformationAtPosition information) {
+        capturedBlock([_WKDraggableElementInfo infoWithInteractionInformationAtPosition:information]);
+    } forRequest:WebKit::InteractionInformationRequest(WebCore::roundedIntPoint(position))];
+}
 
 - (CGRect)_contentVisibleRect
 {
index 3084d8687ca4579319bd5733286f8e04a518709c..cb82c467a501d288962dfd144aebe8d2b79bbf2f 100644 (file)
@@ -67,6 +67,7 @@ typedef NS_ENUM(NSInteger, _WKImmediateActionType) {
 #endif
 
 @class WKBrowsingContextHandle;
+@class _WKDraggableElementInfo;
 @class _WKFrameHandle;
 @class _WKHitTestResult;
 @class _WKIconLoadingDelegate;
@@ -336,6 +337,9 @@ typedef NS_ENUM(NSInteger, _WKImmediateActionType) {
 - (NSArray *)_simulatedItemsForSession:(id)session WK_API_AVAILABLE(ios(WK_IOS_TBA));
 - (void)_simulatePrepareForDataInteractionSession:(id)session completion:(dispatch_block_t)completion WK_API_AVAILABLE(ios(WK_IOS_TBA));
 
+- (_WKDraggableElementInfo *)draggableElementAtPosition:(CGPoint)position;
+- (void)requestDraggableElementAtPosition:(CGPoint)position completionBlock:(void (^)(_WKDraggableElementInfo *))block;
+
 #endif // TARGET_OS_IPHONE
 
 #if !TARGET_OS_IPHONE
diff --git a/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.h b/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.h
new file mode 100644 (file)
index 0000000..f00d5a3
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#if WK_API_ENABLED
+
+NS_ASSUME_NONNULL_BEGIN
+
+WK_CLASS_AVAILABLE(macosx(10.13), ios(11.0))
+@interface _WKDraggableElementInfo : NSObject <NSCopying>
+
+@property (nonatomic, readonly) CGPoint point;
+
+@property (nonatomic, readonly, getter=isLink) BOOL link;
+@property (nonatomic, readonly, getter=isAttachment) BOOL attachment;
+@property (nonatomic, readonly, getter=isImage) BOOL image;
+@property (nonatomic, readonly, getter=isSelected) BOOL selected;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif
diff --git a/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.mm b/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfo.mm
new file mode 100644 (file)
index 0000000..e0ff921
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "_WKDraggableElementInfoInternal.h"
+
+#if WK_API_ENABLED
+
+@implementation _WKDraggableElementInfo
+
+#if PLATFORM(IOS)
+
++ (instancetype)infoWithInteractionInformationAtPosition:(const WebKit::InteractionInformationAtPosition&)information
+{
+    return [[[self alloc] initWithInteractionInformationAtPosition:information] autorelease];
+}
+
+- (instancetype)initWithInteractionInformationAtPosition:(const WebKit::InteractionInformationAtPosition&)information
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _point = information.request.point;
+    _link = information.isLink;
+    _attachment = information.isAttachment;
+    _image = information.isImage;
+#if ENABLE(DATA_INTERACTION)
+    _selected = information.hasSelectionAtPosition;
+#endif
+
+    return self;
+}
+
+#endif
+
+- (instancetype)copyWithZone:(NSZone *)zone
+{
+    _WKDraggableElementInfo *copy = [[_WKDraggableElementInfo alloc] init];
+
+    copy.point = self.point;
+    copy.link = self.link;
+    copy.attachment = self.attachment;
+    copy.image = self.image;
+    copy.selected = self.selected;
+
+    return copy;
+}
+
+@end
+
+#endif
diff --git a/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfoInternal.h b/Source/WebKit2/UIProcess/API/Cocoa/_WKDraggableElementInfoInternal.h
new file mode 100644 (file)
index 0000000..f806ce7
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "InteractionInformationAtPosition.h"
+#import "_WKDraggableElementInfo.h"
+
+#if WK_API_ENABLED
+
+@interface _WKDraggableElementInfo ()
+
+#if TARGET_OS_IPHONE
++ (instancetype)infoWithInteractionInformationAtPosition:(const WebKit::InteractionInformationAtPosition&)information;
+- (instancetype)initWithInteractionInformationAtPosition:(const WebKit::InteractionInformationAtPosition&)information;
+#endif
+
+@property (nonatomic) CGPoint point;
+@property (nonatomic, getter=isLink) BOOL link;
+@property (nonatomic, getter=isAttachment) BOOL attachment;
+@property (nonatomic, getter=isImage) BOOL image;
+@property (nonatomic, getter=isSelected) BOOL selected;
+
+@end
+
+#endif
index 9f3add97bb3c5fe9fb8c317147f5d26baaaed35f..fc1654a17c392e71888076231c35a59b74dad54f 100644 (file)
@@ -87,6 +87,7 @@ typedef void (^UIWKSelectionWithDirectionCompletionHandler)(BOOL selectionEndIsM
 typedef void (^UIWKKeyWebEventCompletionHandler)(::WebEvent *theEvent, BOOL wasHandled);
 
 typedef BlockPtr<void(WebKit::InteractionInformationAtPosition)> InteractionInformationCallback;
+typedef std::pair<WebKit::InteractionInformationRequest, InteractionInformationCallback> InteractionInformationRequestAndCallback;
 
 namespace WebKit {
 
@@ -174,7 +175,9 @@ struct WKAutoCorrectionData {
     WebKit::WKSelectionDrawingInfo _lastSelectionDrawingInfo;
 
     std::optional<WebKit::InteractionInformationRequest> _outstandingPositionInformationRequest;
-    Vector<std::pair<WebKit::InteractionInformationRequest, InteractionInformationCallback>> _pendingPositionInformationHandlers;
+
+    uint64_t _positionInformationCallbackDepth;
+    Vector<std::optional<InteractionInformationRequestAndCallback>> _pendingPositionInformationHandlers;
     
     std::unique_ptr<WebKit::InputViewUpdateDeferrer> _inputViewUpdateDeferrer;
 
@@ -287,6 +290,10 @@ struct WKAutoCorrectionData {
 - (void)_accessibilityRetrieveRectsEnclosingSelectionOffset:(NSInteger)offset withGranularity:(UITextGranularity)granularity;
 - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text;
 
+@property (nonatomic, readonly) WebKit::InteractionInformationAtPosition currentPositionInformation;
+- (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request;
+- (void)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request;
+
 #if ENABLE(DATA_INTERACTION)
 - (void)_didPerformDataInteractionControllerOperation;
 - (void)_didHandleStartDataInteractionRequest:(BOOL)started;
index 3257b159051449b61c3eb13dfcc3564f5f0acc01..550464da9894dc50e51df662525662c55bdb0396 100644 (file)
@@ -57,6 +57,7 @@
 #import "WebPageMessages.h"
 #import "WebProcessProxy.h"
 #import "_WKActivatedElementInfoInternal.h"
+#import "_WKDraggableElementInfoInternal.h"
 #import "_WKElementAction.h"
 #import "_WKFocusedElementInfo.h"
 #import "_WKFormInputSession.h"
@@ -1306,6 +1307,11 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     return [self _actionForLongPressFromPositionInformation:_positionInformation];
 }
 
+- (InteractionInformationAtPosition)currentPositionInformation
+{
+    return _positionInformation;
+}
+
 - (void)doAfterPositionInformationUpdate:(void (^)(InteractionInformationAtPosition))action forRequest:(InteractionInformationRequest)request
 {
     if ([self _currentPositionInformationIsValidForRequest:request]) {
@@ -1314,7 +1320,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
         return;
     }
 
-    _pendingPositionInformationHandlers.append({ request, action });
+    _pendingPositionInformationHandlers.append(InteractionInformationRequestAndCallback(request, action));
 
     if (![self _hasValidOutstandingPositionInformationRequest:request])
         [self requestAsynchronousPositionInformationUpdate:request];
@@ -1361,20 +1367,30 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 {
     ASSERT(_hasValidPositionInformation);
 
-    Vector<size_t> indicesOfHandledRequests;
+    ++_positionInformationCallbackDepth;
+    auto updatedPositionInformation = _positionInformation;
+
     for (size_t index = 0; index < _pendingPositionInformationHandlers.size(); ++index) {
-        auto& requestAndHandler = _pendingPositionInformationHandlers[index];
-        if (![self _currentPositionInformationIsValidForRequest:requestAndHandler.first])
+        auto requestAndHandler = _pendingPositionInformationHandlers[index];
+        if (!requestAndHandler)
             continue;
 
-        if (requestAndHandler.second)
-            requestAndHandler.second(_positionInformation);
+        if (![self _currentPositionInformationIsValidForRequest:requestAndHandler->first])
+            continue;
+
+        _pendingPositionInformationHandlers[index] = std::nullopt;
 
-        indicesOfHandledRequests.append(index);
+        if (requestAndHandler->second)
+            requestAndHandler->second(updatedPositionInformation);
     }
 
-    while (indicesOfHandledRequests.size())
-        _pendingPositionInformationHandlers.remove(indicesOfHandledRequests.takeLast());
+    if (--_positionInformationCallbackDepth)
+        return;
+
+    for (int index = _pendingPositionInformationHandlers.size() - 1; index >= 0; --index) {
+        if (!_pendingPositionInformationHandlers[index])
+            _pendingPositionInformationHandlers.remove(index);
+    }
 }
 
 #if ENABLE(DATA_DETECTION)
index 1887c53b63f1de25bc5ed8d07604eb41dd3de85c..4896c313698f96c0dd6787453940feb45ef499f3 100644 (file)
                F43370971E4D72ED00052B0E /* _WKTestingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = F43370961E4D6A4400052B0E /* _WKTestingDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F44DFEB21E9E752F0038D196 /* WebIconUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = F44DFEB01E9E752F0038D196 /* WebIconUtilities.h */; };
                F44DFEB31E9E752F0038D196 /* WebIconUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44DFEB11E9E752F0038D196 /* WebIconUtilities.mm */; };
+               F4E8CB911EA6AB5B00E31198 /* _WKDraggableElementInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E8CB8E1EA6AB5B00E31198 /* _WKDraggableElementInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               F4E8CB921EA6AB5B00E31198 /* _WKDraggableElementInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4E8CB8F1EA6AB5B00E31198 /* _WKDraggableElementInfo.mm */; };
+               F4E8CB931EA6AB5B00E31198 /* _WKDraggableElementInfoInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E8CB901EA6AB5B00E31198 /* _WKDraggableElementInfoInternal.h */; };
                F6113E25126CE1820057D0A7 /* APIUserContentURLPattern.h in Headers */ = {isa = PBXBuildFile; fileRef = F6113E24126CE1820057D0A7 /* APIUserContentURLPattern.h */; };
                F6113E28126CE19B0057D0A7 /* WKUserContentURLPattern.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6113E26126CE19B0057D0A7 /* WKUserContentURLPattern.cpp */; };
                F6113E29126CE19B0057D0A7 /* WKUserContentURLPattern.h in Headers */ = {isa = PBXBuildFile; fileRef = F6113E27126CE19B0057D0A7 /* WKUserContentURLPattern.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F43370961E4D6A4400052B0E /* _WKTestingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKTestingDelegate.h; sourceTree = "<group>"; };
                F44DFEB01E9E752F0038D196 /* WebIconUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebIconUtilities.h; path = ios/WebIconUtilities.h; sourceTree = "<group>"; };
                F44DFEB11E9E752F0038D196 /* WebIconUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WebIconUtilities.mm; path = ios/WebIconUtilities.mm; sourceTree = "<group>"; };
+               F4E8CB8E1EA6AB5B00E31198 /* _WKDraggableElementInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKDraggableElementInfo.h; sourceTree = "<group>"; };
+               F4E8CB8F1EA6AB5B00E31198 /* _WKDraggableElementInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKDraggableElementInfo.mm; sourceTree = "<group>"; };
+               F4E8CB901EA6AB5B00E31198 /* _WKDraggableElementInfoInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKDraggableElementInfoInternal.h; sourceTree = "<group>"; };
                F6113E24126CE1820057D0A7 /* APIUserContentURLPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIUserContentURLPattern.h; sourceTree = "<group>"; };
                F6113E26126CE19B0057D0A7 /* WKUserContentURLPattern.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WKUserContentURLPattern.cpp; sourceTree = "<group>"; };
                F6113E27126CE19B0057D0A7 /* WKUserContentURLPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKUserContentURLPattern.h; sourceTree = "<group>"; };
                                A1A4FE5818DCE9FA00B5EA8A /* _WKDownload.mm */,
                                A1A4FE6018DD54A400B5EA8A /* _WKDownloadDelegate.h */,
                                A1A4FE5918DCE9FA00B5EA8A /* _WKDownloadInternal.h */,
+                               F4E8CB8E1EA6AB5B00E31198 /* _WKDraggableElementInfo.h */,
+                               F4E8CB8F1EA6AB5B00E31198 /* _WKDraggableElementInfo.mm */,
+                               F4E8CB901EA6AB5B00E31198 /* _WKDraggableElementInfoInternal.h */,
                                379A873818BBFE0F00588AF2 /* _WKElementAction.h */,
                                379A873718BBFE0F00588AF2 /* _WKElementAction.mm */,
                                379A873B18BBFF0700588AF2 /* _WKElementActionInternal.h */,
                        path = NetworkProcess/cache;
                        sourceTree = "<group>";
                };
+               F4D7BCCA1EA494FA00C421D3 /* Recovered References */ = {
+                       isa = PBXGroup;
+                       children = (
+                               C58CDF281887548B00871536 /* InteractionInformationAtPosition.h */,
+                       );
+                       name = "Recovered References";
+                       sourceTree = "<group>";
+               };
                F638955A133BF57D008941D5 /* mac */ = {
                        isa = PBXGroup;
                        children = (
                                7C89D2941A67122F003A5FDE /* APIUserScript.h in Headers */,
                                2D8786241BDB58FF00D02ABB /* APIUserStyleSheet.h in Headers */,
                                C5E1AFED16B21017006CC1F2 /* APIWebArchive.h in Headers */,
+                               F4E8CB931EA6AB5B00E31198 /* _WKDraggableElementInfoInternal.h in Headers */,
                                C5E1AFEF16B21029006CC1F2 /* APIWebArchiveResource.h in Headers */,
                                1AE286841C7F93860069AC4F /* APIWebsiteDataRecord.h in Headers */,
                                1A3635AA1A3144A300ED6197 /* APIWebsiteDataStore.h in Headers */,
                                07A5EBBC1C7BA43E00B9CA69 /* WKFrameHandleRef.h in Headers */,
                                1A4D664C18A3030E00D82E21 /* WKFrameInfo.h in Headers */,
                                2DF9EEE81A78245500B6CFBE /* WKFrameInfoInternal.h in Headers */,
+                               F4E8CB911EA6AB5B00E31198 /* _WKDraggableElementInfo.h in Headers */,
                                1A6FA21E1BD0435B00AAA650 /* WKFrameInfoPrivate.h in Headers */,
                                2D3A65E71A7C3AA700CAC637 /* WKFrameInfoRef.h in Headers */,
                                BCB9F6A51123DD0D00A137E0 /* WKFramePolicyListener.h in Headers */,
                                93A253EF1C922E8E00F9F68D /* WKPreviewActionItem.mm in Sources */,
                                93A253F51C92413200F9F68D /* WKPreviewActionItemIdentifiers.mm in Sources */,
                                9395E68C1BF2C35200F49BCE /* WKPreviewElementInfo.mm in Sources */,
+                               F4E8CB921EA6AB5B00E31198 /* _WKDraggableElementInfo.mm in Sources */,
                                0FCB4E6718BBE3D9000FCFC9 /* WKPrintingView.mm in Sources */,
                                BCBAACEC145225E30053F82F /* WKProcessGroup.mm in Sources */,
                                1A158419189044F50017616C /* WKProcessPool.mm in Sources */,
index c0ff2341104fec4e819309515edf6b53e0abd1b8..b0e3bb3c5c6b20d21e38496675b906a3f901f92c 100644 (file)
@@ -1,3 +1,19 @@
+2017-04-20  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] -[WKContentView doAfterPositionInformationUpdate:atPosition:] should be robust against synchronous reentrancy
+        https://bugs.webkit.org/show_bug.cgi?id=170922
+        <rdar://problem/31634990>
+
+        Reviewed by Tim Horton.
+
+        Adds six new unit tests for retrieving interaction information at a given position in the UI process. See
+        WebKit2 ChangeLog for more details.
+
+        * TestWebKitAPI/Tests/ios/PositionInformationTests.mm:
+        (-[_WKDraggableElementInfo expectToBeLink:image:atPoint:]):
+        (TestWebKitAPI::TEST):
+        (TestWebKitAPI::expectCGPointsToBeEqual): Deleted.
+
 2017-04-20  Xan Lopez  <xlopez@igalia.com>
 
         [GTK][jhbuild] Update glib and glib-networking to the latest stable versions
index 4db2a979474028aaca6535b3302ec8fd7faff283..e23bda0f87b2f5f40f155d37ef214c5c437a3409 100644 (file)
                F4C2AB221DD6D95E00E06D5B /* enormous-video-with-sound.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */; };
                F4D4F3B61E4E2BCB00BB2767 /* DataInteractionSimulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */; };
                F4D4F3B91E4E36E400BB2767 /* DataInteractionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */; };
+               F4D7BCD81EA5789800C421D3 /* PositionInformationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4D7BCD61EA574DD00C421D3 /* PositionInformationTests.mm */; };
                F4DEF6ED1E9B4DB60048EF61 /* image-in-link-and-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4DEF6EC1E9B4D950048EF61 /* image-in-link-and-input.html */; };
                F4F137921D9B683E002BEC57 /* large-video-test-now-playing.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4F137911D9B6832002BEC57 /* large-video-test-now-playing.html */; };
                F4F405BC1D4C0D1C007A9707 /* full-size-autoplaying-video-with-audio.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4F405BA1D4C0CF8007A9707 /* full-size-autoplaying-video-with-audio.html */; };
                F4D4F3B41E4E2BCB00BB2767 /* DataInteractionSimulator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataInteractionSimulator.mm; sourceTree = "<group>"; };
                F4D4F3B51E4E2BCB00BB2767 /* DataInteractionSimulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataInteractionSimulator.h; sourceTree = "<group>"; };
                F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DataInteractionTests.mm; sourceTree = "<group>"; };
+               F4D7BCD61EA574DD00C421D3 /* PositionInformationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PositionInformationTests.mm; sourceTree = "<group>"; };
                F4DEF6EC1E9B4D950048EF61 /* image-in-link-and-input.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "image-in-link-and-input.html"; sourceTree = "<group>"; };
                F4F137911D9B6832002BEC57 /* large-video-test-now-playing.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-video-test-now-playing.html"; sourceTree = "<group>"; };
                F4F405BA1D4C0CF8007A9707 /* full-size-autoplaying-video-with-audio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-size-autoplaying-video-with-audio.html"; sourceTree = "<group>"; };
                                A1C4FB6F1BACCEFA003742D0 /* Resources */,
                                F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */,
                                7560917719259C59009EF06E /* MemoryCacheAddImageToCacheIOS.mm */,
+                               F4D7BCD61EA574DD00C421D3 /* PositionInformationTests.mm */,
                        );
                        path = ios;
                        sourceTree = "<group>";
                                7C83E0531D0A643A00FEBCF3 /* PendingAPIRequestURL.cpp in Sources */,
                                7CCE7EAF1A411A3800447C4C /* PlatformUtilities.cpp in Sources */,
                                0F139E781A423A6B00F590F5 /* PlatformUtilitiesCocoa.mm in Sources */,
+                               F4D7BCD81EA5789800C421D3 /* PositionInformationTests.mm in Sources */,
                                7CCE7EA61A411A0F00447C4C /* PlatformUtilitiesMac.mm in Sources */,
                                7CCE7EA71A411A1300447C4C /* PlatformWebViewMac.mm in Sources */,
                                7CCE7F261A411AF600447C4C /* Preferences.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/ios/PositionInformationTests.mm b/Tools/TestWebKitAPI/Tests/ios/PositionInformationTests.mm
new file mode 100644 (file)
index 0000000..15348de
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if PLATFORM(IOS) && WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/_WKDraggableElementInfo.h>
+
+@implementation _WKDraggableElementInfo (PositionInformationTests)
+
+- (void)expectToBeLink:(BOOL)isLink image:(BOOL)isImage atPoint:(CGPoint)point
+{
+    EXPECT_EQ(isLink, self.isLink);
+    EXPECT_EQ(isImage, self.isImage);
+    EXPECT_EQ(point.x, self.point.x);
+    EXPECT_EQ(point.y, self.point.y);
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+TEST(PositionInformationTests, FindDraggableLinkAtPosition)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
+
+    _WKDraggableElementInfo *information = [webView draggableElementAtPosition:CGPointMake(100, 50)];
+    [information expectToBeLink:YES image:NO atPoint:CGPointMake(100, 50)];
+}
+
+TEST(PositionInformationTests, RequestDraggableLinkAtPosition)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
+
+    __block bool isDone = false;
+    [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *information) {
+        [information expectToBeLink:YES image:NO atPoint:CGPointMake(100, 50)];
+        isDone = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+TEST(PositionInformationTests, FindDraggableLinkAtDifferentPositionWithinRequestBlock)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
+
+    __block bool isDone = false;
+    [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *information) {
+        _WKDraggableElementInfo *synchronousInformation = [webView draggableElementAtPosition:CGPointMake(100, 300)];
+        [synchronousInformation expectToBeLink:NO image:NO atPoint:CGPointMake(100, 300)];
+        [information expectToBeLink:YES image:NO atPoint:CGPointMake(100, 50)];
+        isDone = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+TEST(PositionInformationTests, FindDraggableLinkAtSamePositionWithinRequestBlock)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"link-and-input"];
+
+    __block bool isDone = false;
+    [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *information) {
+        _WKDraggableElementInfo *synchronousInformation = [webView draggableElementAtPosition:CGPointMake(100, 50)];
+        [synchronousInformation expectToBeLink:YES image:NO atPoint:CGPointMake(100, 50)];
+        [information expectToBeLink:YES image:NO atPoint:CGPointMake(100, 50)];
+        isDone = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+TEST(PositionInformationTests, RequestDraggableLinkAtSamePositionWithinRequestBlock)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"image-and-contenteditable"];
+
+    __block bool isDoneWithInner = false;
+    __block bool isDoneWithOuter = false;
+
+    [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *outerInformation) {
+        [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *innerInformation) {
+            [innerInformation expectToBeLink:NO image:YES atPoint:CGPointMake(100, 50)];
+            isDoneWithInner = true;
+        }];
+        [outerInformation expectToBeLink:NO image:YES atPoint:CGPointMake(100, 50)];
+        isDoneWithOuter = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDoneWithOuter);
+    TestWebKitAPI::Util::run(&isDoneWithInner);
+}
+
+TEST(PositionInformationTests, RequestDraggableLinkAtDifferentPositionWithinRequestBlock)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"image-and-contenteditable"];
+
+    __block bool isDoneWithInner = false;
+    __block bool isDoneWithOuter = false;
+
+    [webView requestDraggableElementAtPosition:CGPointMake(100, 50) completionBlock:^(_WKDraggableElementInfo *outerInformation) {
+        [webView requestDraggableElementAtPosition:CGPointMake(100, 300) completionBlock:^(_WKDraggableElementInfo *innerInformation) {
+            [innerInformation expectToBeLink:NO image:NO atPoint:CGPointMake(100, 300)];
+            isDoneWithInner = true;
+        }];
+        [outerInformation expectToBeLink:NO image:YES atPoint:CGPointMake(100, 50)];
+        isDoneWithOuter = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDoneWithOuter);
+    TestWebKitAPI::Util::run(&isDoneWithInner);
+}
+
+} // namespace TestWebKitAPI
+
+#endif