[Cocoa] Add WKWebView SPI to trigger and remove data detection
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 24 Nov 2018 21:06:09 +0000 (21:06 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 24 Nov 2018 21:06:09 +0000 (21:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191918
<rdar://problem/36185051>

Reviewed by Tim Horton.

Source/WebCore:

Add a helper method on DataDetection to remove all data detected links in the given document. See WebKit changes
for more detail.

* editing/cocoa/DataDetection.h:
* editing/cocoa/DataDetection.mm:
(WebCore::DataDetection::removeDataDetectedLinksInDocument):

Source/WebKit:

Adds support for two new WKWebView SPI methods, `-_detectDataWithTypes:completionHandler:` and
`-_removeAllDataDetectedLinks:`, to allow internal WebKit clients to run data detection and add links to data
detected content, or remove all data detected links from the document.

Test: WebKit.AddAndRemoveDataDetectors

* Shared/Cocoa/DataDetectionResult.h:
* Shared/Cocoa/DataDetectionResult.mm:
(WebKit::DataDetectionResult::decode):

Modernize DataDetectionResult's IPC decoding, so that it can be used with reply-based async IPC.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _removeDataDetectedLinks:]):
(-[WKWebView _detectDataWithTypes:completionHandler:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::detectDataInAllFrames):
(WebKit::WebPageProxy::removeDataDetectedLinks):

Add or remove data detected links from each frame in the page, and then propagate the new data detector
results of the main frame to the UI process (this matches current behavior, where the results of -[WKWebView
_dataDetectionResults] only reflects data detection results in the main frame of the page).

* UIProcess/WebPageProxy.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::removeDataDetectedLinks):
(WebKit::WebPage::detectDataInAllFrames):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

Add an API test to exercise the new WebKit SPI.

* TestWebKitAPI/DataDetectorsCoreSPI.h: Added.
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/DataDetection.mm:
(-[WKWebView synchronouslyDetectDataWithTypes:]):
(-[WKWebView synchronouslyRemoveDataDetectedLinks]):
(TEST):
* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(TestWebKitAPI::TEST):
(-[TestWKWebView tagsInBody]): Deleted.
(-[TestWKWebView expectElementTagsInOrder:]): Deleted.
(-[TestWKWebView expectElementCount:tagName:]): Deleted.
(-[TestWKWebView expectElementTag:toComeBefore:]): Deleted.

Rename this from `-expectElementCount:tagName:` to `-expectElementCount:querySelector:`.

* TestWebKitAPI/Tests/WebKitCocoa/data-detectors.html: Added.

Add a new test page containing some content that can be data detected.

* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[WKWebView tagsInBody]):
(-[WKWebView expectElementTagsInOrder:]):
(-[WKWebView expectElementCount:querySelector:]):
(-[WKWebView expectElementTag:toComeBefore:]):

Move some testing helper functions from WKAttachmentTests to a testing category on WKWebView. This allows us to
use `-expectElementCount:querySelector:` in tests outside of WKAttachmentTests.

(-[WKWebView objectByEvaluatingJavaScript:]):
(-[WKWebView objectByEvaluatingJavaScriptWithUserGesture:]):
(-[WKWebView stringByEvaluatingJavaScript:]):

Move some common helper functions from TestWKWebView to a testing category on WKWebView.

(-[TestWKWebView objectByEvaluatingJavaScript:]): Deleted.
(-[TestWKWebView objectByEvaluatingJavaScriptWithUserGesture:]): Deleted.
(-[TestWKWebView stringByEvaluatingJavaScript:]): Deleted.

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

21 files changed:
Source/WebCore/ChangeLog
Source/WebCore/editing/cocoa/DataDetection.h
Source/WebCore/editing/cocoa/DataDetection.mm
Source/WebKit/ChangeLog
Source/WebKit/Shared/Cocoa/DataDetectionResult.h
Source/WebKit/Shared/Cocoa/DataDetectionResult.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/DataDetectorsCoreSPI.h [new file with mode: 0644]
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/DataDetection.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/data-detectors.html [new file with mode: 0644]
Tools/TestWebKitAPI/cocoa/TestWKWebView.h
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm

index d69f43a..a87eb16 100644 (file)
@@ -1,3 +1,18 @@
+2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Add WKWebView SPI to trigger and remove data detection
+        https://bugs.webkit.org/show_bug.cgi?id=191918
+        <rdar://problem/36185051>
+
+        Reviewed by Tim Horton.
+
+        Add a helper method on DataDetection to remove all data detected links in the given document. See WebKit changes
+        for more detail.
+
+        * editing/cocoa/DataDetection.h:
+        * editing/cocoa/DataDetection.mm:
+        (WebCore::DataDetection::removeDataDetectedLinksInDocument):
+
 2018-11-24  Andy Estes  <aestes@apple.com>
 
         [Cocoa] SOFT_LINK_CLASS_FOR_{HEADER,SOURCE} should generate a more concise getter function
index dbe54bd..3a34a09 100644 (file)
@@ -37,6 +37,7 @@ OBJC_CLASS NSDictionary;
 
 namespace WebCore {
 
+class Document;
 class Element;
 class FloatRect;
 class HitTestResult;
@@ -61,6 +62,7 @@ public:
     WEBCORE_EXPORT static RetainPtr<DDActionContext> detectItemAroundHitTestResult(const HitTestResult&, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange);
 #endif
     WEBCORE_EXPORT static NSArray *detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes, NSDictionary *context);
+    WEBCORE_EXPORT static void removeDataDetectedLinksInDocument(Document&);
 #if PLATFORM(IOS_FAMILY)
     WEBCORE_EXPORT static bool canBePresentedByDataDetectors(const URL&);
     WEBCORE_EXPORT static bool isDataDetectorLink(Element&);
index 101ccb9..3a02181 100644 (file)
@@ -429,6 +429,16 @@ static inline CFComparisonResult queryOffsetCompare(DDQueryOffset o1, DDQueryOff
     return kCFCompareEqualTo;
 }
 
+void DataDetection::removeDataDetectedLinksInDocument(Document& document)
+{
+    Vector<Ref<HTMLAnchorElement>> allAnchorElements;
+    for (auto& anchor : descendantsOfType<HTMLAnchorElement>(document))
+        allAnchorElements.append(anchor);
+
+    for (auto& anchor : allAnchorElements)
+        removeResultLinksFromAnchor(anchor.get());
+}
+
 NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes types, NSDictionary *context)
 {
     RetainPtr<DDScannerRef> scanner = adoptCF(softLink_DataDetectorsCore_DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
@@ -647,10 +657,16 @@ NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDe
 }
 
 #else
+
 NSArray *DataDetection::detectContentInRange(RefPtr<Range>&, DataDetectorTypes, NSDictionary *)
 {
     return nil;
 }
+
+void DataDetection::removeDataDetectedLinksInDocument(Document&)
+{
+}
+
 #endif
 
 const String& DataDetection::dataDetectorURLProtocol()
index 7175f6d..92d9b5b 100644 (file)
@@ -1,3 +1,42 @@
+2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Add WKWebView SPI to trigger and remove data detection
+        https://bugs.webkit.org/show_bug.cgi?id=191918
+        <rdar://problem/36185051>
+
+        Reviewed by Tim Horton.
+
+        Adds support for two new WKWebView SPI methods, `-_detectDataWithTypes:completionHandler:` and
+        `-_removeAllDataDetectedLinks:`, to allow internal WebKit clients to run data detection and add links to data
+        detected content, or remove all data detected links from the document.
+
+        Test: WebKit.AddAndRemoveDataDetectors
+
+        * Shared/Cocoa/DataDetectionResult.h:
+        * Shared/Cocoa/DataDetectionResult.mm:
+        (WebKit::DataDetectionResult::decode):
+
+        Modernize DataDetectionResult's IPC decoding, so that it can be used with reply-based async IPC.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _removeDataDetectedLinks:]):
+        (-[WKWebView _detectDataWithTypes:completionHandler:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::detectDataInAllFrames):
+        (WebKit::WebPageProxy::removeDataDetectedLinks):
+
+        Add or remove data detected links from each frame in the page, and then propagate the new data detector
+        results of the main frame to the UI process (this matches current behavior, where the results of -[WKWebView
+        _dataDetectionResults] only reflects data detection results in the main frame of the page).
+
+        * UIProcess/WebPageProxy.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::removeDataDetectedLinks):
+        (WebKit::WebPage::detectDataInAllFrames):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2018-11-24  Andy Estes  <aestes@apple.com>
 
         [Cocoa] SOFT_LINK_CLASS_FOR_{HEADER,SOURCE} should generate a more concise getter function
index 27443ce..a0888f9 100644 (file)
@@ -38,7 +38,7 @@ struct DataDetectionResult {
     RetainPtr<NSArray> results;
 
     void encode(IPC::Encoder&) const;
-    static bool decode(IPC::Decoder&, DataDetectionResult&);
+    static std::optional<DataDetectionResult> decode(IPC::Decoder&);
 };
 
 }
