blob: 81e376ee3c4a8b693bac6d955580fc3de684baa3 [file] [log] [blame]
/*
* Copyright (C) 2016 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"
#if WK_API_ENABLED
#import "TestNavigationDelegate.h"
#import "Utilities.h"
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WebKitPrivate.h>
#import <WebKit/_WKActivatedElementInfo.h>
#import <WebKit/_WKProcessPoolConfiguration.h>
#import <objc/runtime.h>
#import <wtf/RetainPtr.h>
#if PLATFORM(MAC)
#import <AppKit/AppKit.h>
#import <Carbon/Carbon.h>
#import <wtf/mac/AppKitCompatibilityDeclarations.h>
#endif
#if PLATFORM(IOS)
#import <UIKit/UIKit.h>
#import <wtf/SoftLinking.h>
SOFT_LINK_FRAMEWORK(UIKit)
SOFT_LINK_CLASS(UIKit, UIWindow)
#endif
@implementation TestMessageHandler {
NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers;
}
- (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler
{
if (!_messageHandlers)
_messageHandlers = [NSMutableDictionary dictionary];
_messageHandlers[message] = [handler copy];
}
- (void)removeMessage:(NSString *)message
{
[_messageHandlers removeObjectForKey:message];
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
dispatch_block_t handler = _messageHandlers[message.body];
if (handler)
handler();
}
@end
#if PLATFORM(MAC)
@interface TestWKWebViewHostWindow : NSWindow
#else
@interface TestWKWebViewHostWindow : UIWindow
#endif // PLATFORM(MAC)
@end
@implementation TestWKWebViewHostWindow {
BOOL _forceKeyWindow;
}
#if PLATFORM(MAC)
static int gEventNumber = 1;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
{
return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
}
#endif
- (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
{
NSEventType mouseEventType = NSEventTypeLeftMouseDown;
NSEventMask modifierFlags = 0;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
if (simulatePressure)
modifierFlags |= NSEventMaskPressure;
#else
simulatePressure = NO;
#endif
NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
if (!simulatePressure) {
[self sendEvent:event];
return;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
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);
}
#endif
}
- (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
{
[self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
}
#endif // PLATFORM(MAC)
- (BOOL)isKeyWindow
{
return _forceKeyWindow || [super isKeyWindow];
}
- (void)makeKeyWindow
{
if (_forceKeyWindow)
return;
_forceKeyWindow = YES;
#if PLATFORM(MAC)
[[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
#else
[[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
#endif
}
- (void)resignKeyWindow
{
_forceKeyWindow = NO;
[super resignKeyWindow];
}
@end
@implementation TestWKWebView {
RetainPtr<TestWKWebViewHostWindow> _hostWindow;
RetainPtr<TestMessageHandler> _testHandler;
}
- (instancetype)initWithFrame:(CGRect)frame
{
WKWebViewConfiguration *defaultConfiguration = [[[WKWebViewConfiguration alloc] init] autorelease];
return [self initWithFrame:frame configuration:defaultConfiguration];
}
- (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];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration
{
[configuration setProcessPool:[[[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration] autorelease]];
return [self initWithFrame:frame configuration:configuration];
}
- (void)_setUpTestWindow:(NSRect)frame
{
#if PLATFORM(MAC)
_hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
[_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
[_hostWindow setIsVisible:YES];
[_hostWindow contentView].wantsLayer = YES;
[[_hostWindow contentView] addSubview:self];
[_hostWindow makeKeyAndOrderFront:self];
#else
_hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
[_hostWindow setHidden:NO];
[_hostWindow addSubview:self];
#endif
}
- (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)loadTestPageNamed:(NSString *)pageName
{
NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:pageName withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
[self loadRequest:request];
}
- (void)synchronouslyLoadHTMLString:(NSString *)html
{
NSURL *testResourceURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"];
[self loadHTMLString:html baseURL:testResourceURL];
[self _test_waitForDidFinishNavigation];
}
- (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
{
[self loadTestPageNamed:pageName];
[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();
}
- (NSString *)stringByEvaluatingJavaScript:(NSString *)script
{
return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
}
- (void)waitForMessage:(NSString *)message
{
__block bool isDoneWaiting = false;
[self performAfterReceivingMessage:message action:^()
{
isDoneWaiting = true;
}];
TestWebKitAPI::Util::run(&isDoneWaiting);
}
- (void)performAfterLoading:(dispatch_block_t)actions {
TestMessageHandler *handler = [[TestMessageHandler alloc] init];
[handler addMessage:@"loaded" withHandler:actions];
NSString *onloadScript = @"window.onload = () => window.webkit.messageHandlers.onloadHandler.postMessage('loaded')";
WKUserScript *script = [[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
WKUserContentController* contentController = [[self configuration] userContentController];
[contentController addUserScript:script];
[contentController addScriptMessageHandler:handler name:@"onloadHandler"];
}
- (void)waitForNextPresentationUpdate
{
__block bool done = false;
[self _doAfterNextPresentationUpdate:^() {
done = true;
}];
TestWebKitAPI::Util::run(&done);
}
@end
#if PLATFORM(IOS)
@implementation TestWKWebView (IOSOnly)
- (UIView <UITextInput> *)textInputContentView
{
return (UIView <UITextInput> *)[self valueForKey:@"_currentContentView"];
}
- (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate
{
RetainPtr<TestWKWebView> retainedSelf = self;
__block bool isDone = false;
__block RetainPtr<NSArray> selectionRects;
[self _doAfterNextPresentationUpdate:^() {
selectionRects = adoptNS([[retainedSelf _uiTextSelectionRects] retain]);
isDone = true;
}];
TestWebKitAPI::Util::run(&isDone);
return selectionRects;
}
- (_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();
}
@end
#endif
#if PLATFORM(MAC)
@implementation TestWKWebView (MacOnly)
- (void)mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure
{
[_hostWindow _mouseDownAtPoint:point simulatePressure:simulatePressure clickCount:1];
}
- (void)mouseUpAtPoint:(NSPoint)point
{
[_hostWindow _mouseUpAtPoint:point clickCount:1];
}
- (void)mouseMoveToPoint:(NSPoint)point withFlags:(NSEventModifierFlags)flags
{
[self mouseMoved:[NSEvent mouseEventWithType:NSEventTypeMouseMoved location:point modifierFlags:flags timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:0 pressure:0]];
}
- (void)sendClicksAtPoint:(NSPoint)point numberOfClicks:(NSUInteger)numberOfClicks
{
for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) {
[_hostWindow _mouseDownAtPoint:point simulatePressure:NO clickCount:clickCount];
[_hostWindow _mouseUpAtPoint:point clickCount:clickCount];
}
}
- (NSWindow *)hostWindow
{
return _hostWindow.get();
}
- (void)typeCharacter:(char)character {
NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
NSEventType keyDownEventType = NSEventTypeKeyDown;
NSEventType keyUpEventType = NSEventTypeKeyUp;
[self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
[self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
}
@end
#endif
#endif // WK_API_ENABLED