tor-browser

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

property-reflection-helper.js (11526B)


      1 const Behavior = Object.freeze({
      2  ReflectsHost: 'ReflectsHost',
      3  ReflectsHostReadOnly: 'ReflectsHostReadOnly',
      4  ReflectsHostInArray: 'ReflectsHostInArray',
      5  IsNull: 'IsNull',
      6  ReflectsHostID: 'ReflectsHostID',
      7  ReflectsHostIDInDOMTokenList: 'ReflectsHostIDInDOMTokenList',
      8 });
      9 
     10 // We want to test types of elements that are associated with properties that can reflect other
     11 // elements and can therefore interact with reference target in interesting ways.
     12 // The HTML5_LABELABLE_ELEMENTS are defined in https://html.spec.whatwg.org/#category-label,
     13 // while non_labelable_element_types is a manually curated list of other elements with
     14 // reflecting properties (plus div as representative of more "normal" elements).
     15 // We'll test all permutations of these element types being both the referencing element
     16 // pointing into the reference target shadow host, and being the referenced element inside
     17 // the shadow.
     18 const non_labelable_element_types = ["div", "object", "label", "fieldset", "legend", "option", "datalist", "form"];
     19 const element_types = HTML5_LABELABLE_ELEMENTS.concat(non_labelable_element_types);
     20 
     21 function test_property_reflection(element_creation_method, test_name_suffix, referencing_element_type, referenced_element_type, attribute, reflected_property, expected_behavior) {
     22  // There's nothing to test if the referencing element type doesn't have the reflecting
     23  // property.
     24  if (!(reflected_property in document.createElement(referencing_element_type))) {
     25    return;
     26  }
     27 
     28  test(function () {
     29    const referencing_element = document.createElement(referencing_element_type);
     30    document.body.appendChild(referencing_element);
     31    referencing_element.setAttribute(attribute, "host-id");
     32    const host_container = document.querySelector("#host-container");
     33    const host = element_creation_method(host_container, referenced_element_type);
     34    if (expected_behavior === Behavior.ReflectsHost || expected_behavior === Behavior.ReflectsHostReadOnly) {
     35      assert_equals(referencing_element[reflected_property], host);
     36    } else if (expected_behavior === Behavior.ReflectsHostInArray) {
     37      assert_array_equals(referencing_element[reflected_property], [host]);
     38    } else if (expected_behavior === Behavior.IsNull) {
     39      assert_equals(referencing_element[reflected_property], null);
     40    } else if (expected_behavior === Behavior.ReflectsHostID) {
     41      assert_equals(referencing_element[reflected_property], "host-id");
     42    } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) {
     43      assert_true(referencing_element[reflected_property] instanceof DOMTokenList);
     44      assert_array_equals(Array.from(referencing_element[reflected_property]), ["host-id"]);
     45    }
     46    referencing_element.remove();
     47    host_container.setHTMLUnsafe("");
     48  }, `${referencing_element_type}.${reflected_property} has reflection behavior ${expected_behavior} when pointing to ${referenced_element_type} with reference target${test_name_suffix}`);
     49 }
     50 
     51 function test_idl_setter(element_creation_method, test_name_suffix, referencing_element_type, referenced_element_type, attribute, reflected_property, expected_behavior) {
     52  // There's nothing to test if the referencing element type doesn't have the reflecting
     53  // property.
     54  if (!(reflected_property in document.createElement(referencing_element_type))) {
     55    return;
     56  }
     57 
     58  test(function () {
     59     const referencing_element = document.createElement(referencing_element_type);
     60     document.body.appendChild(referencing_element);
     61    const host_container = document.querySelector("#host-container");
     62    const host = element_creation_method(host_container, referenced_element_type);
     63 
     64    if (reflected_property === "ariaOwnsElements") {
     65      // It's undetermined whether reference target should work with aria-owns or not; see
     66      // https://github.com/WICG/webcomponents/issues/1091 and
     67      // https://github.com/w3c/aria/issues/2266
     68      return;
     69    }
     70 
     71    if (expected_behavior === Behavior.ReflectsHost) {
     72      referencing_element[reflected_property] = host;
     73      // For element reflecting properties, the IDL getter should return null when the explicitly
     74      // set element has an invalid reference target.
     75      assert_equals(referencing_element[reflected_property], null);
     76    } else if (expected_behavior === Behavior.ReflectsHostReadOnly) {
     77      referencing_element[reflected_property] = host;
     78      // Setting a read-only property has no effect.
     79      assert_equals(referencing_element[reflected_property], null);
     80    } else if (expected_behavior === Behavior.ReflectsHostInArray) {
     81      referencing_element[reflected_property] = [ host ];
     82      // For element reflecting properties, the IDL getter should not return explicitly set elements
     83      // if they have an invalid reference target.
     84      assert_array_equals(referencing_element[reflected_property], []);
     85    } else if (expected_behavior === Behavior.IsNull) {
     86      referencing_element[reflected_property] = host;
     87      assert_equals(referencing_element[reflected_property], null);
     88    } else if (expected_behavior === Behavior.ReflectsHostID) {
     89      referencing_element[reflected_property] = "host-id";
     90      // Properties reflecting the host ID return the ID even if it points to an element with
     91      // an invalid reference target.
     92      assert_equals(referencing_element[reflected_property], "host-id");
     93    } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) {
     94      // Properties reflecting a DOMTokenList of IDs returns the IDs even if they point to an
     95      // element with an invalid reference target.
     96      referencing_element[reflected_property] = [ "host-id" ];
     97      assert_true(referencing_element[reflected_property] instanceof DOMTokenList);
     98      assert_array_equals(Array.from(referencing_element[reflected_property]), [ "host-id" ]);
     99    }
    100 
    101    // Set the reference target to a valid value.
    102    host.shadowRoot.referenceTarget = host.shadowRoot.querySelector(referenced_element_type).id;
    103 
    104    if (expected_behavior === Behavior.ReflectsHost) {
    105      // For element reflecting properties, if the reference target becomes valid for the explicitly
    106      // set element, we should start returning that element.
    107      assert_equals(referencing_element[reflected_property], host);
    108    } else if (expected_behavior === Behavior.ReflectsHostReadOnly) {
    109      assert_equals(referencing_element[reflected_property], null);
    110    } else if (expected_behavior === Behavior.ReflectsHostInArray) {
    111      // For element reflecting properties, if the reference target becomes valid for any of the
    112      // explicitly set elements, we should start returning that element.
    113      assert_array_equals(referencing_element[reflected_property], [host]);
    114    } else if (expected_behavior === Behavior.IsNull) {
    115      assert_equals(referencing_element[reflected_property], null);
    116    } else if (expected_behavior === Behavior.ReflectsHostID) {
    117      assert_equals(referencing_element[reflected_property], "host-id");
    118    } else if (expected_behavior === Behavior.ReflectsHostIDInDOMTokenList) {
    119      assert_array_equals(Array.from(referencing_element[reflected_property]), [ "host-id" ]);
    120    }
    121 
    122     referencing_element.remove();
    123     host_container.setHTMLUnsafe("");
    124  }, `${referencing_element_type}.${reflected_property} has IDL setter behavior ${expected_behavior} when pointing to ${referenced_element_type} with reference target${test_name_suffix}`);
    125 }
    126 
    127 function run_test_for_all_reflecting_properties(setup_function, test_function, test_name_suffix) {
    128  for(let referencing_element_type of element_types) {
    129    for(let referenced_element_type of element_types) {
    130      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-controls", "ariaControlsElements", Behavior.ReflectsHostInArray);
    131      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-activedescendant", "ariaActiveDescendantElement", Behavior.ReflectsHost);
    132      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-describedby", "ariaDescribedByElements", Behavior.ReflectsHostInArray);
    133      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-details", "ariaDetailsElements", Behavior.ReflectsHostInArray);
    134      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-errormessage", "ariaErrorMessageElements", Behavior.ReflectsHostInArray);
    135      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-flowto", "ariaFlowToElements", Behavior.ReflectsHostInArray);
    136      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-labelledby", "ariaLabelledByElements", Behavior.ReflectsHostInArray);
    137      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "aria-owns", "ariaOwnsElements", Behavior.ReflectsHostInArray);
    138 
    139      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "anchor", "anchorElement", Behavior.ReflectsHost);
    140      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "commandfor", "commandForElement", Behavior.ReflectsHost);
    141      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "popovertarget", "popoverTargetElement", Behavior.ReflectsHost);
    142      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "interestfor", "interestForElement", Behavior.ReflectsHost);
    143 
    144      const expected_htmlFor_property_behavior = (referencing_element_type == "output") ? Behavior.ReflectsHostIDInDOMTokenList : Behavior.ReflectsHostID;
    145      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "for", "htmlFor", expected_htmlFor_property_behavior);
    146 
    147      // The form property of <label>, <legend>, and <option> reflects the form property of the associated labelable element,
    148      // the associated <fieldset>, and the associated <select>, respectively. Here since we don't have those associated elements,
    149      // the form property would return null.
    150      const expected_form_property_behavior = (referenced_element_type == 'form' &&
    151                                              referencing_element_type != "label" &&
    152                                              referencing_element_type != "legend" &&
    153                                              referencing_element_type != "option") ? Behavior.ReflectsHostReadOnly : Behavior.IsNull;
    154      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "form", "form", expected_form_property_behavior);
    155 
    156      const expected_list_property_behavior = (referenced_element_type == 'datalist') ? Behavior.ReflectsHostReadOnly : Behavior.IsNull;
    157      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "list", "list", expected_list_property_behavior);
    158 
    159      const expected_control_property_behavior = HTML5_LABELABLE_ELEMENTS.includes(referenced_element_type) ? Behavior.ReflectsHostReadOnly : Behavior.IsNull;
    160      test_function(setup_function, test_name_suffix, referencing_element_type, referenced_element_type, "for", "control", expected_control_property_behavior);
    161    }
    162  }
    163 }