1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
6 add_task(async function () {
7 await pushPref("layout.css.backdrop-filter.enabled", true);
8 await pushPref("layout.css.individual-transform.enabled", true);
9 await addTab("about:blank");
11 gBrowser.removeCurrentTab();
14 async function performTest() {
15 await SpecialPowers.pushPrefEnv({
16 set: [["security.allow_unsafe_parent_loads", true]],
19 const OutputParser = require("resource://devtools/client/shared/output-parser.js");
21 const { host, doc } = await createHost(
23 "data:text/html," + "<h1>browser_outputParser.js</h1><div></div>"
26 const cssProperties = getClientCssProperties();
28 const parser = new OutputParser(doc, cssProperties);
29 testParseCssProperty(doc, parser);
30 testParseCssVar(doc, parser);
31 testParseURL(doc, parser);
32 testParseFilter(doc, parser);
33 testParseBackdropFilter(doc, parser);
34 testParseAngle(doc, parser);
35 testParseShape(doc, parser);
36 testParseVariable(doc, parser);
37 testParseColorVariable(doc, parser);
38 testParseFontFamily(doc, parser);
43 // Class name used in color swatch.
44 var COLOR_TEST_CLASS = "test-class";
46 // Create a new CSS color-parsing test. |name| is the name of the CSS
47 // property. |value| is the CSS text to use. |segments| is an array
48 // describing the expected result. If an element of |segments| is a
49 // string, it is simply appended to the expected string. Otherwise,
50 // it must be an object with a |name| property, which is the color
51 // name as it appears in the input.
53 // This approach is taken to reduce boilerplate and to make it simpler
54 // to modify the test when the parseCssProperty output changes.
55 function makeColorTest(name, value, segments) {
62 for (const segment of segments) {
63 if (typeof segment === "string") {
64 result.expected += segment;
66 const buttonAttributes = {
67 class: COLOR_TEST_CLASS,
68 style: `background-color:${segment.name}`,
72 if (segment.colorFunction) {
73 buttonAttributes["data-color-function"] = segment.colorFunction;
75 const buttonAttrString = Object.entries(buttonAttributes)
76 .map(([attr, v]) => `${attr}="${v}"`)
81 `<span data-color="${segment.name}">` +
82 `<span ${buttonAttrString}></span>`+
83 `<span>${segment.name}</span>` +
88 result.desc = "Testing " + name + ": " + value;
93 function testParseCssProperty(doc, parser) {
95 makeColorTest("border", "1px solid red", ["1px solid ", { name: "red" }]),
99 "linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
101 "linear-gradient(to right, ",
102 { name: "#F60", colorFunction: "linear-gradient" },
104 { name: "rgba(0,0,0,1)", colorFunction: "linear-gradient" },
109 // In "arial black", "black" is a font, not a color.
110 // (The font-family parser creates a span)
111 makeColorTest("font-family", "arial black", ["<span>arial black</span>"]),
113 makeColorTest("box-shadow", "0 0 1em red", ["0 0 1em ", { name: "red" }]),
115 makeColorTest("box-shadow", "0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)", [
119 { name: "rgba(0,0,0,.5)" },
122 makeColorTest("content", '"red"', ['"red"']),
124 // Invalid property names should not cause exceptions.
125 makeColorTest("hellothere", "'red'", ["'red'"]),
129 "blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)",
131 '<span data-filters="blur(1px) drop-shadow(0 0 0 blue) ',
132 'url(red.svg#blue)"><span>',
133 "blur(1px) drop-shadow(0 0 0 ",
134 { name: "blue", colorFunction: "drop-shadow" },
135 ") url(red.svg#blue)</span></span>",
139 makeColorTest("color", "currentColor", ["currentColor"]),
141 // Test a very long property.
144 "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%)",
146 "linear-gradient(to left, ",
147 { name: "transparent", colorFunction: "linear-gradient" },
149 { name: "transparent", colorFunction: "linear-gradient" },
151 { name: "#F00", colorFunction: "linear-gradient" },
153 { name: "#F00", colorFunction: "linear-gradient" },
155 { name: "#FF0", colorFunction: "linear-gradient" },
157 { name: "#FF0", colorFunction: "linear-gradient" },
159 { name: "#0F0", colorFunction: "linear-gradient" },
161 { name: "#0F0", colorFunction: "linear-gradient" },
163 { name: "#0FF", colorFunction: "linear-gradient" },
165 { name: "#0FF", colorFunction: "linear-gradient" },
167 { name: "#00F", colorFunction: "linear-gradient" },
169 { name: "#00F", colorFunction: "linear-gradient" },
171 { name: "#800", colorFunction: "linear-gradient" },
173 { name: "#800", colorFunction: "linear-gradient" },
175 { name: "#880", colorFunction: "linear-gradient" },
177 { name: "#880", colorFunction: "linear-gradient" },
179 { name: "#080", colorFunction: "linear-gradient" },
181 { name: "#080", colorFunction: "linear-gradient" },
183 { name: "#088", colorFunction: "linear-gradient" },
185 { name: "#088", colorFunction: "linear-gradient" },
187 { name: "#008", colorFunction: "linear-gradient" },
189 { name: "#008", colorFunction: "linear-gradient" },
191 { name: "#FFF", colorFunction: "linear-gradient" },
193 { name: "#FFF", colorFunction: "linear-gradient" },
195 { name: "#EEE", colorFunction: "linear-gradient" },
197 { name: "#EEE", colorFunction: "linear-gradient" },
199 { name: "#CCC", colorFunction: "linear-gradient" },
201 { name: "#CCC", colorFunction: "linear-gradient" },
203 { name: "#999", colorFunction: "linear-gradient" },
205 { name: "#999", colorFunction: "linear-gradient" },
207 { name: "#666", colorFunction: "linear-gradient" },
209 { name: "#666", colorFunction: "linear-gradient" },
211 { name: "#333", colorFunction: "linear-gradient" },
213 { name: "#333", colorFunction: "linear-gradient" },
215 { name: "#111", colorFunction: "linear-gradient" },
217 { name: "#111", colorFunction: "linear-gradient" },
219 { name: "#000", colorFunction: "linear-gradient" },
221 { name: "#000", colorFunction: "linear-gradient" },
223 { name: "transparent", colorFunction: "linear-gradient" },
225 { name: "transparent", colorFunction: "linear-gradient" },
230 // Note the lack of a space before the color here.
231 makeColorTest("border", "1px dotted#f06", [
236 makeColorTest("color", "color-mix(in srgb, red, blue)", [
237 "color-mix(in srgb, ",
238 { name: "red", colorFunction: "color-mix" },
240 { name: "blue", colorFunction: "color-mix" },
246 "linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue)",
248 "linear-gradient(to top, ",
249 "color-mix(in srgb, ",
250 { name: "#008000", colorFunction: "color-mix" },
252 { name: "rgba(255, 255, 0, 0.9)", colorFunction: "color-mix" },
254 { name: "blue", colorFunction: "linear-gradient" },
260 const target = doc.querySelector("div");
261 ok(target, "captain, we have the div");
263 for (const test of tests) {
266 const frag = parser.parseCssProperty(test.name, test.value, {
267 colorSwatchClass: COLOR_TEST_CLASS,
270 target.appendChild(frag);
275 "CSS property correctly parsed for " + test.name + ": " + test.value
278 target.innerHTML = "";
282 function testParseCssVar(doc, parser) {
283 const frag = parser.parseCssProperty("color", "var(--some-kind-of-green)", {
284 colorSwatchClass: "test-colorswatch",
287 const target = doc.querySelector("div");
288 ok(target, "captain, we have the div");
289 target.appendChild(frag);
293 "var(--some-kind-of-green)",
294 "CSS property correctly parsed"
297 target.innerHTML = "";
300 function testParseURL(doc, parser) {
301 info("Test that URL parsing preserves quoting style");
305 desc: "simple test without quotes",
310 desc: "simple test with single quotes",
315 desc: "simple test with double quotes",
320 desc: "test with single quotes and whitespace",
325 desc: "simple test with uppercase",
330 desc: "bad url, missing paren",
333 expectedTrailer: ")",
336 desc: "bad url, missing paren, with baseURI",
337 baseURI: "data:text/html,<style></style>",
340 expectedTrailer: ")",
343 desc: "bad url, double quote, missing paren",
346 expectedTrailer: '")',
349 desc: "bad url, single quote, missing paren and quote",
352 expectedTrailer: "')",
356 for (const test of tests) {
357 const url = test.leader + "something.jpg" + test.trailer;
358 const frag = parser.parseCssProperty("background", url, {
359 urlClass: "test-urlclass",
360 baseURI: test.baseURI,
363 const target = doc.querySelector("div");
364 target.appendChild(frag);
366 const expectedTrailer = test.expectedTrailer || test.trailer;
370 '<a target="_blank" class="test-urlclass" ' +
371 'href="something.jpg">something.jpg</a>' +
374 is(target.innerHTML, expected, test.desc);
376 target.innerHTML = "";
380 function testParseFilter(doc, parser) {
381 const frag = parser.parseCssProperty("filter", "something invalid", {
382 filterSwatchClass: "test-filterswatch",
385 const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
386 is(swatchCount, 1, "filter swatch was created");
389 function testParseBackdropFilter(doc, parser) {
390 const frag = parser.parseCssProperty("backdrop-filter", "something invalid", {
391 filterSwatchClass: "test-filterswatch",
394 const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
395 is(swatchCount, 1, "filter swatch was created for backdrop-filter");
398 function testParseAngle(doc, parser) {
399 let frag = parser.parseCssProperty("rotate", "90deg", {
400 angleSwatchClass: "test-angleswatch",
403 let swatchCount = frag.querySelectorAll(".test-angleswatch").length;
404 is(swatchCount, 1, "angle swatch was created");
406 frag = parser.parseCssProperty(
408 "linear-gradient(90deg, red, blue",
410 angleSwatchClass: "test-angleswatch",
414 swatchCount = frag.querySelectorAll(".test-angleswatch").length;
415 is(swatchCount, 1, "angle swatch was created");
418 function testParseShape(doc, parser) {
419 info("Test shape parsing");
423 desc: "Polygon shape",
425 "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
426 "12em var(--variable), 100% 100%) margin-box",
432 "POLYGON(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
433 "12em var(--variable), 100% 100%) margin-box",
437 desc: "Invalid polygon shape",
438 definition: "polygon(0px 0px 100px 20px, 20% 20%)",
442 desc: "Circle shape with all arguments",
443 definition: "circle(25% at\n 30% 200px) border-box",
447 desc: "Circle shape with only one center",
448 definition: "circle(25em at 40%)",
452 desc: "Circle shape with no radius",
453 definition: "circle(at 30% 40%)",
457 desc: "Circle shape with no center",
458 definition: "circle(12em)",
462 desc: "Circle shape with no arguments",
463 definition: "circle()",
467 desc: "Circle shape with no space before at",
468 definition: "circle(25%at 30% 30%)",
473 definition: "CIRCLE(12em)",
477 desc: "Invalid circle shape",
478 definition: "circle(25%at30%30%)",
482 desc: "Ellipse shape with all arguments",
483 definition: "ellipse(200px 10em at 25% 120px) content-box",
487 desc: "Ellipse shape with only one center",
488 definition: "ellipse(200px 10% at 120px)",
492 desc: "Ellipse shape with no radius",
493 definition: "ellipse(at 25% 120px)",
497 desc: "Ellipse shape with no center",
498 definition: "ellipse(200px\n10em)",
502 desc: "Ellipse shape with no arguments",
503 definition: "ellipse()",
508 definition: "ELLIPSE(200px 10em)",
512 desc: "Invalid ellipse shape",
513 definition: "ellipse(200px100px at 30$ 20%)",
517 desc: "Inset shape with 4 arguments",
518 definition: "inset(200px 100px\n 30%15%)",
522 desc: "Inset shape with 3 arguments",
523 definition: "inset(200px 100px 15%)",
527 desc: "Inset shape with 2 arguments",
528 definition: "inset(200px 100px)",
532 desc: "Inset shape with 1 argument",
533 definition: "inset(200px)",
537 desc: "Inset shape with 0 arguments",
538 definition: "inset()",
543 definition: "INSET(200px)",
547 desc: "offset-path property with inset shape value",
548 property: "offset-path",
549 definition: "inset(200px)",
554 for (const { desc, definition, property = "clip-path", spanCount } of tests) {
556 const frag = parser.parseCssProperty(property, definition, {
557 shapeClass: "ruleview-shape",
559 const spans = frag.querySelectorAll(".ruleview-shape-point");
560 is(spans.length, spanCount, desc + " span count");
561 is(frag.textContent, definition, desc + " text content");
565 function testParseVariable(doc, parser) {
569 variables: { "--seen": "chartreuse" },
572 '<span data-color="chartreuse">' +
574 '<span data-variable="--seen = chartreuse">--seen</span>)' +
579 text: "var(--not-seen)",
584 '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>' +
588 text: "var(--seen, seagreen)",
589 variables: { "--seen": "chartreuse" },
592 '<span data-color="chartreuse">' +
594 '<span data-variable="--seen = chartreuse">--seen</span>,' +
595 '<span class="unmatched-class"> ' +
596 '<span data-color="seagreen">' +
597 "<span>seagreen</span>" +
604 text: "var(--not-seen, var(--seen))",
605 variables: { "--seen": "chartreuse" },
609 '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,' +
611 '<span data-color="chartreuse">' +
613 '<span data-variable="--seen = chartreuse">--seen</span>)' +
620 text: "color-mix(in sgrb, var(--x), purple)",
621 variables: { "--x": "yellow" },
624 `color-mix(in sgrb, ` +
625 `<span data-color="yellow">` +
626 `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` +
628 `<span>var(<span data-variable="--x = yellow">--x</span>)</span>` +
631 `<span data-color="purple">` +
632 `<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` +
634 `<span>purple</span>` +
637 parserExtraOptions: {
638 colorSwatchClass: COLOR_TEST_CLASS,
642 text: "1px solid var(--seen, seagreen)",
643 variables: { "--seen": "chartreuse" },
647 '<span data-color="chartreuse">' +
649 '<span data-variable="--seen = chartreuse">--seen</span>,' +
650 '<span class="unmatched-class"> ' +
651 '<span data-color="seagreen">' +
652 "<span>seagreen</span>" +
659 text: "1px solid var(--not-seen, seagreen)",
665 `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` +
667 `<span data-color="seagreen">` +
668 `<span>seagreen</span>` +
674 text: "rgba(var(--r), 0, 0, var(--a))",
675 variables: { "--r": "255", "--a": "0.5" },
678 '<span data-color="rgba(255, 0, 0, 0.5)">' +
681 'var(<span data-variable="--r = 255">--r</span>)' +
684 'var(<span data-variable="--a = 0.5">--a</span>)' +
690 text: "rgb(var(--not-seen, 255), 0, 0)",
694 '<span data-color="rgb( 255, 0, 0)">' +
697 `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` +
698 `<span> 255</span>` +
705 for (const test of TESTS) {
706 const getValue = function (varName) {
707 return test.variables[varName];
710 const frag = parser.parseCssProperty("color", test.text, {
711 getVariableValue: getValue,
712 unmatchedVariableClass: "unmatched-class",
713 ...(test.parserExtraOptions || {}),
716 const target = doc.querySelector("div");
717 target.appendChild(frag);
719 is(target.innerHTML, test.expected, test.text);
720 target.innerHTML = "";
724 function testParseColorVariable(doc, parser) {
725 const testCategories = [
727 desc: "Test for CSS variable defining color",
729 makeColorTest("--test-var", "lime", [{ name: "lime" }]),
730 makeColorTest("--test-var", "#000", [{ name: "#000" }]),
734 desc: "Test for CSS variable not defining color",
736 makeColorTest("--foo", "something", ["something"]),
737 makeColorTest("--bar", "Arial Black", ["Arial Black"]),
738 makeColorTest("--baz", "10vmin", ["10vmin"]),
742 desc: "Test for non CSS variable defining color",
744 makeColorTest("non-css-variable", "lime", ["lime"]),
745 makeColorTest("-non-css-variable", "#000", ["#000"]),
750 for (const category of testCategories) {
753 for (const test of category.tests) {
755 const target = doc.querySelector("div");
757 const frag = parser.parseCssProperty(test.name, test.value, {
758 colorSwatchClass: COLOR_TEST_CLASS,
761 target.appendChild(frag);
766 `The parsed result for '${test.name}: ${test.value}' is correct`
769 target.innerHTML = "";
774 function testParseFontFamily(doc, parser) {
775 info("Test font-family parsing");
783 desc: "List of fonts",
784 definition: "Arial,Helvetica,sans-serif",
785 families: ["Arial", "Helvetica", "sans-serif"],
788 desc: "Fonts with spaces",
789 definition: "Open Sans",
790 families: ["Open Sans"],
793 desc: "Quoted fonts",
794 definition: "\"Arial\",'Open Sans'",
795 families: ["Arial", "Open Sans"],
798 desc: "Fonts with extra whitespace",
799 definition: " Open Sans ",
800 families: ["Open Sans"],
804 const textContentTests = [
806 desc: "No whitespace between fonts",
807 definition: "Arial,Helvetica,sans-serif",
808 output: "Arial,Helvetica,sans-serif",
811 desc: "Whitespace between fonts",
812 definition: "Arial , Helvetica, sans-serif",
813 output: "Arial , Helvetica, sans-serif",
816 desc: "Whitespace before first font trimmed",
817 definition: " Arial,Helvetica,sans-serif",
818 output: "Arial,Helvetica,sans-serif",
821 desc: "Whitespace after last font trimmed",
822 definition: "Arial,Helvetica,sans-serif ",
823 output: "Arial,Helvetica,sans-serif",
826 desc: "Whitespace between quoted fonts",
827 definition: "'Arial' , \"Helvetica\" ",
828 output: "'Arial' , \"Helvetica\"",
831 desc: "Whitespace within font preserved",
832 definition: "' Ari al '",
833 output: "' Ari al '",
837 for (const { desc, definition, families } of tests) {
839 const frag = parser.parseCssProperty("font-family", definition, {
840 fontFamilyClass: "ruleview-font-family",
842 const spans = frag.querySelectorAll(".ruleview-font-family");
844 is(spans.length, families.length, desc + " span count");
845 for (let i = 0; i < spans.length; i++) {
846 is(spans[i].textContent, families[i], desc + " span contents");
850 info("Test font-family text content");
851 for (const { desc, definition, output } of textContentTests) {
853 const frag = parser.parseCssProperty("font-family", definition, {});
854 is(frag.textContent, output, desc + " text content matches");
857 info("Test font-family with custom properties");
858 const frag = parser.parseCssProperty(
860 "var(--family, Georgia, serif)",
862 getVariableValue: () => {},
863 unmatchedVariableClass: "unmatched-class",
864 fontFamilyClass: "ruleview-font-family",
867 const target = doc.createElement("div");
868 target.appendChild(frag);
873 `<span class="unmatched-class" data-variable="--family is not set">` +
878 `<span class="ruleview-font-family">Georgia</span>` +
880 `<span class="ruleview-font-family">serif</span>` +
883 "Got expected output for font-family with custom properties"