Mail appears to be double inverting code copied from Notes, Xcode, or Terminal.
[WebKit-https.git] / Tools / TestWebKitAPI / cocoa / TestWKWebView.mm
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "TestWKWebView.h"
28
29 #import "ClassMethodSwizzler.h"
30 #import "TestNavigationDelegate.h"
31 #import "Utilities.h"
32
33 #import <WebKit/WKWebViewConfigurationPrivate.h>
34 #import <WebKit/WebKitPrivate.h>
35 #import <WebKit/_WKActivatedElementInfo.h>
36 #import <WebKit/_WKProcessPoolConfiguration.h>
37 #import <objc/runtime.h>
38 #import <wtf/RetainPtr.h>
39
40 #if PLATFORM(MAC)
41 #import <AppKit/AppKit.h>
42 #import <Carbon/Carbon.h>
43 #endif
44
45 #if PLATFORM(IOS_FAMILY)
46 #import "UIKitSPI.h"
47 #import <MobileCoreServices/MobileCoreServices.h>
48 #import <wtf/SoftLinking.h>
49 SOFT_LINK_FRAMEWORK(UIKit)
50 SOFT_LINK_CLASS(UIKit, UIWindow)
51
52 @implementation WKWebView (WKWebViewTestingQuirks)
53
54 // TestWebKitAPI is currently not a UIApplication so we are unable to track if it is in
55 // the background or not (https://bugs.webkit.org/show_bug.cgi?id=175204). This can
56 // cause our processes to get suspended on iOS. We work around this by having
57 // WKWebView._isBackground always return NO in the context of API tests.
58 - (BOOL)_isBackground
59 {
60     return NO;
61 }
62 @end
63 #endif
64
65 @implementation WKWebView (TestWebKitAPI)
66
67 - (void)loadTestPageNamed:(NSString *)pageName
68 {
69     NSURLRequest *request = [NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:pageName withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]];
70     [self loadRequest:request];
71 }
72
73 - (void)synchronouslyLoadRequest:(NSURLRequest *)request
74 {
75     [self loadRequest:request];
76     [self _test_waitForDidFinishNavigation];
77 }
78
79 - (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url
80 {
81     [self loadHTMLString:html baseURL:url];
82     [self _test_waitForDidFinishNavigation];
83 }
84
85 - (void)synchronouslyLoadHTMLString:(NSString *)html
86 {
87     [self synchronouslyLoadHTMLString:html baseURL:[[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
88 }
89
90 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
91 {
92     [self loadTestPageNamed:pageName];
93     [self _test_waitForDidFinishNavigation];
94 }
95
96 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
97 {
98     __block bool done = false;
99     __block bool success;
100     [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) {
101         done = true;
102         success = completionSuccess;
103     }];
104     TestWebKitAPI::Util::run(&done);
105     return success;
106 }
107
108 - (NSArray<NSString *> *)tagsInBody
109 {
110     return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
111 }
112
113 - (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
114 {
115     auto remainingTags = adoptNS([tagNames mutableCopy]);
116     NSArray<NSString *> *tagsInBody = self.tagsInBody;
117     for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
118         if ([tag isEqualToString:[remainingTags lastObject]])
119             [remainingTags removeLastObject];
120         if (![remainingTags count])
121             break;
122     }
123     EXPECT_EQ([remainingTags count], 0U);
124     if ([remainingTags count])
125         NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
126 }
127
128 - (void)expectElementCount:(NSInteger)count querySelector:(NSString *)querySelector
129 {
130     NSString *script = [NSString stringWithFormat:@"document.querySelectorAll('%@').length", querySelector];
131     EXPECT_EQ(count, [self stringByEvaluatingJavaScript:script].integerValue);
132 }
133
134 - (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
135 {
136     [self expectElementTagsInOrder:@[tagName, otherTagName]];
137 }
138
139 - (id)objectByEvaluatingJavaScript:(NSString *)script
140 {
141     bool isWaitingForJavaScript = false;
142     RetainPtr<id> evalResult;
143     [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) {
144         evalResult = result;
145         isWaitingForJavaScript = true;
146         EXPECT_TRUE(!error);
147         if (error)
148             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
149     }];
150     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
151     return evalResult.autorelease();
152 }
153
154 - (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script
155 {
156     bool isWaitingForJavaScript = false;
157     RetainPtr<id> evalResult;
158     [self evaluateJavaScript:script completionHandler:[&] (id result, NSError *error) {
159         evalResult = result;
160         isWaitingForJavaScript = true;
161         EXPECT_TRUE(!error);
162         if (error)
163             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
164     }];
165     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
166     return evalResult.autorelease();
167 }
168
169 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script
170 {
171     return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
172 }
173
174 @end
175
176 @implementation TestMessageHandler {
177     NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers;
178 }
179
180 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler
181 {
182     if (!_messageHandlers)
183         _messageHandlers = [NSMutableDictionary dictionary];
184
185     _messageHandlers[message] = [handler copy];
186 }
187
188 - (void)removeMessage:(NSString *)message
189 {
190     [_messageHandlers removeObjectForKey:message];
191 }
192
193 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
194 {
195     dispatch_block_t handler = _messageHandlers[message.body];
196     if (handler)
197         handler();
198 }
199
200 @end
201
202 #if PLATFORM(MAC)
203 @interface TestWKWebViewHostWindow : NSWindow
204 #else
205 @interface TestWKWebViewHostWindow : UIWindow
206 #endif // PLATFORM(MAC)
207 @end
208
209 @implementation TestWKWebViewHostWindow {
210     BOOL _forceKeyWindow;
211 }
212
213 #if PLATFORM(MAC)
214 static int gEventNumber = 1;
215
216 NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
217 {
218     return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
219 }
220
221 - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
222 {
223     NSEventType mouseEventType = NSEventTypeLeftMouseDown;
224
225     NSEventMask modifierFlags = 0;
226     if (simulatePressure)
227         modifierFlags |= NSEventMaskPressure;
228
229     NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
230     if (!simulatePressure) {
231         [self sendEvent:event];
232         return;
233     }
234
235     IMP simulatedAssociatedEventsMaskImpl = (IMP)__simulated_forceClickAssociatedEventsMask;
236     Method associatedEventsMaskMethod = class_getInstanceMethod([NSEvent class], @selector(associatedEventsMask));
237     IMP originalAssociatedEventsMaskImpl = method_setImplementation(associatedEventsMaskMethod, simulatedAssociatedEventsMaskImpl);
238     @try {
239         [self sendEvent:event];
240     } @finally {
241         // In the case where event sending raises an exception, we still want to restore the original implementation
242         // to prevent subsequent event sending tests from being affected.
243         method_setImplementation(associatedEventsMaskMethod, originalAssociatedEventsMaskImpl);
244     }
245 }
246
247 - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
248 {
249     [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
250 }
251 #endif // PLATFORM(MAC)
252
253 - (BOOL)isKeyWindow
254 {
255     return _forceKeyWindow || [super isKeyWindow];
256 }
257
258 - (void)makeKeyWindow
259 {
260     if (_forceKeyWindow)
261         return;
262
263     _forceKeyWindow = YES;
264 #if PLATFORM(MAC)
265     [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
266 #else
267     [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
268 #endif
269 }
270
271 - (void)resignKeyWindow
272 {
273     _forceKeyWindow = NO;
274     [super resignKeyWindow];
275 }
276
277 @end
278
279 #if PLATFORM(IOS_FAMILY)
280
281 static NSArray<NSString *> *writableTypeIdentifiersForItemProviderWithoutPublicRTFD()
282 {
283     return @[
284         @"com.apple.uikit.attributedstring",
285         (__bridge NSString *)kUTTypeFlatRTFD,
286         (__bridge NSString *)kUTTypeUTF8PlainText,
287     ];
288 }
289
290 static void applyWorkaroundToAllowWritingAttributedStringsToItemProviders()
291 {
292     // FIXME: Remove this once <rdar://problem/51510554> is fixed.
293     static std::unique_ptr<ClassMethodSwizzler> swizzler;
294     static dispatch_once_t onceToken;
295     dispatch_once(&onceToken, ^{
296         swizzler = makeUnique<ClassMethodSwizzler>(NSAttributedString.class, @selector(writableTypeIdentifiersForItemProvider), reinterpret_cast<IMP>(writableTypeIdentifiersForItemProviderWithoutPublicRTFD));
297     });
298 }
299
300 using InputSessionChangeCount = NSUInteger;
301 static InputSessionChangeCount nextInputSessionChangeCount()
302 {
303     static InputSessionChangeCount gInputSessionChangeCount = 0;
304     return ++gInputSessionChangeCount;
305 }
306
307 #endif
308
309 @implementation TestWKWebView {
310     RetainPtr<TestWKWebViewHostWindow> _hostWindow;
311     RetainPtr<TestMessageHandler> _testHandler;
312 #if PLATFORM(IOS_FAMILY)
313     std::unique_ptr<ClassMethodSwizzler> _sharedCalloutBarSwizzler;
314     InputSessionChangeCount _inputSessionChangeCount;
315 #endif
316 }
317
318 - (instancetype)initWithFrame:(CGRect)frame
319 {
320     WKWebViewConfiguration *defaultConfiguration = [[[WKWebViewConfiguration alloc] init] autorelease];
321     return [self initWithFrame:frame configuration:defaultConfiguration];
322 }
323
324 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
325 {
326     return [self initWithFrame:frame configuration:configuration addToWindow:YES];
327 }
328
329 #if PLATFORM(IOS_FAMILY)
330
331 static UICalloutBar *suppressUICalloutBar()
332 {
333     return nil;
334 }
335
336 #endif
337
338 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration addToWindow:(BOOL)addToWindow
339 {
340     self = [super initWithFrame:frame configuration:configuration];
341     if (!self)
342         return nil;
343
344     if (addToWindow)
345         [self _setUpTestWindow:frame];
346
347 #if PLATFORM(IOS_FAMILY)
348     // FIXME: Remove this workaround once <https://webkit.org/b/175204> is fixed.
349     _sharedCalloutBarSwizzler = makeUnique<ClassMethodSwizzler>([UICalloutBar class], @selector(sharedCalloutBar), reinterpret_cast<IMP>(suppressUICalloutBar));
350     _inputSessionChangeCount = 0;
351     applyWorkaroundToAllowWritingAttributedStringsToItemProviders();
352 #endif
353
354     return self;
355 }
356
357 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration
358 {
359     [configuration setProcessPool:[[[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration] autorelease]];
360     return [self initWithFrame:frame configuration:configuration];
361 }
362
363 - (void)_setUpTestWindow:(NSRect)frame
364 {
365 #if PLATFORM(MAC)
366     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
367     [_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
368     [_hostWindow setIsVisible:YES];
369     [_hostWindow contentView].wantsLayer = YES;
370     [[_hostWindow contentView] addSubview:self];
371     [_hostWindow makeKeyAndOrderFront:self];
372 #else
373     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
374     [_hostWindow setHidden:NO];
375     [_hostWindow addSubview:self];
376 #endif
377 }
378
379 - (void)addToTestWindow
380 {
381 #if PLATFORM(MAC)
382     [[_hostWindow contentView] addSubview:self];
383 #else
384     [_hostWindow addSubview:self];
385 #endif
386 }
387
388 - (void)clearMessageHandlers:(NSArray *)messageNames
389 {
390     for (NSString *messageName in messageNames)
391         [_testHandler removeMessage:messageName];
392 }
393
394 - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action
395 {
396     if (!_testHandler) {
397         _testHandler = adoptNS([[TestMessageHandler alloc] init]);
398         [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"];
399     }
400
401     [_testHandler addMessage:message withHandler:action];
402 }
403
404 - (void)waitForMessage:(NSString *)message
405 {
406     __block bool isDoneWaiting = false;
407     [self performAfterReceivingMessage:message action:^()
408     {
409         isDoneWaiting = true;
410     }];
411     TestWebKitAPI::Util::run(&isDoneWaiting);
412 }
413
414 - (void)performAfterLoading:(dispatch_block_t)actions
415 {
416     TestMessageHandler *handler = [[TestMessageHandler alloc] init];
417     [handler addMessage:@"loaded" withHandler:actions];
418
419     NSString *onloadScript = @"window.onload = () => window.webkit.messageHandlers.onloadHandler.postMessage('loaded')";
420     WKUserScript *script = [[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
421
422     WKUserContentController* contentController = [[self configuration] userContentController];
423     [contentController addUserScript:script];
424     [contentController addScriptMessageHandler:handler name:@"onloadHandler"];
425 }
426
427 - (void)waitForNextPresentationUpdate
428 {
429     __block bool done = false;
430     [self _doAfterNextPresentationUpdate:^() {
431         done = true;
432     }];
433
434     TestWebKitAPI::Util::run(&done);
435 }
436
437 - (void)forceDarkMode
438 {
439 #if HAVE(OS_DARK_MODE_SUPPORT)
440 #if USE(APPKIT)
441     [self setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]];
442 #else
443     [self setOverrideUserInterfaceStyle:UIUserInterfaceStyleDark];
444 #endif
445 #endif
446 }
447
448 - (NSString *)stylePropertyAtSelectionStart:(NSString *)propertyName
449 {
450     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).startContainer.parentElement)['%@']", propertyName];
451     return [self stringByEvaluatingJavaScript:script];
452 }
453
454 - (NSString *)stylePropertyAtSelectionEnd:(NSString *)propertyName
455 {
456     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).endContainer.parentElement)['%@']", propertyName];
457     return [self stringByEvaluatingJavaScript:script];
458 }
459
460 - (void)collapseToStart
461 {
462     [self evaluateJavaScript:@"getSelection().collapseToStart()" completionHandler:nil];
463 }
464
465 - (void)collapseToEnd
466 {
467     [self evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
468 }
469
470 #if PLATFORM(IOS_FAMILY)
471
472 - (void)didStartFormControlInteraction
473 {
474     _inputSessionChangeCount = nextInputSessionChangeCount();
475 }
476
477 - (void)didEndFormControlInteraction
478 {
479     _inputSessionChangeCount = 0;
480 }
481
482 #endif // PLATFORM(IOS_FAMILY)
483
484 @end
485
486 #if PLATFORM(IOS_FAMILY)
487
488 @implementation TestWKWebView (IOSOnly)
489
490 - (void)evaluateJavaScriptAndWaitForInputSessionToChange:(NSString *)script
491 {
492     auto initialChangeCount = _inputSessionChangeCount;
493     BOOL hasEmittedWarning = NO;
494     NSTimeInterval secondsToWaitUntilWarning = 2;
495     NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
496
497     [self objectByEvaluatingJavaScript:script];
498     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
499         if (_inputSessionChangeCount != initialChangeCount)
500             break;
501
502         if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
503             continue;
504
505         NSLog(@"Warning: expecting input session change count to differ from %lu", static_cast<unsigned long>(initialChangeCount));
506         hasEmittedWarning = YES;
507     }
508 }
509
510 - (UIView <UITextInputPrivate, UITextInputMultiDocument> *)textInputContentView
511 {
512     return (UIView <UITextInputPrivate, UITextInputMultiDocument> *)[self valueForKey:@"_currentContentView"];
513 }
514
515 - (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate
516 {
517     RetainPtr<TestWKWebView> retainedSelf = self;
518
519     __block bool isDone = false;
520     __block RetainPtr<NSArray> selectionRects;
521     [self _doAfterNextPresentationUpdate:^() {
522         selectionRects = adoptNS([[retainedSelf _uiTextSelectionRects] retain]);
523         isDone = true;
524     }];
525
526     TestWebKitAPI::Util::run(&isDone);
527     return selectionRects;
528 }
529
530 - (CGRect)caretViewRectInContentCoordinates
531 {
532     UIView *selectionView = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView"];
533     CGRect caretFrame = [[selectionView valueForKeyPath:@"caretView.frame"] CGRectValue];
534     return [selectionView convertRect:caretFrame toView:self.textInputContentView];
535 }
536
537 - (NSArray<NSValue *> *)selectionViewRectsInContentCoordinates
538 {
539     NSMutableArray *selectionRects = [NSMutableArray array];
540     NSArray<UITextSelectionRect *> *rects = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView.rects"];
541     for (UITextSelectionRect *rect in rects)
542         [selectionRects addObject:[NSValue valueWithCGRect:rect.rect]];
543     return selectionRects;
544 }
545
546 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position
547 {
548     __block RetainPtr<_WKActivatedElementInfo> info;
549     __block bool finished = false;
550     [self _requestActivatedElementAtPosition:position completionBlock:^(_WKActivatedElementInfo *elementInfo) {
551         info = elementInfo;
552         finished = true;
553     }];
554
555     TestWebKitAPI::Util::run(&finished);
556     return info.autorelease();
557 }
558
559 static WKContentView *recursiveFindWKContentView(UIView *view)
560 {
561     if ([view isKindOfClass:NSClassFromString(@"WKContentView")])
562         return (WKContentView *)view;
563
564     for (UIView *subview in view.subviews) {
565         WKContentView *contentView = recursiveFindWKContentView(subview);
566         if (contentView)
567             return contentView;
568     }
569
570     return nil;
571 }
572
573 - (WKContentView *)wkContentView
574 {
575     return recursiveFindWKContentView(self);
576 }
577
578 @end
579
580 #endif
581
582 #if PLATFORM(MAC)
583 @implementation TestWKWebView (MacOnly)
584 - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure
585 {
586     [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure clickCount:1];
587 }
588
589 - (void)mouseUpAtPoint:(NSPoint)pointInWindow
590 {
591     [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:1];
592 }
593
594 - (void)mouseMoveToPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags
595 {
596     [self mouseMoved:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:GetCurrentEventTime() clickCount:0]];
597 }
598
599 - (void)sendClicksAtPoint:(NSPoint)pointInWindow numberOfClicks:(NSUInteger)numberOfClicks
600 {
601     for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) {
602         [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:NO clickCount:clickCount];
603         [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:clickCount];
604     }
605 }
606
607 - (void)sendClickAtPoint:(NSPoint)pointInWindow
608 {
609     [self sendClicksAtPoint:pointInWindow numberOfClicks:1];
610 }
611
612 - (void)mouseEnterAtPoint:(NSPoint)pointInWindow
613 {
614     [self mouseEntered:[self _mouseEventWithType:NSEventTypeMouseEntered atLocation:pointInWindow]];
615 }
616
617 - (void)mouseDragToPoint:(NSPoint)pointInWindow
618 {
619     [self mouseDragged:[self _mouseEventWithType:NSEventTypeLeftMouseDragged atLocation:pointInWindow]];
620 }
621
622 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)pointInWindow
623 {
624     return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:GetCurrentEventTime() clickCount:0];
625 }
626
627 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)locationInWindow flags:(NSEventModifierFlags)flags timestamp:(NSTimeInterval)timestamp clickCount:(NSUInteger)clickCount
628 {
629     switch (type) {
630     case NSEventTypeMouseEntered:
631     case NSEventTypeMouseExited:
632         return [NSEvent enterExitEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber trackingNumber:1 userData:nil];
633     default:
634         return [NSEvent mouseEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0];
635     }
636 }
637
638 - (NSWindow *)hostWindow
639 {
640     return _hostWindow.get();
641 }
642
643 - (void)typeCharacter:(char)character
644 {
645     NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
646     NSEventType keyDownEventType = NSEventTypeKeyDown;
647     NSEventType keyUpEventType = NSEventTypeKeyUp;
648     [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
649     [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
650 }
651
652 @end
653 #endif // PLATFORM(MAC)
654
655 #if PLATFORM(IOS_FAMILY)
656 @implementation UIView (WKTestingUIViewUtilities)
657
658 - (UIView *)wkFirstSubviewWithClass:(Class)targetClass
659 {
660     for (UIView *view in self.subviews) {
661         if ([view isKindOfClass:targetClass])
662             return view;
663     
664         UIView *foundSubview = [view wkFirstSubviewWithClass:targetClass];
665         if (foundSubview)
666             return foundSubview;
667     }
668     
669     return nil;
670 }
671
672 - (UIView *)wkFirstSubviewWithBoundsSize:(CGSize)size
673 {
674     for (UIView *view in self.subviews) {
675         if (CGSizeEqualToSize([view bounds].size, size))
676             return view;
677     
678         UIView *foundSubview = [view wkFirstSubviewWithBoundsSize:size];
679         if (foundSubview)
680             return foundSubview;
681     }
682     
683     return nil;
684 }
685
686 @end
687
688 #endif // PLATFORM(IOS_FAMILY)