tor-browser

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

CustomElementRegistry-constructor-and-callbacks-are-held-strongly.html (2849B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <title>CustomElementInterface holds constructors and callbacks strongly, preventing them from being GCed if there are no other references</title>
      4 <link rel="help" href="https://html.spec.whatwg.org/multipage/custom-elements.html#concept-custom-element-definition-lifecycle-callbacks">
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="/common/gc.js"></script>
      8 
      9 <body>
     10 <div id="customElementsRoot"></div>
     11 <iframe id="emptyIframe" srcdoc></iframe>
     12 <script>
     13 "use strict";
     14 
     15 const tagNames = [...new Array(100)].map((_, i) => `x-foo${i}`);
     16 const delay = (t, ms) => new Promise(resolve => { t.step_timeout(resolve, ms); });
     17 
     18 const connectedCallbackCalls = new Set;
     19 const disconnectedCallbackCalls = new Set;
     20 const attributeChangedCallbackCalls = new Set;
     21 const adoptedCallbackCalls = new Set;
     22 
     23 for (const tagName of tagNames) {
     24    const constructor = class extends HTMLElement {
     25        connectedCallback() { connectedCallbackCalls.add(tagName); }
     26        disconnectedCallback() { disconnectedCallbackCalls.add(tagName); }
     27        attributeChangedCallback() { attributeChangedCallbackCalls.add(tagName); }
     28        adoptedCallback() { adoptedCallbackCalls.add(tagName); }
     29    };
     30 
     31    constructor.observedAttributes = ["foo"];
     32 
     33    customElements.define(tagName, constructor);
     34 
     35    delete constructor.prototype.connectedCallback;
     36    delete constructor.prototype.disconnectedCallback;
     37    delete constructor.prototype.attributeChangedCallback;
     38    delete constructor.prototype.adoptedCallback;
     39 }
     40 
     41 promise_test(async t => {
     42    await garbageCollect();
     43 
     44    assert_true(tagNames.every(tagName => typeof customElements.get(tagName) === "function"));
     45 }, "constructor");
     46 
     47 promise_test(async t => {
     48    await garbageCollect();
     49    for (const tagName of tagNames) {
     50        customElementsRoot.append(document.createElement(tagName));
     51    }
     52 
     53    await delay(t, 10);
     54    assert_equals(connectedCallbackCalls.size, tagNames.length);
     55 }, "connectedCallback");
     56 
     57 promise_test(async t => {
     58    await garbageCollect();
     59    for (const xFoo of customElementsRoot.children) {
     60        xFoo.setAttribute("foo", "bar");
     61    }
     62 
     63    await delay(t, 10);
     64    assert_equals(attributeChangedCallbackCalls.size, tagNames.length);
     65 }, "attributeChangedCallback");
     66 
     67 promise_test(async t => {
     68    await garbageCollect();
     69    customElementsRoot.innerHTML = "";
     70 
     71    await delay(t, 10);
     72    assert_equals(disconnectedCallbackCalls.size, tagNames.length);
     73 }, "disconnectedCallback");
     74 
     75 promise_test(async t => {
     76    await garbageCollect();
     77    for (const tagName of tagNames) {
     78        emptyIframe.contentDocument.adoptNode(document.createElement(tagName));
     79    }
     80 
     81    await delay(t, 10);
     82    assert_equals(adoptedCallbackCalls.size, tagNames.length);
     83 }, "adoptedCallback");
     84 </script>