[Cocoa] Web Automation: non-sticky virtual keys like 'left arrow' don't work properly
[WebKit-https.git] / Source / WebKit2 / UIProcess / Automation / mac / WebAutomationSessionMac.mm
1 /*
2  * Copyright (C) 2016, 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(MAC)
30
31 #import "WebAutomationSessionMacros.h"
32 #import "WebInspectorProxy.h"
33 #import "WebPageProxy.h"
34 #import "_WKAutomationSession.h"
35 #import <HIToolbox/Events.h>
36 #import <WebCore/IntPoint.h>
37 #import <WebCore/IntSize.h>
38 #import <WebCore/PlatformMouseEvent.h>
39 #import <objc/runtime.h>
40
41 using namespace WebCore;
42
43 namespace WebKit {
44
45 #pragma mark Commands for Platform: 'macOS'
46
47 void WebAutomationSession::resizeWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& sizeObject)
48 {
49     float width;
50     if (!sizeObject.getDouble(WTF::ASCIILiteral("width"), width))
51         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'width' parameter was not found or invalid.");
52
53     float height;
54     if (!sizeObject.getDouble(WTF::ASCIILiteral("height"), height))
55         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'height' parameter was not found or invalid.");
56
57     if (width < 0)
58         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'width' parameter had an invalid value.");
59
60     if (height < 0)
61         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'height' parameter had an invalid value.");
62
63     WebPageProxy* page = webPageProxyForHandle(handle);
64     if (!page)
65         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
66
67     WebCore::FloatRect originalFrame;
68     page->getWindowFrame(originalFrame);
69
70     WebCore::FloatRect newFrame = WebCore::FloatRect(originalFrame.location(), WebCore::FloatSize(width, height));
71     if (newFrame == originalFrame)
72         return;
73
74     page->setWindowFrame(newFrame);
75
76     // If nothing changed at all, it's probably fair to report that something went wrong.
77     // (We can't assume that the requested frame size will be honored exactly, however.)
78     WebCore::FloatRect updatedFrame;
79     page->getWindowFrame(updatedFrame);
80     if (originalFrame == updatedFrame)
81         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window size was expected to have changed, but did not.");
82 }
83
84 void WebAutomationSession::moveWindowOfBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const Inspector::InspectorObject& positionObject)
85 {
86     float x;
87     if (!positionObject.getDouble(WTF::ASCIILiteral("x"), x))
88         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'x' parameter was not found or invalid.");
89
90     float y;
91     if (!positionObject.getDouble(WTF::ASCIILiteral("y"), y))
92         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(MissingParameter, "The 'y' parameter was not found or invalid.");
93
94     if (x < 0)
95         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'x' parameter had an invalid value.");
96
97     if (y < 0)
98         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InvalidParameter, "The 'y' parameter had an invalid value.");
99
100     WebPageProxy* page = webPageProxyForHandle(handle);
101     if (!page)
102         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
103
104     WebCore::FloatRect originalFrame;
105     page->getWindowFrame(originalFrame);
106
107     WebCore::FloatRect newFrame = WebCore::FloatRect(WebCore::FloatPoint(x, y), originalFrame.size());
108     if (newFrame == originalFrame)
109         return;
110
111     page->setWindowFrame(newFrame);
112
113     // If nothing changed at all, it's probably fair to report that something went wrong.
114     // (We can't assume that the requested frame size will be honored exactly, however.)
115     WebCore::FloatRect updatedFrame;
116     page->getWindowFrame(updatedFrame);
117     if (originalFrame == updatedFrame)
118         FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The window position was expected to have changed, but did not.");
119 }
120
121 void WebAutomationSession::inspectBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const bool* optionalEnableAutoCapturing, Ref<InspectBrowsingContextCallback>&& callback)
122 {
123     WebPageProxy* page = webPageProxyForHandle(handle);
124     if (!page)
125         FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
126
127     if (auto callback = m_pendingInspectorCallbacksPerPage.take(page->pageID()))
128         callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
129     m_pendingInspectorCallbacksPerPage.set(page->pageID(), WTFMove(callback));
130
131     // Don't bring the inspector to front since this may be done automatically.
132     // We just want it loaded so it can pause if a breakpoint is hit during a command.
133     if (page->inspector()) {
134         page->inspector()->connect();
135
136         // Start collecting profile information immediately so the entire session is captured.
137         if (optionalEnableAutoCapturing && *optionalEnableAutoCapturing)
138             page->inspector()->togglePageProfiling();
139     }
140 }
141
142 #pragma mark AppKit Event Simulation Support
143
144 static const NSInteger synthesizedMouseEventMagicEventNumber = 0;
145 static const void *synthesizedAutomationEventAssociatedObjectKey = &synthesizedAutomationEventAssociatedObjectKey;
146
147 void WebAutomationSession::sendSynthesizedEventsToPage(WebPageProxy& page, NSArray *eventsToSend)
148 {
149     NSWindow *window = page.platformWindow();
150
151     for (NSEvent *event in eventsToSend) {
152         // Take focus back in case the Inspector became focused while the prior command or
153         // NSEvent was delivered to the window.
154         [window becomeKeyWindow];
155
156         markEventAsSynthesizedForAutomation(event);
157         [window sendEvent:event];
158     }
159 }
160
161 void WebAutomationSession::markEventAsSynthesizedForAutomation(NSEvent *event)
162 {
163     objc_setAssociatedObject(event, &synthesizedAutomationEventAssociatedObjectKey, m_sessionIdentifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
164 }
165
166 bool WebAutomationSession::wasEventSynthesizedForAutomation(NSEvent *event)
167 {
168     NSString *senderSessionIdentifier = objc_getAssociatedObject(event, &synthesizedAutomationEventAssociatedObjectKey);
169     if ([senderSessionIdentifier isEqualToString:m_sessionIdentifier])
170         return true;
171
172     switch (event.type) {
173     case NSEventTypeLeftMouseDown:
174     case NSEventTypeLeftMouseDragged:
175     case NSEventTypeLeftMouseUp:
176     case NSEventTypeMouseMoved:
177     case NSEventTypeOtherMouseDown:
178     case NSEventTypeOtherMouseDragged:
179     case NSEventTypeOtherMouseUp:
180     case NSEventTypeRightMouseDown:
181     case NSEventTypeRightMouseDragged:
182     case NSEventTypeRightMouseUp:
183         // Use this as a backup for checking mouse events, which are frequently copied
184         // and/or faked by AppKit, causing them to lose their associated object tag.
185         return event.eventNumber == synthesizedMouseEventMagicEventNumber;
186     default:
187         break;
188     }
189
190     return false;
191 }
192
193 #pragma mark Platform-dependent Implementations
194
195 void WebAutomationSession::platformSimulateMouseInteraction(WebPageProxy& page, const WebCore::IntPoint& viewPosition, Inspector::Protocol::Automation::MouseInteraction interaction, Inspector::Protocol::Automation::MouseButton button, WebEvent::Modifiers keyModifiers)
196 {
197     IntRect windowRect;
198     page.rootViewToWindow(IntRect(viewPosition, IntSize()), windowRect);
199     IntPoint windowPosition = windowRect.location();
200
201     NSEventModifierFlags modifiers = 0;
202     if (keyModifiers & WebEvent::MetaKey)
203         modifiers |= NSEventModifierFlagCommand;
204     if (keyModifiers & WebEvent::AltKey)
205         modifiers |= NSEventModifierFlagOption;
206     if (keyModifiers & WebEvent::ControlKey)
207         modifiers |= NSEventModifierFlagControl;
208     if (keyModifiers & WebEvent::ShiftKey)
209         modifiers |= NSEventModifierFlagShift;
210     if (keyModifiers & WebEvent::CapsLockKey)
211         modifiers |= NSEventModifierFlagCapsLock;
212
213     NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
214     NSWindow *window = page.platformWindow();
215     NSInteger windowNumber = window.windowNumber;
216
217     NSEventType downEventType = (NSEventType)0;
218     NSEventType dragEventType = (NSEventType)0;
219     NSEventType upEventType = (NSEventType)0;
220     switch (button) {
221     case Inspector::Protocol::Automation::MouseButton::None:
222         downEventType = upEventType = dragEventType = NSEventTypeMouseMoved;
223         break;
224     case Inspector::Protocol::Automation::MouseButton::Left:
225         downEventType = NSEventTypeLeftMouseDown;
226         dragEventType = NSEventTypeLeftMouseDragged;
227         upEventType = NSEventTypeLeftMouseUp;
228         break;
229     case Inspector::Protocol::Automation::MouseButton::Middle:
230         downEventType = NSEventTypeOtherMouseDown;
231         dragEventType = NSEventTypeLeftMouseDragged;
232         upEventType = NSEventTypeOtherMouseUp;
233         break;
234     case Inspector::Protocol::Automation::MouseButton::Right:
235         downEventType = NSEventTypeRightMouseDown;
236         upEventType = NSEventTypeRightMouseUp;
237         break;
238     }
239
240     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
241
242     NSInteger eventNumber = synthesizedMouseEventMagicEventNumber;
243
244     switch (interaction) {
245     case Inspector::Protocol::Automation::MouseInteraction::Move:
246         ASSERT(dragEventType);
247         [eventsToBeSent addObject:[NSEvent mouseEventWithType:dragEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:0 pressure:0.0f]];
248         break;
249     case Inspector::Protocol::Automation::MouseInteraction::Down:
250         ASSERT(downEventType);
251
252         // Hard-code the click count to one, since clients don't expect successive simulated
253         // down/up events to be potentially counted as a double click event.
254         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
255         break;
256     case Inspector::Protocol::Automation::MouseInteraction::Up:
257         ASSERT(upEventType);
258
259         // Hard-code the click count to one, since clients don't expect successive simulated
260         // down/up events to be potentially counted as a double click event.
261         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
262         break;
263     case Inspector::Protocol::Automation::MouseInteraction::SingleClick:
264         ASSERT(upEventType);
265         ASSERT(downEventType);
266
267         // Send separate down and up events. WebCore will see this as a single-click event.
268         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
269         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
270         break;
271     case Inspector::Protocol::Automation::MouseInteraction::DoubleClick:
272         ASSERT(upEventType);
273         ASSERT(downEventType);
274
275         // Send multiple down and up events with proper click count.
276         // WebCore will see this as a single-click event then double-click event.
277         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
278         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
279         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:WebCore::ForceAtClick]];
280         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:0.0f]];
281     }
282
283     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
284 }
285
286 static bool keyHasStickyModifier(Inspector::Protocol::Automation::VirtualKey key)
287 {
288     // Returns whether the key's modifier flags should affect other events while pressed down.
289     switch (key) {
290     case Inspector::Protocol::Automation::VirtualKey::Shift:
291     case Inspector::Protocol::Automation::VirtualKey::Control:
292     case Inspector::Protocol::Automation::VirtualKey::Alternate:
293     case Inspector::Protocol::Automation::VirtualKey::Meta:
294     case Inspector::Protocol::Automation::VirtualKey::Command:
295         return true;
296
297     default:
298         return false;
299     }
300 }
301
302 static int keyCodeForVirtualKey(Inspector::Protocol::Automation::VirtualKey key)
303 {
304     // The likely keyCode for the virtual key as defined in <HIToolbox/Events.h>.
305     switch (key) {
306     case Inspector::Protocol::Automation::VirtualKey::Shift:
307         return kVK_Shift;
308     case Inspector::Protocol::Automation::VirtualKey::Control:
309         return kVK_Control;
310     case Inspector::Protocol::Automation::VirtualKey::Alternate:
311         return kVK_Option;
312     case Inspector::Protocol::Automation::VirtualKey::Meta:
313         // The 'meta' key does not exist on Apple keyboards and is usually
314         // mapped to the Command key when using third-party keyboards.
315     case Inspector::Protocol::Automation::VirtualKey::Command:
316         return kVK_Command;
317     case Inspector::Protocol::Automation::VirtualKey::Help:
318         return kVK_Help;
319     case Inspector::Protocol::Automation::VirtualKey::Backspace:
320         return kVK_Delete;
321     case Inspector::Protocol::Automation::VirtualKey::Tab:
322         return kVK_Tab;
323     case Inspector::Protocol::Automation::VirtualKey::Clear:
324         return kVK_ANSI_KeypadClear;
325     case Inspector::Protocol::Automation::VirtualKey::Enter:
326         return kVK_ANSI_KeypadEnter;
327     case Inspector::Protocol::Automation::VirtualKey::Pause:
328         // The 'pause' key does not exist on Apple keyboards and has no keyCode.
329         // The semantics are unclear so just abort and do nothing.
330         return 0;
331     case Inspector::Protocol::Automation::VirtualKey::Cancel:
332         // The 'cancel' key does not exist on Apple keyboards and has no keyCode.
333         // According to the internet its functionality is similar to 'Escape'.
334     case Inspector::Protocol::Automation::VirtualKey::Escape:
335         return kVK_Escape;
336     case Inspector::Protocol::Automation::VirtualKey::PageUp:
337         return kVK_PageUp;
338     case Inspector::Protocol::Automation::VirtualKey::PageDown:
339         return kVK_PageDown;
340     case Inspector::Protocol::Automation::VirtualKey::End:
341         return kVK_End;
342     case Inspector::Protocol::Automation::VirtualKey::Home:
343         return kVK_Home;
344     case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
345         return kVK_LeftArrow;
346     case Inspector::Protocol::Automation::VirtualKey::UpArrow:
347         return kVK_UpArrow;
348     case Inspector::Protocol::Automation::VirtualKey::RightArrow:
349         return kVK_RightArrow;
350     case Inspector::Protocol::Automation::VirtualKey::DownArrow:
351         return kVK_DownArrow;
352     case Inspector::Protocol::Automation::VirtualKey::Insert:
353         // The 'insert' key does not exist on Apple keyboards and has no keyCode.
354         // The semantics are unclear so just abort and do nothing.
355         return 0;
356     case Inspector::Protocol::Automation::VirtualKey::Delete:
357         return kVK_ForwardDelete;
358     case Inspector::Protocol::Automation::VirtualKey::Space:
359         return kVK_Space;
360     case Inspector::Protocol::Automation::VirtualKey::Semicolon:
361         return kVK_ANSI_Semicolon;
362     case Inspector::Protocol::Automation::VirtualKey::Equals:
363         return kVK_ANSI_Equal;
364     case Inspector::Protocol::Automation::VirtualKey::Return:
365         return kVK_Return;
366     case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
367         return kVK_ANSI_Keypad0;
368     case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
369         return kVK_ANSI_Keypad1;
370     case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
371         return kVK_ANSI_Keypad2;
372     case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
373         return kVK_ANSI_Keypad3;
374     case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
375         return kVK_ANSI_Keypad4;
376     case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
377         return kVK_ANSI_Keypad5;
378     case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
379         return kVK_ANSI_Keypad6;
380     case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
381         return kVK_ANSI_Keypad7;
382     case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
383         return kVK_ANSI_Keypad8;
384     case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
385         return kVK_ANSI_Keypad9;
386     case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
387         return kVK_ANSI_KeypadMultiply;
388     case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
389         return kVK_ANSI_KeypadPlus;
390     case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
391         return kVK_ANSI_KeypadMinus;
392     case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
393         // The 'Separator' key is only present on a few international keyboards.
394         // It is usually mapped to the same character as Decimal ('.' or ',').
395         FALLTHROUGH;
396     case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
397         return kVK_ANSI_KeypadDecimal;
398         // FIXME: this might be locale-dependent. See the above comment.
399     case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
400         return kVK_ANSI_KeypadDivide;
401     case Inspector::Protocol::Automation::VirtualKey::Function1:
402         return kVK_F1;
403     case Inspector::Protocol::Automation::VirtualKey::Function2:
404         return kVK_F2;
405     case Inspector::Protocol::Automation::VirtualKey::Function3:
406         return kVK_F3;
407     case Inspector::Protocol::Automation::VirtualKey::Function4:
408         return kVK_F4;
409     case Inspector::Protocol::Automation::VirtualKey::Function5:
410         return kVK_F5;
411     case Inspector::Protocol::Automation::VirtualKey::Function6:
412         return kVK_F6;
413     case Inspector::Protocol::Automation::VirtualKey::Function7:
414         return kVK_F7;
415     case Inspector::Protocol::Automation::VirtualKey::Function8:
416         return kVK_F8;
417     case Inspector::Protocol::Automation::VirtualKey::Function9:
418         return kVK_F9;
419     case Inspector::Protocol::Automation::VirtualKey::Function10:
420         return kVK_F10;
421     case Inspector::Protocol::Automation::VirtualKey::Function11:
422         return kVK_F11;
423     case Inspector::Protocol::Automation::VirtualKey::Function12:
424         return kVK_F12;
425     }
426 }
427
428 static NSEventModifierFlags eventModifierFlagsForVirtualKey(Inspector::Protocol::Automation::VirtualKey key)
429 {
430     // Computes the modifiers changed by the virtual key when it is pressed or released.
431     // The mapping from keys to modifiers is specified in the documentation for NSEvent.
432     switch (key) {
433     case Inspector::Protocol::Automation::VirtualKey::Shift:
434         return NSEventModifierFlagShift;
435
436     case Inspector::Protocol::Automation::VirtualKey::Control:
437         return NSEventModifierFlagControl;
438
439     case Inspector::Protocol::Automation::VirtualKey::Alternate:
440         return NSEventModifierFlagOption;
441
442     case Inspector::Protocol::Automation::VirtualKey::Meta:
443         // The 'meta' key does not exist on Apple keyboards and is usually
444         // mapped to the Command key when using third-party keyboards.
445     case Inspector::Protocol::Automation::VirtualKey::Command:
446         return NSEventModifierFlagCommand;
447
448     case Inspector::Protocol::Automation::VirtualKey::Help:
449         return NSEventModifierFlagHelp | NSEventModifierFlagFunction;
450
451     case Inspector::Protocol::Automation::VirtualKey::PageUp:
452     case Inspector::Protocol::Automation::VirtualKey::PageDown:
453     case Inspector::Protocol::Automation::VirtualKey::End:
454     case Inspector::Protocol::Automation::VirtualKey::Home:
455         return NSEventModifierFlagFunction;
456
457     case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
458     case Inspector::Protocol::Automation::VirtualKey::UpArrow:
459     case Inspector::Protocol::Automation::VirtualKey::RightArrow:
460     case Inspector::Protocol::Automation::VirtualKey::DownArrow:
461         return NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
462
463     case Inspector::Protocol::Automation::VirtualKey::Delete:
464         return NSEventModifierFlagFunction;
465
466     case Inspector::Protocol::Automation::VirtualKey::Clear:
467     case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
468     case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
469     case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
470     case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
471     case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
472     case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
473     case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
474     case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
475     case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
476     case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
477     case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
478     case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
479     case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
480     case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
481     case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
482     case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
483         return NSEventModifierFlagNumericPad;
484
485     case Inspector::Protocol::Automation::VirtualKey::Function1:
486     case Inspector::Protocol::Automation::VirtualKey::Function2:
487     case Inspector::Protocol::Automation::VirtualKey::Function3:
488     case Inspector::Protocol::Automation::VirtualKey::Function4:
489     case Inspector::Protocol::Automation::VirtualKey::Function5:
490     case Inspector::Protocol::Automation::VirtualKey::Function6:
491     case Inspector::Protocol::Automation::VirtualKey::Function7:
492     case Inspector::Protocol::Automation::VirtualKey::Function8:
493     case Inspector::Protocol::Automation::VirtualKey::Function9:
494     case Inspector::Protocol::Automation::VirtualKey::Function10:
495     case Inspector::Protocol::Automation::VirtualKey::Function11:
496     case Inspector::Protocol::Automation::VirtualKey::Function12:
497         return NSEventModifierFlagFunction;
498
499     default:
500         return 0;
501     }
502 }
503
504 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy& page, Inspector::Protocol::Automation::KeyboardInteractionType interaction, Inspector::Protocol::Automation::VirtualKey key)
505 {
506     // FIXME: this function and the Automation protocol enum should probably adopt key names
507     // from W3C UIEvents standard. For more details: https://w3c.github.io/uievents-code/
508
509     bool isStickyModifier = keyHasStickyModifier(key);
510     NSEventModifierFlags changedModifiers = eventModifierFlagsForVirtualKey(key);
511     int keyCode = keyCodeForVirtualKey(key);
512
513     // FIXME: consider using AppKit SPI to normalize 'characters', i.e., changing * to Shift-8,
514     // and passing that in to charactersIgnoringModifiers. We could hardcode this for ASCII if needed.
515     std::optional<unichar> charCode = charCodeForVirtualKey(key);
516     std::optional<unichar> charCodeIgnoringModifiers = charCodeIgnoringModifiersForVirtualKey(key);
517     NSString *characters = charCode ? [NSString stringWithCharacters:&charCode.value() length:1] : nil;
518     NSString *unmodifiedCharacters = charCodeIgnoringModifiers ? [NSString stringWithCharacters:&charCodeIgnoringModifiers.value() length:1] : nil;
519
520     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
521
522     NSEventModifierFlags existingModifiers = [NSEvent modifierFlags];
523     NSEventModifierFlags updatedModifiers = 0;
524
525     // FIXME: this timestamp is not even close to matching native events. Find out how to get closer.
526     NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
527     NSWindow *window = page.platformWindow();
528     NSInteger windowNumber = window.windowNumber;
529     NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
530
531     switch (interaction) {
532     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress: {
533         NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
534         updatedModifiers = existingModifiers | changedModifiers;
535         [eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
536         break;
537     }
538     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease: {
539         NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyUp;
540         updatedModifiers = existingModifiers & ~changedModifiers;
541         [eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
542         break;
543     }
544     case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey: {
545         // Sticky modifiers should either be 'KeyPress' or 'KeyRelease'.
546         ASSERT(!isStickyModifier);
547         if (isStickyModifier)
548             return;
549
550         updatedModifiers = existingModifiers | changedModifiers;
551         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
552         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
553         break;
554     }
555     }
556
557     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
558 }
559
560 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
561 {
562     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
563
564     // Split the text into combining character sequences and send each separately.
565     // This has no similarity to how keyboards work when inputting complex text.
566     // This command is more similar to the 'insertText:' editing command, except
567     // that this emits keyup/keydown/keypress events for roughly each character.
568     // This API should move more towards that direction in the future.
569     NSString *text = keySequence;
570
571     NSEventModifierFlags modifiers = [NSEvent modifierFlags];
572     NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
573     NSWindow *window = page.platformWindow();
574     NSInteger windowNumber = window.windowNumber;
575     NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
576
577     [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
578         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
579         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
580     }];
581
582     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
583 }
584
585 } // namespace WebKit
586
587 #endif // PLATFORM(MAC)