diff options
Diffstat (limited to 'layout/style/test/test_value_storage.html')
-rw-r--r-- | layout/style/test/test_value_storage.html | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/layout/style/test/test_value_storage.html b/layout/style/test/test_value_storage.html new file mode 100644 index 0000000000..542cad91ed --- /dev/null +++ b/layout/style/test/test_value_storage.html @@ -0,0 +1,365 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <title>Test for parsing, storage, and serialization of CSS values</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="property_database.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style type="text/css" id="prereqsheet"> + #testnode {} + </style> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +<div id="testnode"></div> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for parsing, storage, and serialization of CSS values **/ + +/* + * The idempotence tests here deserve a little bit of explanation. What + * we're testing here are the following operations: + * parse: string -> CSS rule + * serialize: CSS rule -> string (normalization 1) + * (this actually has two variants that go through partly different + * codepaths, which we exercise with getPropertyValue and cssText) + * compute: CSS rule -> computed style + * cserialize: computed style -> string (normalization 2) + * + * Both serialize and cserialize do some normalization, so we can't test + * for pure round-tripping, and we also can't compare their output since + * they could normalize differently. (We might at some point in the + * future want to guarantee that any output of cserialize is + * untouched by going through parse+serialize, though.) + * + * So we test idempotence of parse + serialize by running the whole + * operation twice. Likewise for parse + compute + cserialize. + * + * Slightly more interestingly, we test that serialize + parse is the + * identity transform by comparing the output of parse + compute + + * cserialize to the output of parse + serialize + parse + compute + + * cserialize. + */ + +var gSystemFont = [ + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "-moz-button", + "-moz-pull-down-menu", + "-moz-list", + "-moz-field", +]; + +var gBadCompute = { + // output wrapped around to positive, in exponential notation + "-moz-box-ordinal-group": [ "-1", "-1000" ], +}; + +function xfail_compute(property, value) +{ + if (property in gBadCompute && + gBadCompute[property].includes(value)) + return true; + + return false; +} + +// constructed to map longhands ==> list of containing shorthands +var gPropertyShorthands = {}; + +var gElement = document.getElementById("testnode"); +var gDeclaration = gElement.style; +var gComputedStyle = window.getComputedStyle(gElement); + +var gPrereqDeclaration = + document.getElementById("prereqsheet").sheet.cssRules[0].style; + +// On Android, avoid most 'TEST-PASS' logging by overriding +// SimpleTest.is/isnot, to improve performance +if (navigator.appVersion.includes("Android")) { + is = function is(a, b, name) + { + var pass = Object.is(a, b); + if (!pass) + SimpleTest.is(a, b, name); + } + + isnot = function isnot(a, b, name) + { + var pass = !Object.is(a, b); + if (!pass) + SimpleTest.isnot(a, b, name); + } +} + +// Returns true if propA and propB are equivalent, considering aliasing. +// (i.e. if one is an alias of the other, or if they're both aliases of +// the same 3rd property) +function are_properties_aliased(propA, propB) +{ + // If either property is an alias, replace it with the property it aliases. + if ("alias_for" in gCSSProperties[propA]) { + propA = gCSSProperties[propA].alias_for; + } + if ("alias_for" in gCSSProperties[propB]) { + propB = gCSSProperties[propB].alias_for; + } + + return propA == propB; +} + +function test_property(property) +{ + var info = gCSSProperties[property]; + + // can all properties be removed from the style? + function test_remove_all_properties(propName, value) { + var i, p = []; + for (i = 0; i < gDeclaration.length; i++) p.push(gDeclaration[i]); + for (i = 0; i < p.length; i++) gDeclaration.removeProperty(p[i]); + var errstr = "when setting property " + propName + " to " + value; + is(gDeclaration.length, 0, "unremovable properties " + errstr); + is(gDeclaration.cssText, "", "non-empty serialization after removing all properties " + errstr); + } + + function test_other_shorthands_empty(value, subprop) { + if (!(subprop in gPropertyShorthands)) return; + var shorthands = gPropertyShorthands[subprop]; + for (idx in shorthands) { + var sh = shorthands[idx]; + if (are_properties_aliased(sh, property)) { + continue; + } + is(gDeclaration.getPropertyValue(sh), "", + "setting '" + value + "' on '" + property + "' (for shorthand '" + sh + "')"); + } + } + + function test_value(value, resolved_value) { + var value_has_variable_reference = resolved_value != null; + var is_system_font = property == "font" && gSystemFont.includes(value); + + var colon = ": "; + gDeclaration.setProperty(property, value, ""); + + var idx; + + var step1val = gDeclaration.getPropertyValue(property); + var step1vals = []; + var step1ser = gDeclaration.cssText; + if ("subproperties" in info) + for (idx in info.subproperties) + step1vals.push(gDeclaration.getPropertyValue(info.subproperties[idx])); + var step1comp; + var step1comps = []; + if (info.type != CSS_TYPE_TRUE_SHORTHAND) + step1comp = gComputedStyle.getPropertyValue(property); + if ("subproperties" in info) + for (idx in info.subproperties) + step1comps.push(gComputedStyle.getPropertyValue(info.subproperties[idx])); + + SimpleTest.isnot(step1val, "", "setting '" + value + "' on '" + property + "'"); + if ("subproperties" in info && + // System font doesn't produce meaningful value for subproperties. + !is_system_font) + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + if (value_has_variable_reference && + (!info.alias_for || info.type == CSS_TYPE_TRUE_SHORTHAND || + info.type == CSS_TYPE_LEGACY_SHORTHAND)) { + is(gDeclaration.getPropertyValue(subprop), "", + "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); + test_other_shorthands_empty(value, subprop); + } else { + isnot(gDeclaration.getPropertyValue(subprop), "", + "setting '" + value + "' on '" + property + "' (for '" + subprop + "')"); + } + } + + // We don't care particularly about the whitespace or the placement of + // semicolons, but for simplicity we'll test the current behavior. + var expected_serialization = ""; + if (step1val != "") { + if ("alias_for" in info) { + let newValue = info.legacy_mapping && info.legacy_mapping[step1val] + ? info.legacy_mapping[step1val] : step1val; + // FIXME(emilio): This is a bit unfortunate: + // https://github.com/w3c/csswg-drafts/issues/3332 + if (info.type == CSS_TYPE_LEGACY_SHORTHAND && value_has_variable_reference) + newValue = ""; + expected_serialization = info.alias_for + colon + newValue + ";"; + } else if (info.type == CSS_TYPE_LEGACY_SHORTHAND) { + is(property, "zoom", "Zoom is a bit special because it never " + + "serializes as-is, we always serialize the longhands, " + + "but it doesn't just map to a single property " + + "(and thus we can't use the 'alias_for' mechanism)"); + let transform = step1val == "1" ? "none" : "scale(" + step1val + ")"; + let origin = step1val == "1" ? "50% 50% 0px" : "0px 0px 0px"; + if (value_has_variable_reference) { // See above. + transform = ""; + origin = ""; + } + expected_serialization = "transform" + colon + transform + "; transform-origin" + colon + origin + ";"; + } else { + expected_serialization = property + colon + step1val + ";"; + } + } + is(step1ser, expected_serialization, + "serialization should match property value"); + + gDeclaration.removeProperty(property); + gDeclaration.setProperty(property, step1val, ""); + + is(gDeclaration.getPropertyValue(property), step1val, + "parse+serialize should be idempotent for '" + + property + colon + value + "'"); + if (info.type != CSS_TYPE_TRUE_SHORTHAND) { + is(gComputedStyle.getPropertyValue(property), step1comp, + "serialize+parse should be identity transform for '" + + property + ": " + value + "'"); + } + + if ("subproperties" in info && + // Using setProperty over subproperties is not sufficient for + // system fonts, since the shorthand does more than its parts. + !is_system_font && + !value_has_variable_reference) { + gDeclaration.removeProperty(property); + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + gDeclaration.setProperty(subprop, step1vals[idx], ""); + } + + // Now that all the subprops are set, check their values. Note that we + // need this in a separate loop, in case parts of the shorthand affect + // the computed values of other parts. + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], + "serialize(" + subprop + ")+parse should be the identity " + + "transform for '" + property + ": " + value + "'"); + } + is(gDeclaration.getPropertyValue(property), step1val, + "parse+split+serialize should be idempotent for '" + + property + colon + value + "'"); + } + + // FIXME(emilio): Why is mask special? + if (info.type != CSS_TYPE_TRUE_SHORTHAND && + property != "mask") { + gDeclaration.removeProperty(property); + gDeclaration.setProperty(property, step1comp, ""); + var func = xfail_compute(property, value) ? todo_is : is; + func(gComputedStyle.getPropertyValue(property), step1comp, + "parse+compute+serialize should be idempotent for '" + + property + ": " + value + "'"); + } + if ("subproperties" in info && !is_system_font) { + gDeclaration.removeProperty(property); + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + gDeclaration.setProperty(subprop, step1comps[idx], ""); + } + + // Now that all the subprops are set, check their values. Note that we + // need this in a separate loop, in case parts of the shorthand affect + // the computed values of other parts. + for (idx in info.subproperties) { + var subprop = info.subproperties[idx]; + is(gComputedStyle.getPropertyValue(subprop), step1comps[idx], + "parse+compute+serialize(" + subprop + ") should be idempotent for '" + + property + ": " + value + "'"); + } + } + + // sanity check shorthands to make sure disabled props aren't exposed + if (info.type != CSS_TYPE_LONGHAND) { + gDeclaration.setProperty(property, value, ""); + test_remove_all_properties(property, value); + } + + gDeclaration.removeProperty(property); + } + + function test_value_without_variable(value) { + test_value(value, null); + } + + function test_value_with_variable(value) { + gPrereqDeclaration.setProperty("--a", value, ""); + test_value("var(--a)", value); + gPrereqDeclaration.removeProperty("--a"); + } + + if ("prerequisites" in info) { + var prereqs = info.prerequisites; + for (var prereq in prereqs) { + gPrereqDeclaration.setProperty(prereq, prereqs[prereq], ""); + } + } + + var idx; + for (idx in info.initial_values) { + test_value_without_variable(info.initial_values[idx]); + test_value_with_variable(info.initial_values[idx]); + } + for (idx in info.other_values) { + test_value_without_variable(info.other_values[idx]); + test_value_with_variable(info.other_values[idx]); + } + + if ("prerequisites" in info) { + for (var prereq in info.prerequisites) { + gPrereqDeclaration.removeProperty(prereq); + } + } + +} + +function runTest() { + // To avoid triggering the slow script dialog, we have to test one + // property at a time. + var props = []; + for (var prop in gCSSProperties) { + var info = gCSSProperties[prop]; + if ("subproperties" in info) { + for (var idx in info.subproperties) { + var subprop = info.subproperties[idx]; + if (!(subprop in gPropertyShorthands)) { + gPropertyShorthands[subprop] = []; + } + gPropertyShorthands[subprop].push(prop); + } + } + props.push(prop); + } + props = props.reverse(); + function do_one() { + if (props.length == 0) { + SimpleTest.finish(); + return; + } + test_property(props.pop()); + SimpleTest.executeSoon(do_one); + } + SimpleTest.executeSoon(do_one); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(7); +runTest(); +</script> +</pre> +</body> +</html> |