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>