summaryrefslogtreecommitdiffstats
path: root/testing/marionette/harness/marionette_harness/www/shim.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/marionette/harness/marionette_harness/www/shim.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/marionette/harness/marionette_harness/www/shim.js')
-rw-r--r--testing/marionette/harness/marionette_harness/www/shim.js297
1 files changed, 297 insertions, 0 deletions
diff --git a/testing/marionette/harness/marionette_harness/www/shim.js b/testing/marionette/harness/marionette_harness/www/shim.js
new file mode 100644
index 0000000000..9a8e09d6b6
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/www/shim.js
@@ -0,0 +1,297 @@
+/**
+ * mouse_event_shim.js: generate mouse events from touch events.
+ *
+ * This library listens for touch events and generates mousedown, mousemove
+ * mouseup, and click events to match them. It captures and dicards any
+ * real mouse events (non-synthetic events with isTrusted true) that are
+ * send by gecko so that there are not duplicates.
+ *
+ * This library does emit mouseover/mouseout and mouseenter/mouseleave
+ * events. You can turn them off by setting MouseEventShim.trackMouseMoves to
+ * false. This means that mousemove events will always have the same target
+ * as the mousedown even that began the series. You can also call
+ * MouseEventShim.setCapture() from a mousedown event handler to prevent
+ * mouse tracking until the next mouseup event.
+ *
+ * This library does not support multi-touch but should be sufficient
+ * to do drags based on mousedown/mousemove/mouseup events.
+ *
+ * This library does not emit dblclick events or contextmenu events
+ */
+
+"use strict";
+
+(function () {
+ // Make sure we don't run more than once
+ if (MouseEventShim) {
+ return;
+ }
+
+ // Bail if we're not on running on a platform that sends touch
+ // events. We don't need the shim code for mouse events.
+ try {
+ document.createEvent("TouchEvent");
+ } catch (e) {
+ return;
+ }
+
+ let starttouch; // The Touch object that we started with
+ let target; // The element the touch is currently over
+ let emitclick; // Will we be sending a click event after mouseup?
+
+ // Use capturing listeners to discard all mouse events from gecko
+ window.addEventListener("mousedown", discardEvent, true);
+ window.addEventListener("mouseup", discardEvent, true);
+ window.addEventListener("mousemove", discardEvent, true);
+ window.addEventListener("click", discardEvent, true);
+
+ function discardEvent(e) {
+ if (e.isTrusted) {
+ e.stopImmediatePropagation(); // so it goes no further
+ if (e.type === "click") {
+ e.preventDefault();
+ } // so it doesn't trigger a change event
+ }
+ }
+
+ // Listen for touch events that bubble up to the window.
+ // If other code has called stopPropagation on the touch events
+ // then we'll never see them. Also, we'll honor the defaultPrevented
+ // state of the event and will not generate synthetic mouse events
+ window.addEventListener("touchstart", handleTouchStart);
+ window.addEventListener("touchmove", handleTouchMove);
+ window.addEventListener("touchend", handleTouchEnd);
+ window.addEventListener("touchcancel", handleTouchEnd); // Same as touchend
+
+ function handleTouchStart(e) {
+ // If we're already handling a touch, ignore this one
+ if (starttouch) {
+ return;
+ }
+
+ // Ignore any event that has already been prevented
+ if (e.defaultPrevented) {
+ return;
+ }
+
+ // Sometimes an unknown gecko bug causes us to get a touchstart event
+ // for an iframe target that we can't use because it is cross origin.
+ // Don't start handling a touch in that case
+ try {
+ e.changedTouches[0].target.ownerDocument;
+ } catch (e) {
+ // Ignore the event if we can't see the properties of the target
+ return;
+ }
+
+ // If there is more than one simultaneous touch, ignore all but the first
+ starttouch = e.changedTouches[0];
+ target = starttouch.target;
+ emitclick = true;
+
+ // Move to the position of the touch
+ emitEvent("mousemove", target, starttouch);
+
+ // Now send a synthetic mousedown
+ let result = emitEvent("mousedown", target, starttouch);
+
+ // If the mousedown was prevented, pass that on to the touch event.
+ // And remember not to send a click event
+ if (!result) {
+ e.preventDefault();
+ emitclick = false;
+ }
+ }
+
+ function handleTouchEnd(e) {
+ if (!starttouch) {
+ return;
+ }
+
+ // End a MouseEventShim.setCapture() call
+ if (MouseEventShim.capturing) {
+ MouseEventShim.capturing = false;
+ MouseEventShim.captureTarget = null;
+ }
+
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ let touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier) {
+ continue;
+ }
+
+ emitEvent("mouseup", target, touch);
+
+ // If target is still the same element we started and the touch did not
+ // move more than the threshold and if the user did not prevent
+ // the mousedown, then send a click event, too.
+ if (emitclick) {
+ emitEvent("click", starttouch.target, touch);
+ }
+
+ starttouch = null;
+ return;
+ }
+ }
+
+ function handleTouchMove(e) {
+ if (!starttouch) {
+ return;
+ }
+
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ let touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier) {
+ continue;
+ }
+
+ // Don't send a mousemove if the touchmove was prevented
+ if (e.defaultPrevented) {
+ return;
+ }
+
+ // See if we've moved too much to emit a click event
+ let dx = Math.abs(touch.screenX - starttouch.screenX);
+ let dy = Math.abs(touch.screenY - starttouch.screenY);
+ if (
+ dx > MouseEventShim.dragThresholdX ||
+ dy > MouseEventShim.dragThresholdY
+ ) {
+ emitclick = false;
+ }
+
+ let tracking =
+ MouseEventShim.trackMouseMoves && !MouseEventShim.capturing;
+
+ let oldtarget;
+ let newtarget;
+ if (tracking) {
+ // If the touch point moves, then the element it is over
+ // may have changed as well. Note that calling elementFromPoint()
+ // forces a layout if one is needed.
+ // XXX: how expensive is it to do this on each touchmove?
+ // Can we listen for (non-standard) touchleave events instead?
+ oldtarget = target;
+ newtarget = document.elementFromPoint(touch.clientX, touch.clientY);
+ if (newtarget === null) {
+ // this can happen as the touch is moving off of the screen, e.g.
+ newtarget = oldtarget;
+ }
+ if (newtarget !== oldtarget) {
+ leave(oldtarget, newtarget, touch); // mouseout, mouseleave
+ target = newtarget;
+ }
+ } else if (MouseEventShim.captureTarget) {
+ target = MouseEventShim.captureTarget;
+ }
+
+ emitEvent("mousemove", target, touch);
+
+ if (tracking && newtarget !== oldtarget) {
+ enter(newtarget, oldtarget, touch); // mouseover, mouseenter
+ }
+ }
+ }
+
+ // Return true if element a contains element b
+ function contains(a, b) {
+ return (a.compareDocumentPosition(b) & 16) !== 0;
+ }
+
+ // A touch has left oldtarget and entered newtarget
+ // Send out all the events that are required
+ function leave(oldtarget, newtarget, touch) {
+ emitEvent("mouseout", oldtarget, touch, newtarget);
+
+ // If the touch has actually left oldtarget (and has not just moved
+ // into a child of oldtarget) send a mouseleave event. mouseleave
+ // events don't bubble, so we have to repeat this up the hierarchy.
+ for (let e = oldtarget; !contains(e, newtarget); e = e.parentNode) {
+ emitEvent("mouseleave", e, touch, newtarget);
+ }
+ }
+
+ // A touch has entered newtarget from oldtarget
+ // Send out all the events that are required.
+ function enter(newtarget, oldtarget, touch) {
+ emitEvent("mouseover", newtarget, touch, oldtarget);
+
+ // Emit non-bubbling mouseenter events if the touch actually entered
+ // newtarget and wasn't already in some child of it
+ for (let e = newtarget; !contains(e, oldtarget); e = e.parentNode) {
+ emitEvent("mouseenter", e, touch, oldtarget);
+ }
+ }
+
+ function emitEvent(type, target, touch, relatedTarget) {
+ let synthetic = document.createEvent("MouseEvents");
+ let bubbles = type !== "mouseenter" && type !== "mouseleave";
+ let count =
+ type === "mousedown" || type === "mouseup" || type === "click" ? 1 : 0;
+
+ synthetic.initMouseEvent(
+ type,
+ bubbles, // canBubble
+ true, // cancelable
+ window,
+ count, // detail: click count
+ touch.screenX,
+ touch.screenY,
+ touch.clientX,
+ touch.clientY,
+ false, // ctrlKey: we don't have one
+ false, // altKey: we don't have one
+ false, // shiftKey: we don't have one
+ false, // metaKey: we don't have one
+ 0, // we're simulating the left button
+ relatedTarget || null
+ );
+
+ try {
+ return target.dispatchEvent(synthetic);
+ } catch (e) {
+ console.warn("Exception calling dispatchEvent", type, e);
+ return true;
+ }
+ }
+})();
+
+const MouseEventShim = {
+ // It is a known gecko bug that synthetic events have timestamps measured
+ // in microseconds while regular events have timestamps measured in
+ // milliseconds. This utility function returns a the timestamp converted
+ // to milliseconds, if necessary.
+ getEventTimestamp(e) {
+ if (e.isTrusted) {
+ // XXX: Are real events always trusted?
+ return e.timeStamp;
+ }
+ return e.timeStamp / 1000;
+ },
+
+ // Set this to false if you don't care about mouseover/out events
+ // and don't want the target of mousemove events to follow the touch
+ trackMouseMoves: true,
+
+ // Call this function from a mousedown event handler if you want to guarantee
+ // that the mousemove and mouseup events will go to the same element
+ // as the mousedown even if they leave the bounds of the element. This is
+ // like setting trackMouseMoves to false for just one drag. It is a
+ // substitute for event.target.setCapture(true)
+ setCapture(target) {
+ this.capturing = true; // Will be set back to false on mouseup
+ if (target) {
+ this.captureTarget = target;
+ }
+ },
+
+ capturing: false,
+
+ // Keep these in sync with ui.dragThresholdX and ui.dragThresholdY prefs.
+ // If a touch ever moves more than this many pixels from its starting point
+ // then we will not synthesize a click event when the touch ends.
+ dragThresholdX: 25,
+ dragThresholdY: 25,
+};