index af42146..dafed9f 100644 (file)
@@ -46,22 +46,23 @@ void DataDetectionResult::encode(IPC::Encoder& encoder) const
     IPC::encode(encoder, (__bridge CFDataRef)archiver.get().encodedData);
 }
 
-bool DataDetectionResult::decode(IPC::Decoder& decoder, DataDetectionResult& result)
+std::optional<DataDetectionResult> DataDetectionResult::decode(IPC::Decoder& decoder)
 {
     RetainPtr<CFDataRef> data;
     if (!IPC::decode(decoder, data))
-        return false;
+        return std::nullopt;
 
+    DataDetectionResult result;
     auto unarchiver = secureUnarchiverFromData((__bridge NSData *)data.get());
     @try {
         result.results = [unarchiver decodeObjectOfClasses:[NSSet setWithArray:@[ [NSArray class], getDDScannerResultClass()] ] forKey:@"dataDetectorResults"];
     } @catch (NSException *exception) {
         LOG_ERROR("Failed to decode NSArray of DDScanResult: %@", exception);
-        return false;
+        return std::nullopt;
     }
     
     [unarchiver finishDecoding];
-    return true;
+    return { WTFMove(result) };
 }
 #endif
 
index 061cc7a..bc3022e 100644 (file)
@@ -5423,10 +5423,39 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     _page->setMuted(coreState);
 }
 
