3c5cad8302953a09e07574b04fbf94a89a91d885
[WebKit-https.git] / Source / WebKit / UIProcess / Automation / ios / WebAutomationSessionIOS.mm
1 /*
2  * Copyright (C) 2017, 2019 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 "WebAutomationSession.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "NativeWebKeyboardEvent.h"
32 #import "WebAutomationSessionMacros.h"
33 #import "WebPageProxy.h"
34 #import "_WKTouchEventGenerator.h"
35 #import <WebCore/KeyEventCodesIOS.h>
36 #import <WebCore/NotImplemented.h>
37 #import <WebCore/WebEvent.h>
38 #import <wtf/BlockPtr.h>
39
40 namespace WebKit {
41 using namespace WebCore;
42
43 void WebAutomationSession::sendSynthesizedEventsToPage(WebPageProxy& page, NSArray *eventsToSend)
44 {
45     // 'eventsToSend' contains WebCore::WebEvent instances. Use a wrapper type specific to the event type.
46     for (::WebEvent *event in eventsToSend) {
47         switch (event.type) {
48         case WebEventMouseDown:
49         case WebEventMouseUp:
50         case WebEventMouseMoved:
51         case WebEventScrollWheel:
52         case WebEventTouchBegin:
53         case WebEventTouchChange:
54         case WebEventTouchEnd:
55         case WebEventTouchCancel:
56             notImplemented();
57             break;
58
59         case WebEventKeyDown:
60         case WebEventKeyUp:
61             page.handleKeyboardEvent(NativeWebKeyboardEvent(event, NativeWebKeyboardEvent::HandledByInputMethod::No));
62             break;
63         }
64     }
65 }
66
67 #pragma mark Commands for Platform: 'iOS'
68
69 #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
70 void WebAutomationSession::platformSimulateKeyboardInteraction(WebPageProxy& page, KeyboardInteraction interaction, WTF::Variant<VirtualKey, CharKey>&& key)
71 {
72     // The modifiers changed by the virtual key when it is pressed or released.
73     WebEventFlags changedModifiers = 0;
74
75     // UIKit does not send key codes for virtual keys even for a hardware keyboard.
76     // Instead, it sends single unichars and WebCore maps these to "windows" key codes.
77     // Synthesize a single unichar such that the correct key code is inferred.
78     Optional<unichar> charCode;
79     Optional<unichar> charCodeIgnoringModifiers;
80
81     // Figure out the effects of sticky modifiers.
82     WTF::switchOn(key,
83         [&] (VirtualKey virtualKey) {
84             charCode = charCodeForVirtualKey(virtualKey);
85             charCodeIgnoringModifiers = charCodeIgnoringModifiersForVirtualKey(virtualKey);
86
87             switch (virtualKey) {
88             case VirtualKey::Shift:
89                 changedModifiers |= WebEventFlagMaskShiftKey;
90                 break;
91             case VirtualKey::Control:
92                 changedModifiers |= WebEventFlagMaskControlKey;
93                 break;
94             case VirtualKey::Alternate:
95                 changedModifiers |= WebEventFlagMaskOptionKey;
96                 break;
97             case VirtualKey::Meta:
98                 // The 'meta' key does not exist on Apple keyboards and is usually
99                 // mapped to the Command key when using third-party keyboards.
100             case VirtualKey::Command:
101                 changedModifiers |= WebEventFlagMaskCommandKey;
102                 break;
103             default:
104                 break;
105             }
106         },
107         [&] (CharKey charKey) {
108             charCode = (unichar)charKey;
109             charCodeIgnoringModifiers = (unichar)charKey;
110         }
111     );
112
113     // FIXME: consider using UIKit SPI to normalize 'characters', i.e., changing * to Shift-8,
114     // and passing that in to charactersIgnoringModifiers. This is probably not worth the trouble
115     // unless it causes an actual behavioral difference.
116     NSString *characters = charCode ? [NSString stringWithCharacters:&charCode.value() length:1] : nil;
117     NSString *unmodifiedCharacters = charCodeIgnoringModifiers ? [NSString stringWithCharacters:&charCodeIgnoringModifiers.value() length:1] : nil;
118     BOOL isTabKey = charCode && charCode.value() == NSTabCharacter;
119
120     // This is used as WebEvent.keyboardFlags, which are only used if we need to
121     // send this event back to UIKit to be interpreted by the keyboard / input manager.
122     // Just ignore this for now; we can fix it if there's an actual behavioral difference.
123     NSUInteger inputFlags = 0;
124
125     // Provide an empty keyCode so that WebCore infers it from the charCode.
126     uint16_t keyCode = 0;
127
128     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
129
130     switch (interaction) {
131     case KeyboardInteraction::KeyPress: {
132         m_currentModifiers |= changedModifiers;
133
134         [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]];
135         break;
136     }
137     case KeyboardInteraction::KeyRelease: {
138         m_currentModifiers &= ~changedModifiers;
139
140         [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]];
141         break;
142     }
143     case KeyboardInteraction::InsertByKey: {
144         // Modifiers only change with KeyPress or KeyRelease, this code path is for single characters.
145         [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]];
146         [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]];
147         break;
148     }
149     }
150
151     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
152 }
153
154 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
155 {
156     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
157
158     // Split the text into combining character sequences and send each separately.
159     // This has no similarity to how keyboards work when inputting complex text.
160     // This command is more similar to the 'insertText:' editing command, except
161     // that this emits keyup/keydown/keypress events for roughly each character.
162     // This API should move more towards that direction in the future.
163     NSString *text = keySequence;
164     BOOL isTabKey = [text isEqualToString:@"\t"];
165
166     [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
167         auto keyDownEvent = adoptNS([[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]);
168         [eventsToBeSent addObject:keyDownEvent.get()];
169         auto keyUpEvent = adoptNS([[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]);
170         [eventsToBeSent addObject:keyUpEvent.get()];
171     }];
172
173     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
174 }
175 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
176
177 #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
178 void WebAutomationSession::platformSimulateTouchInteraction(WebPageProxy& page, TouchInteraction interaction, const WebCore::IntPoint& locationInViewport, Optional<Seconds> duration, AutomationCompletionHandler&& completionHandler)
179 {
180     WebCore::IntPoint locationOnScreen = page.syncRootViewToScreen(IntRect(locationInViewport, IntSize())).location();
181     _WKTouchEventGenerator *generator = [_WKTouchEventGenerator sharedTouchEventGenerator];
182
183     auto interactionFinished = makeBlockPtr([completionHandler = WTFMove(completionHandler)] () mutable {
184         completionHandler(WTF::nullopt);
185     });
186     
187     switch (interaction) {
188     case TouchInteraction::TouchDown:
189         [generator touchDown:locationOnScreen completionBlock:interactionFinished.get()];
190         break;
191     case TouchInteraction::LiftUp:
192         [generator liftUp:locationOnScreen completionBlock:interactionFinished.get()];
193         break;
194     case TouchInteraction::MoveTo:
195         [generator moveToPoint:locationOnScreen duration:duration.valueOr(0_s).seconds() completionBlock:interactionFinished.get()];
196         break;
197     }
198 }
199 #endif // ENABLE(WEBDRIVER_TOUCH_INTERACTIONS)
200
201 } // namespace WebKit
202
203 #endif // PLATFORM(IOS_FAMILY)