fune/devtools/client/shared/test/browser_outputparser.js
Alexandre Poirot 2e2e9b1590 Bug 1320607 - [devtools] Remove the generated CSS Properties database. r=devtools-reviewers,nchevobbe
This wasn't really used anymore.
We are fetching the database from the server runtime in order to support
remote debugging correctly, where frontend CSS may be different from debuggee CSS.

Differential Revision: https://phabricator.services.mozilla.com/D187492
2023-09-07 20:59:34 +00:00

835 lines
24 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function () {
await pushPref("layout.css.backdrop-filter.enabled", true);
await pushPref("layout.css.individual-transform.enabled", true);
await pushPref("layout.css.color-mix.enabled", true);
await pushPref("layout.css.motion-path-basic-shapes.enabled", true);
await addTab("about:blank");
await performTest();
gBrowser.removeCurrentTab();
});
async function performTest() {
await SpecialPowers.pushPrefEnv({
set: [["security.allow_unsafe_parent_loads", true]],
});
const OutputParser = require("resource://devtools/client/shared/output-parser.js");
const { host, doc } = await createHost(
"bottom",
"data:text/html," + "<h1>browser_outputParser.js</h1><div></div>"
);
const cssProperties = getClientCssProperties();
const parser = new OutputParser(doc, cssProperties);
testParseCssProperty(doc, parser);
testParseCssVar(doc, parser);
testParseURL(doc, parser);
testParseFilter(doc, parser);
testParseBackdropFilter(doc, parser);
testParseAngle(doc, parser);
testParseShape(doc, parser);
testParseVariable(doc, parser);
testParseColorVariable(doc, parser);
testParseFontFamily(doc, parser);
host.destroy();
}
// Class name used in color swatch.
var COLOR_TEST_CLASS = "test-class";
// Create a new CSS color-parsing test. |name| is the name of the CSS
// property. |value| is the CSS text to use. |segments| is an array
// describing the expected result. If an element of |segments| is a
// string, it is simply appended to the expected string. Otherwise,
// it must be an object with a |name| property, which is the color
// name as it appears in the input.
//
// This approach is taken to reduce boilerplate and to make it simpler
// to modify the test when the parseCssProperty output changes.
function makeColorTest(name, value, segments) {
const result = {
name,
value,
expected: "",
};
for (const segment of segments) {
if (typeof segment === "string") {
result.expected += segment;
} else {
const buttonAttributes = {
class: COLOR_TEST_CLASS,
style: `background-color:${segment.name}`,
tabindex: 0,
role: "button",
};
if (segment.colorFunction) {
buttonAttributes["data-color-function"] = segment.colorFunction;
}
const buttonAttrString = Object.entries(buttonAttributes)
.map(([attr, v]) => `${attr}="${v}"`)
.join(" ");
// prettier-ignore
result.expected +=
`<span data-color="${segment.name}">` +
`<span ${buttonAttrString}></span>`+
`<span>${segment.name}</span>` +
`</span>`;
}
}
result.desc = "Testing " + name + ": " + value;
return result;
}
function testParseCssProperty(doc, parser) {
const tests = [
makeColorTest("border", "1px solid red", ["1px solid ", { name: "red" }]),
makeColorTest(
"background-image",
"linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
[
"linear-gradient(to right, ",
{ name: "#F60", colorFunction: "linear-gradient" },
" 10%, ",
{ name: "rgba(0,0,0,1)", colorFunction: "linear-gradient" },
")",
]
),
// In "arial black", "black" is a font, not a color.
// (The font-family parser creates a span)
makeColorTest("font-family", "arial black", ["<span>arial black</span>"]),
makeColorTest("box-shadow", "0 0 1em red", ["0 0 1em ", { name: "red" }]),
makeColorTest("box-shadow", "0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)", [
"0 0 1em ",
{ name: "red" },
", 2px 2px 0 0 ",
{ name: "rgba(0,0,0,.5)" },
]),
makeColorTest("content", '"red"', ['"red"']),
// Invalid property names should not cause exceptions.
makeColorTest("hellothere", "'red'", ["'red'"]),
makeColorTest(
"filter",
"blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)",
[
'<span data-filters="blur(1px) drop-shadow(0 0 0 blue) ',
'url(red.svg#blue)"><span>',
"blur(1px) drop-shadow(0 0 0 ",
{ name: "blue", colorFunction: "drop-shadow" },
") url(red.svg#blue)</span></span>",
]
),
makeColorTest("color", "currentColor", ["currentColor"]),
// Test a very long property.
makeColorTest(
"background-image",
"linear-gradient(to left, transparent 0, transparent 5%,#F00 0, #F00 10%,#FF0 0, #FF0 15%,#0F0 0, #0F0 20%,#0FF 0, #0FF 25%,#00F 0, #00F 30%,#800 0, #800 35%,#880 0, #880 40%,#080 0, #080 45%,#088 0, #088 50%,#008 0, #008 55%,#FFF 0, #FFF 60%,#EEE 0, #EEE 65%,#CCC 0, #CCC 70%,#999 0, #999 75%,#666 0, #666 80%,#333 0, #333 85%,#111 0, #111 90%,#000 0, #000 95%,transparent 0, transparent 100%)",
[
"linear-gradient(to left, ",
{ name: "transparent", colorFunction: "linear-gradient" },
" 0, ",
{ name: "transparent", colorFunction: "linear-gradient" },
" 5%,",
{ name: "#F00", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#F00", colorFunction: "linear-gradient" },
" 10%,",
{ name: "#FF0", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#FF0", colorFunction: "linear-gradient" },
" 15%,",
{ name: "#0F0", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#0F0", colorFunction: "linear-gradient" },
" 20%,",
{ name: "#0FF", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#0FF", colorFunction: "linear-gradient" },
" 25%,",
{ name: "#00F", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#00F", colorFunction: "linear-gradient" },
" 30%,",
{ name: "#800", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#800", colorFunction: "linear-gradient" },
" 35%,",
{ name: "#880", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#880", colorFunction: "linear-gradient" },
" 40%,",
{ name: "#080", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#080", colorFunction: "linear-gradient" },
" 45%,",
{ name: "#088", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#088", colorFunction: "linear-gradient" },
" 50%,",
{ name: "#008", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#008", colorFunction: "linear-gradient" },
" 55%,",
{ name: "#FFF", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#FFF", colorFunction: "linear-gradient" },
" 60%,",
{ name: "#EEE", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#EEE", colorFunction: "linear-gradient" },
" 65%,",
{ name: "#CCC", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#CCC", colorFunction: "linear-gradient" },
" 70%,",
{ name: "#999", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#999", colorFunction: "linear-gradient" },
" 75%,",
{ name: "#666", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#666", colorFunction: "linear-gradient" },
" 80%,",
{ name: "#333", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#333", colorFunction: "linear-gradient" },
" 85%,",
{ name: "#111", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#111", colorFunction: "linear-gradient" },
" 90%,",
{ name: "#000", colorFunction: "linear-gradient" },
" 0, ",
{ name: "#000", colorFunction: "linear-gradient" },
" 95%,",
{ name: "transparent", colorFunction: "linear-gradient" },
" 0, ",
{ name: "transparent", colorFunction: "linear-gradient" },
" 100%)",
]
),
// Note the lack of a space before the color here.
makeColorTest("border", "1px dotted#f06", [
"1px dotted ",
{ name: "#f06" },
]),
makeColorTest("color", "color-mix(in srgb, red, blue)", [
"color-mix(in srgb, ",
{ name: "red", colorFunction: "color-mix" },
", ",
{ name: "blue", colorFunction: "color-mix" },
")",
]),
makeColorTest(
"background-image",
"linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue)",
[
"linear-gradient(to top, ",
"color-mix(in srgb, ",
{ name: "#008000", colorFunction: "color-mix" },
", ",
{ name: "rgba(255, 255, 0, 0.9)", colorFunction: "color-mix" },
"), ",
{ name: "blue", colorFunction: "linear-gradient" },
")",
]
),
];
const target = doc.querySelector("div");
ok(target, "captain, we have the div");
for (const test of tests) {
info(test.desc);
const frag = parser.parseCssProperty(test.name, test.value, {
colorSwatchClass: COLOR_TEST_CLASS,
});
target.appendChild(frag);
is(
target.innerHTML,
test.expected,
"CSS property correctly parsed for " + test.name + ": " + test.value
);
target.innerHTML = "";
}
}
function testParseCssVar(doc, parser) {
const frag = parser.parseCssProperty("color", "var(--some-kind-of-green)", {
colorSwatchClass: "test-colorswatch",
});
const target = doc.querySelector("div");
ok(target, "captain, we have the div");
target.appendChild(frag);
is(
target.innerHTML,
"var(--some-kind-of-green)",
"CSS property correctly parsed"
);
target.innerHTML = "";
}
function testParseURL(doc, parser) {
info("Test that URL parsing preserves quoting style");
const tests = [
{
desc: "simple test without quotes",
leader: "url(",
trailer: ")",
},
{
desc: "simple test with single quotes",
leader: "url('",
trailer: "')",
},
{
desc: "simple test with double quotes",
leader: 'url("',
trailer: '")',
},
{
desc: "test with single quotes and whitespace",
leader: "url( \t'",
trailer: "'\r\n\f)",
},
{
desc: "simple test with uppercase",
leader: "URL(",
trailer: ")",
},
{
desc: "bad url, missing paren",
leader: "url(",
trailer: "",
expectedTrailer: ")",
},
{
desc: "bad url, missing paren, with baseURI",
baseURI: "data:text/html,<style></style>",
leader: "url(",
trailer: "",
expectedTrailer: ")",
},
{
desc: "bad url, double quote, missing paren",
leader: 'url("',
trailer: '"',
expectedTrailer: '")',
},
{
desc: "bad url, single quote, missing paren and quote",
leader: "url('",
trailer: "",
expectedTrailer: "')",
},
];
for (const test of tests) {
const url = test.leader + "something.jpg" + test.trailer;
const frag = parser.parseCssProperty("background", url, {
urlClass: "test-urlclass",
baseURI: test.baseURI,
});
const target = doc.querySelector("div");
target.appendChild(frag);
const expectedTrailer = test.expectedTrailer || test.trailer;
const expected =
test.leader +
'<a target="_blank" class="test-urlclass" ' +
'href="something.jpg">something.jpg</a>' +
expectedTrailer;
is(target.innerHTML, expected, test.desc);
target.innerHTML = "";
}
}
function testParseFilter(doc, parser) {
const frag = parser.parseCssProperty("filter", "something invalid", {
filterSwatchClass: "test-filterswatch",
});
const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
is(swatchCount, 1, "filter swatch was created");
}
function testParseBackdropFilter(doc, parser) {
const frag = parser.parseCssProperty("backdrop-filter", "something invalid", {
filterSwatchClass: "test-filterswatch",
});
const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
is(swatchCount, 1, "filter swatch was created for backdrop-filter");
}
function testParseAngle(doc, parser) {
let frag = parser.parseCssProperty("rotate", "90deg", {
angleSwatchClass: "test-angleswatch",
});
let swatchCount = frag.querySelectorAll(".test-angleswatch").length;
is(swatchCount, 1, "angle swatch was created");
frag = parser.parseCssProperty(
"background-image",
"linear-gradient(90deg, red, blue",
{
angleSwatchClass: "test-angleswatch",
}
);
swatchCount = frag.querySelectorAll(".test-angleswatch").length;
is(swatchCount, 1, "angle swatch was created");
}
function testParseShape(doc, parser) {
info("Test shape parsing");
const tests = [
{
desc: "Polygon shape",
definition:
"polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
"12em var(--variable), 100% 100%) margin-box",
spanCount: 18,
},
{
desc: "Invalid polygon shape",
definition: "polygon(0px 0px 100px 20px, 20% 20%)",
spanCount: 0,
},
{
desc: "Circle shape with all arguments",
definition: "circle(25% at\n 30% 200px) border-box",
spanCount: 4,
},
{
desc: "Circle shape with only one center",
definition: "circle(25em at 40%)",
spanCount: 3,
},
{
desc: "Circle shape with no radius",
definition: "circle(at 30% 40%)",
spanCount: 3,
},
{
desc: "Circle shape with no center",
definition: "circle(12em)",
spanCount: 1,
},
{
desc: "Circle shape with no arguments",
definition: "circle()",
spanCount: 0,
},
{
desc: "Circle shape with no space before at",
definition: "circle(25%at 30% 30%)",
spanCount: 4,
},
{
desc: "Invalid circle shape",
definition: "circle(25%at30%30%)",
spanCount: 0,
},
{
desc: "Ellipse shape with all arguments",
definition: "ellipse(200px 10em at 25% 120px) content-box",
spanCount: 5,
},
{
desc: "Ellipse shape with only one center",
definition: "ellipse(200px 10% at 120px)",
spanCount: 4,
},
{
desc: "Ellipse shape with no radius",
definition: "ellipse(at 25% 120px)",
spanCount: 3,
},
{
desc: "Ellipse shape with no center",
definition: "ellipse(200px\n10em)",
spanCount: 2,
},
{
desc: "Ellipse shape with no arguments",
definition: "ellipse()",
spanCount: 0,
},
{
desc: "Invalid ellipse shape",
definition: "ellipse(200px100px at 30$ 20%)",
spanCount: 0,
},
{
desc: "Inset shape with 4 arguments",
definition: "inset(200px 100px\n 30%15%)",
spanCount: 4,
},
{
desc: "Inset shape with 3 arguments",
definition: "inset(200px 100px 15%)",
spanCount: 3,
},
{
desc: "Inset shape with 2 arguments",
definition: "inset(200px 100px)",
spanCount: 2,
},
{
desc: "Inset shape with 1 argument",
definition: "inset(200px)",
spanCount: 1,
},
{
desc: "Inset shape with 0 arguments",
definition: "inset()",
spanCount: 0,
},
{
desc: "offset-path property with inset shape value",
property: "offset-path",
definition: "inset(200px)",
spanCount: 1,
},
];
for (const { desc, definition, property = "clip-path", spanCount } of tests) {
info(desc);
const frag = parser.parseCssProperty(property, definition, {
shapeClass: "ruleview-shape",
});
const spans = frag.querySelectorAll(".ruleview-shape-point");
is(spans.length, spanCount, desc + " span count");
is(frag.textContent, definition, desc + " text content");
}
}
function testParseVariable(doc, parser) {
const TESTS = [
{
text: "var(--seen)",
variables: { "--seen": "chartreuse" },
expected:
// prettier-ignore
'<span data-color="chartreuse">' +
"<span>var(" +
'<span data-variable="--seen = chartreuse">--seen</span>)' +
"</span>" +
"</span>",
},
{
text: "var(--not-seen)",
variables: {},
expected:
// prettier-ignore
"<span>var(" +
'<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>' +
")</span>",
},
{
text: "var(--seen, seagreen)",
variables: { "--seen": "chartreuse" },
expected:
// prettier-ignore
'<span data-color="chartreuse">' +
"<span>var(" +
'<span data-variable="--seen = chartreuse">--seen</span>,' +
'<span class="unmatched-class"> ' +
'<span data-color="seagreen">' +
"<span>seagreen</span>" +
"</span>" +
"</span>)" +
"</span>" +
"</span>",
},
{
text: "var(--not-seen, var(--seen))",
variables: { "--seen": "chartreuse" },
expected:
// prettier-ignore
"<span>var(" +
'<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,' +
"<span> " +
'<span data-color="chartreuse">' +
"<span>var(" +
'<span data-variable="--seen = chartreuse">--seen</span>)' +
"</span>" +
"</span>" +
"</span>)" +
"</span>",
},
{
text: "color-mix(in sgrb, var(--x), purple)",
variables: { "--x": "yellow" },
expected:
// prettier-ignore
`color-mix(in sgrb, ` +
`<span data-color="yellow">` +
`<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` +
`</span>` +
`<span>var(<span data-variable="--x = yellow">--x</span>)</span>` +
`</span>` +
`, ` +
`<span data-color="purple">` +
`<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` +
`</span>` +
`<span>purple</span>` +
`</span>` +
`)`,
parserExtraOptions: {
colorSwatchClass: COLOR_TEST_CLASS,
},
},
{
text: "1px solid var(--seen, seagreen)",
variables: { "--seen": "chartreuse" },
expected:
// prettier-ignore
'1px solid ' +
'<span data-color="chartreuse">' +
"<span>var(" +
'<span data-variable="--seen = chartreuse">--seen</span>,' +
'<span class="unmatched-class"> ' +
'<span data-color="seagreen">' +
"<span>seagreen</span>" +
"</span>" +
"</span>)" +
"</span>" +
"</span>",
},
{
text: "1px solid var(--not-seen, seagreen)",
variables: {},
expected:
// prettier-ignore
`1px solid ` +
`<span>var(` +
`<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` +
`<span> ` +
`<span data-color="seagreen">` +
`<span>seagreen</span>` +
`</span>` +
`</span>)` +
`</span>`,
},
];
for (const test of TESTS) {
const getValue = function (varName) {
return test.variables[varName];
};
const frag = parser.parseCssProperty("color", test.text, {
getVariableValue: getValue,
unmatchedVariableClass: "unmatched-class",
...(test.parserExtraOptions || {}),
});
const target = doc.querySelector("div");
target.appendChild(frag);
is(target.innerHTML, test.expected, test.text);
target.innerHTML = "";
}
}
function testParseColorVariable(doc, parser) {
const testCategories = [
{
desc: "Test for CSS variable defining color",
tests: [
makeColorTest("--test-var", "lime", [{ name: "lime" }]),
makeColorTest("--test-var", "#000", [{ name: "#000" }]),
],
},
{
desc: "Test for CSS variable not defining color",
tests: [
makeColorTest("--foo", "something", ["something"]),
makeColorTest("--bar", "Arial Black", ["Arial Black"]),
makeColorTest("--baz", "10vmin", ["10vmin"]),
],
},
{
desc: "Test for non CSS variable defining color",
tests: [
makeColorTest("non-css-variable", "lime", ["lime"]),
makeColorTest("-non-css-variable", "#000", ["#000"]),
],
},
];
for (const category of testCategories) {
info(category.desc);
for (const test of category.tests) {
info(test.desc);
const target = doc.querySelector("div");
const frag = parser.parseCssProperty(test.name, test.value, {
colorSwatchClass: COLOR_TEST_CLASS,
});
target.appendChild(frag);
is(
target.innerHTML,
test.expected,
`The parsed result for '${test.name}: ${test.value}' is correct`
);
target.innerHTML = "";
}
}
}
function testParseFontFamily(doc, parser) {
info("Test font-family parsing");
const tests = [
{
desc: "No fonts",
definition: "",
families: [],
},
{
desc: "List of fonts",
definition: "Arial,Helvetica,sans-serif",
families: ["Arial", "Helvetica", "sans-serif"],
},
{
desc: "Fonts with spaces",
definition: "Open Sans",
families: ["Open Sans"],
},
{
desc: "Quoted fonts",
definition: "\"Arial\",'Open Sans'",
families: ["Arial", "Open Sans"],
},
{
desc: "Fonts with extra whitespace",
definition: " Open Sans ",
families: ["Open Sans"],
},
];
const textContentTests = [
{
desc: "No whitespace between fonts",
definition: "Arial,Helvetica,sans-serif",
output: "Arial,Helvetica,sans-serif",
},
{
desc: "Whitespace between fonts",
definition: "Arial , Helvetica, sans-serif",
output: "Arial , Helvetica, sans-serif",
},
{
desc: "Whitespace before first font trimmed",
definition: " Arial,Helvetica,sans-serif",
output: "Arial,Helvetica,sans-serif",
},
{
desc: "Whitespace after last font trimmed",
definition: "Arial,Helvetica,sans-serif ",
output: "Arial,Helvetica,sans-serif",
},
{
desc: "Whitespace between quoted fonts",
definition: "'Arial' , \"Helvetica\" ",
output: "'Arial' , \"Helvetica\"",
},
{
desc: "Whitespace within font preserved",
definition: "' Ari al '",
output: "' Ari al '",
},
];
for (const { desc, definition, families } of tests) {
info(desc);
const frag = parser.parseCssProperty("font-family", definition, {
fontFamilyClass: "ruleview-font-family",
});
const spans = frag.querySelectorAll(".ruleview-font-family");
is(spans.length, families.length, desc + " span count");
for (let i = 0; i < spans.length; i++) {
is(spans[i].textContent, families[i], desc + " span contents");
}
}
info("Test font-family text content");
for (const { desc, definition, output } of textContentTests) {
info(desc);
const frag = parser.parseCssProperty("font-family", definition, {});
is(frag.textContent, output, desc + " text content matches");
}
info("Test font-family with custom properties");
const frag = parser.parseCssProperty(
"font-family",
"var(--family, Georgia, serif)",
{
getVariableValue: () => {},
unmatchedVariableClass: "unmatched-class",
fontFamilyClass: "ruleview-font-family",
}
);
const target = doc.createElement("div");
target.appendChild(frag);
is(
target.innerHTML,
// prettier-ignore
`<span>var(` +
`<span class="unmatched-class" data-variable="--family is not set">` +
`--family` +
`</span>` +
`,` +
`<span> ` +
`<span class="ruleview-font-family">Georgia</span>` +
`, ` +
`<span class="ruleview-font-family">serif</span>` +
`</span>)` +
`</span>`,
"Got expected output for font-family with custom properties"
);
}