+- (void)_removeDataDetectedLinks:(dispatch_block_t)completion
+{
+#if ENABLE(DATA_DETECTION)
+    _page->removeDataDetectedLinks([completion = makeBlockPtr(completion), page = makeWeakPtr(_page.get())] (auto& result) {
+        if (page)
+            page->setDataDetectionResult(result);
+        if (completion)
+            completion();
+    });
+#else
+    UNUSED_PARAM(completion);
+#endif
+}
+
 #pragma mark iOS-specific methods
 
 #if PLATFORM(IOS_FAMILY)
 
+- (void)_detectDataWithTypes:(WKDataDetectorTypes)types completionHandler:(dispatch_block_t)completion
+{
+#if ENABLE(DATA_DETECTION)
+    _page->detectDataInAllFrames(fromWKDataDetectorTypes(types), [completion = makeBlockPtr(completion), page = makeWeakPtr(_page.get())] (auto& result) {
+        if (page)
+            page->setDataDetectionResult(result);
+        if (completion)
+            completion();
+    });
+#else
+    UNUSED_PARAM(types);
+    UNUSED_PARAM(completion);
+#endif
+}
+
 #if ENABLE(FULLSCREEN_API)
 - (void)removeFromSuperview
 {
index 133a3e7..5965023 100644 (file)
@@ -27,6 +27,7 @@
 
 #if WK_API_ENABLED
 
+#import <WebKit/WKDataDetectorTypes.h>
 #import <WebKit/_WKActivatedElementInfo.h>
 #import <WebKit/_WKAttachment.h>
 #import <WebKit/_WKFindOptions.h>
@@ -193,6 +194,8 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (void)_showSafeBrowsingWarningWithTitle:(NSString *)title warning:(NSString *)warning details:(NSAttributedString *)details completionHandler:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 - (void)_isJITEnabled:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_removeDataDetectedLinks:(dispatch_block_t)completion WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 - (IBAction)_alignCenter:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (IBAction)_alignJustified:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (IBAction)_alignLeft:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
@@ -216,6 +219,8 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (void)_setFontSize:(CGFloat)fontSize sender:(id)sender WK_API_AVAILABLE(ios(WK_IOS_TBA));
 - (void)_setTextColor:(UIColor *)color sender:(id)sender WK_API_AVAILABLE(ios(WK_IOS_TBA));
 
+- (void)_detectDataWithTypes:(WKDataDetectorTypes)types completionHandler:(dispatch_block_t)completion WK_API_AVAILABLE(ios(WK_IOS_TBA));
+
 // DERECATED: The setters of the three following function are deprecated, please use overrideLayoutParameters.
 // Define the smallest size a page take with a regular viewport.
 @property (nonatomic, readonly) CGSize _minimumLayoutSizeOverride;
index 11109dd..302b4e1 100644 (file)
@@ -8243,6 +8243,20 @@ void WebPageProxy::updateCurrentModifierState()
 #endif
 }
 
+#if ENABLE(DATA_DETECTION)
+
+void WebPageProxy::detectDataInAllFrames(WebCore::DataDetectorTypes types, CompletionHandler<void(const DataDetectionResult&)>&& completionHandler)
+{
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::DetectDataInAllFrames(static_cast<uint64_t>(types)), WTFMove(completionHandler), m_pageID);
+}
+
+void WebPageProxy::removeDataDetectedLinks(CompletionHandler<void(const DataDetectionResult&)>&& completionHandler)
+{
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::RemoveDataDetectedLinks(), WTFMove(completionHandler), m_pageID);
+}
+
+#endif
+
 } // namespace WebKit
 
 #undef MERGE_WHEEL_EVENTS
index c393685..ada5c62 100644 (file)
@@ -362,6 +362,8 @@ public:
 
 #if ENABLE(DATA_DETECTION)
     NSArray *dataDetectionResults() { return m_dataDetectionResults.get(); }
+    void detectDataInAllFrames(WebCore::DataDetectorTypes, CompletionHandler<void(const DataDetectionResult&)>&&);
+    void removeDataDetectedLinks(CompletionHandler<void(const DataDetectionResult&)>&&);
 #endif
         
 #if ENABLE(ASYNC_SCROLLING) && PLATFORM(COCOA)
index 4aae14f..10a7c94 100644 (file)
@@ -3385,13 +3385,42 @@ void WebPage::updatePreferences(const WebPreferencesStore& store)
 }
 
 #if ENABLE(DATA_DETECTION)
+
 void WebPage::setDataDetectionResults(NSArray *detectionResults)
 {
     DataDetectionResult dataDetectionResult;
     dataDetectionResult.results = detectionResults;
     send(Messages::WebPageProxy::SetDataDetectionResult(dataDetectionResult));
 }
