browser_rules_container-queries.js (9279B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test that the rule-view content is correct when the page defines container queries. 7 const TEST_URI = ` 8 <!DOCTYPE html> 9 <style type="text/css"> 10 body { 11 container: mycontainer / size; 12 } 13 14 section { 15 container: mycontainer / inline-size; 16 } 17 18 @container (width > 0px) { 19 h1, [test-hint="nocontainername"]{ 20 outline-color: chartreuse; 21 } 22 } 23 24 @container unknowncontainer (min-width: 2vw) { 25 h1, [test-hint="unknowncontainer"] { 26 border-color: salmon; 27 } 28 } 29 30 @container mycontainer (1px < width < 10000px) { 31 h1, [test-hint="container"] { 32 color: tomato; 33 } 34 35 section, [test-hint="container-duplicate-name--body"] { 36 color: gold; 37 } 38 39 div, [test-hint="container-duplicate-name--section"] { 40 color: salmon; 41 } 42 } 43 </style> 44 <body id=myBody class="a-container test"> 45 <h1>Hello @container!</h1> 46 <section> 47 <div> 48 <h2>You rock</h2> 49 </div> 50 </section> 51 </body> 52 `; 53 54 add_task(async function () { 55 await addTab( 56 "https://example.com/document-builder.sjs?html=" + 57 encodeURIComponent(TEST_URI) 58 ); 59 const { inspector, view } = await openRuleView(); 60 61 await selectNode("h1", inspector); 62 assertContainerQueryData(view, [ 63 { selector: "element", ancestorRulesData: null }, 64 { 65 selector: `h1, [test-hint="container"]`, 66 ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"], 67 }, 68 { 69 selector: `h1, [test-hint="nocontainername"]`, 70 ancestorRulesData: ["@container (width > 0px) {"], 71 }, 72 ]); 73 74 info("Check that the query container tooltip works as expected"); 75 // Retrieve query containers sizes 76 const { bodyInlineSize, bodyBlockSize, sectionInlineSize } = 77 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 78 const body = content.document.body; 79 const section = content.document.querySelector("section"); 80 return { 81 bodyInlineSize: content.getComputedStyle(body).inlineSize, 82 bodyBlockSize: content.getComputedStyle(body).blockSize, 83 sectionInlineSize: content.getComputedStyle(section).inlineSize, 84 }; 85 }); 86 87 await assertQueryContainerTooltip({ 88 inspector, 89 view, 90 ruleIndex: 1, 91 expectedHeaderText: "<body#myBody.a-container.test>", 92 expectedBodyText: [ 93 "container-type: size", 94 `inline-size: ${bodyInlineSize}`, 95 `block-size: ${bodyBlockSize}`, 96 ], 97 }); 98 99 info("Check that the 'jump to container' button works as expected"); 100 await assertJumpToContainerButton(inspector, view, 1, "body"); 101 102 info("Check that inherited rules display container query data as expected"); 103 await selectNode("h2", inspector); 104 105 assertContainerQueryData(view, [ 106 { selector: "element", ancestorRulesData: null }, 107 { 108 selector: `div, [test-hint="container-duplicate-name--section"]`, 109 ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"], 110 }, 111 { 112 selector: `section, [test-hint="container-duplicate-name--body"]`, 113 ancestorRulesData: ["@container mycontainer (1px < width < 10000px) {"], 114 }, 115 ]); 116 117 info( 118 "Check that the query container tooltip works as expected for inherited rules as well" 119 ); 120 await assertQueryContainerTooltip({ 121 inspector, 122 view, 123 ruleIndex: 1, 124 expectedHeaderText: "<section>", 125 expectedBodyText: [ 126 "container-type: inline-size", 127 `inline-size: ${sectionInlineSize}`, 128 ], 129 }); 130 await assertQueryContainerTooltip({ 131 inspector, 132 view, 133 ruleIndex: 2, 134 expectedHeaderText: "<body#myBody.a-container.test>", 135 expectedBodyText: [ 136 "container-type: size", 137 `inline-size: ${bodyInlineSize}`, 138 `block-size: ${bodyBlockSize}`, 139 ], 140 }); 141 142 info( 143 "Check that the 'jump to container' button works as expected for inherited rules" 144 ); 145 await assertJumpToContainerButton(inspector, view, 1, "section"); 146 147 await selectNode("h2", inspector); 148 await assertJumpToContainerButton(inspector, view, 2, "body"); 149 }); 150 151 function assertContainerQueryData(view, expectedRules) { 152 const rulesInView = Array.from( 153 view.element.querySelectorAll(".ruleview-rule") 154 ); 155 156 is( 157 rulesInView.length, 158 expectedRules.length, 159 "All expected rules are displayed" 160 ); 161 162 for (let i = 0; i < expectedRules.length; i++) { 163 const expectedRule = expectedRules[i]; 164 info(`Checking rule #${i}: ${expectedRule.selector}`); 165 166 const selector = rulesInView[i].querySelector( 167 ".ruleview-selectors-container" 168 ).innerText; 169 is(selector, expectedRule.selector, `Expected selector for ${selector}`); 170 171 const ancestorDataEl = getRuleViewAncestorRulesDataElementByIndex(view, i); 172 173 if (expectedRule.ancestorRulesData == null) { 174 is( 175 ancestorDataEl, 176 null, 177 `No ancestor rules data displayed for ${selector}` 178 ); 179 } else { 180 is( 181 ancestorDataEl?.innerText, 182 expectedRule.ancestorRulesData.join("\n"), 183 `Expected ancestor rules data displayed for ${selector}` 184 ); 185 Assert.notStrictEqual( 186 ancestorDataEl.querySelector(".container-query .open-inspector"), 187 null, 188 "An icon is displayed to select the container in the markup view" 189 ); 190 } 191 } 192 } 193 194 async function assertJumpToContainerButton( 195 inspector, 196 view, 197 ruleIndex, 198 expectedSelectedNodeAfterClick 199 ) { 200 const selectContainerButton = getRuleViewAncestorRulesDataElementByIndex( 201 view, 202 ruleIndex 203 ).querySelector(".open-inspector"); 204 205 // Ensure that the button can be targetted from EventUtils. 206 selectContainerButton.scrollIntoView(); 207 208 const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } = 209 getHighlighterTestHelpers(inspector); 210 211 const onNodeHighlight = waitForHighlighterTypeShown( 212 inspector.highlighters.TYPES.BOXMODEL 213 ); 214 EventUtils.synthesizeMouseAtCenter( 215 selectContainerButton, 216 { type: "mouseover" }, 217 selectContainerButton.ownerDocument.defaultView 218 ); 219 const { nodeFront: highlightedNodeFront } = await onNodeHighlight; 220 is( 221 highlightedNodeFront.displayName, 222 expectedSelectedNodeAfterClick, 223 "The correct node was highlighted" 224 ); 225 226 const onceNewNodeFront = inspector.selection.once("new-node-front"); 227 const onNodeUnhighlight = waitForHighlighterTypeHidden( 228 inspector.highlighters.TYPES.BOXMODEL 229 ); 230 231 EventUtils.synthesizeMouseAtCenter( 232 selectContainerButton, 233 {}, 234 selectContainerButton.ownerDocument.defaultView 235 ); 236 237 const nodeFront = await onceNewNodeFront; 238 is( 239 nodeFront.displayName, 240 expectedSelectedNodeAfterClick, 241 "The correct node has been selected" 242 ); 243 244 await onNodeUnhighlight; 245 ok(true, "Highlighter was hidden when clicking on icon"); 246 247 // Move mouse so it does stay in a position where it could hover something impacting 248 // the test. 249 EventUtils.synthesizeMouse( 250 selectContainerButton.closest("body"), 251 0, 252 0, 253 { type: "mouseover" }, 254 selectContainerButton.ownerDocument.defaultView 255 ); 256 } 257 258 async function assertQueryContainerTooltip({ 259 inspector, 260 view, 261 ruleIndex, 262 expectedHeaderText, 263 expectedBodyText, 264 }) { 265 const parent = getRuleViewAncestorRulesDataElementByIndex(view, ruleIndex); 266 const highlighterTriggerEl = parent.querySelector(".open-inspector"); 267 const tooltipTriggerEl = parent.querySelector(".container-query-declaration"); 268 269 // Ensure that the element can be targetted from EventUtils. 270 parent.scrollIntoView(); 271 272 const { waitForHighlighterTypeShown, waitForHighlighterTypeHidden } = 273 getHighlighterTestHelpers(inspector); 274 275 const onNodeHighlight = waitForHighlighterTypeShown( 276 inspector.highlighters.TYPES.BOXMODEL 277 ); 278 279 const tooltip = view.tooltips.getTooltip("interactiveTooltip"); 280 281 info("synthesizing mousemove on open-inspector icon: " + tooltip.isVisible()); 282 EventUtils.synthesizeMouseAtCenter( 283 highlighterTriggerEl, 284 { type: "mousemove" }, 285 highlighterTriggerEl.ownerDocument.defaultView 286 ); 287 288 await onNodeHighlight; 289 info("node was highlighted"); 290 291 const onNodeUnhighlight = waitForHighlighterTypeHidden( 292 inspector.highlighters.TYPES.BOXMODEL 293 ); 294 295 const onTooltipReady = tooltip.once("shown"); 296 297 info("synthesizing mousemove on tooltip el: " + tooltip.isVisible()); 298 EventUtils.synthesizeMouseAtCenter( 299 tooltipTriggerEl, 300 { type: "mousemove" }, 301 tooltipTriggerEl.ownerDocument.defaultView 302 ); 303 304 await onTooltipReady; 305 info("tooltip was shown"); 306 307 await onNodeUnhighlight; 308 info("highlighter was hidden"); 309 310 is( 311 tooltip.panel.querySelector("header").textContent, 312 expectedHeaderText, 313 "Tooltip has expected header content" 314 ); 315 316 const lis = Array.from(tooltip.panel.querySelectorAll("li")).map( 317 li => li.textContent 318 ); 319 Assert.deepEqual(lis, expectedBodyText, "Tooltip has expected body items"); 320 321 info("Hide the tooltip"); 322 const onHidden = tooltip.once("hidden"); 323 324 // Move the mouse elsewhere to hide the tooltip 325 EventUtils.synthesizeMouse( 326 tooltipTriggerEl.ownerDocument.body, 327 1, 328 1, 329 { type: "mousemove" }, 330 tooltipTriggerEl.ownerDocument.defaultView 331 ); 332 await onHidden; 333 }