tor-browser

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

browser_rules_linear-easing-swatch.js (13185B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Tests that linear easing widget pickers appear when clicking on linear
      7 // swatches.
      8 
      9 const TEST_URI = `
     10  <style type="text/css">
     11    div {
     12      animation: move 3s linear(0, 0.2, 1);
     13      transition: top 4s linear(0 10%, 0.5 20% 80%, 0 90%);
     14    }
     15    .test {
     16      animation-timing-function: linear(0, 1 50% 100%);
     17      transition-timing-function: linear(1 -10%, 0, -1 110%);
     18    }
     19  </style>
     20  <div class="test">Testing the linear easing tooltip!</div>
     21 `;
     22 
     23 add_task(async function testSwatches() {
     24  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
     25  const { inspector, view } = await openRuleView();
     26  await selectNode("div", inspector);
     27 
     28  const tooltip = view.tooltips.getTooltip("linearEaseFunction");
     29  ok(tooltip, "The rule-view has the expected linear tooltip");
     30  const panel = tooltip.tooltip.panel;
     31  ok(panel, "The XUL panel for the linear tooltip exists");
     32 
     33  const expectedData = [
     34    {
     35      selector: "div",
     36      property: "animation",
     37      expectedPoints: [
     38        [0, 0],
     39        [0.5, 0.2],
     40        [1, 1],
     41      ],
     42    },
     43    {
     44      selector: "div",
     45      property: "transition",
     46      expectedPoints: [
     47        [0.1, 0],
     48        [0.2, 0.5],
     49        [0.8, 0.5],
     50        [0.9, 0],
     51      ],
     52    },
     53    {
     54      selector: ".test",
     55      property: "animation-timing-function",
     56      expectedPoints: [
     57        [0, 0],
     58        [0.5, 1],
     59        [1, 1],
     60      ],
     61    },
     62    {
     63      selector: ".test",
     64      property: "transition-timing-function",
     65      expectedPoints: [
     66        [-0.1, 1],
     67        [0.5, 0],
     68        [1.1, -1],
     69      ],
     70    },
     71  ];
     72 
     73  for (const { selector, property, expectedPoints } of expectedData) {
     74    const messagePrefix = `[${selector}|${property}]`;
     75    const swatch = getRuleViewLinearEasingSwatch(view, selector, property);
     76    ok(swatch, `${messagePrefix} the swatch exists`);
     77 
     78    const onWidgetReady = tooltip.once("ready");
     79    swatch.click();
     80    await onWidgetReady;
     81    ok(true, `${messagePrefix} clicking the swatch displayed the tooltip`);
     82 
     83    ok(
     84      !inplaceEditor(swatch.parentNode),
     85      `${messagePrefix} inplace editor wasn't shown`
     86    );
     87 
     88    checkChartState(panel, expectedPoints);
     89 
     90    await hideTooltipAndWaitForRuleViewChanged(tooltip, view);
     91  }
     92 });
     93 
     94 add_task(async function testChart() {
     95  await pushPref("ui.prefersReducedMotion", 0);
     96 
     97  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
     98  const { inspector, view } = await openRuleView();
     99  await selectNode("div", inspector);
    100 
    101  const tooltip = view.tooltips.getTooltip("linearEaseFunction");
    102  ok(tooltip, "The rule-view has the expected linear tooltip");
    103  const panel = tooltip.tooltip.panel;
    104  ok(panel, "The XUL panel for the linear tooltip exists");
    105 
    106  const selector = ".test";
    107  const property = "animation-timing-function";
    108 
    109  const swatch = getRuleViewLinearEasingSwatch(view, selector, property);
    110  const onWidgetReady = tooltip.once("ready");
    111  swatch.click();
    112  await onWidgetReady;
    113  const widget = await tooltip.widget;
    114 
    115  const svgEl = panel.querySelector(`svg.chart`);
    116  const svgRect = svgEl.getBoundingClientRect();
    117 
    118  checkChartState(
    119    panel,
    120    [
    121      [0, 0],
    122      [0.5, 1],
    123      [1, 1],
    124    ],
    125    "testChart - initial state:"
    126  );
    127 
    128  info("Check that double clicking a point removes it");
    129  const middlePoint = panel.querySelector(
    130    `svg.chart .control-points-group .control-point:nth-of-type(2)`
    131  );
    132  let onWidgetUpdated = widget.once("updated");
    133  let onRuleViewChanged = view.once("ruleview-changed");
    134  EventUtils.sendMouseEvent(
    135    { type: "dblclick" },
    136    middlePoint,
    137    widget.parent.ownerGlobal
    138  );
    139 
    140  let newValue = await onWidgetUpdated;
    141  is(newValue, `linear(0 0%, 1 100%)`);
    142  checkChartState(
    143    panel,
    144    [
    145      [0, 0],
    146      [1, 1],
    147    ],
    148    "testChart - after point removed:"
    149  );
    150  await onRuleViewChanged;
    151  await checkRuleView(view, selector, property, newValue);
    152 
    153  info(
    154    "Check that double clicking a point when there are only 2 points on the line does not remove it"
    155  );
    156  const timeoutRes = Symbol();
    157  let onTimeout = wait(1000).then(() => timeoutRes);
    158  onWidgetUpdated = widget.once("updated");
    159  EventUtils.sendMouseEvent(
    160    { type: "dblclick" },
    161    panel.querySelector(`svg.chart .control-points-group .control-point`),
    162    widget.parent.ownerGlobal
    163  );
    164  let raceWinner = await Promise.race([onWidgetUpdated, onTimeout]);
    165  is(
    166    raceWinner,
    167    timeoutRes,
    168    "The widget wasn't updated after double clicking one of the 2 last points"
    169  );
    170  checkChartState(
    171    panel,
    172    [
    173      [0, 0],
    174      [1, 1],
    175    ],
    176    "testChart - no point removed:"
    177  );
    178 
    179  info("Check that double clicking on the svg does add a point");
    180  onWidgetUpdated = widget.once("updated");
    181  onRuleViewChanged = view.once("ruleview-changed");
    182  // Clicking on svg center with shiftKey so it snaps to the grid
    183  EventUtils.synthesizeMouseAtCenter(
    184    panel.querySelector(`svg.chart`),
    185    { clickCount: 2, shiftKey: true },
    186    widget.parent.ownerGlobal
    187  );
    188 
    189  newValue = await onWidgetUpdated;
    190  is(
    191    newValue,
    192    `linear(0 0%, 0.5 50%, 1 100%)`,
    193    "Widget was updated with expected value"
    194  );
    195  checkChartState(
    196    panel,
    197    [
    198      [0, 0],
    199      [0.5, 0.5],
    200      [1, 1],
    201    ],
    202    "testChart - new point added"
    203  );
    204  await onRuleViewChanged;
    205  await checkRuleView(view, selector, property, newValue);
    206 
    207  info("Check that points can be moved");
    208  onWidgetUpdated = widget.once("updated");
    209  onRuleViewChanged = view.once("ruleview-changed");
    210  EventUtils.synthesizeMouseAtCenter(
    211    panel.querySelector(`svg.chart .control-points-group .control-point`),
    212    { type: "mousedown" },
    213    widget.parent.ownerGlobal
    214  );
    215 
    216  EventUtils.synthesizeMouse(
    217    svgEl,
    218    svgRect.width / 3,
    219    svgRect.height / 3,
    220    { type: "mousemove", shiftKey: true },
    221    widget.parent.ownerGlobal
    222  );
    223  newValue = await onWidgetUpdated;
    224  is(
    225    newValue,
    226    `linear(0.7 30%, 0.5 50%, 1 100%)`,
    227    "Widget was updated with expected value"
    228  );
    229  checkChartState(
    230    panel,
    231    [
    232      [0.3, 0.7],
    233      [0.5, 0.5],
    234      [1, 1],
    235    ],
    236    "testChart - point moved"
    237  );
    238  await onRuleViewChanged;
    239  await checkRuleView(view, selector, property, newValue);
    240 
    241  info("Check that the points can be moved past the next point");
    242  onWidgetUpdated = widget.once("updated");
    243  onRuleViewChanged = view.once("ruleview-changed");
    244 
    245  // the second point is at 50%, so simulate a mousemove all the way to the right (which
    246  // should be ~100%)
    247  EventUtils.synthesizeMouse(
    248    svgEl,
    249    svgRect.width,
    250    svgRect.height / 3,
    251    { type: "mousemove", shiftKey: true },
    252    widget.parent.ownerGlobal
    253  );
    254  newValue = await onWidgetUpdated;
    255  is(
    256    newValue,
    257    `linear(0.7 50%, 0.5 50%, 1 100%)`,
    258    "point wasn't moved past the next point (50%)"
    259  );
    260  checkChartState(
    261    panel,
    262    [
    263      [0.5, 0.7],
    264      [0.5, 0.5],
    265      [1, 1],
    266    ],
    267    "testChart - point moved constrained by next point"
    268  );
    269  await onRuleViewChanged;
    270  await checkRuleView(view, selector, property, newValue);
    271 
    272  info("Stop dragging");
    273  EventUtils.synthesizeMouseAtCenter(
    274    svgEl,
    275    { type: "mouseup" },
    276    widget.parent.ownerGlobal
    277  );
    278 
    279  onTimeout = wait(1000).then(() => timeoutRes);
    280  onWidgetUpdated = widget.once("updated");
    281  EventUtils.synthesizeMouseAtCenter(
    282    svgEl,
    283    { type: "mousemove" },
    284    widget.parent.ownerGlobal
    285  );
    286  raceWinner = await Promise.race([onWidgetUpdated, onTimeout]);
    287  is(raceWinner, timeoutRes, "Dragging is disabled after mouseup");
    288 
    289  info("Add another point, which should be the first one for the line");
    290  onWidgetUpdated = widget.once("updated");
    291  onRuleViewChanged = view.once("ruleview-changed");
    292 
    293  EventUtils.synthesizeMouse(
    294    svgEl,
    295    svgRect.width / 3,
    296    svgRect.height - 1,
    297    { clickCount: 2, shiftKey: true },
    298    widget.parent.ownerGlobal
    299  );
    300 
    301  newValue = await onWidgetUpdated;
    302  is(
    303    newValue,
    304    `linear(0 30%, 0.7 50%, 0.5 50%, 1 100%)`,
    305    "Widget was updated with expected value"
    306  );
    307  checkChartState(
    308    panel,
    309    [
    310      [0.3, 0],
    311      [0.5, 0.7],
    312      [0.5, 0.5],
    313      [1, 1],
    314    ],
    315    "testChart - point added at beginning"
    316  );
    317  await onRuleViewChanged;
    318  await checkRuleView(view, selector, property, newValue);
    319 
    320  info("Check that the points can't be moved past previous point");
    321  onWidgetUpdated = widget.once("updated");
    322  onRuleViewChanged = view.once("ruleview-changed");
    323  EventUtils.synthesizeMouseAtCenter(
    324    panel.querySelector(
    325      `svg.chart .control-points-group .control-point:nth-of-type(2)`
    326    ),
    327    { type: "mousedown" },
    328    widget.parent.ownerGlobal
    329  );
    330 
    331  EventUtils.synthesizeMouse(
    332    svgEl,
    333    0,
    334    svgRect.height / 3,
    335    { type: "mousemove", shiftKey: true },
    336    widget.parent.ownerGlobal
    337  );
    338  newValue = await onWidgetUpdated;
    339  is(
    340    newValue,
    341    `linear(0 30%, 0.7 30%, 0.5 50%, 1 100%)`,
    342    "point wasn't moved past previous point (30%)"
    343  );
    344  checkChartState(
    345    panel,
    346    [
    347      [0.3, 0],
    348      [0.3, 0.7],
    349      [0.5, 0.5],
    350      [1, 1],
    351    ],
    352    "testChart - point moved constrained by previous point"
    353  );
    354  await onRuleViewChanged;
    355  await checkRuleView(view, selector, property, newValue);
    356 
    357  info("Stop dragging");
    358  EventUtils.synthesizeMouseAtCenter(
    359    svgEl,
    360    { type: "mouseup" },
    361    widget.parent.ownerGlobal
    362  );
    363 
    364  info(
    365    "Check that timing preview is destroyed if prefers-reduced-motion gets enabled"
    366  );
    367  const getTimingFunctionPreview = () =>
    368    panel.querySelector(".timing-function-preview");
    369  ok(getTimingFunctionPreview(), "By default, timing preview is visible");
    370  info("Enable prefersReducedMotion");
    371  await pushPref("ui.prefersReducedMotion", 1);
    372  await waitFor(() => !getTimingFunctionPreview());
    373  ok(true, "timing preview was removed after enabling prefersReducedMotion");
    374 
    375  info("Disable prefersReducedMotion");
    376  await pushPref("ui.prefersReducedMotion", 0);
    377  await waitFor(() => getTimingFunctionPreview());
    378  ok(
    379    true,
    380    "timing preview was added back after disabling prefersReducedMotion"
    381  );
    382 
    383  info("Hide tooltip with escape to cancel modification");
    384  const onHidden = tooltip.tooltip.once("hidden");
    385  const onModifications = view.once("ruleview-changed");
    386  focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE");
    387  await onHidden;
    388  await onModifications;
    389 
    390  await checkRuleView(
    391    view,
    392    selector,
    393    property,
    394    "linear(0, 1 50% 100%)",
    395    "linear(0 0%, 1 50%, 1 100%)"
    396  );
    397 });
    398 
    399 /**
    400 * Check that the svg chart line and control points are placed where we expect them.
    401 *
    402 * @param {ToolipPanel} panel
    403 * @param {Array<Array<number>>} expectedPoints: Array of coordinated
    404 * @param {string} messagePrefix
    405 */
    406 function checkChartState(panel, expectedPoints, messagePrefix = "") {
    407  const svgLine = panel.querySelector("svg.chart .chart-linear");
    408  is(
    409    svgLine.getAttribute("points"),
    410    expectedPoints.map(([x, y]) => `${x},${1 - y}`).join(" "),
    411    `${messagePrefix} line has the expected points`
    412  );
    413 
    414  const controlPoints = panel.querySelectorAll(
    415    `svg.chart .control-points-group .control-point`
    416  );
    417 
    418  is(
    419    controlPoints.length,
    420    expectedPoints.length,
    421    `${messagePrefix} the expected number of control points were created`
    422  );
    423  controlPoints.forEach((controlPoint, i) => {
    424    is(
    425      parseFloat(controlPoint.getAttribute("cx")),
    426      expectedPoints[i][0],
    427      `${messagePrefix} Control point ${i} has correct cx`
    428    );
    429    is(
    430      parseFloat(controlPoint.getAttribute("cy")),
    431      // XXX work around floating point issues
    432      Math.round((1 - expectedPoints[i][1]) * 10) / 10,
    433      `${messagePrefix} Control point ${i} has correct cy`
    434    );
    435  });
    436 }
    437 
    438 /**
    439 * Checks if the property in the rule view has the expected state
    440 *
    441 * @param {RuleView} view
    442 * @param {string} selector
    443 * @param {string} property
    444 * @param {string} expectedLinearValue: Expected value in the rule view
    445 * @param {string} expectedComputedLinearValue: Expected computed value. Defaults to expectedLinearValue.
    446 * @returns {Element|null}
    447 */
    448 async function checkRuleView(
    449  view,
    450  selector,
    451  property,
    452  expectedLinearValue,
    453  expectedComputedLinearValue = expectedLinearValue
    454 ) {
    455  await waitForComputedStyleProperty(
    456    selector,
    457    null,
    458    property,
    459    expectedComputedLinearValue
    460  );
    461 
    462  is(
    463    getRuleViewProperty(view, selector, property).valueSpan.textContent,
    464    expectedLinearValue,
    465    `${selector} ${property} has expected value`
    466  );
    467  const swatch = getRuleViewLinearEasingSwatch(view, selector, property);
    468  is(
    469    swatch.getAttribute("data-linear"),
    470    expectedLinearValue,
    471    `${selector} ${property} swatch has expected "data-linear" attribute`
    472  );
    473 }
    474 
    475 /**
    476 * Returns the linear easing swatch for a rule (defined by its selector), and a property.
    477 *
    478 * @param {RuleView} view
    479 * @param {string} selector
    480 * @param {string} property
    481 * @returns {Element|null}
    482 */
    483 function getRuleViewLinearEasingSwatch(view, selector, property) {
    484  return getRuleViewProperty(view, selector, property).valueSpan.querySelector(
    485    ".inspector-lineareasingswatch"
    486  );
    487 }