browser_rules_selector_warnings.js (4713B)
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 selector warnings are displayed in the rule-view. 7 const TEST_URI = ` 8 <!DOCTYPE html> 9 <style> 10 main, :scope ~ * { 11 outline-color: tomato; 12 } 13 14 main, :has(form) { 15 /* /!\ space between & and : is important */ 16 & :has(input), 17 & :has(select), 18 &:has(button) { 19 background: gold; 20 } 21 } 22 </style> 23 <body> 24 <main> 25 <form> 26 <input> 27 </form> 28 </main> 29 </body>`; 30 31 const UNCONSTRAINED_HAS_WARNING_MESSAGE = 32 "This selector uses unconstrained :has(), which can be slow"; 33 const SIBLING_COMTINATOR_AFTER_SCOPE_WARNING_MESSAGE = 34 "Sibling selectors after :scope will never match anything"; 35 36 add_task(async function () { 37 await addTab( 38 "https://example.com/document-builder.sjs?html=" + 39 encodeURIComponent(TEST_URI) 40 ); 41 const { inspector, view } = await openRuleView(); 42 43 await selectNode("main", inspector); 44 const { ancestorDataEl, selectorText } = getRuleViewRuleEditor(view, 1); 45 46 info( 47 "Check that unconstrained :has() warnings are displayed for the rules selectors" 48 ); 49 let ruleSelectors = Array.from( 50 selectorText.querySelectorAll(".ruleview-selector") 51 ); 52 53 await assertSelectorWarnings({ 54 view, 55 selectorEl: ruleSelectors[0], 56 selectorText: "& :has(input)", 57 expectedWarnings: [UNCONSTRAINED_HAS_WARNING_MESSAGE], 58 }); 59 await assertSelectorWarnings({ 60 view, 61 selectorEl: ruleSelectors[1], 62 selectorText: "& :has(select)", 63 expectedWarnings: [UNCONSTRAINED_HAS_WARNING_MESSAGE], 64 }); 65 // Warning is not displayed when the selector does not have warnings 66 await assertSelectorWarnings({ 67 view, 68 selectorEl: ruleSelectors[2], 69 selectorText: "&:has(button)", 70 expectedWarnings: [], 71 }); 72 73 info( 74 "Check that unconstrained :has() warnings are displayed for the parent rules selectors" 75 ); 76 const parentRuleSelectors = Array.from( 77 ancestorDataEl.querySelectorAll(".ruleview-selector") 78 ); 79 await assertSelectorWarnings({ 80 view, 81 selectorEl: parentRuleSelectors[0], 82 selectorText: "main", 83 expectedWarnings: [], 84 }); 85 await assertSelectorWarnings({ 86 view, 87 selectorEl: parentRuleSelectors[1], 88 selectorText: ":has(form)", 89 expectedWarnings: [UNCONSTRAINED_HAS_WARNING_MESSAGE], 90 }); 91 92 const scopeSiblingRuleEditor = getRuleViewRuleEditor(view, 3); 93 ruleSelectors = Array.from( 94 scopeSiblingRuleEditor.selectorText.querySelectorAll(".ruleview-selector") 95 ); 96 // Warning is not displayed when the selector does not have warnings 97 await assertSelectorWarnings({ 98 view, 99 selectorEl: ruleSelectors[0], 100 selectorText: "main", 101 expectedWarnings: [], 102 }); 103 await assertSelectorWarnings({ 104 view, 105 selectorEl: ruleSelectors[1], 106 selectorText: ":scope ~ *", 107 expectedWarnings: [SIBLING_COMTINATOR_AFTER_SCOPE_WARNING_MESSAGE], 108 }); 109 }); 110 111 async function assertSelectorWarnings({ 112 view, 113 selectorEl, 114 selectorText, 115 expectedWarnings, 116 }) { 117 is( 118 selectorEl.textContent, 119 selectorText, 120 "Passed selector element is the expected one" 121 ); 122 123 const selectorWarningsContainerEl = selectorEl.querySelector( 124 ".ruleview-selector-warnings" 125 ); 126 127 if (expectedWarnings.length === 0) { 128 Assert.strictEqual( 129 selectorWarningsContainerEl, 130 null, 131 `"${selectorText}" does not have warnings` 132 ); 133 return; 134 } 135 136 Assert.notStrictEqual( 137 selectorWarningsContainerEl, 138 null, 139 `"${selectorText}" does have warnings` 140 ); 141 142 is( 143 selectorWarningsContainerEl 144 .getAttribute("data-selector-warning-kind") 145 ?.split(",")?.length || 0, 146 expectedWarnings.length, 147 `"${selectorText}" has expected number of warnings` 148 ); 149 150 // Ensure that the element can be targetted from EventUtils. 151 selectorWarningsContainerEl.scrollIntoView(); 152 153 const tooltip = view.tooltips.getTooltip("interactiveTooltip"); 154 const onTooltipReady = tooltip.once("shown"); 155 EventUtils.synthesizeMouseAtCenter( 156 selectorWarningsContainerEl, 157 { type: "mousemove" }, 158 selectorWarningsContainerEl.ownerDocument.defaultView 159 ); 160 await onTooltipReady; 161 162 const lis = Array.from(tooltip.panel.querySelectorAll("li")).map( 163 li => li.textContent 164 ); 165 Assert.deepEqual(lis, expectedWarnings, "Tooltip has expected items"); 166 167 info("Hide the tooltip"); 168 const onHidden = tooltip.once("hidden"); 169 // Move the mouse elsewhere to hide the tooltip 170 EventUtils.synthesizeMouse( 171 selectorWarningsContainerEl.ownerDocument.body, 172 1, 173 1, 174 { type: "mousemove" }, 175 selectorWarningsContainerEl.ownerDocument.defaultView 176 ); 177 await onHidden; 178 }