tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Event.sys.mjs (8575B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
      9 });
     10 
     11 /** Provides functionality for creating and sending DOM events. */
     12 export const event = {};
     13 
     14 const _eventUtils = new WeakMap();
     15 
     16 function _getEventUtils(win) {
     17  if (!_eventUtils.has(win)) {
     18    const eventUtilsObject = {
     19      window: win,
     20      parent: win,
     21      _EU_Ci: Ci,
     22      _EU_Cc: Cc,
     23    };
     24    Services.scriptloader.loadSubScript(
     25      "chrome://remote/content/external/EventUtils.js",
     26      eventUtilsObject
     27    );
     28    _eventUtils.set(win, eventUtilsObject);
     29  }
     30  return _eventUtils.get(win);
     31 }
     32 
     33 event.MouseEvents = {
     34  click: 0,
     35  dblclick: 1,
     36  mousedown: 2,
     37  mouseup: 3,
     38  mouseover: 4,
     39  mouseout: 5,
     40 };
     41 
     42 event.Modifiers = {
     43  shiftKey: 0,
     44  ctrlKey: 1,
     45  altKey: 2,
     46  metaKey: 3,
     47 };
     48 
     49 event.MouseButton = {
     50  isPrimary(button) {
     51    return button === 0;
     52  },
     53  isAuxiliary(button) {
     54    return button === 1;
     55  },
     56  isSecondary(button) {
     57    return button === 2;
     58  },
     59 };
     60 
     61 /**
     62 * Synthesize a mouse event in `win` at a point.
     63 *
     64 * If the type is specified in `event`, a mouse event of that type is
     65 * fired. Otherwise, a mousedown followed by a mouseup is performed.
     66 *
     67 * @param {number} left - Value for the X offset in CSS pixels.
     68 * @param {number} top - Value for the Y offset in CSS pixels.
     69 * @param {module:EventUtils~MouseEventData} event - Details of the mouse event
     70 *     to dispatch.
     71 * @param {DOMWindow} win - DOM window used to dispatch the event.
     72 *
     73 * @returns {Promise<boolean>} Promise that resolves to a boolean,
     74 *     indicating whether the event had preventDefault() called on it.
     75 */
     76 event.synthesizeMouseAtPoint = function (left, top, event, win) {
     77  if (!event.asyncEnabled) {
     78    return Promise.resolve(
     79      _getEventUtils(win).synthesizeMouseAtPoint(left, top, event, win)
     80    );
     81  }
     82 
     83  // A callback must be used when handling events with the `asyncEnabled`
     84  // flag set to `true`, as these events are synthesized in the parent process.
     85  // We need to wait for them to be fully dispatched to the content process
     86  // before continuing.
     87  const { promise, resolve } = Promise.withResolvers();
     88  const preventDefaultFlag = _getEventUtils(win).synthesizeMouseAtPoint(
     89    left,
     90    top,
     91    event,
     92    win,
     93    () => resolve()
     94  );
     95 
     96  return promise.then(() => preventDefaultFlag);
     97 };
     98 
     99 /**
    100 * Synthesize a touch event at a point.
    101 *
    102 * If the type is specified in opts, a touch event of that type is
    103 * fired. Otherwise, a touchstart followed by a touchend is performed.
    104 *
    105 * @param {number} left
    106 *     Offset from viewport left, in CSS pixels
    107 * @param {number} top
    108 *     Offset from viewport top, in CSS pixels
    109 * @param {object} opts
    110 *     Object which may contain the properties "id", "rx", "ry", "angle",
    111 *     "force", "shiftKey", "ctrlKey", "altKey", "metaKey", "accessKey",
    112 *     "type".
    113 * @param {Window} win
    114 *     Window object.
    115 *
    116 * @returns {boolean} defaultPrevented
    117 */
    118 event.synthesizeTouchAtPoint = function (left, top, opts, win) {
    119  return _getEventUtils(win).synthesizeTouchAtPoint(left, top, opts, win);
    120 };
    121 
    122 /**
    123 * Synthesize a wheel event in `win` at a point, without flushing layout.
    124 *
    125 * @param {number} left - Floating-point value for the X offset in CSS pixels.
    126 * @param {number} top - Floating-point value for the Y offset in CSS pixels.
    127 * @param {module:EventUtils~WheelEventData} event - Details of the wheel event
    128 *     to dispatch.
    129 * @param {DOMWindow} win - DOM window used to dispatch the event.
    130 *
    131 * @returns {Promise} - Promise that resolves once the event has been dispatched.
    132 */
    133 event.synthesizeWheelAtPoint = function (left, top, event, win) {
    134  const dpr = win.devicePixelRatio;
    135 
    136  // All delta properties expect the value in device pixels while the
    137  // WebDriver specification uses CSS pixels.
    138  // TODO: check if the conversion needs to be done at the platform level
    139  if (typeof event.deltaX !== "undefined") {
    140    event.deltaX *= dpr;
    141  }
    142  if (typeof event.deltaY !== "undefined") {
    143    event.deltaY *= dpr;
    144  }
    145  if (typeof event.deltaZ !== "undefined") {
    146    event.deltaZ *= dpr;
    147  }
    148 
    149  return new Promise(resolve =>
    150    _getEventUtils(win).synthesizeWheelAtPoint(left, top, event, win, resolve)
    151  );
    152 };
    153 
    154 event.synthesizeMultiTouch = function (opts, win) {
    155  const modifiers = _getEventUtils(win)._parseModifiers(opts);
    156  win.windowUtils.sendTouchEvent(
    157    opts.type,
    158    opts.id,
    159    opts.x,
    160    opts.y,
    161    opts.rx,
    162    opts.ry,
    163    opts.angle,
    164    opts.force,
    165    opts.tiltx,
    166    opts.tilty,
    167    opts.twist,
    168    modifiers
    169  );
    170 };
    171 
    172 /**
    173 * Synthesize a keydown event for a single key.
    174 *
    175 * @param {object} key
    176 *     Key data as returned by keyData.getData
    177 * @param {Window} win
    178 *     Window object.
    179 */
    180 event.sendKeyDown = function (key, win) {
    181  event.sendSingleKey(key, win, "keydown");
    182 };
    183 
    184 /**
    185 * Synthesize a keyup event for a single key.
    186 *
    187 * @param {object} key
    188 *     Key data as returned by keyData.getData
    189 * @param {Window} win
    190 *     Window object.
    191 */
    192 event.sendKeyUp = function (key, win) {
    193  event.sendSingleKey(key, win, "keyup");
    194 };
    195 
    196 /**
    197 * Synthesize a key event for a single key.
    198 *
    199 * @param {object} key
    200 *     Key data as returned by keyData.getData
    201 * @param {Window} win
    202 *     Window object.
    203 * @param {string=} type
    204 *     Event to emit. By default the full keydown/keypressed/keyup event
    205 *     sequence is emitted.
    206 */
    207 event.sendSingleKey = function (key, win, type = null) {
    208  let keyValue = key.key;
    209  if (!key.printable) {
    210    keyValue = `KEY_${keyValue}`;
    211  }
    212  const event = {
    213    code: key.code,
    214    location: key.location,
    215    altKey: key.altKey ?? false,
    216    shiftKey: key.shiftKey ?? false,
    217    ctrlKey: key.ctrlKey ?? false,
    218    metaKey: key.metaKey ?? false,
    219    repeat: key.repeat ?? false,
    220  };
    221  if (type) {
    222    event.type = type;
    223  }
    224  _getEventUtils(win).synthesizeKey(keyValue, event, win);
    225 };
    226 
    227 /**
    228 * Send a string as a series of keypresses.
    229 *
    230 * @param {string} keyString
    231 *     Sequence of characters to send as key presses
    232 * @param {Window} win
    233 *     Window object
    234 */
    235 event.sendKeys = function (keyString, win) {
    236  const modifiers = {};
    237  for (let modifier in event.Modifiers) {
    238    modifiers[modifier] = false;
    239  }
    240 
    241  for (let keyValue of keyString) {
    242    // keyValue will contain enough to represent the UTF-16 encoding of a single abstract character
    243    // i.e. either a single scalar value, or a surrogate pair
    244    if (modifiers.shiftKey) {
    245      keyValue = lazy.keyData.getShiftedKey(keyValue);
    246    }
    247    const data = lazy.keyData.getData(keyValue);
    248    const key = { ...data, ...modifiers };
    249    if (data.modifier) {
    250      // Negating the state of the modifier here is not spec compliant but
    251      // makes us compatible to Chrome's behavior for now. That's fine unless
    252      // we know the correct behavior.
    253      //
    254      // @see: https://github.com/w3c/webdriver/issues/1734
    255      modifiers[data.modifier] = !modifiers[data.modifier];
    256    }
    257    event.sendSingleKey(key, win);
    258  }
    259 };
    260 
    261 event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
    262  opts.canBubble = opts.canBubble || true;
    263 
    264  let doc = el.ownerDocument || el.document;
    265  let ev = doc.createEvent("Event");
    266 
    267  ev.shiftKey = modifiers.shift;
    268  ev.metaKey = modifiers.meta;
    269  ev.altKey = modifiers.alt;
    270  ev.ctrlKey = modifiers.ctrl;
    271 
    272  ev.initEvent(eventType, opts.canBubble, true);
    273  el.dispatchEvent(ev);
    274 };
    275 
    276 event.mouseover = function (el, modifiers = {}, opts = {}) {
    277  return event.sendEvent("mouseover", el, modifiers, opts);
    278 };
    279 
    280 event.mousemove = function (el, modifiers = {}, opts = {}) {
    281  return event.sendEvent("mousemove", el, modifiers, opts);
    282 };
    283 
    284 event.mousedown = function (el, modifiers = {}, opts = {}) {
    285  return event.sendEvent("mousedown", el, modifiers, opts);
    286 };
    287 
    288 event.mouseup = function (el, modifiers = {}, opts = {}) {
    289  return event.sendEvent("mouseup", el, modifiers, opts);
    290 };
    291 
    292 event.cancel = function (el, modifiers = {}, opts = {}) {
    293  return event.sendEvent("cancel", el, modifiers, opts);
    294 };
    295 
    296 event.click = function (el, modifiers = {}, opts = {}) {
    297  return event.sendEvent("click", el, modifiers, opts);
    298 };
    299 
    300 event.change = function (el, modifiers = {}, opts = {}) {
    301  return event.sendEvent("change", el, modifiers, opts);
    302 };
    303 
    304 event.input = function (el, modifiers = {}, opts = {}) {
    305  return event.sendEvent("input", el, modifiers, opts);
    306 };