| // <copyright file="PointerInputDevice.cs" company="Selenium Committers"> |
| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| // </copyright> |
| |
| using OpenQA.Selenium.Internal; |
| using System; |
| using System.Collections.Generic; |
| using System.Globalization; |
| |
| namespace OpenQA.Selenium.Interactions; |
| |
| /// <summary> |
| /// Represents the origin of the coordinates for mouse movement. |
| /// </summary> |
| public enum CoordinateOrigin |
| { |
| /// <summary> |
| /// The coordinate origin is the origin of the view port of the browser. |
| /// </summary> |
| Viewport, |
| |
| /// <summary> |
| /// The origin of the movement is the most recent pointer location. |
| /// </summary> |
| Pointer, |
| |
| /// <summary> |
| /// The origin of the movement is the center of the element specified. |
| /// </summary> |
| Element |
| } |
| |
| /// <summary> |
| /// Specifies the type of pointer a pointer device represents. |
| /// </summary> |
| public enum PointerKind |
| { |
| /// <summary> |
| /// The pointer device is a mouse. |
| /// </summary> |
| Mouse, |
| |
| /// <summary> |
| /// The pointer device is a pen or stylus. |
| /// </summary> |
| Pen, |
| |
| /// <summary> |
| /// The pointer device is a touch screen device. |
| /// </summary> |
| Touch |
| } |
| |
| /// <summary> |
| /// Specifies the button used during a pointer down or up action. |
| /// </summary> |
| public enum MouseButton |
| { |
| /// <summary> |
| /// This button is used for signifying touch actions. |
| /// </summary> |
| Touch = 0, |
| |
| /// <summary> |
| /// The button used is the primary button. |
| /// </summary> |
| Left = 0, |
| |
| /// <summary> |
| /// The button used is the middle button or mouse wheel. |
| /// </summary> |
| Middle = 1, |
| |
| /// <summary> |
| /// The button used is the secondary button. |
| /// </summary> |
| Right = 2, |
| |
| /// <summary> |
| /// The X1 button used for navigating back. |
| /// </summary> |
| Back = 3, |
| |
| /// <summary> |
| /// The X2 button used for navigating forward. |
| /// </summary> |
| Forward = 4, |
| } |
| |
| /// <summary> |
| /// Represents a pointer input device, such as a stylus, mouse, or finger on a touch screen. |
| /// </summary> |
| public class PointerInputDevice : InputDevice |
| { |
| private readonly PointerKind pointerKind; |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="PointerInputDevice"/> class. |
| /// </summary> |
| public PointerInputDevice() |
| : this(PointerKind.Mouse) |
| { |
| } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="PointerInputDevice"/> class. |
| /// </summary> |
| /// <param name="pointerKind">The kind of pointer represented by this input device.</param> |
| public PointerInputDevice(PointerKind pointerKind) |
| : this(pointerKind, Guid.NewGuid().ToString()) |
| { |
| } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="PointerInputDevice"/> class. |
| /// </summary> |
| /// <param name="pointerKind">The kind of pointer represented by this input device.</param> |
| /// <param name="deviceName">The unique name for this input device.</param> |
| /// <exception cref="ArgumentException">If <paramref name="deviceName"/> is <see langword="null"/> or <see cref="string.Empty"/>.</exception> |
| public PointerInputDevice(PointerKind pointerKind, string deviceName) |
| : base(deviceName) |
| { |
| this.pointerKind = pointerKind; |
| } |
| |
| /// <summary> |
| /// Gets the type of device for this input device. |
| /// </summary> |
| public override InputDeviceKind DeviceKind => InputDeviceKind.Pointer; |
| |
| /// <summary> |
| /// Returns a value for this input device that can be transmitted across the wire to a remote end. |
| /// </summary> |
| /// <returns>A <see cref="Dictionary{TKey, TValue}"/> representing this action.</returns> |
| public override Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn = new Dictionary<string, object>(); |
| |
| toReturn["type"] = "pointer"; |
| toReturn["id"] = this.DeviceName; |
| |
| Dictionary<string, object> parameters = new Dictionary<string, object>(); |
| parameters["pointerType"] = this.pointerKind.ToString().ToLowerInvariant(); |
| toReturn["parameters"] = parameters; |
| |
| return toReturn; |
| } |
| |
| /// <summary> |
| /// Creates a pointer down action. |
| /// </summary> |
| /// <param name="button">The button of the pointer that should be pressed.</param> |
| /// <returns>The action representing the pointer down gesture.</returns> |
| public Interaction CreatePointerDown(MouseButton button) |
| { |
| return CreatePointerDown(button, new PointerEventProperties()); |
| } |
| |
| /// <summary> |
| /// Creates a pointer down action. |
| /// </summary> |
| /// <remarks> |
| /// MouseButton value applies to Pen types for primary, secondary and erase functionality (0, 2, and 5 respectively) |
| /// </remarks> |
| /// <param name="button">The button of the pointer that should be pressed.</param> |
| /// <param name="properties">The specifications for the pointer event interaction</param> |
| /// <returns>The action representing the pointer down gesture.</returns> |
| public Interaction CreatePointerDown(MouseButton button, PointerEventProperties properties) |
| { |
| return new PointerDownInteraction(this, button, properties); |
| } |
| |
| /// <summary> |
| /// Creates a pointer up action. |
| /// </summary> |
| /// <param name="button">The button of the pointer that should be released.</param> |
| /// <returns>The action representing the pointer up gesture.</returns> |
| public Interaction CreatePointerUp(MouseButton button) |
| { |
| return CreatePointerUp(button, new PointerEventProperties()); |
| } |
| |
| /// <summary> |
| /// Creates a pointer down action. |
| /// </summary> |
| /// <remarks> |
| /// MouseButton value applies to Pen types for primary, secondary and erase functionality (0, 2, and 5 respectively) |
| /// </remarks> |
| /// <param name="button">The button of the pointer that should be pressed.</param> |
| /// <param name="properties">The specifications for the pointer event interaction</param> |
| /// <returns>The action representing the pointer down gesture.</returns> |
| public Interaction CreatePointerUp(MouseButton button, PointerEventProperties properties) |
| { |
| return new PointerUpInteraction(this, button, properties); |
| } |
| |
| /// <summary> |
| /// Creates a pointer move action to a specific element. |
| /// </summary> |
| /// <param name="target">The <see cref="IWebElement"/> used as the target for the move.</param> |
| /// <param name="xOffset">The horizontal offset from the origin of the move.</param> |
| /// <param name="yOffset">The vertical offset from the origin of the move.</param> |
| /// <param name="duration">The length of time the move gesture takes to complete.</param> |
| /// <returns>The action representing the pointer move gesture.</returns> |
| /// <exception cref="ArgumentNullException">If <paramref name="target"/> is <see langword="null"/>.</exception> |
| public Interaction CreatePointerMove(IWebElement target, int xOffset, int yOffset, TimeSpan duration) |
| { |
| return CreatePointerMove(target, xOffset, yOffset, duration, new PointerEventProperties()); |
| } |
| |
| /// <summary> |
| /// Creates a pointer move action to a specific element. |
| /// </summary> |
| /// <param name="target">The <see cref="IWebElement"/> used as the target for the move.</param> |
| /// <param name="xOffset">The horizontal offset from the origin of the move.</param> |
| /// <param name="yOffset">The vertical offset from the origin of the move.</param> |
| /// <param name="duration">The length of time the move gesture takes to complete.</param> |
| /// <param name="properties">The specifications for the pointer event interaction</param> |
| /// <returns>The action representing the pointer move gesture.</returns> |
| /// <exception cref="ArgumentNullException">If <paramref name="target"/> is <see langword="null"/>.</exception> |
| public Interaction CreatePointerMove(IWebElement target, int xOffset, int yOffset, TimeSpan duration, PointerEventProperties properties) |
| { |
| return new PointerMoveInteraction(this, target, CoordinateOrigin.Element, xOffset, yOffset, duration, properties); |
| } |
| |
| /// <summary> |
| /// Creates a pointer move action to an absolute coordinate. |
| /// </summary> |
| /// <param name="origin">The origin of coordinates for the move. Values can be relative to |
| /// the view port origin, or the most recent pointer position.</param> |
| /// <param name="xOffset">The horizontal offset from the origin of the move.</param> |
| /// <param name="yOffset">The vertical offset from the origin of the move.</param> |
| /// <param name="duration">The length of time the move gesture takes to complete.</param> |
| /// <returns>The action representing the pointer move gesture.</returns> |
| /// <exception cref="ArgumentException">Thrown when passing CoordinateOrigin.Element into origin. |
| /// Users should us the other CreatePointerMove overload to move to a specific element.</exception> |
| public Interaction CreatePointerMove(CoordinateOrigin origin, int xOffset, int yOffset, TimeSpan duration) |
| { |
| return CreatePointerMove(origin, xOffset, yOffset, duration, new PointerEventProperties()); |
| } |
| |
| /// <summary> |
| /// Creates a pointer move action to an absolute coordinate. |
| /// </summary> |
| /// <param name="origin">The origin of coordinates for the move. Values can be relative to |
| /// the view port origin, or the most recent pointer position.</param> |
| /// <param name="xOffset">The horizontal offset from the origin of the move.</param> |
| /// <param name="yOffset">The vertical offset from the origin of the move.</param> |
| /// <param name="duration">The length of time the move gesture takes to complete.</param> |
| /// <param name="properties">The object containing additional properties for this pointer move operation.</param> |
| /// <returns>The action representing the pointer move gesture.</returns> |
| /// <exception cref="ArgumentException">Thrown when passing CoordinateOrigin.Element into origin. |
| /// Users should use the other CreatePointerMove overload to move to a specific element.</exception> |
| public Interaction CreatePointerMove(CoordinateOrigin origin, int xOffset, int yOffset, TimeSpan duration, PointerEventProperties properties) |
| { |
| if (origin == CoordinateOrigin.Element) |
| { |
| throw new ArgumentException("Using a value of CoordinateOrigin.Element without an element is not supported.", nameof(origin)); |
| } |
| |
| return new PointerMoveInteraction(this, null, origin, xOffset, yOffset, duration, properties); |
| } |
| |
| /// <summary> |
| /// Creates a pointer cancel action. |
| /// </summary> |
| /// <returns>The action representing the pointer cancel gesture.</returns> |
| public Interaction CreatePointerCancel() |
| { |
| return new PointerCancelInteraction(this); |
| } |
| |
| /// <summary> |
| /// A class representing the properties of a pointer event. |
| /// </summary> |
| public class PointerEventProperties |
| { |
| /// <summary> |
| /// Gets or sets the width (magnitude on x-axis) in pixels of the contact geometry of the pointer. |
| /// </summary> |
| public double? Width { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the height (magnitude on y-axis) in pixels of the contact geometry of the pointer. |
| /// </summary> |
| public double? Height { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the normalized pressure of the pointer input. |
| /// </summary> |
| /// <remarks> |
| /// 0 and 1 represent the minimum and maximum pressure the hardware is capable of detecting, respectively. |
| /// </remarks> |
| public double? Pressure { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the normalized tangential pressure (also known as barrel pressure) of the pointer input. |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between -1 and 1 with 0 being the neutral position of the control. |
| /// Some hardware may only support positive values between 0 and 1. |
| /// </remarks> |
| public double? TangentialPressure { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the plane angle in degrees between the Y-Z plane and the plane containing |
| /// both the transducer (e.g. pen stylus) axis and the Y axis.. |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between -90 and 90. A positive value is to the right. |
| /// </remarks> |
| public int? TiltX { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the plane angle in degrees between the X-Z plane and the plane containing |
| /// both the transducer (e.g. pen stylus) axis and the X axis.. |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between -90 and 90. A positive value is toward the user. |
| /// </remarks> |
| public int? TiltY { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the clockwise rotation in degrees of a transducer (e.g. stylus) around its own major axis |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between 0 and 359. |
| /// </remarks> |
| public int? Twist { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the altitude in radians of the transducer (e.g. pen/stylus) |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between 0 and π/2, where 0 is parallel to the surface (X-Y plane), |
| /// and π/2 is perpendicular to the surface. |
| /// </remarks> |
| public double? AltitudeAngle { get; set; } |
| |
| /// <summary> |
| /// Gets or sets the azimuth angle (in radians) of the transducer (e.g. pen/stylus) |
| /// </summary> |
| /// <remarks> |
| /// Valid values are between 0 and 2Ï€, |
| /// where 0 represents a transducer whose cap is pointing in the direction of increasing X values, |
| /// and the values progressively increase when going clockwise. |
| /// </remarks> |
| public double? AzimuthAngle { get; set; } |
| |
| /// <summary> |
| /// Serializes the properties of this input device as a dictionary. |
| /// </summary> |
| /// <returns>The dictionary containing the properties of this device.</returns> |
| public Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn = new Dictionary<string, object>(); |
| |
| if (this.Width.HasValue) |
| { |
| toReturn["width"] = this.Width.Value; |
| } |
| |
| if (this.Height.HasValue) |
| { |
| toReturn["height"] = this.Height.Value; |
| } |
| |
| if (this.Pressure.HasValue) |
| { |
| toReturn["pressure"] = this.Pressure.Value; |
| } |
| |
| if (this.TangentialPressure.HasValue) |
| { |
| toReturn["tangentialPressure"] = this.TangentialPressure.Value; |
| } |
| |
| if (this.TiltX.HasValue) |
| { |
| toReturn["tiltX"] = this.TiltX.Value; |
| } |
| |
| if (this.TiltY.HasValue) |
| { |
| toReturn["tiltY"] = this.TiltY.Value; |
| } |
| |
| if (this.Twist.HasValue) |
| { |
| toReturn["twist"] = this.Twist.Value; |
| } |
| |
| if (this.AltitudeAngle.HasValue) |
| { |
| toReturn["altitudeAngle"] = this.AltitudeAngle.Value; |
| } |
| |
| if (this.AzimuthAngle.HasValue) |
| { |
| toReturn["azimuthAngle"] = this.AzimuthAngle.Value; |
| } |
| |
| return toReturn; |
| } |
| } |
| |
| private class PointerDownInteraction : Interaction |
| { |
| private readonly MouseButton button; |
| private readonly PointerEventProperties eventProperties; |
| |
| public PointerDownInteraction(InputDevice sourceDevice, MouseButton button, PointerEventProperties properties) |
| : base(sourceDevice) |
| { |
| this.button = button; |
| this.eventProperties = properties; |
| } |
| |
| public override Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn; |
| if (eventProperties is null) |
| { |
| toReturn = new Dictionary<string, object>(); |
| } |
| else |
| { |
| toReturn = eventProperties.ToDictionary(); |
| } |
| toReturn["type"] = "pointerDown"; |
| toReturn["button"] = Convert.ToInt32(this.button, CultureInfo.InvariantCulture); |
| |
| return toReturn; |
| } |
| |
| public override string ToString() |
| { |
| return "Pointer down"; |
| } |
| } |
| |
| private class PointerUpInteraction : Interaction |
| { |
| private readonly MouseButton button; |
| private readonly PointerEventProperties eventProperties; |
| |
| public PointerUpInteraction(InputDevice sourceDevice, MouseButton button, PointerEventProperties properties) |
| : base(sourceDevice) |
| { |
| this.button = button; |
| this.eventProperties = properties; |
| } |
| |
| public override Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn; |
| if (eventProperties is null) |
| { |
| toReturn = new Dictionary<string, object>(); |
| } |
| else |
| { |
| toReturn = eventProperties.ToDictionary(); |
| } |
| |
| toReturn["type"] = "pointerUp"; |
| toReturn["button"] = Convert.ToInt32(this.button, CultureInfo.InvariantCulture); |
| |
| return toReturn; |
| } |
| |
| public override string ToString() |
| { |
| return "Pointer up"; |
| } |
| } |
| |
| private class PointerCancelInteraction : Interaction |
| { |
| public PointerCancelInteraction(InputDevice sourceDevice) |
| : base(sourceDevice) |
| { |
| } |
| |
| public override Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn = new Dictionary<string, object>(); |
| toReturn["type"] = "pointerCancel"; |
| return toReturn; |
| } |
| |
| public override string ToString() |
| { |
| return "Pointer cancel"; |
| } |
| } |
| |
| private class PointerMoveInteraction : Interaction |
| { |
| private readonly IWebElement? target; |
| private readonly int x = 0; |
| private readonly int y = 0; |
| private TimeSpan duration = TimeSpan.MinValue; |
| private readonly CoordinateOrigin origin = CoordinateOrigin.Pointer; |
| private readonly PointerEventProperties eventProperties; |
| |
| public PointerMoveInteraction(InputDevice sourceDevice, IWebElement? target, CoordinateOrigin origin, int x, int y, TimeSpan duration, PointerEventProperties properties) |
| : base(sourceDevice) |
| { |
| if (target != null) |
| { |
| this.target = target; |
| this.origin = CoordinateOrigin.Element; |
| } |
| else |
| { |
| if (this.origin != CoordinateOrigin.Element) |
| { |
| this.origin = origin; |
| } |
| } |
| |
| if (duration != TimeSpan.MinValue) |
| { |
| this.duration = duration; |
| } |
| |
| this.x = x; |
| this.y = y; |
| this.eventProperties = properties; |
| } |
| |
| public override Dictionary<string, object> ToDictionary() |
| { |
| Dictionary<string, object> toReturn; |
| if (eventProperties == null) |
| { |
| toReturn = new Dictionary<string, object>(); |
| } |
| else |
| { |
| toReturn = eventProperties.ToDictionary(); |
| } |
| |
| toReturn["type"] = "pointerMove"; |
| if (this.duration != TimeSpan.MinValue) |
| { |
| toReturn["duration"] = Convert.ToInt64(this.duration.TotalMilliseconds); |
| } |
| |
| if (this.target != null) |
| { |
| toReturn["origin"] = this.ConvertElement(); |
| } |
| else |
| { |
| toReturn["origin"] = this.origin.ToString().ToLowerInvariant(); |
| } |
| |
| toReturn["x"] = this.x; |
| toReturn["y"] = this.y; |
| |
| return toReturn; |
| } |
| |
| public override string ToString() |
| { |
| string originDescription = this.origin.ToString(); |
| if (this.origin == CoordinateOrigin.Element) |
| { |
| originDescription = this.target?.ToString() ?? "<null>"; |
| } |
| |
| return string.Format(CultureInfo.InvariantCulture, "Pointer move [origin: {0}, x offset: {1}, y offset: {2}, duration: {3}ms]", originDescription, this.x, this.y, this.duration.TotalMilliseconds); |
| } |
| |
| private Dictionary<string, object> ConvertElement() |
| { |
| IWebDriverObjectReference? elementReference = this.target as IWebDriverObjectReference; |
| if (elementReference == null) |
| { |
| IWrapsElement? elementWrapper = this.target as IWrapsElement; |
| if (elementWrapper != null) |
| { |
| elementReference = elementWrapper.WrappedElement as IWebDriverObjectReference; |
| } |
| } |
| |
| if (elementReference == null) |
| { |
| throw new ArgumentException("Target element cannot be converted to IWebElementReference"); |
| } |
| |
| Dictionary<string, object> elementDictionary = elementReference.ToDictionary(); |
| return elementDictionary; |
| } |
| } |
| } |