[Cocoa] Web Automation: non-sticky virtual keys like 'left arrow' don't work properly
[WebKit-https.git] / Source / WebKit2 / UIProcess / Automation / ios / WebAutomationSessionIOS.mm
1 /*
2  * Copyright (C) 2017 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)
30
31 #import "NativeWebKeyboardEvent.h"
32 #import "WebAutomationSessionMacros.h"
33 #import "WebPageProxy.h"
34 #import <WebCore/KeyEventCodesIOS.h>
35 #import <WebCore/NotImplemented.h>
36 #import <WebCore/WebEvent.h>
37
38 using namespace WebCore;
39
40 namespace WebKit {
41
42 void WebAutomationSession::sendSynthesizedEventsToPage(WebPageProxy& page, NSArray *eventsToSend)
43 {
44     // 'eventsToSend' contains WebCore::WebEvent instances. Use a wrapper type specific to the event type.
45     for (::WebEvent *event in eventsToSend) {
46         switch (event.type) {
47         case WebEventMouseDown:
48         case WebEventMouseUp:
49         case WebEventMouseMoved:
50         case WebEventScrollWheel:
51         case WebEventTouchBegin:
52         case WebEventTouchChange:
53         case WebEventTouchEnd:
54         case WebEventTouchCancel:
55             notImplemented();
56             break;
57
58         case WebEventKeyDown:
59         case WebEventKeyUp:
60             page.handleKeyboardEvent(NativeWebKeyboardEvent(event));
61             break;
62         }
63     }
64 }
65
66 #pragma mark Commands for Platform: 'iOS'
67
68 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy& page, Inspector::Protocol::Automation::KeyboardInteractionType interaction, Inspector::Protocol::Automation::VirtualKey key)
69 {
70     // The modifiers changed by the virtual key when it is pressed or released.
71     WebEventFlags changedModifiers = 0;
72
73     // Figure out the effects of sticky modifiers.
74     switch (key) {
75     case Inspector::Protocol::Automation::VirtualKey::Shift:
76         changedModifiers |= WebEventFlagMaskShift;
77         break;
78     case Inspector::Protocol::Automation::VirtualKey::Control:
79         changedModifiers |= WebEventFlagMaskControl;
80         break;
81     case Inspector::Protocol::Automation::VirtualKey::Alternate:
82         changedModifiers |= WebEventFlagMaskAlternate;
83         break;
84     case Inspector::Protocol::Automation::VirtualKey::Meta:
85         // The 'meta' key does not exist on Apple keyboards and is usually
86         // mapped to the Command key when using third-party keyboards.
87     case Inspector::Protocol::Automation::VirtualKey::Command:
88         changedModifiers |= WebEventFlagMaskCommand;
89         break;
90     default:
91         break;
92     }
93
94     // UIKit does not send key codes for virtual keys even for a hardware keyboard.
95     // Instead, it sends single unichars and WebCore maps these to "windows" key codes.
96     // Synthesize a single unichar such that the correct key code is inferred.
97     std::optional<unichar> charCode = charCodeForVirtualKey(key);
98     std::optional<unichar> charCodeIgnoringModifiers = charCodeIgnoringModifiersForVirtualKey(key);
99
100     // FIXME: consider using UIKit SPI to normalize 'characters', i.e., changing * to Shift-8,
101     // and passing that in to charactersIgnoringModifiers. This is probably not worth the trouble
102     // unless it causes an actual behavioral difference.
103     NSString *characters = charCode ? [NSString stringWithCharacters:&charCode.value() length:1] : nil;
104     NSString *unmodifiedCharacters = charCodeIgnoringModifiers ? [NSString stringWithCharacters:&charCodeIgnoringModifiers.value() length:1] : nil;
105     BOOL isTabKey = charCode && charCode.value() == NSTabCharacter;
106
107     // This is used as WebEvent.keyboardFlags, which are only used if we need to
108     // send this event back to UIKit to be interpreted by the keyboard / input manager.
109     // Just ignore this for now; we can fix it if there's an actual behavioral difference.
110     NSUInteger inputFlags = 0;
111
112     // Provide an empty keyCode so that WebCore infers it from the charCode.
113     uint16_t keyCode = 0;
114
115     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
116
117     switch (interaction) {
118     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress: {
119         m_currentModifiers |= changedModifiers;
120
121         [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]];
122         break;
123     }
124     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease: {
125         m_currentModifiers &= ~changedModifiers;
126
127         [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]];
128         break;
129     }
130     case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey: {
131         // Modifiers only change with KeyPress or KeyRelease, this code path is for single characters.
132         [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]];
133         [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]];
134         break;
135     }
136     }
137
138     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
139 }
140
141 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
142 {
143     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
144
145     // Split the text into combining character sequences and send each separately.
146     // This has no similarity to how keyboards work when inputting complex text.
147     // This command is more similar to the 'insertText:' editing command, except
148     // that this emits keyup/keydown/keypress events for roughly each character.
149     // This API should move more towards that direction in the future.
150     NSString *text = keySequence;
151     BOOL isTabKey = [text isEqualToString:@"\t"];
152
153     [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
154         [eventsToBeSent addObject:[[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]];
155         [eventsToBeSent addObject:[[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]];
156     }];
157
158     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
159 }
160
161 } // namespace WebKit
162
163 #endif // PLATFORM(IOS)