WebAutomationSessionCocoa.mm   [plain text]


/*
 * Copyright (C) 2016 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"

#import "WebPageProxy.h"
#import "_WKAutomationSession.h"
#import <WebCore/IntPoint.h>
#import <WebCore/IntSize.h>
#import <WebCore/PlatformMouseEvent.h>
#import <objc/runtime.h>

#if USE(APPKIT)
#import <HIToolbox/Events.h>
#endif

using namespace WebCore;

namespace WebKit {

#if USE(APPKIT)

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 makeKeyAndOrderFront:nil];

        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;
}

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;

    NSTimeInterval timestamp = [NSDate timeIntervalSinceReferenceDate];
    NSWindow *window = page.platformWindow();
    NSInteger windowNumber = window.windowNumber;

    NSEventType downEventType;
    NSEventType dragEventType;
    NSEventType upEventType;
    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:
        [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:
        // 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:
        // 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:
        // 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:
        // 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());
}

void WebAutomationSession::platformSimulateKeyStroke(WebPageProxy& page, Inspector::Protocol::Automation::KeyboardInteractionType interaction, Inspector::Protocol::Automation::VirtualKey key)
{
    // If true, the key's modifier flags should affect other events while pressed down.
    bool isStickyModifier = false;
    // 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.
    NSEventModifierFlags changedModifiers = 0;
    // The likely keyCode for the virtual key as defined in <HIToolbox/Events.h>.
    int keyCode = 0;
    // Typical characters produced by the virtual key, if any.
    NSString *characters = @"";

    // 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/

    switch (key) {
    case Inspector::Protocol::Automation::VirtualKey::Shift:
        isStickyModifier = true;
        changedModifiers |= NSEventModifierFlagShift;
        keyCode = kVK_Shift;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Control:
        isStickyModifier = true;
        changedModifiers |= NSEventModifierFlagControl;
        keyCode = kVK_Control;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Alternate:
        isStickyModifier = true;
        changedModifiers |= NSEventModifierFlagOption;
        keyCode = kVK_Option;
        break;
    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:
        isStickyModifier = true;
        changedModifiers |= NSEventModifierFlagCommand;
        keyCode = kVK_Command;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Help:
        changedModifiers |= NSEventModifierFlagHelp | NSEventModifierFlagFunction;
        keyCode = kVK_Help;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Backspace:
        keyCode = kVK_Delete;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Tab:
        keyCode = kVK_Tab;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Clear:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadClear;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Enter:
        keyCode = kVK_ANSI_KeypadEnter;
        break;
    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;
    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:
        keyCode = kVK_Escape;
        break;
    case Inspector::Protocol::Automation::VirtualKey::PageUp:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_PageUp;
        break;
    case Inspector::Protocol::Automation::VirtualKey::PageDown:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_PageDown;
        break;
    case Inspector::Protocol::Automation::VirtualKey::End:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_End;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Home:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_Home;
        break;
    case Inspector::Protocol::Automation::VirtualKey::LeftArrow:
        changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
        keyCode = kVK_LeftArrow;
        break;
    case Inspector::Protocol::Automation::VirtualKey::UpArrow:
        changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
        keyCode = kVK_UpArrow;
        break;
    case Inspector::Protocol::Automation::VirtualKey::RightArrow:
        changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
        keyCode = kVK_RightArrow;
        break;
    case Inspector::Protocol::Automation::VirtualKey::DownArrow:
        changedModifiers |= NSEventModifierFlagNumericPad | NSEventModifierFlagFunction;
        keyCode = kVK_DownArrow;
        break;
    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;
    case Inspector::Protocol::Automation::VirtualKey::Delete:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_ForwardDelete;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Space:
        keyCode = kVK_Space;
        characters = @" ";
        break;
    case Inspector::Protocol::Automation::VirtualKey::Semicolon:
        keyCode = kVK_ANSI_Semicolon;
        characters = @";";
        break;
    case Inspector::Protocol::Automation::VirtualKey::Equals:
        keyCode = kVK_ANSI_Equal;
        characters = @"=";
        break;
    case Inspector::Protocol::Automation::VirtualKey::Return:
        keyCode = kVK_Return;
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad0:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad0;
        characters = @"0";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad1:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad1;
        characters = @"1";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad2:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad2;
        characters = @"2";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad3:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad3;
        characters = @"3";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad4:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad4;
        characters = @"4";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad5:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad5;
        characters = @"5";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad6:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad6;
        characters = @"6";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad7:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad7;
        characters = @"7";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad8:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad8;
        characters = @"8";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPad9:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_Keypad9;
        characters = @"9";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPadMultiply:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadMultiply;
        characters = @"*";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPadAdd:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadPlus;
        characters = @"+";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPadSubtract:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadMinus;
        characters = @"-";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPadSeparator:
        changedModifiers |= NSEventModifierFlagNumericPad;
        // 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:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadDecimal;
        // FIXME: this might be locale-dependent. See the above comment.
        characters = @".";
        break;
    case Inspector::Protocol::Automation::VirtualKey::NumberPadDivide:
        changedModifiers |= NSEventModifierFlagNumericPad;
        keyCode = kVK_ANSI_KeypadDivide;
        characters = @"/";
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function1:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F1;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function2:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F2;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function3:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F3;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function4:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F4;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function5:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F5;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function6:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F6;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function7:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F7;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function8:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F8;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function9:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F9;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function10:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F10;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function11:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F11;
        break;
    case Inspector::Protocol::Automation::VirtualKey::Function12:
        changedModifiers |= NSEventModifierFlagFunction;
        keyCode = kVK_F12;
        break;
    }

    auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]);

    ASSERT(isStickyModifier || interaction == Inspector::Protocol::Automation::KeyboardInteractionType::KeyPress);

    NSEventModifierFlags existingModifiers = [NSEvent modifierFlags];
    NSEventModifierFlags updatedModifiers = 0;
    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:characters 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:characters 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:characters isARepeat:NO keyCode:keyCode]];
        [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:updatedModifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]];
        break;
    }
    }

    sendSynthesizedEventsToPage(page, eventsToBeSent.get());
}

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) {
        [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyDown location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
        [eventsToBeSent addObject:[NSEvent keyEventWithType:NSEventTypeKeyUp location:eventPosition modifierFlags:modifiers timestamp:timestamp windowNumber:windowNumber context:nil characters:substring charactersIgnoringModifiers:substring isARepeat:NO keyCode:0]];
    }];

    sendSynthesizedEventsToPage(page, eventsToBeSent.get());
}

String WebAutomationSession::platformGetBase64EncodedPNGData(const ShareableBitmap::Handle& imageDataHandle)
{
    RefPtr<ShareableBitmap> bitmap = ShareableBitmap::create(imageDataHandle, SharedMemory::Protection::ReadOnly);
    RetainPtr<CGImageRef> cgImage = bitmap->makeCGImage();
    RetainPtr<NSMutableData> imageData = adoptNS([[NSMutableData alloc] init]);
    RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData((CFMutableDataRef)imageData.get(), kUTTypePNG, 1, 0));
    if (!destination)
        return String();

    CGImageDestinationAddImage(destination.get(), cgImage.get(), 0);
    CGImageDestinationFinalize(destination.get());

    return [imageData base64EncodedStringWithOptions:0];
}

#endif // USE(APPKIT)

} // namespace WebKit