mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-21 03:10:26 +00:00
Some instances of CSSStyleProperties can lack an owner node, for instance the return value of a call to `window.getComputedStyle` where the specified pseudo-element is invalid. In this case we should treat the computed style as empty, as there is no node to compute the style for.
294 lines
10 KiB
HTML
294 lines
10 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>CSSOM: Correct resolution of resolved value for display-affected pseudo-elements</title>
|
|
<link rel="help" href="https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle">
|
|
<link rel="help" href="https://drafts.csswg.org/cssom/#resolved-values">
|
|
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
|
|
<link rel="author" title="Karl Dubost" href="https://github.com/karlcow">
|
|
<script src=../../resources/testharness.js></script>
|
|
<script src=../../resources/testharnessreport.js></script>
|
|
<style>
|
|
#test { width: 100px; }
|
|
|
|
#contents {
|
|
display: contents;
|
|
border: 10px solid red;
|
|
}
|
|
|
|
#test::before,
|
|
#test::after,
|
|
#contents::before,
|
|
#contents::after,
|
|
#flex::before,
|
|
#flex::after {
|
|
content: " ";
|
|
width: 50%;
|
|
height: 10px;
|
|
display: block;
|
|
}
|
|
#none {
|
|
display: none;
|
|
}
|
|
#none::before,
|
|
#none::after {
|
|
content: "Foo";
|
|
}
|
|
#flex {
|
|
display: flex;
|
|
}
|
|
#flex-no-pseudo {
|
|
display: flex;
|
|
}
|
|
#contents-pseudos::before,
|
|
#contents-pseudos::after {
|
|
display: contents;
|
|
content: "foo";
|
|
position: absolute;
|
|
}
|
|
#contents-pseudos-dynamic::before,
|
|
#contents-pseudos-dynamic::after {
|
|
display: block;
|
|
content: "foo";
|
|
position: absolute;
|
|
}
|
|
#contents-pseudos-dynamic.contents::before,
|
|
#contents-pseudos-dynamic.contents::after {
|
|
display: contents;
|
|
}
|
|
#pseudo-invalid::backdrop {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::file-selector-button {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::grammar-error {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::highlight(name) {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::marker {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::placeholder {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::spelling-error {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::view-transition {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::view-transition-image-pair(name) {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::view-transition-group(name) {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::view-transition-old(name) {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid::view-transition-new(name) {
|
|
color: rgb(0, 128, 0);
|
|
}
|
|
#pseudo-invalid {
|
|
color: rgb(255, 0, 0)
|
|
}
|
|
</style>
|
|
<div id="test">
|
|
<div id="contents"></div>
|
|
<div id="none"></div>
|
|
<div id="flex"></div>
|
|
<div id="flex-no-pseudo"></div>
|
|
<div id="contents-pseudos"></div>
|
|
<div id="contents-pseudos-dynamic"></div>
|
|
<ul><li id="pseudo-invalid">Item</li></ul>
|
|
</div>
|
|
<script>
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
["before", "after"].forEach(pseudo => {
|
|
assert_equals(getComputedStyle(div, pseudo).width, "100px");
|
|
});
|
|
}, "Resolution of width is correct when pseudo-element argument is ignored (due to no colon)");
|
|
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
[
|
|
":before ",
|
|
"::before ",
|
|
"::before\t",
|
|
"::before\f",
|
|
"::before\n",
|
|
"::before,",
|
|
"::before,::after",
|
|
"::before@after",
|
|
"::before#after",
|
|
"::\"before\"",
|
|
"::before\u0000",
|
|
"::before-->",
|
|
"::before0",
|
|
].forEach(pseudo => {
|
|
assert_equals(getComputedStyle(div, pseudo).width, "", pseudo);
|
|
});
|
|
}, "Resolution of width is correct when pseudo-element argument is invalid (due to a trailing token)");
|
|
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
[":before", ":after"].forEach(pseudo => {
|
|
assert_equals(getComputedStyle(div, pseudo).width, "50px");
|
|
});
|
|
}, "Resolution of width is correct for ::before and ::after pseudo-elements (single-colon)");
|
|
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
["::before", "::after"].forEach(pseudo => {
|
|
assert_equals(getComputedStyle(div, pseudo).width, "50px");
|
|
});
|
|
}, "Resolution of width is correct for ::before and ::after pseudo-elements (double-colon)");
|
|
|
|
test(function() {
|
|
const div = document.getElementById('test');
|
|
[":bef\\oRE", "::\\000041fter"].forEach(pseudo => {
|
|
assert_equals(getComputedStyle(div, pseudo).width, "50px");
|
|
});
|
|
}, "Pseudo-elements can use the full range of CSS syntax");
|
|
|
|
test(function() {
|
|
var contents = document.getElementById('contents');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(contents, pseudo).width, "50px");
|
|
});
|
|
}, "Resolution of width is correct for ::before and ::after pseudo-elements of display: contents elements");
|
|
|
|
test(function() {
|
|
var has_no_pseudos = document.body;
|
|
has_no_pseudos.style.position = "relative";
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(has_no_pseudos, pseudo).position, "static",
|
|
"Nonexistent " + pseudo + " pseudo-element shouldn't claim to have " +
|
|
"the same style as the originating element");
|
|
assert_equals(getComputedStyle(has_no_pseudos, pseudo).width, "auto",
|
|
"Nonexistent " + pseudo + " pseudo-element shouldn't claim to have " +
|
|
"definite size");
|
|
});
|
|
}, "Resolution of nonexistent pseudo-element styles");
|
|
|
|
test(function() {
|
|
var none = document.getElementById('none');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(none, pseudo).content, "\"Foo\"",
|
|
"Pseudo-styles of display: none elements should be correct");
|
|
});
|
|
}, "Resolution of pseudo-element styles in display: none elements");
|
|
|
|
test(function() {
|
|
var flex = document.getElementById('flex');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(flex, pseudo).display, "block",
|
|
"Pseudo-styles of display: flex elements should get blockified");
|
|
});
|
|
}, "Item-based blockification of pseudo-elements");
|
|
|
|
test(function() {
|
|
var flexNoPseudo = document.getElementById('flex-no-pseudo');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(flexNoPseudo, pseudo).display, "block",
|
|
"Pseudo-styles of display: flex elements should get blockified");
|
|
});
|
|
}, "Item-based blockification of nonexistent pseudo-elements");
|
|
|
|
test(function() {
|
|
var contentsPseudos = document.getElementById('contents-pseudos');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(contentsPseudos, pseudo).display, "contents",
|
|
"display: contents in " + pseudo + " should get reflected on CSSOM");
|
|
assert_equals(getComputedStyle(contentsPseudos, pseudo).width, "auto",
|
|
pseudo + " with display: contents should have no box");
|
|
assert_equals(getComputedStyle(contentsPseudos, pseudo).position, "absolute",
|
|
"display: contents in " + pseudo + " should reflect other non-inherited properties in CSSOM");
|
|
});
|
|
}, "display: contents on pseudo-elements");
|
|
|
|
test(function() {
|
|
var contentsPseudosDynamic = document.getElementById('contents-pseudos-dynamic');
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(contentsPseudosDynamic, pseudo).display, "block",
|
|
"Check that display for " + pseudo + " is block before change");
|
|
});
|
|
contentsPseudosDynamic.className = "contents";
|
|
[":before", ":after"].forEach(function(pseudo) {
|
|
assert_equals(getComputedStyle(contentsPseudosDynamic, pseudo).display, "contents",
|
|
"display: contents in " + pseudo + " should get reflected on CSSOM");
|
|
assert_equals(getComputedStyle(contentsPseudosDynamic, pseudo).width, "auto",
|
|
pseudo + " with display: contents should have no box");
|
|
assert_equals(getComputedStyle(contentsPseudosDynamic, pseudo).position, "absolute",
|
|
"display: contents in " + pseudo + " should reflect other non-inherited properties in CSSOM");
|
|
});
|
|
}, "Dynamically change to display: contents on pseudo-elements");
|
|
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
// Note that these assertions deliberately avoid assert_[not_]equals to
|
|
// avoid gCS().length in the failure output.
|
|
assert_true(
|
|
getComputedStyle(div, "totallynotapseudo").length != 0,
|
|
"Should return the element's style for unknown pseudo-elements that don't start with a colon");
|
|
assert_true(
|
|
getComputedStyle(div, "::totallynotapseudo").length == 0,
|
|
"Should return an empty style for unknown pseudo-elements starting with double-colon");
|
|
assert_true(
|
|
getComputedStyle(div, ":totallynotapseudo").length == 0,
|
|
"Should return an empty style for unknown pseudo-elements starting with colon");
|
|
}, "Unknown pseudo-elements");
|
|
|
|
test(() => {
|
|
const div = document.getElementById('test');
|
|
|
|
const style1 = getComputedStyle(div, "totallynotapseudo");
|
|
assert_throws_dom("NoModificationAllowedError", () => style1.color = "1");
|
|
assert_throws_dom("NoModificationAllowedError", () => style1.margin = "10px");
|
|
|
|
const style2 = getComputedStyle(div, "::totallynotapseudo");
|
|
assert_throws_dom("NoModificationAllowedError", () => style2.color = "1");
|
|
assert_throws_dom("NoModificationAllowedError", () => style2.margin = "10px");
|
|
|
|
const style3 = getComputedStyle(div, ":totallynotapseudo");
|
|
assert_throws_dom("NoModificationAllowedError", () => style3.color = "1");
|
|
assert_throws_dom("NoModificationAllowedError", () => style3.margin = "10px");
|
|
}, "CSSStyleDeclaration is immutable");
|
|
|
|
// If you add a pseudo-element identifier here, don't forget to add the corresponding style rule in
|
|
// <style> above.
|
|
[
|
|
"backdrop",
|
|
"file-selector-button",
|
|
"grammar-error",
|
|
"highlight(name)",
|
|
"marker",
|
|
"placeholder",
|
|
"spelling-error",
|
|
"view-transition",
|
|
"view-transition-image-pair(name)",
|
|
"view-transition-group(name)",
|
|
"view-transition-old(name)",
|
|
"view-transition-new(name)"
|
|
].forEach(pseudoIdentifier => {
|
|
test(() => {
|
|
assert_implements_optional(CSS.supports(`selector(::${pseudoIdentifier})`), `::${pseudoIdentifier}`);
|
|
const li = document.querySelector('li');
|
|
assert_true(
|
|
getComputedStyle(li, `:${pseudoIdentifier}`).length == 0,
|
|
`Should return an empty style for :${pseudoIdentifier}`);
|
|
assert_true(
|
|
getComputedStyle(li, pseudoIdentifier).length != 0,
|
|
`Should return the element style for ${pseudoIdentifier}`);
|
|
assert_equals(
|
|
getComputedStyle(li, pseudoIdentifier).color, "rgb(255, 0, 0)",
|
|
`Should return the element style for ${pseudoIdentifier}`);
|
|
assert_equals(
|
|
getComputedStyle(li, `::${pseudoIdentifier}`).color, "rgb(0, 128, 0)",
|
|
`Should return the ::${pseudoIdentifier} style`);
|
|
}, `Unknown pseudo-element with a known identifier: ${pseudoIdentifier}`);
|
|
});
|
|
</script>
|