-#endif
+
+void WebPage::removeDataDetectedLinks(Messages::WebPage::RemoveDataDetectedLinks::AsyncReply&& reply)
+{
+    for (auto frame = makeRefPtr(&m_page->mainFrame()); frame; frame = frame->tree().traverseNext()) {
+        auto document = makeRefPtr(frame->document());
+        if (!document)
+            continue;
+
+        DataDetection::removeDataDetectedLinksInDocument(*document);
+        frame->setDataDetectionResults(nullptr);
+    }
+    reply({ m_page->mainFrame().dataDetectionResults() });
+}
+
+void WebPage::detectDataInAllFrames(uint64_t types, Messages::WebPage::DetectDataInAllFrames::AsyncReply&& reply)
+{
+    auto dataDetectorTypes = static_cast<WebCore::DataDetectorTypes>(types);
+    for (auto frame = makeRefPtr(&m_page->mainFrame()); frame; frame = frame->tree().traverseNext()) {
+        auto document = makeRefPtr(frame->document());
+        if (!document)
+            continue;
+
+        RefPtr<Range> range = Range::create(*document, Position { document.get(), Position::PositionIsBeforeChildren }, Position { document.get(), Position::PositionIsAfterChildren });
+        frame->setDataDetectionResults(DataDetection::detectContentInRange(range, dataDetectorTypes, m_dataDetectionContext.get()));
+    }
+    reply({ m_page->mainFrame().dataDetectionResults() });
+}
+
+#endif // ENABLE(DATA_DETECTION)
 
 #if PLATFORM(COCOA)
 void WebPage::willCommitLayerTree(RemoteLayerTreeTransaction& layerTransaction)
index 5753298..382d1f4 100644 (file)
@@ -48,6 +48,7 @@
 #include "SharedMemory.h"
 #include "UserData.h"
 #include "WebBackForwardListProxy.h"
+#include "WebPageMessages.h"
 #include "WebURLSchemeHandler.h"
 #include "WebUserContentController.h"
 #include <JavaScriptCore/InspectorFrontendChannel.h>
@@ -92,7 +93,6 @@
 
 #if PLATFORM(IOS_FAMILY)
 #include "GestureTypes.h"
-#include "WebPageMessages.h"
 #include <WebCore/IntPointHash.h>
 #include <WebCore/ViewportConfiguration.h>
 #endif
 #include <WebCore/PlatformTouchEvent.h>
 #endif
 
+#if ENABLE(DATA_DETECTION)
+#include <WebCore/DataDetection.h>
+#endif
+
 #if ENABLE(MAC_GESTURE_EVENTS)
 #include <WebKitAdditions/PlatformGestureEventMac.h>
 #endif
@@ -992,6 +996,8 @@ public:
 
 #if ENABLE(DATA_DETECTION)
     void setDataDetectionResults(NSArray *);
+    void detectDataInAllFrames(uint64_t, Messages::WebPage::DetectDataInAllFrames::AsyncReply&&);
+    void removeDataDetectedLinks(Messages::WebPage::RemoveDataDetectedLinks::AsyncReply&&);
 #endif
 
     unsigned extendIncrementalRenderingSuppression();
index 8b296ce..18b0510 100644 (file)
@@ -193,6 +193,11 @@ messages -> WebPage LegacyReceiver {
     PerformDictionaryLookupAtLocation(WebCore::FloatPoint point)
 #endif
 
+#if ENABLE(DATA_DETECTION)
+    DetectDataInAllFrames(uint64_t types) -> (struct WebKit::DataDetectionResult result) Async
+    RemoveDataDetectedLinks() -> (struct WebKit::DataDetectionResult result) Async
+#endif
+
 #if PLATFORM(MAC)
     PerformDictionaryLookupOfCurrentSelection()
 #endif
index e017f04..1b03463 100644 (file)
@@ -1,3 +1,52 @@
+2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Add WKWebView SPI to trigger and remove data detection
+        https://bugs.webkit.org/show_bug.cgi?id=191918
+        <rdar://problem/36185051>
+
+        Reviewed by Tim Horton.
+
+        Add an API test to exercise the new WebKit SPI.
+
+        * TestWebKitAPI/DataDetectorsCoreSPI.h: Added.
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/DataDetection.mm:
+        (-[WKWebView synchronouslyDetectDataWithTypes:]):
+        (-[WKWebView synchronouslyRemoveDataDetectedLinks]):
+        (TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (TestWebKitAPI::TEST):
+        (-[TestWKWebView tagsInBody]): Deleted.
+        (-[TestWKWebView expectElementTagsInOrder:]): Deleted.
+        (-[TestWKWebView expectElementCount:tagName:]): Deleted.
+        (-[TestWKWebView expectElementTag:toComeBefore:]): Deleted.
+
+        Rename this from `-expectElementCount:tagName:` to `-expectElementCount:querySelector:`.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/data-detectors.html: Added.
+
+        Add a new test page containing some content that can be data detected.
+
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[WKWebView tagsInBody]):
+        (-[WKWebView expectElementTagsInOrder:]):
+        (-[WKWebView expectElementCount:querySelector:]):
+        (-[WKWebView expectElementTag:toComeBefore:]):
+
+        Move some testing helper functions from WKAttachmentTests to a testing category on WKWebView. This allows us to
+        use `-expectElementCount:querySelector:` in tests outside of WKAttachmentTests.
+
+        (-[WKWebView objectByEvaluatingJavaScript:]):
+        (-[WKWebView objectByEvaluatingJavaScriptWithUserGesture:]):
+        (-[WKWebView stringByEvaluatingJavaScript:]):
+
+        Move some common helper functions from TestWKWebView to a testing category on WKWebView.
+
+        (-[TestWKWebView objectByEvaluatingJavaScript:]): Deleted.
+        (-[TestWKWebView objectByEvaluatingJavaScriptWithUserGesture:]): Deleted.
+        (-[TestWKWebView stringByEvaluatingJavaScript:]): Deleted.
+
 2018-11-23  Sam Weinig  <sam@webkit.org>
 
         Add raw pointer overloads to ListHashSet via SmartPtr specialized functions
diff --git a/Tools/TestWebKitAPI/DataDetectorsCoreSPI.h b/Tools/TestWebKitAPI/DataDetectorsCoreSPI.h
new file mode 100644 (file)
index 0000000..f29b436
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#if ENABLE(DATA_DETECTION)
+
+#import <pal/spi/cocoa/DataDetectorsCoreSPI.h>
+
+#if !USE(APPLE_INTERNAL_SDK)
+
+@interface DDScannerResult (Private)
+@property (readonly, nonatomic) NSString *value;
+@property (readonly, nonatomic) NSString *type;
+@property (readonly, nonatomic) DDResultCategory category;
+@end
+
+#endif // !USE(APPLE_INTERNAL_SDK)
+
+#endif // ENABLE(DATA_DETECTION)
index ef0bd00..de62df0 100644 (file)
                F46A095A1ED8A6E600D4AA55 /* apple.gif in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30EB1ED28619000482E1 /* apple.gif */; };
                F46A095B1ED8A6E600D4AA55 /* gif-and-file-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */; };
                F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
