mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
295 lines
13 KiB
JavaScript
295 lines
13 KiB
JavaScript
"use strict";
|
|
(function(exports) {
|
|
const sheet = document.head.appendChild(document.createElement("style"));
|
|
|
|
// Specify size for outer <div> to avoid unconstrained-size warnings
|
|
// when writing-mode of the inner test <div> is vertical-*
|
|
const wrapper = document.body.appendChild(document.createElement("div"));
|
|
wrapper.style.cssText = "width:100px; height: 100px;";
|
|
const testElement = wrapper.appendChild(document.createElement("div"));
|
|
testElement.id = testElement.className = "test";
|
|
|
|
// Values to use while testing
|
|
const testValues = {
|
|
"length": ["1px", "2px", "3px", "4px", "5px"],
|
|
"color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"],
|
|
"border-style": ["solid", "dashed", "dotted", "double", "groove"],
|
|
};
|
|
|
|
// Six unique overall writing modes for property-mapping purposes.
|
|
const writingModes = [
|
|
{
|
|
styles: [
|
|
{"writing-mode": "horizontal-tb", "direction": "ltr"},
|
|
],
|
|
blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right",
|
|
block: "vertical", inline: "horizontal" },
|
|
{
|
|
styles: [
|
|
{"writing-mode": "horizontal-tb", "direction": "rtl"},
|
|
],
|
|
blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left",
|
|
block: "vertical", inline: "horizontal" },
|
|
{
|
|
styles: [
|
|
{"writing-mode": "vertical-rl", "direction": "rtl"},
|
|
{"writing-mode": "sideways-rl", "direction": "rtl"},
|
|
],
|
|
blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top",
|
|
block: "horizontal", inline: "vertical" },
|
|
{
|
|
styles: [
|
|
{"writing-mode": "vertical-rl", "direction": "ltr"},
|
|
{"writing-mode": "sideways-rl", "direction": "ltr"},
|
|
],
|
|
blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom",
|
|
block: "horizontal", inline: "vertical" },
|
|
{
|
|
styles: [
|
|
{"writing-mode": "vertical-lr", "direction": "rtl"},
|
|
{"writing-mode": "sideways-lr", "direction": "ltr"},
|
|
],
|
|
blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top",
|
|
block: "horizontal", inline: "vertical" },
|
|
{
|
|
styles: [
|
|
{"writing-mode": "vertical-lr", "direction": "ltr"},
|
|
{"writing-mode": "sideways-lr", "direction": "rtl"},
|
|
],
|
|
blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom",
|
|
block: "horizontal", inline: "vertical" },
|
|
];
|
|
|
|
function testCSSValues(testName, style, expectedValues) {
|
|
for (const [property, value] of expectedValues) {
|
|
assert_equals(style.getPropertyValue(property), value, `${testName}, ${property}`);
|
|
}
|
|
}
|
|
|
|
function testComputedValues(testName, rules, expectedValues) {
|
|
sheet.textContent = rules;
|
|
const cs = getComputedStyle(testElement);
|
|
testCSSValues(testName, cs, expectedValues);
|
|
sheet.textContent = "";
|
|
}
|
|
|
|
function makeDeclaration(object = {}, replacement = "*") {
|
|
let decl = "";
|
|
for (const [property, value] of Object.entries(object)) {
|
|
decl += `${property.replace("*", replacement)}: ${value}; `;
|
|
}
|
|
return decl;
|
|
}
|
|
|
|
/**
|
|
* Creates a group of physical and logical box properties, such as
|
|
*
|
|
* { physical: {
|
|
* left: "margin-left", right: "margin-right",
|
|
* top: "margin-top", bottom: "margin-bottom",
|
|
* }, logical: {
|
|
* inlineStart: "margin-inline-start", inlineEnd: "margin-inline-end",
|
|
* blockStart: "margin-block-start", blockEnd: "margin-block-end",
|
|
* }, shorthands: {
|
|
* inline: ["margin-inline-start", "margin-inline-end"],
|
|
* block: ["margin-block-start", "margin-block-end"],
|
|
* }, type: ["length"], prerequisites: "...", property: "'margin-*'" }
|
|
*
|
|
* @param {string} property
|
|
* A string representing the property names, like "margin-*".
|
|
* @param {Object} descriptor
|
|
* @param {string|string[]} descriptor.type
|
|
* Describes the kind of values accepted by the property, like "length".
|
|
* Must be a key or a collection of keys from the `testValues` object.
|
|
* @param {Object={}} descriptor.prerequisites
|
|
* Represents property declarations that are needed by `property` to work.
|
|
* For example, border-width properties require a border style.
|
|
*/
|
|
exports.createBoxPropertyGroup = function(property, descriptor) {
|
|
const logical = {};
|
|
const physical = {};
|
|
const shorthands = {};
|
|
for (const axis of ["inline", "block"]) {
|
|
const shorthand = property.replace("*", axis);
|
|
const longhands = [];
|
|
shorthands[shorthand] = longhands;
|
|
for (const side of ["start", "end"]) {
|
|
const logicalSide = axis + "-" + side;
|
|
const camelCase = logicalSide.replace(/-(.)/g, (match, $1) => $1.toUpperCase());
|
|
const longhand = property.replace("*", logicalSide);
|
|
logical[camelCase] = longhand;
|
|
longhands.push(longhand);
|
|
}
|
|
}
|
|
const isInset = property === "inset-*";
|
|
let prerequisites = "";
|
|
for (const physicalSide of ["left", "right", "top", "bottom"]) {
|
|
physical[physicalSide] = isInset ? physicalSide : property.replace("*", physicalSide);
|
|
prerequisites += makeDeclaration(descriptor.prerequisites, physicalSide);
|
|
}
|
|
const type = [].concat(descriptor.type);
|
|
return {name, logical, physical, shorthands, type, prerequisites, property};
|
|
};
|
|
|
|
/**
|
|
* Creates a group of physical and logical sizing properties.
|
|
*
|
|
* @param {string} prefix
|
|
* One of "", "max-" or "min-".
|
|
*/
|
|
exports.createSizingPropertyGroup = function(prefix) {
|
|
return {
|
|
logical: {
|
|
inline: `${prefix}inline-size`,
|
|
block: `${prefix}block-size`,
|
|
},
|
|
physical: {
|
|
horizontal: `${prefix}width`,
|
|
vertical: `${prefix}height`,
|
|
},
|
|
type: ["length"],
|
|
prerequisites: makeDeclaration({display: "block"}),
|
|
property: (prefix ? prefix.slice(0, -1) + " " : "") + "sizing",
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Tests a grup of logical and physical properties in different writing modes.
|
|
*
|
|
* @param {Object} group
|
|
* An object returned by createBoxPropertyGroup or createSizingPropertyGroup.
|
|
*/
|
|
exports.runTests = function(group) {
|
|
const values = testValues[group.type[0]].map(function(_, i) {
|
|
return group.type.map(type => testValues[type][i]).join(" ");
|
|
});
|
|
const logicals = Object.values(group.logical);
|
|
const physicals = Object.values(group.physical);
|
|
const shorthands = group.shorthands ? Object.entries(group.shorthands) : null;
|
|
|
|
test(function() {
|
|
const expected = [];
|
|
for (const [i, logicalProp] of logicals.entries()) {
|
|
testElement.style.setProperty(logicalProp, values[i]);
|
|
expected.push([logicalProp, values[i]]);
|
|
}
|
|
testCSSValues("logical properties in inline style", testElement.style, expected);
|
|
}, `Test that logical ${group.property} properties are supported.`);
|
|
testElement.style.cssText = "";
|
|
|
|
for (const writingMode of writingModes) {
|
|
for (const style of writingMode.styles) {
|
|
const writingModeDecl = makeDeclaration(style);
|
|
|
|
const associated = {};
|
|
for (const [logicalSide, logicalProp] of Object.entries(group.logical)) {
|
|
const physicalProp = group.physical[writingMode[logicalSide]];
|
|
associated[logicalProp] = physicalProp;
|
|
associated[physicalProp] = logicalProp;
|
|
}
|
|
|
|
// Test that logical properties are converted to their physical
|
|
// equivalent correctly when all in the group are present on a single
|
|
// declaration, with no overwriting of previous properties and
|
|
// no physical properties present. We put the writing mode properties
|
|
// on a separate declaration to test that the computed values of these
|
|
// properties are used, rather than those on the same declaration.
|
|
test(function() {
|
|
let decl = group.prerequisites;
|
|
const expected = [];
|
|
for (const [i, logicalProp] of logicals.entries()) {
|
|
decl += `${logicalProp}: ${values[i]}; `;
|
|
expected.push([logicalProp, values[i]]);
|
|
expected.push([associated[logicalProp], values[i]]);
|
|
}
|
|
testComputedValues("logical properties on one declaration, writing " +
|
|
`mode properties on another, '${writingModeDecl}'`,
|
|
`.test { ${writingModeDecl} } .test { ${decl} }`,
|
|
expected);
|
|
}, `Test that logical ${group.property} properties share computed values `
|
|
+ `with their physical associates, with '${writingModeDecl}'.`);
|
|
|
|
|
|
// Test logical shorthand properties.
|
|
if (shorthands) {
|
|
test(function() {
|
|
for (const [shorthand, longhands] of shorthands) {
|
|
let shorthandValues;
|
|
if (group.type.length > 1) {
|
|
shorthandValues = [values[0]];
|
|
} else {
|
|
shorthandValues = testValues[group.type].slice(0, longhands.length);
|
|
}
|
|
const decl = group.prerequisites + `${shorthand}: ${shorthandValues.join(" ")}; `;
|
|
const expected = [];
|
|
for (let [i, longhand] of longhands.entries()) {
|
|
const longhandValue = shorthandValues[group.type.length > 1 ? 0 : i];
|
|
expected.push([longhand, longhandValue]);
|
|
expected.push([associated[longhand], longhandValue]);
|
|
}
|
|
testComputedValues("shorthand properties on one declaration, writing " +
|
|
`mode properties on another, '${writingModeDecl}'`,
|
|
`.test { ${writingModeDecl} } .test { ${decl} }`,
|
|
expected);
|
|
}
|
|
}, `Test that ${group.property} shorthands set the computed value of both `
|
|
+ `logical and physical longhands, with '${writingModeDecl}'.`);
|
|
}
|
|
|
|
// Test that logical and physical properties are cascaded together,
|
|
// honoring their relative order on a single declaration
|
|
// (a) with a single logical property after the physical ones
|
|
// (b) with a single physical property after the logical ones
|
|
test(function() {
|
|
for (const lastIsLogical of [true, false]) {
|
|
const lasts = lastIsLogical ? logicals : physicals;
|
|
const others = lastIsLogical ? physicals : logicals;
|
|
for (const lastProp of lasts) {
|
|
let decl = writingModeDecl + group.prerequisites;
|
|
const expected = [];
|
|
for (const [i, prop] of others.entries()) {
|
|
decl += `${prop}: ${values[i]}; `;
|
|
const valueIdx = associated[prop] === lastProp ? others.length : i;
|
|
expected.push([prop, values[valueIdx]]);
|
|
expected.push([associated[prop], values[valueIdx]]);
|
|
}
|
|
decl += `${lastProp}: ${values[others.length]}; `;
|
|
testComputedValues(`'${lastProp}' last on single declaration, '${writingModeDecl}'`,
|
|
`.test { ${decl} }`,
|
|
expected);
|
|
}
|
|
}
|
|
}, `Test that ${group.property} properties honor order of appearance when both `
|
|
+ `logical and physical associates are declared, with '${writingModeDecl}'.`);
|
|
|
|
// Test that logical and physical properties are cascaded properly when
|
|
// on different declarations
|
|
// (a) with a logical property in the high specificity rule
|
|
// (b) with a physical property in the high specificity rule
|
|
test(function() {
|
|
for (const highIsLogical of [true, false]) {
|
|
let lowDecl = writingModeDecl + group.prerequisites;
|
|
const high = highIsLogical ? logicals : physicals;
|
|
const others = highIsLogical ? physicals : logicals;
|
|
for (const [i, prop] of others.entries()) {
|
|
lowDecl += `${prop}: ${values[i]}; `;
|
|
}
|
|
for (const highProp of high) {
|
|
const highDecl = `${highProp}: ${values[others.length]}; `;
|
|
const expected = [];
|
|
for (const [i, prop] of others.entries()) {
|
|
const valueIdx = associated[prop] === highProp ? others.length : i;
|
|
expected.push([prop, values[valueIdx]]);
|
|
expected.push([associated[prop], values[valueIdx]]);
|
|
}
|
|
testComputedValues(`'${highProp}', two declarations, '${writingModeDecl}'`,
|
|
`#test { ${highDecl} } .test { ${lowDecl} }`,
|
|
expected);
|
|
}
|
|
}
|
|
}, `Test that ${group.property} properties honor selector specificty when both `
|
|
+ `logical and physical associates are declared, with '${writingModeDecl}'.`);
|
|
}
|
|
}
|
|
};
|
|
})(window);
|