tor-browser

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

pointer-events.js (10145B)


      1 SimpleTest.waitForExplicitFinish();
      2 
      3 var pointer_events_values = [
      4  "auto",
      5  "visiblePainted",
      6  "visibleFill",
      7  "visibleStroke",
      8  "visible",
      9  "painted",
     10  "fill",
     11  "stroke",
     12  "all",
     13  "none",
     14 ];
     15 
     16 var paint_values = ["blue", "transparent", "none"];
     17 
     18 var opacity_values = ["1", "0.5", "0"];
     19 
     20 var visibility_values = ["visible", "hidden", "collapse"];
     21 
     22 /**
     23 * List of attributes and various values for which we want to test permutations
     24 * when hit testing a pointer event that is over an element's fill area,
     25 * stroke area, or both (where they overlap).
     26 *
     27 * We're using an array of objects so that we have control over the order in
     28 * which permutations are tested.
     29 *
     30 * TODO: test the effect of clipping, masking, filters, markers, etc.
     31 */
     32 var hit_test_inputs = {
     33  fill: [
     34    { name: "pointer-events", values: pointer_events_values },
     35    { name: "fill", values: paint_values },
     36    { name: "fill-opacity", values: opacity_values },
     37    { name: "opacity", values: opacity_values },
     38    { name: "visibility", values: visibility_values },
     39  ],
     40  stroke: [
     41    { name: "pointer-events", values: pointer_events_values },
     42    { name: "stroke", values: paint_values },
     43    { name: "stroke-opacity", values: opacity_values },
     44    { name: "opacity", values: opacity_values },
     45    { name: "visibility", values: visibility_values },
     46  ],
     47  both: [
     48    { name: "pointer-events", values: pointer_events_values },
     49    { name: "fill", values: paint_values },
     50    { name: "fill-opacity", values: opacity_values },
     51    { name: "stroke", values: paint_values },
     52    { name: "stroke-opacity", values: opacity_values },
     53    { name: "opacity", values: opacity_values },
     54    { name: "visibility", values: visibility_values },
     55  ],
     56 };
     57 
     58 /**
     59 * The following object contains a list of 'pointer-events' property values,
     60 * each with an object detailing the conditions under which the fill and stroke
     61 * of a graphical object will intercept pointer events for the given value. If
     62 * the object contains a 'fill-intercepts-iff' property then the fill is
     63 * expected to intercept pointer events for that value of 'pointer-events' if
     64 * and only if the conditions listed in the 'fill-intercepts-iff' object are
     65 * met. If there are no conditions in the 'fill-intercepts-iff' object then the
     66 * fill should always intercept pointer events. However, if the
     67 * 'fill-intercepts-iff' property is not set at all then it indicates that the
     68 * fill should never intercept pointer events. The same rules apply for
     69 * 'stroke-intercepts-iff'.
     70 *
     71 * If an attribute name in the conditions list is followed by the "!"
     72 * character then the requirement for a hit is that its value is NOT any
     73 * of the values listed in the given array.
     74 */
     75 var hit_conditions = {
     76  auto: {
     77    "fill-intercepts-iff": {
     78      visibility: ["visible"],
     79      "fill!": ["none"],
     80    },
     81    "stroke-intercepts-iff": {
     82      visibility: ["visible"],
     83      "stroke!": ["none"],
     84    },
     85  },
     86  visiblePainted: {
     87    "fill-intercepts-iff": {
     88      visibility: ["visible"],
     89      "fill!": ["none"],
     90    },
     91    "stroke-intercepts-iff": {
     92      visibility: ["visible"],
     93      "stroke!": ["none"],
     94    },
     95  },
     96  visibleFill: {
     97    "fill-intercepts-iff": {
     98      visibility: ["visible"],
     99    },
    100    // stroke never intercepts pointer events
    101  },
    102  visibleStroke: {
    103    // fill never intercepts pointer events
    104    "stroke-intercepts-iff": {
    105      visibility: ["visible"],
    106    },
    107  },
    108  visible: {
    109    "fill-intercepts-iff": {
    110      visibility: ["visible"],
    111    },
    112    "stroke-intercepts-iff": {
    113      visibility: ["visible"],
    114    },
    115  },
    116  painted: {
    117    "fill-intercepts-iff": {
    118      "fill!": ["none"],
    119    },
    120    "stroke-intercepts-iff": {
    121      "stroke!": ["none"],
    122    },
    123  },
    124  fill: {
    125    "fill-intercepts-iff": {
    126      // fill always intercepts pointer events
    127    },
    128    // stroke never intercepts pointer events
    129  },
    130  stroke: {
    131    // fill never intercepts pointer events
    132    "stroke-intercepts-iff": {
    133      // stroke always intercepts pointer events
    134    },
    135  },
    136  all: {
    137    "fill-intercepts-iff": {
    138      // fill always intercepts pointer events
    139    },
    140    "stroke-intercepts-iff": {
    141      // stroke always intercepts pointer events
    142    },
    143  },
    144  none: {
    145    // neither fill nor stroke intercept pointer events
    146  },
    147 };
    148 
    149 // bit flags
    150 var POINT_OVER_FILL = 0x1;
    151 var POINT_OVER_STROKE = 0x2;
    152 
    153 /**
    154 * Examine the element's attribute values and, based on the area(s) of the
    155 * element that the pointer event is over (fill and/or stroke areas), return
    156 * true if the element is expected to intercept the event, otherwise false.
    157 */
    158 function hit_expected(
    159  element,
    160  over /* bit flags indicating which area(s) of the element the pointer is over */
    161 ) {
    162  function expect_hit(target) {
    163    var intercepts_iff =
    164      hit_conditions[element.getAttribute("pointer-events")][
    165        target + "-intercepts-iff"
    166      ];
    167 
    168    if (!intercepts_iff) {
    169      return false; // never intercepts events
    170    }
    171 
    172    for (var attr in intercepts_iff) {
    173      var vals = intercepts_iff[attr]; // must get this before we adjust 'attr'
    174      var invert = false;
    175      if (attr.substr(-1) == "!") {
    176        invert = true;
    177        attr = attr.substr(0, attr.length - 1);
    178      }
    179      var match = vals.indexOf(element.getAttribute(attr)) > -1;
    180      if (invert) {
    181        match = !match;
    182      }
    183      if (!match) {
    184        return false;
    185      }
    186    }
    187 
    188    return true;
    189  }
    190 
    191  return (
    192    ((over & POINT_OVER_FILL) != 0 && expect_hit("fill")) ||
    193    ((over & POINT_OVER_STROKE) != 0 && expect_hit("stroke"))
    194  );
    195 }
    196 
    197 function for_all_permutations(inputs, callback) {
    198  var current_permutation = arguments[2] || {};
    199  var index = arguments[3] || 0;
    200 
    201  if (index < inputs.length) {
    202    var name = inputs[index].name;
    203    var values = inputs[index].values;
    204    for (var i = 0; i < values.length; ++i) {
    205      current_permutation[name] = values[i];
    206      for_all_permutations(inputs, callback, current_permutation, index + 1);
    207    }
    208    return;
    209  }
    210 
    211  callback(current_permutation);
    212 }
    213 
    214 function make_log_msg(over, tag, attributes) {
    215  var target;
    216  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
    217    target = "fill and stroke";
    218  } else if (over == POINT_OVER_FILL) {
    219    target = "fill";
    220  } else if (over == POINT_OVER_STROKE) {
    221    target = "stroke";
    222  } else {
    223    throw new Error("unexpected bit combination in 'over'");
    224  }
    225  var msg =
    226    "Check if events are intercepted at a point over the " +
    227    target +
    228    " on <" +
    229    tag +
    230    "> for";
    231  for (var attr in attributes) {
    232    msg += " " + attr + "=" + attributes[attr];
    233  }
    234  return msg;
    235 }
    236 
    237 var dx, dy; // offset of <svg> element from pointer coordinates origin
    238 
    239 function test_element(
    240  id,
    241  x,
    242  y,
    243  over /* bit flags indicating which area(s) of the element the pointer is over */
    244 ) {
    245  var element = document.getElementById(id);
    246  var tag = element.tagName;
    247 
    248  function test_permutation(attributes) {
    249    for (var attr in attributes) {
    250      element.setAttribute(attr, attributes[attr]);
    251    }
    252    var hits = document.elementFromPoint(dx + x, dy + y) == element;
    253    var msg = make_log_msg(over, tag, attributes);
    254 
    255    is(hits, hit_expected(element, over), msg);
    256  }
    257 
    258  var inputs;
    259  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
    260    inputs = hit_test_inputs.both;
    261  } else if (over == POINT_OVER_FILL) {
    262    inputs = hit_test_inputs.fill;
    263  } else if (over == POINT_OVER_STROKE) {
    264    inputs = hit_test_inputs.stroke;
    265  } else {
    266    throw new Error("unexpected bit combination in 'over'");
    267  }
    268 
    269  for_all_permutations(inputs, test_permutation);
    270 
    271  // To reduce the chance of bogus results in subsequent tests:
    272  element.setAttribute("fill", "none");
    273  element.setAttribute("stroke", "none");
    274 }
    275 
    276 function run_tests(subtest) {
    277  var div = document.getElementById("div");
    278  dx = div.offsetLeft;
    279  dy = div.offsetTop;
    280 
    281  // Run the test with only a subset of pointer-events values, to avoid
    282  // running over the mochitest time limit.  The subtest argument indicates
    283  // whether to use the first half of the pointer-events values (0)
    284  // or the second half (1).
    285  var partition = Math.floor(pointer_events_values.length / 2);
    286  switch (subtest) {
    287    case 0:
    288      pointer_events_values.splice(partition);
    289      break;
    290    case 1:
    291      pointer_events_values.splice(0, partition);
    292      break;
    293    case 2:
    294      throw new Error("unexpected subtest number");
    295  }
    296 
    297  test_element("rect", 30, 30, POINT_OVER_FILL);
    298  test_element("rect", 5, 5, POINT_OVER_STROKE);
    299 
    300  // The SVG 1.1 spec essentially says that, for text, hit testing is done
    301  // against the character cells of the text, and not the fill and stroke as
    302  // you might expect for a normal graphics element like <path>. See the
    303  // paragraph starting "For text elements..." in this section:
    304  //
    305  //   http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
    306  //
    307  // This requirement essentially means that for the purposes of hit testing
    308  // the fill and stroke areas are the same area - the character cell. (At
    309  // least until we support having any fill or stroke that lies outside the
    310  // character cells intercept events like Opera does - see below.) Thus, for
    311  // text, when a pointer event is over a character cell it is essentially over
    312  // both the fill and stroke at the same time. That's the reason we pass both
    313  // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
    314  // argument below. It's also the reason why we only test one point in the
    315  // text rather than having separate tests for fill and stroke.
    316  //
    317  // For hit testing of text, Opera essentially treats fill and stroke like it
    318  // would on any normal element, but it adds the character cells of glyhs to
    319  // both the glyphs' fill AND stroke. I think this is what we should do too.
    320  // It's compatible with the letter of the SVG 1.1 rules, and it allows any
    321  // parts of a glyph that are outside the glyph's character cells to also
    322  // intercept events in the normal way. When we make that change we'll be able
    323  // to add separate fill and stroke tests for text below.
    324 
    325  test_element("text", 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
    326 
    327  SimpleTest.finish();
    328 }