7906b132ec89627349376cdf6a70f954480f682c
[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 makeKeyAndOrderFront:nil];
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;
218     NSEventType dragEventType;
219     NSEventType upEventType;
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         [eventsToBeSent addObject:[NSEvent mouseEventWithType:dragEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:0 pressure:0.0f]];
247         break;
248     case Inspector::Protocol::Automation::MouseInteraction::Down:
249         // Hard-code the click count to one, since clients don't expect successive simulated
250         // down/up events to be potentially counted as a double click event.
251         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
252         break;
253     case Inspector::Protocol::Automation::MouseInteraction::Up:
254         // Hard-code the click count to one, since clients don't expect successive simulated
255         // down/up events to be potentially counted as a double click event.
256         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
257         break;
258     case Inspector::Protocol::Automation::MouseInteraction::SingleClick:
259         // Send separate down and up events. WebCore will see this as a single-click event.
260         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
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::DoubleClick:
264         // Send multiple down and up events with proper click count.
265         // WebCore will see this as a single-click event then double-click event.
266         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
267         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
268         [eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:WebCore::ForceAtClick]];
269         [eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:0.0f]];
270     }
271
272     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
273 }
274
275 void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy& page, Inspector::Protocol::Automation::KeyboardInteractionType interaction, Inspector::Protocol::Automation::VirtualKey key)
276 {
277     // If true, the key's modifier flags should affect other events while pressed down.
278     bool isStickyModifier = false;
279     // The modifiers changed by the virtual key when it is pressed or released.
280     // The mapping from keys to modifiers is specified in the documentation for NSEvent.
281     NSEventModifierFlags changedModifiers = 0;
282     // The likely keyCode for the virtual key as defined in <HIToolbox/Events.h>.
283     int keyCode = 0;
284     // Typical characters produced by the virtual key, if any.
285     NSString *characters = @"";
286
287     // FIXME: this function and the Automation protocol enum should probably adopt key names
288     // from W3C UIEvents standard. For more details: https://w3c.github.io/uievents-code/
289
290     switch (key) {
291     case Inspector::Protocol::Automation::VirtualKey::Shift:
292         isStickyModifier = true;
293         changedModifiers |= NSEventModifierFlagShift;
294         keyCode = kVK_Shift;
295         break;
296     case Inspector::Protocol::Automation::VirtualKey::Control:
297         isStickyModifier = true;
298         changedModifiers |= NSEventModifierFlagControl;
299         keyCode = kVK_Control;
300         break;
301     case Inspector::Protocol::Automation::VirtualKey::Alternate:
302         isStickyModifier = true;
303         changedModifiers |= NSEventModifierFlagOption;
304         keyCode = kVK_Option;
305         break;
306     case Inspector::Protocol::Automation::VirtualKey::Meta:
307         // The 'meta' key does not exist on Apple keyboards and is usually
308         // mapped to the Command key when using third-party keyboards.
309     case Inspector::Protocol::Automation::VirtualKey::Command:
310         isStickyModifier = true;
311         changedModifiers |= NSEventModifierFlagCommand;
312         keyCode = kVK_Command;
313         break;
314     case Inspector::Protocol::Automation::VirtualKey::Help:
315         changedModifiers |= NSEventModifierFlagHelp | NSEventModifierFlagFunction;
316         keyCode = kVK_Help;
317         break;
318     case Inspector::Protocol::Automation::VirtualKey::Backspace:
319         keyCode = kVK_Delete;
320         break;
321     case Inspector::Protocol::Automation::VirtualKey::Tab:
322         keyCode = kVK_Tab;
323         break;
324     case Inspector::Protocol::Automation::VirtualKey::Clear:
325         changedModifiers |= NSEventModifierFlagNumericPad;
326         keyCode = kVK_ANSI_KeypadClear;
327         break;
328     case Inspector::Protocol::Automation::VirtualKey::Enter:
329         keyCode = kVK_ANSI_KeypadEnter;
330         break;
331     case Inspector::Protocol::Automation::VirtualKey::Pause:
332         // The 'pause' key does not exist on Apple keyboards and has no keycode.
333         // The semantics are unclear so just abort and do nothing.
334         return;
335     case Inspector::Protocol::Automation::VirtualKey::Cancel:
336         // The 'cancel' key does not exist on Apple keyboards and has no keycode.
337         // According to the internet its functionality is similar to 'Escape'.
338     case Inspector::Protocol::Automation::VirtualKey::Escape:
339         keyCode = kVK_Escape;
340         break;
341     case Inspector::Protocol::Automation::VirtualKey::PageUp:
342         changedModifiers |= NSEventModifierFlagFunction;
343         keyCode = kVK_PageUp;
344         break;
345     case Inspector::Protocol::Automation::VirtualKey::PageDown:
346         changedModifiers |= NSEventModifierFlagFunction;
347         keyCode = kVK_PageDown;
348         break;
349     case Inspector::Protocol::Automation::VirtualKey::End:
350         changedModifiers |= NSEventModifierFlagFunction;
351         keyCode = kVK_End;
352         break;
353     case Inspector::Protocol::Automation::VirtualKey::Home:
354         changedModifiers |= NSEventModifierFlagFunction;
355         keyCode = kVK_Home;
356         break;
357     case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
358         changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
359         keyCode = kVK_LeftArrow;
360         break;
361     case Inspector::Protocol::Automation::VirtualKey::UpArrow:
362         changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
363         keyCode = kVK_UpArrow;
364         break;
365     case Inspector::Protocol::Automation::VirtualKey::RightArrow:
366         changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
367         keyCode = kVK_RightArrow;
368         break;
369     case Inspector::Protocol::Automation::VirtualKey::DownArrow:
370         changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
371         keyCode = kVK_DownArrow;
372         break;
373     case Inspector::Protocol::Automation::VirtualKey::Insert:
374         // The 'insert' key does not exist on Apple keyboards and has no keycode.
375         // The semantics are unclear so just abort and do nothing.
376         return;
377     case Inspector::Protocol::Automation::VirtualKey::Delete:
378         changedModifiers |= NSEventModifierFlagFunction;
379         keyCode = kVK_ForwardDelete;
380         break;
381     case Inspector::Protocol::Automation::VirtualKey::Space:
382         keyCode = kVK_Space;
383         characters = @" ";
384         break;
385     case Inspector::Protocol::Automation::VirtualKey::Semicolon:
386         keyCode = kVK_ANSI_Semicolon;
387         characters = @";";
388         break;
389     case Inspector::Protocol::Automation::VirtualKey::Equals:
390         keyCode = kVK_ANSI_Equal;
391         characters = @"=";
392         break;
393     case Inspector::Protocol::Automation::VirtualKey::Return:
394         keyCode = kVK_Return;
395         break;
396     case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
397         changedModifiers |= NSEventModifierFlagNumericPad;
398         keyCode = kVK_ANSI_Keypad0;
399         characters = @"0";
400         break;
401     case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
402         changedModifiers |= NSEventModifierFlagNumericPad;
403         keyCode = kVK_ANSI_Keypad1;
404         characters = @"1";
405         break;
406     case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
407         changedModifiers |= NSEventModifierFlagNumericPad;
408         keyCode = kVK_ANSI_Keypad2;
409         characters = @"2";
410         break;
411     case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
412         changedModifiers |= NSEventModifierFlagNumericPad;
413         keyCode = kVK_ANSI_Keypad3;
414         characters = @"3";
415         break;
416     case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
417         changedModifiers |= NSEventModifierFlagNumericPad;
418         keyCode = kVK_ANSI_Keypad4;
419         characters = @"4";
420         break;
421     case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
422         changedModifiers |= NSEventModifierFlagNumericPad;
423         keyCode = kVK_ANSI_Keypad5;
424         characters = @"5";
425         break;
426     case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
427         changedModifiers |= NSEventModifierFlagNumericPad;
428         keyCode = kVK_ANSI_Keypad6;
429         characters = @"6";
430         break;
431     case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
432         changedModifiers |= NSEventModifierFlagNumericPad;
433         keyCode = kVK_ANSI_Keypad7;
434         characters = @"7";
435         break;
436     case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
437         changedModifiers |= NSEventModifierFlagNumericPad;
438         keyCode = kVK_ANSI_Keypad8;
439         characters = @"8";
440         break;
441     case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
442         changedModifiers |= NSEventModifierFlagNumericPad;
443         keyCode = kVK_ANSI_Keypad9;
444         characters = @"9";
445         break;
446     case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
447         changedModifiers |= NSEventModifierFlagNumericPad;
448         keyCode = kVK_ANSI_KeypadMultiply;
449         characters = @"*";
450         break;
451     case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
452         changedModifiers |= NSEventModifierFlagNumericPad;
453         keyCode = kVK_ANSI_KeypadPlus;
454         characters = @"+";
455         break;
456     case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
457         changedModifiers |= NSEventModifierFlagNumericPad;
458         keyCode = kVK_ANSI_KeypadMinus;
459         characters = @"-";
460         break;
461     case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
462         changedModifiers |= NSEventModifierFlagNumericPad;
463         // The 'Separator' key is only present on a few international keyboards.
464         // It is usually mapped to the same character as Decimal ('.' or ',').
465         FALLTHROUGH;
466     case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
467         changedModifiers |= NSEventModifierFlagNumericPad;
468         keyCode = kVK_ANSI_KeypadDecimal;
469         // FIXME: this might be locale-dependent. See the above comment.
470         characters = @".";
471         break;
472     case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
473         changedModifiers |= NSEventModifierFlagNumericPad;
474         keyCode = kVK_ANSI_KeypadDivide;
475         characters = @"/";
476         break;
477     case Inspector::Protocol::Automation::VirtualKey::Function1:
478         changedModifiers |= NSEventModifierFlagFunction;
479         keyCode = kVK_F1;
480         break;
481     case Inspector::Protocol::Automation::VirtualKey::Function2:
482         changedModifiers |= NSEventModifierFlagFunction;
483         keyCode = kVK_F2;
484         break;
485     case Inspector::Protocol::Automation::VirtualKey::Function3:
486         changedModifiers |= NSEventModifierFlagFunction;
487         keyCode = kVK_F3;
488         break;
489     case Inspector::Protocol::Automation::VirtualKey::Function4:
490         changedModifiers |= NSEventModifierFlagFunction;
491         keyCode = kVK_F4;
492         break;
493     case Inspector::Protocol::Automation::VirtualKey::Function5:
494         changedModifiers |= NSEventModifierFlagFunction;
495         keyCode = kVK_F5;
496         break;
497     case Inspector::Protocol::Automation::VirtualKey::Function6:
498         changedModifiers |= NSEventModifierFlagFunction;
499         keyCode = kVK_F6;
500         break;
501     case Inspector::Protocol::Automation::VirtualKey::Function7:
502         changedModifiers |= NSEventModifierFlagFunction;
503         keyCode = kVK_F7;
504         break;
505     case Inspector::Protocol::Automation::VirtualKey::Function8:
506         changedModifiers |= NSEventModifierFlagFunction;
507         keyCode = kVK_F8;
508         break;
509     case Inspector::Protocol::Automation::VirtualKey::Function9:
510         changedModifiers |= NSEventModifierFlagFunction;
511         keyCode = kVK_F9;
512         break;
513     case Inspector::Protocol::Automation::VirtualKey::Function10:
514         changedModifiers |= NSEventModifierFlagFunction;
515         keyCode = kVK_F10;
516         break;
517     case Inspector::Protocol::Automation::VirtualKey::Function11:
518         changedModifiers |= NSEventModifierFlagFunction;
519         keyCode = kVK_F11;
520         break;
521     case Inspector::Protocol::Automation::VirtualKey::Function12:
522         changedModifiers |= NSEventModifierFlagFunction;
523         keyCode = kVK_F12;
524         break;
525     }
526
527     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
528
529     ASSERT(isStickyModifier || interaction == Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress);
530
531     NSEventModifierFlags existingModifiers = [NSEvent modifierFlags];
532     NSEventModifierFlags updatedModifiers = 0;
533     NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
534     NSWindow *window = page.platformWindow();
535     NSInteger windowNumber = window.windowNumber;
536     NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
537
538     switch (interaction) {
539     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress: {
540         NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
541         updatedModifiers = existingModifiers | changedModifiers;
542         [eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]];
543         break;
544     }
545     case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease: {
546         NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyUp;
547         updatedModifiers = existingModifiers & ~changedModifiers;
548         [eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]];
549         break;
550     }
551     case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey: {
552         // Sticky modifiers should either be 'KeyPress' or 'KeyRelease'.
553         ASSERT(!isStickyModifier);
554         if (isStickyModifier)
555             return;
556
557         updatedModifiers = existingModifiers | changedModifiers;
558         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]];
559         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]];
560         break;
561     }
562     }
563
564     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
565 }
566
567 void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
568 {
569     auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
570
571     // Split the text into combining character sequences and send each separately.
572     // This has no similarity to how keyboards work when inputting complex text.
573     // This command is more similar to the 'insertText:' editing command, except
574     // that this emits keyup/keydown/keypress events for roughly each character.
575     // This API should move more towards that direction in the future.
576     NSString *text = keySequence;
577
578     NSEventModifierFlags modifiers = [NSEvent modifierFlags];
579     NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
580     NSWindow *window = page.platformWindow();
581     NSInteger windowNumber = window.windowNumber;
582     NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
583
584     [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
585         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
586         [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
587     }];
588
589     sendSynthesizedEventsToPage(page, eventsToBeSent.get());
590 }
591
592 } // namespace WebKit
593
594 #endif // PLATFORM(MAC)