diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/marionette/harness/marionette_harness/www/shim.js | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
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.js | 297 |
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..c304333088 --- /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, +}; |