WebAutomationSessionMac.mm [plain text]
/*
* Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "WebAutomationSession.h"
#if PLATFORM(MAC)
#import "WebAutomationSessionMacros.h"
#import "WebInspectorProxy.h"
#import "WebPageProxy.h"
#import "_WKAutomationSession.h"
#import <HIToolbox/Events.h>
#import <WebCore/IntPoint.h>
#import <WebCore/IntSize.h>
#import <WebCore/PlatformMouseEvent.h>
#import <objc/runtime.h>
using namespace WebCore;
namespace WebKit {
#pragma mark Commands for Platform: 'macOS'
void WebAutomationSession::inspectBrowsingContext(Inspector::ErrorString& errorString, const String& handle, const bool* optionalEnableAutoCapturing, Ref<InspectBrowsingContextCallback>&& callback)
{
WebPageProxy* page = webPageProxyForHandle(handle);
if (!page)
FAIL_WITH_PREDEFINED_ERROR(WindowNotFound);
if (auto callback = m_pendingInspectorCallbacksPerPage.take(page->pageID()))
callback->sendFailure(STRING_FOR_PREDEFINED_ERROR_NAME(Timeout));
m_pendingInspectorCallbacksPerPage.set(page->pageID(), WTFMove(callback));
// Don't bring the inspector to front since this may be done automatically.
// We just want it loaded so it can pause if a breakpoint is hit during a command.
if (page->inspector()) {
page->inspector()->connect();
// Start collecting profile information immediately so the entire session is captured.
if (optionalEnableAutoCapturing && *optionalEnableAutoCapturing)
page->inspector()->togglePageProfiling();
}
}
#pragma mark AppKit Event Simulation Support
static const NSInteger synthesizedMouseEventMagicEventNumber = 0;
static const void *synthesizedAutomationEventAssociatedObjectKey = &synthesizedAutomationEventAssociatedObjectKey;
void WebAutomationSession::sendSynthesizedEventsToPage(WebPageProxy& page, NSArray *eventsToSend)
{
NSWindow *window = page.platformWindow();
for (NSEvent *event in eventsToSend) {
// Take focus back in case the Inspector became focused while the prior command or
// NSEvent was delivered to the window.
[window becomeKeyWindow];
markEventAsSynthesizedForAutomation(event);
[window sendEvent:event];
}
}
void WebAutomationSession::markEventAsSynthesizedForAutomation(NSEvent *event)
{
objc_setAssociatedObject(event, &synthesizedAutomationEventAssociatedObjectKey, m_sessionIdentifier, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
bool WebAutomationSession::wasEventSynthesizedForAutomation(NSEvent *event)
{
NSString *senderSessionIdentifier = objc_getAssociatedObject(event, &synthesizedAutomationEventAssociatedObjectKey);
if ([senderSessionIdentifier isEqualToString:m_sessionIdentifier])
return true;
switch (event.type) {
case NSEventTypeLeftMouseDown:
case NSEventTypeLeftMouseDragged:
case NSEventTypeLeftMouseUp:
case NSEventTypeMouseMoved:
case NSEventTypeOtherMouseDown:
case NSEventTypeOtherMouseDragged:
case NSEventTypeOtherMouseUp:
case NSEventTypeRightMouseDown:
case NSEventTypeRightMouseDragged:
case NSEventTypeRightMouseUp:
// Use this as a backup for checking mouse events, which are frequently copied
// and/or faked by AppKit, causing them to lose their associated object tag.
return event.eventNumber == synthesizedMouseEventMagicEventNumber;
default:
break;
}
return false;
}
#pragma mark Platform-dependent Implementations
void WebAutomationSession::platformSimulateMouseInteraction(WebPageProxy& page, const WebCore::IntPoint& viewPosition, Inspector::Protocol::Automation::MouseInteraction interaction, Inspector::Protocol::Automation::MouseButton button, WebEvent::Modifiers keyModifiers)
{
IntRect windowRect;
page.rootViewToWindow(IntRect(viewPosition, IntSize()), windowRect);
IntPoint windowPosition = windowRect.location();
NSEventModifierFlags modifiers = 0;
if (keyModifiers & WebEvent::MetaKey)
modifiers |= NSEventModifierFlagCommand;
if (keyModifiers & WebEvent::AltKey)
modifiers |= NSEventModifierFlagOption;
if (keyModifiers & WebEvent::ControlKey)
modifiers |= NSEventModifierFlagControl;
if (keyModifiers & WebEvent::ShiftKey)
modifiers |= NSEventModifierFlagShift;
if (keyModifiers & WebEvent::CapsLockKey)
modifiers |= NSEventModifierFlagCapsLock;
NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
NSWindow *window = page.platformWindow();
NSInteger windowNumber = window.windowNumber;
NSEventType downEventType = (NSEventType)0;
NSEventType dragEventType = (NSEventType)0;
NSEventType upEventType = (NSEventType)0;
switch (button) {
case Inspector::Protocol::Automation::MouseButton::None:
downEventType = upEventType = dragEventType = NSEventTypeMouseMoved;
break;
case Inspector::Protocol::Automation::MouseButton::Left:
downEventType = NSEventTypeLeftMouseDown;
dragEventType = NSEventTypeLeftMouseDragged;
upEventType = NSEventTypeLeftMouseUp;
break;
case Inspector::Protocol::Automation::MouseButton::Middle:
downEventType = NSEventTypeOtherMouseDown;
dragEventType = NSEventTypeLeftMouseDragged;
upEventType = NSEventTypeOtherMouseUp;
break;
case Inspector::Protocol::Automation::MouseButton::Right:
downEventType = NSEventTypeRightMouseDown;
upEventType = NSEventTypeRightMouseUp;
break;
}
auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
NSInteger eventNumber = synthesizedMouseEventMagicEventNumber;
switch (interaction) {
case Inspector::Protocol::Automation::MouseInteraction::Move:
ASSERT(dragEventType);
[eventsToBeSent addObject:[NSEvent mouseEventWithType:dragEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:0 pressure:0.0f]];
break;
case Inspector::Protocol::Automation::MouseInteraction::Down:
ASSERT(downEventType);
// Hard-code the click count to one, since clients don't expect successive simulated
// down/up events to be potentially counted as a double click event.
[eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
break;
case Inspector::Protocol::Automation::MouseInteraction::Up:
ASSERT(upEventType);
// Hard-code the click count to one, since clients don't expect successive simulated
// down/up events to be potentially counted as a double click event.
[eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
break;
case Inspector::Protocol::Automation::MouseInteraction::SingleClick:
ASSERT(upEventType);
ASSERT(downEventType);
// Send separate down and up events. WebCore will see this as a single-click event.
[eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
[eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
break;
case Inspector::Protocol::Automation::MouseInteraction::DoubleClick:
ASSERT(upEventType);
ASSERT(downEventType);
// Send multiple down and up events with proper click count.
// WebCore will see this as a single-click event then double-click event.
[eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:WebCore::ForceAtClick]];
[eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:1 pressure:0.0f]];
[eventsToBeSent addObject:[NSEvent mouseEventWithType:downEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:WebCore::ForceAtClick]];
[eventsToBeSent addObject:[NSEvent mouseEventWithType:upEventType location:windowPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:eventNumber clickCount:2 pressure:0.0f]];
}
sendSynthesizedEventsToPage(page, eventsToBeSent.get());
}
static bool keyHasStickyModifier(Inspector::Protocol::Automation::VirtualKey key)
{
// Returns whether the key's modifier flags should affect other events while pressed down.
switch (key) {
case Inspector::Protocol::Automation::VirtualKey::Shift:
case Inspector::Protocol::Automation::VirtualKey::Control:
case Inspector::Protocol::Automation::VirtualKey::Alternate:
case Inspector::Protocol::Automation::VirtualKey::Meta:
case Inspector::Protocol::Automation::VirtualKey::Command:
return true;
default:
return false;
}
}
static int keyCodeForVirtualKey(Inspector::Protocol::Automation::VirtualKey key)
{
// The likely keyCode for the virtual key as defined in <HIToolbox/Events.h>.
switch (key) {
case Inspector::Protocol::Automation::VirtualKey::Shift:
return kVK_Shift;
case Inspector::Protocol::Automation::VirtualKey::Control:
return kVK_Control;
case Inspector::Protocol::Automation::VirtualKey::Alternate:
return kVK_Option;
case Inspector::Protocol::Automation::VirtualKey::Meta:
// The 'meta' key does not exist on Apple keyboards and is usually
// mapped to the Command key when using third-party keyboards.
case Inspector::Protocol::Automation::VirtualKey::Command:
return kVK_Command;
case Inspector::Protocol::Automation::VirtualKey::Help:
return kVK_Help;
case Inspector::Protocol::Automation::VirtualKey::Backspace:
return kVK_Delete;
case Inspector::Protocol::Automation::VirtualKey::Tab:
return kVK_Tab;
case Inspector::Protocol::Automation::VirtualKey::Clear:
return kVK_ANSI_KeypadClear;
case Inspector::Protocol::Automation::VirtualKey::Enter:
return kVK_ANSI_KeypadEnter;
case Inspector::Protocol::Automation::VirtualKey::Pause:
// The 'pause' key does not exist on Apple keyboards and has no keyCode.
// The semantics are unclear so just abort and do nothing.
return 0;
case Inspector::Protocol::Automation::VirtualKey::Cancel:
// The 'cancel' key does not exist on Apple keyboards and has no keyCode.
// According to the internet its functionality is similar to 'Escape'.
case Inspector::Protocol::Automation::VirtualKey::Escape:
return kVK_Escape;
case Inspector::Protocol::Automation::VirtualKey::PageUp:
return kVK_PageUp;
case Inspector::Protocol::Automation::VirtualKey::PageDown:
return kVK_PageDown;
case Inspector::Protocol::Automation::VirtualKey::End:
return kVK_End;
case Inspector::Protocol::Automation::VirtualKey::Home:
return kVK_Home;
case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
return kVK_LeftArrow;
case Inspector::Protocol::Automation::VirtualKey::UpArrow:
return kVK_UpArrow;
case Inspector::Protocol::Automation::VirtualKey::RightArrow:
return kVK_RightArrow;
case Inspector::Protocol::Automation::VirtualKey::DownArrow:
return kVK_DownArrow;
case Inspector::Protocol::Automation::VirtualKey::Insert:
// The 'insert' key does not exist on Apple keyboards and has no keyCode.
// The semantics are unclear so just abort and do nothing.
return 0;
case Inspector::Protocol::Automation::VirtualKey::Delete:
return kVK_ForwardDelete;
case Inspector::Protocol::Automation::VirtualKey::Space:
return kVK_Space;
case Inspector::Protocol::Automation::VirtualKey::Semicolon:
return kVK_ANSI_Semicolon;
case Inspector::Protocol::Automation::VirtualKey::Equals:
return kVK_ANSI_Equal;
case Inspector::Protocol::Automation::VirtualKey::Return:
return kVK_Return;
case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
return kVK_ANSI_Keypad0;
case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
return kVK_ANSI_Keypad1;
case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
return kVK_ANSI_Keypad2;
case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
return kVK_ANSI_Keypad3;
case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
return kVK_ANSI_Keypad4;
case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
return kVK_ANSI_Keypad5;
case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
return kVK_ANSI_Keypad6;
case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
return kVK_ANSI_Keypad7;
case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
return kVK_ANSI_Keypad8;
case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
return kVK_ANSI_Keypad9;
case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
return kVK_ANSI_KeypadMultiply;
case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
return kVK_ANSI_KeypadPlus;
case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
return kVK_ANSI_KeypadMinus;
case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
// The 'Separator' key is only present on a few international keyboards.
// It is usually mapped to the same character as Decimal ('.' or ',').
FALLTHROUGH;
case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
return kVK_ANSI_KeypadDecimal;
// FIXME: this might be locale-dependent. See the above comment.
case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
return kVK_ANSI_KeypadDivide;
case Inspector::Protocol::Automation::VirtualKey::Function1:
return kVK_F1;
case Inspector::Protocol::Automation::VirtualKey::Function2:
return kVK_F2;
case Inspector::Protocol::Automation::VirtualKey::Function3:
return kVK_F3;
case Inspector::Protocol::Automation::VirtualKey::Function4:
return kVK_F4;
case Inspector::Protocol::Automation::VirtualKey::Function5:
return kVK_F5;
case Inspector::Protocol::Automation::VirtualKey::Function6:
return kVK_F6;
case Inspector::Protocol::Automation::VirtualKey::Function7:
return kVK_F7;
case Inspector::Protocol::Automation::VirtualKey::Function8:
return kVK_F8;
case Inspector::Protocol::Automation::VirtualKey::Function9:
return kVK_F9;
case Inspector::Protocol::Automation::VirtualKey::Function10:
return kVK_F10;
case Inspector::Protocol::Automation::VirtualKey::Function11:
return kVK_F11;
case Inspector::Protocol::Automation::VirtualKey::Function12:
return kVK_F12;
}
}
static NSEventModifierFlags eventModifierFlagsForVirtualKey(Inspector::Protocol::Automation::VirtualKey key)
{
// Computes the modifiers changed by the virtual key when it is pressed or released.
// The mapping from keys to modifiers is specified in the documentation for NSEvent.
switch (key) {
case Inspector::Protocol::Automation::VirtualKey::Shift:
return NSEventModifierFlagShift;
case Inspector::Protocol::Automation::VirtualKey::Control:
return NSEventModifierFlagControl;
case Inspector::Protocol::Automation::VirtualKey::Alternate:
return NSEventModifierFlagOption;
case Inspector::Protocol::Automation::VirtualKey::Meta:
// The 'meta' key does not exist on Apple keyboards and is usually
// mapped to the Command key when using third-party keyboards.
case Inspector::Protocol::Automation::VirtualKey::Command:
return NSEventModifierFlagCommand;
case Inspector::Protocol::Automation::VirtualKey::Help:
return NSEventModifierFlagHelp | NSEventModifierFlagFunction;
case Inspector::Protocol::Automation::VirtualKey::PageUp:
case Inspector::Protocol::Automation::VirtualKey::PageDown:
case Inspector::Protocol::Automation::VirtualKey::End:
case Inspector::Protocol::Automation::VirtualKey::Home:
return NSEventModifierFlagFunction;
case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
case Inspector::Protocol::Automation::VirtualKey::UpArrow:
case Inspector::Protocol::Automation::VirtualKey::RightArrow:
case Inspector::Protocol::Automation::VirtualKey::DownArrow:
return NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
case Inspector::Protocol::Automation::VirtualKey::Delete:
return NSEventModifierFlagFunction;
case Inspector::Protocol::Automation::VirtualKey::Clear:
case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
case Inspector::Protocol::Automation::VirtualKey::NumberPadDecimal:
case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
return NSEventModifierFlagNumericPad;
case Inspector::Protocol::Automation::VirtualKey::Function1:
case Inspector::Protocol::Automation::VirtualKey::Function2:
case Inspector::Protocol::Automation::VirtualKey::Function3:
case Inspector::Protocol::Automation::VirtualKey::Function4:
case Inspector::Protocol::Automation::VirtualKey::Function5:
case Inspector::Protocol::Automation::VirtualKey::Function6:
case Inspector::Protocol::Automation::VirtualKey::Function7:
case Inspector::Protocol::Automation::VirtualKey::Function8:
case Inspector::Protocol::Automation::VirtualKey::Function9:
case Inspector::Protocol::Automation::VirtualKey::Function10:
case Inspector::Protocol::Automation::VirtualKey::Function11:
case Inspector::Protocol::Automation::VirtualKey::Function12:
return NSEventModifierFlagFunction;
default:
return 0;
}
}
void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy& page, Inspector::Protocol::Automation::KeyboardInteractionType interaction, Inspector::Protocol::Automation::VirtualKey key)
{
// FIXME: this function and the Automation protocol enum should probably adopt key names
// from W3C UIEvents standard. For more details: https://w3c.github.io/uievents-code/
bool isStickyModifier = keyHasStickyModifier(key);
NSEventModifierFlags changedModifiers = eventModifierFlagsForVirtualKey(key);
int keyCode = keyCodeForVirtualKey(key);
// FIXME: consider using AppKit SPI to normalize 'characters', i.e., changing * to Shift-8,
// and passing that in to charactersIgnoringModifiers. We could hardcode this for ASCII if needed.
std::optional<unichar> charCode = charCodeForVirtualKey(key);
std::optional<unichar> charCodeIgnoringModifiers = charCodeIgnoringModifiersForVirtualKey(key);
NSString *characters = charCode ? [NSString stringWithCharacters:&charCode.value() length:1] : nil;
NSString *unmodifiedCharacters = charCodeIgnoringModifiers ? [NSString stringWithCharacters:&charCodeIgnoringModifiers.value() length:1] : nil;
auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
NSEventModifierFlags existingModifiers = [NSEvent modifierFlags];
NSEventModifierFlags updatedModifiers = 0;
// FIXME: this timestamp is not even close to matching native events. Find out how to get closer.
NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
NSWindow *window = page.platformWindow();
NSInteger windowNumber = window.windowNumber;
NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
switch (interaction) {
case Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress: {
NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyDown;
updatedModifiers = existingModifiers | changedModifiers;
[eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
break;
}
case Inspector::Protocol::Automation::KeyboardInteractionType::KeyRelease: {
NSEventType eventType = isStickyModifier ? NSEventTypeFlagsChanged : NSEventTypeKeyUp;
updatedModifiers = existingModifiers & ~changedModifiers;
[eventsToBeSent addObject:[NSEvent keyEventWithType:eventType location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
break;
}
case Inspector::Protocol::Automation::KeyboardInteractionType::InsertByKey: {
// Sticky modifiers should either be 'KeyPress' or 'KeyRelease'.
ASSERT(!isStickyModifier);
if (isStickyModifier)
return;
updatedModifiers = existingModifiers | changedModifiers;
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:unmodifiedCharacters isARepeat:NO keyCode:keyCode]];
break;
}
}
sendSynthesizedEventsToPage(page, eventsToBeSent.get());
}
static BOOL characterIsProducedUsingShift(NSString *characters)
{
ASSERT(characters.length == 1);
// Per the WebDriver specification, keystrokes are assumed to be produced a standard 108-key US keyboard layout.
// If this is no longer the case, then we'll need to use system keyboard SPI to decompose modifiers from keystrokes.
unichar c = [characters characterAtIndex:0];
if (c > 128)
return NO;
if (c >= 'A' && c <= 'Z')
return YES;
switch (c) {
case '~':
case '!':
case '@':
case '#':
case '$':
case '%':
case '^':
case '&':
case '*':
case '(':
case ')':
case '_':
case '+':
case '{':
case '}':
case '|':
case ':':
case '"':
case '<':
case '>':
case '?':
return YES;
default:
return NO;
}
}
void WebAutomationSession::platformSimulateKeySequence(WebPageProxy& page, const String& keySequence)
{
auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);
// Split the text into combining character sequences and send each separately.
// This has no similarity to how keyboards work when inputting complex text.
// This command is more similar to the 'insertText:' editing command, except
// that this emits keyup/keydown/keypress events for roughly each character.
// This API should move more towards that direction in the future.
NSString *text = keySequence;
NSEventModifierFlags modifiers = [NSEvent modifierFlags];
NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
NSWindow *window = page.platformWindow();
NSInteger windowNumber = window.windowNumber;
NSPoint eventPosition = NSMakePoint(0, window.frame.size.height);
[text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
// For ASCII characters that are produced using Shift on a US-108 key keyboard layout,
// WebDriver expects these to be delivered as [shift-down, key-down, key-up, shift-up]
// keystrokes unless the shift key is already pressed down from an earlier interaction.
BOOL shiftAlreadyPressed = modifiers & NSEventModifierFlagShift;
BOOL shouldPressShift = !shiftAlreadyPressed && substringRange.length == 1 && characterIsProducedUsingShift(substring);
NSEventModifierFlags modifiersForKeystroke = shouldPressShift ? modifiers | NSEventModifierFlagShift : modifiers;
if (shouldPressShift)
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeFlagsChanged location:eventPosition modifierFlags:modifiersForKeystroke timestamp:timestamp windowNumber:windowNumber context:nil characters:@"" charactersIgnoringModifiers:@"" isARepeat:NO keyCode:kVK_Shift]];
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:modifiersForKeystroke timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:modifiersForKeystroke timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
if (shouldPressShift)
[eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeFlagsChanged location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:@"" charactersIgnoringModifiers:@"" isARepeat:NO keyCode:kVK_Shift]];
}];
sendSynthesizedEventsToPage(page, eventsToBeSent.get());
}
} // namespace WebKit
#endif // PLATFORM(MAC)