+               F47DFB2621A878DF00021FB6 /* data-detectors.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47DFB2421A8704A00021FB6 /* data-detectors.html */; };
                F4811E5921940BDE00A5E0FD /* WKWebViewEditActions.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4811E5821940B4400A5E0FD /* WKWebViewEditActions.mm */; };
                F4856CA31E649EA8009D7EE7 /* attachment-element.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4856CA21E6498A8009D7EE7 /* attachment-element.html */; };
                F486B1D01F67952300F34BDD /* DataTransfer-setDragImage.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */; };
                                7AEAD4811E20122700416EFE /* CrossPartitionFileSchemeAccess.html in Copy Resources */,
                                F4AB578A1F65165400DB0DA1 /* custom-draggable-div.html in Copy Resources */,
                                290F4275172A221C00939FF0 /* custom-protocol-sync-xhr.html in Copy Resources */,
+                               F47DFB2621A878DF00021FB6 /* data-detectors.html in Copy Resources */,
                                F486B1D01F67952300F34BDD /* DataTransfer-setDragImage.html in Copy Resources */,
                                F457A9D6202D68AF00F7E9D5 /* DataTransfer.html in Copy Resources */,
                                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */,
                F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-page-contenteditable.html"; sourceTree = "<group>"; };
                F47D30EB1ED28619000482E1 /* apple.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = apple.gif; sourceTree = "<group>"; };
                F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "gif-and-file-input.html"; sourceTree = "<group>"; };
+               F47DFB2421A8704A00021FB6 /* data-detectors.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "data-detectors.html"; sourceTree = "<group>"; };
+               F47DFB2721A885E700021FB6 /* DataDetectorsCoreSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataDetectorsCoreSPI.h; sourceTree = "<group>"; };
                F4811E5821940B4400A5E0FD /* WKWebViewEditActions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKWebViewEditActions.mm; sourceTree = "<group>"; };
                F4856CA21E6498A8009D7EE7 /* attachment-element.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "attachment-element.html"; sourceTree = "<group>"; };
                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "DataTransfer-setDragImage.html"; sourceTree = "<group>"; };
                                A13EBB441B87332B00097110 /* WebProcessPlugIn */,
                                F4517B682054E0AC00C26721 /* ClassMethodSwizzler.h */,
                                F4517B692054E0AC00C26721 /* ClassMethodSwizzler.mm */,
+                               F47DFB2721A885E700021FB6 /* DataDetectorsCoreSPI.h */,
                                F46128B4211C861A00D9FADB /* DragAndDropSimulator.h */,
                                F44D06481F3962E3001A0E29 /* EditingTestHarness.h */,
                                F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */,
                                9B1056421F9047CC00D5583F /* copy-html.html */,
                                9B62630B1F8C2510007EE29B /* copy-url.html */,
                                F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */,
+                               F47DFB2421A8704A00021FB6 /* data-detectors.html */,
                                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */,
                                F457A9B3202D535300F7E9D5 /* DataTransfer.html */,
                                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */,
index bf3c966..9359ed0 100644 (file)
 
 #include "config.h"
 
+#import "DataDetectorsCoreSPI.h"
 #import "PlatformUtilities.h"
 #import "Test.h"
 #import "TestNavigationDelegate.h"
+#import "TestWKWebView.h"
 #import <WebKit/WebKit.h>
 #import <wtf/RetainPtr.h>
 
 #if WK_API_ENABLED && PLATFORM(IOS_FAMILY)
 
