/* * Copyright (C) 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 "WKMouseGestureRecognizer.h" #if HAVE(UIKIT_WITH_MOUSE_SUPPORT) #import "NativeWebMouseEvent.h" #import #import #import static OptionSet webEventModifiersForUIKeyModifierFlags(UIKeyModifierFlags flags) { OptionSet modifiers; if (flags & UIKeyModifierShift) modifiers.add(WebKit::WebEvent::Modifier::ShiftKey); if (flags & UIKeyModifierControl) modifiers.add(WebKit::WebEvent::Modifier::ControlKey); if (flags & UIKeyModifierAlternate) modifiers.add(WebKit::WebEvent::Modifier::AltKey); if (flags & UIKeyModifierCommand) modifiers.add(WebKit::WebEvent::Modifier::MetaKey); if (flags & UIKeyModifierAlphaShift) modifiers.add(WebKit::WebEvent::Modifier::CapsLockKey); return modifiers; } #if USE(APPLE_INTERNAL_SDK) #include #else static String pointerTypeForUITouchType(UITouchType) { return WebCore::mousePointerEventType(); } #endif @implementation WKMouseGestureRecognizer { RetainPtr _currentHoverEvent; RetainPtr _currentTouch; BOOL _touching; std::unique_ptr _lastEvent; std::optional _lastLocation; std::optional _pressedButtonMask; } - (void)setEnabled:(BOOL)enabled { [super setEnabled:enabled]; if (!enabled) { _currentHoverEvent = nil; _currentTouch = nil; _touching = NO; _lastEvent = nil; _lastLocation = std::nullopt; _pressedButtonMask = std::nullopt; } } - (BOOL)_shouldReceiveTouch:(UITouch *)touch forEvent:(UIEvent *)event recognizerView:(id)gestureOwner { // FIXME: We should move off of this UIKit IPI method once we have a viable SPI or API alternative // for opting a UIHoverGestureRecognizer subclass into receiving UITouches. See also: rdar://80700227. return touch == _currentTouch && touch._isPointerTouch; } - (WebKit::NativeWebMouseEvent *)lastMouseEvent { return _lastEvent.get(); } - (std::optional)lastMouseLocation { return _lastLocation; } - (UITouch *)mouseTouch { return _currentTouch.get(); } - (void)reset { [super reset]; _currentHoverEvent = nil; _currentTouch = nil; } - (std::unique_ptr)createMouseEventWithType:(WebKit::WebEvent::Type)type wasCancelled:(BOOL)cancelled { auto modifiers = webEventModifiersForUIKeyModifierFlags(self.modifierFlags); BOOL isRightButton = modifiers.contains(WebKit::WebEvent::Modifier::ControlKey) || (_pressedButtonMask && (*_pressedButtonMask & UIEventButtonMaskSecondary)); auto button = [&] { if (!_touching) return WebKit::WebMouseEvent::NoButton; if (isRightButton) return WebKit::WebMouseEvent::RightButton; return WebKit::WebMouseEvent::LeftButton; }(); // FIXME: 'buttons' should report any buttons that are still down in the case when one button is released from a chord. auto buttons = [&] { if (!_touching || type == WebKit::WebEvent::Type::MouseUp) return 0; if (isRightButton) return 2; return 1; }(); WebCore::IntPoint point { [self locationInView:self.view] }; auto delta = point - WebCore::IntPoint { [_currentTouch previousLocationInView:self.view] }; // UITouch's timestamp uses mach_absolute_time as its timebase, same as MonotonicTime. auto timestamp = MonotonicTime::fromRawSeconds([_currentTouch timestamp]).approximateWallTime(); return WTF::makeUnique(type, button, buttons, point, point, delta.width(), delta.height(), 0, [_currentTouch tapCount], modifiers, timestamp, 0, cancelled ? WebKit::GestureWasCancelled::Yes : WebKit::GestureWasCancelled::No, pointerTypeForUITouchType([_currentTouch type])); } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { _touching = YES; _pressedButtonMask = [event _buttonMask]; _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseDown wasCancelled:NO]; _lastLocation = [self locationInView:self.view]; self.state = UIGestureRecognizerStateChanged; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseMove wasCancelled:NO]; _lastLocation = [self locationInView:self.view]; self.state = UIGestureRecognizerStateChanged; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseUp wasCancelled:NO]; _lastLocation = [self locationInView:self.view]; _touching = NO; _pressedButtonMask = std::nullopt; self.state = UIGestureRecognizerStateChanged; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseUp wasCancelled:YES]; _lastLocation = [self locationInView:self.view]; _touching = NO; _pressedButtonMask = std::nullopt; self.state = UIGestureRecognizerStateChanged; } - (void)_hoverEntered:(NSSet *)touches withEvent:(UIEvent *)event { _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseMove wasCancelled:NO]; if (_currentHoverEvent == nil && touches.count == 1 && [event isKindOfClass:NSClassFromString(@"UIHoverEvent")]) { _currentHoverEvent = event; _currentTouch = touches.anyObject; _lastLocation = [self locationInView:self.view]; self.state = UIGestureRecognizerStateBegan; } } - (void)_hoverMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (_touching) { _lastEvent = nullptr; return; } _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseMove wasCancelled:NO]; _lastLocation = [self locationInView:self.view]; if (_currentHoverEvent == event && [touches containsObject:_currentTouch.get()]) self.state = UIGestureRecognizerStateChanged; } - (void)_hoverExited:(NSSet *)touches withEvent:(UIEvent *)event { _lastEvent = [self createMouseEventWithType:WebKit::WebEvent::MouseMove wasCancelled:NO]; _lastLocation = [self locationInView:self.view]; if (_currentHoverEvent == event) { _currentHoverEvent = nil; _currentTouch = nil; self.state = UIGestureRecognizerStateEnded; } } - (void)_hoverCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self _hoverExited:touches withEvent:event]; } - (CGPoint)locationInView:(UIView *)view { if (!_currentTouch) return CGPointMake(-1, -1); return [_currentTouch locationInView:view]; } @end #endif