Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: layout/style/test/mochitest.toml
<!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:
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>