+@interface WKWebView (DataDetection)
+- (void)synchronouslyDetectDataWithTypes:(WKDataDetectorTypes)types;
+- (void)synchronouslyRemoveDataDetectedLinks;
+@end
+
+@implementation WKWebView (DataDetection)
+
+- (void)synchronouslyDetectDataWithTypes:(WKDataDetectorTypes)types
+{
+    __block bool done = false;
+    [self _detectDataWithTypes:types completionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+}
+
+- (void)synchronouslyRemoveDataDetectedLinks
+{
+    __block bool done = false;
+    [self _removeDataDetectedLinks:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+}
+
+@end
+
 static bool ranScript;
 
 @interface DataDetectionUIDelegate : NSObject <WKUIDelegate>
@@ -97,4 +126,39 @@ TEST(WebKit, DISABLED_DataDetectionReferenceDate)
     expectLinkCount(webView.get(), @"yesterday at 6PM", 1);
 }
 
+TEST(WebKit, AddAndRemoveDataDetectors)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"data-detectors"];
+    [webView expectElementCount:0 querySelector:@"a[x-apple-data-detectors=true]"];
+    EXPECT_EQ(0U, [webView _dataDetectionResults].count);
+
+    auto checkDataDetectionResults = [] (NSArray<DDScannerResult *> *results) {
+        EXPECT_EQ(3U, results.count);
+        EXPECT_EQ(DDResultCategoryUnknown, results[0].category);
+        EXPECT_TRUE([results[0].value containsString:@"+1-234-567-8900"]);
+        EXPECT_TRUE([results[0].value containsString:@"https://www.apple.com"]);
+        EXPECT_TRUE([results[0].value containsString:@"2 Apple Park Way, Cupertino 95014"]);
+        EXPECT_WK_STREQ("SignatureBlock", results[0].type);
+        EXPECT_EQ(DDResultCategoryCalendarEvent, results[1].category);
+        EXPECT_WK_STREQ("Date", results[1].type);
+        EXPECT_WK_STREQ("December 21, 2021", results[1].value);
+        EXPECT_EQ(DDResultCategoryMisc, results[2].category);
+        EXPECT_WK_STREQ("FlightInformation", results[2].type);
+        EXPECT_WK_STREQ("AC780", results[2].value);
+    };
+
+    [webView synchronouslyDetectDataWithTypes:WKDataDetectorTypeAll];
+    [webView expectElementCount:5 querySelector:@"a[x-apple-data-detectors=true]"];
+    checkDataDetectionResults([webView _dataDetectionResults]);
+
+    [webView synchronouslyRemoveDataDetectedLinks];
+    [webView expectElementCount:0 querySelector:@"a[x-apple-data-detectors=true]"];
+    EXPECT_EQ(0U, [webView _dataDetectionResults].count);
+
+    [webView synchronouslyDetectDataWithTypes:WKDataDetectorTypeAddress | WKDataDetectorTypePhoneNumber];
+    [webView expectElementCount:2 querySelector:@"a[x-apple-data-detectors=true]"];
+    checkDataDetectionResults([webView _dataDetectionResults]);
+}
+
 #endif
index e2ce72a..d48f43e 100644 (file)
@@ -215,37 +215,6 @@ static NSData *testPDFData()
 
 @implementation TestWKWebView (AttachmentTesting)
 
-- (NSArray<NSString *> *)tagsInBody
-{
-    return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
-}
-
-- (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
-{
-    auto remainingTags = adoptNS([tagNames mutableCopy]);
-    NSArray<NSString *> *tagsInBody = self.tagsInBody;
-    for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
-        if ([tag isEqualToString:[remainingTags lastObject]])
-            [remainingTags removeLastObject];
-        if (![remainingTags count])
-            break;
-    }
-    EXPECT_EQ([remainingTags count], 0U);
-    if ([remainingTags count])
-        NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
-}
-
-- (void)expectElementCount:(NSInteger)count tagName:(NSString *)tagName
-{
-    NSString *script = [NSString stringWithFormat:@"document.querySelectorAll('%@').length", tagName];
-    EXPECT_EQ(count, [self stringByEvaluatingJavaScript:script].integerValue);
-}
-
-- (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
-{
-    [self expectElementTagsInOrder:@[tagName, otherTagName]];
-}
-
 - (_WKAttachment *)synchronouslyInsertAttachmentWithFileWrapper:(NSFileWrapper *)fileWrapper contentType:(NSString *)contentType
 {
     __block bool done = false;
@@ -831,7 +800,7 @@ TEST(WKAttachmentTests, InsertFolderAndFileWithUnknownExtension)
     }
 
     auto checkAttachmentConsistency = [webView, file, folder] (_WKAttachment *expectedFileAttachment, _WKAttachment *expectedFolderAttachment) {
-        [webView expectElementCount:2 tagName:@"ATTACHMENT"];
+        [webView expectElementCount:2 querySelector:@"ATTACHMENT"];
         EXPECT_TRUE(UTTypeConformsTo((__bridge CFStringRef)[webView valueOfAttribute:@"type" forQuerySelector:@"attachment[title=folder]"], kUTTypeDirectory));
         EXPECT_TRUE(UTTypeConformsTo((__bridge CFStringRef)[webView valueOfAttribute:@"type" forQuerySelector:@"attachment[title^=test]"], kUTTypeData));
         EXPECT_WK_STREQ(expectedFileAttachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title^=test]').uniqueIdentifier"]);
@@ -928,7 +897,7 @@ TEST(WKAttachmentTests, RemoveNewlinesBeforePastedImage)
         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
         observer.expectAttachmentUpdates(@[ ], @[ ]);
-        [webView expectElementCount:0 tagName:@"BR"];
+        [webView expectElementCount:0 querySelector:@"BR"];
     }
 }
 
