browser_rules_variables_unused.js (7779B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 add_task(async function testHiddenUnusedVariables() { 7 const h1Declarations = [ 8 { name: "--foo", value: "1" }, 9 { name: "--bar", value: "2" }, 10 { name: "--foobar", value: "calc( var(--foo, 3) * var(--bar, 4))" }, 11 { name: "--fallback", value: "var(--fallback-a)" }, 12 { name: "--fallback-a", value: "var(--fallback-b)" }, 13 { name: "--fallback-b", value: "var(--fallback-c)" }, 14 { name: "--fallback-c", value: "10" }, 15 { name: "--cycle-a", value: "var(--cycle-b)" }, 16 { name: "--cycle-b", value: "var(--cycle-a)" }, 17 { name: "--unused-a", value: "var(--unused-b)" }, 18 { name: "--unused-b", value: "5" }, 19 { name: "--h", value: "400px" }, 20 // Generate a good amount of variables that won't be referenced anywhere to trigger the 21 // "hide unused" mechanism 22 ...Array.from({ length: 10 }, (_, i) => ({ 23 name: `--unused-no-dep-${i}`, 24 value: i.toString(), 25 })), 26 { 27 name: "width", 28 value: `calc(var(--foobar, var(--fallback)) + var(--cycle-a) + var(--unset))`, 29 }, 30 ]; 31 32 // set a different rule using a variable from the first rule to check if its detected 33 // as being used 34 const whereH1Declarations = [ 35 // declare 9 unused variables, so they should be visible by default 36 ...Array.from({ length: 9 }, (_, i) => ({ 37 name: `--unused-where-${i}`, 38 value: i.toString(), 39 })), 40 { 41 name: "height", 42 // Using variable for the h1 rule 43 value: "var(--h)", 44 }, 45 ]; 46 47 const TEST_URI = ` 48 <style> 49 h1 { 50 ${h1Declarations 51 .map(({ name, value }) => `${name}: ${value};`) 52 .join("\n")} 53 } 54 55 :where(h1) { 56 ${whereH1Declarations 57 .map(({ name, value }) => `${name}: ${value};`) 58 .join("\n")} 59 } 60 </style> 61 <h1>Hello</h1> 62 `; 63 64 await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); 65 const { inspector, view } = await openRuleView(); 66 await selectNode("h1", inspector); 67 68 info("Check that elementStyle.usedVariables has the expected data"); 69 Assert.deepEqual(Array.from(view._elementStyle.usedVariables), [ 70 // in `h1 -> width` 71 "--foobar", 72 // in `h1 -> width` 73 "--fallback", 74 // in `h1 -> width` 75 "--cycle-a", 76 // in `h1 -> width`, is picked up even if it's not defined 77 "--unset", 78 // in `:where(h1) -> height` 79 "--h", 80 // in `h1 -> --foobar`, which is used in `h1 -> width` 81 "--foo", 82 // in `h1 -> --foobar`, which is used in `h1 -> width` 83 "--bar", 84 // in `h1 -> --fallback`, which is used in `h1 -> width` 85 "--fallback-a", 86 // in `h1 -> --fallback-a`, which is used in `h1 -> --fallback`, which is used in `h1 -> width` 87 "--fallback-b", 88 // in `h1 -> --fallback-b`, which is used in `h1 -> --fallback-a`, which is used in `h1 -> --fallback`, 89 // which is used in `h1 -> width` 90 "--fallback-c", 91 // in `h1 --cycle-a`, which is used in `h1 -> width` 92 "--cycle-b", 93 ]); 94 95 await checkRuleViewContent(view, [ 96 { 97 selector: "element", 98 selectorEditable: false, 99 declarations: [], 100 }, 101 { 102 selector: "h1", 103 declarations: [ 104 { name: "--foo", value: "1" }, 105 { name: "--bar", value: "2" }, 106 { name: "--foobar", value: "calc( var(--foo, 3) * var(--bar, 4))" }, 107 { name: "--fallback", value: "var(--fallback-a)" }, 108 { name: "--fallback-a", value: "var(--fallback-b)" }, 109 { name: "--fallback-b", value: "var(--fallback-c)" }, 110 { name: "--fallback-c", value: "10" }, 111 { name: "--cycle-a", value: "var(--cycle-b)" }, 112 { name: "--cycle-b", value: "var(--cycle-a)" }, 113 // Displayed because used in `:where(h1) -> height` 114 { name: "--h", value: "400px" }, 115 { 116 name: "width", 117 value: `calc(var(--foobar, var(--fallback)) + var(--cycle-a) + var(--unset))`, 118 }, 119 ], 120 }, 121 { 122 selector: ":where(h1)", 123 // All declarations are displayed, even the unused variables, because we don't 124 // hit the threshold to trigger the "hide unused" UI 125 declarations: whereH1Declarations, 126 }, 127 ]); 128 129 info("Check that the 'Show X unused variables button is displayed'"); 130 const showUnusedVariablesButton = getUnusedVariableButton(view, 1); 131 ok(!!showUnusedVariablesButton, "Show unused variables button is displayed"); 132 133 info("Check that the button doesn't prevent the usual keyboard navigation"); 134 const h1RuleEditor = getRuleViewRuleEditor(view, 1); 135 const whereH1RuleEditor = getRuleViewRuleEditor(view, 2); 136 137 await focusNewRuleViewProperty(h1RuleEditor); 138 139 EventUtils.synthesizeKey("VK_TAB", {}, view.styleWindow); 140 is( 141 inplaceEditor(view.styleDocument.activeElement), 142 inplaceEditor(whereH1RuleEditor.selectorText), 143 "Hitting Tab triggered the editor for the selector of the next rule" 144 ); 145 146 EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, view.styleWindow); 147 is( 148 inplaceEditor(view.styleDocument.activeElement), 149 inplaceEditor(h1RuleEditor.newPropSpan), 150 "Hitting Shift+Tab triggered the editor for the new property" 151 ); 152 153 // Blur the input to not interfere with the rest of the test 154 const onBlur = once(view.styleDocument.activeElement, "blur"); 155 view.styleDocument.activeElement.blur(); 156 await onBlur; 157 158 info( 159 "Check that clicking the Show unused variable button does show the unused variables" 160 ); 161 showUnusedVariablesButton.click(); 162 163 is( 164 getUnusedVariableButton(view, 1), 165 null, 166 "Show unused variable button is not visible anymore" 167 ); 168 169 await checkRuleViewContent(view, [ 170 { 171 selector: "element", 172 selectorEditable: false, 173 declarations: [], 174 }, 175 { 176 selector: "h1", 177 declarations: h1Declarations, 178 }, 179 { 180 selector: ":where(h1)", 181 declarations: whereH1Declarations, 182 }, 183 ]); 184 185 info( 186 "Selecting another node and select h1 back to assert the rules after a refresh" 187 ); 188 await selectNode("body", inspector); 189 await selectNode("h1", inspector); 190 191 is( 192 getUnusedVariableButton(view, 1), 193 null, 194 "Unused variable button is kept hidden after refreshing rules view" 195 ); 196 197 info("Add another unused variables to the :where(h1) rule"); 198 // Sanity check 199 ok( 200 !getUnusedVariableButton(view, 2), 201 "The unused variable button isn't displayed at first for :where(h1) rule" 202 ); 203 204 // We shouldn't add the property via the UI, as variables added by the user in the 205 // Rules view are always visible (see browser_rules_variables_unused_add_property.js). 206 // We could add the property via CSSOM, but it looks like there's a bug at the moment 207 // where properties aren't showing up (unrelated to unused variable). 208 // So add the property via the UI, but clear the user properties so it won't be seen 209 // as added by the user. 210 await addProperty(view, 2, "--added-unused-where", "new-1"); 211 view.store.userProperties.clear(); 212 213 info( 214 "Selecting another node and select h1 back after adding property via CSSOM" 215 ); 216 await selectNode("body", inspector); 217 await selectNode("h1", inspector); 218 219 await checkRuleViewContent(view, [ 220 { 221 selector: "element", 222 selectorEditable: false, 223 declarations: [], 224 }, 225 { 226 selector: "h1", 227 declarations: h1Declarations, 228 }, 229 { 230 selector: ":where(h1)", 231 // we only see the height variable, --unused-c is now hidden, as well as all the 232 // --unused-cssom-* variables 233 declarations: [{ name: "height", value: "var(--h)" }], 234 }, 235 ]); 236 237 is( 238 getUnusedVariableButton(view, 2).textContent, 239 "Show 10 unused custom CSS properties", 240 "Unused variable button is kept hidden after refreshing rules view" 241 ); 242 });