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