test_value_storage.html (12767B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 --> 5 <head> 6 <title>Test for parsing, storage, and serialization of CSS values</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <script type="text/javascript" src="property_database.js"></script> 9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 10 <style type="text/css" id="prereqsheet"> 11 #testnode {} 12 </style> 13 </head> 14 <body> 15 <p id="display"></p> 16 <div id="content" style="display: none"> 17 18 <div id="testnode"></div> 19 20 </div> 21 <pre id="test"> 22 <script class="testbody" type="text/javascript"> 23 24 /** Test for parsing, storage, and serialization of CSS values */ 25 26 /* 27 * The idempotence tests here deserve a little bit of explanation. What 28 * we're testing here are the following operations: 29 * parse: string -> CSS rule 30 * serialize: CSS rule -> string (normalization 1) 31 * (this actually has two variants that go through partly different 32 * codepaths, which we exercise with getPropertyValue and cssText) 33 * compute: CSS rule -> computed style 34 * cserialize: computed style -> string (normalization 2) 35 * 36 * Both serialize and cserialize do some normalization, so we can't test 37 * for pure round-tripping, and we also can't compare their output since 38 * they could normalize differently. (We might at some point in the 39 * future want to guarantee that any output of cserialize is 40 * untouched by going through parse+serialize, though.) 41 * 42 * So we test idempotence of parse + serialize by running the whole 43 * operation twice. Likewise for parse + compute + cserialize. 44 * 45 * Slightly more interestingly, we test that serialize + parse is the 46 * identity transform by comparing the output of parse + compute + 47 * cserialize to the output of parse + serialize + parse + compute + 48 * cserialize. 49 */ 50 51 var gSystemFont = [ 52 "caption", 53 "icon", 54 "menu", 55 "message-box", 56 "small-caption", 57 "status-bar", 58 "-moz-button", 59 "-moz-pull-down-menu", 60 "-moz-list", 61 "-moz-field", 62 ]; 63 64 var gBadCompute = { 65 // output wrapped around to positive, in exponential notation 66 "-moz-box-ordinal-group": [ "-1", "-1000" ], 67 }; 68 69 function xfail_compute(property, value) 70 { 71 if (property in gBadCompute && 72 gBadCompute[property].includes(value)) 73 return true; 74 75 return false; 76 } 77 78 // constructed to map longhands ==> list of containing shorthands 79 var gPropertyShorthands = {}; 80 81 var gElement = document.getElementById("testnode"); 82 var gDeclaration = gElement.style; 83 var gComputedStyle = window.getComputedStyle(gElement); 84 85 var gPrereqDeclaration = 86 document.getElementById("prereqsheet").sheet.cssRules[0].style; 87 88 // On Android, avoid most 'TEST-PASS' logging by overriding 89 // SimpleTest.is/isnot, to improve performance 90 if (navigator.appVersion.includes("Android")) { 91 is = function is(a, b, name) 92 { 93 var pass = Object.is(a, b); 94 if (!pass) 95 SimpleTest.is(a, b, name); 96 } 97 98 isnot = function isnot(a, b, name) 99 { 100 var pass = !Object.is(a, b); 101 if (!pass) 102 SimpleTest.isnot(a, b, name); 103 } 104 } 105 106 // Returns true if propA and propB are equivalent, considering aliasing. 107 // (i.e. if one is an alias of the other, or if they're both aliases of 108 // the same 3rd property) 109 function are_properties_aliased(propA, propB) 110 { 111 // If either property is an alias, replace it with the property it aliases. 112 if ("alias_for" in gCSSProperties[propA]) { 113 propA = gCSSProperties[propA].alias_for; 114 } 115 if ("alias_for" in gCSSProperties[propB]) { 116 propB = gCSSProperties[propB].alias_for; 117 } 118 119 return propA == propB; 120 } 121 122 function test_property(property) 123 { 124 var info = gCSSProperties[property]; 125 126 // can all properties be removed from the style? 127 function test_remove_all_properties(propName, value) { 128 var i, p = []; 129 for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]); 130 for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]); 131 var errstr = "when setting property " + propName + " to " + value; 132 is(gDeclaration.length, 0, "unremovable properties " + errstr); 133 is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr); 134 } 135 136 function test_other_shorthands_empty(value, subprop) { 137 if (!(subprop in gPropertyShorthands)) return; 138 var shorthands = gPropertyShorthands[subprop]; 139 for (idx in shorthands) { 140 var sh = shorthands[idx]; 141 if (are_properties_aliased(sh, property)) { 142 continue; 143 } 144 is(gDeclaration.getPropertyValue(sh), "", 145 "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')"); 146 } 147 } 148 149 function test_value(value, resolved_value) { 150 var value_has_variable_reference = resolved_value != null; 151 var is_system_font = property == "font" && gSystemFont.includes(value); 152 153 var colon = ": "; 154 gDeclaration.setProperty(property, value, ""); 155 156 var idx; 157 158 var step1val = gDeclaration.getPropertyValue(property); 159 var step1vals = []; 160 var step1ser = gDeclaration.cssText; 161 if ("subproperties" in info) 162 for (idx in info.subproperties) 163 step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx])); 164 var step1comp; 165 var step1comps = []; 166 if (info.type != CSS_TYPE_TRUE_SHORTHAND) 167 step1comp = gComputedStyle.getPropertyValue(property); 168 if ("subproperties" in info) 169 for (idx in info.subproperties) 170 step1comps.push(gComputedStyle.getPropertyValue(info.subproperties[idx])); 171 172 SimpleTest.isnot(step1val, "", "setting '" + value + "' on '" + property + "'"); 173 if ("subproperties" in info && 174 // System font doesn't produce meaningful value for subproperties. 175 !is_system_font) 176 for (idx in info.subproperties) { 177 var subprop = info.subproperties[idx]; 178 if (value_has_variable_reference && 179 (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND || 180 info.type == CSS_TYPE_LEGACY_SHORTHAND)) { 181 is(gDeclaration.getPropertyValue(subprop), "", 182 "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); 183 test_other_shorthands_empty(value, subprop); 184 } else { 185 isnot(gDeclaration.getPropertyValue(subprop), "", 186 "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); 187 } 188 } 189 190 // We don't care particularly about the whitespace or the placement of 191 // semicolons, but for simplicity we'll test the current behavior. 192 var expected_serialization = ""; 193 if (step1val != "") { 194 if ("alias_for" in info) { 195 let newValue = info.legacy_mapping && info.legacy_mapping[step1val] 196 ? info.legacy_mapping[step1val] : step1val; 197 // FIXME(emilio): This is a bit unfortunate: 198 // https://github.com/w3c/csswg-drafts/issues/3332 199 if (info.type == CSS_TYPE_LEGACY_SHORTHAND && value_has_variable_reference) 200 newValue = ""; 201 expected_serialization = info.alias_for + colon + newValue + ";"; 202 } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) { 203 is(property, "zoom", "Zoom is a bit special because it never " + 204 "serializes as-is, we always serialize the longhands, " + 205 "but it doesn't just map to a single property " + 206 "(and thus we can't use the 'alias_for' mechanism)"); 207 let transform = step1val == "1" ? "none" : "scale(" + step1val + ")"; 208 let origin = step1val == "1" ? "50% 50% 0px" : "0px 0px 0px"; 209 if (value_has_variable_reference) { // See above. 210 transform = ""; 211 origin = ""; 212 } 213 expected_serialization = "transform" + colon + transform + "; transform-origin" + colon + origin + ";"; 214 } else { 215 expected_serialization = property + colon + step1val + ";"; 216 } 217 } 218 is(step1ser, expected_serialization, 219 "serialization should match property value"); 220 221 gDeclaration.removeProperty(property); 222 gDeclaration.setProperty(property, step1val, ""); 223 224 is(gDeclaration.getPropertyValue(property), step1val, 225 "parse+serialize should be idempotent for '" + 226 property + colon + value + "'"); 227 if (info.type != CSS_TYPE_TRUE_SHORTHAND) { 228 is(gComputedStyle.getPropertyValue(property), step1comp, 229 "serialize+parse should be identity transform for '" + 230 property + ": " + value + "'"); 231 } 232 233 if ("subproperties" in info && 234 // Using setProperty over subproperties is not sufficient for 235 // system fonts, since the shorthand does more than its parts. 236 !is_system_font && 237 !value_has_variable_reference) { 238 gDeclaration.removeProperty(property); 239 for (idx in info.subproperties) { 240 var subprop = info.subproperties[idx]; 241 gDeclaration.setProperty(subprop, step1vals[idx], ""); 242 } 243 244 // Now that all the subprops are set, check their values. Note that we 245 // need this in a separate loop, in case parts of the shorthand affect 246 // the computed values of other parts. 247 for (idx in info.subproperties) { 248 var subprop = info.subproperties[idx]; 249 is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], 250 "serialize(" + subprop + ")+parse should be the identity " + 251 "transform for '" + property + ": " + value + "'"); 252 } 253 is(gDeclaration.getPropertyValue(property), step1val, 254 "parse+split+serialize should be idempotent for '" + 255 property + colon + value + "'"); 256 } 257 258 // FIXME(emilio): Why is mask special? 259 if (info.type != CSS_TYPE_TRUE_SHORTHAND && 260 property != "mask") { 261 gDeclaration.removeProperty(property); 262 gDeclaration.setProperty(property, step1comp, ""); 263 var func = xfail_compute(property, value) ? todo_is : is; 264 func(gComputedStyle.getPropertyValue(property), step1comp, 265 "parse+compute+serialize should be idempotent for '" + 266 property + ": " + value + "'"); 267 } 268 if ("subproperties" in info && !is_system_font) { 269 gDeclaration.removeProperty(property); 270 for (idx in info.subproperties) { 271 var subprop = info.subproperties[idx]; 272 gDeclaration.setProperty(subprop, step1comps[idx], ""); 273 } 274 275 // Now that all the subprops are set, check their values. Note that we 276 // need this in a separate loop, in case parts of the shorthand affect 277 // the computed values of other parts. 278 for (idx in info.subproperties) { 279 var subprop = info.subproperties[idx]; 280 is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], 281 "parse+compute+serialize(" + subprop + ") should be idempotent for '" + 282 property + ": " + value + "'"); 283 } 284 } 285 286 // sanity check shorthands to make sure disabled props aren't exposed 287 if (info.type != CSS_TYPE_LONGHAND) { 288 gDeclaration.setProperty(property, value, ""); 289 test_remove_all_properties(property, value); 290 } 291 292 gDeclaration.removeProperty(property); 293 } 294 295 function test_value_without_variable(value) { 296 test_value(value, null); 297 } 298 299 function test_value_with_variable(value) { 300 gPrereqDeclaration.setProperty("--a", value, ""); 301 test_value("var(--a)", value); 302 gPrereqDeclaration.removeProperty("--a"); 303 } 304 305 if ("prerequisites" in info) { 306 var prereqs = info.prerequisites; 307 for (var prereq in prereqs) { 308 gPrereqDeclaration.setProperty(prereq, prereqs[prereq], ""); 309 } 310 } 311 312 var idx; 313 for (idx in info.initial_values) { 314 test_value_without_variable(info.initial_values[idx]); 315 test_value_with_variable(info.initial_values[idx]); 316 } 317 for (idx in info.other_values) { 318 test_value_without_variable(info.other_values[idx]); 319 test_value_with_variable(info.other_values[idx]); 320 } 321 322 if ("prerequisites" in info) { 323 for (var prereq in info.prerequisites) { 324 gPrereqDeclaration.removeProperty(prereq); 325 } 326 } 327 328 } 329 330 function runTest() { 331 // To avoid triggering the slow script dialog, we have to test one 332 // property at a time. 333 var props = []; 334 for (var prop in gCSSProperties) { 335 var info = gCSSProperties[prop]; 336 if ("subproperties" in info) { 337 for (var idx in info.subproperties) { 338 var subprop = info.subproperties[idx]; 339 if (!(subprop in gPropertyShorthands)) { 340 gPropertyShorthands[subprop] = []; 341 } 342 gPropertyShorthands[subprop].push(prop); 343 } 344 } 345 props.push(prop); 346 } 347 props = props.reverse(); 348 function do_one() { 349 if (props.length == 0) { 350 SimpleTest.finish(); 351 return; 352 } 353 test_property(props.pop()); 354 SimpleTest.executeSoon(do_one); 355 } 356 SimpleTest.executeSoon(do_one); 357 } 358 359 SimpleTest.waitForExplicitFinish(); 360 SimpleTest.requestLongerTimeout(7); 361 runTest(); 362 </script> 363 </pre> 364 </body> 365 </html>