tor-browser

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

browser_rules_edit-size-property-dragging.js (13905B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test that increasing / decreasing values in rule view by dragging with
      7 // the mouse works correctly.
      8 
      9 const TEST_URI = `
     10  <style>
     11    #test {
     12      padding-top: 10px;
     13      margin-top: unset;
     14      margin-bottom: 0px;
     15      width: 0px;
     16      border: 1px solid red;
     17      line-height: 2;
     18      border-width: var(--12px);
     19      max-height: +10.2e3vmin;
     20      min-height: 1% !important;
     21      font-size: 10Q;
     22      transform: rotate(45deg);
     23      margin-left: 28.3em;
     24      animation-delay: +15s;
     25      margin-right: -2px;
     26      padding-bottom: .9px;
     27      rotate: 90deg;
     28    }
     29  </style>
     30  <div id="test"></div>
     31 `;
     32 
     33 const DRAGGABLE_VALUE_CLASSNAME = "ruleview-propertyvalue-draggable";
     34 
     35 add_task(async function () {
     36  await pushPref("devtools.inspector.draggable_properties", true);
     37 
     38  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
     39 
     40  const { inspector, view } = await openRuleView();
     41  await selectNode("#test", inspector);
     42 
     43  testDraggingClassIsAddedWhenNeeded(view);
     44 
     45  // Check that toggling the feature updates the UI immediately.
     46  await pushPref("devtools.inspector.draggable_properties", false);
     47  testDraggingClassIsRemovedAfterPrefChange(view);
     48 
     49  await pushPref("devtools.inspector.draggable_properties", true);
     50  testDraggingClassIsAddedWhenNeeded(view);
     51 
     52  await testDraggingClassIsAddedOnValueUpdate(view);
     53  await testPressingEscapeWhileDragging(view);
     54  await testPressingEscapeWhileDraggingWithinDeadzone(view);
     55  await testUpdateDisabledValue(view);
     56  await testWidthIncrements(view);
     57  // This needs to happen last as dragging angles are causing further trouble.
     58  // This will be fixed as part of Bug 1885232.
     59  await testIncrementAngleValue(view);
     60 });
     61 
     62 const PROPERTIES = [
     63  {
     64    name: "border",
     65    value: "1px solid red",
     66    shouldBeDraggable: false,
     67  },
     68  {
     69    name: "line-height",
     70    value: "2",
     71    shouldBeDraggable: false,
     72  },
     73  {
     74    name: "border-width",
     75    value: "var(--12px)",
     76    shouldBeDraggable: false,
     77  },
     78  {
     79    name: "transform",
     80    value: "rotate(45deg)",
     81    shouldBeDraggable: false,
     82  },
     83  {
     84    name: "max-height",
     85    value: "+10.2e3vmin",
     86    shouldBeDraggable: true,
     87  },
     88  {
     89    name: "min-height",
     90    value: "1%",
     91    shouldBeDraggable: true,
     92  },
     93  {
     94    name: "font-size",
     95    value: "10Q",
     96    shouldBeDraggable: true,
     97  },
     98  {
     99    name: "margin-left",
    100    value: "28.3em",
    101    shouldBeDraggable: true,
    102  },
    103  {
    104    name: "animation-delay",
    105    value: "+15s",
    106    shouldBeDraggable: true,
    107  },
    108  {
    109    name: "margin-right",
    110    value: "-2px",
    111    shouldBeDraggable: true,
    112  },
    113  {
    114    name: "padding-bottom",
    115    value: ".9px",
    116    shouldBeDraggable: true,
    117  },
    118  {
    119    name: "rotate",
    120    value: "90deg",
    121    shouldBeDraggable: true,
    122  },
    123 ];
    124 
    125 function testDraggingClassIsAddedWhenNeeded(view) {
    126  info("Testing class is added or not on different property values");
    127  runIsDraggableTest(view, PROPERTIES);
    128 }
    129 
    130 function testDraggingClassIsRemovedAfterPrefChange(view) {
    131  info("Testing class is removed if the feature is disabled");
    132  runIsDraggableTest(
    133    view,
    134    // Create a temporary copy of the test PROPERTIES, where shouldBeDraggable is
    135    // always false.
    136    PROPERTIES.map(prop =>
    137      Object.assign({}, prop, { shouldBeDraggable: false })
    138    )
    139  );
    140 }
    141 
    142 async function testIncrementAngleValue(view) {
    143  info("Testing updating an angle value with the angle swatch span");
    144  const rotatePropEditor = getTextProperty(view, 1, {
    145    rotate: "90deg",
    146  }).editor;
    147  await runIncrementTest(rotatePropEditor, view, [
    148    {
    149      startValue: "90deg",
    150      expectedEndValue: "100deg",
    151      distance: 10,
    152      description: "updating angle value",
    153    },
    154  ]);
    155 }
    156 
    157 async function testPressingEscapeWhileDragging(view) {
    158  info("Testing pressing escape while dragging with mouse");
    159  const marginPropEditor = getTextProperty(view, 1, {
    160    "margin-bottom": "0px",
    161  }).editor;
    162  await runIncrementTest(marginPropEditor, view, [
    163    {
    164      startValue: "0px",
    165      expectedEndValue: "0px",
    166      expectedEndValueBeforeEscape: "100px",
    167      escape: true,
    168      distance: 100,
    169      description: "Pressing escape to check if value has been reset",
    170    },
    171  ]);
    172 }
    173 
    174 async function testPressingEscapeWhileDraggingWithinDeadzone(view) {
    175  info("Testing pressing escape while dragging with mouse within the deadzone");
    176  const marginPropEditor = getTextProperty(view, 1, {
    177    "margin-bottom": "0px",
    178  }).editor;
    179  await runIncrementTest(marginPropEditor, view, [
    180    {
    181      startValue: "0px",
    182      expectedEndValue: "0px",
    183      expectedEndValueBeforeEscape: "0px",
    184      escape: true,
    185      deadzoneIncluded: true,
    186      distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1,
    187      description: "Pressing escape to check if value has been reset",
    188    },
    189  ]);
    190 }
    191 
    192 async function testUpdateDisabledValue(view) {
    193  info("Testing updating a disabled value by dragging mouse");
    194 
    195  const textProperty = getTextProperty(view, 1, { "padding-top": "10px" });
    196  const editor = textProperty.editor;
    197 
    198  await togglePropStatus(view, textProperty);
    199  ok(!editor.enable.checked, "Should be disabled");
    200  await runIncrementTest(editor, view, [
    201    {
    202      startValue: "10px",
    203      expectedEndValue: "110px",
    204      distance: 100,
    205      description: "Updating disabled value",
    206    },
    207  ]);
    208  ok(editor.enable.checked, "Should be enabled");
    209 }
    210 
    211 async function testWidthIncrements(view) {
    212  info("Testing dragging the mouse on the width property");
    213 
    214  const marginPropEditor = getTextProperty(view, 1, { width: "0px" }).editor;
    215  await runIncrementTest(marginPropEditor, view, [
    216    {
    217      startValue: "0px",
    218      expectedEndValue: "20px",
    219      distance: 20,
    220      description: "Increasing value while dragging",
    221    },
    222    {
    223      startValue: "20px",
    224      expectedEndValue: "0px",
    225      distance: -20,
    226      description: "Decreasing value while dragging",
    227    },
    228    {
    229      startValue: "0px",
    230      expectedEndValue: "2px",
    231      ...getSmallIncrementKey(),
    232      distance: 20,
    233      description:
    234        "Increasing value with small increments by pressing ctrl or alt",
    235    },
    236    {
    237      startValue: "2px",
    238      expectedEndValue: "202px",
    239      shift: true,
    240      distance: 20,
    241      description: "Increasing value with large increments by pressing shift",
    242    },
    243    {
    244      startValue: "202px",
    245      expectedEndValue: "402px",
    246      distance: 200,
    247      description: "Increasing value with long distance",
    248    },
    249    {
    250      startValue: "402px",
    251      expectedEndValue: "402px",
    252      distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1,
    253      description: "No change in the deadzone (positive value)",
    254      deadzoneIncluded: true,
    255    },
    256    {
    257      startValue: "402px",
    258      expectedEndValue: "402px",
    259      distance: -1 * (marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1),
    260      description: "No change in the deadzone (negative value)",
    261      deadzoneIncluded: true,
    262    },
    263    {
    264      startValue: "402px",
    265      expectedEndValue: "403px",
    266      distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE + 1,
    267      description: "Changed by 1 when leaving the deadzone (positive value)",
    268      deadzoneIncluded: true,
    269    },
    270    {
    271      startValue: "403px",
    272      expectedEndValue: "402px",
    273      distance: -1 * (marginPropEditor._DRAGGING_DEADZONE_DISTANCE + 1),
    274      description: "Changed by 1 when leaving the deadzone (negative value)",
    275      deadzoneIncluded: true,
    276    },
    277  ]);
    278 }
    279 
    280 async function testDraggingClassIsAddedOnValueUpdate(view) {
    281  info("Testing dragging class is added when a supported unit is detected");
    282 
    283  const editor = getTextProperty(view, 1, { "margin-top": "unset" }).editor;
    284  const valueSpan = editor.valueSpan;
    285  ok(
    286    !valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME),
    287    "Should not be draggable"
    288  );
    289  valueSpan.scrollIntoView();
    290  await setProperty(view, editor.prop, "23em");
    291  ok(
    292    valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME),
    293    "Should be draggable"
    294  );
    295 }
    296 
    297 /**
    298 * Runs each test and check whether or not the property is draggable
    299 *
    300 * @param {CSSRuleView} view
    301 * @param {{name: string, value: string, shouldBeDraggable: boolean}[]} tests
    302 */
    303 function runIsDraggableTest(view, tests) {
    304  for (const test of tests) {
    305    const property = test;
    306    info(`Testing ${property.name} with value ${property.value}`);
    307    const editor = getTextProperty(view, 1, {
    308      [property.name]: property.value,
    309    }).editor;
    310    const valueSpan = editor.valueSpan;
    311    if (property.shouldBeDraggable) {
    312      ok(
    313        valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME),
    314        "Should be draggable"
    315      );
    316    } else {
    317      ok(
    318        !valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME),
    319        "Should not be draggable"
    320      );
    321    }
    322  }
    323 }
    324 
    325 /**
    326 * Runs each test in the tests array by synthesizing a mouse dragging
    327 *
    328 * @param  {TextPropertyEditor} editor
    329 * @param  {CSSRuleView} view
    330 * @param  {Array} tests
    331 */
    332 async function runIncrementTest(editor, view, tests) {
    333  for (const test of tests) {
    334    await testIncrement(editor, test, view);
    335  }
    336  view.debounce.flush();
    337 }
    338 
    339 /**
    340 * Runs an increment test
    341 *
    342 * 1. We initialize the TextProperty value with "startValue"
    343 * 2. We synthesize a mouse dragging of "distance" length
    344 * 3. We check the value of TextProperty is equal to "expectedEndValue"
    345 *
    346 * @param  {TextPropertyEditor} editor
    347 * @param  {Array} options
    348 * @param  {string} options.startValue
    349 * @param  {string} options.expectedEndValue
    350 * @param  {boolean} options.shift Whether or not we press the shift key
    351 * @param  {number} options.distance Distance of the dragging
    352 * @param  {string} options.description
    353 * @param  {boolean} options.ctrl Small increment key
    354 * @param  {boolean} options.alt Small increment key for macosx
    355 * @param  {boolean} options.deadzoneIncluded True if the provided distance
    356 *         accounts for the deadzone. When false, the deadzone will automatically
    357 *         be added to the distance.
    358 */
    359 async function testIncrement(editor, options) {
    360  info("Running subtest: " + options.description);
    361 
    362  editor.valueSpan.scrollIntoView();
    363  await setProperty(editor.ruleView, editor.prop, options.startValue);
    364 
    365  is(
    366    editor.prop.value,
    367    options.startValue,
    368    "Value initialized at " + options.startValue
    369  );
    370 
    371  const onMouseUp = once(editor.valueSpan, "mouseup");
    372 
    373  await synthesizeMouseDragging(editor, options.distance, options);
    374 
    375  // mouseup event not triggered when escape is pressed
    376  if (!options.escape) {
    377    info("Waiting mouseup");
    378    await onMouseUp;
    379    info("Received mouseup");
    380  }
    381 
    382  is(
    383    editor.prop.value,
    384    options.expectedEndValue,
    385    "Value changed to " + editor.prop.value
    386  );
    387 }
    388 
    389 /**
    390 * Synthesizes mouse dragging (mousedown + mousemove + mouseup)
    391 *
    392 * @param {TextPropertyEditor} editor
    393 * @param {number} distance length of the horizontal dragging (negative if dragging left)
    394 * @param {object} option
    395 * @param {boolean} option.escape
    396 * @param {boolean} option.alt
    397 * @param {boolean} option.shift
    398 * @param {boolean} option.ctrl
    399 * @param {boolean} option.deadzoneIncluded
    400 */
    401 async function synthesizeMouseDragging(editor, distance, options = {}) {
    402  info(`Start to synthesize mouse dragging (from ${1} to ${1 + distance})`);
    403 
    404  const styleWindow = editor.ruleView.styleWindow;
    405  const elm = editor.valueSpan;
    406  const startPosition = [1, 1];
    407 
    408  // Handle the pixel based deadzone.
    409  const deadzone = editor._DRAGGING_DEADZONE_DISTANCE;
    410  if (!options.deadzoneIncluded) {
    411    // Most tests do not care about the deadzone and the provided distance does
    412    // not account for the deadzone. Add it automatically.
    413    distance = distance + Math.sign(distance) * deadzone;
    414  }
    415  const updateExpected = Math.abs(options.distance) > deadzone;
    416 
    417  const endPosition = [startPosition[0] + distance, startPosition[1]];
    418 
    419  EventUtils.synthesizeMouse(
    420    elm,
    421    startPosition[0],
    422    startPosition[1],
    423    { type: "mousedown" },
    424    styleWindow
    425  );
    426 
    427  // If the drag will not trigger any update, simply wait for 100ms.
    428  // Otherwise, wait for the next property-updated-by-dragging event and the rule view change.
    429  const updated = updateExpected
    430    ? Promise.all([
    431        editor.ruleView.once("ruleview-changed"),
    432        editor.ruleView.once("property-updated-by-dragging"),
    433      ])
    434    : wait(100);
    435 
    436  EventUtils.synthesizeMouse(
    437    elm,
    438    endPosition[0],
    439    endPosition[1],
    440    {
    441      type: "mousemove",
    442      shiftKey: !!options.shift,
    443      ctrlKey: !!options.ctrl,
    444      altKey: !!options.alt,
    445    },
    446    styleWindow
    447  );
    448 
    449  // We wait because the mousemove event listener is throttled to 30ms
    450  // in the TextPropertyEditor class
    451  info("waiting for event property-updated-by-dragging");
    452  await updated;
    453  ok(true, "received event property-updated-by-dragging");
    454 
    455  if (options.escape) {
    456    is(
    457      editor.prop.value,
    458      options.expectedEndValueBeforeEscape,
    459      "testing value before pressing escape"
    460    );
    461    const onRuleViewChanged = updateExpected
    462      ? editor.ruleView.once("ruleview-changed")
    463      : null;
    464    EventUtils.synthesizeKey("VK_ESCAPE", {}, styleWindow);
    465    await onRuleViewChanged;
    466  }
    467 
    468  EventUtils.synthesizeMouse(
    469    elm,
    470    endPosition[0],
    471    endPosition[1],
    472    {
    473      type: "mouseup",
    474    },
    475    styleWindow
    476  );
    477 
    478  // If the drag did not trigger any update, mouseup might open an inline editor.
    479  // Leave the editor.
    480  const inplaceEditor = styleWindow.document.querySelector(
    481    ".styleinspector-propertyeditor"
    482  );
    483  if (inplaceEditor) {
    484    const onBlur = once(inplaceEditor, "blur");
    485    EventUtils.synthesizeKey("VK_ESCAPE", {}, styleWindow);
    486    await onBlur;
    487  }
    488 
    489  info("Finish to synthesize mouse dragging");
    490 }
    491 
    492 function getSmallIncrementKey() {
    493  if (AppConstants.platform === "macosx") {
    494    return { alt: true };
    495  }
    496  return { ctrl: true };
    497 }