tor-browser

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

attribute-changed-callback.html (13522B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <title>Custom Elements: attributeChangedCallback</title>
      5 <meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
      6 <meta name="assert" content="attributeChangedCallback must be enqueued whenever custom element's attribute is added, changed or removed">
      7 <link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-attribute-changed-callback">
      8 <script src="/resources/testharness.js"></script>
      9 <script src="/resources/testharnessreport.js"></script>
     10 <script src="resources/custom-elements-helpers.js"></script>
     11 </head>
     12 <body>
     13 <div id="log"></div>
     14 <parser-created-element title></parser-created-element>
     15 <script>
     16 
     17 var customElement = define_new_custom_element(['title', 'id', 'r']);
     18 
     19 test(function () {
     20    const instance = document.createElement(customElement.name);
     21    assert_array_equals(customElement.takeLog().types(), ['constructed']);
     22 
     23    instance.setAttribute('title', 'foo');
     24    assert_equals(instance.getAttribute('title'), 'foo');
     25    var logEntries = customElement.takeLog();
     26    assert_array_equals(logEntries.types(), ['attributeChanged']);
     27    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'foo', namespace: null});
     28 
     29    instance.removeAttribute('title');
     30    assert_equals(instance.getAttribute('title'), null);
     31    var logEntries = customElement.takeLog();
     32    assert_array_equals(logEntries.types(), ['attributeChanged']);
     33    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'foo', newValue: null, namespace: null});
     34 }, 'setAttribute and removeAttribute must enqueue and invoke attributeChangedCallback');
     35 
     36 test(function () {
     37    var instance = document.createElement(customElement.name);
     38    assert_array_equals(customElement.takeLog().types(), ['constructed']);
     39 
     40    instance.setAttributeNS('http://www.w3.org/2000/svg', 'title', 'hello');
     41    assert_equals(instance.getAttribute('title'), 'hello');
     42    var logEntries = customElement.takeLog();
     43    assert_array_equals(logEntries.types(), ['attributeChanged']);
     44    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: 'hello', namespace: 'http://www.w3.org/2000/svg'});
     45 
     46    instance.removeAttributeNS('http://www.w3.org/2000/svg', 'title');
     47    assert_equals(instance.getAttribute('title'), null);
     48    var logEntries = customElement.takeLog();
     49    assert_array_equals(logEntries.types(), ['attributeChanged']);
     50    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: 'hello', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
     51 }, 'setAttributeNS and removeAttributeNS must enqueue and invoke attributeChangedCallback');
     52 
     53 test(function () {
     54    var instance = document.createElement(customElement.name);
     55    assert_array_equals(customElement.takeLog().types(), ['constructed']);
     56 
     57    var attr = document.createAttribute('id');
     58    attr.value = 'bar';
     59    instance.setAttributeNode(attr);
     60 
     61    assert_equals(instance.getAttribute('id'), 'bar');
     62    var logEntries = customElement.takeLog();
     63    assert_array_equals(logEntries.types(), ['attributeChanged']);
     64    assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: null, newValue: 'bar', namespace: null});
     65 
     66    instance.removeAttributeNode(attr);
     67    assert_equals(instance.getAttribute('id'), null);
     68    var logEntries = customElement.takeLog();
     69    assert_array_equals(logEntries.types(), ['attributeChanged']);
     70    assert_attribute_log_entry(logEntries.last(), {name: 'id', oldValue: 'bar', newValue: null, namespace: null});
     71 }, 'setAttributeNode and removeAttributeNode must enqueue and invoke attributeChangedCallback for an HTML attribute');
     72 
     73 test(function () {
     74    const instance = document.createElement(customElement.name);
     75    assert_array_equals(customElement.takeLog().types(), ['constructed']);
     76 
     77    const attr = document.createAttributeNS('http://www.w3.org/2000/svg', 'r');
     78    attr.value = '100';
     79    instance.setAttributeNode(attr);
     80 
     81    assert_equals(instance.getAttribute('r'), '100');
     82    var logEntries = customElement.takeLog();
     83    assert_array_equals(logEntries.types(), ['attributeChanged']);
     84    assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: null, newValue: '100', namespace: 'http://www.w3.org/2000/svg'});
     85 
     86    instance.removeAttributeNode(attr);
     87    assert_equals(instance.getAttribute('r'), null);
     88    var logEntries = customElement.takeLog();
     89    assert_array_equals(logEntries.types(), ['attributeChanged']);
     90    assert_attribute_log_entry(logEntries.last(), {name: 'r', oldValue: '100', newValue: null, namespace: 'http://www.w3.org/2000/svg'});
     91 }, 'setAttributeNode and removeAttributeNS must enqueue and invoke attributeChangedCallback for an SVG attribute');
     92 
     93 test(function () {
     94    const instance = document.createElement(customElement.name);
     95    assert_array_equals(customElement.takeLog().types(), ['constructed']);
     96 
     97    instance.toggleAttribute('title', true);
     98    assert_equals(instance.hasAttribute('title'), true);
     99    var logEntries = customElement.takeLog();
    100    assert_array_equals(logEntries.types(), ['attributeChanged']);
    101    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: null, newValue: '', namespace: null});
    102 
    103    instance.toggleAttribute('title');
    104    assert_equals(instance.hasAttribute('title'), false);
    105    var logEntries = customElement.takeLog();
    106    assert_array_equals(logEntries.types(), ['attributeChanged']);
    107    assert_attribute_log_entry(logEntries.last(), {name: 'title', oldValue: '', newValue: null, namespace: null});
    108 }, 'toggleAttribute must enqueue and invoke attributeChangedCallback');
    109 
    110 test(function () {
    111    const callsToOld = [];
    112    const callsToNew = [];
    113    class CustomElement extends HTMLElement { }
    114    CustomElement.prototype.attributeChangedCallback = function (...args) {
    115        callsToOld.push(create_attribute_changed_callback_log(this, ...args));
    116    }
    117    CustomElement.observedAttributes = ['title'];
    118    customElements.define('element-with-mutated-attribute-changed-callback', CustomElement);
    119    CustomElement.prototype.attributeChangedCallback = function (...args) {
    120        callsToNew.push(create_attribute_changed_callback_log(this, ...args));
    121    }
    122 
    123    const instance = document.createElement('element-with-mutated-attribute-changed-callback');
    124    instance.setAttribute('title', 'hi');
    125    assert_equals(instance.getAttribute('title'), 'hi');
    126    assert_array_equals(callsToNew, []);
    127    assert_equals(callsToOld.length, 1);
    128    assert_attribute_log_entry(callsToOld[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
    129 }, 'Mutating attributeChangedCallback after calling customElements.define must not affect the callback being invoked');
    130 
    131 test(function () {
    132    const calls = [];
    133    class CustomElement extends HTMLElement {
    134        attributeChangedCallback(...args) {
    135            calls.push(create_attribute_changed_callback_log(this, ...args));
    136        }
    137    }
    138    CustomElement.observedAttributes = ['title'];
    139    customElements.define('element-not-observing-id-attribute', CustomElement);
    140 
    141    const instance = document.createElement('element-not-observing-id-attribute');
    142    assert_equals(calls.length, 0);
    143    instance.setAttribute('title', 'hi');
    144    assert_equals(calls.length, 1);
    145    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
    146    instance.setAttribute('id', 'some');
    147    assert_equals(calls.length, 1);
    148    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
    149 }, 'attributedChangedCallback must not be invoked when the observed attributes does not contain the attribute');
    150 
    151 test(function () {
    152    const calls = [];
    153    class CustomElement extends HTMLElement { }
    154    CustomElement.prototype.attributeChangedCallback = function (...args) {
    155        calls.push(create_attribute_changed_callback_log(this, ...args));
    156    }
    157    CustomElement.observedAttributes = ['title', 'lang'];
    158    customElements.define('element-with-mutated-observed-attributes', CustomElement);
    159    CustomElement.observedAttributes = ['title', 'id'];
    160 
    161    const instance = document.createElement('element-with-mutated-observed-attributes');
    162    instance.setAttribute('title', 'hi');
    163    assert_equals(calls.length, 1);
    164    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hi', namespace: null});
    165 
    166    instance.setAttribute('id', 'some');
    167    assert_equals(calls.length, 1);
    168 
    169    instance.setAttribute('lang', 'en');
    170    assert_equals(calls.length, 2);
    171    assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
    172 }, 'Mutating observedAttributes after calling customElements.define must not affect the set of attributes for which attributedChangedCallback is invoked');
    173 
    174 test(function () {
    175    var calls = [];
    176    class CustomElement extends HTMLElement { }
    177    CustomElement.prototype.attributeChangedCallback = function (...args) {
    178        calls.push(create_attribute_changed_callback_log(this, ...args));
    179    }
    180    CustomElement.observedAttributes = { [Symbol.iterator]: function *() { yield 'lang'; yield 'style'; } };
    181    customElements.define('element-with-generator-observed-attributes', CustomElement);
    182 
    183    var instance = document.createElement('element-with-generator-observed-attributes');
    184    instance.setAttribute('lang', 'en');
    185    assert_equals(calls.length, 1);
    186    assert_attribute_log_entry(calls[0], {name: 'lang', oldValue: null, newValue: 'en', namespace: null});
    187 
    188    instance.setAttribute('lang', 'ja');
    189    assert_equals(calls.length, 2);
    190    assert_attribute_log_entry(calls[1], {name: 'lang', oldValue: 'en', newValue: 'ja', namespace: null});
    191 
    192    instance.setAttribute('title', 'hello');
    193    assert_equals(calls.length, 2);
    194 
    195    instance.setAttribute('style', 'font-size: 2rem');
    196    assert_equals(calls.length, 3);
    197    assert_attribute_log_entry(calls[2], {name: 'style', oldValue: null, newValue: 'font-size: 2rem', namespace: null});
    198 }, 'attributedChangedCallback must be enqueued for attributes specified in a non-Array iterable observedAttributes');
    199 
    200 test(function () {
    201    var calls = [];
    202    class CustomElement extends HTMLElement { }
    203    CustomElement.prototype.attributeChangedCallback = function (...args) {
    204        calls.push(create_attribute_changed_callback_log(this, ...args));
    205    }
    206    CustomElement.observedAttributes = ['style'];
    207    customElements.define('element-with-style-attribute-observation', CustomElement);
    208 
    209    var instance = document.createElement('element-with-style-attribute-observation');
    210    assert_equals(calls.length, 0);
    211 
    212    instance.style.fontSize = '10px';
    213    assert_equals(calls.length, 1);
    214    assert_attribute_log_entry(calls[0], {name: 'style', oldValue: null, newValue: 'font-size: 10px;', namespace: null});
    215 
    216    instance.style.fontSize = '20px';
    217    assert_equals(calls.length, 2);
    218    assert_attribute_log_entry(calls[1], {name: 'style', oldValue: 'font-size: 10px;', newValue: 'font-size: 20px;', namespace: null});
    219 
    220 }, 'attributedChangedCallback must be enqueued for style attribute change by mutating inline style declaration');
    221 
    222 test(function () {
    223    var calls = [];
    224    class CustomElement extends HTMLElement { }
    225    CustomElement.prototype.attributeChangedCallback = function (...args) {
    226        calls.push(create_attribute_changed_callback_log(this, ...args));
    227    }
    228    CustomElement.observedAttributes = ['title'];
    229    customElements.define('element-with-no-style-attribute-observation', CustomElement);
    230 
    231    var instance = document.createElement('element-with-no-style-attribute-observation');
    232    assert_equals(calls.length, 0);
    233    instance.style.fontSize = '10px';
    234    assert_equals(calls.length, 0);
    235    instance.title = 'hello';
    236    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: 'hello', namespace: null});
    237 }, 'attributedChangedCallback must not be enqueued when mutating inline style declaration if the style attribute is not observed');
    238 
    239 test(function () {
    240    var calls = [];
    241    class CustomElement extends HTMLElement { }
    242    CustomElement.prototype.attributeChangedCallback = function (...args) {
    243        calls.push(create_attribute_changed_callback_log(this, ...args));
    244    }
    245    CustomElement.observedAttributes = ['title'];
    246    customElements.define('parser-created-element', CustomElement);
    247    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
    248 }, 'Upgrading a parser created element must enqueue and invoke attributeChangedCallback for an HTML attribute');
    249 
    250 test(function () {
    251    var calls = [];
    252    class CustomElement extends HTMLElement { }
    253    CustomElement.prototype.attributeChangedCallback = function (...args) {
    254        calls.push(create_attribute_changed_callback_log(this, ...args));
    255    }
    256    CustomElement.observedAttributes = ['title'];
    257    customElements.define('cloned-element-with-attribute', CustomElement);
    258 
    259    var instance = document.createElement('cloned-element-with-attribute');
    260    assert_equals(calls.length, 0);
    261    instance.title = '';
    262    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
    263 
    264    calls = [];
    265    var clone = instance.cloneNode(false);
    266    assert_attribute_log_entry(calls[0], {name: 'title', oldValue: null, newValue: '', namespace: null});
    267 }, 'Upgrading a cloned element must enqueue and invoke attributeChangedCallback for an HTML attribute');
    268 
    269 </script>
    270 </body>
    271 </html>