tor-browser

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

test_Actions.js (22880B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { actions, CLICK_INTERVAL, ClickTracker } = ChromeUtils.importESModule(
      8  "chrome://remote/content/shared/webdriver/Actions.sys.mjs"
      9 );
     10 
     11 const { setTimeout } = ChromeUtils.importESModule(
     12  "resource://gre/modules/Timer.sys.mjs"
     13 );
     14 
     15 const XHTMLNS = "http://www.w3.org/1999/xhtml";
     16 
     17 const domEl = {
     18  nodeType: 1,
     19  ELEMENT_NODE: 1,
     20  namespaceURI: XHTMLNS,
     21 };
     22 
     23 add_task(function test_createInputState() {
     24  for (let type of ["none", "key", "pointer" /*"wheel"*/]) {
     25    const state = new actions.State();
     26    const id = "device";
     27    const actionSequence = {
     28      type,
     29      id,
     30      actions: [],
     31    };
     32    actions.Chain.fromJSON(state, [actionSequence]);
     33    equal(state.inputStateMap.size, 1);
     34    equal(state.inputStateMap.get(id).constructor.type, type);
     35  }
     36 });
     37 
     38 add_task(async function test_defaultPointerParameters() {
     39  let state = new actions.State();
     40  const inputTickActions = [
     41    { type: "pointer", subtype: "pointerDown", button: 0 },
     42  ];
     43  const chain = await actions.Chain.fromJSON(
     44    state,
     45    chainForTick(inputTickActions)
     46  );
     47  const pointerAction = chain[0][0];
     48  equal(
     49    state.getInputSource(pointerAction.id).pointer.constructor.type,
     50    "mouse"
     51  );
     52 });
     53 
     54 add_task(async function test_processPointerParameters() {
     55  for (let subtype of ["pointerDown", "pointerUp"]) {
     56    for (let pointerType of [2, true, {}, []]) {
     57      const inputTickActions = [
     58        {
     59          type: "pointer",
     60          parameters: { pointerType },
     61          subtype,
     62          button: 0,
     63        },
     64      ];
     65      let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
     66      await checkFromJSONErrors(
     67        inputTickActions,
     68        /Expected "pointerType" to be a string/,
     69        message
     70      );
     71    }
     72 
     73    for (let pointerType of ["", "foo"]) {
     74      const inputTickActions = [
     75        {
     76          type: "pointer",
     77          parameters: { pointerType },
     78          subtype,
     79          button: 0,
     80        },
     81      ];
     82      let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
     83      await checkFromJSONErrors(
     84        inputTickActions,
     85        /Expected "pointerType" to be one of/,
     86        message
     87      );
     88    }
     89  }
     90 
     91  for (let pointerType of ["mouse" /*"touch"*/]) {
     92    let state = new actions.State();
     93    const inputTickActions = [
     94      {
     95        type: "pointer",
     96        parameters: { pointerType },
     97        subtype: "pointerDown",
     98        button: 0,
     99      },
    100    ];
    101    const chain = await actions.Chain.fromJSON(
    102      state,
    103      chainForTick(inputTickActions)
    104    );
    105    const pointerAction = chain[0][0];
    106    equal(
    107      state.getInputSource(pointerAction.id).pointer.constructor.type,
    108      pointerType
    109    );
    110  }
    111 });
    112 
    113 add_task(async function test_processPointerDownAction() {
    114  for (let button of [-1, "a"]) {
    115    const inputTickActions = [
    116      { type: "pointer", subtype: "pointerDown", button },
    117    ];
    118    await checkFromJSONErrors(
    119      inputTickActions,
    120      /Expected "button" to be a positive integer/,
    121      `pointerDown with {button: ${button}}`
    122    );
    123  }
    124  let state = new actions.State();
    125  const inputTickActions = [
    126    { type: "pointer", subtype: "pointerDown", button: 5 },
    127  ];
    128  const chain = await actions.Chain.fromJSON(
    129    state,
    130    chainForTick(inputTickActions)
    131  );
    132  equal(chain[0][0].button, 5);
    133 });
    134 
    135 add_task(async function test_validateActionDurationAndCoordinates() {
    136  for (let [type, subtype] of [
    137    ["none", "pause"],
    138    ["pointer", "pointerMove"],
    139  ]) {
    140    for (let duration of [-1, "a"]) {
    141      const inputTickActions = [{ type, subtype, duration }];
    142      await checkFromJSONErrors(
    143        inputTickActions,
    144        /Expected "duration" to be a positive integer/,
    145        `{subtype} with {duration: ${duration}}`
    146      );
    147    }
    148  }
    149  for (let name of ["x", "y"]) {
    150    const actionItem = {
    151      type: "pointer",
    152      subtype: "pointerMove",
    153      duration: 5000,
    154    };
    155    actionItem[name] = "a";
    156    await checkFromJSONErrors(
    157      [actionItem],
    158      /Expected "." to be a finite number/,
    159      `${name}: "a", subtype: pointerMove`
    160    );
    161  }
    162 });
    163 
    164 add_task(async function test_processPointerMoveActionOriginStringValidation() {
    165  for (let origin of ["", "viewports", "pointers"]) {
    166    const inputTickActions = [
    167      {
    168        type: "pointer",
    169        x: 0,
    170        y: 0,
    171        duration: 5000,
    172        subtype: "pointerMove",
    173        origin,
    174      },
    175    ];
    176    await checkFromJSONErrors(
    177      inputTickActions,
    178      /Expected "origin" to be undefined, "viewport", "pointer", or an element/,
    179      `actionItem.origin: ${origin}`,
    180      { isElementOrigin: () => false }
    181    );
    182  }
    183 });
    184 
    185 add_task(async function test_processPointerMoveActionOriginElementValidation() {
    186  const element = { foo: "bar" };
    187  const inputTickActions = [
    188    {
    189      type: "pointer",
    190      x: 0,
    191      y: 0,
    192      duration: 5000,
    193      subtype: "pointerMove",
    194      origin: element,
    195    },
    196  ];
    197 
    198  // invalid element origin
    199  await checkFromJSONErrors(
    200    inputTickActions,
    201    /Expected "origin" to be undefined, "viewport", "pointer", or an element/,
    202    `actionItem.origin: (${getTypeString(element)})`,
    203    { isElementOrigin: elem => "foo1" in elem }
    204  );
    205 
    206  let state = new actions.State();
    207  const actionsOptions = {
    208    isElementOrigin: elem => "foo" in elem,
    209    getElementOrigin: elem => elem,
    210  };
    211 
    212  // valid element origin
    213  const chain = await actions.Chain.fromJSON(
    214    state,
    215    chainForTick(inputTickActions),
    216    actionsOptions
    217  );
    218  deepEqual(chain[0][0].origin, { element });
    219 });
    220 
    221 add_task(async function test_processPointerMoveActionDefaultOrigin() {
    222  let state = new actions.State();
    223  const inputTickActions = [
    224    { type: "pointer", x: 0, y: 0, duration: 5000, subtype: "pointerMove" },
    225  ];
    226  const chain = await actions.Chain.fromJSON(
    227    state,
    228    chainForTick(inputTickActions),
    229    {}
    230  );
    231  // The default is viewport coordinates which have an origin at [0,0] and don't depend on inputSource
    232  deepEqual(chain[0][0].origin.getOriginCoordinates(null, null), {
    233    x: 0,
    234    y: 0,
    235  });
    236 });
    237 
    238 add_task(async function test_processPointerMoveAction() {
    239  let state = new actions.State();
    240  const actionItems = [
    241    {
    242      duration: 5000,
    243      type: "pointerMove",
    244      origin: undefined,
    245      x: 0,
    246      y: 0,
    247    },
    248    {
    249      duration: undefined,
    250      type: "pointerMove",
    251      origin: domEl,
    252      x: 0,
    253      y: 0,
    254    },
    255    {
    256      duration: 5000,
    257      type: "pointerMove",
    258      x: 1,
    259      y: 2,
    260      origin: undefined,
    261    },
    262  ];
    263  const actionSequence = {
    264    id: "some_id",
    265    type: "pointer",
    266    actions: actionItems,
    267  };
    268  let actionsOptions = {
    269    isElementOrigin: elem => elem == domEl,
    270    getElementOrigin: elem => elem,
    271  };
    272 
    273  let chain = await actions.Chain.fromJSON(
    274    state,
    275    [actionSequence],
    276    actionsOptions
    277  );
    278  equal(chain.length, actionItems.length);
    279  for (let i = 0; i < actionItems.length; i++) {
    280    let actual = chain[i][0];
    281    let expected = actionItems[i];
    282    equal(actual.duration, expected.duration);
    283    equal(actual.x, expected.x);
    284    equal(actual.y, expected.y);
    285 
    286    let originClass;
    287    if (expected.origin === undefined || expected.origin == "viewport") {
    288      originClass = "ViewportOrigin";
    289    } else if (expected.origin === "pointer") {
    290      originClass = "PointerOrigin";
    291    } else {
    292      originClass = "ElementOrigin";
    293    }
    294    deepEqual(actual.origin.constructor.name, originClass);
    295  }
    296 });
    297 
    298 add_task(async function test_computePointerDestinationViewport() {
    299  const state = new actions.State();
    300  const inputTickActions = [
    301    {
    302      type: "pointer",
    303      subtype: "pointerMove",
    304      x: 100,
    305      y: 200,
    306      origin: "viewport",
    307    },
    308  ];
    309  const chain = await actions.Chain.fromJSON(
    310    state,
    311    chainForTick(inputTickActions),
    312    {}
    313  );
    314  const actionItem = chain[0][0];
    315  const inputSource = state.getInputSource(actionItem.id);
    316  // these values should not affect the outcome
    317  inputSource.moveTo(99, 10);
    318  const target = await actionItem.origin.getTargetCoordinates(
    319    inputSource,
    320    [actionItem.x, actionItem.y],
    321    null
    322  );
    323  equal(actionItem.x, target[0]);
    324  equal(actionItem.y, target[1]);
    325 });
    326 
    327 add_task(async function test_computePointerDestinationPointer() {
    328  const state = new actions.State();
    329  const inputTickActions = [
    330    {
    331      type: "pointer",
    332      subtype: "pointerMove",
    333      x: 100,
    334      y: 200,
    335      origin: "pointer",
    336    },
    337  ];
    338  const chain = await actions.Chain.fromJSON(
    339    state,
    340    chainForTick(inputTickActions),
    341    {}
    342  );
    343  const actionItem = chain[0][0];
    344  const inputSource = state.getInputSource(actionItem.id);
    345  inputSource.moveTo(10, 99);
    346  const target = await actionItem.origin.getTargetCoordinates(
    347    inputSource,
    348    [actionItem.x, actionItem.y],
    349    null
    350  );
    351  equal(actionItem.x + inputSource.x, target[0]);
    352  equal(actionItem.y + inputSource.y, target[1]);
    353 });
    354 
    355 add_task(async function test_processPointerAction() {
    356  for (let pointerType of ["mouse", "touch"]) {
    357    const actionItems = [
    358      {
    359        duration: 2000,
    360        type: "pause",
    361      },
    362      {
    363        type: "pointerMove",
    364        duration: 2000,
    365        x: 0,
    366        y: 0,
    367      },
    368      {
    369        type: "pointerUp",
    370        button: 1,
    371      },
    372    ];
    373    let actionSequence = {
    374      type: "pointer",
    375      id: "some_id",
    376      parameters: {
    377        pointerType,
    378      },
    379      actions: actionItems,
    380    };
    381    const state = new actions.State();
    382    const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    383    equal(chain.length, actionItems.length);
    384    for (let i = 0; i < actionItems.length; i++) {
    385      const actual = chain[i][0];
    386      const expected = actionItems[i];
    387      equal(actual.type, expected.type === "pause" ? "none" : "pointer");
    388      equal(actual.subtype, expected.type);
    389      equal(actual.id, actionSequence.id);
    390      if (expected.type === "pointerUp") {
    391        equal(actual.button, expected.button);
    392      } else {
    393        equal(actual.duration, expected.duration);
    394      }
    395      if (expected.type !== "pause") {
    396        equal(
    397          state.getInputSource(actual.id).pointer.constructor.type,
    398          pointerType
    399        );
    400      }
    401    }
    402  }
    403 });
    404 
    405 add_task(async function test_processPauseAction() {
    406  for (let type of ["none", "key", "pointer"]) {
    407    const state = new actions.State();
    408    const actionSequence = {
    409      type,
    410      id: "some_id",
    411      actions: [{ type: "pause", duration: 5000 }],
    412    };
    413    const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    414    const actionItem = chain[0][0];
    415    equal(actionItem.type, "none");
    416    equal(actionItem.subtype, "pause");
    417    equal(actionItem.id, "some_id");
    418    equal(actionItem.duration, 5000);
    419  }
    420  const state = new actions.State();
    421  const actionSequence = {
    422    type: "none",
    423    id: "some_id",
    424    actions: [{ type: "pause" }],
    425  };
    426  const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    427  const actionItem = chain[0][0];
    428  equal(actionItem.duration, undefined);
    429 });
    430 
    431 add_task(async function test_processActionSubtypeValidation() {
    432  for (let type of ["none", "key", "pointer"]) {
    433    const message = `type: ${type}, subtype: dancing`;
    434    const inputTickActions = [{ type, subtype: "dancing" }];
    435    await checkFromJSONErrors(
    436      inputTickActions,
    437      new RegExp(`Expected known subtype for type`),
    438      message
    439    );
    440  }
    441 });
    442 
    443 add_task(async function test_processKeyActionDown() {
    444  for (let value of [-1, undefined, [], ["a"], { length: 1 }, null]) {
    445    const inputTickActions = [{ type: "key", subtype: "keyDown", value }];
    446    const message = `actionItem.value: (${getTypeString(value)})`;
    447    await checkFromJSONErrors(
    448      inputTickActions,
    449      /Expected "value" to be a string that represents single code point/,
    450      message
    451    );
    452  }
    453 
    454  const state = new actions.State();
    455  const actionSequence = {
    456    type: "key",
    457    id: "keyboard",
    458    actions: [{ type: "keyDown", value: "\uE004" }],
    459  };
    460  const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    461  const actionItem = chain[0][0];
    462 
    463  equal(actionItem.type, "key");
    464  equal(actionItem.id, "keyboard");
    465  equal(actionItem.subtype, "keyDown");
    466  equal(actionItem.value, "\ue004");
    467 });
    468 
    469 add_task(async function test_processInputSourceActionSequenceValidation() {
    470  await checkFromJSONErrors(
    471    [{ type: "swim", subtype: "pause", id: "some id" }],
    472    /Expected known action type/,
    473    "actionSequence type: swim"
    474  );
    475 
    476  await checkFromJSONErrors(
    477    [{ type: "none", subtype: "pause", id: -1 }],
    478    /Expected "id" to be a string/,
    479    "actionSequence id: -1"
    480  );
    481 
    482  await checkFromJSONErrors(
    483    [{ type: "none", subtype: "pause", id: undefined }],
    484    /Expected "id" to be a string/,
    485    "actionSequence id: undefined"
    486  );
    487 
    488  const state = new actions.State();
    489  const actionSequence = [
    490    { type: "none", subtype: "pause", id: "some_id", actions: -1 },
    491  ];
    492  const errorRegex = /Expected "actionSequence.actions" to be an array/;
    493  const message = "actionSequence actions: -1";
    494 
    495  await Assert.rejects(
    496    actions.Chain.fromJSON(state, actionSequence, {}),
    497    /InvalidArgumentError/,
    498    message
    499  );
    500  await Assert.rejects(
    501    actions.Chain.fromJSON(state, actionSequence, {}),
    502    errorRegex,
    503    message
    504  );
    505 });
    506 
    507 add_task(async function test_processInputSourceActionSequence() {
    508  const state = new actions.State();
    509  const actionItem = { type: "pause", duration: 5 };
    510  const actionSequence = {
    511    type: "none",
    512    id: "some id",
    513    actions: [actionItem],
    514  };
    515  const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    516  equal(chain.length, 1);
    517  const tickActions = chain[0];
    518  equal(tickActions.length, 1);
    519  equal(tickActions[0].type, "none");
    520  equal(tickActions[0].subtype, "pause");
    521  equal(tickActions[0].duration, 5);
    522  equal(tickActions[0].id, "some id");
    523 });
    524 
    525 add_task(async function test_processInputSourceActionSequencePointer() {
    526  const state = new actions.State();
    527  const actionItem = { type: "pointerDown", button: 1 };
    528  const actionSequence = {
    529    type: "pointer",
    530    id: "9",
    531    actions: [actionItem],
    532    parameters: {
    533      pointerType: "mouse", // TODO "pen"
    534    },
    535  };
    536  const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    537  equal(chain.length, 1);
    538  const tickActions = chain[0];
    539  equal(tickActions.length, 1);
    540  equal(tickActions[0].type, "pointer");
    541  equal(tickActions[0].subtype, "pointerDown");
    542  equal(tickActions[0].button, 1);
    543  equal(tickActions[0].id, "9");
    544  const inputSource = state.getInputSource(tickActions[0].id);
    545  equal(inputSource.constructor.type, "pointer");
    546  equal(inputSource.pointer.constructor.type, "mouse");
    547 });
    548 
    549 add_task(async function test_processInputSourceActionSequenceKey() {
    550  const state = new actions.State();
    551  const actionItem = { type: "keyUp", value: "a" };
    552  const actionSequence = {
    553    type: "key",
    554    id: "9",
    555    actions: [actionItem],
    556  };
    557  const chain = await actions.Chain.fromJSON(state, [actionSequence], {});
    558  equal(chain.length, 1);
    559  const tickActions = chain[0];
    560  equal(tickActions.length, 1);
    561  equal(tickActions[0].type, "key");
    562  equal(tickActions[0].subtype, "keyUp");
    563  equal(tickActions[0].value, "a");
    564  equal(tickActions[0].id, "9");
    565 });
    566 
    567 add_task(async function test_processInputSourceActionSequenceInputStateMap() {
    568  const state = new actions.State();
    569  const id = "1";
    570  const actionItem = { type: "pause", duration: 5000 };
    571  const actionSequence = {
    572    type: "key",
    573    id,
    574    actions: [actionItem],
    575  };
    576  await actions.Chain.fromJSON(state, [actionSequence], {});
    577  equal(state.inputStateMap.size, 1);
    578  equal(state.inputStateMap.get(id).constructor.type, "key");
    579 
    580  // Construct a different state with the same input id
    581  const state1 = new actions.State();
    582  const actionItem1 = { type: "pointerDown", button: 0 };
    583  const actionSequence1 = {
    584    type: "pointer",
    585    id,
    586    actions: [actionItem1],
    587  };
    588  await actions.Chain.fromJSON(state1, [actionSequence1], {});
    589  equal(state1.inputStateMap.size, 1);
    590 
    591  // Overwrite the state in the initial map with one of a different type
    592  state.inputStateMap.set(id, state1.inputStateMap.get(id));
    593  equal(state.inputStateMap.get(id).constructor.type, "pointer");
    594 
    595  const message = "Wrong state for input id type";
    596  await Assert.rejects(
    597    actions.Chain.fromJSON(state, [actionSequence]),
    598    /InvalidArgumentError/,
    599    message
    600  );
    601  await Assert.rejects(
    602    actions.Chain.fromJSON(state, [actionSequence]),
    603    /Expected input source \[object String\] "1" to be type pointer/,
    604    message
    605  );
    606 });
    607 
    608 add_task(async function test_extractActionChainValidation() {
    609  for (let action of [-1, "a", undefined, null]) {
    610    const state = new actions.State();
    611    let message = `actions: ${getTypeString(action)}`;
    612    await Assert.rejects(
    613      actions.Chain.fromJSON(state, action),
    614      /InvalidArgumentError/,
    615      message
    616    );
    617    await Assert.rejects(
    618      actions.Chain.fromJSON(state, action),
    619      /Expected "actions" to be an array/,
    620      message
    621    );
    622  }
    623 });
    624 
    625 add_task(async function test_extractActionChainEmpty() {
    626  const state = new actions.State();
    627  deepEqual(await actions.Chain.fromJSON(state, [], {}), []);
    628 });
    629 
    630 add_task(async function test_extractActionChain_oneTickOneInput() {
    631  const state = new actions.State();
    632  const actionItem = { type: "pause", duration: 5000 };
    633  const actionSequence = {
    634    type: "none",
    635    id: "some id",
    636    actions: [actionItem],
    637  };
    638  const actionsByTick = await actions.Chain.fromJSON(
    639    state,
    640    [actionSequence],
    641    {}
    642  );
    643  equal(1, actionsByTick.length);
    644  equal(1, actionsByTick[0].length);
    645  equal(actionsByTick[0][0].id, actionSequence.id);
    646  equal(actionsByTick[0][0].type, "none");
    647  equal(actionsByTick[0][0].subtype, "pause");
    648  equal(actionsByTick[0][0].duration, actionItem.duration);
    649 });
    650 
    651 add_task(async function test_extractActionChain_twoAndThreeTicks() {
    652  const state = new actions.State();
    653  const mouseActionItems = [
    654    {
    655      type: "pointerDown",
    656      button: 2,
    657    },
    658    {
    659      type: "pointerUp",
    660      button: 2,
    661    },
    662  ];
    663  const mouseActionSequence = {
    664    type: "pointer",
    665    id: "7",
    666    actions: mouseActionItems,
    667    parameters: {
    668      pointerType: "mouse",
    669    },
    670  };
    671  const keyActionItems = [
    672    {
    673      type: "keyDown",
    674      value: "a",
    675    },
    676    {
    677      type: "pause",
    678      duration: 4,
    679    },
    680    {
    681      type: "keyUp",
    682      value: "a",
    683    },
    684  ];
    685  let keyActionSequence = {
    686    type: "key",
    687    id: "1",
    688    actions: keyActionItems,
    689  };
    690  let actionsByTick = await actions.Chain.fromJSON(
    691    state,
    692    [keyActionSequence, mouseActionSequence],
    693    {}
    694  );
    695  // number of ticks is same as longest action sequence
    696  equal(keyActionItems.length, actionsByTick.length);
    697  equal(2, actionsByTick[0].length);
    698  equal(2, actionsByTick[1].length);
    699  equal(1, actionsByTick[2].length);
    700 
    701  equal(actionsByTick[2][0].id, keyActionSequence.id);
    702  equal(actionsByTick[2][0].type, "key");
    703  equal(actionsByTick[2][0].subtype, "keyUp");
    704 });
    705 
    706 add_task(async function test_computeTickDuration() {
    707  const state = new actions.State();
    708  const expected = 8000;
    709  const inputTickActions = [
    710    { type: "none", subtype: "pause", duration: 5000 },
    711    { type: "key", subtype: "pause", duration: 1000 },
    712    { type: "pointer", subtype: "pointerMove", duration: 6000, x: 0, y: 0 },
    713    // invalid because keyDown should not have duration, so duration should be ignored.
    714    { type: "key", subtype: "keyDown", duration: 100000, value: "a" },
    715    { type: "pointer", subtype: "pause", duration: expected },
    716    { type: "pointer", subtype: "pointerUp", button: 0 },
    717  ];
    718  const chain = await actions.Chain.fromJSON(
    719    state,
    720    chainForTick(inputTickActions),
    721    {}
    722  );
    723  equal(1, chain.length);
    724  const tickActions = chain[0];
    725  equal(expected, tickActions.getDuration());
    726 });
    727 
    728 add_task(async function test_computeTickDuration_noDurations() {
    729  const state = new actions.State();
    730  const inputTickActions = [
    731    // invalid because keyDown should not have duration, so duration should be ignored.
    732    { type: "key", subtype: "keyDown", duration: 100000, value: "a" },
    733    // undefined duration permitted
    734    { type: "none", subtype: "pause" },
    735    { type: "pointer", subtype: "pointerMove", button: 0, x: 0, y: 0 },
    736    { type: "pointer", subtype: "pointerDown", button: 0 },
    737    { type: "key", subtype: "keyUp", value: "a" },
    738  ];
    739  const chain = await actions.Chain.fromJSON(
    740    state,
    741    chainForTick(inputTickActions),
    742    {}
    743  );
    744  equal(0, chain[0].getDuration());
    745 });
    746 
    747 add_task(function test_ClickTracker_setClick() {
    748  const clickTracker = new ClickTracker();
    749  const button1 = 1;
    750  const button2 = 2;
    751 
    752  clickTracker.setClick(button1);
    753  equal(1, clickTracker.count);
    754 
    755  // Make sure that clicking different mouse buttons doesn't increase the count.
    756  clickTracker.setClick(button2);
    757  equal(1, clickTracker.count);
    758 
    759  clickTracker.setClick(button2);
    760  equal(2, clickTracker.count);
    761 
    762  clickTracker.reset();
    763  equal(0, clickTracker.count);
    764 });
    765 
    766 add_task(function test_ClickTracker_reset_after_timeout() {
    767  const clickTracker = new ClickTracker();
    768 
    769  clickTracker.setClick(1);
    770  equal(1, clickTracker.count);
    771 
    772  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    773  setTimeout(() => equal(0, clickTracker.count), CLICK_INTERVAL + 10);
    774 });
    775 
    776 // helpers
    777 function getTypeString(obj) {
    778  return Object.prototype.toString.call(obj);
    779 }
    780 
    781 async function checkFromJSONErrors(
    782  inputTickActions,
    783  regex,
    784  message,
    785  options = {}
    786 ) {
    787  const { isElementOrigin = () => true, getElementOrigin = elem => elem } =
    788    options;
    789 
    790  const state = new actions.State();
    791  const actionsOptions = { isElementOrigin, getElementOrigin };
    792 
    793  if (typeof message == "undefined") {
    794    message = `fromJSON`;
    795  }
    796 
    797  await Assert.rejects(
    798    actions.Chain.fromJSON(
    799      state,
    800      chainForTick(inputTickActions),
    801      actionsOptions
    802    ),
    803    /InvalidArgumentError/,
    804    message
    805  );
    806  await Assert.rejects(
    807    actions.Chain.fromJSON(
    808      state,
    809      chainForTick(inputTickActions),
    810      actionsOptions
    811    ),
    812    regex,
    813    message
    814  );
    815 }
    816 
    817 function chainForTick(tickActions) {
    818  const actions = [];
    819  let lastId = 0;
    820  for (let { type, subtype, parameters, ...props } of tickActions) {
    821    let id;
    822    if (!props.hasOwnProperty("id")) {
    823      id = `${type}_${lastId++}`;
    824    } else {
    825      id = props.id;
    826      delete props.id;
    827    }
    828    const inputAction = { type, id, actions: [{ type: subtype, ...props }] };
    829    if (parameters !== undefined) {
    830      inputAction.parameters = parameters;
    831    }
    832    actions.push(inputAction);
    833  }
    834  return actions;
    835 }