browser_stylesheet_change_events.js (7492B)
1 const gTestRoot = getRootDirectory(gTestPath).replace( 2 "chrome://mochitests/content/", 3 "http://127.0.0.1:8888/" 4 ); 5 6 add_task(async function test() { 7 await BrowserTestUtils.withNewTab( 8 { gBrowser, url: gTestRoot + "file_stylesheet_change_events.html" }, 9 async function (browser) { 10 await SpecialPowers.spawn( 11 browser, 12 [gTestRoot], 13 testApplicableStateChangeEvent 14 ); 15 } 16 ); 17 }); 18 19 // This function runs entirely in the content process. It doesn't have access 20 // any free variables in this file. 21 async function testApplicableStateChangeEvent(testRoot) { 22 // We've seen the original stylesheet in the document. 23 // Now add a stylesheet on the fly and make sure we see it. 24 let doc = content.document; 25 doc.styleSheetChangeEventsEnabled = true; 26 27 const unexpectedContentEvent = event => 28 ok(false, "Received a " + event.type + " event on content"); 29 doc.addEventListener( 30 "StyleSheetApplicableStateChanged", 31 unexpectedContentEvent 32 ); 33 doc.defaultView.addEventListener( 34 "StyleSheetApplicableStateChanged", 35 unexpectedContentEvent 36 ); 37 doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent); 38 doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent); 39 40 function shouldIgnoreEvent(e) { 41 // accessiblecaret.css might be reported, interfering with the test 42 // assertions, so let's ignore it 43 return ( 44 e.stylesheet?.href === "resource://gre-resources/accessiblecaret.css" 45 ); 46 } 47 48 function waitForStyleApplicableStateChanged() { 49 return ContentTaskUtils.waitForEvent( 50 docShell.chromeEventHandler, 51 "StyleSheetApplicableStateChanged", 52 true, 53 e => !shouldIgnoreEvent(e) 54 ); 55 } 56 57 function waitForStyleSheetRemovedEvent() { 58 return ContentTaskUtils.waitForEvent( 59 docShell.chromeEventHandler, 60 "StyleSheetRemoved", 61 true, 62 e => !shouldIgnoreEvent(e) 63 ); 64 } 65 66 function checkApplicableStateChangeEvent(event, { applicable, stylesheet }) { 67 is( 68 event.type, 69 "StyleSheetApplicableStateChanged", 70 "event.type has expected value" 71 ); 72 is(event.target, doc, "event targets correct document"); 73 is(event.stylesheet, stylesheet, "event.stylesheet has the expected value"); 74 is(event.applicable, applicable, "event.applicable has the expected value"); 75 } 76 77 function checkStyleSheetRemovedEvent(event, { stylesheet }) { 78 is(event.type, "StyleSheetRemoved", "event.type has expected value"); 79 is(event.target, doc, "event targets correct document"); 80 is(event.stylesheet, stylesheet, "event.stylesheet has the expected value"); 81 } 82 83 // Updating the text content will actually create a new StyleSheet instance, 84 // and so we should get one event for the new instance, and another one for 85 // the removal of the "previous"one. 86 function waitForTextContentChange() { 87 return Promise.all([ 88 waitForStyleSheetRemovedEvent(), 89 waitForStyleApplicableStateChanged(), 90 ]); 91 } 92 93 let stateChanged, evt; 94 95 { 96 const gStyleSheet = "stylesheet_change_events.css"; 97 98 info("Add <link> and wait for applicable state change event"); 99 let linkEl = doc.createElement("link"); 100 linkEl.setAttribute("rel", "stylesheet"); 101 linkEl.setAttribute("type", "text/css"); 102 linkEl.setAttribute("href", testRoot + gStyleSheet); 103 104 stateChanged = waitForStyleApplicableStateChanged(); 105 doc.body.appendChild(linkEl); 106 evt = await stateChanged; 107 108 ok(true, "received dynamic style sheet applicable state change event"); 109 checkApplicableStateChangeEvent(evt, { 110 stylesheet: linkEl.sheet, 111 applicable: true, 112 }); 113 114 stateChanged = waitForStyleApplicableStateChanged(); 115 linkEl.sheet.disabled = true; 116 evt = await stateChanged; 117 118 ok(true, "received dynamic style sheet applicable state change event"); 119 checkApplicableStateChangeEvent(evt, { 120 stylesheet: linkEl.sheet, 121 applicable: false, 122 }); 123 124 info("Remove stylesheet and wait for removed event"); 125 const removedStylesheet = linkEl.sheet; 126 const onStyleSheetRemoved = waitForStyleSheetRemovedEvent(); 127 doc.body.removeChild(linkEl); 128 const removedStyleSheetEvt = await onStyleSheetRemoved; 129 130 ok(true, "received removed sheet event"); 131 checkStyleSheetRemovedEvent(removedStyleSheetEvt, { 132 stylesheet: removedStylesheet, 133 }); 134 } 135 136 { 137 info("Add <style> node and wait for applicable state changed event"); 138 let styleEl = doc.createElement("style"); 139 styleEl.textContent = `body { background: tomato; }`; 140 141 stateChanged = waitForStyleApplicableStateChanged(); 142 doc.head.appendChild(styleEl); 143 evt = await stateChanged; 144 145 ok(true, "received dynamic style sheet applicable state change event"); 146 checkApplicableStateChangeEvent(evt, { 147 stylesheet: styleEl.sheet, 148 applicable: true, 149 }); 150 151 info("Updating <style> text content"); 152 stateChanged = waitForTextContentChange(); 153 const inlineStyleSheetBeforeChange = styleEl.sheet; 154 155 styleEl.textContent = `body { background: gold; }`; 156 const [inlineRemovedEvt, inlineAddedEvt] = await stateChanged; 157 158 ok(true, "received expected style sheet events"); 159 checkStyleSheetRemovedEvent(inlineRemovedEvt, { 160 stylesheet: inlineStyleSheetBeforeChange, 161 }); 162 checkApplicableStateChangeEvent(inlineAddedEvt, { 163 stylesheet: styleEl.sheet, 164 applicable: true, 165 }); 166 167 info("Remove stylesheet and wait for removed event"); 168 const onStyleSheetRemoved = waitForStyleSheetRemovedEvent(); 169 170 const removedInlineStylesheet = styleEl.sheet; 171 styleEl.remove(); 172 const removedStyleSheetEvt = await onStyleSheetRemoved; 173 174 ok(true, "received removed style sheet event"); 175 checkStyleSheetRemovedEvent(removedStyleSheetEvt, { 176 stylesheet: removedInlineStylesheet, 177 }); 178 } 179 180 { 181 info( 182 "Create a custom element and check we get an event for its stylesheet" 183 ); 184 stateChanged = waitForStyleApplicableStateChanged(); 185 const el = doc.createElement("div"); 186 const shadowRoot = el.attachShadow({ mode: "open" }); 187 doc.body.appendChild(el); 188 shadowRoot.innerHTML = ` 189 <span>custom</span> 190 <style> 191 span { color: salmon; } 192 </style>`; 193 evt = await stateChanged; 194 195 ok(true, "received dynamic style sheet applicable state change event"); 196 const shadowStyleEl = shadowRoot.querySelector("style"); 197 checkApplicableStateChangeEvent(evt, { 198 stylesheet: shadowStyleEl.sheet, 199 applicable: true, 200 }); 201 202 info("Updating <style> text content"); 203 stateChanged = waitForTextContentChange(); 204 const styleSheetBeforeChange = shadowStyleEl.sheet; 205 shadowStyleEl.textContent = `span { color: cyan; }`; 206 const [removedEvt, addedEvt] = await stateChanged; 207 208 ok(true, "received expected style sheet events"); 209 checkStyleSheetRemovedEvent(removedEvt, { 210 stylesheet: styleSheetBeforeChange, 211 }); 212 checkApplicableStateChangeEvent(addedEvt, { 213 stylesheet: shadowStyleEl.sheet, 214 applicable: true, 215 }); 216 217 info("Remove stylesheet and wait for removed event"); 218 const onStyleSheetRemoved = waitForStyleSheetRemovedEvent(); 219 const removedShadowStylesheet = shadowStyleEl.sheet; 220 shadowStyleEl.remove(); 221 const removedStyleSheetEvt = await onStyleSheetRemoved; 222 ok(true, "received removed style sheet event"); 223 checkStyleSheetRemovedEvent(removedStyleSheetEvt, { 224 stylesheet: removedShadowStylesheet, 225 }); 226 } 227 }