REGRESSION (r241988): Switching tabs is slow
[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 #import <wtf/mac/AppKitCompatibilityDeclarations.h>
44 #endif
45
46 #if PLATFORM(IOS_FAMILY)
47 #import "UIKitSPI.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)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url
74 {
75     [self loadHTMLString:html baseURL:url];
76     [self _test_waitForDidFinishNavigation];
77 }
78
79 - (void)synchronouslyLoadHTMLString:(NSString *)html
80 {
81     [self synchronouslyLoadHTMLString:html baseURL:[[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"TestWebKitAPI.resources"]];
82 }
83
84 - (void)synchronouslyLoadTestPageNamed:(NSString *)pageName
85 {
86     [self loadTestPageNamed:pageName];
87     [self _test_waitForDidFinishNavigation];
88 }
89
90 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
91 {
92     __block bool done = false;
93     __block bool success;
94     [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) {
95         done = true;
96         success = completionSuccess;
97     }];
98     TestWebKitAPI::Util::run(&done);
99     return success;
100 }
101
102 - (NSArray<NSString *> *)tagsInBody
103 {
104     return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
105 }
106
107 - (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
108 {
109     auto remainingTags = adoptNS([tagNames mutableCopy]);
110     NSArray<NSString *> *tagsInBody = self.tagsInBody;
111     for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
112         if ([tag isEqualToString:[remainingTags lastObject]])
113             [remainingTags removeLastObject];
114         if (![remainingTags count])
115             break;
116     }
117     EXPECT_EQ([remainingTags count], 0U);
118     if ([remainingTags count])
119         NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
120 }
121
122 - (void)expectElementCount:(NSInteger)count querySelector:(NSString *)querySelector
123 {
124     NSString *script = [NSString stringWithFormat:@"document.querySelectorAll('%@').length", querySelector];
125     EXPECT_EQ(count, [self stringByEvaluatingJavaScript:script].integerValue);
126 }
127
128 - (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
129 {
130     [self expectElementTagsInOrder:@[tagName, otherTagName]];
131 }
132
133 - (id)objectByEvaluatingJavaScript:(NSString *)script
134 {
135     bool isWaitingForJavaScript = false;
136     RetainPtr<id> evalResult;
137     [self _evaluateJavaScriptWithoutUserGesture:script completionHandler:[&] (id result, NSError *error) {
138         evalResult = result;
139         isWaitingForJavaScript = true;
140         EXPECT_TRUE(!error);
141         if (error)
142             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
143     }];
144     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
145     return evalResult.autorelease();
146 }
147
148 - (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script
149 {
150     bool isWaitingForJavaScript = false;
151     RetainPtr<id> evalResult;
152     [self evaluateJavaScript:script completionHandler:[&] (id result, NSError *error) {
153         evalResult = result;
154         isWaitingForJavaScript = true;
155         EXPECT_TRUE(!error);
156         if (error)
157             NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
158     }];
159     TestWebKitAPI::Util::run(&isWaitingForJavaScript);
160     return evalResult.autorelease();
161 }
162
163 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script
164 {
165     return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
166 }
167
168 @end
169
170 @implementation TestMessageHandler {
171     NSMutableDictionary<NSString *, dispatch_block_t> *_messageHandlers;
172 }
173
174 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler
175 {
176     if (!_messageHandlers)
177         _messageHandlers = [NSMutableDictionary dictionary];
178
179     _messageHandlers[message] = [handler copy];
180 }
181
182 - (void)removeMessage:(NSString *)message
183 {
184     [_messageHandlers removeObjectForKey:message];
185 }
186
187 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
188 {
189     dispatch_block_t handler = _messageHandlers[message.body];
190     if (handler)
191         handler();
192 }
193
194 @end
195
196 #if PLATFORM(MAC)
197 @interface TestWKWebViewHostWindow : NSWindow
198 #else
199 @interface TestWKWebViewHostWindow : UIWindow
200 #endif // PLATFORM(MAC)
201 @end
202
203 @implementation TestWKWebViewHostWindow {
204     BOOL _forceKeyWindow;
205 }
206
207 #if PLATFORM(MAC)
208 static int gEventNumber = 1;
209
210 NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
211 {
212     return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
213 }
214
215 - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
216 {
217     NSEventType mouseEventType = NSEventTypeLeftMouseDown;
218
219     NSEventMask modifierFlags = 0;
220     if (simulatePressure)
221         modifierFlags |= NSEventMaskPressure;
222
223     NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
224     if (!simulatePressure) {
225         [self sendEvent:event];
226         return;
227     }
228
229     IMP simulatedAssociatedEventsMaskImpl = (IMP)__simulated_forceClickAssociatedEventsMask;
230     Method associatedEventsMaskMethod = class_getInstanceMethod([NSEvent class], @selector(associatedEventsMask));
231     IMP originalAssociatedEventsMaskImpl = method_setImplementation(associatedEventsMaskMethod, simulatedAssociatedEventsMaskImpl);
232     @try {
233         [self sendEvent:event];
234     } @finally {
235         // In the case where event sending raises an exception, we still want to restore the original implementation
236         // to prevent subsequent event sending tests from being affected.
237         method_setImplementation(associatedEventsMaskMethod, originalAssociatedEventsMaskImpl);
238     }
239 }
240
241 - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
242 {
243     [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
244 }
245 #endif // PLATFORM(MAC)
246
247 - (BOOL)isKeyWindow
248 {
249     return _forceKeyWindow || [super isKeyWindow];
250 }
251
252 - (void)makeKeyWindow
253 {
254     if (_forceKeyWindow)
255         return;
256
257     _forceKeyWindow = YES;
258 #if PLATFORM(MAC)
259     [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidBecomeKeyNotification object:self];
260 #else
261     [[NSNotificationCenter defaultCenter] postNotificationName:UIWindowDidBecomeKeyNotification object:self];
262 #endif
263 }
264
265 - (void)resignKeyWindow
266 {
267     _forceKeyWindow = NO;
268     [super resignKeyWindow];
269 }
270
271 @end
272
273 #if PLATFORM(IOS_FAMILY)
274
275 using InputSessionChangeCount = NSUInteger;
276 static InputSessionChangeCount nextInputSessionChangeCount()
277 {
278     static InputSessionChangeCount gInputSessionChangeCount = 0;
279     return ++gInputSessionChangeCount;
280 }
281
282 #endif
283
284 @implementation TestWKWebView {
285     RetainPtr<TestWKWebViewHostWindow> _hostWindow;
286     RetainPtr<TestMessageHandler> _testHandler;
287 #if PLATFORM(IOS_FAMILY)
288     std::unique_ptr<ClassMethodSwizzler> _sharedCalloutBarSwizzler;
289     InputSessionChangeCount _inputSessionChangeCount;
290 #endif
291 }
292
293 - (instancetype)initWithFrame:(CGRect)frame
294 {
295     WKWebViewConfiguration *defaultConfiguration = [[[WKWebViewConfiguration alloc] init] autorelease];
296     return [self initWithFrame:frame configuration:defaultConfiguration];
297 }
298
299 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
300 {
301     return [self initWithFrame:frame configuration:configuration addToWindow:YES];
302 }
303
304 #if PLATFORM(IOS_FAMILY)
305
306 static UICalloutBar *suppressUICalloutBar()
307 {
308     return nil;
309 }
310
311 #endif
312
313 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration addToWindow:(BOOL)addToWindow
314 {
315     self = [super initWithFrame:frame configuration:configuration];
316     if (!self)
317         return nil;
318
319     if (addToWindow)
320         [self _setUpTestWindow:frame];
321
322 #if PLATFORM(IOS_FAMILY)
323     // FIXME: Remove this workaround once <https://webkit.org/b/175204> is fixed.
324     _sharedCalloutBarSwizzler = std::make_unique<ClassMethodSwizzler>([UICalloutBar class], @selector(sharedCalloutBar), reinterpret_cast<IMP>(suppressUICalloutBar));
325     _inputSessionChangeCount = 0;
326 #endif
327
328     return self;
329 }
330
331 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration processPoolConfiguration:(_WKProcessPoolConfiguration *)processPoolConfiguration
332 {
333     [configuration setProcessPool:[[[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration] autorelease]];
334     return [self initWithFrame:frame configuration:configuration];
335 }
336
337 - (void)_setUpTestWindow:(NSRect)frame
338 {
339 #if PLATFORM(MAC)
340     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO]);
341     [_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
342     [_hostWindow setIsVisible:YES];
343     [_hostWindow contentView].wantsLayer = YES;
344     [[_hostWindow contentView] addSubview:self];
345     [_hostWindow makeKeyAndOrderFront:self];
346 #else
347     _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
348     [_hostWindow setHidden:NO];
349     [_hostWindow addSubview:self];
350 #endif
351 }
352
353 - (void)addToTestWindow
354 {
355 #if PLATFORM(MAC)
356     [[_hostWindow contentView] addSubview:self];
357 #else
358     [_hostWindow addSubview:self];
359 #endif
360 }
361
362 - (void)clearMessageHandlers:(NSArray *)messageNames
363 {
364     for (NSString *messageName in messageNames)
365         [_testHandler removeMessage:messageName];
366 }
367
368 - (void)performAfterReceivingMessage:(NSString *)message action:(dispatch_block_t)action
369 {
370     if (!_testHandler) {
371         _testHandler = adoptNS([[TestMessageHandler alloc] init]);
372         [[[self configuration] userContentController] addScriptMessageHandler:_testHandler.get() name:@"testHandler"];
373     }
374
375     [_testHandler addMessage:message withHandler:action];
376 }
377
378 - (void)waitForMessage:(NSString *)message
379 {
380     __block bool isDoneWaiting = false;
381     [self performAfterReceivingMessage:message action:^()
382     {
383         isDoneWaiting = true;
384     }];
385     TestWebKitAPI::Util::run(&isDoneWaiting);
386 }
387
388 - (void)performAfterLoading:(dispatch_block_t)actions {
389     TestMessageHandler *handler = [[TestMessageHandler alloc] init];
390     [handler addMessage:@"loaded" withHandler:actions];
391
392     NSString *onloadScript = @"window.onload = () => window.webkit.messageHandlers.onloadHandler.postMessage('loaded')";
393     WKUserScript *script = [[WKUserScript alloc] initWithSource:onloadScript injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
394
395     WKUserContentController* contentController = [[self configuration] userContentController];
396     [contentController addUserScript:script];
397     [contentController addScriptMessageHandler:handler name:@"onloadHandler"];
398 }
399
400 - (void)waitForNextPresentationUpdate
401 {
402     __block bool done = false;
403     [self _doAfterNextPresentationUpdate:^() {
404         done = true;
405     }];
406
407     TestWebKitAPI::Util::run(&done);
408 }
409
410 - (NSString *)stylePropertyAtSelectionStart:(NSString *)propertyName
411 {
412     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).startContainer.parentElement)['%@']", propertyName];
413     return [self stringByEvaluatingJavaScript:script];
414 }
415
416 - (NSString *)stylePropertyAtSelectionEnd:(NSString *)propertyName
417 {
418     NSString *script = [NSString stringWithFormat:@"getComputedStyle(getSelection().getRangeAt(0).endContainer.parentElement)['%@']", propertyName];
419     return [self stringByEvaluatingJavaScript:script];
420 }
421
422 - (void)collapseToStart
423 {
424     [self evaluateJavaScript:@"getSelection().collapseToStart()" completionHandler:nil];
425 }
426
427 - (void)collapseToEnd
428 {
429     [self evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
430 }
431
432 #if PLATFORM(IOS_FAMILY)
433
434 - (void)didStartFormControlInteraction
435 {
436     _inputSessionChangeCount = nextInputSessionChangeCount();
437 }
438
439 - (void)didEndFormControlInteraction
440 {
441     _inputSessionChangeCount = 0;
442 }
443
444 #endif // PLATFORM(IOS_FAMILY)
445
446 @end
447
448 #if PLATFORM(IOS_FAMILY)
449
450 @implementation TestWKWebView (IOSOnly)
451
452 - (void)evaluateJavaScriptAndWaitForInputSessionToChange:(NSString *)script
453 {
454     auto initialChangeCount = _inputSessionChangeCount;
455     BOOL hasEmittedWarning = NO;
456     NSTimeInterval secondsToWaitUntilWarning = 2;
457     NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
458
459     [self objectByEvaluatingJavaScript:script];
460     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
461         if (_inputSessionChangeCount != initialChangeCount)
462             break;
463
464         if (hasEmittedWarning || startTime + secondsToWaitUntilWarning >= [NSDate timeIntervalSinceReferenceDate])
465             continue;
466
467         NSLog(@"Warning: expecting input session change count to differ from %lu", static_cast<unsigned long>(initialChangeCount));
468         hasEmittedWarning = YES;
469     }
470 }
471
472 - (UIView <UITextInputPrivate, UITextInputMultiDocument> *)textInputContentView
473 {
474     return (UIView <UITextInputPrivate, UITextInputMultiDocument> *)[self valueForKey:@"_currentContentView"];
475 }
476
477 - (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate
478 {
479     RetainPtr<TestWKWebView> retainedSelf = self;
480
481     __block bool isDone = false;
482     __block RetainPtr<NSArray> selectionRects;
483     [self _doAfterNextPresentationUpdate:^() {
484         selectionRects = adoptNS([[retainedSelf _uiTextSelectionRects] retain]);
485         isDone = true;
486     }];
487
488     TestWebKitAPI::Util::run(&isDone);
489     return selectionRects;
490 }
491
492 - (CGRect)caretViewRectInContentCoordinates
493 {
494     UIView *selectionView = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView"];
495     CGRect caretFrame = [[selectionView valueForKeyPath:@"caretView.frame"] CGRectValue];
496     return [selectionView convertRect:caretFrame toView:self.textInputContentView];
497 }
498
499 - (NSArray<NSValue *> *)selectionViewRectsInContentCoordinates
500 {
501     NSMutableArray *selectionRects = [NSMutableArray array];
502     NSArray<UITextSelectionRect *> *rects = [self.textInputContentView valueForKeyPath:@"interactionAssistant.selectionView.rangeView.rects"];
503     for (UITextSelectionRect *rect in rects)
504         [selectionRects addObject:[NSValue valueWithCGRect:rect.rect]];
505     return selectionRects;
506 }
507
508 - (_WKActivatedElementInfo *)activatedElementAtPosition:(CGPoint)position
509 {
510     __block RetainPtr<_WKActivatedElementInfo> info;
511     __block bool finished = false;
512     [self _requestActivatedElementAtPosition:position completionBlock:^(_WKActivatedElementInfo *elementInfo) {
513         info = elementInfo;
514         finished = true;
515     }];
516
517     TestWebKitAPI::Util::run(&finished);
518     return info.autorelease();
519 }
520
521 static WKContentView *recursiveFindWKContentView(UIView *view)
522 {
523     if ([view isKindOfClass:NSClassFromString(@"WKContentView")])
524         return (WKContentView *)view;
525
526     for (UIView *subview in view.subviews) {
527         WKContentView *contentView = recursiveFindWKContentView(subview);
528         if (contentView)
529             return contentView;
530     }
531
532     return nil;
533 }
534
535 - (WKContentView *)wkContentView
536 {
537     return recursiveFindWKContentView(self);
538 }
539
540 @end
541
542 #endif
543
544 #if PLATFORM(MAC)
545 @implementation TestWKWebView (MacOnly)
546 - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure
547 {
548     [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure clickCount:1];
549 }
550
551 - (void)mouseUpAtPoint:(NSPoint)pointInWindow
552 {
553     [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:1];
554 }
555
556 - (void)mouseMoveToPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags
557 {
558     [self mouseMoved:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:GetCurrentEventTime() clickCount:0]];
559 }
560
561 - (void)sendClicksAtPoint:(NSPoint)pointInWindow numberOfClicks:(NSUInteger)numberOfClicks
562 {
563     for (NSUInteger clickCount = 1; clickCount <= numberOfClicks; ++clickCount) {
564         [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:NO clickCount:clickCount];
565         [_hostWindow _mouseUpAtPoint:pointInWindow clickCount:clickCount];
566     }
567 }
568
569 - (void)mouseEnterAtPoint:(NSPoint)pointInWindow
570 {
571     [self mouseEntered:[self _mouseEventWithType:NSEventTypeMouseEntered atLocation:pointInWindow]];
572 }
573
574 - (void)mouseDragToPoint:(NSPoint)pointInWindow
575 {
576     [self mouseDragged:[self _mouseEventWithType:NSEventTypeLeftMouseDragged atLocation:pointInWindow]];
577 }
578
579 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)pointInWindow
580 {
581     return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:GetCurrentEventTime() clickCount:0];
582 }
583
584 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)locationInWindow flags:(NSEventModifierFlags)flags timestamp:(NSTimeInterval)timestamp clickCount:(NSUInteger)clickCount
585 {
586     switch (type) {
587     case NSEventTypeMouseEntered:
588     case NSEventTypeMouseExited:
589         return [NSEvent enterExitEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber trackingNumber:1 userData:nil];
590     default:
591         return [NSEvent mouseEventWithType:type location:locationInWindow modifierFlags:flags timestamp:timestamp windowNumber:[_hostWindow windowNumber] context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0];
592     }
593 }
594
595 - (NSWindow *)hostWindow
596 {
597     return _hostWindow.get();
598 }
599
600 - (void)typeCharacter:(char)character
601 {
602     NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
603     NSEventType keyDownEventType = NSEventTypeKeyDown;
604     NSEventType keyUpEventType = NSEventTypeKeyUp;
605     [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
606     [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
607 }
608
609 @end
610 #endif