tor-browser

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

pointerevent_support.js (17716B)


      1 const All_Pointer_Events = [
      2  "pointerdown",
      3  "pointerup",
      4  "pointercancel",
      5  "pointermove",
      6  "pointerover",
      7  "pointerout",
      8  "pointerenter",
      9  "pointerleave",
     10  "gotpointercapture",
     11  "lostpointercapture"
     12 ];
     13 
     14 // https://w3c.github.io/pointerevents/#the-button-property
     15 // Values for the button property, which indicates the device button whose state
     16 // change fired the event.
     17 const ButtonChange = {
     18  NONE: -1,
     19  PEN_CONTACT: 0,
     20  TOUCH_CONTACT: 0,
     21  LEFT_MOUSE: 0,
     22  MIDDLE_MOUSE: 1,
     23  RIGHT_MOUSE: 2,
     24  X1_MOUSE: 3,
     25  X2_MOUSE: 4,
     26  PEN_ERASER_BUTTON: 5
     27 };
     28 
     29 // https://w3c.github.io/pointerevents/#the-buttons-property
     30 // The buttons property gives the current state of the device buttons as a
     31 // bitmask.
     32 const ButtonsBitfield = {
     33  NONE: 0,
     34  PEN_CONTACT: 1,
     35  TOUCH_CONTACT: 1,
     36  LEFT_MOUSE: 1,
     37  RIGHT_MOUSE: 2,
     38  PEN_BARREL_BUTTON: 2,
     39  MIDDLE_MOUSE: 4,
     40  X1_MOUSE: 8,
     41  X2_MOUSE: 16,
     42  PEN_ERASER_BUTTON: 32
     43 };
     44 
     45 // Check for conformance to PointerEvent interface
     46 // https://w3c.github.io/pointerevents/#pointerevent-interface
     47 function check_PointerEvent(event, testNamePrefix, standardAttrs = true) {
     48  if (testNamePrefix === undefined)
     49    testNamePrefix = "";
     50 
     51  // Use expectedPointerType if set otherwise just use the incoming event pointerType in the test name.
     52  var pointerTestName = (testNamePrefix ? testNamePrefix + ' ' : '')
     53    + (expectedPointerType == null ? event.pointerType : expectedPointerType) + ' ' + event.type;
     54 
     55  if (standardAttrs) {
     56    if (expectedPointerType != null) {
     57      test(function () {
     58        assert_equals(event.pointerType, expectedPointerType);
     59      }, pointerTestName + ".pointerType is correct.");
     60    }
     61 
     62    test(function () {
     63      assert_true(event instanceof event.target.ownerDocument.defaultView.PointerEvent);
     64    }, pointerTestName + " event is a PointerEvent event");
     65  }
     66 
     67  // Check attributes for conformance to WebIDL (existence, type, being readable).
     68  var idl_type_check = {
     69    "long": function (v) { return typeof v === "number" && Math.round(v) === v; },
     70    "float": function (v) { return typeof v === "number"; },
     71    "string": function (v) { return typeof v === "string"; },
     72    "boolean": function (v) { return typeof v === "boolean" },
     73    "object": function (v) { return typeof v === "object" }
     74  };
     75 
     76  // Check values for inherited attributes.
     77  // https://w3c.github.io/pointerevents/#attributes-and-default-actions
     78  if (!standardAttrs) {
     79    test(function () {
     80      assert_implements_optional("fromElement" in event);
     81      assert_equals(event.fromElement, null);
     82    }, pointerTestName + ".fromElement value is null");
     83    test(function () {
     84      assert_implements_optional("toElement" in event);
     85      assert_equals(event.toElement, null);
     86    }, pointerTestName + ".toElement value is null");
     87  } else {
     88    test(function () {
     89      assert_equals(event.isTrusted, true);
     90    }, pointerTestName + ".isTrusted value is true");
     91    test(function () {
     92      let expected = (event.type != 'pointerenter' && event.type != 'pointerleave');
     93      assert_equals(event.composed, expected);
     94    }, pointerTestName + ".composed value is valid");
     95    test(function () {
     96      let expected = (event.type != 'pointerenter' && event.type != 'pointerleave');
     97      assert_equals(event.bubbles, expected);
     98    }, pointerTestName + ".bubbles value is valid");
     99    test(function () {
    100      let cancelable_events = [
    101        'pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'
    102      ];
    103      assert_equals(event.cancelable, cancelable_events.includes(event.type));
    104    }, pointerTestName + ".cancelable value is valid");
    105 
    106    // Check the pressure value.
    107    // https://w3c.github.io/pointerevents/#dom-pointerevent-pressure
    108    test(function () {
    109      assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0");
    110      assert_less_than_equal(event.pressure, 1, "pressure is less than or equal to 1");
    111 
    112      if (event.buttons === 0) {
    113        assert_equals(event.pressure, 0, "pressure is 0 with no buttons pressed");
    114      } else {
    115        assert_greater_than(event.pressure, 0, "pressure is greater than 0 with a button pressed");
    116        if (event.pointerType === "mouse") {
    117          assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed");
    118        }
    119      }
    120    }, pointerTestName + ".pressure value is valid");
    121 
    122    // Check mouse-specific properties.
    123    if (event.pointerType === "mouse") {
    124      test(function () {
    125        assert_equals(event.width, 1, "width of mouse should be 1");
    126        assert_equals(event.height, 1, "height of mouse should be 1");
    127        assert_equals(event.tiltX, 0, event.type + ".tiltX is 0 for mouse");
    128        assert_equals(event.tiltY, 0, event.type + ".tiltY is 0 for mouse");
    129        assert_true(event.isPrimary, event.type + ".isPrimary is true for mouse");
    130      }, pointerTestName + " properties for pointerType = mouse");
    131    }
    132 
    133    // Check "pointerup" specific properties.
    134    if (event.type == "pointerup") {
    135      test(function () {
    136        assert_equals(event.width, 1, "width of pointerup should be 1");
    137        assert_equals(event.height, 1, "height of pointerup should be 1");
    138      }, pointerTestName + " properties for pointerup");
    139    }
    140  }
    141 }
    142 
    143 function showPointerTypes() {
    144  var complete_notice = document.getElementById("complete-notice");
    145  var pointertype_log = document.getElementById("pointertype-log");
    146  var pointertypes = Object.keys(detected_pointertypes);
    147  pointertype_log.innerHTML = pointertypes.length ?
    148    pointertypes.join(",") : "(none)";
    149  complete_notice.style.display = "block";
    150 }
    151 
    152 function showLoggedEvents() {
    153  var event_log_elem = document.getElementById("event-log");
    154  event_log_elem.innerHTML = event_log.length ? event_log.join(", ") : "(none)";
    155 
    156  var complete_notice = document.getElementById("complete-notice");
    157  complete_notice.style.display = "block";
    158 }
    159 
    160 function failOnScroll() {
    161  assert_true(false,
    162  "scroll received while shouldn't");
    163 }
    164 
    165 function updateDescriptionNextStep() {
    166  document.getElementById('desc').innerHTML = "Test Description: Try to scroll text RIGHT.";
    167 }
    168 
    169 function updateDescriptionComplete() {
    170  document.getElementById('desc').innerHTML = "Test Description: Test complete";
    171 }
    172 
    173 function objectScroller(target, direction, value) {
    174  if (direction == 'up') {
    175    target.scrollTop = 0;
    176  } else if (direction == 'left') {
    177    target.scrollLeft = 0;
    178  }
    179 }
    180 
    181 function sPointerCapture(e) {
    182  try {
    183    target0.setPointerCapture(e.pointerId);
    184  }
    185  catch(e) {
    186  }
    187 }
    188 
    189 function rPointerCapture(e) {
    190  try {
    191    captureButton.value = 'Set Capture';
    192    target0.releasePointerCapture(e.pointerId);
    193  }
    194  catch(e) {
    195  }
    196 }
    197 
    198 var globalPointerEventTest = null;
    199 var expectedPointerType = null;
    200 const ALL_POINTERS = ['mouse', 'touch', 'pen'];
    201 
    202 function MultiPointerTypeTest(testName, types) {
    203  this.testName = testName;
    204  this.types = types;
    205  this.currentTypeIndex = 0;
    206  this.currentTest = null;
    207  this.createNextTest();
    208 }
    209 
    210 MultiPointerTypeTest.prototype.step = function(op) {
    211  this.currentTest.step(op);
    212 }
    213 
    214 MultiPointerTypeTest.prototype.skip = function() {
    215  var prevTest = this.currentTest;
    216  this.createNextTest();
    217  prevTest.timeout();
    218 }
    219 
    220 MultiPointerTypeTest.prototype.done = function() {
    221  if (this.currentTest.status != 1) {
    222    var prevTest = this.currentTest;
    223    this.createNextTest();
    224    if (prevTest != null)
    225      prevTest.done();
    226  }
    227 }
    228 
    229 MultiPointerTypeTest.prototype.step = function(stepFunction) {
    230  this.currentTest.step(stepFunction);
    231 }
    232 
    233 MultiPointerTypeTest.prototype.createNextTest = function() {
    234  if (this.currentTypeIndex < this.types.length) {
    235    var pointerTypeDescription = document.getElementById('pointerTypeDescription');
    236    document.getElementById('pointerTypeDescription').innerHTML = "Follow the test instructions with <span style='color: red'>" + this.types[this.currentTypeIndex] + "</span>. If you don't have the device <a href='javascript:;' onclick='globalPointerEventTest.skip()'>skip it</a>.";
    237    this.currentTest = async_test(this.types[this.currentTypeIndex] + ' ' + this.testName);
    238    expectedPointerType = this.types[this.currentTypeIndex];
    239    this.currentTypeIndex++;
    240  } else {
    241    document.getElementById('pointerTypeDescription').innerHTML = "";
    242  }
    243  resetTestState();
    244 }
    245 
    246 function setup_pointerevent_test(testName, supportedPointerTypes) {
    247  return globalPointerEventTest = new MultiPointerTypeTest(testName, supportedPointerTypes);
    248 }
    249 
    250 function checkPointerEventType(event) {
    251  assert_equals(event.pointerType, expectedPointerType, "pointerType should be the same as the requested device.");
    252 }
    253 
    254 function touchScrollInTarget(target, direction) {
    255  var x_delta = 0;
    256  var y_delta = 0;
    257  if (direction == "down") {
    258    x_delta = 0;
    259    y_delta = -10;
    260  } else if (direction == "up") {
    261    x_delta = 0;
    262    y_delta = 10;
    263  } else if (direction == "right") {
    264    x_delta = -10;
    265    y_delta = 0;
    266  } else if (direction == "left") {
    267    x_delta = 10;
    268    y_delta = 0;
    269  } else {
    270    throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'");
    271  }
    272  return new test_driver.Actions()
    273    .addPointer("touchPointer1", "touch")
    274    .pointerMove(0, 0, {origin: target})
    275    .pointerDown()
    276    .pointerMove(x_delta, y_delta, {origin: target})
    277    .pointerMove(2 * x_delta, 2 * y_delta, {origin: target})
    278    .pointerMove(3 * x_delta, 3 * y_delta, {origin: target})
    279    .pointerMove(4 * x_delta, 4 * y_delta, {origin: target})
    280    .pointerMove(5 * x_delta, 5 * y_delta, {origin: target})
    281    .pointerMove(6 * x_delta, 6 * y_delta, {origin: target})
    282    .pause(100)
    283    .pointerUp()
    284    .send();
    285 }
    286 
    287 function clickInTarget(pointerType, target) {
    288  var pointerId = pointerType + "Pointer1";
    289  return new test_driver.Actions()
    290    .addPointer(pointerId, pointerType)
    291    .pointerMove(0, 0, {origin: target})
    292    .pointerDown()
    293    .pointerUp()
    294    .send();
    295 }
    296 
    297 function rightClickInTarget(pointerType, target) {
    298  let pointerId = pointerType + "Pointer1";
    299  let actions = new test_driver.Actions();
    300  return actions.addPointer(pointerId, pointerType)
    301    .pointerMove(0, 0, {origin: target})
    302    .pointerDown({button:actions.ButtonType.RIGHT})
    303    .pointerUp({button:actions.ButtonType.RIGHT})
    304    .send();
    305 }
    306 
    307 function twoFingerDrag(target) {
    308  return new test_driver.Actions()
    309    .addPointer("touchPointer1", "touch")
    310    .addPointer("touchPointer2", "touch")
    311    .pointerMove(0, 0, { origin: target, sourceName: "touchPointer1" })
    312    .pointerMove(10, 0, { origin: target, sourceName: "touchPointer2" })
    313    .pointerDown({ sourceName: "touchPointer1" })
    314    .pointerDown({ sourceName: "touchPointer2" })
    315    .pointerMove(0, 10, { origin: target, sourceName: "touchPointer1" })
    316    .pointerMove(10, 10, { origin: target, sourceName: "touchPointer2" })
    317    .pointerMove(0, 20, { origin: target, sourceName: "touchPointer1" })
    318    .pointerMove(10, 20, { origin: target, sourceName: "touchPointer2" })
    319    .pause(100)
    320    .pointerUp({ sourceName: "touchPointer1" })
    321    .pointerUp({ sourceName: "touchPointer2" })
    322    .send();
    323 }
    324 
    325 function pointerDragInTarget(pointerType, target, direction) {
    326  var x_delta = 0;
    327  var y_delta = 0;
    328  if (direction == "down") {
    329    x_delta = 0;
    330    y_delta = 10;
    331  } else if (direction == "up") {
    332    x_delta = 0;
    333    y_delta = -10;
    334  } else if (direction == "right") {
    335    x_delta = 10;
    336    y_delta = 0;
    337  } else if (direction == "left") {
    338    x_delta = -10;
    339    y_delta = 0;
    340  } else {
    341    throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'");
    342  }
    343  var pointerId = pointerType + "Pointer1";
    344  return new test_driver.Actions()
    345    .addPointer(pointerId, pointerType)
    346    .pointerMove(0, 0, {origin: target})
    347    .pointerDown()
    348    .pointerMove(x_delta, y_delta, {origin: target})
    349    .pointerMove(2 * x_delta, 2 * y_delta, {origin: target})
    350    .pointerMove(3 * x_delta, 3 * y_delta, {origin: target})
    351    .pointerUp()
    352    .send();
    353 }
    354 
    355 function pointerHoverInTarget(pointerType, target, direction) {
    356  var x_delta = 0;
    357  var y_delta = 0;
    358  if (direction == "down") {
    359    x_delta = 0;
    360    y_delta = 10;
    361  } else if (direction == "up") {
    362    x_delta = 0;
    363    y_delta = -10;
    364  } else if (direction == "right") {
    365    x_delta = 10;
    366    y_delta = 0;
    367  } else if (direction == "left") {
    368    x_delta = -10;
    369    y_delta = 0;
    370  } else {
    371    throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'");
    372  }
    373  var pointerId = pointerType + "Pointer1";
    374  return new test_driver.Actions()
    375    .addPointer(pointerId, pointerType)
    376    .pointerMove(0, 0, {origin: target})
    377    .pointerMove(x_delta, y_delta, {origin: target})
    378    .pointerMove(2 * x_delta, 2 * y_delta, {origin: target})
    379    .pointerMove(3 * x_delta, 3 * y_delta, {origin: target})
    380    .send();
    381 }
    382 
    383 function moveToDocument(pointerType) {
    384  var pointerId = pointerType + "Pointer1";
    385  return new test_driver.Actions()
    386    .addPointer(pointerId, pointerType)
    387    // WebDriver initializes the pointer position (0, 0), therefore, we need
    388    // to move different position first.  Otherwise, moving to (0, 0) may be
    389    // ignored.
    390    .pointerMove(1, 1)
    391    .pointerMove(0, 0)
    392    .send();
    393 }
    394 
    395 // Returns a promise that only gets resolved when the condition is met.
    396 function resolveWhen(condition) {
    397  return new Promise((resolve, reject) => {
    398    function tick() {
    399      if (condition())
    400        resolve();
    401      else
    402        requestAnimationFrame(tick.bind(this));
    403    }
    404    tick();
    405  });
    406 }
    407 
    408 // Returns a promise that only gets resolved after n animation frames
    409 function waitForAnimationFrames(n) {
    410  let p = 0;
    411  function next() {
    412    p++;
    413    return p === n;
    414  }
    415  return resolveWhen(next);
    416 }
    417 
    418 function isPointerEvent(eventName) {
    419  return All_Pointer_Events.includes(eventName);
    420 }
    421 
    422 function isMouseEvent(eventName) {
    423  return ["mousedown", "mouseup", "mousemove", "mouseover",
    424    "mouseenter", "mouseout", "mouseleave",
    425    "click", "contextmenu", "dblclick"
    426  ].includes(eventName);
    427 }
    428 
    429 // Events is a list of events fired at a target.
    430 //
    431 // Checks to see if each pointer event has a corresponding mouse event in the
    432 // event array and the two events are in the proper order (pointer event is
    433 // first).
    434 //
    435 // See https://w3c.github.io/pointerevents/#mapping-for-devices-that-support-hover
    436 function arePointerEventsBeforeCompatMouseEvents(events) {
    437  function arePointerAndMouseEventCompatible(pointerEventName, mouseEventName) {
    438    return pointerEventName.startsWith("pointer")
    439      && mouseEventName.startsWith("mouse")
    440      && pointerEventName.substring(7) === mouseEventName.substring(5);
    441  }
    442 
    443  function arePointerAndMouseEventInProperOrder(pointerEventIndex, mouseEventIndex, events) {
    444    return (pointerEventIndex < mouseEventIndex && isPointerEvent(events[pointerEventIndex]) && isMouseEvent(events[mouseEventIndex])
    445      && arePointerAndMouseEventCompatible(events[pointerEventIndex], events[mouseEventIndex]));
    446  }
    447 
    448  let currentPointerEventIndex = events.findIndex((event) => isPointerEvent(event));
    449  let currentMouseEventIndex = events.findIndex((event) => isMouseEvent(event));
    450 
    451  while (1) {
    452    if (currentMouseEventIndex < 0 && currentPointerEventIndex < 0)
    453      return true;
    454    if (currentMouseEventIndex < 0 || currentPointerEventIndex < 0)
    455      return false;
    456    if (!arePointerAndMouseEventInProperOrder(currentPointerEventIndex, currentMouseEventIndex, events))
    457      return false;
    458 
    459    let pointerIdx = events.slice(currentPointerEventIndex + 1).findIndex(isPointerEvent);
    460    let mouseIdx = events.slice(currentMouseEventIndex + 1).findIndex(isMouseEvent);
    461 
    462    currentPointerEventIndex = (pointerIdx < 0) ? pointerIdx : (currentPointerEventIndex + 1 + pointerIdx);
    463    currentMouseEventIndex = (mouseIdx < 0) ? mouseIdx : (currentMouseEventIndex + 1 + mouseIdx);
    464  }
    465 
    466  return true;
    467 }
    468 
    469 // Returns a |Promise| that gets resolved with the event object when |target|
    470 // receives an event of type |event_type|.
    471 //
    472 // The optional |test| parameter adds event handler cleanup for the case |test|
    473 // terminates before the event is received.
    474 function getEvent(event_type, target, test) {
    475  return new Promise(resolve => {
    476    const listener = e => resolve(e);
    477    target.addEventListener(event_type, listener, { once: true });
    478    if (test) {
    479      test.add_cleanup(() =>
    480          target.removeEventListener(event_type, listener, { once: true }));
    481    }
    482  });
    483 }
    484 
    485 // Returns a |Promise| that gets resolved with |event.data| when |window|
    486 // receives from |source| a "message" event whose |event.data.type| matches the
    487 // string |message_data_type|.
    488 //
    489 // The optional |test| parameter adds event handler cleanup for the case |test|
    490 // terminates before a matching event is received.
    491 function getMessageData(message_data_type, source, test) {
    492  return new Promise(resolve => {
    493    const listener = e => {
    494      if (e.source != source || !e.data || e.data.type != message_data_type)
    495        return;
    496      window.removeEventListener("message", listener);
    497      resolve(e.data);
    498    }
    499 
    500    window.addEventListener("message", listener);
    501    if (test) {
    502      test.add_cleanup(() =>
    503          window.removeEventListener("message", listener));
    504    }
    505  });
    506 }
    507 
    508 // The optional |test| parameter adds event handler cleanup for the case |test|
    509 // terminates before the event is received.
    510 function preventDefaultPointerdownOnce(target, test) {
    511  return new Promise((resolve) => {
    512    const listener = e => {
    513      e.preventDefault();
    514      resolve();
    515    }
    516 
    517    target.addEventListener("pointerdown", listener, { once: true });
    518    if (test) {
    519      test.add_cleanup(() =>
    520          target.removeEventListener("pointerdown", listener, { once: true }));
    521    }
    522  });
    523 }