@@ -971,13 +940,13 @@ TEST(WKAttachmentTests, MovePastedImageByDragging)
     [webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
     [webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
     [webView expectElementTag:@"IMG" toComeBefore:@"STRONG"];
-    [webView expectElementCount:1 tagName:@"IMG"];
+    [webView expectElementCount:1 querySelector:@"IMG"];
 
     // Drag the attachment element to somewhere below the strong text.
     [simulator runFrom:CGPointMake(50, 50) to:CGPointMake(50, 350)];
 
     [webView expectElementTag:@"STRONG" toComeBefore:@"IMG"];
-    [webView expectElementCount:1 tagName:@"IMG"];
+    [webView expectElementCount:1 querySelector:@"IMG"];
     EXPECT_EQ([simulator insertedAttachments].count, [simulator removedAttachments].count);
 
     [simulator endDataTransfer];
@@ -1034,8 +1003,8 @@ TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachment
     }
 
     EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment);
-    [webView expectElementCount:2 tagName:@"ATTACHMENT"];
-    [webView expectElementCount:1 tagName:@"IMG"];
+    [webView expectElementCount:2 querySelector:@"ATTACHMENT"];
+    [webView expectElementCount:1 querySelector:@"IMG"];
     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
 
     NSString *zipAttachmentType = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"];
@@ -1338,7 +1307,7 @@ TEST(WKAttachmentTests, PasteWebArchiveContainingImages)
 
     ObserveAttachmentUpdatesForScope observer(webView.get());
     [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
-    [webView expectElementCount:2 tagName:@"IMG"];
+    [webView expectElementCount:2 querySelector:@"IMG"];
 
     for (_WKAttachment *attachment in observer.observer().inserted) {
         if ([attachment.info.contentType isEqualToString:@"image/png"])
@@ -1473,7 +1442,7 @@ TEST(WKAttachmentTests, CopyAndPasteBetweenWebViews)
     auto secondWebView = webViewForTestingAttachments();
     ObserveAttachmentUpdatesForScope observer(secondWebView.get());
     [secondWebView paste:nil];
-    [secondWebView expectElementCount:3 tagName:@"attachment"];
+    [secondWebView expectElementCount:3 querySelector:@"attachment"];
     EXPECT_EQ(3U, observer.observer().inserted.count);
 
     NSString *plainFileIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title^=test]').uniqueIdentifier"];
@@ -1516,8 +1485,8 @@ TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
         EXPECT_EQ(2U, [insertedAttachments count]);
     }
 
-    [webView expectElementCount:1 tagName:@"ATTACHMENT"];
-    [webView expectElementCount:1 tagName:@"IMG"];
+    [webView expectElementCount:1 querySelector:@"ATTACHMENT"];
+    [webView expectElementCount:1 querySelector:@"IMG"];
     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('type')"]);
     EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('title')"]);
 
@@ -1552,8 +1521,8 @@ TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
 
     [simulator runFrom:CGPointMake(0, 0) to:CGPointMake(50, 50)];
 
-    [webView expectElementCount:1 tagName:@"ATTACHMENT"];
-    [webView expectElementCount:1 tagName:@"IMG"];
+    [webView expectElementCount:1 querySelector:@"ATTACHMENT"];
+    [webView expectElementCount:1 querySelector:@"IMG"];
     EXPECT_EQ(2U, [simulator insertedAttachments].count);
 
     auto insertedAttachments = retainPtr([simulator insertedAttachments]);
@@ -1573,8 +1542,8 @@ TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
     [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
     auto removedAttachments = retainPtr([simulator removedAttachments]);
     EXPECT_EQ(2U, [removedAttachments count]);
-    [webView expectElementCount:0 tagName:@"ATTACHMENT"];
-    [webView expectElementCount:0 tagName:@"IMG"];
+    [webView expectElementCount:0 querySelector:@"ATTACHMENT"];
+    [webView expectElementCount:0 querySelector:@"IMG"];
     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
 }
@@ -1681,7 +1650,7 @@ TEST(WKAttachmentTestsIOS, InsertDroppedRichAndPlainTextFilesAsAttachments)
     for (_WKAttachment *attachment in [dragAndDropSimulator insertedAttachments])
         EXPECT_GT([attachment info].data.length, 0U);
 
