tor-browser

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

test_custom_element_lifecycle.html (14786B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <!--
      4 https://bugzilla.mozilla.org/show_bug.cgi?id=783129
      5 -->
      6 <head>
      7  <title>Test for custom elements lifecycle callback</title>
      8  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
     10 </head>
     11 <body>
     12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=783129">Bug 783129</a>
     13 <div id="container">
     14  <x-hello id="hello"></x-hello>
     15  <button id="extbutton" is="x-button"></button>
     16 </div>
     17 <script>
     18 
     19 var container = document.getElementById("container");
     20 
     21 // Tests callbacks after defining element type that is already in the document.
     22 // create element in document -> define -> remove from document
     23 function testRegisterUnresolved() {
     24  var helloElem = document.getElementById("hello");
     25 
     26  var connectedCallbackCalled = false;
     27  var disconnectedCallbackCalled = false;
     28 
     29  class Hello extends HTMLElement {
     30    connectedCallback() {
     31      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
     32      is(this, helloElem, "The 'this' value should be the custom element.");
     33      connectedCallbackCalled = true;
     34    }
     35 
     36    disconnectedCallback() {
     37      is(connectedCallbackCalled, true, "Connected callback should be called before detached");
     38      is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
     39      disconnectedCallbackCalled = true;
     40      is(this, helloElem, "The 'this' value should be the custom element.");
     41      runNextTest();
     42    }
     43 
     44    attributeChangedCallback(name, oldValue, newValue) {
     45      ok(false, "attributeChanged callback should never be called in this test.");
     46    }
     47  };
     48 
     49  customElements.define("x-hello", Hello);
     50 
     51  // Remove element from document to trigger disconnected callback.
     52  container.removeChild(helloElem);
     53 }
     54 
     55 // Tests callbacks after defining an extended element type that is already in the document.
     56 // create element in document -> define -> remove from document
     57 function testRegisterUnresolvedExtended() {
     58  var buttonElem = document.getElementById("extbutton");
     59 
     60  var connectedCallbackCalled = false;
     61  var disconnectedCallbackCalled = false;
     62 
     63  class XButton extends HTMLButtonElement {
     64    connectedCallback() {
     65      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
     66      is(this, buttonElem, "The 'this' value should be the custom element.");
     67      connectedCallbackCalled = true;
     68    }
     69 
     70    disconnectedCallback() {
     71      is(connectedCallbackCalled, true, "Connected callback should be called before detached");
     72      is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
     73      disconnectedCallbackCalled = true;
     74      is(this, buttonElem, "The 'this' value should be the custom element.");
     75      runNextTest();
     76    }
     77 
     78    attributeChangedCallback(name, oldValue, newValue) {
     79      ok(false, "attributeChanged callback should never be called in this test.");
     80    }
     81  };
     82 
     83  customElements.define("x-button", XButton, { extends: "button" });
     84 
     85  // Remove element from document to trigger disconnected callback.
     86  container.removeChild(buttonElem);
     87 }
     88 
     89 function testInnerHTML() {
     90  var connectedCallbackCalled = false;
     91 
     92  class XInnerHTML extends HTMLElement {
     93    connectedCallback() {
     94      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
     95      connectedCallbackCalled = true;
     96    }
     97  };
     98 
     99  customElements.define("x-inner-html", XInnerHTML);
    100  var div = document.createElement(div);
    101  document.documentElement.appendChild(div);
    102  div.innerHTML = '<x-inner-html></x-inner-html>';
    103  is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
    104  runNextTest();
    105 }
    106 
    107 function testInnerHTMLExtended() {
    108  var connectedCallbackCalled = false;
    109 
    110  class XInnerHTMLExtend extends HTMLButtonElement {
    111    connectedCallback() {
    112      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
    113      connectedCallbackCalled = true;
    114    }
    115  };
    116 
    117  customElements.define("x-inner-html-extended", XInnerHTMLExtend, { extends: "button" });
    118  var div = document.createElement(div);
    119  document.documentElement.appendChild(div);
    120  div.innerHTML = '<button is="x-inner-html-extended"></button>';
    121  is(connectedCallbackCalled, true, "Connected callback should be called after setting innerHTML.");
    122  runNextTest();
    123 }
    124 
    125 function testInnerHTMLUpgrade() {
    126  var connectedCallbackCalled = false;
    127 
    128  var div = document.createElement(div);
    129  document.documentElement.appendChild(div);
    130  div.innerHTML = '<x-inner-html-upgrade></x-inner-html-upgrade>';
    131 
    132  class XInnerHTMLUpgrade extends HTMLElement {
    133    connectedCallback() {
    134      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
    135      connectedCallbackCalled = true;
    136    }
    137  };
    138 
    139  customElements.define("x-inner-html-upgrade", XInnerHTMLUpgrade);
    140  is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
    141  runNextTest();
    142 }
    143 
    144 function testInnerHTMLExtendedUpgrade() {
    145  var connectedCallbackCalled = false;
    146 
    147  var div = document.createElement(div);
    148  document.documentElement.appendChild(div);
    149  div.innerHTML = '<button is="x-inner-html-extended-upgrade"></button>';
    150 
    151  class XInnerHTMLExtnedUpgrade extends HTMLButtonElement {
    152    connectedCallback() {
    153      is(connectedCallbackCalled, false, "Connected callback should only be called once in this test.");
    154      connectedCallbackCalled = true;
    155    }
    156  };
    157 
    158  customElements.define("x-inner-html-extended-upgrade", XInnerHTMLExtnedUpgrade, { extends: "button" });
    159  is(connectedCallbackCalled, true, "Connected callback should be called after registering.");
    160  runNextTest();
    161 }
    162 
    163 // Test callback when creating element after defining an element type.
    164 // define -> create element -> insert into document -> remove from document
    165 function testRegisterResolved() {
    166  var connectedCallbackCalled = false;
    167  var disconnectedCallbackCalled = false;
    168 
    169  class Resolved extends HTMLElement {
    170    connectedCallback() {
    171      is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
    172      is(this, createdElement, "The 'this' value should be the custom element.");
    173      connectedCallbackCalled = true;
    174    }
    175 
    176    disconnectedCallback() {
    177      is(connectedCallbackCalled, true, "Connected callback should be called before detached");
    178      is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
    179      is(this, createdElement, "The 'this' value should be the custom element.");
    180      disconnectedCallbackCalled = true;
    181      runNextTest();
    182    }
    183 
    184    attributeChangedCallback() {
    185      ok(false, "attributeChanged callback should never be called in this test.");
    186    }
    187  };
    188 
    189  customElements.define("x-resolved", Resolved);
    190 
    191  var createdElement = document.createElement("x-resolved");
    192  is(createdElement.__proto__, Resolved.prototype, "Prototype of custom element should be the defined prototype.");
    193 
    194  // Insert element into document to trigger attached callback.
    195  container.appendChild(createdElement);
    196 
    197  // Remove element from document to trigger detached callback.
    198  container.removeChild(createdElement);
    199 }
    200 
    201 // Callbacks should always be the same ones when registered.
    202 function testChangingCallback() {
    203  var callbackCalled = false;
    204 
    205  class TestCallback extends HTMLElement
    206  {
    207    attributeChangedCallback(aName, aOldValue, aNewValue) {
    208      is(callbackCalled, false, "Callback should only be called once in this test.");
    209      callbackCalled = true;
    210      runNextTest();
    211    }
    212 
    213    static get observedAttributes() {
    214      return ["data-foo"];
    215    }
    216  }
    217 
    218  customElements.define("x-test-callback", TestCallback);
    219 
    220  TestCallback.prototype.attributeChangedCallback = function(name, oldValue, newValue) {
    221    ok(false, "Only callbacks at registration should be called.");
    222  };
    223 
    224  var elem = document.createElement("x-test-callback");
    225  elem.setAttribute("data-foo", "bar");
    226 }
    227 
    228 function testAttributeChanged() {
    229  var createdElement;
    230  // Sequence of callback arguments that we expect from attribute changed callback
    231  // after changing attributes values in a specific order.
    232  var expectedCallbackArguments = [
    233    // [oldValue, newValue]
    234    [null, "newvalue"], // Setting the attribute value to "newvalue"
    235    ["newvalue", "nextvalue"], // Changing the attribute value from "newvalue" to "nextvalue"
    236    ["nextvalue", ""], // Changing the attribute value from "nextvalue" to empty string
    237    ["", null], // Removing the attribute.
    238  ];
    239 
    240  class AttrChange extends HTMLElement
    241  {
    242    attributeChangedCallback(name, oldValue, newValue) {
    243      is(this, createdElement, "The 'this' value should be the custom element.");
    244      ok(expectedCallbackArguments.length, "Attribute changed callback should not be called more than expected.");
    245 
    246      is(name, "changeme", "name arugment in attribute changed callback should be the name of the changed attribute.");
    247 
    248      var expectedArgs = expectedCallbackArguments.shift();
    249      is(oldValue, expectedArgs[0], "The old value argument should match the expected value.");
    250      is(newValue, expectedArgs[1], "The new value argument should match the expected value.");
    251 
    252      if (expectedCallbackArguments.length === 0) {
    253        // Done with the attribute changed callback test.
    254        runNextTest();
    255      }
    256    }
    257 
    258    static get observedAttributes() {
    259      return ["changeme"];
    260    }
    261  }
    262 
    263  customElements.define("x-attrchange", AttrChange);
    264 
    265  createdElement = document.createElement("x-attrchange");
    266  createdElement.setAttribute("changeme", "newvalue");
    267  createdElement.setAttribute("changeme", "nextvalue");
    268  createdElement.setAttribute("changeme", "");
    269  createdElement.removeAttribute("changeme");
    270 }
    271 
    272 function testAttributeChangedExtended() {
    273  var callbackCalled = false;
    274 
    275  class ExtnededAttributeChange extends HTMLButtonElement
    276  {
    277    attributeChangedCallback(name, oldValue, newValue) {
    278      is(callbackCalled, false, "Callback should only be called once in this test.");
    279      callbackCalled = true;
    280      runNextTest();
    281    }
    282 
    283    static get observedAttributes() {
    284      return ["foo"];
    285    }
    286  }
    287 
    288  customElements.define("x-extended-attribute-change", ExtnededAttributeChange,
    289                        { extends: "button" });
    290 
    291  var elem = document.createElement("button", {is: "x-extended-attribute-change"});
    292  elem.setAttribute("foo", "bar");
    293 }
    294 
    295 function testStyleAttributeChange() {
    296  var expectedCallbackArguments = [
    297    // [name, oldValue, newValue]
    298    ["style", null, "font-size: 10px;"],
    299    ["style", "font-size: 10px;", "font-size: 20px;"],
    300    ["style", "font-size: 20px;", "font-size: 30px;"],
    301  ];
    302 
    303  customElements.define("x-style-attribute-change", class extends HTMLElement {
    304    attributeChangedCallback(name, oldValue, newValue) {
    305      if (expectedCallbackArguments.length === 0) {
    306        ok(false, "Got unexpected attributeChangedCallback?");
    307        return;
    308      }
    309 
    310      let expectedArgument = expectedCallbackArguments.shift();
    311      is(name, expectedArgument[0],
    312         "The name argument should match the expected value.");
    313      is(oldValue, expectedArgument[1],
    314         "The old value argument should match the expected value.");
    315      is(newValue, expectedArgument[2],
    316         "The new value argument should match the expected value.");
    317    }
    318 
    319    static get observedAttributes() {
    320      return ["style"];
    321    }
    322  });
    323 
    324  var elem = document.createElement("x-style-attribute-change");
    325  elem.style.fontSize = "10px";
    326  elem.style.fontSize = "20px";
    327  elem.style.fontSize = "30px";
    328 
    329  ok(expectedCallbackArguments.length === 0,
    330     "The attributeChangedCallback should be fired synchronously.");
    331  runNextTest();
    332 }
    333 
    334 // Creates a custom element that is an upgrade candidate (no registration)
    335 // and mutate the element in ways that would call callbacks for registered
    336 // elements.
    337 function testUpgradeCandidate() {
    338  var createdElement = document.createElement("x-upgrade-candidate");
    339  container.appendChild(createdElement);
    340  createdElement.setAttribute("foo", "bar");
    341  container.removeChild(createdElement);
    342  ok(true, "Nothing bad should happen when trying to mutating upgrade candidates.");
    343  runNextTest();
    344 }
    345 
    346 function testNotInDocEnterLeave() {
    347  class DestinedForFragment extends HTMLElement {
    348    connectedCallback() {
    349      ok(false, "Connected callback should not be called.");
    350    }
    351 
    352    disconnectedCallback() {
    353      ok(false, "Disconnected callback should not be called.");
    354    }
    355  };
    356 
    357  var createdElement = document.createElement("x-destined-for-fragment");
    358 
    359  customElements.define("x-destined-for-fragment", DestinedForFragment);
    360 
    361  var fragment = new DocumentFragment();
    362  fragment.appendChild(createdElement);
    363  fragment.removeChild(createdElement);
    364 
    365  var divNotInDoc = document.createElement("div");
    366  divNotInDoc.appendChild(createdElement);
    367  divNotInDoc.removeChild(createdElement);
    368 
    369  runNextTest();
    370 }
    371 
    372 function testEnterLeaveView() {
    373  var connectedCallbackCalled = false;
    374  var disconnectedCallbackCalled = false;
    375 
    376  class ElementInDiv extends HTMLElement {
    377    connectedCallback() {
    378      is(connectedCallbackCalled, false, "Connected callback should only be called on in this test.");
    379      connectedCallbackCalled = true;
    380    }
    381 
    382    disconnectedCallback() {
    383      is(connectedCallbackCalled, true, "Connected callback should be called before detached");
    384      is(disconnectedCallbackCalled, false, "Disconnected callback should only be called once in this test.");
    385      disconnectedCallbackCalled = true;
    386      runNextTest();
    387    }
    388  };
    389 
    390  var div = document.createElement("div");
    391  customElements.define("x-element-in-div", ElementInDiv);
    392  var customElement = document.createElement("x-element-in-div");
    393  div.appendChild(customElement);
    394  is(connectedCallbackCalled, false, "Appending a custom element to a node that is not in the document should not call the connected callback.");
    395 
    396  container.appendChild(div);
    397  container.removeChild(div);
    398 }
    399 
    400 var testFunctions = [
    401  testRegisterUnresolved,
    402  testRegisterUnresolvedExtended,
    403  testInnerHTML,
    404  testInnerHTMLExtended,
    405  testInnerHTMLUpgrade,
    406  testInnerHTMLExtendedUpgrade,
    407  testRegisterResolved,
    408  testAttributeChanged,
    409  testAttributeChangedExtended,
    410  testStyleAttributeChange,
    411  testUpgradeCandidate,
    412  testChangingCallback,
    413  testNotInDocEnterLeave,
    414  testEnterLeaveView,
    415  SimpleTest.finish
    416 ];
    417 
    418 function runNextTest() {
    419  if (testFunctions.length) {
    420    var nextTestFunction = testFunctions.shift();
    421    info(`Start ${nextTestFunction.name} ...`);
    422    nextTestFunction();
    423  }
    424 }
    425 
    426 SimpleTest.waitForExplicitFinish();
    427 
    428 runNextTest();
    429 
    430 </script>
    431 </body>
    432 </html>