| <!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) | |
| } | |
| #test-file-input::file-selector-button { | |
| color: rgb(0, 128, 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> | |
| <input type="file" id="test-file-input"> | |
| </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}`); | |
| }); | |
| test(() => { | |
| const fileInput = document.getElementById('test-file-input'); | |
| const style = getComputedStyle(fileInput, '::file-selector-button'); | |
| assert_equals(style.color, 'rgb(0, 128, 0)', | |
| '::file-selector-button should resolve to the backing element style'); | |
| }, '::file-selector-button resolves to the file input button element'); | |
| test(() => { | |
| const fileInput = document.getElementById('test-file-input'); | |
| const style = getComputedStyle(fileInput, '::-webkit-file-upload-button'); | |
| assert_true(style.length == 0, | |
| '::-webkit-file-upload-button should return empty style'); | |
| }, 'Prefixed ::-webkit-file-upload-button does not resolve in getComputedStyle'); | |
| </script> |