-    [webView expectElementCount:2 tagName:@"ATTACHMENT"];
+    [webView expectElementCount:2 querySelector:@"ATTACHMENT"];
     EXPECT_WK_STREQ("hello.rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
     EXPECT_WK_STREQ("text/rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
     EXPECT_WK_STREQ("world.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
@@ -1706,7 +1675,7 @@ TEST(WKAttachmentTestsIOS, InsertDroppedZipArchiveAsAttachment)
     EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
     [[dragAndDropSimulator insertedAttachments].firstObject expectRequestedDataToBe:data];
-    [webView expectElementCount:1 tagName:@"ATTACHMENT"];
+    [webView expectElementCount:1 querySelector:@"ATTACHMENT"];
     EXPECT_WK_STREQ("archive.zip", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
     EXPECT_WK_STREQ("application/zip", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
 }
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/data-detectors.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/data-detectors.html
new file mode 100644 (file)
index 0000000..f9f87af
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<body>
+<table>
+<tbody>
+<tr>
+    <td>Telephone number</td>
+    <td>+1-234-567-8900</td>
+</tr>
+<tr>
+    <td>Link</td>
+    <td>https://www.apple.com</td>
+</tr>
+<tr>
+    <td>Address</td>
+    <td>2 Apple Park Way, Cupertino 95014</td>
+</tr>
+<tr>
+    <td>Calendar event</td>
+    <td>December 21, 2021</td>
+</tr>
+<tr>
+    <td>Flight number</td>
+    <td>AC780</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
index 5488369..883abd1 100644 (file)
 @end
 
 @interface WKWebView (TestWebKitAPI)
+@property (nonatomic, readonly) NSArray<NSString *> *tagsInBody;
 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument;
+- (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames;
+- (void)expectElementCount:(NSInteger)count querySelector:(NSString *)querySelector;
+- (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName;
+- (NSString *)stringByEvaluatingJavaScript:(NSString *)script;
+- (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script;
+- (id)objectByEvaluatingJavaScript:(NSString *)script;
 @end
 
 @interface TestMessageHandler : NSObject <WKScriptMessageHandler>
@@ -64,9 +71,6 @@
 - (void)synchronouslyLoadHTMLString:(NSString *)html;
 - (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url;
 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName;
-- (id)objectByEvaluatingJavaScript:(NSString *)script;
-- (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script;
-- (NSString *)stringByEvaluatingJavaScript:(NSString *)script;
 - (void)waitForMessage:(NSString *)message;
 - (void)performAfterLoading:(dispatch_block_t)actions;
 - (void)waitForNextPresentationUpdate;
index ccbeff8..e8eadf8 100644 (file)
@@ -78,6 +78,72 @@ SOFT_LINK_CLASS(UIKit, UIWindow)
     return success;
 }
 
+- (NSArray<NSString *> *)tagsInBody
+{
+    return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
+}
+
+- (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
+{
+    auto remainingTags = adoptNS([tagNames mutableCopy]);
+    NSArray<NSString *> *tagsInBody = self.tagsInBody;
+    for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
+        if ([tag isEqualToString:[remainingTags lastObject]])
+            [remainingTags removeLastObject];
+        if (![remainingTags count])
+            break;
+    }
+    EXPECT_EQ([remainingTags count], 0U);
+    if ([remainingTags count])
+        NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
+}
+
+- (void)expectElementCount:(NSInteger)count querySelector:(NSString *)querySelector
+{
+    NSString *script = [NSString stringWithFormat:@"document.querySelectorAll('%@').length", querySelector];
+    EXPECT_EQ(count, [self stringByEvaluatingJavaScript:script].integerValue);
+}
+
+- (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
+{
+    [self expectElementTagsInOrder:@[tagName, otherTagName]];
+}
+
+- (id)objectByEvaluatingJavaScript:(NSString *)script
+{
+    bool isWaitingForJavaScript = false;
+    RetainPtr<id> evalResult;
+    [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) {
+        evalResult = result;
+        isWaitingForJavaScript = true;
+        EXPECT_TRUE(!error);
+        if (error)
+            NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
+    }];
+    TestWebKitAPI::Util::run(&isWaitingForJavaScript);
+    return evalResult.autorelease();
+}
+
+- (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script
+{
+    bool isWaitingForJavaScript = false;
+    RetainPtr<id> evalResult;
+    [self evaluateJavaScript:script completionHandler:[&] (id result, NSError *error) {
+        evalResult = result;
+        isWaitingForJavaScript = true;
+        EXPECT_TRUE(!error);
+        if (error)
+            NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
+    }];
+    TestWebKitAPI::Util::run(&isWaitingForJavaScript);
+    return evalResult.autorelease();
+}
+
+- (NSString *)stringByEvaluatingJavaScript:(NSString *)script
+{
+    return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
+}
+
 @end
 
 @implementation TestMessageHandler {
@@ -289,41 +355,6 @@ static UICalloutBar *suppressUICalloutBar()
     [self _test_waitForDidFinishNavigation];
 }
 
-- (id)objectByEvaluatingJavaScript:(NSString *)script
-{
-    bool isWaitingForJavaScript = false;
-    RetainPtr<id> evalResult;
-    [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) {
-        evalResult = result;
-        isWaitingForJavaScript = true;
-        EXPECT_TRUE(!error);
-        if (error)
-            NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
-    }];
-    TestWebKitAPI::Util::run(&isWaitingForJavaScript);
-    return evalResult.autorelease();
-}
-
-- (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script
-{
-    bool isWaitingForJavaScript = false;
-    RetainPtr<id> evalResult;
-    [self evaluateJavaScript:script completionHandler:[&] (id result, NSError *error) {
-        evalResult = result;
-        isWaitingForJavaScript = true;
-        EXPECT_TRUE(!error);
-        if (error)
-            NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
-    }];
-    TestWebKitAPI::Util::run(&isWaitingForJavaScript);
-    return evalResult.autorelease();
-}
-
-- (NSString *)stringByEvaluatingJavaScript:(NSString *)script
-{
-    return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
-}
-
 - (void)waitForMessage:(NSString *)message
 {
     __block bool isDoneWaiting = false;