tor-browser

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

test_event_target_radius.html (35022B)


      1 <!DOCTYPE HTML>
      2 <html id="html" style="height:100%">
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=780847
      5 https://bugzilla.mozilla.org/show_bug.cgi?id=1733509
      6 https://bugzilla.mozilla.org/show_bug.cgi?id=1957321
      7 https://bugzilla.mozilla.org/show_bug.cgi?id=1983191
      8 -->
      9 <head>
     10  <title>Test radii for mouse events</title>
     11  <script src="/tests/SimpleTest/EventUtils.js"></script>
     12  <script src="/tests/SimpleTest/paint_listener.js"></script>
     13  <script src="/tests/SimpleTest/SimpleTest.js"></script>
     14  <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
     15  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     16  <style>
     17  .target { position:absolute; left:100px; top:100px; width:100px; height:100px; background:blue; }
     18  .scaled { background: green; transform: scale(0.5); }
     19  iframe { margin:0; padding:0; width:50; height:50; border:1px solid black; background:yellowgreen; }
     20  </style>
     21 </head>
     22 <body id="body" onload="setTimeout(startTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
     23 <p id="display"></p>
     24 <div id="content">
     25  <!-- We make the `t` target shorter than normal in case because we test the
     26       bottom edge fluffing on this element, and the test page may be hosted
     27       inside a short iframe in the test harness on some platforms.
     28  -->
     29  <div class="target" style="height:80px" id="t" onmousedown="x=1"></div>
     30 
     31  <div class="target" id="t2" hidden></div>
     32 
     33  <input class="target" id="t3_1" hidden inputMode="none"></input>
     34  <a href="#" class="target" id="t3_2" hidden></a>
     35  <label class="target" id="t3_3" hidden></label>
     36  <button class="target" id="t3_4" hidden></button>
     37  <select class="target" id="t3_5" hidden></select>
     38  <textarea class="target" id="t3_6" hidden inputMode="none"></textarea>
     39  <div role="button" class="target" id="t3_7" hidden></div>
     40  <div role="key" class="target" id="t3_8" hidden></div>
     41  <img class="target" id="t3_9" hidden></img>
     42 
     43  <div class="target" style="transform:translate(-80px,0);" id="t4" onmousedown="x=1" hidden></div>
     44 
     45  <div class="target" style="left:0; top:0; z-index:1" id="t5_left" onmousedown="x=1" hidden></div>
     46  <div class="target" style="left:111px; top:0" id="t5_right" onmousedown="x=1" hidden></div>
     47  <div class="target" style="left:0; top:111px" id="t5_below" onmousedown="x=1" hidden></div>
     48 
     49  <div class="target" id="t6" onmousedown="x=1" style="width: 300px" hidden>
     50    <div id="t6_inner" style="position:absolute; left:-40px; top:20px; width:60px; height:60px; background:yellow;"></div>
     51    <div id="t6_inner_clickable" style="position:absolute; left:-40px; top: 80px; width: 60px; height: 5px; background:red" onmousedown="x=1"></div>
     52  </div>
     53  <div id="t6_outer" style="position:absolute; left:360px; top:120px; width:60px; height:60px; background:green;" onmousedown="x=1" hidden></div>
     54 
     55  <div class="target" id="t7" onmousedown="x=1" hidden></div>
     56  <div class="target" id="t7_over" hidden></div>
     57 
     58  <div id="t8" contenteditable="true" class="target" hidden inputMode="none"></div>
     59 
     60  <div id="t9" class="target" ontouchend="x=1" hidden></div>
     61 
     62  <div id="t10_left" class="target" style="left:-50px;" onmousedown="x=1" hidden></div>
     63  <div id="t10_right" class="target" style="left:auto;right:-50px" onmousedown="x=1" hidden></div>
     64  <div id="t10_top" class="target" style="top:-50px;" onmousedown="x=1" hidden></div>
     65  <div id="t10_bottom" class="target" style="top:auto;bottom:-50px;" onmousedown="x=1" hidden></div>
     66  <div id="t10_over" style="position:absolute; left:0; top:0; width:100%; height:100%; background:yellow;" hidden></div>
     67 
     68  <div id="t11" class="target" style="cursor:pointer" hidden></div>
     69  <div id="t11_with_child" class="target" style="cursor:pointer" hidden><div id="t11_child" style="width:100px; height:100px; background:green;"></div></div>
     70  <div id="t11_covered" class="target" style="cursor:pointer" hidden><div id="t11_coverer" style="width:100px; height:100px; background:green; cursor:text"></div></div>
     71 
     72  <div id="t12" class="target" hidden>
     73    <input id="t12_input" style="width: 100px; height: 20px; border: 0; padding: 0"></input>
     74    <div id="t12_zisland" style="width: 100px; height: 50px; position:relative; z-index: 5">
     75      <div id="t12_target" style="width: 100px; height: 20px; background-color: green"></div>
     76    </div>
     77  </div>
     78 
     79  <div id="t13" class="target" style="cursor:pointer" hidden>
     80   <div id="t13_touchlistener" style="width: 50px; height: 50px; background:red" ontouchend="x=1"></div>
     81   <div id="t13_notouchlistener" style="width: 50px; height: 50px; background:green"></div>
     82  </div>
     83 
     84  <div id="t14" class="target scaled" hidden>
     85    <iframe id="t14iframe"></iframe>
     86  </div>
     87  <div id="t15" class="target" onmousedown="x=1" style="padding: 50px" hidden>
     88    <div id="t15_touchListener" ontouchend="x=1" style="width: 50px; height: 50px; background: lightcyan;"></div>
     89  </div>
     90  <div id="t16" class="target" ontouchend="x=1" style="padding: 50px" hidden>
     91    <div id="t16_mouseListener" onmousedown="x=1" style="width: 50px; height: 50px; background: lightcyan;"></div>
     92  </div>
     93  <div id="t17" class="target" style="width: 100px; position: absolute; padding: 0" hidden>
     94    <div id="t17_container" style="height: 20px; margin: 0; padding: 0; overflow: hidden; white-space: nowrap">
     95      <span id="t17_clickable_partially_overlapped" onclick="x=1">overlapped or clipped by t17_clickable_cover</span>
     96    </div>
     97    <!-- overlaps the right half of t17_container -->
     98    <span id="t17_clickable_cover" onclick="x=1" style="width: 50px; height: 20px; position: absolute; right: 0; top: 1px">cover</span>
     99  </div>
    100  <!-- t18 is the counter case of t17, unrealistic use case, though -->
    101  <div id="t18" class="target" style="width: 100px; position: absolute; padding: 0" hidden>
    102    <div id="t18_container" style="height: 20px; margin: 0; padding: 0; overflow: hidden; white-space: nowrap">
    103      <span id="t18_clickable_bigger_z_index_but_clipped" style="position:relative; z-index: 99" onclick="x=1">topmost frame, but clipped by t18_container due to its long text</span>
    104    </div>
    105    <span id="t18_clickable_partially_overlapped" onclick="x=1" style="width: 120px; height: 20px; position: absolute; right: 0; top: 1px">cover</span>
    106  </div>
    107  <div id="t19" class="target" onclick="x=1" style="padding: 10px" hidden>
    108    <span id="t19_child">foo</span>
    109  </div>
    110  <div id="t20" class="target" onclick="x=1" style="padding: 10px; cursor: pointer" hidden>
    111    <span id="t20_child">foo</span>
    112  </div>
    113  <div id="t21" class="target" onclick="x=1" style="padding: 10px;" hidden>
    114    <span id="t21_child" style="cursor: pointer">foo</span>
    115  </div>
    116 </div>
    117 <pre id="test">
    118 <script type="application/javascript">
    119 function startTest() {
    120  SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.enabled", true],
    121                                     ["ui.mouse.radius.inputSource.touchOnly", false],
    122                                     ["ui.mouse.radius.leftmm", 12],
    123                                     ["ui.mouse.radius.topmm", 4],
    124                                     ["ui.mouse.radius.rightmm", 4],
    125                                     ["ui.mouse.radius.bottommm", 4],
    126                                     ["ui.mouse.radius.visitedweight", 50]]}, runTest);
    127 }
    128 
    129 
    130 SimpleTest.waitForExplicitFinish();
    131 // SimpleTest.requestCompleteLog();
    132 
    133 const kIsAndroid = navigator.appVersion.includes("Android");
    134 
    135 function stringifyBoundingClientRect(aId) {
    136  const rect = document.getElementById(aId).getBoundingClientRect();
    137  return `x: ${rect.x}, y: ${rect.y}, right: ${rect.right}, bottom: ${rect.bottom} (${aId})`;
    138 }
    139 
    140 function stringifyPoint(aPoint) {
    141  return `x: ${aPoint.x}, y: ${aPoint.y}`;
    142 }
    143 
    144 function endTest() {
    145  SimpleTest.finish();
    146 }
    147 
    148 var eventTarget;
    149 window.onmousedown = function(event) { eventTarget = event.target; };
    150 
    151 function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
    152  eventTarget = null;
    153  synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
    154  try {
    155    is(eventTarget.id, idTarget,
    156       "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
    157  } catch (ex) {
    158    ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
    159  }
    160 }
    161 
    162 var touchTarget;
    163 document.addEventListener('touchstart', event => { touchTarget = event.target; });
    164 
    165 function testTouch(idPosition, dx, dy, idTarget, msg, options) {
    166  touchTarget = null;
    167  synthesizeTouch(document.getElementById(idPosition), dx, dy, options || {});
    168  try {
    169    if (Array.isArray(idTarget)) {
    170      if (touchTarget === null) {
    171        ok(
    172          idTarget.includes(null),
    173          `checking '${idPosition}' offset ${dx},${dy} [${msg}]: null is not allowed`
    174        );
    175      } else {
    176        ok(
    177          idTarget.includes(touchTarget.id),
    178          `checking '${idPosition}' offset ${dx},${dy} [${msg}]: "${idTarget.id}" is not allowed`
    179        );
    180      }
    181    } else {
    182      is(
    183        touchTarget.id,
    184        idTarget,
    185        `checking '${idPosition}' offset ${dx},${dy} [${msg}]`
    186      );
    187    }
    188  } catch (ex) {
    189    ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + touchTarget);
    190  }
    191 }
    192 
    193 function setShowing(id, show) {
    194  var e = document.getElementById(id);
    195  e.hidden = !show;
    196 }
    197 
    198 function getTopWindowResolution() {
    199  return SpecialPowers.getDOMWindowUtils(window.top).getResolution();
    200 }
    201 
    202 function getTopWindowDevicePixelRatio() {
    203  return window.top.devicePixelRatio;
    204 }
    205 
    206 function getTopWindowDesktopToDeviceScale() {
    207  return SpecialPowers.wrap(window.top).desktopToDeviceScale;
    208 }
    209 
    210 function maybeGetWindowInnerPointInUnscaledCSSPixels() {
    211  // See _getScreenXInUnscaledCSSPixels and _getScreenYInUnscaledCSSPixels in EventUtils.js
    212  const point = {
    213    x: window.mozInnerScreenX,
    214    y: window.mozInnerScreenX,
    215  };
    216  try {
    217    const resolution = getTopWindowResolution();
    218    point.x = window.top.mozInnerScreenX + (window.mozInnerScreenX - window.top.mozInnerScreenX) * resolution;
    219    point.y = window.top.mozInnerScreenY + (window.mozInnerScreenY - window.top.mozInnerScreenY) * resolution;
    220  } catch (e) {
    221    // XXX fission+xorigin test throws permission denied since win.top is
    222    //     cross-origin.
    223  }
    224  return point;
    225 }
    226 
    227 var mm;
    228 function runTest() {
    229  info(
    230    `Starting tests within the top window whose values are:\n  resolution: ${
    231      getTopWindowResolution()
    232    }\n  devicePixelRatio: ${getTopWindowDevicePixelRatio()}\n  desktopToDeviceScale: ${
    233      getTopWindowDesktopToDeviceScale()
    234    }\n  window inner position: ${stringifyPoint(maybeGetWindowInnerPointInUnscaledCSSPixels())}`
    235  );
    236 
    237  let resolution = 1;
    238  if (kIsAndroid) {
    239    // This test runs on Android, zoomed out. Therefore we need to account
    240    // for the resolution as well, because the fluff area is relative to screen
    241    // pixels rather than CSS pixels.
    242    resolution = getTopWindowResolution();
    243    ok(resolution <= 1, "Resolution is " + resolution);
    244  }
    245  mm = SpecialPowers.getDOMWindowUtils(window.top).physicalMillimeterInCSSPixels / resolution;
    246  ok(4*mm >= 10, "WARNING: mm " + mm + " too small in this configuration. Test results will be bogus");
    247 
    248  // Test basic functionality: clicks sufficiently close to the element
    249  // should be allowed to hit the element. We test points just inside and
    250  // just outside the edges we set up in the prefs.
    251  testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality");
    252  testMouseClick("t", 100 + 11*mm, 10, "t", "basic functionality");
    253  testMouseClick("t", 10, 80 + 5*mm, "body", "basic functionality");
    254  testMouseClick("t", 10, 80 + 3*mm, "t", "basic functionality");
    255  testMouseClick("t", -5*mm, 10, "body", "basic functionality");
    256  testMouseClick("t", -3*mm, 10, "t", "basic functionality");
    257  testMouseClick("t", 10, -5*mm, "body", "basic functionality");
    258  testMouseClick("t", 10, -3*mm, "t", "basic functionality");
    259 
    260  // When inputSource.touchOnly is true, mouse input is not retargeted.
    261  SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", true]]}, test2);
    262 }
    263 
    264 function test2() {
    265  testMouseClick("t", 100 + 11*mm, 10, "body", "disabled for mouse input");
    266  testMouseClick("t", 100 + 11*mm, 10, "t", "enabled for touch input", {
    267    inputSource: MouseEvent.MOZ_SOURCE_TOUCH
    268  });
    269  testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality for touch", {
    270    inputSource: MouseEvent.MOZ_SOURCE_TOUCH
    271  });
    272  SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", false]]}, test3);
    273 }
    274 
    275 function test3() {
    276  setShowing("t", false);
    277 
    278  // Now test the criteria we use to determine which elements are hittable
    279  // this way.
    280 
    281  setShowing("t2", true);
    282  var t2 = document.getElementById("t2");
    283  // Unadorned DIVs are not click radius targets
    284  testMouseClick("t2", 100 + 11*mm, 10, "body", "unadorned DIV");
    285  // DIVs with the right event handlers are click radius targets
    286  t2.onmousedown = function() {};
    287  testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmousedown");
    288  t2.onmousedown = null;
    289  testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onmousedown removed");
    290  t2.onmouseup = function() {};
    291  testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmouseup");
    292  t2.onmouseup = null;
    293  t2.onclick = function() {};
    294  testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onclick");
    295  t2.onclick = null;
    296  t2.onpointerdown = function() {};
    297  testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onpointerdown");
    298  t2.onpointerdown = null;
    299  testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onpointerdown removed");
    300  t2.onpointerup = function() {};
    301  testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onpointerup");
    302  t2.onpointerup = null;
    303  testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onpointerup removed");
    304  // Keypresses don't make click radius targets
    305  t2.onkeypress = function() {};
    306  testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onkeypress");
    307  t2.onkeypress = null;
    308  setShowing("t2", false);
    309 
    310  // Now check that certain elements are click radius targets and others are not
    311  for (var i = 1; i <= 9; ++i) {
    312    var id = "t3_" + i;
    313    var shouldHit = i <= 8;
    314    setShowing(id, true);
    315    testMouseClick(id, 100 + 11*mm, 10, shouldHit ? id : "body",
    316                   "<" + document.getElementById(id).tagName + "> element");
    317    setShowing(id, false);
    318  }
    319 
    320  // Check that our targeting computations take into account the effects of
    321  // CSS transforms
    322  setShowing("t4", true);
    323  testMouseClick("t4", -1, 10, "t4", "translated DIV");
    324  setShowing("t4", false);
    325 
    326  // Test the prioritization of multiple targets based on distance to
    327  // the target.
    328  setShowing("t5_left", true);   // 0-99    - 0-99
    329  setShowing("t5_right", true);  // 111-210 - 0-99
    330  setShowing("t5_below", true);  // 0-99    - 111-210
    331  info(
    332    `t5:\n${stringifyBoundingClientRect("t5_left")}\n${
    333      stringifyBoundingClientRect("t5_right")
    334    }\n${stringifyBoundingClientRect("t5_below")}`
    335  );
    336 
    337  testMouseClick("t5_left", 102, 10, "t5_left", "closest DIV is left");
    338  testMouseClick("t5_left", 107, 10, "t5_right", "closest DIV is right");
    339  testMouseClick("t5_left", 10, 102, "t5_left", "closest DIV is left");
    340  testMouseClick("t5_left", 10, 107, "t5_below", "closest DIV is below");
    341  // We'd like to check the behavior when clicking exactly center of 2 elements.
    342  // However, it may be impossible if the resolution is not 1.0 because
    343  // PositionedEventTargeting rounds border-box to device pixels.
    344  if (!kIsAndroid || getTopWindowResolution() == 1.0) {
    345    testMouseClick("t5_left", 105, 10, "t5_left",
    346      "closest DIV to midpoint is left because of its higher z-index");
    347    testMouseClick("t5_left", 10, 105, "t5_left",
    348      "closest DIV to midpoint is left because of its higher z-index");
    349  }
    350  setShowing("t5_left", false);
    351  setShowing("t5_right", false);
    352  setShowing("t5_below", false);
    353 
    354  // Test behavior of nested elements.
    355  // The following behaviors are questionable and may need to be changed.
    356  setShowing("t6", true);
    357  setShowing("t6_outer", true);
    358  info(
    359    `t6:\n${stringifyBoundingClientRect("t6")}\n${
    360      stringifyBoundingClientRect("t6_inner")
    361    }\n${stringifyBoundingClientRect("t6_outer")}\n${
    362      stringifyBoundingClientRect("t6_inner_clickable")
    363    }`
    364  );
    365  testMouseClick("t6_inner", -2, 10, "t6_inner",
    366    "inner element is clickable because its parent is, even when it sticks outside parent");
    367  // FIXME: This test or PositionedEventTargeting or its caller is broken.
    368  // This tries to click a position where the same distance from both t6 and t6_inner.
    369  // However, the axis is not same, along x-axis for t6 but along y-axis for t6_inner.
    370  // This may cause clicking unexpected position which may be near t6 due to the
    371  // rounding issue.
    372  // testMouseClick("t6_inner", 38, -2, "t6_inner",
    373  //   "when outside both inner and parent, but in range of both, the inner is selected");
    374  testMouseClick("t6_inner", 45, -2, "t6",
    375    "clicking in clickable parent close to inner shouldn't get retargeted to the inner because of not clickable");
    376  testMouseClick("t6_inner_clickable", 2, -2, "t6_inner",
    377    "clicking on inner doesn't get redirected to inner_clickable because they are both clickable");
    378  testMouseClick("t6_inner_clickable", 2, 2, "t6_inner_clickable",
    379    "clicking on inner_clickable doesn't get redirected to inner because they are both clickable");
    380  testMouseClick("t6_inner_clickable", 45, -2, "t6_inner",
    381    "clicking on inner while backed by its parent still doesn't get redirected to inner_clickable");
    382  testMouseClick("t6_inner_clickable", 45, 2, "t6_inner_clickable",
    383    "clicking on inner_clickable while backed by its parent still doesn't get redirected to inner");
    384  testMouseClick("t6_inner_clickable", 45, 6, "t6_inner_clickable",
    385    "clicking on parent near inner_clickable gets redirected to inner_clickable rather than inner because it is closer");
    386  // 280 is the distance from t6_inner's right edge to t6's right edge
    387  // 240 is the distance from t6_inner's right edge to t6_outer's right edge.
    388  // we want to click on t6, but at least 13mm away from t6_inner, so that
    389  // t6_inner doesn't steal the click.
    390  ok(13*mm < 280, "no point inside t6 that's not within radius of t6_inner; adjust layout of t6/inner/outer as needed");
    391  testMouseClick("t6_outer", -240 + 13*mm, -2, "t6",
    392    "clicking in clickable container close to outer activates parent, not outer");
    393  testMouseClick("t6_outer", 2, 2, "t6_outer",
    394    "clicking directly on the outer activates it");
    395  setShowing("t6", false);
    396  setShowing("t6_outer", false);
    397 
    398  setShowing("t7", true);
    399  setShowing("t7_over", true);
    400  testMouseClick("t7", 100 + 11*mm, 10, "body", "covered div is not clickable");
    401  testMouseClick("t7", 10, 10, "t7_over", "covered div is not clickable even within its bounds");
    402  setShowing("t7", false);
    403  setShowing("t7_over", false);
    404 
    405  // Check that contenteditable elements are considered clickable for fluffing.
    406  setShowing("t8", true);
    407  var rect = document.getElementById("t8").getBoundingClientRect();
    408  testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for mouse input");
    409  testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for touch input", {
    410    inputSource: MouseEvent.MOZ_SOURCE_TOUCH
    411  });
    412  setShowing("t8", false);
    413 
    414  // Check that elements are touchable
    415  setShowing("t9", true);
    416  var rect = document.getElementById("t9").getBoundingClientRect();
    417  testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with mouse input");
    418  testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with touch input", {
    419    inputSource: MouseEvent.MOZ_SOURCE_TOUCH
    420  });
    421  setShowing("t9", false);
    422 
    423  setShowing("t10_over", true);
    424  setShowing("t10_left", true);
    425  setShowing("t10_right", true);
    426  setShowing("t10_top", true);
    427  setShowing("t10_bottom", true);
    428  testMouseClick("t10_left", 51, 10, "t10_over", "element outside of visible area is not selected");
    429  if (self.frameElement &&
    430      (self.frameElement.offsetLeft + self.innerWidth >
    431       SpecialPowers.wrap(top).innerWidth)) {
    432    info("WARNING: Window is too narrow, can't test t10_right");
    433  } else {
    434    testMouseClick("t10_right", 49, 10, "t10_over", "element outside of visible area is not selected");
    435  }
    436  testMouseClick("t10_top", 10, 51, "t10_over", "element outside of visible area is not selected");
    437  if (self.frameElement &&
    438      (self.frameElement.offsetTop + self.innerHeight >
    439       SpecialPowers.wrap(top).innerHeight)) {
    440    info("WARNING: Window is too short, can't test t10_bottom");
    441  } else {
    442    testMouseClick("t10_bottom", 10, 49, "t10_over", "element outside of visible area is not selected");
    443  }
    444  setShowing("t10_over", false);
    445  setShowing("t10_left", false);
    446  setShowing("t10_right", false);
    447  setShowing("t10_top", false);
    448  setShowing("t10_bottom", false);
    449 
    450  setShowing("t11", true);
    451  testMouseClick("t11", 100 + 11*mm, 10, "t11",
    452                 "Elements with cursor:pointer are fluff targets");
    453  setShowing("t11", false);
    454 
    455  setShowing("t11_with_child", true);
    456  testMouseClick("t11_with_child", 100 + 11*mm, 10, "t11_child",
    457                 "Elements that inherit cursor:pointer are fluff targets");
    458  setShowing("t11_with_child", false);
    459 
    460  setShowing("t11_covered", true);
    461  testMouseClick("t11_covered", 100 + 11*mm, 10, "body",
    462                 "Elements that override an inherited cursor:pointer are not fluff targets");
    463  setShowing("t11_covered", false);
    464 
    465  setShowing("t12", true);
    466  testMouseClick("t12_target", 1, 1, "t12_target",
    467                 "Event retargeting should not escape out from a z-index ancestor");
    468  setShowing("t12", false);
    469 
    470  // Click empty area of textarea
    471  let textarea = document.getElementById("t3_6");
    472  textarea.value = "foo bar baz\nfoo"
    473  textarea.style.height = "3.3em";
    474  textarea.style.lineHeight = "1.1";
    475  setShowing("t3_6", true);
    476  textarea.selectionStart = 0;
    477  synthesizeMouseAtCenter(textarea, {});
    478  is(textarea.selectionStart, textarea.value.length,
    479     "selection should be set to last character");
    480 
    481  textarea.value = ""
    482  textarea.style.height = "auto";
    483  textarea.style.lineHeight = "";
    484  setShowing("t3_6", false);
    485 
    486  // Not yet tested:
    487  // -- visited link weight
    488  // -- "Closest" using Euclidean distance
    489 
    490 
    491  SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 1],
    492                                     ["ui.touch.radius.enabled", true],
    493                                     ["ui.touch.radius.leftmm", 12],
    494                                     ["ui.touch.radius.topmm", 4],
    495                                     ["ui.touch.radius.rightmm", 4],
    496                                     ["ui.touch.radius.bottommm", 4],
    497                                     ["ui.touch.radius.visitedweight", 50]]}, testTouchable);
    498 }
    499 
    500 async function testTouchable() {
    501  const kClickableIsTouchableForSingleTap =
    502    SpecialPowers.getBoolPref("ui.touch.radius.single_touch.treat_clickable_as_touchable");
    503  // Element "t" has a mousedown listener but no touch listener.  However, the
    504  // touch target will be captured implicitly if nobody sets pointer capture
    505  // explicitly.  Additionally, the fallback click event target will be the
    506  // implicitly capture element.  Therefore, the touches that land immediately
    507  // outside "t" should hit "t".
    508  setShowing("t", true);
    509  var rect = document.getElementById("t").getBoundingClientRect();
    510  testTouch("t", rect.width - 1, 10, "t", "touch inside t right edge");
    511  testTouch(
    512    "t",
    513    rect.width + 1,
    514    10,
    515    kClickableIsTouchableForSingleTap ? "t" : "body",
    516    "touch outside t right edge"
    517  );
    518  testTouch("t", 10, rect.height - 1, "t", "touch inside t bottom edge");
    519  testTouch(
    520    "t",
    521    10,
    522    rect.height + 1,
    523    kClickableIsTouchableForSingleTap ? "t" : "body",
    524    "touch outside t bottom edge"
    525  );
    526  testTouch("t", 1, 10, "t", "touch inside t left edge");
    527  testTouch(
    528    "t",
    529    -1,
    530    10,
    531    kClickableIsTouchableForSingleTap ? "t" : "body",
    532    "touch outside t left edge"
    533  );
    534  testTouch("t", 10, 1, "t", "touch inside t top edge");
    535  testTouch(
    536    "t",
    537    10,
    538    -1,
    539    kClickableIsTouchableForSingleTap ? "t" : "body",
    540    "touch outside t top edge"
    541  );
    542  setShowing("t", false);
    543 
    544  // Element "t9" has a touchend listener, so touches within the radius
    545  // distance from it should hit it.
    546  setShowing("t9", true);
    547  testTouch("t9", -5*mm, 10, "body", "touch outside t9 left edge radius");
    548  testTouch("t9", -3*mm, 10, "t9", "touch inside t9 left edge radius");
    549  setShowing("t9", false);
    550 
    551  // Element "t13" is clickable, so touches on descendants should not get retargeted.
    552  // In particular, the touch that lands on t13_notouchlistener but within the touch radius
    553  // of t13_touchlistener should not get retargeted.
    554  setShowing("t13", true);
    555  testTouch("t13", 10, 50 + (2*mm), "t13_notouchlistener", "touch outside t13_touchlistener bottom edge");
    556  testTouch("t13", 10, 50 - (2*mm), "t13_touchlistener", "touch inside t13_touchlistener bottom edge");
    557  setShowing("t13", false);
    558 
    559  // Element "t15" is clickable.  When both inside/outside its touchable child
    560  // should get retargeted to the touchable child since the touch event listener
    561  // is rawer input event listener than mouse event listener.
    562  // XXX It might be better to use the parent.  If you know some web-compat
    563  // issues, feel free to change these expectations.
    564  setShowing("t15", true);
    565  rect = document.getElementById("t15_touchListener").getBoundingClientRect();
    566  testTouch("t15_touchListener", rect.width - 1, 10, "t15_touchListener", "touch inside t15_touchListener right edge");
    567  testTouch("t15_touchListener", rect.width + 1, 10, "t15_touchListener", "touch outside t15_touchListener right edge");
    568  testTouch("t15_touchListener", 10, rect.height - 1, "t15_touchListener", "touch inside t15_touchListener bottom edge");
    569  testTouch("t15_touchListener", 10, rect.height + 1, "t15_touchListener", "touch outside t15_touchListener bottom edge");
    570  testTouch("t15_touchListener", 1, 10, "t15_touchListener", "touch inside t15_touchListener left edge");
    571  testTouch("t15_touchListener", -1, 10, "t15_touchListener", "touch outside t15_touchListener left edge");
    572  testTouch("t15_touchListener", 10, 1, "t15_touchListener", "touch inside t15_touchListener top edge");
    573  testTouch("t15_touchListener", 10, -1, "t15_touchListener", "touch outside t15_touchListener top edge");
    574  setShowing("t15", false);
    575 
    576  // Element "t16" is touchable.  When inside its clickable child should get
    577  // retargeted to the parent touchable to prefer the touch event listener since
    578  // it's a rawer input event listener.
    579  // XXX It might be better to use the parent.  If you know some web-compat
    580  // issues, feel free to change these expectations.
    581  setShowing("t16", true);
    582  rect = document.getElementById("t16_mouseListener").getBoundingClientRect();
    583  testTouch("t16", rect.width - 1, 10, "t16", "touch inside t16 right edge");
    584  testTouch("t16", rect.width + 1, 10, "t16", "touch outside t16 right edge");
    585  testTouch("t16", 10, rect.height - 1, "t16", "touch inside t16 bottom edge");
    586  testTouch("t16", 10, rect.height + 1, "t16", "touch outside t16 bottom edge");
    587  testTouch("t16", 1, 10, "t16", "touch inside t16 left edge");
    588  testTouch("t16", -1, 10, "t16", "touch outside t16 left edge");
    589  testTouch("t16", 10, 1, "t16", "touch inside t16 top edge");
    590  testTouch("t16", 10, -1, "t16", "touch outside t16 top edge");
    591  setShowing("t16", false);
    592 
    593  // t17_clickable_cover is on top of t17_clickable_partially_overlapped except its left side.
    594  // Therefore, when its right side space is tapped, t17_clickable_partially_overlapped should not
    595  // be clicked because the web app tries to prevent to work it as clickable at least over
    596  // t17_clickable_cover.  However, when t17_clickable_partially_overlapped is directly tapped,
    597  // it should be clickable, of course.
    598  setShowing("t17", true);
    599  rect = document.getElementById("t17_clickable_cover").getBoundingClientRect();
    600  info(
    601    `t17:\n${stringifyBoundingClientRect("t17_clickable_cover")}\n${
    602      stringifyBoundingClientRect("t17_clickable_partially_overlapped")
    603    }`
    604  );
    605  testTouch("t17_clickable_cover", rect.width + 2, 10, "t17_clickable_cover",
    606            "touch right side of the cover should cause a click on the cover");
    607  testTouch("t17_clickable_cover", -2, 10, "t17_clickable_partially_overlapped",
    608            "touch left side of the cover should cause a click on the overlapped content");
    609  setShowing("t17", false);
    610 
    611  // The counter testcase against t17.  This is not a realistic case at least this DOM structure and
    612  // the styling.  However, let's check whether the basic strategy of GetClosest() in
    613  // PositionedEventTargeting.cpp is not broken.  When the former frame,
    614  // t18_clickable_bigger_z_index_but_clipped, has big `z-index` and clipped text frame, the text
    615  // frame is checked before t18_clickable_partially_overlapped.  At this time, the text frame is
    616  // ignored because the nearest point from the tapped position (right side of the container rect).
    617  // However, the rect should not be subtracted from the region because the container element is
    618  // still clickable, i.e., should be tested later.  With current logic, it won't be targeted later.
    619  // However, t18_clickable_partially_overlapped should not be clicked because it's overlapped by
    620  // t18_clickable_bigger_z_index_but_clipped.
    621  setShowing("t18", true);
    622  rect = document.getElementById("t18_clickable_bigger_z_index_but_clipped").getBoundingClientRect();
    623  testTouch("t18_clickable_bigger_z_index_but_clipped", rect.width + 1, 10,
    624            // We should not acceptable "t18_clickable_partially_overlapped" because the web app
    625            // tries to hide it with "t18_clickable_bigger_z_index_but_clipped".
    626            ["body", null],
    627            "touch right side of the cover should cause a click on the cover");
    628  testTouch("t18_clickable_bigger_z_index_but_clipped", -1, 10, "t18_clickable_partially_overlapped",
    629            "touch left side of the cover should cause a click on the overlapped content");
    630  setShowing("t18", false);
    631 
    632  // If the user taps around a child element which is a clickable/touchable descendant of the
    633  // directly tapped element, we should not redirect to the inner element because the event listener
    634  // may work with the event target strictly. See bug 1978818.
    635  // On the other hand, if the child is another clickable/touchable element, it should be preferred
    636  // to make it easier to tap/click the smaller element.  Perhaps, the user does not tap there to
    637  // do something with the outer element.  This case is tested in t6 and t15.
    638  setShowing("t19", true);
    639  rect = document.getElementById("t19_child").getBoundingClientRect();
    640  testTouch("t19_child", -1, 10, "t19", "tap left of the span should not cause a click on the span");
    641  testTouch("t19_child", 10, -1, "t19", "tap above of the span should not cause a click on the span");
    642  testTouch("t19_child", rect.width + 1, 10, "t19", "tap right of the span should not cause a click on the span");
    643  testTouch("t19_child", 10, rect.height + 1, "t19", "tap bottom of the span should not cause a click on the span");
    644  testTouch("t19_child", 10, 10, "t19_child", "tap in the child span should cause a click on the span");
    645  setShowing("t19", false);
    646 
    647  // A variant of t19. `cursor: pointer` implies that it's clickable.  However, we should consider
    648  // whether it's an independent clickable element or not in an clickable element only with the
    649  // element type and the event listener.
    650  setShowing("t20", true);
    651  rect = document.getElementById("t20_child").getBoundingClientRect();
    652  testTouch("t20_child", -1, 10, "t20", "tap left of the span should not cause a click on the span");
    653  testTouch("t20_child", 10, -1, "t20", "tap above of the span should not cause a click on the span");
    654  testTouch("t20_child", rect.width + 1, 10, "t20", "tap right of the span should not cause a click on the span");
    655  testTouch("t20_child", 10, rect.height + 1, "t20", "tap bottom of the span should not cause a click on the span");
    656  testTouch("t20_child", 10, 10, "t20_child", "tap in the child span should cause a click on the span");
    657  setShowing("t20", false);
    658 
    659  // A variant of t19 and t20. `cursor: pointer` is set to t21_child and t21 itself is a clickable
    660  // element.  Then, t21_child might be different target from t21.  Therefore, tapping around
    661  // t21_child should be retargeted to t21_child.
    662  setShowing("t21", true);
    663  rect = document.getElementById("t21_child").getBoundingClientRect();
    664  testTouch("t21_child", -1, 10, "t21_child", "tap left of the span should cause a click on the span");
    665  testTouch("t21_child", 10, -1, "t21_child", "tap above of the span should cause a click on the span");
    666  testTouch("t21_child", rect.width + 1, 10, "t21_child", "tap right of the span should cause a click on the span");
    667  testTouch("t21_child", 10, rect.height + 1, "t21_child", "tap bottom of the span should cause a click on the span");
    668  testTouch("t21_child", 10, 10, "t21_child", "tap in the child span should cause a click on the span");
    669  setShowing("t21", false);
    670 
    671  // https://bugzilla.mozilla.org/show_bug.cgi?id=1733509
    672  await waitUntilApzStable();
    673  setShowing("t14", true);
    674  // Skip non-Fission for now because of bug 1890522
    675  if (SpecialPowers.Services.appinfo.fissionAutostart) {
    676    let iframeURL = SimpleTest.getTestFileURL("helper_bug1733509.html");
    677    const iframe = document.querySelector("#t14iframe");
    678    iframeURL = iframeURL.replace(window.location.origin, "https://example.com");
    679    await setupIframe(iframe, iframeURL);
    680    // todo: Also perform this test for an in-process iframe
    681    // once bug 1890522 is fixed.
    682    if (SpecialPowers.wrap(iframe).frameLoader.isRemoteFrame) {
    683      const result = await SpecialPowers.spawn(iframe, [], async () => {
    684        await content.wrappedJSObject.waitUntilApzStable();
    685        var iframeEventTarget = null;
    686        content.window.onmousedown = function (event) { iframeEventTarget = event.target; };
    687        content.wrappedJSObject.synthesizeMouse(content.document.documentElement, 2, 2, {});
    688        if (!iframeEventTarget) {
    689          return nullptr;
    690        }
    691        return `<${iframeEventTarget.localName}${
    692          iframeEventTarget.id ? ` id="${iframeEventTarget.id}"` : ""
    693        }>`;
    694      });
    695      is(
    696        result,
    697        `<button id="btn">`,
    698        "Failed to target button inside iframe"
    699      );
    700    }
    701  }
    702  setShowing("t14", false);
    703 
    704  endTest();
    705 }
    706 
    707 async function setupIframe(aIFrame, aURL) {
    708  const iframeLoadPromise = promiseOneEvent(aIFrame, "load", null);
    709  aIFrame.src = aURL;
    710  await iframeLoadPromise;
    711 
    712  await SpecialPowers.spawn(aIFrame, [], async () => {
    713    await content.wrappedJSObject.waitUntilApzStable();
    714    await SpecialPowers.contentTransformsReceived(content);
    715  });
    716 }
    717 </script>
    718 </pre>
    719 </body>
    720 </html>