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">
16 <div id=
"content" style=
"display: none">
18 <div id=
"testnode"></div>
22 <script class=
"testbody" type=
"text/javascript">
24 /** Test for parsing, storage, and serialization of CSS values **/
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)
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.)
42 * So we test idempotence of parse + serialize by running the whole
43 * operation twice. Likewise for parse + compute + cserialize.
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 +
59 "-moz-pull-down-menu",
65 // output wrapped around to positive, in exponential notation
66 "-moz-box-ordinal-group": [
"-1",
"-1000" ],
69 function xfail_compute(property, value)
71 if (property in gBadCompute &&
72 gBadCompute[property].includes(value))
78 // constructed to map longhands ==
> list of containing shorthands
79 var gPropertyShorthands = {};
81 var gElement = document.getElementById(
"testnode");
82 var gDeclaration = gElement.style;
83 var gComputedStyle = window.getComputedStyle(gElement);
85 var gPrereqDeclaration =
86 document.getElementById(
"prereqsheet").sheet.cssRules[
0].style;
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)
93 var pass = Object.is(a, b);
95 SimpleTest.is(a, b, name);
98 isnot = function isnot(a, b, name)
100 var pass = !Object.is(a, b);
102 SimpleTest.isnot(a, b, name);
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)
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;
115 if (
"alias_for" in gCSSProperties[propB]) {
116 propB = gCSSProperties[propB].alias_for;
119 return propA == propB;
122 function test_property(property)
124 var info = gCSSProperties[property];
126 // can all properties be removed from the style?
127 function test_remove_all_properties(propName, value) {
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);
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)) {
144 is(gDeclaration.getPropertyValue(sh),
"",
145 "setting '" + value +
"' on '" + property +
"' (for shorthand '" + sh +
"')");
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);
154 gDeclaration.setProperty(property, value,
"");
158 var step1val = gDeclaration.getPropertyValue(property);
160 var step1ser = gDeclaration.cssText;
161 if (
"subproperties" in info)
162 for (idx in info.subproperties)
163 step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx]));
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]));
172 SimpleTest.isnot(step1val,
"",
"setting '" + value +
"' on '" + property +
"'");
173 if (
"subproperties" in info &&
174 // System font doesn't produce meaningful value for subproperties.
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);
185 isnot(gDeclaration.getPropertyValue(subprop),
"",
186 "setting '" + value +
"' on '" + property +
"' (for '" + subprop +
"')");
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)
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.
213 expected_serialization =
"transform" + colon + transform +
"; transform-origin" + colon + origin +
";";
215 expected_serialization = property + colon + step1val +
";";
218 is(step1ser, expected_serialization,
219 "serialization should match property value");
221 gDeclaration.removeProperty(property);
222 gDeclaration.setProperty(property, step1val,
"");
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 +
"'");
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.
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],
"");
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 +
"'");
253 is(gDeclaration.getPropertyValue(property), step1val,
254 "parse+split+serialize should be idempotent for '" +
255 property + colon + value +
"'");
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 +
"'");
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],
"");
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 +
"'");
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);
292 gDeclaration.removeProperty(property);
295 function test_value_without_variable(value) {
296 test_value(value, null);
299 function test_value_with_variable(value) {
300 gPrereqDeclaration.setProperty(
"--a", value,
"");
301 test_value(
"var(--a)", value);
302 gPrereqDeclaration.removeProperty(
"--a");
305 if (
"prerequisites" in info) {
306 var prereqs = info.prerequisites;
307 for (var prereq in prereqs) {
308 gPrereqDeclaration.setProperty(prereq, prereqs[prereq],
"");
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]);
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]);
322 if (
"prerequisites" in info) {
323 for (var prereq in info.prerequisites) {
324 gPrereqDeclaration.removeProperty(prereq);
331 // To avoid triggering the slow script dialog, we have to test one
332 // property at a time.
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] = [];
342 gPropertyShorthands[subprop].push(prop);
347 props = props.reverse();
349 if (props.length ==
0) {
353 test_property(props.pop());
354 SimpleTest.executeSoon(do_one);
356 SimpleTest.executeSoon(do_one);
359 SimpleTest.waitForExplicitFinish();
360 SimpleTest.requestLongerTimeout(
7);