| /* |
| * Copyright (C) 2017, 2019 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(IOS_FAMILY) |
| |
| #import "Logging.h" |
| #import "NativeWebKeyboardEvent.h" |
| #import "WebAutomationSessionMacros.h" |
| #import "WebPageProxy.h" |
| #import "_WKTouchEventGenerator.h" |
| #import <WebCore/KeyEventCodesIOS.h> |
| #import <WebCore/NotImplemented.h> |
| #import <WebCore/WebEvent.h> |
| #import <wtf/BlockPtr.h> |
| |
| namespace WebKit { |
| using namespace WebCore; |
| |
| void WebAutomationSession::sendSynthesizedEventsToPage(WebPageProxy& page, NSArray *eventsToSend) |
| { |
| // 'eventsToSend' contains WebCore::WebEvent instances. Use a wrapper type specific to the event type. |
| for (::WebEvent *event in eventsToSend) { |
| switch (event.type) { |
| case WebEventMouseDown: |
| case WebEventMouseUp: |
| case WebEventMouseMoved: |
| case WebEventScrollWheel: |
| case WebEventTouchBegin: |
| case WebEventTouchChange: |
| case WebEventTouchEnd: |
| case WebEventTouchCancel: |
| notImplemented(); |
| break; |
| |
| case WebEventKeyDown: |
| case WebEventKeyUp: |
| page.handleKeyboardEvent(NativeWebKeyboardEvent(event, NativeWebKeyboardEvent::HandledByInputMethod::No)); |
| break; |
| } |
| } |
| } |
| |
| #pragma mark Commands for Platform: 'iOS' |
| |
| #if ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS) |
| void WebAutomationSession::platformSimulateKeyboardInteraction(WebPageProxy& page, KeyboardInteraction interaction, WTF::Variant<VirtualKey, CharKey>&& key) |
| { |
| // The modifiers changed by the virtual key when it is pressed or released. |
| WebEventFlags changedModifiers = 0; |
| |
| // UIKit does not send key codes for virtual keys even for a hardware keyboard. |
| // Instead, it sends single unichars and WebCore maps these to "windows" key codes. |
| // Synthesize a single unichar such that the correct key code is inferred. |
| Optional<unichar> charCode; |
| Optional<unichar> charCodeIgnoringModifiers; |
| |
| // Figure out the effects of sticky modifiers. |
| WTF::switchOn(key, |
| [&] (VirtualKey virtualKey) { |
| charCode = charCodeForVirtualKey(virtualKey); |
| charCodeIgnoringModifiers = charCodeIgnoringModifiersForVirtualKey(virtualKey); |
| |
| switch (virtualKey) { |
| case VirtualKey::Shift: |
| changedModifiers |= WebEventFlagMaskShiftKey; |
| break; |
| case VirtualKey::Control: |
| changedModifiers |= WebEventFlagMaskControlKey; |
| break; |
| case VirtualKey::Alternate: |
| changedModifiers |= WebEventFlagMaskOptionKey; |
| break; |
| case 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 VirtualKey::Command: |
| changedModifiers |= WebEventFlagMaskCommandKey; |
| break; |
| default: |
| break; |
| } |
| }, |
| [&] (CharKey charKey) { |
| charCode = (unichar)charKey; |
| charCodeIgnoringModifiers = (unichar)charKey; |
| } |
| ); |
| |
| // FIXME: consider using UIKit SPI to normalize 'characters', i.e., changing * to Shift-8, |
| // and passing that in to charactersIgnoringModifiers. This is probably not worth the trouble |
| // unless it causes an actual behavioral difference. |
| NSString *characters = charCode ? [NSString stringWithCharacters:&charCode.value() length:1] : nil; |
| NSString *unmodifiedCharacters = charCodeIgnoringModifiers ? [NSString stringWithCharacters:&charCodeIgnoringModifiers.value() length:1] : nil; |
| BOOL isTabKey = charCode && charCode.value() == NSTabCharacter; |
| |
| // This is used as WebEvent.keyboardFlags, which are only used if we need to |
| // send this event back to UIKit to be interpreted by the keyboard / input manager. |
| // Just ignore this for now; we can fix it if there's an actual behavioral difference. |
| NSUInteger inputFlags = 0; |
| |
| // Provide an empty keyCode so that WebCore infers it from the charCode. |
| uint16_t keyCode = 0; |
| |
| auto eventsToBeSent = adoptNS([[NSMutableArray alloc] init]); |
| |
| switch (interaction) { |
| case KeyboardInteraction::KeyPress: { |
| m_currentModifiers |= changedModifiers; |
| |
| [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]]; |
| break; |
| } |
| case KeyboardInteraction::KeyRelease: { |
| m_currentModifiers &= ~changedModifiers; |
| |
| [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]]; |
| break; |
| } |
| case KeyboardInteraction::InsertByKey: { |
| // Modifiers only change with KeyPress or KeyRelease, this code path is for single characters. |
| [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]]; |
| [eventsToBeSent addObject:[[[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:characters charactersIgnoringModifiers:unmodifiedCharacters modifiers:m_currentModifiers isRepeating:NO withFlags:inputFlags withInputManagerHint:nil keyCode:keyCode isTabKey:isTabKey] autorelease]]; |
| 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; |
| BOOL isTabKey = [text isEqualToString:@"\t"]; |
| |
| [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { |
| auto keyDownEvent = adoptNS([[::WebEvent alloc] initWithKeyEventType:WebEventKeyDown timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]); |
| [eventsToBeSent addObject:keyDownEvent.get()]; |
| auto keyUpEvent = adoptNS([[::WebEvent alloc] initWithKeyEventType:WebEventKeyUp timeStamp:CFAbsoluteTimeGetCurrent() characters:substring charactersIgnoringModifiers:substring modifiers:m_currentModifiers isRepeating:NO withFlags:0 withInputManagerHint:nil keyCode:0 isTabKey:isTabKey]); |
| [eventsToBeSent addObject:keyUpEvent.get()]; |
| }]; |
| |
| sendSynthesizedEventsToPage(page, eventsToBeSent.get()); |
| } |
| #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS) |
| |
| #if ENABLE(WEBDRIVER_TOUCH_INTERACTIONS) |
| #if !LOG_DISABLED |
| static TextStream& operator<<(TextStream& ts, TouchInteraction interaction) |
| { |
| switch (interaction) { |
| case TouchInteraction::TouchDown: |
| ts << "TouchDown"; |
| break; |
| case TouchInteraction::MoveTo: |
| ts << "MoveTo"; |
| break; |
| case TouchInteraction::LiftUp: |
| ts << "LiftUp"; |
| break; |
| } |
| return ts; |
| } |
| #endif // !LOG_DISABLED |
| |
| void WebAutomationSession::platformSimulateTouchInteraction(WebPageProxy& page, TouchInteraction interaction, const WebCore::IntPoint& locationInViewport, Optional<Seconds> duration, AutomationCompletionHandler&& completionHandler) |
| { |
| WebCore::IntPoint locationOnScreen = page.syncRootViewToScreen(IntRect(locationInViewport, IntSize())).location(); |
| LOG_WITH_STREAM(AutomationInteractions, stream << "platformSimulateTouchInteraction: interaction=" << interaction << ", locationInViewport=" << locationInViewport << ", locationOnScreen=" << locationOnScreen << ", duration=" << duration.valueOr(0_s).seconds()); |
| |
| auto interactionFinished = makeBlockPtr([completionHandler = WTFMove(completionHandler)] () mutable { |
| completionHandler(WTF::nullopt); |
| }); |
| |
| _WKTouchEventGenerator *generator = [_WKTouchEventGenerator sharedTouchEventGenerator]; |
| switch (interaction) { |
| case TouchInteraction::TouchDown: |
| [generator touchDown:locationOnScreen completionBlock:interactionFinished.get()]; |
| break; |
| case TouchInteraction::LiftUp: |
| [generator liftUp:locationOnScreen completionBlock:interactionFinished.get()]; |
| break; |
| case TouchInteraction::MoveTo: |
| [generator moveToPoint:locationOnScreen duration:duration.valueOr(0_s).seconds() completionBlock:interactionFinished.get()]; |
| break; |
| } |
| } |
| #endif // ENABLE(WEBDRIVER_TOUCH_INTERACTIONS) |
| |
| } // namespace WebKit |
| |
| #endif // PLATFORM(IOS_FAMILY) |