| /* |
| * Copyright (C) 2016-2020 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 "config.h" |
| #import "TestWKWebView.h" |
| |
| #ifdef __cplusplus |
| |
| #import "CGImagePixelReader.h" |
| #import "ClassMethodSwizzler.h" |
| #import "HostWindowManager.h" |
| #import "InstanceMethodSwizzler.h" |
| #import "PlatformUtilities.h" |
| #import "Test.h" |
| #import "TestCocoa.h" |
| #import "TestNavigationDelegate.h" |
| #import "Utilities.h" |
| |
| #import <WebCore/Color.h> |
| #import <WebKit/WKContentWorld.h> |
| #import <WebKit/WKUIDelegate.h> |
| #import <WebKit/WKWebViewConfigurationPrivate.h> |
| #import <WebKit/WKWebViewPrivateForTesting.h> |
| #import <WebKit/WebKitPrivate.h> |
| #import <WebKit/_WKActivatedElementInfo.h> |
| #import <WebKit/_WKContextMenuElementInfo.h> |
| #import <WebKit/_WKFrameTreeNode.h> |
| #import <WebKit/_WKJSHandle.h> |
| #import <WebKit/_WKProcessPoolConfiguration.h> |
| #import <WebKit/_WKTextInputContext.h> |
| #import <objc/runtime.h> |
| #import <pal/spi/ios/BrowserEngineKitSPI.h> |
| #import <wtf/BlockPtr.h> |
| #import <wtf/Deque.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/Vector.h> |
| #import <wtf/WeakObjCPtr.h> |
| #import <wtf/cocoa/TypeCastsCocoa.h> |
| |
| #endif |
| |
| #if PLATFORM(MAC) |
| #import <AppKit/AppKit.h> |
| #import <Carbon/Carbon.h> |
| #endif |
| |
| #ifdef __cplusplus |
| |
| #if PLATFORM(IOS_FAMILY) |
| #import "UIKitSPIForTesting.h" |
| #import <MobileCoreServices/MobileCoreServices.h> |
| #import <wtf/SoftLinking.h> |
| SOFT_LINK_FRAMEWORK(UIKit) |
| SOFT_LINK_CLASS(UIKit, UIWindow) |
| |
| #if USE(BROWSERENGINEKIT) |
| // FIXME: This workaround can be removed once the fix for rdar://120390585 lands in the SDK. |
| SOFT_LINK_CLASS(UIKit, UIKeyEvent) |
| #endif |
| |
| static NSString *overrideBundleIdentifier(id, SEL) |
| { |
| return @"com.apple.TestWebKitAPI"; |
| } |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| @interface WKWebView (TextServices) |
| - (void)_lookup:(id)sender; |
| @end |
| |
| #if PLATFORM(IOS_FAMILY) |
| @interface WKWebView (UIScrollViewDelegate) |
| - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; |
| - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view; |
| @end |
| #endif |
| |
| @implementation WKWebView (TestWebKitAPI) |
| |
| - (void)loadTestPageNamed:(NSString *)pageName |
| { |
| NSURLRequest *request = [NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:pageName withExtension:@"html"]]; |
| [self loadRequest:request]; |
| } |
| |
| - (void)synchronouslyGoBack |
| { |
| [self goBack]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyGoForward |
| { |
| [self goForward]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyLoadRequest:(NSURLRequest *)request |
| { |
| [self loadRequest:request]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyLoadRequest:(NSURLRequest *)request preferences:(WKWebpagePreferences *)preferences |
| { |
| [self loadRequest:request]; |
| [self _test_waitForDidFinishNavigationWithPreferences:preferences]; |
| } |
| |
| - (void)synchronouslyLoadSimulatedRequest:(NSURLRequest *)request responseHTMLString:(NSString *)htmlString |
| { |
| [self loadSimulatedRequest:request responseHTMLString:htmlString]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyLoadRequestIgnoringSSLErrors:(NSURLRequest *)request |
| { |
| [self loadRequest:request]; |
| [self _test_waitForDidFinishNavigationWhileIgnoringSSLErrors]; |
| } |
| |
| - (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url |
| { |
| [self loadHTMLString:html baseURL:url]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyLoadHTMLString:(NSString *)html |
| { |
| [self synchronouslyLoadHTMLString:html baseURL:NSBundle.test_resourcesBundle.resourceURL]; |
| } |
| |
| - (void)synchronouslyLoadHTMLString:(NSString *)html preferences:(WKWebpagePreferences *)preferences |
| { |
| [self loadHTMLString:html baseURL:NSBundle.test_resourcesBundle.resourceURL]; |
| [self _test_waitForDidFinishNavigationWithPreferences:preferences]; |
| } |
| |
| - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName |
| { |
| [self loadTestPageNamed:pageName]; |
| [self _test_waitForDidFinishNavigation]; |
| } |
| |
| - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName asStringWithBaseURL:(NSURL *)url |
| { |
| [self synchronouslyLoadHTMLString:[NSString stringWithContentsOfURL:[NSBundle.test_resourcesBundle URLForResource:pageName withExtension:@"html"] encoding:NSUTF8StringEncoding error:nil] baseURL:url]; |
| } |
| |
| - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName preferences:(WKWebpagePreferences *)preferences |
| { |
| [self loadTestPageNamed:pageName]; |
| [self _test_waitForDidFinishNavigationWithPreferences:preferences]; |
| } |
| |
| - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument |
| { |
| __block bool done = false; |
| __block bool success; |
| [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) { |
| done = true; |
| success = completionSuccess; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return success; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| - (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect |
| { |
| __block bool finished = false; |
| __block RetainPtr<NSArray<_WKTextInputContext *>> result; |
| [self _requestTextInputContextsInRect:rect completionHandler:^(NSArray<_WKTextInputContext *> *contexts) { |
| result = contexts; |
| finished = true; |
| }]; |
| TestWebKitAPI::Util::run(&finished); |
| return result.autorelease(); |
| } |
| |
| - (void)defineSelection |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| [self lookup:nil]; |
| return; |
| } |
| #endif |
| [self _lookup:nil]; |
| } |
| |
| - (void)shareSelection |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| [self share:nil]; |
| return; |
| } |
| #endif |
| [self _share:nil]; |
| } |
| |
| - (BOOL)hasAsyncTextInput |
| { |
| #if USE(BROWSERENGINEKIT) |
| return !!self.asyncTextInput; |
| #else |
| return NO; |
| #endif |
| } |
| |
| - (CGRect)selectionClipRect |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) |
| return asyncTextInput.selectionClipRect; |
| #endif |
| return self.textInputContentView._selectionClipRect; |
| } |
| |
| - (void)moveSelectionToStartOfParagraph |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput moveInStorageDirection:UITextStorageDirectionBackward byGranularity:UITextGranularityParagraph]; |
| return; |
| } |
| #endif |
| [self.textInputContentView _moveToStartOfParagraph:NO withHistory:nil]; |
| } |
| |
| - (void)extendSelectionToStartOfParagraph |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput extendInStorageDirection:UITextStorageDirectionBackward byGranularity:UITextGranularityParagraph]; |
| return; |
| } |
| #endif |
| [self.textInputContentView _moveToStartOfParagraph:YES withHistory:nil]; |
| } |
| |
| - (void)moveSelectionToEndOfParagraph |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput moveInStorageDirection:UITextStorageDirectionForward byGranularity:UITextGranularityParagraph]; |
| return; |
| } |
| #endif |
| [self.textInputContentView _moveToEndOfParagraph:NO withHistory:nil]; |
| } |
| |
| - (void)extendSelectionToEndOfParagraph |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput extendInStorageDirection:UITextStorageDirectionForward byGranularity:UITextGranularityParagraph]; |
| return; |
| } |
| #endif |
| [self.textInputContentView _moveToEndOfParagraph:YES withHistory:nil]; |
| } |
| |
| - (void)insertTextSuggestion:(UITextSuggestion *)textSuggestion |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (id<BETextInput> asyncTextInput = self.asyncTextInput) { |
| RetainPtr beTextSuggestion = adoptNS([[BETextSuggestion alloc] _initWithUIKitTextSuggestion:textSuggestion]); |
| [asyncTextInput insertTextSuggestion:beTextSuggestion.get()]; |
| return; |
| } |
| #endif |
| [self.textInputContentView insertTextSuggestion:textSuggestion]; |
| } |
| |
| #if HAVE(UI_WK_DOCUMENT_CONTEXT) |
| |
| - (UIWKDocumentContext *)synchronouslyRequestDocumentContext:(UIWKDocumentRequest *)request |
| { |
| __block bool finished = false; |
| __block RetainPtr<id> result; |
| [self.textInputContentView requestDocumentContext:request completionHandler:^(UIWKDocumentContext *context) { |
| result = context; |
| finished = true; |
| }]; |
| TestWebKitAPI::Util::run(&finished); |
| |
| #if USE(BROWSERENGINEKIT) |
| if (RetainPtr context = dynamic_objc_cast<BETextDocumentContext>(result.get())) |
| return [context _uikitDocumentContext]; |
| #endif |
| |
| if (RetainPtr context = dynamic_objc_cast<UIWKDocumentContext>(result.get())) |
| return context.autorelease(); |
| |
| return nil; |
| } |
| |
| #endif // HAVE(UI_WK_DOCUMENT_CONTEXT) |
| |
| #if USE(BROWSERENGINEKIT) |
| |
| - (id<BETextInput>)asyncTextInput |
| { |
| static BOOL conformsToAsyncTextInput = class_conformsToProtocol(NSClassFromString(@"WKContentView"), @protocol(BETextInput)); |
| if (!conformsToAsyncTextInput) |
| return nil; |
| |
| return (id<BETextInput>)self.textInputContentView; |
| } |
| |
| - (id<BEExtendedTextInputTraits>)extendedTextInputTraits |
| { |
| return self.asyncTextInput.extendedTextInputTraits; |
| } |
| |
| #endif // USE(BROWSERENGINEKIT) |
| |
| - (std::pair<CGRect, CGRect>)autocorrectionRectsForString:(NSString *)string |
| { |
| std::pair<CGRect, CGRect> result; |
| bool done = false; |
| #if USE(BROWSERENGINEKIT) |
| if (auto asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput requestTextRectsForString:string withCompletionHandler:[&](NSArray<UITextSelectionRect *> *rects) { |
| result = { rects.firstObject.rect, rects.lastObject.rect }; |
| done = true; |
| }]; |
| } else |
| #endif |
| { |
| [self.textInputContentView requestAutocorrectionRectsForString:string withCompletionHandler:[&](UIWKAutocorrectionRects *rects) { |
| result = { rects.firstRect, rects.lastRect }; |
| done = true; |
| }]; |
| } |
| TestWebKitAPI::Util::run(&done); |
| return result; |
| } |
| |
| - (TestWebKitAPI::AutocorrectionContext)autocorrectionContext |
| { |
| TestWebKitAPI::AutocorrectionContext result; |
| bool done = false; |
| #if USE(BROWSERENGINEKIT) |
| if (auto asyncTextInput = self.asyncTextInput) { |
| [asyncTextInput requestTextContextForAutocorrectionWithCompletionHandler:[&](WKBETextDocumentContext *context) { |
| auto uiContext = dynamic_objc_cast<UIWKDocumentContext>(context); |
| if (auto seContext = dynamic_objc_cast<WKBETextDocumentContext>(context)) |
| uiContext = seContext._uikitDocumentContext; |
| result = { |
| dynamic_objc_cast<NSString>(uiContext.contextBefore), |
| dynamic_objc_cast<NSString>(uiContext.selectedText), |
| dynamic_objc_cast<NSString>(uiContext.contextAfter), |
| dynamic_objc_cast<NSString>(uiContext.markedText), |
| uiContext.selectedRangeInMarkedText, |
| }; |
| done = true; |
| }]; |
| } else |
| #endif // USE(BROWSERENGINEKIT) |
| { |
| [self.textInputContentView requestAutocorrectionContextWithCompletionHandler:[&](UIWKAutocorrectionContext *context) { |
| result = { context.contextBeforeSelection, context.selectedText, context.contextAfterSelection, context.markedText, context.rangeInMarkedText }; |
| done = true; |
| }]; |
| } |
| TestWebKitAPI::Util::run(&done); |
| return result; |
| } |
| |
| - (id<UITextInputTraits_Private>)effectiveTextInputTraits |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) |
| return static_cast<id<UITextInputTraits_Private>>(self.extendedTextInputTraits); |
| #endif |
| return static_cast<id<UITextInputTraits_Private>>(self.textInputContentView.textInputTraits); |
| } |
| |
| - (void)replaceText:(NSString *)input withText:(NSString *)correction shouldUnderline:(BOOL)shouldUnderline completion:(void(^)())completion |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| auto options = shouldUnderline ? BETextReplacementOptionsAddUnderline : BETextReplacementOptionsNone; |
| [self.asyncTextInput replaceText:input withText:correction options:options completionHandler:[completion = makeBlockPtr(completion)](NSArray<UITextSelectionRect *> *) { |
| completion(); |
| }]; |
| return; |
| } |
| #endif |
| |
| [self.textInputContentView applyAutocorrection:correction toString:input shouldUnderline:shouldUnderline withCompletionHandler:[completion = makeBlockPtr(completion)](UIWKAutocorrectionRects *) { |
| completion(); |
| }]; |
| } |
| |
| - (void)insertText:(NSString *)primaryString alternatives:(NSArray<NSString *> *)alternativeStrings |
| { |
| if (!alternativeStrings.count) { |
| [self.textInputContentView insertText:primaryString]; |
| return; |
| } |
| |
| #if USE(BROWSERENGINEKIT) |
| auto nsAlternatives = adoptNS([[NSTextAlternatives alloc] initWithPrimaryString:primaryString alternativeStrings:alternativeStrings]); |
| auto alternatives = adoptNS([[BETextAlternatives alloc] _initWithNSTextAlternatives:nsAlternatives.get()]); |
| [self.asyncTextInput insertTextAlternatives:alternatives.get()]; |
| #else |
| [self.textInputContentView insertText:primaryString alternatives:alternativeStrings style:UITextAlternativeStyleNone]; |
| #endif |
| } |
| |
| #if USE(BROWSERENGINEKIT) |
| |
| static RetainPtr<BEKeyEntry> wrap(WebEvent *webEvent) |
| { |
| auto uiKeyEvent = adoptNS([allocUIKeyEventInstance() initWithWebEvent:webEvent]); |
| return adoptNS([[BEKeyEntry alloc] _initWithUIKitKeyEvent:uiKeyEvent.get()]); |
| } |
| |
| static WebEvent *unwrap(BEKeyEntry *event) |
| { |
| auto uiEvent = retainPtr(event._uikitKeyEvent); |
| return [uiEvent webEvent]; |
| } |
| |
| #endif // USE(BROWSERENGINEKIT) |
| |
| #if HAVE(UI_WK_DOCUMENT_CONTEXT) |
| |
| - (void)synchronouslyAdjustSelectionWithDelta:(NSRange)range |
| { |
| __block bool finished = false; |
| auto completion = ^{ |
| finished = true; |
| }; |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| BEDirectionalTextRange directionalRange { |
| static_cast<NSInteger>(range.location), |
| static_cast<NSInteger>(range.length) |
| }; |
| [self.asyncTextInput adjustSelectionByRange:directionalRange completionHandler:completion]; |
| } else |
| #endif |
| [self.textInputContentView adjustSelectionWithDelta:range completionHandler:completion]; |
| TestWebKitAPI::Util::run(&finished); |
| } |
| |
| #endif // HAVE(UI_WK_DOCUMENT_CONTEXT) |
| |
| - (void)selectTextForContextMenuWithLocationInView:(CGPoint)locationInView completion:(void(^)(BOOL shouldPresent))completion |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| [self.asyncTextInput selectTextForEditMenuWithLocationInView:locationInView completionHandler:[completion = makeBlockPtr(completion)](BOOL shouldPresent, NSString *, NSRange) { |
| completion(shouldPresent); |
| }]; |
| return; |
| } |
| #endif |
| [self.textInputContentView prepareSelectionForContextMenuWithLocationInView:locationInView completionHandler:[completion = makeBlockPtr(completion)](BOOL shouldPresent, RVItem *) { |
| completion(shouldPresent); |
| }]; |
| } |
| |
| - (void)selectTextInGranularity:(UITextGranularity)granularity atPoint:(CGPoint)pointInRootView |
| { |
| bool done = false; |
| auto completion = makeBlockPtr([&] { |
| done = true; |
| }); |
| |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) |
| [self.asyncTextInput selectTextInGranularity:granularity atPoint:pointInRootView completionHandler:completion.get()]; |
| else |
| #endif |
| [self.textInputContentView selectTextWithGranularity:granularity atPoint:pointInRootView completionHandler:completion.get()]; |
| |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| - (void)handleKeyEvent:(WebEvent *)event completion:(void (^)(WebEvent *theEvent, BOOL handled))completion |
| { |
| #if USE(BROWSERENGINEKIT) |
| if (self.hasAsyncTextInput) { |
| [self.asyncTextInput handleKeyEntry:wrap(event).get() withCompletionHandler:[completion = makeBlockPtr(completion)](BEKeyEntry *event, BOOL handled) { |
| completion(unwrap(event), handled); |
| }]; |
| return; |
| } |
| #endif |
| [self.textInputContentView handleKeyWebEvent:event withCompletionHandler:completion]; |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| - (NSUInteger)gpuToWebProcessConnectionCount |
| { |
| __block bool done = false; |
| __block NSUInteger count = 0; |
| [self _gpuToWebProcessConnectionCountForTesting:^(NSUInteger result) { |
| done = true; |
| count = result; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return count; |
| } |
| |
| - (NSUInteger)modelProcessModelPlayerCount |
| { |
| __block bool done = false; |
| __block NSUInteger count = 0; |
| [self _modelProcessModelPlayerCountForTesting:^(NSUInteger result) { |
| done = true; |
| count = result; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return count; |
| } |
| |
| - (NSString *)contentsAsString |
| { |
| __block bool done = false; |
| __block RetainPtr<NSString> result; |
| [self _getContentsAsStringWithCompletionHandler:^(NSString *contents, NSError *error) { |
| result = contents; |
| done = true; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return result.autorelease(); |
| } |
| |
| - (NSData *)contentsAsWebArchive |
| { |
| __block bool done = false; |
| __block RetainPtr<NSData> result; |
| [self createWebArchiveDataWithCompletionHandler:^(NSData *contents, NSError *error) { |
| result = contents; |
| done = true; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return result.autorelease(); |
| } |
| |
| - (NSArray<NSString *> *)tagsInBody |
| { |
| return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"]; |
| } |
| |
| - (NSString *)selectedText |
| { |
| return [self stringByEvaluatingJavaScript:@"getSelection().toString()"]; |
| } |
| |
| - (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]]; |
| } |
| |
| - (BOOL)evaluateMediaQuery:(NSString *)query |
| { |
| return [[self objectByEvaluatingJavaScript:[NSString stringWithFormat:@"window.matchMedia(\"(%@)\").matches", query]] boolValue]; |
| } |
| |
| - (id)objectByEvaluatingJavaScript:(NSString *)script |
| { |
| bool callbackComplete = false; |
| RetainPtr<id> evalResult; |
| [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) { |
| evalResult = result; |
| callbackComplete = true; |
| EXPECT_TRUE(!error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&callbackComplete); |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script inFrame:(WKFrameInfo *)frame |
| { |
| bool callbackComplete = false; |
| RetainPtr<id> evalResult; |
| [self _evaluateJavaScript:script withSourceURL:nil inFrame:frame inContentWorld:WKContentWorld.pageWorld withUserGesture:YES completionHandler:[&](id result, NSError *error) { |
| evalResult = result; |
| callbackComplete = true; |
| EXPECT_TRUE(!error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&callbackComplete); |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByEvaluatingJavaScript:(NSString *)script inFrame:(WKFrameInfo *)frame |
| { |
| bool callbackComplete = false; |
| RetainPtr<id> evalResult; |
| [self _evaluateJavaScript:script withSourceURL:nil inFrame:frame inContentWorld:WKContentWorld.pageWorld withUserGesture:NO completionHandler:[&](id result, NSError *error) { |
| evalResult = result; |
| callbackComplete = true; |
| EXPECT_TRUE(!error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&callbackComplete); |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByEvaluatingJavaScript:(NSString *)script inFrame:(WKFrameInfo *)frame inContentWorld:(WKContentWorld *)world |
| { |
| __block RetainPtr<id> evalResult; |
| __block bool done { false }; |
| [self evaluateJavaScript:script inFrame:frame inContentWorld:world completionHandler:^(id result, NSError *error) { |
| evalResult = result; |
| done = true; |
| EXPECT_FALSE(error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script |
| { |
| bool callbackComplete = false; |
| RetainPtr<id> evalResult; |
| [self evaluateJavaScript:script completionHandler:[&] (id result, NSError *error) { |
| evalResult = result; |
| callbackComplete = true; |
| EXPECT_TRUE(!error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&callbackComplete); |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByCallingAsyncFunction:(NSString *)script withArguments:(NSDictionary *)arguments |
| { |
| NSError *error = nil; |
| id result = [self objectByCallingAsyncFunction:script withArguments:arguments error:&error]; |
| EXPECT_NULL(error); |
| return result; |
| } |
| |
| - (id)objectByCallingAsyncFunction:(NSString *)script withArguments:(NSDictionary *)arguments error:(NSError **)errorOut |
| { |
| bool callbackComplete = false; |
| if (errorOut) |
| *errorOut = nil; |
| |
| RetainPtr<id> evalResult; |
| RetainPtr<NSError> strongError; |
| [self callAsyncJavaScript:script arguments:arguments inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) { |
| evalResult = result; |
| strongError = error; |
| callbackComplete = true; |
| }]; |
| TestWebKitAPI::Util::run(&callbackComplete); |
| |
| if (errorOut) |
| *errorOut = strongError.autorelease(); |
| |
| return evalResult.autorelease(); |
| } |
| |
| - (id)objectByCallingAsyncFunction:(NSString *)script withArguments:(NSDictionary *)arguments inFrame:(WKFrameInfo *)frame inContentWorld:(WKContentWorld *)world |
| { |
| __block RetainPtr<id> evalResult; |
| __block bool done { false }; |
| [self callAsyncJavaScript:script arguments:arguments inFrame:frame inContentWorld:world completionHandler:^(id result, NSError *error) { |
| evalResult = result; |
| done = true; |
| EXPECT_FALSE(error); |
| if (error) |
| NSLog(@"Encountered error: %@ while evaluating script: %@", error, script); |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return evalResult.autorelease(); |
| } |
| |
| - (NSString *)stringByEvaluatingJavaScript:(NSString *)script inFrame:(WKFrameInfo *)frame |
| { |
| return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script inFrame:frame]]; |
| } |
| |
| - (NSString *)stringByEvaluatingJavaScript:(NSString *)script |
| { |
| return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]]; |
| } |
| |
| - (unsigned)waitUntilClientWidthIs:(unsigned)expectedClientWidth |
| { |
| int timeout = 10; |
| unsigned clientWidth = 0; |
| do { |
| if (timeout != 10) |
| TestWebKitAPI::Util::runFor(0.1_s); |
| |
| id result = [self objectByEvaluatingJavaScript:@"function ___forceLayoutAndGetClientWidth___() { document.body.offsetTop; return document.body.clientWidth; }; ___forceLayoutAndGetClientWidth___();"]; |
| clientWidth = [result integerValue]; |
| |
| --timeout; |
| } while (clientWidth != expectedClientWidth && timeout >= 0); |
| |
| return clientWidth; |
| } |
| |
| - (CGRect)elementRectFromSelector:(NSString *)selector |
| { |
| __block CGRect rect; |
| __block bool doneEvaluatingScript = false; |
| auto script = [NSString stringWithFormat:@"r = document.querySelector('%@').getBoundingClientRect(); [r.left, r.top, r.width, r.height]", selector]; |
| [self evaluateJavaScript:script completionHandler:^(NSArray<NSNumber *> *result, NSError *error) { |
| if (error) |
| NSLog(@"Error while getting element rect for '%@': %@", selector, error); |
| EXPECT_NULL(error); |
| rect = CGRectMake(result[0].floatValue, result[1].floatValue, result[2].floatValue, result[3].floatValue); |
| doneEvaluatingScript = true; |
| }]; |
| TestWebKitAPI::Util::run(&doneEvaluatingScript); |
| return rect; |
| } |
| |
| - (CGPoint)elementMidpointFromSelector:(NSString *)selector |
| { |
| auto rect = [self elementRectFromSelector:selector]; |
| return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); |
| } |
| |
| static IterationStatus forEachCALayer(CALayer *layer, IterationStatus(^visitor)(CALayer *)) |
| { |
| if (visitor(layer) == IterationStatus::Done) |
| return IterationStatus::Done; |
| |
| for (CALayer *sublayer in layer.sublayers) { |
| if (forEachCALayer(sublayer, visitor) == IterationStatus::Done) |
| return IterationStatus::Done; |
| } |
| |
| return IterationStatus::Continue; |
| } |
| |
| - (void)forEachCALayer:(IterationStatus(^)(CALayer *))visitor |
| { |
| forEachCALayer(self.layer, visitor); |
| } |
| |
| - (CALayer *)firstLayerWithName:(NSString *)layerName |
| { |
| __block RetainPtr<CALayer> result; |
| [self forEachCALayer:^(CALayer *layer) { |
| if (![layer.name isEqualToString:@"Gesture Swipe Snapshot Layer"]) |
| return IterationStatus::Continue; |
| |
| result = layer; |
| return IterationStatus::Done; |
| }]; |
| return result.autorelease(); |
| } |
| |
| - (CGImageRef)snapshotAfterScreenUpdates |
| { |
| __block RetainPtr<CGImage> result; |
| __block bool done = false; |
| RetainPtr configuration = adoptNS([WKSnapshotConfiguration new]); |
| [configuration setAfterScreenUpdates:YES]; |
| [self takeSnapshotWithConfiguration:configuration.get() completionHandler:^(TestWebKitAPI::Util::PlatformImage *snapshot, NSError *) { |
| result = TestWebKitAPI::Util::convertToCGImage(snapshot); |
| done = true; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| return result.autorelease(); |
| } |
| |
| - (_WKJSHandle *)querySelector:(NSString *)selector frame:(WKFrameInfo *)frame world:(WKContentWorld *)world |
| { |
| RetainPtr script = [NSString stringWithFormat:@"window.webkit.createJSHandle(document.querySelector(`%@`))", selector]; |
| return dynamic_objc_cast<_WKJSHandle>([self objectByEvaluatingJavaScript:script.get() inFrame:frame inContentWorld:world]); |
| } |
| |
| @end |
| |
| #endif // __cplusplus |
| |
| @implementation WKWebView (TestWebKitAPI_NonCpp) |
| |
| #if PLATFORM(IOS_FAMILY) |
| - (UIView <UITextInputPrivate, UITextInputMultiDocument> *)textInputContentView |
| { |
| return (UIView <UITextInputPrivate, UITextInputMultiDocument> *)[self valueForKey:@"_currentContentView"]; |
| } |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| @end |
| |
| #ifdef __cplusplus |
| |
| @implementation TestMessageHandler { |
| NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers; |
| } |
| |
| - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler |
| { |
| if (!_messageHandlers) |
| _messageHandlers = [NSMutableDictionary dictionary]; |
| |
| _messageHandlers[message] = adoptNS([handler copy]).autorelease(); |
| } |
| |
| - (void)removeMessage:(NSString *)message |
| { |
| [_messageHandlers removeObjectForKey:message]; |
| } |
| |
| - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message |
| { |
| if (dispatch_block_t handler = _messageHandlers[message.body]) |
| handler(); |
| if (_didReceiveScriptMessage) |
| _didReceiveScriptMessage(message.body); |
| } |
| |
| @end |
| |
| #if PLATFORM(MAC) |
| @interface TestWKWebViewHostWindow : NSWindow |
| #else |
| @interface TestWKWebViewHostWindow : UIWindow |
| #endif // PLATFORM(MAC) |
| @end |
| |
| @implementation TestWKWebViewHostWindow { |
| BOOL _forceKeyWindow; |
| __weak TestWKWebView *_webView; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| static int gEventNumber = 1; |
| |
| NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd) |
| { |
| return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged; |
| } |
| |
| - (instancetype)initWithWebView:(TestWKWebView *)webView contentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag |
| { |
| if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag]) |
| _webView = webView; |
| return self; |
| } |
| |
| - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount modifierFlags:(NSEventModifierFlags)modifierFlags mouseEventType:(NSEventType)mouseEventType |
| { |
| if (simulatePressure) |
| modifierFlags |= NSEventMaskPressure; |
| |
| NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:_webView.eventTimestamp windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure]; |
| if (!simulatePressure) { |
| [self sendEvent:event]; |
| return; |
| } |
| |
| IMP simulatedAssociatedEventsMaskImpl = (IMP)__simulated_forceClickAssociatedEventsMask; |
| Method associatedEventsMaskMethod = class_getInstanceMethod([NSEvent class], @selector(associatedEventsMask)); |
| IMP originalAssociatedEventsMaskImpl = method_setImplementation(associatedEventsMaskMethod, simulatedAssociatedEventsMaskImpl); |
| @try { |
| [self sendEvent:event]; |
| } @finally { |
| // In the case where event sending raises an exception, we still want to restore the original implementation |
| // to prevent subsequent event sending tests from being affected. |
| method_setImplementation(associatedEventsMaskMethod, originalAssociatedEventsMaskImpl); |
| } |
| } |
| |
| - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount modifierFlags:(NSEventModifierFlags)modifierFlags eventType:(NSEventType)eventType |
| { |
| [self sendEvent:[NSEvent mouseEventWithType:eventType location:point modifierFlags:modifierFlags timestamp:_webView.eventTimestamp windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]]; |
| } |
| |
| - (BOOL)canBecomeKeyWindow |
| { |
| return _webView.forceWindowToBecomeKey || super.canBecomeKeyWindow; |
| } |
| |
| #endif |
| |
| - (BOOL)isKeyWindow |
| { |
| return _forceKeyWindow || [super isKeyWindow]; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| static NeverDestroyed<RetainPtr<UIWindow>> gOverriddenApplicationKeyWindow; |
| static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gApplicationKeyWindowSwizzler; |
| static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gSharedApplicationSwizzler; |
| |
| static void setOverriddenApplicationKeyWindow(UIWindow *window) |
| { |
| if (gOverriddenApplicationKeyWindow.get() == window) |
| return; |
| |
| if (!UIApplication.sharedApplication) { |
| InstanceMethodSwizzler bundleIdentifierSwizzler(NSBundle.class, @selector(bundleIdentifier), reinterpret_cast<IMP>(overrideBundleIdentifier)); |
| TestWebKitAPI::Util::instantiateUIApplicationIfNeeded(); |
| } |
| |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| gApplicationKeyWindowSwizzler.get() = makeUnique<InstanceMethodSwizzler>(UIApplication.class, @selector(keyWindow), reinterpret_cast<IMP>(applicationKeyWindowOverride)); |
| }); |
| gOverriddenApplicationKeyWindow.get() = window; |
| } |
| |
| static UIWindow *applicationKeyWindowOverride(id, SEL) |
| { |
| return gOverriddenApplicationKeyWindow.get().get(); |
| } |
| |
| - (instancetype)initWithWebView:(TestWKWebView *)webView frame:(CGRect)frame |
| { |
| if (self = [super initWithFrame:frame]) |
| _webView = webView; |
| return self; |
| } |
| |
| #endif |
| |
| - (void)makeKeyWindow |
| { |
| if (_forceKeyWindow) |
| return; |
| |
| _forceKeyWindow = YES; |
| #if PLATFORM(MAC) |
| [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self]; |
| #else |
| setOverriddenApplicationKeyWindow(self); |
| [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self]; |
| #endif |
| } |
| |
| - (void)resignKeyWindow |
| { |
| _forceKeyWindow = NO; |
| [super resignKeyWindow]; |
| } |
| |
| @end |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| using InputSessionChangeCount = NSUInteger; |
| static InputSessionChangeCount nextInputSessionChangeCount() |
| { |
| static InputSessionChangeCount gInputSessionChangeCount = 0; |
| return ++gInputSessionChangeCount; |
| } |
| |
| #endif |
| |
| @implementation TestWKWebView { |
| WeakObjCPtr<TestWKWebViewHostWindow> _hostWindow; |
| RetainPtr<TestMessageHandler> _testHandler; |
| RetainPtr<WKUserScript> _onloadScript; |
| #if PLATFORM(IOS_FAMILY) |
| InputSessionChangeCount _inputSessionChangeCount; |
| UIEdgeInsets _overrideSafeAreaInset; |
| RetainPtr<NSString> _textForSpeakSelection; |
| bool _doneWaitingForSpeakSelectionContent; |
| #endif |
| #if PLATFORM(MAC) |
| BOOL _forceWindowToBecomeKey; |
| NSTimeInterval _eventTimestampOffset; |
| #endif |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame |
| { |
| auto defaultConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]); |
| return [self initWithFrame:frame configuration:defaultConfiguration.get()]; |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration |
| { |
| return [self initWithFrame:frame configuration:configuration addToWindow:YES]; |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration addToWindow:(BOOL)addToWindow |
| { |
| self = [super initWithFrame:frame configuration:configuration]; |
| if (!self) |
| return nil; |
| |
| if (addToWindow) |
| [self _setUpTestWindow:frame]; |
| |
| #if PLATFORM(IOS_FAMILY) |
| _inputSessionChangeCount = 0; |
| // We suppress safe area insets by default in order to ensure consistent results when running against device models |
| // that may or may not have safe area insets, have insets with different values (e.g. iOS devices with a notch). |
| _overrideSafeAreaInset = UIEdgeInsetsZero; |
| #endif |
| |
| return self; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| static UIWindowScene *windowScene() |
| { |
| for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { |
| if ([scene isKindOfClass:UIWindowScene.class]) |
| return (UIWindowScene *)scene; |
| } |
| return nil; |
| } |
| #endif |
| |
| - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration |
| { |
| [configuration setProcessPool:adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration]).get()]; |
| return [self initWithFrame:frame configuration:configuration]; |
| } |
| |
| - (void)_setUpTestWindow:(NSRect)frame |
| { |
| RetainPtr<TestWKWebViewHostWindow> hostWindow; |
| #if PLATFORM(MAC) |
| hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithWebView:self contentRect:frame styleMask:(NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable) backing:NSBackingStoreBuffered defer:NO]); |
| [hostWindow setHasShadow:NO]; |
| [hostWindow setFrameOrigin:frame.origin]; |
| [hostWindow setIsVisible:YES]; |
| [hostWindow setReleasedWhenClosed:NO]; |
| [hostWindow contentView].wantsLayer = YES; |
| [[hostWindow contentView] addSubview:self]; |
| [hostWindow makeKeyAndOrderFront:self]; |
| #else |
| hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithWebView:self frame:frame]); |
| if (UIWindowScene *windowScene = ::windowScene()) |
| [hostWindow setWindowScene:windowScene]; |
| [hostWindow setHidden:NO]; |
| [hostWindow addSubview:self]; |
| #endif |
| _hostWindow = hostWindow.get(); |
| |
| TestWebKitAPI::HostWindowManager::singleton().closeHostWindowOnTestEnd(hostWindow.get()); |
| } |
| |
| - (void)addToTestWindow |
| { |
| if (!_hostWindow) { |
| [self _setUpTestWindow:self.frame]; |
| return; |
| } |
| |
| #if PLATFORM(MAC) |
| [[_hostWindow contentView] addSubview:self]; |
| #else |
| [_hostWindow addSubview:self]; |
| #endif |
| } |
| |
| - (void)removeFromTestWindow |
| { |
| if (_hostWindow) |
| [self removeFromSuperview]; |
| } |
| |
| - (void)clearMessageHandlers:(NSArray *)messageNames |
| { |
| for (NSString *messageName in messageNames) |
| [_testHandler removeMessage:messageName]; |
| } |
| |
| - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action |
| { |
| if (!_testHandler) { |
| _testHandler = adoptNS([[TestMessageHandler alloc] init]); |
| [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"]; |
| } |
| |
| [_testHandler addMessage:message withHandler:action]; |
| } |
| |
| - (void)performAfterReceivingAnyMessage:(void (^)(NSString *))action |
| { |
| if (!_testHandler) { |
| _testHandler = adoptNS([[TestMessageHandler alloc] init]); |
| [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"]; |
| } |
| [_testHandler setDidReceiveScriptMessage:action]; |
| } |
| |
| - (void)synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:(NSString *)html |
| { |
| bool didFireDOMLoadEvent = false; |
| [self performAfterLoading:[&] { didFireDOMLoadEvent = true; }]; |
| [self loadHTMLString:html baseURL:NSBundle.test_resourcesBundle.resourceURL]; |
| TestWebKitAPI::Util::run(&didFireDOMLoadEvent); |
| [self waitForNextPresentationUpdate]; |
| } |
| |
| - (void)waitForMessage:(NSString *)message |
| { |
| __block bool isDoneWaiting = false; |
| [self performAfterReceivingMessage:message action:^() |
| { |
| isDoneWaiting = true; |
| }]; |
| TestWebKitAPI::Util::run(&isDoneWaiting); |
| } |
| |
| - (void)waitForMessages:(NSArray<NSString *> *)expectedMessages |
| { |
| __block Deque<RetainPtr<NSString>> receivedMessages; |
| RetainPtr messageHandler = adoptNS([TestMessageHandler new]); |
| [messageHandler setDidReceiveScriptMessage:^(NSString *message) { |
| receivedMessages.append(message); |
| }]; |
| [self.configuration.userContentController addScriptMessageHandler:messageHandler.get() name:@"testHandler"]; |
| for (NSString *expectedMessage in expectedMessages) { |
| while (receivedMessages.isEmpty()) |
| TestWebKitAPI::Util::spinRunLoop(); |
| EXPECT_WK_STREQ(receivedMessages.takeFirst().get(), expectedMessage); |
| } |
| [self.configuration.userContentController removeScriptMessageHandlerForName:@"testHandler"]; |
| } |
| |
| - (void)performAfterLoading:(dispatch_block_t)actions |
| { |
| NSString *const viewDidLoadMessage = @"TestWKWebViewDidLoad"; |
| if (!_onloadScript) { |
| NSString *onloadScript = [NSString stringWithFormat:@"window.addEventListener('load', () => window.webkit.messageHandlers.testHandler.postMessage('%@'), true /* useCapture */)", viewDidLoadMessage]; |
| _onloadScript = adoptNS([[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]); |
| [self.configuration.userContentController addUserScript:_onloadScript.get()]; |
| } |
| [self performAfterReceivingMessage:viewDidLoadMessage action:actions]; |
| } |
| |
| - (void)waitForNextPresentationUpdate |
| { |
| __block bool done = false; |
| [self _doAfterNextPresentationUpdate:^{ |
| done = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| - (void)waitForNextVisibleContentRectUpdate |
| { |
| __block bool done = false; |
| [self _doAfterNextVisibleContentRectUpdate:^{ |
| done = true; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| - (void)waitUntilActivityStateUpdateDone |
| { |
| __block bool done = false; |
| [self _doAfterActivityStateUpdate:^() { |
| done = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&done); |
| } |
| |
| - (void)forceLightMode |
| { |
| #if USE(APPKIT) |
| [self setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameAqua]]; |
| #else |
| [self setOverrideUserInterfaceStyle:UIUserInterfaceStyleLight]; |
| #endif |
| } |
| |
| - (void)forceDarkMode |
| { |
| #if USE(APPKIT) |
| [self setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]]; |
| #else |
| [self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark]; |
| #endif |
| } |
| |
| - (NSString *)stylePropertyAtSelectionStart:(NSString *)propertyName |
| { |
| NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).startContainer.parentElement)['%@']", propertyName]; |
| return [self stringByEvaluatingJavaScript:script]; |
| } |
| |
| - (NSString *)stylePropertyAtSelectionEnd:(NSString *)propertyName |
| { |
| NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).endContainer.parentElement)['%@']", propertyName]; |
| return [self stringByEvaluatingJavaScript:script]; |
| } |
| |
| - (void)collapseToStart |
| { |
| [self evaluateJavaScript:@"getSelection().collapseToStart()" completionHandler:nil]; |
| } |
| |
| - (void)collapseToEnd |
| { |
| [self evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil]; |
| } |
| |
| - (BOOL)selectionRangeHasStartOffset:(int)start endOffset:(int)end |
| { |
| __block bool isDone = false; |
| __block bool matches = true; |
| [self evaluateJavaScript:@"window.getSelection().getRangeAt(0).startOffset" completionHandler:^(id result, NSError *error) { |
| if ([(NSNumber *)result intValue] != start) |
| matches = false; |
| }]; |
| [self evaluateJavaScript:@"window.getSelection().getRangeAt(0).endOffset" completionHandler:^(id result, NSError *error) { |
| if ([(NSNumber *)result intValue] != end) |
| matches = false; |
| isDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&isDone); |
| |
| return matches; |
| } |
| |
| - (BOOL)selectionRangeHasStartOffset:(int)start endOffset:(int)end inFrame:(WKFrameInfo *)frameInfo |
| { |
| __block bool isDone = false; |
| __block bool matches = true; |
| [self evaluateJavaScript:@"window.getSelection().getRangeAt(0).startOffset" inFrame:frameInfo inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) { |
| if ([(NSNumber *)result intValue] != start) |
| matches = false; |
| }]; |
| [self evaluateJavaScript:@"window.getSelection().getRangeAt(0).endOffset" inFrame:frameInfo inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) { |
| if ([(NSNumber *)result intValue] != end) |
| matches = false; |
| isDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&isDone); |
| |
| return matches; |
| } |
| |
| - (void)clickOnElementID:(NSString *)elementID |
| { |
| [self evaluateJavaScript:[NSString stringWithFormat:@"document.getElementById('%@').click();", elementID] completionHandler:nil]; |
| } |
| |
| - (void)waitForPendingMouseEvents |
| { |
| __block bool doneProcessingMouseEvents = false; |
| [self _doAfterProcessingAllPendingMouseEvents:^{ |
| doneProcessingMouseEvents = true; |
| }]; |
| TestWebKitAPI::Util::run(&doneProcessingMouseEvents); |
| } |
| |
| - (void)focus |
| { |
| #if PLATFORM(MAC) |
| [_hostWindow makeFirstResponder:self]; |
| #else |
| [super becomeFirstResponder]; |
| #endif |
| } |
| |
| - (std::optional<CGPoint>)getElementMidpoint:(NSString *)selector |
| { |
| NSArray<NSNumber *> *midpoint = [self objectByEvaluatingJavaScript:[NSString stringWithFormat:@"(() => {" |
| " let element = document.querySelector('%@');" |
| " if (!element)" |
| " return [];" |
| " const rect = element.getBoundingClientRect();" |
| " return [rect.left + (rect.width / 2), rect.top + (rect.height / 2)];" |
| "})()", selector]]; |
| if (midpoint.count != 2) |
| return std::nullopt; |
| return CGPointMake(midpoint.firstObject.doubleValue, midpoint.lastObject.doubleValue); |
| } |
| |
| - (Vector<WebCore::Color>)sampleColors |
| { |
| return [self sampleColorsWithInterval:TestWebKitAPI::CGImagePixelReader::defaultWebViewSamplingInterval]; |
| } |
| |
| - (Vector<WebCore::Color>)sampleColorsWithInterval:(unsigned)interval |
| { |
| [self waitForNextPresentationUpdate]; |
| Vector<WebCore::Color> samples; |
| TestWebKitAPI::CGImagePixelReader reader { [self snapshotAfterScreenUpdates] }; |
| for (unsigned x = interval; x < reader.width() - interval; x += interval) { |
| for (unsigned y = interval; y < reader.height() - interval; y += interval) |
| samples.append(reader.at(x, y)); |
| } |
| return samples; |
| } |
| |
| - (RetainPtr<_WKFrameTreeNode>)frameTree |
| { |
| __block RetainPtr<_WKFrameTreeNode> result; |
| __block bool isDone = false; |
| |
| [self _frames:^(_WKFrameTreeNode *tree) { |
| result = tree; |
| isDone = true; |
| }]; |
| TestWebKitAPI::Util::run(&isDone); |
| |
| return result; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| - (NSString *)textForSpeakSelection |
| { |
| _textForSpeakSelection = { }; |
| _doneWaitingForSpeakSelectionContent = false; |
| [self _accessibilityRetrieveSpeakSelectionContent]; |
| |
| TestWebKitAPI::Util::run(&_doneWaitingForSpeakSelectionContent); |
| return _textForSpeakSelection.get(); |
| } |
| |
| - (void)_accessibilityDidGetSpeakSelectionContent:(NSString *)content |
| { |
| _textForSpeakSelection = adoptNS(content.copy); |
| _doneWaitingForSpeakSelectionContent = true; |
| } |
| |
| - (void)didStartFormControlInteraction |
| { |
| _inputSessionChangeCount = nextInputSessionChangeCount(); |
| } |
| |
| - (void)didEndFormControlInteraction |
| { |
| _inputSessionChangeCount = 0; |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| @end |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| @implementation TestWKWebView (IOSOnly) |
| |
| - (void)evaluateJavaScriptAndWaitForInputSessionToChange:(NSString *)script |
| { |
| auto initialChangeCount = _inputSessionChangeCount; |
| BOOL hasEmittedWarning = NO; |
| NSTimeInterval secondsToWaitUntilWarning = 2; |
| NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate]; |
| |
| [self objectByEvaluatingJavaScript:script]; |
| while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) { |
| if (_inputSessionChangeCount != initialChangeCount) |
| break; |
| |
| if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate]) |
| continue; |
| |
| NSLog(@"Warning: expecting input session change count to differ from %lu", static_cast<unsigned long>(initialChangeCount)); |
| hasEmittedWarning = YES; |
| } |
| } |
| |
| - (UIEdgeInsets)overrideSafeAreaInset |
| { |
| return _overrideSafeAreaInset; |
| } |
| |
| - (void)setOverrideSafeAreaInset:(UIEdgeInsets)inset |
| { |
| _overrideSafeAreaInset = inset; |
| } |
| |
| - (UIEdgeInsets)_safeAreaInsetsForFrame:(CGRect)frame inSuperview:(UIView *)view |
| { |
| return _overrideSafeAreaInset; |
| } |
| |
| - (CGRect)caretViewRectInContentCoordinates |
| { |
| UIView *caretView = nil; |
| #if HAVE(UI_TEXT_SELECTION_DISPLAY_INTERACTION) |
| if (auto view = self.textSelectionDisplayInteraction.cursorView; !view.hidden) |
| caretView = view; |
| #endif |
| |
| return [caretView convertRect:caretView.bounds toView:self.textInputContentView]; |
| } |
| |
| - (NSArray<NSValue *> *)selectionViewRectsInContentCoordinates |
| { |
| #if HAVE(UI_TEXT_SELECTION_DISPLAY_INTERACTION) |
| RetainPtr contentView = [self textInputContentView]; |
| if (auto view = self.textSelectionDisplayInteraction.highlightView; !view.hidden) { |
| RetainPtr uiTextSelectionRects = [view selectionRects]; |
| NSMutableArray *selectionRects = [NSMutableArray arrayWithCapacity:[uiTextSelectionRects count]]; |
| for (UITextSelectionRect *rect in uiTextSelectionRects.get()) { |
| CGRect rectInContentView = [view convertRect:rect.rect toView:contentView.get()]; |
| [selectionRects addObject:[NSValue valueWithCGRect:rectInContentView]]; |
| } |
| return selectionRects; |
| } |
| #endif |
| return @[ ]; |
| } |
| |
| - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position |
| { |
| __block RetainPtr<_WKActivatedElementInfo> info; |
| __block bool finished = false; |
| [self _requestActivatedElementAtPosition:position completionBlock:^(_WKActivatedElementInfo *elementInfo) { |
| info = elementInfo; |
| finished = true; |
| }]; |
| |
| TestWebKitAPI::Util::run(&finished); |
| return info.autorelease(); |
| } |
| |
| #if HAVE(UI_TEXT_SELECTION_DISPLAY_INTERACTION) |
| |
| - (UITextSelectionDisplayInteraction *)textSelectionDisplayInteraction |
| { |
| return dynamic_objc_cast<UITextSelectionDisplayInteraction>([self.textInputContentView valueForKeyPath:@"interactionAssistant._selectionViewManager"]); |
| } |
| |
| #endif |
| |
| static WKContentView *recursiveFindWKContentView(UIView *view) |
| { |
| if ([view isKindOfClass:NSClassFromString(@"WKContentView")]) |
| return (WKContentView *)view; |
| |
| for (UIView *subview in view.subviews) { |
| WKContentView *contentView = recursiveFindWKContentView(subview); |
| if (contentView) |
| return contentView; |
| } |
| |
| return nil; |
| } |
| |
| - (WKContentView *)wkContentView |
| { |
| return recursiveFindWKContentView(self); |
| } |
| |
| - (void)setZoomScaleSimulatingUserTriggeredZoom:(CGFloat)zoomScale |
| { |
| InstanceMethodSwizzler gestureSwizzler { |
| [UIPinchGestureRecognizer class], |
| @selector(state), |
| imp_implementationWithBlock(^UIGestureRecognizerState { |
| return UIGestureRecognizerStateBegan; |
| }) |
| }; |
| |
| RetainPtr scrollView = [self scrollView]; |
| [self scrollViewWillBeginZooming:scrollView.get() withView:[self viewForZoomingInScrollView:scrollView.get()]]; |
| [scrollView setZoomScale:zoomScale]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| #if PLATFORM(MAC) |
| |
| @implementation TestWKWebView (MacOnly) |
| |
| - (void)setEventTimestampOffset:(NSTimeInterval)offset |
| { |
| _eventTimestampOffset += offset; |
| } |
| |
| - (NSTimeInterval)eventTimestamp |
| { |
| return GetCurrentEventTime() + _eventTimestampOffset; |
| } |
| |
| - (BOOL)forceWindowToBecomeKey |
| { |
| return _forceWindowToBecomeKey; |
| } |
| |
| - (void)setForceWindowToBecomeKey:(BOOL)forceWindowToBecomeKey |
| { |
| _forceWindowToBecomeKey = forceWindowToBecomeKey; |
| } |
| |
| - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure |
| { |
| [self mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure withFlags:0 eventType:NSEventTypeLeftMouseDown]; |
| } |
| |
| - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure withFlags:(NSEventModifierFlags)flags eventType:(NSEventType)eventType |
| { |
| [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure clickCount:1 modifierFlags:flags mouseEventType:eventType]; |
| } |
| |
| - (void)mouseUpAtPoint:(NSPoint)pointInWindow |
| { |
| [self mouseUpAtPoint:pointInWindow withFlags:0 eventType:NSEventTypeLeftMouseUp]; |
| } |
| |
| - (void)mouseUpAtPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags eventType:(NSEventType)eventType |
| { |
| [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:1 modifierFlags:flags eventType:eventType]; |
| } |
| |
| - (void)mouseMoveToPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags |
| { |
| [self _simulateMouseMove:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:self.eventTimestamp clickCount:0]]; |
| } |
| |
| - (void)sendClicksAtPoint:(NSPoint)pointInWindow numberOfClicks:(NSUInteger)numberOfClicks |
| { |
| for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) { |
| [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:NO clickCount:clickCount modifierFlags:0 mouseEventType:NSEventTypeLeftMouseDown]; |
| [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:clickCount modifierFlags:0 eventType:NSEventTypeLeftMouseUp]; |
| } |
| } |
| |
| - (void)sendClickAtPoint:(NSPoint)pointInWindow |
| { |
| [self sendClicksAtPoint:pointInWindow numberOfClicks:1]; |
| } |
| |
| - (void)rightClickAtPoint:(NSPoint)pointInWindow |
| { |
| [self mouseDownAtPoint:pointInWindow simulatePressure:NO withFlags:0 eventType:NSEventTypeRightMouseDown]; |
| [self mouseUpAtPoint:pointInWindow withFlags:0 eventType:NSEventTypeRightMouseUp]; |
| } |
| |
| - (BOOL)acceptsFirstMouseAtPoint:(NSPoint)pointInWindow |
| { |
| return [self acceptsFirstMouse:[self _mouseEventWithType:NSEventTypeLeftMouseDown atLocation:pointInWindow]]; |
| } |
| |
| - (void)mouseEnterAtPoint:(NSPoint)pointInWindow |
| { |
| [self mouseEntered:[self _mouseEventWithType:NSEventTypeMouseEntered atLocation:pointInWindow]]; |
| } |
| |
| - (void)mouseDragToPoint:(NSPoint)pointInWindow |
| { |
| [self mouseDragged:[self _mouseEventWithType:NSEventTypeLeftMouseDragged atLocation:pointInWindow]]; |
| } |
| |
| - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)pointInWindow |
| { |
| return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:self.eventTimestamp clickCount:0]; |
| } |
| |
| - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)locationInWindow flags:(NSEventModifierFlags)flags timestamp:(NSTimeInterval)timestamp clickCount:(NSUInteger)clickCount |
| { |
| switch (type) { |
| case NSEventTypeMouseEntered: |
| case NSEventTypeMouseExited: |
| return [NSEvent enterExitEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber trackingNumber:1 userData:nil]; |
| default: |
| return [NSEvent mouseEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]; |
| } |
| } |
| |
| - (void)wheelEventAtPoint:(CGPoint)pointInWindow wheelDelta:(CGSize)delta |
| { |
| RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitPixel, 2, delta.height, delta.width, 0)); |
| |
| CGPoint locationInGlobalScreenCoordinates = [[self window] convertPointToScreen:pointInWindow]; |
| locationInGlobalScreenCoordinates.y = [[[NSScreen screens] objectAtIndex:0] frame].size.height - locationInGlobalScreenCoordinates.y; |
| CGEventSetLocation(cgScrollEvent.get(), locationInGlobalScreenCoordinates); |
| |
| NSEvent* event = [NSEvent eventWithCGEvent:cgScrollEvent.get()]; |
| [self scrollWheel:event]; |
| } |
| |
| - (NSWindow *)hostWindow |
| { |
| return _hostWindow.getAutoreleased(); |
| } |
| |
| - (void)typeCharacter:(char)character |
| { |
| [self typeCharacter:character modifiers:0]; |
| } |
| |
| - (void)sendKey:(NSString *)characters code:(unsigned short)keyCode isDown:(BOOL)isDown modifiers:(NSEventModifierFlags)modifiers |
| { |
| NSEvent *event = [NSEvent keyEventWithType:isDown ? NSEventTypeKeyDown : NSEventTypeKeyUp |
| location:NSZeroPoint |
| modifierFlags:modifiers |
| timestamp:self.eventTimestamp |
| windowNumber:[_hostWindow windowNumber] |
| context:nil |
| characters:characters |
| charactersIgnoringModifiers:characters |
| isARepeat:NO |
| keyCode:keyCode]; |
| |
| if (isDown) |
| [self keyDown:event]; |
| else |
| [self keyUp:event]; |
| } |
| |
| - (void)typeCharacter:(char)character modifiers:(NSEventModifierFlags)modifiers |
| { |
| NSString *characters = [NSString stringWithFormat:@"%c", character]; |
| for (auto isDown : std::array { YES, NO }) |
| [self sendKey:characters code:character isDown:isDown modifiers:modifiers]; |
| } |
| |
| // Note: this testing strategy makes a couple of assumptions: |
| // 1. The network process hasn't already died and allowed the system to reuse the same PID. |
| // 2. The API test did not take more than ~120 seconds to run. |
| - (NSArray<NSString *> *)collectLogsForNewConnections |
| { |
| auto predicate = [NSString stringWithFormat:@"subsystem == 'com.apple.network'" |
| " AND category == 'connection'" |
| " AND eventMessage endswith 'start'" |
| " AND processIdentifier == %d", self._networkProcessIdentifier]; |
| RetainPtr pipe = [NSPipe pipe]; |
| // FIXME: This is currently reliant on `NSTask`, which is absent on iOS. We should find a way to |
| // make this helper work on both platforms. |
| auto task = adoptNS([NSTask new]); |
| [task setLaunchPath:@"/usr/bin/log"]; |
| [task setArguments:@[ @"show", @"--last", @"2m", @"--style", @"json", @"--predicate", predicate ]]; |
| [task setStandardOutput:pipe.get()]; |
| [task launch]; |
| [task waitUntilExit]; |
| |
| auto rawData = [pipe fileHandleForReading].availableData; |
| auto messages = [NSMutableArray<NSString *> array]; |
| for (id messageData in dynamic_objc_cast<NSArray>([NSJSONSerialization JSONObjectWithData:rawData options:0 error:nil])) |
| [messages addObject:dynamic_objc_cast<NSString>([messageData objectForKey:@"eventMessage"])]; |
| return messages; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |
| |
| #if PLATFORM(IOS_FAMILY) |
| @implementation UIView (WKTestingUIViewUtilities) |
| |
| - (UIView *)wkFirstSubviewWithClass:(Class)targetClass |
| { |
| for (UIView *view in self.subviews) { |
| if ([view isKindOfClass:targetClass]) |
| return view; |
| |
| UIView *foundSubview = [view wkFirstSubviewWithClass:targetClass]; |
| if (foundSubview) |
| return foundSubview; |
| } |
| |
| return nil; |
| } |
| |
| - (UIView *)wkFirstSubviewWithBoundsSize:(CGSize)size |
| { |
| for (UIView *view in self.subviews) { |
| if (CGSizeEqualToSize([view bounds].size, size)) |
| return view; |
| |
| UIView *foundSubview = [view wkFirstSubviewWithBoundsSize:size]; |
| if (foundSubview) |
| return foundSubview; |
| } |
| |
| return nil; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| @implementation TestWKWebView (SiteIsolation) |
| |
| - (_WKFrameTreeNode *)mainFrame |
| { |
| __block RetainPtr<_WKFrameTreeNode> frame; |
| [self _frames:^(_WKFrameTreeNode *mainFrame) { |
| frame = mainFrame; |
| }]; |
| while (!frame) |
| TestWebKitAPI::Util::spinRunLoop(); |
| return frame.autorelease(); |
| } |
| |
| - (WKFrameInfo *)firstChildFrame |
| { |
| return [self mainFrame].childFrames.firstObject.info; |
| } |
| |
| - (WKFrameInfo *)secondChildFrame |
| { |
| return [self mainFrame].childFrames[1].info; |
| } |
| |
| - (void)evaluateJavaScript:(NSString *)string inFrame:(WKFrameInfo *)frame completionHandler:(void(^)(id, NSError *))completionHandler |
| { |
| [self evaluateJavaScript:string inFrame:frame inContentWorld:WKContentWorld.pageWorld completionHandler:completionHandler]; |
| } |
| |
| - (WKFindResult *)findStringAndWait:(NSString *)string withConfiguration:(WKFindConfiguration *)configuration |
| { |
| __block RetainPtr<WKFindResult> findResult; |
| [self findString:string withConfiguration:configuration completionHandler:^(WKFindResult *result) { |
| findResult = result; |
| }]; |
| while (!findResult) |
| TestWebKitAPI::Util::spinRunLoop(); |
| return findResult.autorelease(); |
| } |
| |
| @end |
| |
| |
| @implementation TestWKWebView (ContextMenu) |
| |
| #if PLATFORM(MAC) |
| |
| static NSMenuItem *itemMatchingFilter(NSMenu *menu, MenuItemFilter filter) |
| { |
| for (NSInteger index = 0; index < menu.numberOfItems; ++index) { |
| auto *item = [menu itemAtIndex:index]; |
| if (!item) |
| continue; |
| |
| if (filter(item)) |
| return item; |
| |
| if (item.hasSubmenu) { |
| if (auto *foundItem = itemMatchingFilter(item.submenu, filter)) |
| return foundItem; |
| } |
| } |
| return nil; |
| } |
| |
| - (void)rightClick:(NSPoint)clickLocation andSelectItemMatching:(MenuItemFilter)filter |
| { |
| bool selectedItem = false; |
| RetainPtr selectItemTimer = [NSTimer timerWithTimeInterval:0.25 repeats:YES block:[&selectedItem, strongSelf = RetainPtr { self }, filter = makeBlockPtr(filter)](NSTimer *timer) { |
| NSMenu *activeMenu = [strongSelf _activeMenu]; |
| if (!activeMenu) |
| return; |
| |
| auto *item = itemMatchingFilter(activeMenu, filter.get()); |
| if (!item) |
| return; |
| |
| auto *itemMenu = item.menu; |
| [itemMenu performActionForItemAtIndex:[itemMenu indexOfItem:item]]; |
| [activeMenu cancelTracking]; |
| [timer invalidate]; |
| selectedItem = true; |
| }]; |
| |
| [NSRunLoop.mainRunLoop addTimer:selectItemTimer.get() forMode:NSEventTrackingRunLoopMode]; |
| [self.window orderFrontRegardless]; |
| [self mouseDownAtPoint:NSMakePoint(50, 350) simulatePressure:NO withFlags:0 eventType:NSEventTypeRightMouseDown]; |
| [self mouseUpAtPoint:NSMakePoint(50, 350) withFlags:0 eventType:NSEventTypeRightMouseUp]; |
| TestWebKitAPI::Util::run(&selectedItem); |
| } |
| |
| - (_WKContextMenuElementInfo *)rightClickAtPointAndWaitForContextMenu:(NSPoint)clickLocation |
| { |
| RetainPtr uiDelegate = adoptNS([TestUIDelegate new]); |
| |
| __block RetainPtr<_WKContextMenuElementInfo> result; |
| __block bool gotProposedMenu = false; |
| [uiDelegate setGetContextMenuFromProposedMenu:^(NSMenu *, _WKContextMenuElementInfo *elementInfo, id<NSSecureCoding>, void (^completion)(NSMenu *)) { |
| result = elementInfo; |
| gotProposedMenu = true; |
| completion(nil); |
| }]; |
| |
| EXPECT_NULL(self.UIDelegate); |
| self.UIDelegate = uiDelegate.get(); |
| [self rightClickAtPoint:clickLocation]; |
| TestWebKitAPI::Util::run(&gotProposedMenu); |
| [self waitForNextPresentationUpdate]; |
| |
| self.UIDelegate = nil; |
| return result.autorelease(); |
| } |
| |
| #endif |
| |
| @end |
| |
| #endif // __cplusplus |