diff options
Diffstat (limited to 'testing/web-platform/tests/css/css-cascade')
120 files changed, 9065 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-cascade/META.yml b/testing/web-platform/tests/css/css-cascade/META.yml new file mode 100644 index 0000000000..96467729fe --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/META.yml @@ -0,0 +1,4 @@ +spec: https://drafts.csswg.org/css-cascade/ +suggested_reviewers: + - fantasai + - tabatkins diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html new file mode 100644 index 0000000000..e255585783 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-001-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: all shorthand Reference File</title> +</head> +<body> + <p>Test passes if the digits are <strong>in order</strong> and there is <strong>no red</strong>.</p> + + <div class="test">123 456 789</div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-001.html b/testing/web-platform/tests/css/css-cascade/all-prop-001.html new file mode 100644 index 0000000000..9c07dfe1b8 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-001.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: "all" shorthand property excludes "direction" and "unicode-bidi"</title> + <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#all-shorthand"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="all-prop-001-ref.html"> + <meta name="assert" content="Test passes if 'all' resets properties other than 'direction' and 'unicode-bidi'."> + <style> +.test { + /* these must not be overridden */ + direction: rtl; + unicode-bidi: bidi-override; +} + +.test, bdo { + /* all of these must be overridden */ + border: solid red; + background: red; + color: red; + text-decoration: line-through; + font: bold italic small-caps 20px monospace; + outline: solid red; + float: left; + letter-spacing: 1em; + display: list-item; + text-align: center; + width: 0.5em; + margin: 10em; + overflow: scroll; +} + +.test, bdo { + all: initial; + /* if incorrectly implemented, this causes direction: initial; unicode-bidi: initial; + which is the same as direction:ltr; unicode-bidi: normal */ +} + </style> +</head> +<body> + <p>Test passes if the digits are <strong>in order</strong> and there is <strong>no red</strong>.</p> + + <div class="test"><bdo dir=rtl>987 <span>654</span></bdo> 321</div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-002.html b/testing/web-platform/tests/css/css-cascade/all-prop-002.html new file mode 100644 index 0000000000..1e5b450ce1 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-002.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: all:inherit includes display:inherit</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#all-shorthand"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="all:inherit should cause display:inherit so the red divs will display inline/horizontal (like their parent span) rather than block/vertical (like the default UA style for a div)."> + <style> +.inline { + all: inherit;/* inherit display:inline from span */ +} +.half-red-sq { + /* half of a red square */ + display: inline-block; + width: 50px; + height: 100px; + background-color: red; +} +#positioned { + position: relative; +} +#green-sq { + position: absolute;/* put higher on Z axis */ + width: 100px; + height: 100px; + background-color: green; +} + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + <div id="positioned"> + <div id="green-sq"></div> + <span> + <div class="inline"><div class="half-red-sq"></div></div><div class="inline"><div class="half-red-sq"></div></div> + </span> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html new file mode 100644 index 0000000000..6cd8425cb9 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-inherit-color.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: "color" property preceded by "all: initial"</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="reference/ref-green-text.html"> + <meta name="assert" content="Own 'color', preceded by 'all: inherit', overrides inherited 'color'."> + <style> + .outer { + color: red; + } + + .inner { + all: inherit; + color: green; + } + </style> +</head> +<body> + <p class="outer"><span class="inner">Test passes if this text is green.</span></p> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html new file mode 100644 index 0000000000..83a78bd086 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-color.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: "color" property preceded by "all: initial"</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="reference/ref-green-text.html"> + <meta name="assert" content="Own 'color', preceded by 'all: initial', overrides inherited 'color'."> + <style> + .outer { + color: red; + } + + .inner { + all: initial; + color: green; + } + </style> +</head> +<body> + <p class="outer"><span class="inner">Test passes if this text is green.</span></p> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html new file mode 100644 index 0000000000..f596b559b0 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited-ref.html @@ -0,0 +1,3 @@ +<!doctype html> +<title>CSS Test: Reference</title> +<span style="color:green">This text must be green.</a> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html new file mode 100644 index 0000000000..6fb7936652 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-visited.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>CSS Cascade: Apply all:initial to a visited link overriding with a color</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#initial"> +<link rel="match" href="all-prop-initial-visited-ref.html"> +<style> + a { all: initial } + a:visited { color: green } +</style> +<a href="">This text must be green.</a> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html b/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html new file mode 100644 index 0000000000..730ccac25f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-initial-xml.html @@ -0,0 +1,42 @@ +<!doctype html> +<title>all: initial on unknown XML tree</title> +<link rel=help href=https://www.w3.org/TR/css-cascade-3/#all-shorthand> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +const iframe = document.createElement("iframe"); +const setup_test = async_test("setup"); +iframe.onload = setup_test.step_func_done(function() { + const root = iframe.contentDocument.documentElement; + // we need the empty stylesheet to avoid default XSLT views of the XML + const style = iframe.contentDocument.createElementNS("http://www.w3.org/1999/xhtml", "style"); + root.appendChild(style); + + // Grab initial styles from a random element, as the root can get non-initial UA styling. + const div = iframe.contentDocument.createElementNS("http://www.w3.org/1999/xhtml", "div"); + root.appendChild(div); + // the document element should have unicode-bidi 'normal', while <div> has 'isolate': + div.style.unicodeBidi = 'normal'; + const cs = iframe.contentWindow.getComputedStyle(div); + + let actual_initial = Object.create(null); + for (let i = 0; i < cs.length; i++) { + let prop_name = cs[i]; + actual_initial[prop_name] = cs[prop_name]; + } + const rootCS = iframe.contentWindow.getComputedStyle(root); + test(() => { + style.textContent = ":root { color: blue }"; + assert_equals(rootCS["color"], "rgb(0, 0, 255)"); + }, "stylesheet takes effect"); + style.textContent = ":root { all: initial; direction: initial; unicode-bidi: initial; } style { display: none; }"; + for (let prop_name in actual_initial) { + test(() => { + assert_equals(rootCS[prop_name], actual_initial[prop_name]); + }, prop_name); + } +}); +iframe.src = URL.createObjectURL(new Blob(["<foo/>"], { type: "application/xml" })); +document.body.appendChild(iframe); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html new file mode 100644 index 0000000000..786bd08109 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-color.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: "color" property preceded by "all: revert"</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="reference/ref-green-text.html"> + <meta name="assert" content="Own 'color', preceded by 'all: revert', overrides inherited 'color'."> + <style> + .outer { + color: red; + } + + .inner { + all: revert; + color: green; + } + </style> +</head> +<body> + <p class="outer"><span class="inner">Test passes if this text is green.</span></p> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html new file mode 100644 index 0000000000..5c31c5803c --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer-noop.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: "all: revert-layer"</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer"> +<meta name="assert" content="Checks that adding 'all: revert-layer' inside @layer has no effect on elements with no other author rules."> +<!-- Split into chunks to avoid timeouts. --> +<meta name="variant" content="?include=0"> +<meta name="variant" content="?include=1"> +<meta name="variant" content="?include=2"> +<meta name="variant" content="?include=3"> +<meta name="variant" content="?include=4"> +<meta name="variant" content="?include=5"> +<meta name="variant" content="?include=6"> +<meta name="variant" content="?include=7"> +<script> + const CHUNKS = 8; +</script> + +<style> +@layer { + .revert-all { + all: revert-layer; + } +} +</style> + +<div id="log"></div> +<div id="wrapper"></div> + +<script src="/common/subset-tests-by-key.js"></script> +<script src="/html/resources/common.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +function cloneStyle(style) { + const clone = Object.create(null); + for (let property of style) { + clone[property] = style.getPropertyValue(property); + } + return clone; +} + +function assertSameClones(clone1, clone2, callback) { + for (let property in clone1) { + const value1 = clone1[property]; + const value2 = clone2[property]; + // assert_equals is slow, so only call it if it's going to fail. + if (value1 !== value2) { + assert_equals(value1, value2, property); + } + } +} + +const wrapper = document.getElementById("wrapper"); +const elementNames = [...HTML5_ELEMENTS, "math", "svg", "z-custom"].sort(); +for (let i = 0; i < elementNames.length; ++i) { + let elementName = elementNames[i]; + let chunk = i % CHUNKS; + subsetTestByKey(chunk.toString(), test, function() { + const element = document.createElement(elementName); + wrapper.appendChild(element); + const style = getComputedStyle(element); + const clonedBaseStyle = cloneStyle(style); + element.classList.add("revert-all"); + const clonedRevertedStyle = cloneStyle(style); + assertSameClones(clonedRevertedStyle, clonedBaseStyle); + }, elementName); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html new file mode 100644 index 0000000000..868267b285 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-layer.html @@ -0,0 +1,472 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: "all: revert-layer"</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer"> +<meta name="assert" content="Checks that adding 'all: revert-layer' on the last layer has no effect."> +<style> +/* Set properties to a value different than the initial one. */ +#nothing { + accent-color: #123; + align-content: baseline; + align-items: baseline; + align-self: baseline; + alignment-baseline: central; + alt: "a"; + animation-composition: add; + animation-delay: 123s; + animation-delay-start: 123s; + animation-delay-end: 456s; + animation-direction: reverse; + animation-duration: 123s; + animation-fill-mode: both; + animation-iteration-count: 123; + animation-name: \.; + animation-play-state: paused; + animation-range: 10% 20%; + animation-timeline: none; + animation-timing-function: linear; + app-region: drag; + appearance: auto; + aspect-ratio: 3 / 4; + backdrop-filter: invert(1); + backface-visibility: hidden; + background-attachment: fixed; + background-blend-mode: overlay; + background-clip: content-box; + background-color: #123; + background-image: url("#ref"); + background-origin: border-box; + background-position: 123px; + background-repeat: round; + background-size: 123px; + baseline-shift: 123px; + baseline-source: first; + block-size: 123px; + border-block-end: 123px dashed #123; + border-block-start: 123px dashed #123; + border-bottom: 123px dashed #123; + border-collapse: collapse; + border-end-end-radius: 123px; + border-end-start-radius: 123px; + border-image-outset: 123; + border-image-repeat: round; + border-image-slice: 123; + border-image-source: url("#ref"); + border-image-width: 123px; + border-inline-end: 123px dashed #123; + border-inline-start: 123px dashed #123; + border-left: 123px dashed #123; + border-radius: 123px; + border-right: 123px dashed #123; + border-start-end-radius: 123px; + border-start-start-radius: 123px; + border-spacing: 123px; + border-top: 123px dashed #123; + bottom: 123px; + box-decoration-break: clone; + box-shadow: #123 123px 123px 123px 123px; + box-sizing: border-box; + break-after: avoid; + break-before: avoid; + break-inside: avoid; + buffered-rendering: static; + caption-side: bottom; + caret-color: #123; + clear: both; + clip: rect(123px, 123px, 123px, 123px); + clip-path: url("#ref"); + clip-rule: evenodd; + color: #123; + color-interpolation: auto; + color-interpolation-filters: auto; + color-rendering: optimizespeed; + color-scheme: dark; + column-count: 123; + column-fill: auto; + column-gap: 123px; + column-rule-color: #123; + column-rule-style: dashed; + column-rule-width: 123px; + column-span: all; + column-width: 123px; + contain: size; + contain-intrinsic-block-size: 123px; + contain-intrinsic-inline-size: 123px; + contain-intrinsic-size: 123px 123px; + container-name: foo; + container-type: size; + content: "b"; + content-visibility: auto; + counter-increment: add 123; + counter-reset: add 123; + counter-set: add 123; + cursor: none; + cx: 123px; + cy: 123px; + d: path("M 1 1"); + direction: rtl; + display: flow-root; + dominant-baseline: middle; + empty-cells: hide; + fill: #123; + fill-opacity: 0.123; + fill-rule: evenodd; + filter: url("#ref"); + flex-basis: 123px; + flex-direction: column; + flex-grow: 123; + flex-shrink: 123; + flex-wrap: wrap; + float: right; + flood-color: #123; + flood-opacity: 0.123; + font-family: "c"; + font-feature-settings: "smcp"; + font-kerning: none; + font-language-override: "d"; + font-optical-sizing: none; + font-palette: dark; + font-size: 123px; + font-size-adjust: 123; + font-stretch: 123%; + font-style: italic; + font-synthesis: none; + font-variant-alternates: historical-forms; + font-variant-caps: small-caps; + font-variant-east-asian: full-width; + font-variant-emoji: emoji; + font-variant-ligatures: none; + font-variant-numeric: tabular-nums; + font-variant-position: super; + font-variation-settings: "smcp" 1; + font-weight: 123; + forced-color-adjust: none; + glyph-orientation-horizontal: 123deg; + glyph-orientation-vertical: 123deg; + grid-auto-columns: 123px; + grid-auto-flow: column; + grid-auto-rows: 123px; + grid-column-end: 123; + grid-column-start: 123; + grid-row-end: 123; + grid-row-start: 123; + grid-template-areas: "."; + grid-template-columns: 123fr; + grid-template-rows: 123fr; + hanging-punctuation: first; + height: 123px; + hyphenate-character: "e"; + hyphenate-limit-chars: 5; + hyphens: auto; + image-orientation: none; + image-rendering: pixelated; + ime-mode: normal; + initial-letter: 123; + inline-size: 123px; + input-security: none; + inset-block-end: 123px; + inset-block-start: 123px; + inset-inline-end: 123px; + inset-inline-start: 123px; + isolation: isolate; + justify-content: center; + justify-items: baseline; + justify-self: baseline; + kerning: 123px; + left: 123px; + letter-spacing: 123px; + lighting-color: #123; + line-break: anywhere; + line-height: 123px; + line-height-step: 123px; + list-style-image: url("#ref"); + list-style-position: inside; + list-style-type: square; + margin-block-end: 123px; + margin-block-start: 123px; + margin-bottom: 123px; + margin-inline-end: 123px; + margin-inline-start: 123px; + margin-left: 123px; + margin-right: 123px; + margin-top: 123px; + margin-trim: block; + marker-end: url("#ref"); + marker-mid: url("#ref"); + marker-start: url("#ref"); + mask-clip: content-box; + mask-composite: exclude; + mask-image: url("#ref"); + mask-mode: alpha; + mask-origin: content-box; + mask-position-x: 123px; + mask-position-y: 123px; + mask-repeat: round; + mask-size: 123px; + mask-type: alpha; + masonry-auto-flow: ordered; + math-depth: 123; + math-shift: compact; + math-style: compact; + max-block-size: 123px; + max-height: 123px; + max-inline-size: 123px; + max-width: 123px; + min-block-size: 123px; + min-height: 123px; + min-inline-size: 123px; + min-width: 123px; + mix-blend-mode: overlay; + object-fit: contain; + object-overflow: visible; + object-position: 123px 123%; + object-view-box: inset(123px); + offset-anchor: 123px 123%; + offset-distance: 123px; + offset-path: path("M 1 1"); + offset-position: 123px; + offset-rotate: 123deg; + opacity: 0.123; + order: 123; + orphans: 123; + outline-color: #123; + outline-offset: 123px; + outline-style: auto; + outline-width: 123px; + overflow-anchor: none; + overflow-block: auto; + overflow-clip-margin: 123px; + overflow-inline: hidden; + overflow-wrap: anywhere; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-block: contain; + overscroll-behavior-inline: contain; + overscroll-behavior-x: contain; + overscroll-behavior-y: contain; + padding-block-end: 123px; + padding-block-start: 123px; + padding-bottom: 123px; + padding-inline-end: 123px; + padding-inline-start: 123px; + padding-left: 123px; + padding-right: 123px; + padding-top: 123px; + page: page; + paint-order: fill; + perspective: 123px; + perspective-origin: 123px 123%; + pointer-events: all; + position: relative; + print-color-adjust: exact; + quotes: none; + r: 123px; + resize: both; + right: 123px; + rotate: 123deg; + row-gap: 123px; + ruby-align: center; + ruby-position: under; + rx: 123px; + ry: 123px; + scale: 123; + scroll-behavior: smooth; + scroll-margin-block-end: 123px; + scroll-margin-block-start: 123px; + scroll-margin-bottom: 123px; + scroll-margin-inline-end: 123px; + scroll-margin-inline-start: 123px; + scroll-margin-left: 123px; + scroll-margin-right: 123px; + scroll-margin-top: 123px; + scroll-padding-block-end: 123px; + scroll-padding-block-start: 123px; + scroll-padding-bottom: 123px; + scroll-padding-inline-end: 123px; + scroll-padding-inline-start: 123px; + scroll-padding-left: 123px; + scroll-padding-right: 123px; + scroll-padding-top: 123px; + scroll-snap-align: center; + scroll-snap-stop: always; + scroll-snap-type: both; + scroll-timeline: --foo inline; + scrollbar-color: #123 #123; + scrollbar-gutter: stable; + scrollbar-width: none; + shape-image-threshold: 123; + shape-margin: 123px; + shape-outside: border-box; + shape-rendering: optimizespeed; + speak: spell-out; + speak-as: spell-out; + stop-color: #123; + stop-opacity: 0.123; + stroke: #123; + stroke-color: #123; + stroke-dasharray: 123px; + stroke-dashoffset: 123px; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 123; + stroke-opacity: 0.123; + stroke-width: 123px; + tab-size: 123; + table-layout: fixed; + text-align: center; + text-align-last: center; + text-anchor: middle; + text-combine-upright: all; + text-decoration-color: #123; + text-decoration-line: underline; + text-decoration-skip-ink: none; + text-decoration-style: dashed; + text-decoration-thickness: 123px; + text-emphasis-color: #123; + text-emphasis-position: under right; + text-emphasis-style: dot; + text-indent: 123px; + text-justify: none; + text-orientation: sideways; + text-overflow: ellipsis; + text-rendering: optimizespeed; + text-shadow: #123 123px 123px 123px; + text-size-adjust: none; + text-transform: lowercase; + text-underline-offset: 123px; + text-underline-position: under; + text-wrap-style: balance; + timeline-scope: --foo; + top: 123px; + touch-action: none; + transform: scale(-1); + transform-box: fill-box; + transform-origin: 123px 123px 123px; + transform-style: preserve-3d; + transition-behavior: allow-discrete; + transition-delay: 123s; + transition-duration: 123s; + transition-property: add; + transition-timing-function: linear; + translate: 123px; + unicode-bidi: plaintext; + user-select: all; + vector-effect: non-scaling-stroke; + vertical-align: 123px; + view-timeline: --foo inline 10px; + view-transition-name: --foo; + visibility: collapse; + white-space: pre; + white-space-trim: discard-inner; + widows: 123; + width: 123px; + will-change: height; + word-break: break-word; + word-spacing: 123px; + word-wrap: break-word; + writing-mode: vertical-lr; + x: 123px; + y: 123px; + z-index: 123; + zoom: 123; +} + +@layer layer1 { + /* Reset properties to their initial value */ + #target { + all: initial; + } +} + +@layer layer2 { + /* This will be populated with properties set to a non-initial value */ + #target {} +} + +@layer layer3 { + /* This should roll back to the values from the previous layer */ + #target.rollback { + all: revert-layer; + } +} +</style> + +<div id="log"></div> + +<!-- This custom element is unlikely to get important UA styles --> +<foo-bar id="target"></foo-bar> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const { sheet } = document.querySelector("style"); +const nonInitialStyle = sheet.cssRules[0].style; +const layer2Style = sheet.cssRules[2].cssRules[0].style; + +const target = document.getElementById("target"); +const cs = getComputedStyle(target); + +// Some properties can be forced to compute to their initial value +// unless another property is set to a certain value. +function prerequisites(property) { + switch (property) { + case "border-block-end-width": + case "border-block-start-width": + case "border-bottom-width": + case "border-inline-end-width": + case "border-inline-start-width": + case "border-left-width": + case "border-right-width": + case "border-top-width": + return "border-style: solid"; + case "column-rule-width": + return "column-rule-style: solid"; + case "outline-width": + return "outline-style: solid"; + case "rotate": + case "scale": + case "transform": + case "transform-style": + case "translate": + return "display: block"; + default: + return ""; + } +} + +const initialValues = Object.create(null); +for (let property of cs) { + if (!property.startsWith("-")) { + initialValues[property] = cs.getPropertyValue(property); + } +} + +for (let property in initialValues) { + // Skip property if the stylesheet above doesn't provide a non-initial value. + // This is to avoid having to update the test every time a new CSS property is added. + const nonInitialValue = nonInitialStyle.getPropertyValue(property); + if (nonInitialValue === "") { + continue; + } + + test(function() { + const initialValue = initialValues[property]; + assert_not_equals(initialValue, "", "Should have the initial value."); + + this.add_cleanup(() => { + layer2Style.cssText = ""; + target.classList.remove("rollback"); + }); + + layer2Style.cssText = prerequisites(property); + layer2Style.setProperty(property, nonInitialValue); + const changedValue = cs.getPropertyValue(property); + assert_not_equals(changedValue, initialValue, "Should get a different computed value."); + + target.classList.add("rollback"); + const revertedValue = cs.getPropertyValue(property); + assert_equals(revertedValue, changedValue, "Layer 3 should rollback to layer 2."); + }, property); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html new file mode 100644 index 0000000000..e8f560d685 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-noop.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: "all: revert"</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-4/#default"> +<meta name="assert" content="Checks that adding 'all: revert' has no effect on elements with no other author rules."> +<!-- Split into chunks to avoid timeouts. --> +<meta name="variant" content="?include=0"> +<meta name="variant" content="?include=1"> +<meta name="variant" content="?include=2"> +<meta name="variant" content="?include=3"> +<meta name="variant" content="?include=4"> +<meta name="variant" content="?include=5"> +<meta name="variant" content="?include=6"> +<meta name="variant" content="?include=7"> +<script> + const CHUNKS = 8; +</script> + +<style> +.revert-all { + all: revert; +} +</style> + +<div id="log"></div> +<div id="wrapper"></div> + +<script src="/common/subset-tests-by-key.js"></script> +<script src="/html/resources/common.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +function cloneStyle(style) { + const clone = Object.create(null); + for (let property of style) { + clone[property] = style.getPropertyValue(property); + } + return clone; +} + +function assertSameClones(clone1, clone2, callback) { + for (let property in clone1) { + const value1 = clone1[property]; + const value2 = clone2[property]; + // assert_equals is slow, so only call it if it's going to fail. + if (value1 !== value2) { + assert_equals(value1, value2, property); + } + } +} + +const wrapper = document.getElementById("wrapper"); +const elementNames = [...HTML5_ELEMENTS, "math", "svg", "z-custom"].sort(); +for (let i = 0; i < elementNames.length; ++i) { + let elementName = elementNames[i]; + let chunk = i % CHUNKS; + subsetTestByKey(chunk.toString(), test, function() { + const element = document.createElement(elementName); + wrapper.appendChild(element); + const style = getComputedStyle(element); + const clonedBaseStyle = cloneStyle(style); + element.classList.add("revert-all"); + const clonedRevertedStyle = cloneStyle(style); + assertSameClones(clonedRevertedStyle, clonedBaseStyle); + }, elementName); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html new file mode 100644 index 0000000000..0ef326c272 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited-ref.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<title>CSS Cascade: all:revert in :visited</title> +<a href="">Test passes if this text has UA style for visited links</a>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html new file mode 100644 index 0000000000..9df1277aca --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-revert-visited.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>CSS Cascade: all:revert in :visited</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#all-shorthand"> +<link rel="match" href="all-prop-revert-visited-ref.html"> +<style> + :root { color: red; } + a:visited { color: red; } + a:visited { all: revert; } +</style> +<a href="">Test passes if this text has UA style for visited links</a> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html new file mode 100644 index 0000000000..457901f841 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-color.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: "color" property preceded by "all: unset"</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#all-shorthand"> + <link rel="match" href="reference/ref-green-text.html"> + <meta name="assert" content="Own 'color', preceded by 'all: unset', overrides inherited 'color'."> + <style> + .outer { + color: red; + } + + .inner { + all: unset; + color: green; + } + </style> +</head> +<body> + <p class="outer"><span class="inner">Test passes if this text is green.</span></p> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html new file mode 100644 index 0000000000..e67b972768 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited-ref.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<title>CSS Cascade: all:unset in :visited</title> +<style> + :root { color: green; } + a:visited { color: red; } + a:visited { color: unset; } +</style> +<a href="">Test passes if this text is green.</a> diff --git a/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html new file mode 100644 index 0000000000..598d3f5edc --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/all-prop-unset-visited.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>CSS Cascade: all:unset in :visited</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#all-shorthand"> +<link rel="match" href="all-prop-unset-visited-ref.html"> +<style> + :root { color: green; } + a:visited { color: red; } + a:visited { all: unset; } +</style> +<a href="">Test passes if this text is green.</a> diff --git a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html new file mode 100644 index 0000000000..88e28fe4ff --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html @@ -0,0 +1,78 @@ +<!doctype html> +<title>@scope: parsing</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<main id=main></main> +<script> + function test_valid(actual, expected) { + if (expected === undefined) + expected = actual; + test(t => { + t.add_cleanup(() => main.replaceChildren()); + let style = document.createElement('style'); + style.textContent = `${actual}{}`; + main.append(style); + assert_equals(style.sheet.rules.length, 1); + let rule = style.sheet.rules[0]; + assert_equals(rule.cssText, `${expected} {\n}`); + }, `${actual} is valid`); + } + + function test_invalid(actual) { + test(t => { + t.add_cleanup(() => main.replaceChildren()); + let style = document.createElement('style'); + style.textContent = `${actual}{}`; + main.append(style); + assert_equals(style.sheet.rules.length, 0); + }, `${actual} is not valid`); + } + + test_valid('@scope (.a)'); + test_valid('@scope (.a + .b)'); + test_valid('@scope (.a:hover)'); + test_valid('@scope (.a:hover, #b, div)'); + test_valid('@scope (:is(div, span))'); + + test_valid('@scope (.a) to (.b)'); + test_valid('@scope (.a)to (.b)', '@scope (.a) to (.b)'); + test_valid('@scope (.a) to (.b:hover, #c, div)'); + test_valid('@scope'); + test_valid('@scope to (.a)'); + test_valid('@scope (.a) to (&)'); + test_valid('@scope (.a) to (& > &)'); + test_valid('@scope (.a) to (> .b)'); + test_valid('@scope (.a) to (+ .b)'); + test_valid('@scope (.a) to (~ .b)'); + test_valid('@scope ()', '@scope'); + test_valid('@scope to ()', '@scope'); + test_valid('@scope () to ()', '@scope'); + + // Forgiving behavior (keep invalid selector as-is for the serialization): + test_valid('@scope (.c <> .d)'); + test_valid('@scope (.a, .c <> .d)'); + test_valid('@scope (.a <> .b, .c)'); + test_valid('@scope (div::before)'); + test_valid('@scope (div::after)'); + test_valid('@scope (slotted(div))'); + test_valid('@scope (.a) to (div::before)'); + test_valid('@scope (> &) to (>>)'); + + test_invalid('@scope div'); + test_invalid('@scope (.a) unknown (.c)'); + test_invalid('@scope (.a) to unknown (.c)'); + test_invalid('@scope (.a) 1px (.c)'); + test_invalid('@scope (.a) to unknown(c)'); + test_invalid('@scope unknown(.a)'); + test_invalid('@scope 1px'); + test_invalid('@scope creep'); + test_invalid('@scope )))'); + test_invalid('@scope ('); + test_invalid('@scope ( {}'); + test_invalid('@scope to'); + test_invalid('@scope }'); + test_invalid('@scope (.a'); + test_invalid('@scope (.a to (.b)'); + test_invalid('@scope ( to (.b)'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/idlharness.html b/testing/web-platform/tests/css/css-cascade/idlharness.html new file mode 100644 index 0000000000..4f89f73aa7 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/idlharness.html @@ -0,0 +1,40 @@ +<!doctype html> +<title>CSS Cascade IDL tests</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layer-apis"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="/resources/idlharness.js"></script> + +<style> +@layer bar, baz; +@import url('data:text/css,') layer(qux); +@layer foo { } +@scope (div) to (span) { } +</style> + +<script> + 'use strict'; + idl_test( + ['css-cascade', 'css-cascade-6'], + ['cssom'], + idl_array => { + try { + self.statement = document.styleSheets[0].cssRules.item(0); + self.layeredImport = document.styleSheets[0].cssRules.item(1); + self.block = document.styleSheets[0].cssRules.item(2); + self.scope = document.styleSheets[0].cssRules.item(3); + } catch (e) { + // Will be surfaced when any rule is undefined below. + } + + idl_array.add_objects({ + CSSLayerBlockRule: ['block'], + CSSLayerStatementRule: ['statement'], + CSSImportRule: ['layeredImport'], + CSSScopeRule: ['scope'], + }); + } + ); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/import-conditional-001.html b/testing/web-platform/tests/css/css-cascade/import-conditional-001.html new file mode 100644 index 0000000000..a841f26545 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/import-conditional-001.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: @import with basic media query</title> + <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"> + <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#conditional-import"> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#conditional-import"> + <link rel="help" href="https://www.w3.org/TR/css3-mediaqueries/#syntax"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="Test passes on visual UAs if @import can be combined with a media query."> + <style> + @import "support/test-red.css"; + @import "support/test-green.css" + (min-width: 1px) and /* assuming screen < 1km */ (max-width: 40000in), nonsense; + @import "support/test-red.css" + (max-width: 1px), nonsense; + div { + box-sizing: border-box; + width: 100px; + height: 100px; + padding: 5px; /* Avoids text antialiasing issues */ + background: red; + } + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div class="test">FAIL</div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/import-conditional-002.html b/testing/web-platform/tests/css/css-cascade/import-conditional-002.html new file mode 100644 index 0000000000..79e850a742 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/import-conditional-002.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: @import with basic supports condition</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#conditional-import"> + <link rel="help" href="https://www.w3.org/TR/css-cascade-5/#conditional-import"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="Test passes on visual UAs if @import can be combined with a supports condition."> + <style> + @import "support/test-red.css"; + @import "support/test-green.css" + supports(display: block); + @import "support/test-red.css" + supports(foo: bar); + div { + box-sizing: border-box; + width: 100px; + height: 100px; + padding: 5px; /* Avoids text antialiasing issues */ + background: red; + } + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div class="test"></div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/import-conditions.html b/testing/web-platform/tests/css/css-cascade/import-conditions.html new file mode 100644 index 0000000000..9c1e5c6e87 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/import-conditions.html @@ -0,0 +1,132 @@ +<!DOCTYPE html> +<title>CSS Cascade Test: import conditions</title> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#import-conditions"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @layer { + .target { color: red; } + } +</style> +<div id="target" class="target"></div> +<script> + const testCases = [ + { + importCondition: "supports(display:block)", + matches: true + }, + { + importCondition: "supports((display:flex))", + matches: true + }, + { + importCondition: "supports((display:block) and (display:flex))", + matches: true + }, + { + importCondition: "supports((display:block) and (foo:bar))", + matches: false + }, + { + importCondition: "supports((display:block) or (display:flex))", + matches: true + }, + { + importCondition: "supports((display:block) or (foo:bar))", + matches: true + }, + { + importCondition: "supports(not (display: flex))", + matches: false + }, + { + importCondition: "supports(display: block !important)", + matches: true + }, + { + importCondition: "supports(foo:bar)", + matches: false + }, + { + importCondition: "supports(display:block) (width >= 0px)", + matches: true + }, + { + importCondition: "(width >= 0px) supports(foo:bar)", + matches: false + }, + { + importCondition: "(width >= 0px) supports(display:block)", + matches: false + }, + + // selector() + { + importCondition: "supports(selector(a))", + matches: true + }, + { + importCondition: "supports(selector(p a))", + matches: true + }, + { + importCondition: "supports(selector(p > a))", + matches: true + }, + { + importCondition: "supports(selector(p + a))", + matches: true + }, + + // font-tech() + { + importCondition: "supports(font-tech(color-COLRv1))", + matches: true + }, + { + importCondition: "supports(font-tech(invalid))", + matches: false + }, + + // font-format() + { + importCondition: "supports(font-format(opentype))", + matches: true + }, + { + importCondition: "supports(font-format(woff))", + matches: true + }, + { + importCondition: "supports(font-format(invalid))", + matches: false + }, + { + importCondition: "layer(A.B) supports(font-format(opentype))", + matches: true + }, + { + importCondition: "layer supports(selector(a))", + matches: true + }, + ]; + let target = document.getElementById("target"); + for (let testCase of testCases) { + promise_test(async t => { + let styleElement = document.createElement("style"); + styleElement.innerText = "@import url(data:text/css,.target{color:green}) " + testCase.importCondition + ";"; + + await new Promise(resolve => { + styleElement.onload = resolve; + styleElement.onerror = resolve; + document.head.appendChild(styleElement); + }); + + try { + assert_equals(getComputedStyle(target).color, testCase.matches ? "rgb(0, 128, 0)" : "rgb(255, 0, 0)"); + } finally { + styleElement.remove(); + } + }, testCase.importCondition + " is " + (testCase.matches ? "" : "not ") + "a valid import condition"); + } +</script> diff --git a/testing/web-platform/tests/css/css-cascade/import-removal.html b/testing/web-platform/tests/css/css-cascade/import-removal.html new file mode 100644 index 0000000000..6fb1ea3458 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/import-removal.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title></title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#at-import"> +<link rel="help" href="https://bugs.webkit.org/show_bug.cgi?id=235930"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that the page is rendered correctly when @import rule is removed with JS."> +<p>Test passes if there is a filled green square.</p> +<div style="width:100px; height:100px;"></div> +<script> +const style = document.createElement("style"); +document.head.append(style); +const {sheet} = style; +sheet.insertRule("@import url('data:text/css,div { background: red !important }');"); +sheet.insertRule("div { background: green }", 1); +sheet.deleteRule(0); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/important-prop-ref.html b/testing/web-platform/tests/css/css-cascade/important-prop-ref.html new file mode 100644 index 0000000000..004679da73 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-prop-ref.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade Green Right Square Reference File</title> +<link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk"> +<style> +#success { + width: 100px; + height: 100px; + background-color: green; +} +</style> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div> + <div id="success"></div> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/important-prop.html b/testing/web-platform/tests/css/css-cascade/important-prop.html new file mode 100644 index 0000000000..e8abffdf88 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-prop.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: Important vs. Animations</title> + <link rel="author" title="David Burns" href="http://www.theautomatedtester.co.uk"> + <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"> + <link rel="help" href="https://drafts.csswg.org/css-cascade/#importance"> + <link rel="match" href="important-prop-ref.html"> + <meta name="assert" content="Test passes if normal rules are overridden by animations, important rules override animations, and !important declarations are ignored in animations."> + <style> + @keyframes override { + from, to { + background: #f00; color: green; + border-color: green; border-color: red !important; + } + } + + .square { + color:#00f; + animation: override 1s infinite; + width: 80px; + height: 80px; + border: 10px solid red; + text-align: center; + } + div { + background-color:green !important; + color: red; + } + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div class="square green">FAIL</div> + +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/important-transition-manual.html b/testing/web-platform/tests/css/css-cascade/important-transition-manual.html new file mode 100644 index 0000000000..b9abed469f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-transition-manual.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: Author !important vs. Transitions</title> + <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"> + <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#cascade-sort"> + <style> + .container { padding: 1em 0; border-style: dotted none; border-width: 1px; } + .container > div { width: 5em; text-align: center; border: solid; transition: all 3s; } + + .container > .ref { border-color: blue ; color: navy ; background: aqua ; margin: 0.25em ; } + :hover > .ref { border-color: aqua ; color: orange ; background: teal ; margin-left: 40% ; } + + :not(:hover) > .test { border-color: blue !important; color: navy !important; background: aqua ; } + div > .test { margin: 0.25em !important; } + :hover > .test { border-color: aqua !important; color: orange ; background: teal !important; margin-left: 40% !important; } + </style> + +<p>Test passes if the two boxes transition identically when hovering with a mouse below. + +<div class=container> + <div class=test>Box 1</div> + <div class=ref>Box 2</div> +</div> diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html new file mode 100644 index 0000000000..33b33bf943 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-001.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: inline style loses to !important</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#cascade-sort"> + <link rel="author" href="mailto:sesse@chromium.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .outer { + opacity: 0.5 !important; + } + </style> +</head> +<body> + <p class="outer" id="el">Test passes if this text is semi-transparent.</p> +</body> +<script> +test(() => { + el.offsetTop; + assert_equals(getComputedStyle(el).opacity, "0.5", "style is set correctly"); +}); +test(() => { + el.offsetTop; + el.style.opacity = 0.75; + assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than adding inline style"); +}); +test(() => { + el.offsetTop; + el.style.opacity = 1.0; + assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than modifying inline style"); +}); +test(() => { + el.offsetTop; + el.style.opacity = null; + assert_equals(getComputedStyle(el).opacity, "0.5", "!important has higher priority than removing inline style"); +}); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html new file mode 100644 index 0000000000..e16aedc5bb --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-002.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: inline style loses to !important</title> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#cascade-sort"> + <link rel="author" href="mailto:sesse@chromium.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .outer { + font-size: 18px !important; + line-height: 2em; + border: 1px solid black; + } + </style> +</head> +<body> + <p class="outer" id="el">Test passes if the line-height is twice the font size.</p> +</body> +<script> +test(() => { + el.offsetTop; + assert_equals(getComputedStyle(el).lineHeight, "36px", "style is set correctly"); +}); +test(() => { + el.offsetTop; + el.style.fontSize = "24px"; + assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than adding inline style"); +}); +test(() => { + el.offsetTop; + el.style.fontSize = "36px"; + assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than modifying inline style"); +}); +test(() => { + el.offsetTop; + el.style.fontSize = null; + assert_equals(getComputedStyle(el).lineHeight, "36px", "!important has higher priority than removing inline style"); +}); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html b/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html new file mode 100644 index 0000000000..b1103abbf2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/important-vs-inline-003.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: inline style loses to !important</title> + <link rel="help" href="https://crbug.com/1332956"> + <link rel="author" href="mailto:sesse@chromium.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .cls { + visibility: inherit !important; + } + </style> +</head> +<body> + <div class="cls" id="el" style="visibility: hidden; height: 200px;"><iframe></iframe></div> +</body> +<script> +test(() => { + el.setAttribute('disabled', 'disabled'); + el.offsetTop; + el.style.height = '400px'; + assert_equals(getComputedStyle(el).visibility, "visible", "!important has higher priority than inline style"); +}); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/inherit-initial.html b/testing/web-platform/tests/css/css-cascade/inherit-initial.html new file mode 100644 index 0000000000..8d8dfef38d --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/inherit-initial.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>CSS Cascading and Inheritance test: Root element inherits from initial values</title> +<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com"> +<link rel="help" href="https://www.w3.org/TR/css3-cascade/#inheriting"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +html { + z-index: inherit; + position: inherit; + overflow: inherit; + background-color: inherit; +} +body { + overflow: scroll; + background-color: pink; +} +</style> +<script> + test(function() { + assert_equals(getComputedStyle(document.documentElement).zIndex, "auto"); + }, "z-index:inherit on root element should compute to 'auto'."); + + test(function() { + assert_equals(getComputedStyle(document.documentElement).position, "static"); + }, "position:inherit on root element should compute to 'static'."); + + test(function() { + assert_equals(getComputedStyle(document.documentElement).overflow, "visible"); + }, "overflow:inherit on root element should compute to 'visible'."); + + test(function() { + assert_equals(getComputedStyle(document.documentElement).backgroundColor, "rgba(0, 0, 0, 0)"); + }, "background-color:inherit on root element should compute to 'rgba(0, 0, 0, 0)'."); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/initial-background-color.html b/testing/web-platform/tests/css/css-cascade/initial-background-color.html new file mode 100644 index 0000000000..80897e0ef2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/initial-background-color.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title> + CSS Cascading and Inheritance Test: + Initial property and background-color + </title> + <meta name="assert" content=" + The initial keyword is supported on background-color. + " /> + + <link + rel="author" + title="François REMY" + href="mailto:fremycompany.developer@yahoo.fr" + / > + + <link rel="help" href="https://www.w3.org/TR/css-cascade-3/#initial"/> + + <link + rel="match" + href="reference/all-green.html" + /> + + <style type="text/css"> + + html, body { margin: 0px; padding: 0px; } + + html { background: green; overflow: hidden; } + #outer { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; } + #outer { background: red; background-color: initial; } + + </style> + +</head> +<body> + + <div id="outer"></div> + +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html b/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html new file mode 100644 index 0000000000..ffac42763f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/initial-color-background-001-ref.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade "W" Reference File</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <style> +body { + background-color: white; +} +div { + font-size: 100px; +} + </style> +</head> +<body> + <p>Test passes if there is a "W" and <strong>no red</strong>.</p> + <div>W</div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html b/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html new file mode 100644 index 0000000000..50e1384b0f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/initial-color-background-001.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: the "initial" value</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#initial"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#initial"> + <link rel="match" href="initial-color-background-001-ref.html"> + <meta name="assert" content="initial is not the same as inherit. color:initial results in non-red. background-color:initial results in transparent."> + <style> +body { + background-color: white; +} +div { + font-size: 100px; +} +.outer { + color: red; +} +.inner { + background-color: red; +} +.inner { + color: initial;/* normally black, almost certainly not red */ + background-color: initial;/* transparent, so the body's white will show thru */ +} + </style> +</head> +<body> + <p>Test passes if there is a "W" and <strong>no red</strong>.</p> + <div class="outer"> + <div class="inner">W</div> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/layer-basic.html b/testing/web-platform/tests/css/css-cascade/layer-basic.html new file mode 100644 index 0000000000..e214bffc25 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-basic.html @@ -0,0 +1,524 @@ +<!DOCTYPE html> +<html> +<head> +<title>CSS Cascade Layers: Basic functionality</title> +<meta name="assert" content="Basic functionality of CSS Cascade Layers"> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<target class="first"></target> +<target class="second"></target> +<div id="log"></div> +<script> + +// In all test cases, the rule specified as "color: green" should win. +var testCases = [ + { + title: 'A1 Anonymous layers', + style: ` + @layer { } + target { color: green; } + `, + }, + { + title: 'A2 Anonymous layers', + style: ` + target { color: green; } + @layer { + target { color: red; } + } + `, + }, + { + title: 'A3 Anonymous layers', + style: ` + @layer { + target { color: red; } + } + target { color: green; } + `, + }, + { + title: 'A4 Anonymous layers', + style: ` + @layer { + target { color: red; } + } + @layer { + target { color: green; } + } + `, + }, + { + title: 'A5 Anonymous layers', + style: ` + @layer { + target { color: green; } + @layer { + target { color: red; } + } + } + `, + }, + { + title: 'A6 Anonymous layers', + style: ` + @layer { + @layer { + target { color: red; } + } + target { color: green; } + } + `, + }, + { + title: 'A7 Anonymous layers', + style: ` + @layer { + @layer { + target { color: red; } + } + target { color: red; } + } + @layer { + @layer { + target { color: red; } + } + target { color: green; } + } + `, + }, + { + title: 'A8 Anonymous layers', + style: ` + @layer { + @layer { + @layer { + target { color: red; } + } + } + target { color: red; } + } + @layer { + @layer { + target { color: red; } + } + target { color: green; } + } + `, + }, + { + title: 'A9 Anonymous layers', + style: ` + @layer { + @layer { + target { color: red; } + } + target { color: red; } + } + @layer { + @layer { + @layer { + target { color: red; } + } + } + target { color: green; } + } + `, + }, + { + title: 'B1 Named layers', + style: ` + @layer A { + } + target { color: green; } + `, + }, + { + title: 'B2 Named layers', + style: ` + @layer A { + target { color: red; } + } + target { color: green; } + `, + }, + { + title: 'B3 Named layers', + style: ` + @layer A { + target { color: red; } + } + @layer A { + target { color: green; } + } + `, + }, + { + title: 'B4 Named layers', + style: ` + @layer A { + target { color: red; } + } + @layer B { + target { color: green; } + } + @layer A { + target { color: red; } + } + `, + }, + { + title: 'B5 Named layers', + style: ` + @layer A { + target { color: green; } + @layer A { + target { color: red; } + } + } + `, + }, + { + title: 'B6 Named layers', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer A { + @layer A { + target { color: green; } + } + } + `, + }, + { + title: 'B7 Named layers', + style: ` + @layer A { + target { color: red; } + @layer A { + target { color: red; } + } + } + @layer B { + target { color: green; } + } + @layer A { + @layer A { + target { color: red; } + } + } + `, + }, + { + title: 'B8 Named layers', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer B { + @layer A { + target { color: green; } + } + } + `, + }, + { + title: 'B9 Named layers', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer B { + @layer A { + target.first { color: green; } + } + } + @layer A { + @layer A { + target.first { color: red; } + target.second { color: green; } + } + } + `, + }, + { + title: 'B10 Named layers', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer B { + @layer A { + target.first { color: green; } + } + } + @layer A { + @layer B { + target.first { color: red; } + target.second { color: green; } + } + } + `, + }, + { + title: 'C1 Named layers shorthand', + style: ` + @layer A.A { + target { color: red; } + } + @layer B.A { + target { color: green; } + } + `, + }, + { + title: 'C2 Named layers shorthand', + style: ` + @layer A.A { + target { color: red; } + } + @layer B.A { + target.first { color: green; } + } + @layer A.A { + target.first { color: red; } + target.second { color: green; } + } + `, + }, + { + title: 'C3 Named layers shorthand', + style: ` + @layer A.A { + target { color: red; } + } + @layer B.A { + target.first { color: green; } + } + @layer A.B { + target.first { color: red; } + target.second { color: green; } + } + `, + }, + { + title: 'C4 Named layers shorthand', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer B.A { + target { color: green; } + } + @layer A.A + target { color: red; } + } + `, + }, + { + title: 'C5 Named layers shorthand', + style: ` + @layer A { + @layer A { + target { color: red; } + } + } + @layer B.A { + target { color: green; } + } + @layer A.B { + target { color: red; } + } + `, + }, + { + title: 'D1 Mixed named and anonymous layers', + style: ` + @layer A { + target { color: red; } + } + @layer { + target { color: green; } + } + `, + }, + { + title: 'D2 Mixed named and anonymous layers', + style: ` + @layer A { + @layer { + target { color: red; } + } + } + @layer A { + target { color: green; } + } + `, + }, + { + title: 'D3 Mixed named and anonymous layers', + style: ` + @layer A { + @layer { + target { color: red; } + } + } + @layer A { + @layer { + target { color: green; } + } + } + `, + }, + { + title: 'D4 Mixed named and anonymous layers', + style: ` + @layer A { + @layer { + target { color: red; } + } + } + @layer { + target { color: green; } + } + @layer A { + @layer { + target { color: red; } + } + } + `, + }, + { + title: 'D5 Mixed named and anonymous layers', + style: ` + @layer { + @layer A { + target { color: red; } + } + } + @layer { + target { color: green; } + } + `, + }, + { + title: 'E1 Statement syntax', + style: ` + @layer A, B, C; + @layer A { + target.first { color: red; } + target.second { color: red; } + } + @layer B { + target.first { color: red; } + } + @layer C { + target.first { color: green; } + target.second { color: green; } + } + `, + }, + { + title: 'E2 Statement syntax', + style: ` + @layer A, C, B; + @layer A { + target.first { color: red; } + target.second { color: red; } + } + @layer B { + target.first { color: green; } + } + @layer C { + target.first { color: red; } + target.second { color: green; } + } + `, + }, + { + title: 'E3 Statement syntax', + style: ` + @layer C, B, A; + @layer A { + target.first { color: green; } + target.second { color: green; } + } + @layer B { + target.first { color: red; } + } + @layer C { + target.first { color: red; } + target.second { color: red; } + } + `, + }, + { + title: 'E4 Statement syntax', + style: ` + @layer B, A.B, A.A; + @layer A { + @layer A { + target.first { color: green; } + } + @layer B { + target.first { color: red; } + target.second { color: green; } + } + } + @layer B { + target { color: red; } + } + `, + }, + { + title: 'E5 Statement syntax', + style: ` + @layer A.B, B, A.A; + @layer A { + @layer A { + target.first { color: red; } + } + @layer B { + target.first { color: red; } + target.second { color: red; } + } + } + @layer B { + target { color: green; } + } + `, + }, +]; + +for (let testCase of testCases) { + const styleElement = document.createElement('style'); + styleElement.textContent = testCase['style']; + document.head.append(styleElement); + + test(function () { + var targets = document.querySelectorAll('target'); + for (target of targets) + assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)', + testCase['title'] + ", target '" + target.classList[0] + "'"); + }, testCase['title']); + + styleElement.remove(); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html b/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html new file mode 100644 index 0000000000..1720898457 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-counter-style-override.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<title>Resolving @counter-style name conflicts with cascade layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#target, #reference { + font-family: monospace; + width: min-content; +} + +#reference::before { + content: '0000'; +} + +@counter-style three { + system: cyclic; + symbols: '000'; +} + +@counter-style four { + system: cyclic; + symbols: '0000'; +} +</style> + +<ul> + <li id="target"></li> + <li id="reference"></li> +</ul> + +<script> +// In all tests, #target::before should have 4 characters, same as #reference. + +const testCases = [ + { + title: '@counter-style unlayered overrides layered', + style: ` + #target::before { + content: counter(dont-care, custom-counter-style); + } + + @counter-style custom-counter-style { + system: extends four; + } + + @layer { + @counter-style custom-counter-style { + system: extends three; + } + } + ` + }, + + { + title: '@counter-style override between layers', + style: ` + @layer base, override; + + #target::before { + content: counter(dont-care, custom-counter-style); + } + + @layer override { + @counter-style custom-counter-style { + system: extends four; + } + } + + @layer base { + @counter-style custom-counter-style { + system: extends three; + } + } + ` + }, + + { + title: '@counter-style override update with appended sheet 1', + style: ` + @layer base, override; + + #target::before { + content: counter(dont-care, custom-counter-style); + } + + @layer override { + @counter-style custom-counter-style { + system: extends four; + } + } + `, + append: ` + @layer base { + @counter-style custom-counter-style { + system: extends three; + } + } + ` + }, + + { + title: '@counter-style override update with appended sheet 2', + style: ` + @layer base, override; + + #target::before { + content: counter(dont-care, custom-counter-style); + } + + @layer base { + @counter-style custom-counter-style { + system: extends three; + } + } + `, + append: ` + @layer override { + @counter-style custom-counter-style { + system: extends four; + } + } + ` + }, +]; + +for (let testCase of testCases) { + var documentStyle = document.createElement('style'); + documentStyle.appendChild(document.createTextNode(testCase['style'])); + document.head.appendChild(documentStyle); + + var appendedStyle; + if (testCase['append']) { + document.body.offsetLeft; // Force style update + appendedStyle = document.createElement('style'); + appendedStyle.appendChild(document.createTextNode(testCase['append'])); + document.head.appendChild(appendedStyle); + } + + test(function () { + assert_equals(getComputedStyle(target).width, + getComputedStyle(reference).width); + }, testCase['title']); + + if (appendedStyle) + appendedStyle.remove(); + documentStyle.remove(); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html new file mode 100644 index 0000000000..dfa6bbfcfb --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse-at-property.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: @property rule invalidation on layer order changes</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="stylesheet" href="/fonts/ahem.css"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#reference { + color: green; + --foo: green; +} +</style> + +<div id=target>Lorem ipsum</div> +<div id=reference>Lorem ipsum</div> + +<script> +const testCases = [ + { + title: 'Insert layer invalidates @property', + sheets: [ + '', + ` + @layer first { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + } + @layer second { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + `, + ], + update: function(sheets) { + sheets[0].insertRule('@layer second {}', 0); + }, + property: '--foo', + }, + { + title: 'Delete layer invalidates @property', + sheets: [ + '@layer second {}', + ` + @layer first { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + @layer second { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + } + `, + ], + update: function(sheets) { + sheets[0].deleteRule(0); + }, + property: '--foo', + }, +]; + +for (let testCase of testCases) { + test(testObj => { + const styleElements = testCase.sheets.map(sheet => { + const element = document.createElement('style'); + element.appendChild(document.createTextNode(sheet)); + document.head.appendChild(element); + return element; + }); + testObj.add_cleanup(() => { + for (let element of styleElements) + element.remove(); + }); + + const sheets = styleElements.map(element => element.sheet); + testCase.update(sheets); + const actual = getComputedStyle(target).getPropertyValue(testCase.property); + const expected = getComputedStyle(reference).getPropertyValue(testCase.property); + assert_equals(actual, expected); + }, testCase.title); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html new file mode 100644 index 0000000000..ddc5977d42 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-cssom-order-reverse.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: at-rule and style invalidation on layer order changes</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="stylesheet" href="/fonts/ahem.css"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#reference { + color: green; + font: 20px/1 ahem; + width: max-content; +} +</style> + +<div id=target>Lorem ipsum</div> +<div id=reference>Lorem ipsum</div> + +<script> +const testCases = [ + { + title: 'Insert layer invalidates style', + sheets: [ + '', + ` + @layer first { + #target { color: green; } + } + @layer second { + #target { color: red; } + } + `, + ], + update: function(sheets) { + sheets[0].insertRule('@layer second {}', 0); + }, + property: 'color', + }, + { + title: 'Delete layer invalidates style', + sheets: [ + '@layer second {}', + ` + @layer first { + #target { color: red; } + } + @layer second { + #target { color: green; } + } + `, + ], + update: function(sheets) { + sheets[0].deleteRule(0); + }, + property: 'color', + }, + { + title: 'Insert layer invalidates @font-face', + sheets: [ + '', + ` + @layer first { + @font-face { + font-family: custom; + src: local('Ahem'), url('/fonts/Ahem.ttf'); + } + } + @layer second { + @font-face { + font-family: custom; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + #target { font: 20px/1 custom; width: max-content; } + `, + ], + update: async function(sheets) { + await document.fonts.load('20px/1 ahem'); + await document.fonts.load('20px/1 custom'); + document.body.offsetLeft; // Force style recalc + sheets[0].insertRule('@layer second {}', 0); + await document.fonts.load('20px/1 custom'); + }, + property: 'width', + }, + { + title: 'Delete layer invalidates @font-face', + sheets: [ + '@layer second {}', + ` + @layer first { + @font-face { + font-family: custom; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + @layer second { + @font-face { + font-family: custom; + src: local('Ahem'), url('/fonts/Ahem.ttf'); + } + } + #target { font: 20px/1 custom; width: max-content; } + `, + ], + update: async function(sheets) { + await document.fonts.load('20px/1 ahem'); + await document.fonts.load('20px/1 custom'); + document.body.offsetLeft; // Force style recalc + sheets[0].deleteRule(0); + await document.fonts.load('20px/1 custom'); + }, + property: 'width', + }, +]; + +for (let testCase of testCases) { + promise_test(async test => { + const styleElements = testCase.sheets.map(sheet => { + const element = document.createElement('style'); + element.appendChild(document.createTextNode(sheet)); + document.head.appendChild(element); + return element; + }); + test.add_cleanup(() => { + for (let element of styleElements) + element.remove(); + }); + + const sheets = styleElements.map(element => element.sheet); + await testCase.update(sheets); + const actual = getComputedStyle(target).getPropertyValue(testCase.property); + const expected = getComputedStyle(reference).getPropertyValue(testCase.property); + assert_equals(actual, expected); +}, testCase.title); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html b/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html new file mode 100644 index 0000000000..d35caca012 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-font-face-override.html @@ -0,0 +1,141 @@ +<!DOCTYPE html> +<title>Resolving @keyframe name conflicts with cascade layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#target { + font-size: 20px; + width: min-content; +} +</style> + +<div id="target">Test</div> + +<script> +// In all tests, width of #target should be 80px. + +const testCases = [ + { + title: '@font-face unlayered overrides layered', + style: ` + #target { + font-family: custom-font; + } + + @font-face { + font-family: custom-font; + src: url('/fonts/Ahem.ttf'); + } + + @layer { + @font-face { + font-family: custom-font; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + ` + }, + + { + title: '@font-face override between layers', + style: ` + @layer base, override; + + #target { + font-family: custom-font; + } + + @layer override { + @font-face { + font-family: custom-font; + src: url('/fonts/Ahem.ttf'); + } + } + + @layer base { + @font-face { + font-family: custom-font; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + ` + }, + + { + title: '@font-face override update with appended sheet 1', + style: ` + @layer base, override; + + #target { + font-family: custom-font; + } + + @layer override { + @font-face { + font-family: custom-font; + src: url('/fonts/Ahem.ttf'); + } + } + `, + append: ` + @layer base { + @font-face { + font-family: custom-font; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + ` + }, + + { + title: '@font-face override update with appended sheet 2', + style: ` + @layer base, override; + + #target { + font-family: custom-font; + } + + @layer base { + @font-face { + font-family: custom-font; + src: url('/fonts/noto/noto-sans-v8-latin-regular.woff') format('woff'); + } + } + `, + append: ` + @layer override { + @font-face { + font-family: custom-font; + src: url('/fonts/Ahem.ttf'); + } + } + ` + }, +]; + +for (let testCase of testCases) { + promise_test(async () => { + var documentStyle = document.createElement('style'); + documentStyle.appendChild(document.createTextNode(testCase['style'])); + document.head.appendChild(documentStyle); + + var appendedStyle; + if (testCase['append']) { + document.body.offsetLeft; // Force style update + appendedStyle = document.createElement('style'); + appendedStyle.appendChild(document.createTextNode(testCase['append'])); + document.head.appendChild(appendedStyle); + } + + await document.fonts.load('20px/1 custom-font'); + assert_equals(getComputedStyle(target).width, '80px'); + + if (appendedStyle) + appendedStyle.remove(); + documentStyle.remove(); + }, testCase['title']); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-import.html b/testing/web-platform/tests/css/css-cascade/layer-import.html new file mode 100644 index 0000000000..821bc7d72f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-import.html @@ -0,0 +1,294 @@ +<!DOCTYPE html> +<html> +<head> +<title>CSS Cascade Layers: Imports</title> +<meta name="assert" content="Import functionality of CSS Cascade Layers"> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<target class="first"></target> +<div id="log"></div> +<script> + +// In all test cases, the rule specified as "color: green" should win. +const imports = { + "basic-green.css": ` + target { color: green; } + `, + "basic-red.css": ` + target { color: red; } + `, + "layer-green.css": ` + @layer { + target { color: green; } + } + `, + "layer-red.css": ` + @layer { + target { color: red; } + } + `, + "layer-A-green.css": ` + @layer A { + target { color: green; } + } + `, + "layer-A-red.css": ` + @layer A { + target { color: red; } + } + `, + "layer-B-green.css": ` + @layer B { + target { color: green; } + } + `, + "layer-B-red.css": ` + @layer B { + target { color: red; } + } + `, +}; + +const testCases = [ + { + title: 'A1 Layer rules with import', + style: ` + @import url(basic-green.css); + @layer { + target { color: red; } + } + ` + }, + { + title: 'A2 Layer rules with import', + style: ` + @import url(layer-red.css); + target { color: green; } + ` + }, + { + title: 'A3 Layer rules with import', + style: ` + @import url(basic-green.css); + @import url(layer-red.css); + ` + }, + { + title: 'A4 Layer rules with import', + style: ` + @import url(layer-A-red.css); + @layer B { + target { color: green; } + } + @layer A { + target { color: red; } + } + ` + }, + { + title: 'B1 Anonymous imports', + style: ` + @import url(basic-red.css) layer; + target { color: green; } + ` + }, + { + title: 'B2 Anonymous imports', + style: ` + @import url(basic-red.css) layer; + @import url(basic-green.css) layer; + ` + }, + { + title: 'B3 Anonymous imports', + style: ` + @import url(basic-red.css) layer; + @layer { + target { color: green; } + } + ` + }, + { + title: 'B4 Anonymous imports', + style: ` + @import url(layer-red.css); + @import url(basic-green.css) layer; + ` + }, + { + title: 'C1 Named imports', + style: ` + @import url(basic-red.css) layer(A); + target { color: green; } + ` + }, + { + title: 'C2 Named imports', + style: ` + @import url(basic-red.css) layer(A); + @import url(basic-green.css) layer(A); + ` + }, + { + title: 'C3 Named imports', + style: ` + @import url(basic-red.css) layer(A); + @layer A { + target { color: green; } + } + ` + }, + { + title: 'C4 Named imports', + style: ` + @import url(layer-red.css) layer(A); + @layer A { + target { color: green; } + } + ` + }, + { + title: 'C5 Named imports', + style: ` + @import url(layer-A-red.css) layer(A); + @layer A.A { + target { color: green; } + } + ` + }, + { + title: 'C6 Named imports', + style: ` + @import url(layer-A-red.css) layer(A); + @layer B { + target { color: green; } + } + @layer A.B { + target { color: red; } + } + ` + }, + { + title: 'C7 Named imports', + style: ` + @import url(basic-green.css) layer(A); + @import url(basic-red.css) layer(B); + @import url(basic-green.css) layer(C); + ` + }, + { + title: 'C8 Named imports', + style: ` + @import url(basic-red.css) layer(A); + @import url(basic-green.css) layer(B); + @import url(basic-red.css) layer(A); + ` + }, + { + title: 'C9 Named imports', + style: ` + @import url(basic-red.css) layer(A); + @import url(basic-red.css) layer(B.A); + @import url(basic-green.css) layer(B); + ` + }, + { + title: 'D1 Layer statement with imports', + style: ` + @import url(basic-red.css) layer(A); + @import url(basic-green.css) layer(B); + @layer B, A; + ` + }, + { + title: 'D2 Layer statement with imports', + style: ` + @layer B; + @import url(basic-green.css) layer(A); + @layer B { + target { color: red; } + } + ` + }, + { + title: 'D3 Layer statement with imports', + style: ` + @layer B; + @import url(basic-green.css) layer(A); + @import url(basic-red.css) layer(B); + ` + }, + { + title: 'D4 Layer statement with imports', + style: ` + @layer C, B, A; + @import url(basic-green.css) layer(A); + @import url(basic-red.css) layer(B); + @layer C { + target { color: red; } + } + ` + }, + { + title: 'D5 Layer statement with imports', + style: ` + @layer A.B, A.A; + @import url(basic-green.css) layer(A.A); + @import url(layer-B-red.css) layer(A); + ` + }, + { + title: 'D6 Layer statement with imports', + style: ` + @layer B, A; + @import url(layer-A-red.css) layer(A); + @import url(layer-A-red.css) layer(B); + @layer A.B { + target { color: green; } + } + ` + }, + { + title: 'E1 Named imports establish layer even with network errors', + style: ` + @import "nonexist.css" layer(A); + @layer B { + target { color: green; } + } + @layer A { + target { color: red; } + } + `, + }, +]; + +for (let testCase of testCases) { + promise_test(async t => { + const styleElement = document.createElement('style'); + const styleText = testCase['style'].replaceAll(/url\((.+?)\)/g, (match, p1) => { + return `url(data:text/css,${ encodeURI(imports[p1]) })`; + }); + styleElement.textContent = styleText; + + await new Promise(resolve => { + styleElement.onload = resolve; + styleElement.onerror = resolve; + document.head.append(styleElement); + }); + + try { + const targets = document.querySelectorAll('target'); + for (target of targets) + assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)', testCase['title'] + ", target '" + target.classList[0] + "'"); + } finally { + styleElement.remove(); + } + }, testCase['title']); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/layer-important.html b/testing/web-platform/tests/css/css-cascade/layer-important.html new file mode 100644 index 0000000000..23bfd167fd --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-important.html @@ -0,0 +1,107 @@ +<!DOCTYPE html> +<html> +<head> +<title>CSS Cascade Layers: !important </title> +<meta name="assert" content="!important functionality of CSS Cascade Layers"> +<link rel="author" title="Romain Menke" href="mailto:romainmenke@gmail.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#cascade-layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<target class="first"></target> +<target class="second"></target> +<div id="log"></div> +<script> + +// In all test cases, the rule specified as "color: green" should win. +var testCases = [ + { + title: 'A1 Unlayered !important style', + style: ` + target { color: green !important; } + target { color: red; } + ` + }, + { + title: 'B1 Same specificity, layered !important first', + style: ` + @layer { target { color: green !important; } } + target { color: red; } + `, + }, + { + title: 'C1 Same specificity, layered !important second', + style: ` + target { color: red; } + @layer { target { color: green !important; } } + `, + }, + { + title: 'D1 Same specificity, all !important', + style: ` + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + target { color: red !important; } + `, + }, + { + title: 'D2 Same specificity, all !important', + style: ` + @layer { target { color: green !important; } } + target { color: red !important; } + @layer { target { color: red !important; } } + `, + }, + { + title: 'D3 Same specificity, all !important', + style: ` + target { color: red !important; } + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + `, + }, + { + title: 'D4 Same specificity, all !important', + style: ` + @layer A, B; + @layer B { target { color: red !important; } } + @layer A { target { color: green !important; } } + target { color: red !important; } + `, + }, + { + title: 'E1 Different specificity, all !important', + style: ` + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + target.first { color: red !important; } + `, + }, + { + title: 'E2 Different specificity, all !important', + style: ` + @layer { target { color: green !important; } } + @layer { target.first { color: red !important; } } + target { color: red !important; } + `, + }, +]; + +for (let testCase of testCases) { + const styleElement = document.createElement('style'); + styleElement.textContent = testCase['style']; + document.head.append(styleElement); + + test(function () { + var targets = document.querySelectorAll('target'); + for (target of targets) + assert_equals(window.getComputedStyle(target).color, 'rgb(0, 128, 0)', + testCase['title'] + ", target '" + target.classList[0] + "'"); + }, testCase['title']); + + styleElement.remove(); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html b/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html new file mode 100644 index 0000000000..d0f4044f1e --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-keyframes-override.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<title>Resolving @keyframe name conflicts with cascade layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#target, #reference { + width: 100px; + height: 100px; +} + +#reference { + background-color: green; +} +</style> + +<div id="target"></div> +<div id="reference"></div> + +<script> +// In all tests, background color of #target should be green, same as #reference + +const testCases = [ + { + title: '@keyframes unlayered overrides layered', + style: ` + #target { + animation: anim 1s paused; + } + + @keyframes anim { + from { background-color: green; } + } + + @layer { + @keyframes anim { + from { background-color: red; } + } + } + ` + }, + + { + title: '@keyframes override between layers', + style: ` + @layer base, override; + + #target { + animation: anim 1s paused; + } + + @layer override { + @keyframes anim { + from { background-color: green; } + } + } + + @layer base { + @keyframes anim { + from { background-color: red; } + } + } + ` + }, + + { + title: '@keyframes override update with appended sheet 1', + style: ` + @layer base, override; + + #target { + animation: anim 1s paused; + } + + @layer override { + @keyframes anim { + from { background-color: green; } + } + } + `, + append: ` + @layer base { + @keyframes anim { + from { background-color: red; } + } + } + ` + }, + + { + title: '@keyframes override update with appended sheet 2', + style: ` + @layer base, override; + + #target { + animation: anim 1s paused; + } + + @layer base { + @keyframes anim { + from { background-color: red; } + } + } + `, + append: ` + @layer override { + @keyframes anim { + from { background-color: green; } + } + } + ` + }, +]; + +for (let testCase of testCases) { + var documentStyle = document.createElement('style'); + documentStyle.appendChild(document.createTextNode(testCase['style'])); + document.head.appendChild(documentStyle); + + var appendedStyle; + if (testCase['append']) { + document.body.offsetLeft; // Force style update + appendedStyle = document.createElement('style'); + appendedStyle.appendChild(document.createTextNode(testCase['append'])); + document.head.appendChild(appendedStyle); + } + + test(function () { + assert_equals(getComputedStyle(target).backgroundColor, + getComputedStyle(reference).backgroundColor); + }, testCase['title']); + + if (appendedStyle) + appendedStyle.remove(); + documentStyle.remove(); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-media-query.html b/testing/web-platform/tests/css/css-cascade/layer-media-query.html new file mode 100644 index 0000000000..92a0f55a6a --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-media-query.html @@ -0,0 +1,169 @@ +<!DOCTYPE html> +<html> +<head> +<title>CSS Cascade Layers: Media queries</title> +<meta name="assert" content="CSS Cascade Layers nested in Media Queries"> +<link rel="author" title="Antti Koivisto" href="mailto:antti@apple.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<iframe width=300 height=300 frameborder=0></iframe> +<div id="log"></div> +<script> + +const imports = { + "basic-green.css": ` + target { color: green; } + `, + "basic-red.css": ` + target { color: red; } + `, + "empty.css": "", +}; + +// For 300px wide iframe the target should be red and for 500px green. +const testCases = [ + { + title: 'A1 Basic', + style: ` + @layer { target { color: red } } + @media (min-width: 500px) { + @layer { + target { color: green; } + } + } + ` + }, + { + title: 'A2 Basic', + style: ` + @media (min-width: 500px) { + @layer { + target { color: green; } + } + } + @media (max-width: 300px) { + @layer { + target { color: red; } + } + } + ` + }, + { + title: 'B1 Basic import', + style: ` + @import url(basic-red.css) layer; + @import url(basic-green.css) layer (min-width: 500px); + ` + }, + { + title: 'B2 Basic import', + style: ` + @import url(basic-green.css) layer (min-width: 500px); + @import url(basic-red.css) layer (max-width: 300px); + ` + }, + { + title: 'C1 Reordering', + style: ` + @media (max-width: 300px) { + @layer B { + target { color: green; } + } + @layer A { + target { color: red; } + } + } + @media (min-width: 500px) { + @layer A { + target { color: red; } + } + @layer B { + target { color: green; } + } + } + ` + }, + { + title: 'C2 Reordering', + style: ` + @media (max-width: 300px) { + @layer B { } + @layer A { target { color: red; } } + } + @media (min-width: 500px) { + @layer A { target { color: red; } } + @layer B { } + } + @layer B { + target { color: green; } + } + ` + }, + { + title: 'C3 Reordering', + style: ` + @media (max-width: 300px) { + @layer B, A; + } + @media (min-width: 500px) { + @layer A, B; + } + @layer A { + target { color: red; } + } + @layer B { + target { color: green; } + } + ` + }, + { + title: 'C4 Reordering', + style: ` + @import url(empty.css) layer(B) (max-width: 300px); + @import url(empty.css) layer(A) (max-width: 300px); + @import url(empty.css) layer(A) (min-width: 500px); + @import url(empty.css) layer(B) (min-width: 500px); + @layer A { + target { color: red; } + } + @layer B { + target { color: green; } + } + ` + }, +]; + +let iframe = document.querySelector("iframe"); + +for (let testCase of testCases) { + promise_test(async t => { + const styleText = testCase['style'].replaceAll(/url\((.+?)\)/g, (match, p1) => { + return `url(data:text/css,${ encodeURI(imports[p1]) })`; + }); + + iframe.width = 300; + + await new Promise(resolve => { + iframe.onload = resolve; + iframe.srcdoc = ` + <style> + ${styleText} + </style> + <target></target> + `; + }); + + const target = iframe.contentDocument.querySelector('target'); + assert_equals(getComputedStyle(target).color, 'rgb(255, 0, 0)', testCase['title']); + + iframe.width = 500; + + assert_equals(getComputedStyle(target).color, 'rgb(0, 128, 0)', testCase['title']); + }, testCase['title']); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html b/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html new file mode 100644 index 0000000000..83a037a2bd --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-media-toggle.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: Tests against a Chrome bug that modifying a sheet affects existing layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1313357"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +@layer foo, bar; +@layer bar { + #target { background-color: green; } +} +@layer foo { + #target { background-color: red; } +} +</style> +<style media="print" id="toggle"> +#target { + width: 100px; + height: 100px; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> + +<script> +document.body.offsetWidth; // Force style calculation +document.getElementById('toggle').media = 'all'; +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-property-override.html b/testing/web-platform/tests/css/css-cascade/layer-property-override.html new file mode 100644 index 0000000000..9d3f9cb926 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-property-override.html @@ -0,0 +1,154 @@ +<!DOCTYPE html> +<title>Resolving @property name conflicts with cascade layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#target, #reference { + width: 100px; + height: 100px; +} + +#reference { + background-color: green; +} +</style> + +<div id="target"></div> +<div id="reference"></div> + +<script> +// In all tests, background color of #target should be green, same as #reference + +const testCases = [ + { + title: '@property unlayered overrides layered', + style: ` + #target { + background-color: var(--foo); + } + + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + + @layer { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + ` + }, + + { + title: '@property override between layers', + style: ` + @layer base, override; + + #target { + background-color: var(--foo); + } + + @layer override { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + } + + @layer base { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + ` + }, + + { + title: '@property override update with appended sheet 1', + style: ` + @layer base, override; + + #target { + background-color: var(--foo); + } + + @layer override { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + } + `, + append: ` + @layer base { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + ` + }, + + { + title: '@property override update with appended sheet 2', + style: ` + @layer base, override; + + #target { + background-color: var(--foo); + } + + @layer base { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: red; + } + } + `, + append: ` + @layer override { + @property --foo { + syntax: '<color>'; + inherits: false; + initial-value: green; + } + } + ` + }, +]; + +for (let testCase of testCases) { + var documentStyle = document.createElement('style'); + documentStyle.appendChild(document.createTextNode(testCase['style'])); + document.head.appendChild(documentStyle); + + var appendedStyle; + if (testCase['append']) { + document.body.offsetLeft; // Force style update + appendedStyle = document.createElement('style'); + appendedStyle.appendChild(document.createTextNode(testCase['append'])); + document.head.appendChild(appendedStyle); + } + + test(function () { + assert_equals(getComputedStyle(target).backgroundColor, + getComputedStyle(reference).backgroundColor); + }, testCase['title']); + + if (appendedStyle) + appendedStyle.remove(); + documentStyle.remove(); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html b/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html new file mode 100644 index 0000000000..c9d88681bd --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-replaceSync-clears-stale.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: CSSStyleSheet.replaceSync clears stale statements</title> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="target"></div> +<div id="reference" style="color: green"></div> + +<script> +// In all test cases, the 'color' property value of #target should be green. + +const testCases = [ + { + title: 'replaceSync clears stale layer statements', + style: ` + @layer second, first; + @layer second { + #target { color: green; } + } + @layer first { + #target { color: red; } + } + `, + operations: function(sheet) { + sheet.replaceSync(` + @layer first { + #target { color: red; } + } + @layer second { + #target { color: green; } + } + `); + } + }, +]; + +const target = document.getElementById('target'); +const reference = document.getElementById('reference'); + +for (let testCase of testCases) { + test(t => { + let sheet = new CSSStyleSheet(); + sheet.replaceSync(testCase.style); + document.adoptedStyleSheets = [sheet]; + + try { + testCase.operations(sheet); + assert_equals(getComputedStyle(target).color, getComputedStyle(reference).color); + } finally { + document.adoptedStyleSheets = []; + } + }, testCase.title); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html b/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html new file mode 100644 index 0000000000..b81960df6f --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-rules-cssom.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<title>The CSSOM API for Cascade Layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layer-apis"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const testCases = [ + { + style: `@layer foo { }`, + expectedNames: ['foo'], + title: 'Basic layer block name', + }, + { + style: `@layer { }`, + expectedNames: [''], + title: 'Anonymous layer block name', + }, + { + style: ` + @layer foo; + `, + expectedNames: [['foo']], + title: 'Basic layer statement name', + }, + { + style: ` + @layer foo, bar; + `, + expectedNames: [['foo', 'bar']], + title: 'Layer statement with multiple names', + }, + { + style: ` + @layer outer { + @layer foo.bar { } + } + @layer outer.foo.bar { } + `, + expectedNames: ['outer', 'foo.bar', 'outer.foo.bar'], + title: 'Nested layer block names', + }, + { + style: ` + @layer outer { + @layer foo.bar, baz; + } + @layer outer.foo.bar, outer.baz; + `, + expectedNames: ['outer', ['foo.bar', 'baz'], ['outer.foo.bar', 'outer.baz']], + title: 'Nested layer statement name lists', + }, + { + style: ` + @import url('data:text/css,') layer; + `, + expectedNames: [''], + title: 'Import into anonymous layer', + }, + { + style: ` + @import url('data:text/css,') layer(foo); + `, + expectedNames: ['foo'], + title: 'Import into named layer', + }, + { + style: ` + @import url('data:text/css,'); + `, + expectedNames: [null], + title: 'Import without layer', + }, +]; + +for (let testCase of testCases) { + promise_test(async function (t) { + assert_implements(window.CSSLayerBlockRule); + assert_implements(window.CSSLayerStatementRule); + + const style = document.createElement('style'); + t.add_cleanup(() => style.remove()); + + const isLoadAsync = testCase.style.includes("@import"); + const load = new Promise(resolve => { + style.addEventListener("load", resolve, { once: true }); + }); + + style.appendChild(document.createTextNode(testCase.style)); + document.head.appendChild(style); + + if (isLoadAsync) { + await load; + } + + let index = 0; + function compareNames(ruleOrSheet) { + if (ruleOrSheet instanceof CSSLayerBlockRule) + assert_equals(ruleOrSheet.name, testCase.expectedNames[index++]); + else if (ruleOrSheet instanceof CSSImportRule) + assert_equals(ruleOrSheet.layerName, testCase.expectedNames[index++]); + else if (ruleOrSheet instanceof CSSLayerStatementRule) + assert_array_equals(ruleOrSheet.nameList, testCase.expectedNames[index++]); + if (ruleOrSheet.cssRules) { + for (let i = 0; i < ruleOrSheet.cssRules.length; ++i) + compareNames(ruleOrSheet.cssRules.item(i)); + } + } + compareNames(style.sheet); + assert_equals(index, testCase.expectedNames.length); + }, testCase.title); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html b/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html new file mode 100644 index 0000000000..a33a5a6787 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-slotted-rule.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>::slotted rules should be associated with the correct cascade layers</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> +<style> +#target { + width: 100px; + height: 100px; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="host"> + <div id="target"></target> +</div> + +<script> +const host = document.getElementById('host'); +host.attachShadow({mode: 'open'}).innerHTML = ` +<style> +@layer { + ::slotted(*) { + background-color: green !important; + } +} +::slotted(*) { + background-color: red !important; +} +</style> +<slot></slot> +`; +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html b/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html new file mode 100644 index 0000000000..fcde960532 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-statement-before-import.html @@ -0,0 +1,157 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: Empty layer statements before import rules</title> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="target"></div> +<div id="reference" style="color: green"></div> + +<script> +// In all test cases, the 'color' property value of #target should be green. + +const testCases = [ + { + title: 'length and item', + style: ` + @layer first, second; + @import url(data:text/css,); + @layer second { + #target { color: green; } + } + @layer first { + #target { color: red; } + } + `, + operations: function(sheet) { + assert_equals(sheet.cssRules.length, 4); + assert_equals(sheet.cssRules.item(0).cssText, '@layer first, second;'); + assert_equals(sheet.cssRules.item(1).cssText, `@import url("data:text/css,");`); + assert_equals(sheet.cssRules.item(2).cssText, + '@layer second {\n #target { color: green; }\n}'); + assert_equals(sheet.cssRules.item(3).cssText, + '@layer first {\n #target { color: red; }\n}'); + } + }, + { + title: 'insertRule before imports', + style: ` + @import url(data:text/css,); + @layer second { + #target { color: green; } + } + @layer first { + #target { color: red; } + } + `, + operations: function(sheet) { + sheet.insertRule('@layer first, second', 0); + } + }, + { + title: 'insertRule after imports', + style: ` + @layer first, second; + @import url(data:text/css,); + @layer first { + #target { color: red; } + } + `, + operations: function(sheet) { + sheet.insertRule('@layer second { #target { color: green; } }', 2); + } + }, + { + title: 'insert other rules to pre-import layer statements fails', + style: ` + @layer first, second; + @import url(data:text/css,); + @layer second { + #target { color: green; } + } + @layer first { + #target { color: red; } + } + `, + operations: function(sheet) { + assert_throws_dom('HierarchyRequestError', + () => sheet.insertRule('#target { color: red !important; }', 0)); + assert_throws_dom('HierarchyRequestError', + () => sheet.insertRule('#target { color: red !important; }', 1)); + } + }, + { + title: 'insert other rules before the first layer statement without imports', + style: ` + @layer first, second; + @layer second { + #target { color: red !important; } + } + `, + operations: function(sheet) { + sheet.insertRule(`@layer first { + #target { color: green !important; } + }`, 0); + } + }, + { + title: 'deleteRule before imports', + style: ` + @layer second, first; + @import url(data:text/css,); + @layer first { + #target { color: red; } + } + @layer second { + #target { color: green; } + } + `, + operations: function(sheet) { + sheet.deleteRule(0); + } + }, + { + title: 'deleteRule after imports', + style: ` + @layer first, second; + @import url(data:text/css,); + @layer second { + #target { color: green; } + } + @layer first { + #target { color: red; } + } + #target { + color: red; + } + `, + operations: function(sheet) { + sheet.deleteRule(4); + } + }, +]; + +const target = document.getElementById('target'); +const reference = document.getElementById('reference'); + +for (let testCase of testCases) { + promise_test(async t => { + let styleElement = document.createElement('style'); + styleElement.textContent = testCase.style; + await new Promise(resolve => { + styleElement.onload = resolve; + styleElement.onerror = resolve; + document.head.append(styleElement); + }); + let sheet = styleElement.sheet; + + try { + testCase.operations(sheet); + assert_equals(getComputedStyle(target).color, getComputedStyle(reference).color); + } finally { + styleElement.remove(); + } + }, testCase.title); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html b/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html new file mode 100644 index 0000000000..f183ab30ab --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-statement-copy-crash.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<title>Chromium bug: Crash when copying layer statement rule from memory cache</title> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#layer-empty"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1345181"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="stylesheet" href="data:text/css,@layer foo;@media(all){}"> +<link rel="stylesheet" href="data:text/css,@layer foo;@media(all){}"> +<body> + <p style="color: green">Test passes if it does not crash.</p> +</body> diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html new file mode 100644 index 0000000000..7b3ff4abc6 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-important.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>Test important style in anonymous layers with stylesheet sharing</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="layer-stylesheet-sharing-ref.html"> +<style> + target { + display: block; + width: 100px; + height: 100px; + } +</style> +<link rel=stylesheet href="data:text/css,@layer{target{background-color:green !important}}"> +<style> +@layer A { target { background-color: red !important} } +</style> +<link rel=stylesheet href="data:text/css,@layer{target{background-color:green !important}}"> +<target></target>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html new file mode 100644 index 0000000000..fe004e5bda --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing-ref.html @@ -0,0 +1,10 @@ +<!doctype html> +<style> + target { + display: block; + width: 100px; + height: 100px; + background-color: green; + } +</style> +<target></target> diff --git a/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html new file mode 100644 index 0000000000..c172baaf81 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-stylesheet-sharing.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>Test anonymous layers with stylesheet sharing</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1730123"> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="match" href="layer-stylesheet-sharing-ref.html"> +<style> + target { + display: block; + width: 100px; + height: 100px; + } +</style> +<link rel=stylesheet href="data:text/css,@layer{target{background-color:green}}"> +<style> +@layer A { target { background-color: red } } +</style> +<link rel=stylesheet href="data:text/css,@layer{target{background-color:green}}"> +<target></target> diff --git a/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html b/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html new file mode 100644 index 0000000000..9ddfbc3907 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/layer-vs-inline-style.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#cascade-sort"> +<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#target, #reference { + width: 100px; + height: 100px; +} + +#reference { + background-color: green; +} +</style> + +<div id="target"></div> +<div id="reference"></div> + +<script> +// In all tests, #target should have green background color, same as #reference + +const testCases = [ + { + title: 'Normal inline style > normal layered style', + style: '@layer { #target { background-color: red; }}', + inlineStyle: 'background-color: green' + }, + { + title: 'Normal inline style < important layered style', + style: '@layer { #target { background-color: green !important; }}', + inlineStyle: 'background-color: red' + }, + { + title: 'Important inline style > normal layered style', + style: '@layer { #target { background-color: red; }}', + inlineStyle: 'background-color: green !important' + }, + { + title: 'Important inline style > important layered style', + style: '@layer { #target { background-color: red !important; }}', + inlineStyle: 'background-color: green !important' + }, +]; + +for (let testCase of testCases) { + var documentStyle = document.createElement('style'); + documentStyle.appendChild(document.createTextNode(testCase['style'])); + document.head.appendChild(documentStyle); + + target.style = testCase['inlineStyle']; + + test(function () { + assert_equals(getComputedStyle(target).backgroundColor, + getComputedStyle(reference).backgroundColor); + }, testCase['title']); + + documentStyle.remove(); +} +</script> diff --git a/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html b/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html new file mode 100644 index 0000000000..4a1d045ecc --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/parsing/all-invalid.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Cascading and Inheritance Level 3: parsing all with invalid values</title> +<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-3/#propdef-all"> +<meta name="assert" content="all supports only the grammar 'initial | inherit | unset'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +</head> +<body> +<script> +test_invalid_value("all", "auto"); +test_invalid_value("all", "none"); +test_invalid_value("all", "filter"); +test_invalid_value("all", "unset inherit"); +test_invalid_value("all", "inherit initial"); +test_invalid_value("all", "initial unset"); +test_invalid_value("all", "opacity transform"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html b/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html new file mode 100644 index 0000000000..3a9e5922de --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/parsing/all-valid.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Cascading and Inheritance Level 3: parsing all with valid values</title> +<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-3/#propdef-all"> +<meta name="assert" content="all supports the full grammar 'initial | inherit | unset'."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +</head> +<body> +<script> +test_valid_value("all", "initial"); +test_valid_value("all", "inherit"); +test_valid_value("all", "unset"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html b/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html new file mode 100644 index 0000000000..f879ba8897 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/parsing/layer-import-parsing.html @@ -0,0 +1,79 @@ +<!doctype html> +<meta charset="utf-8"> +<title>@import rule with layer parsing / serialization</title> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#at-import"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + function setupSheet(rule) { + const style = document.createElement("style"); + document.head.append(style); + const {sheet} = style; + const {cssRules} = sheet; + + assert_equals(cssRules.length, 0, "Sheet should have no rules"); + sheet.insertRule(rule); + assert_equals(cssRules.length, 1, "Sheet should have 1 rule"); + + return {sheet, cssRules}; + } + + function test_valid_layer_import(rule, serialized) { + if (serialized === undefined) + serialized = rule; + + test(function() { + const {sheet, cssRules} = setupSheet(rule); + + const serialization = cssRules[0].cssText; + assert_equals(serialization, serialized, 'serialization should be canonical'); + + const media = cssRules[0].media; + assert_equals(media.length, 0, 'layer() should be valid'); + + sheet.deleteRule(0); + assert_equals(cssRules.length, 0, 'Sheet should have no rule'); + sheet.insertRule(serialization); + assert_equals(cssRules.length, 1, 'Sheet should have 1 rule'); + + assert_equals(cssRules[0].cssText, serialization, 'serialization should round-trip'); + }, rule + ' should be a valid layered import rule'); + } + + function test_invalid_layer_import(rule) { + test(function() { + const {sheet, cssRules} = setupSheet(rule); + + const media = cssRules[0].media; + assert_not_equals(media.length, 0, + 'invalid layer declaration should be parsed as <general-enclosed> media query'); + + sheet.deleteRule(0); + assert_equals(cssRules.length, 0, 'Sheet should have no rule'); + }, rule + ' should still be a valid import rule with an invalid layer declaration'); + } + + test_valid_layer_import('@import url("nonexist.css") layer;'); + test_valid_layer_import('@import url("nonexist.css") layer(A);'); + test_valid_layer_import('@import url("nonexist.css") layer(A.B);'); + + test_valid_layer_import('@import url(nonexist.css) layer;', + '@import url("nonexist.css") layer;'); + test_valid_layer_import('@import url(nonexist.css) layer(A);', + '@import url("nonexist.css") layer(A);'); + test_valid_layer_import('@import url(nonexist.css) layer(A.B);', + '@import url("nonexist.css") layer(A.B);'); + + test_valid_layer_import('@import "nonexist.css" layer;', + '@import url("nonexist.css") layer;'); + test_valid_layer_import('@import "nonexist.css" layer(A);', + '@import url("nonexist.css") layer(A);'); + test_valid_layer_import('@import "nonexist.css" layer(A.B);', + '@import url("nonexist.css") layer(A.B);'); + + test_invalid_layer_import('@import url("nonexist.css") layer();'); + test_invalid_layer_import('@import url("nonexist.css") layer(A B);'); + test_invalid_layer_import('@import url("nonexist.css") layer(A . B);'); + test_invalid_layer_import('@import url("nonexist.css") layer(A, B, C);'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/parsing/layer.html b/testing/web-platform/tests/css/css-cascade/parsing/layer.html new file mode 100644 index 0000000000..3bfc863ede --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/parsing/layer.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset="utf-8"> +<title>@layer rule parsing / serialization</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/parsing-testcommon.js"></script> +<script> + test_valid_rule("@layer A;"); + test_valid_rule("@layer A, B, C;"); + test_valid_rule("@layer A.A;"); + test_valid_rule("@layer A, B.C.D, C;"); + + test_invalid_rule("@layer;"); + test_invalid_rule("@layer A . A;"); + + test_valid_rule("@layer {\n}"); + test_valid_rule("@layer A {\n}"); + test_valid_rule("@layer A.B {\n}"); + test_invalid_rule("@layer A . B {\n}"); + + test_invalid_rule("@layer A, B, C {\n}"); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html b/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html new file mode 100644 index 0000000000..64cf930e6c --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/parsing/supports-import-parsing.html @@ -0,0 +1,78 @@ +<!doctype html> +<meta charset="utf-8"> +<title>@import rule with supports parsing / serialization</title> +<link rel="author" href="mailto:oj@oojmed.com"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-4/#at-import"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + function setupSheet(rule) { + const style = document.createElement("style"); + document.head.append(style); + const {sheet} = style; + const {cssRules} = sheet; + + assert_equals(cssRules.length, 0, "Sheet should have no rules"); + sheet.insertRule(rule); + assert_equals(cssRules.length, 1, "Sheet should have 1 rule"); + + return {sheet, cssRules}; + } + + function test_valid_supports_import(rule, serialized) { + if (serialized === undefined) + serialized = rule; + + test(function() { + const {sheet, cssRules} = setupSheet(rule); + + const serialization = cssRules[0].cssText; + assert_equals(serialization, serialized, 'serialization should be canonical'); + + sheet.deleteRule(0); + assert_equals(cssRules.length, 0, 'Sheet should have no rule'); + sheet.insertRule(serialization); + assert_equals(cssRules.length, 1, 'Sheet should have 1 rule'); + + assert_equals(cssRules[0].cssText, serialization, 'serialization should round-trip'); + }, rule + ' should be a valid supports() import rule'); + } + + function test_invalid_supports_import(rule) { + test(function() { + const {sheet, cssRules} = setupSheet(rule); + + sheet.deleteRule(0); + assert_equals(cssRules.length, 0, 'Sheet should have no rule'); + }, rule + ' should still be a valid import rule with an invalid supports() declaration'); + } + + test_valid_supports_import('@import url("nonexist.css") supports();'); + test_valid_supports_import('@import url("nonexist.css") supports(display:block);'); + test_valid_supports_import('@import url("nonexist.css") supports((display:flex));'); + test_valid_supports_import('@import url("nonexist.css") supports(not (display: flex));'); + test_valid_supports_import('@import url("nonexist.css") supports((display: flex) and (display: block));'); + test_valid_supports_import('@import url("nonexist.css") supports((display: flex) or (display: block));'); + test_valid_supports_import('@import url("nonexist.css") supports((display: flex) or (foo: bar));'); + test_valid_supports_import('@import url("nonexist.css") supports(display: block !important);'); + + test_valid_supports_import('@import url("nonexist.css") layer supports();'); + test_valid_supports_import('@import url("nonexist.css") layer(A) supports((display: flex) or (foo: bar));'); + test_valid_supports_import('@import url("nonexist.css") layer(A.B) supports((display: flex) and (foo: bar));'); + + test_valid_supports_import('@import url("nonexist.css") supports(selector(a));'); + test_valid_supports_import('@import url("nonexist.css") supports(selector(p a));'); + test_valid_supports_import('@import url("nonexist.css") supports(selector(p > a));'); + test_valid_supports_import('@import url("nonexist.css") supports(selector(p + a));'); + + test_valid_supports_import('@import url("nonexist.css") supports(font-tech(color-colrv1));'); + test_valid_supports_import('@import url("nonexist.css") supports(font-format(opentype));'); + + test_valid_supports_import('@import url(nonexist.css) supports(display:block);', + '@import url("nonexist.css") supports(display:block);'); + + test_valid_supports_import('@import "nonexist.css" supports(display:block);', + '@import url("nonexist.css") supports(display:block);'); + + test_invalid_supports_import('@import url("nonexist.css") supports;'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html b/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html new file mode 100644 index 0000000000..c3188fd0d7 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/presentational-hints-cascade.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#preshint"> +<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +.test { + height: 100px; + background-color: green; +} + +#target1 { + width: 100px; +} + +@layer { + #target3 { + width: 100px; + } +} +</style> + +<img class=test id=target1 width=200> +<img class=test id=target2 width=200 style="width: 100px"> +<img class=test id=target3 width=200> + +<script> +test(() => { + assert_equals(getComputedStyle(target1).width, '100px'); +}, 'Presentational hints have lower precedence than regular author style sheets'); + +test(() => { + assert_equals(getComputedStyle(target2).width, '100px'); +}, 'Presentational hints have lower precedence than the style attribute'); + +test(() => { + assert_equals(getComputedStyle(target3).width, '100px'); +}, 'Presentational hints have lower precedence than layered style'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html b/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html new file mode 100644 index 0000000000..8178daf60c --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/presentational-hints-rollback.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: rolling back the cascade with presentation hints</title> +<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#preshint"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#default"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-5/#revert-layer"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content-other.html#dimension-attributes"> +<meta name="assert" content="Checks that 'revert' considers presentational hints as part of the author origin, + and 'revert-layer' considers them an independent origin between the user origin and the author origin."> + +<style> +@layer { + .revert-1 { + width: revert; + height: revert; + } + .revert-layer-1 { + width: revert-layer; + height: revert-layer; + } +} + +.revert-2 { + width: revert; + height: revert; +} +.revert-layer-2 { + width: revert-layer; + height: revert-layer; +} + +.revert-3 { + animation: revert-3 paused 2s -1s; +} +.revert-layer-3 { + animation: revert-layer-3 paused 2s -1s; +} +@keyframes revert-3 { + from, to { + width: revert; + height: revert; + } +} +@keyframes revert-layer-3 { + from, to { + width: revert-layer; + height: revert-layer; + } +} +</style> + +<div id="log"></div> + +<div id="tests"> + <!-- 'revert' considers presentational hints as part of the author origin, so it rolls back to user origin. + The images should then get an 'auto' size, which will use the natural size of the resource. --> + <img class="revert-1" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="60" + height="33" data-expected-client-height="60"> + <img class="revert-2" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="60" + height="33" data-expected-client-height="60"> + <img class="revert-3" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="60" + height="33" data-expected-client-height="60"> + <img style="width: revert; height: revert" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="60" + height="33" data-expected-client-height="60"> + + <!-- 'revert-layer' considers presentational hints as an independent origin, so it rolls back to them. + The images should then get size specified in the attributes. --> + <img class="revert-layer-1" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"> + <img class="revert-layer-2" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"> + <img class="revert-layer-3" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"> + <img style="width: revert-layer; height: revert-layer" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"> + + <!-- 'revert' considers presentational hints as part of the author origin, so it rolls back to user origin. + The iframes should then get an 'auto' size, which will default to 300x150. --> + <iframe class="revert-1" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="300" + height="33" data-expected-client-height="150"></iframe> + <iframe class="revert-2" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="300" + height="33" data-expected-client-height="150"></iframe> + <iframe class="revert-3" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="300" + height="33" data-expected-client-height="150"></iframe> + <iframe style="width: revert; height: revert" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="300" + height="33" data-expected-client-height="150"></iframe> + + <!-- 'revert-layer' considers presentational hints as an independent origin, so it rolls back to them. + The iframes should then get size specified in the attributes. --> + <iframe class="revert-layer-1" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"></iframe> + <iframe class="revert-layer-2" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"></iframe> + <iframe class="revert-layer-3" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"></iframe> + <iframe style="width: revert-layer; height: revert-layer" src="/css/support/60x60-green.png" + width="44" data-expected-client-width="44" + height="33" data-expected-client-height="33"></iframe> +</div> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/check-layout-th.js"></script> +<script> +addEventListener("load", function() { + checkLayout("#tests > *", false); + done(); +}, {once: true}); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/reference/all-green.html b/testing/web-platform/tests/css/css-cascade/reference/all-green.html new file mode 100644 index 0000000000..c70532129a --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/reference/all-green.html @@ -0,0 +1 @@ +<html style="background: green"></html>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht b/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht new file mode 100644 index 0000000000..05a1379448 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/reference/ref-filled-green-100px-square.xht @@ -0,0 +1,19 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>CSS Reftest Reference</title> + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/" /> + <style type="text/css"><![CDATA[ + div + { + background-color: green; + height: 100px; + width: 100px; + } + ]]></style> + </head> + <body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + <div></div> + </body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html b/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html new file mode 100644 index 0000000000..8183c04087 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/reference/ref-green-text.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Green text reference</title> +<style> + .test { color: green; } +</style> +<body> + <p class="test">Test passes if this text is green.</p> +</body> diff --git a/testing/web-platform/tests/css/css-cascade/resources/scope.css b/testing/web-platform/tests/css/css-cascade/resources/scope.css new file mode 100644 index 0000000000..780e90fb67 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/resources/scope.css @@ -0,0 +1,4 @@ +@scope { + :scope { z-index:1; } + .a { z-index:2; } +} diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-001.html b/testing/web-platform/tests/css/css-cascade/revert-layer-001.html new file mode 100644 index 0000000000..009867e751 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-001.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' from one explicit layer to another</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; +} + +@layer { + #target { background-color: green; } +} + +@layer { + #target { + background-color: red; + background-color: revert-layer; + } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-002.html b/testing/web-platform/tests/css/css-cascade/revert-layer-002.html new file mode 100644 index 0000000000..38d3d33d93 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-002.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' from the implicit outer layer to explicit</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; +} + +@layer { + #target { background-color: green; } +} + +#target { + background-color: red; + background-color: revert-layer; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-003.html b/testing/web-platform/tests/css/css-cascade/revert-layer-003.html new file mode 100644 index 0000000000..e4e331c82d --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-003.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'all: revert-layer'</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +@layer { + #target { + width: 100px; + height: 100px; + background-color: green; + } +} + +@layer { + #target { + width: 200px; + height: 200px; + background-color: red; + } + + #target { + all: revert-layer; + } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-004.html b/testing/web-platform/tests/css/css-cascade/revert-layer-004.html new file mode 100644 index 0000000000..b751359857 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-004.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' to previous context</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; + background-color: red; + background-color: revert-layer; +} + +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> + +<script> +target.attachShadow({mode: 'open'}).innerHTML = ` +<style> +:host { + background-color: green; +} +</style> +`; +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-005.html b/testing/web-platform/tests/css/css-cascade/revert-layer-005.html new file mode 100644 index 0000000000..6cd4030727 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-005.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: important 'revert-layer'</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; +} + +@layer { + #target { background-color: green; } +} + +@layer { + #target { + background-color: red; + background-color: red !important; + background-color: revert-layer !important; + } +} + +@layer { + #target { + background-color: red; + background-color: red !important; + } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-006.html b/testing/web-platform/tests/css/css-cascade/revert-layer-006.html new file mode 100644 index 0000000000..678c3e1e9b --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-006.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' reverts origin when no lower priority declarations in the same origin</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#outer { + background-color: red; + width: 100px; + height: 100px; + overflow: hidden; +} +#inner { + color: green; + background-color: green; + display: inline; + display: revert-layer; /* This should behave as 'revert', setting 'display' to 'block' */ +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="outer"> + <div id="inner"> + This<br> + is<br> + filler<br> + text.<br> + This<br> + is<br> + filler<br> + text. + </div> +</div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-007.html b/testing/web-platform/tests/css/css-cascade/revert-layer-007.html new file mode 100644 index 0000000000..7915beeafa --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-007.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' chain</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; +} + +@layer { + #target { background-color: green; } +} + +@layer { + #target { + background-color: red; + background-color: revert-layer; + } +} + +@layer { + #target { + background-color: red; + background-color: revert-layer; + } +} + +@layer { + #target { + background-color: red; + background-color: revert-layer; + } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-008.html b/testing/web-platform/tests/css/css-cascade/revert-layer-008.html new file mode 100644 index 0000000000..cafb17dee1 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-008.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' triggers a smooth transition</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<style> +@layer revert-to, revert-from; + +@layer revert-from { + #target { + font-size: 10px; + transition: font-size 2s linear -1s; + } + + #target.reverted { + font-size: revert-layer; + } +} + +@layer revert-to { + #target { font-size: 20px; } +} +</style> + +<div id="target"></div> + +<script> +function raf() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} +promise_test(async () => { + const target = document.getElementById('target'); + getComputedStyle(target).getPropertyValue('font-size'); + + await raf(); + target.classList.toggle('reverted'); + + const result = getComputedStyle(target).getPropertyValue('font-size'); + assert_equals(result, '15px'); +}, "'revert-layer' should revert font-size to 20px and trigger a smooth transition"); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-009.html b/testing/web-platform/tests/css/css-cascade/revert-layer-009.html new file mode 100644 index 0000000000..e5c8e62ae0 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-009.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' from the style attribute to other style sheets</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; + background-color: green; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target" style="background-color: red; background-color: revert-layer"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-010.html b/testing/web-platform/tests/css/css-cascade/revert-layer-010.html new file mode 100644 index 0000000000..278905c6cf --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-010.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' from animation origin to author origin</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 150px; + height: 100px; + background-color: green; + animation: anim linear 2s -1s paused; +} + +@keyframes anim { + from { width: 50px; } + to { width: revert-layer; } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-011.html b/testing/web-platform/tests/css/css-cascade/revert-layer-011.html new file mode 100644 index 0000000000..73a3772f80 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-011.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' from animation origin to author origin on custom property</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: var(--x); + --x: 150px; + height: 100px; + background-color: green; + animation: anim linear 2s -1s paused; +} + +@property --x { + syntax: '<length>'; + initial-value: 0px; + inherits: false; +} + +@keyframes anim { + from { --x: 50px; } + to { --x: revert-layer; } +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-012.html b/testing/web-platform/tests/css/css-cascade/revert-layer-012.html new file mode 100644 index 0000000000..e065defb53 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-012.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: important 'revert-layer' from the style attribute to other style sheets</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; + background-color: green !important; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target" style="background-color: red !important; background-color: revert-layer !important"></div> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-013.html b/testing/web-platform/tests/css/css-cascade/revert-layer-013.html new file mode 100644 index 0000000000..862ee72746 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-013.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' to host context</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; + background-color: revert-layer; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="target"></div> + +<script> +target.attachShadow({mode: 'open'}).innerHTML = ` +<style> +@layer first { + :host { background-color: green; } +} +@layer second { + :host { background-color: revert-layer; } +} +</style> +`; +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-014.html b/testing/web-platform/tests/css/css-cascade/revert-layer-014.html new file mode 100644 index 0000000000..6b96862562 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-014.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' in slotted context</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> + +<style> +#target { + width: 100px; + height: 100px; + background-color: revert-layer; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div id="host"><div id="target"></div></div> + +<script> +host.attachShadow({mode: 'open'}).innerHTML = ` +<style> +@layer first { + ::slotted(*) { background-color: green; } +} +@layer second { + ::slotted(*) { background-color: revert-layer; } +} +</style> +<slot></slot> +`; +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html b/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html new file mode 100644 index 0000000000..661016619c --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-015-ref.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<style> +input::placeholder { background-color: green; } +</style> +<input placeholder="placeholder"> diff --git a/testing/web-platform/tests/css/css-cascade/revert-layer-015.html b/testing/web-platform/tests/css/css-cascade/revert-layer-015.html new file mode 100644 index 0000000000..a60f5d78f5 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-layer-015.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>CSS Cascade Layers: 'revert-layer' with shadow pseudo-element</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#revert-layer"> +<link rel="match" href="revert-layer-015-ref.html"> + +<style> +@layer first { + input::placeholder { background-color: green; } +} +@layer second { + input::placeholder { background-color: revert-layer; } +} +</style> +<input placeholder="placeholder"> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-001.html b/testing/web-platform/tests/css/css-cascade/revert-val-001.html new file mode 100644 index 0000000000..b3d79d9e69 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-001.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: 'revert' keyword for 'display' property of div element</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="help" href="https://www.w3.org/TR/css-cascade-4/#default"> + <link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="On a <div>, display:revert should compute to display:block per the default styles for <div>s in the UA stylesheet."> + <style> +#outer { + background-color: red; + width: 100px; + height: 100px; + overflow: hidden; +} +#inner { + color: green; + background-color: green; + display: inline; + display: revert;/* since #inner is a <div>, this should compute to 'block' */ +} + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + <div id="outer"> + <div id="inner"> + This<br> + is<br> + filler<br> + text.<br> + This<br> + is<br> + filler<br> + text. + </div> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-002.html b/testing/web-platform/tests/css/css-cascade/revert-val-002.html new file mode 100644 index 0000000000..d145ea42b2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-002.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: 'revert' keyword interaction with !important</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<link rel="match" href="reference/ref-filled-green-100px-square.xht"> +<style> +#outer { + background-color: red; + width: 100px; + height: 100px; + overflow: hidden; +} +#inner { + /* This should win over `revert` */ + display: block !important; +} +#inner { + color: green; + background-color: green; + display: revert; +} +</style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + <div id="outer"> + <span id="inner"> + This<br> + is<br> + filler<br> + text.<br> + This<br> + is<br> + filler<br> + text. + </span> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-003.html b/testing/web-platform/tests/css/css-cascade/revert-val-003.html new file mode 100644 index 0000000000..b819eb0b2d --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-003.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Cascade: 'revert' keyword in transition</title> +<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> +<link rel="author" title="Mozilla" href="https://mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +html, body { margin: 0 } +h1 { + margin: 0; + transition: margin 10s; + transition-delay: -5s; /* So we can expect it to be half-way the transition when toggling the property */ +} +</style> +<h1>This is a header that should get some margin</h1> +<script> +test(function() { + const el = document.querySelector("h1"); + const cs = getComputedStyle(el); + assert_equals(cs.marginTop, "0px", "Margin before transition"); + el.style.margin = "revert"; + const midTransition = cs.marginTop; + assert_not_equals(midTransition, "0px", "Margin mid transition"); + el.style.transition = "none"; + assert_not_equals(cs.marginTop, midTransition, "Default margin"); +}, "revert works with transitions"); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-004.html b/testing/web-platform/tests/css/css-cascade/revert-val-004.html new file mode 100644 index 0000000000..6a7046c0e4 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-004.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>CSS Cascade: using 'revert' with the 'all' property</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + div { + display: inline; + } + .revert { + all: revert; + } +</style> +<div id=div></div> +<script> + test(function() { + let cs = getComputedStyle(div); + assert_equals(cs.display, 'inline'); + div.className = 'revert'; + assert_equals(cs.display, 'block'); + }, 'The revert keyword works with the all property'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-005.html b/testing/web-platform/tests/css/css-cascade/revert-val-005.html new file mode 100644 index 0000000000..7295605d4b --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-005.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' in css-logical properties</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #h1_physical { + margin: 0px; + margin: revert; + } + #h1_logical { + margin: 0px; + margin-inline-start: revert; + margin-inline-end: revert; + margin-block-start: revert; + margin-block-end: revert; + } +</style> +<h1 id=h1_physical></h1> +<h1 id=h1_logical></h1> +<h1 id=ref></h1> +<script> + test(function() { + let actual = getComputedStyle(h1_physical).marginTop; + let expected = getComputedStyle(ref).marginTop; + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected, '0px'); + assert_equals(actual, expected); + }, 'The revert keyword works with physical properties'); + + test(function() { + let actual = getComputedStyle(h1_logical).marginTop; + let expected = getComputedStyle(ref).marginTop; + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected, '0px'); + assert_equals(actual, expected); + }, 'The revert keyword works with logical properties'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-006.html b/testing/web-platform/tests/css/css-cascade/revert-val-006.html new file mode 100644 index 0000000000..2b238f6d7e --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-006.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' keyword in keyframe animations</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @keyframes test { + from { margin-top: 0px; } + 50% { margin-top: revert; } + to { margin-top: 0px; } + } + #h1 { + margin-top: 0px; + animation: test linear 1000s -500s; + } +</style> +<h1 id=h1></h1> +<h1 id=ref></h1> +<script> + test(function() { + let actual = getComputedStyle(h1).marginTop; + let expected = getComputedStyle(ref).marginTop; + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected, '0px'); + assert_equals(actual, expected); + }, 'The revert keyword works with @keyframes'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-007.html b/testing/web-platform/tests/css/css-cascade/revert-val-007.html new file mode 100644 index 0000000000..38078fcfa9 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-007.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' in keyframe animations on identical elements</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<link rel="help" href="https://crbug.com/1065387"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @keyframes test { + from { margin-top: revert; } + to { margin-top: 100px; } + } + .anim { + margin-top: 0px; + animation: test linear 1s paused; + } +</style> +<h1 class="anim"></h1> +<h1 class="anim"></h1> +<h1 class="anim"></h1> +<h1 id=ref></h1> +<script> + test(function() { + // This querySelectorAll includes #ref, but that's OK. + let targets = document.querySelectorAll('h1'); + for (let t of targets) { + let actual = getComputedStyle(t).marginTop; + let expected = getComputedStyle(ref).marginTop; + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected, '0px'); + assert_equals(actual, expected); + } + }, 'A @keyframe animation with revert works when applied to multiple identical elements'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-008.html b/testing/web-platform/tests/css/css-cascade/revert-val-008.html new file mode 100644 index 0000000000..77cceae7a3 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-008.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' in final keyframe of web animation</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<link rel="help" href="https://crbug.com/1065387"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<h1 id=h1></h1> +<h1 id=ref></h1> +<script> + test(function() { + let expected_lower = parseInt(getComputedStyle(ref).marginTop); + let expected_upper = expected_lower * 2; + + h1.animate([ + { marginTop: `${expected_lower * 4}px` }, + { marginTop: `${expected_lower * 3}px` }, + { marginTop: `${expected_lower * 2}px` }, + { marginTop: 'revert' }, + ], { + duration: 4000, + delay: -3500, + }).pause(); + + let actual = parseInt(getComputedStyle(h1).marginTop); + + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected_lower, 0); + assert_not_equals(expected_upper, 0); + assert_between_exclusive(actual, expected_lower, expected_upper); + }, 'The revert keyword works in the final frame of a web animation'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-009.html b/testing/web-platform/tests/css/css-cascade/revert-val-009.html new file mode 100644 index 0000000000..e9683e90f4 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-009.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' in implicit keyframes</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<link rel="help" href="https://crbug.com/1065387"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<h1 id=h1></h1> +<h1 id=ref></h1> +<script> + test(function() { + let expected_lower = parseInt(getComputedStyle(ref).marginTop); + let expected_upper = expected_lower * 2; + h1.style = `margin-top: ${expected_lower * 1000}px; margin-top: revert;`; + + h1.animate([ + { marginTop: `${expected_upper}px` }, + ], { + duration: 1000, + delay: -500, + }).pause(); + + let actual = parseInt(getComputedStyle(h1).marginTop); + + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(expected_lower, 0); + assert_not_equals(expected_upper, 0); + assert_between_exclusive(actual, expected_lower, expected_upper); + }, 'The revert keyword works in implicit keyframes'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-010.html b/testing/web-platform/tests/css/css-cascade/revert-val-010.html new file mode 100644 index 0000000000..58449e9132 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-010.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>CSS Cascade: 'revert' appearing in setKeyframes</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> +<link rel="help" href="https://drafts.csswg.org/web-animations-1/#dom-keyframeeffect-setkeyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<h1 id=h1></h1> +<script> + test(function() { + let original = parseInt(getComputedStyle(h1).marginTop); + + // This test assumes that the UA style sheet sets a non-0px value on + // <h1> elements: + assert_not_equals(original, 0); + + let animation = h1.animate([ + { marginTop: `${original*4}px` }, + { marginTop: `${original*8}px` }, + ], { + duration: 1000000, + delay: -500000, + easing: 'steps(2, end)' + }); + + let animated = parseInt(getComputedStyle(h1).marginTop); + assert_equals(animated, original*6); + + animation.effect.setKeyframes([ + { marginTop: 'revert' }, + { marginTop: `${original*3}px` }, + ]); + + let animated_revert = parseInt(getComputedStyle(h1).marginTop); + assert_equals(animated_revert, original*2); + }, 'The revert works when appearing in setKeyframes'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/revert-val-011.html b/testing/web-platform/tests/css/css-cascade/revert-val-011.html new file mode 100644 index 0000000000..9c034084c5 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/revert-val-011.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: 'revert' from mutating inline style</title> + <link rel="help" href="https://drafts.csswg.org/css-cascade/#default"> + <link rel="author" href="mailto:sesse@chromium.org"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + .outer { + left: 1px; + } + </style> +</head> +<body> + <p class="outer" id="el">Test passes if the text is black (not red).</p> +</body> +<script> +test(() => { + el.offsetTop; + assert_equals(getComputedStyle(el).left, "1px", "style is set correctly"); +}); +test(() => { + el.offsetTop; + el.style.left = "2px"; + assert_equals(getComputedStyle(el).left, "2px", "style is modified correctly"); +}); +test(() => { + el.offsetTop; + el.style.left = "revert"; + assert_equals(getComputedStyle(el).left, "auto", "style is reverted correctly"); +}); +</script> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/scope-container.html b/testing/web-platform/tests/css/css-cascade/scope-container.html new file mode 100644 index 0000000000..3e976088e2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-container.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>@scope - inner @container</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://drafts.csswg.org/css-contain-3/#container-rule"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + main { + width: 100px; + height: 100px; + container-type: size; + } + + @scope (.a) { + @container (width > 0px) { + :scope { + z-index: 1; + } + + .b { + background-color: green; + } + } + } +</style> +<main> + <div class=a> + <div class=b> + </div> + </div> + <div class=b></div> +</main> +<script> + test(() => { + let a = document.querySelector('main > .a'); + let b = document.querySelector('main > .a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).backgroundColor, 'rgb(0, 128, 0)'); + + let out_of_scope_b = document.querySelector('main > .b'); + assert_equals(getComputedStyle(out_of_scope_b).backgroundColor, 'rgba(0, 0, 0, 0)'); + }, 'Style rules within @container are scoped'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-cssom.html b/testing/web-platform/tests/css/css-cascade/scope-cssom.html new file mode 100644 index 0000000000..3603fc0e83 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-cssom.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<title>@scope - CSSOM</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#the-cssscoperule-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style id=style> + @scope {} + @scope (.a) {} + @scope (.a) to (.b) { + div { + display: block; + } + } + @scope to (.b) {} +</style> +<script> + +// CSSScopeRule.cssText +test(() => { + assert_equals(style.sheet.rules[0].cssText, '@scope {\n}'); +}, 'CSSScopeRule.cssText, implicit scope'); + +test(() => { + assert_equals(style.sheet.rules[1].cssText, '@scope (.a) {\n}'); +}, 'CSSScopeRule.cssText, root only'); + +test(() => { + assert_equals(style.sheet.rules[2].cssText, '@scope (.a) to (.b) {\n div { display: block; }\n}'); +}, 'CSSScopeRule.cssText, root and limit'); + +test(() => { + assert_equals(style.sheet.rules[3].cssText, '@scope to (.b) {\n}'); +}, 'CSSScopeRule.cssText, limit only'); + +// start +test(() => { + assert_equals(style.sheet.rules[0].start, null); +}, 'CSSScopeRule.start, implicit scope'); + +test(() => { + assert_equals(style.sheet.rules[1].start, '.a'); +}, 'CSSScopeRule.start, root only'); + +test(() => { + assert_equals(style.sheet.rules[2].start, '.a'); +}, 'CSSScopeRule.start, root and limit'); + +test(() => { + assert_equals(style.sheet.rules[3].start, null); +}, 'CSSScopeRule.start, limit only'); + +// end +test(() => { + assert_equals(style.sheet.rules[0].end, null); +}, 'CSSScopeRule.end, implicit scope'); + +test(() => { + assert_equals(style.sheet.rules[1].end, null); +}, 'CSSScopeRule.end, root only'); + +test(() => { + assert_equals(style.sheet.rules[2].end, '.b'); +}, 'CSSScopeRule.end, root and limit'); + +test(() => { + assert_equals(style.sheet.rules[3].end, '.b'); +}, 'CSSScopeRule.end, limit only'); + +test(() => { + assert_true(style.sheet.rules[0] instanceof CSSGroupingRule); + assert_false(style.sheet.rules[0] instanceof CSSConditionRule); +}, 'CSSScopeRule is a CSSGroupingRule'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-declaration-list-crash.html b/testing/web-platform/tests/css/css-cascade/scope-declaration-list-crash.html new file mode 100644 index 0000000000..c459da053a --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-declaration-list-crash.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<title>@scope crash with invalid selectors</title> +<style> + @scope (div) { + z-index: 1; + } + @scope (div) { + .a; + } +</style> +<p> + PASS if no crash. +</p> diff --git a/testing/web-platform/tests/css/css-cascade/scope-deep.html b/testing/web-platform/tests/css/css-cascade/scope-deep.html new file mode 100644 index 0000000000..0e88778202 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-deep.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>@scope - deeply nested</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + main * { background-color: black; } +</style> +<main id=main></main> +<script> + +// @scope (.s0) { @scope (.s1) { ... span {} ... } } +function createStyleSheet(length, i) { + if (length == 0) + return 'span { background-color: green; }'; + if (i === undefined) + i = 0; + return ` + @scope (.s${i}) { + ${createStyleSheet(length - 1, i + 1)} + } + `.trim(); +} + +// <div class=s0><div class=s1>...<span></span>...</div></div> +function createElementChain(length, i) { + if (length < 1) + throw 'Invalid length'; + if (i === undefined) + i = 0; + let e = document.createElement('div'); + e.classList.add(`s${i}`); + if (length > 1) + e.append(createElementChain(length - 1, i + 1)); + else + e.append(document.createElement('span')); + return e; +} + +const COUNT = 90; + +let style_node = document.createElement('style'); +style_node.textContent = createStyleSheet(COUNT); +main.append(style_node); + +main.append(createElementChain(COUNT)); + +test(() => { + for (let span of main.querySelectorAll('span')) + assert_equals(getComputedStyle(span).backgroundColor, 'rgb(0, 128, 0)'); +}, 'Deep @scope nesting'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-evaluation.html b/testing/web-platform/tests/css/css-cascade/scope-evaluation.html new file mode 100644 index 0000000000..f181048115 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-evaluation.html @@ -0,0 +1,547 @@ +<!DOCTYPE html> +<title>@scope - evaluation</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +function test_scope(script_element, callback_fn, description) { + test((t) => { + // The provided <script> element must be an immedate subsequent sibling of + // a <template> element. + let template_element = script_element.previousElementSibling; + assert_equals(template_element.tagName, 'TEMPLATE'); + + t.add_cleanup(() => main.replaceChildren()); + + main.append(template_element.content.cloneNode(true)); + + callback_fn(); + }, description); +} + +function assert_green(selector) { + assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 128, 0)'); +} +function assert_not_green(selector) { + assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 0, 0)'); +} +</script> +<style> + :where(main *) { + background-color: black; + } +</style> +<main id=main> +</main> + +<!-- Tests follow --> + +<template> + <style> + @scope (.a) { + span { background-color: green; } + } + </style> + <div class=a> + <span>green</span> + </div> + <div class=b> + <span>not green</span> + </div> + <span>not green</span> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > span'); + assert_not_green('.b > span'); + assert_not_green(':scope > span'); +}, 'Single scope'); +</script> + +<template> + <style> + @scope (.a) { + .a { background-color: green; } + } + </style> + <div class=a> <!-- green --> + <span>not green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.a'); + assert_not_green('.a > span'); +}, 'Scope can not match its own root without :scope'); +</script> + +<template> + <style> + @scope (.a) { + :scope { background-color: green; } + } + </style> + <div class=a> <!-- green --> + <span>not green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a'); + assert_not_green('.a > span'); +}, 'Selecting self with :scope'); +</script> + +<template> + <style> + @scope (.a) to (.c) { + span { background-color: green; } + } + </style> + <div class=a> + <div class=b> + <span>green</span> + </div> + <div class=c> + <span>not green</span> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.b > span'); + assert_not_green('.c > span'); +}, 'Single scope with limit'); +</script> + +<template> + <style> + @scope (.a) { + :scope > span { background-color: green; } + } + </style> + <div class=a> + <span>green</span> + <div class=b> + <span>not green</span> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > span'); + assert_not_green('.b > span'); +}, 'Single scope, :scope pseudo in main selector'); +</script> + +<template> + <style> + @scope (.a) to (:scope > .b) { + span { background-color: green; } + } + </style> + <div class=a> + <div class=b> + <span>not green</span> + </div> + <div class=c> + <div class=b> + <span>green</span> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.a > .b > span'); + assert_green('.a > .c > .b > span'); +}, 'Single scope, :scope pseudo in to-selector'); +</script> + +<template> + <style> + @scope (.a) to (:scope > .b) { + span { background-color: green; } + } + </style> + <div class=a> + <div class=b> + <span>not green</span> + </div> + <div class=a> + <div class=b> + <span>green</span> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.a > .b > span'); + // Note that this span is in the outer .a-scope, but not in the inner scope. + assert_green('.a > .a > .b > span'); +}, 'Multiple scopes, :scope pseudo in to-selector'); +</script> + +<template> + <style> + @scope (.a) { + @scope (:scope > .b) { + span { background-color: green; } + } + } + </style> + <div class=a> + <div class=b> + <span>green</span> + </div> + <div> + <div class=b> + <span>not green</span> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > .b > span'); + assert_not_green('.a > div > .b > span'); +}, 'Inner @scope with :scope in from-selector'); +</script> + +<template> + <style> + @scope (.a) to (:scope > .b) { + .c { background-color: green; } + } + </style> + <div class=a> + <div> + <div class=a> + <div class=b> + <div class=c></div> + </div> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + // Not in the inner scope, but is in the outer scope. + assert_green('.c'); +}, 'Multiple scopes from same @scope-rule, only one limited'); +</script> + +<template> + <style> + @scope (.a) to (.b) { + .c { background-color: green; } + } + </style> + <div class=a> + <div> + <div class=a> + <div class=b> + <div class=c></div> + </div> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.c'); +}, 'Multiple scopes from same @scope-rule, both limited'); +</script> + +<template> + <style> + @scope (.a) { + @scope (.b) { + span { background-color: green; } + } + } + </style> + <div class=a> + <div class=b> + <span>green</span> + </div> + <span>not green</span> + </div> + <div class=b> + <span>not green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > .b > span'); + assert_not_green('.a > span'); + assert_not_green(':scope > .b > span'); +}, 'Nested scopes'); +</script> + +<template> + <style> + @scope (.b) { + @scope (.a) { + span { background-color: green; } + } + } + </style> + <div class=a> + <div class=b> + <span>not green</span> + </div> + <span>not green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.a > .b > span'); + assert_not_green('.a > span'); +}, 'Nested scopes, reverse'); +</script> + + +<template> + <style> + @scope (.a) { + @scope (.b) to (.c) { + span { background-color: green; } + } + } + </style> + <div class=a> + <div class=b> + <span>green</span> + </div> + <div class=b> + <div class=c> + <span>not green</span> + </div> + </div> + <span>not green</span> + </div> + <div class=b> + <span>not green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > .b > span'); + assert_not_green('.a > span'); + assert_not_green('.a > .b > .c > span'); + assert_not_green(':scope > .b > span'); +}, 'Nested scopes, with to-selector'); +</script> + +<template> + <style> + @scope (.a) { + :scope { background-color: green; } + } + </style> + <div class=a></div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a'); +}, ':scope selecting itself'); +</script> + +<template> + <style> + @scope (.a) to (.b) { + * { background-color: green; } + } + </style> + <div id=above> + <div class=a> + <div> + <div class=b> + <div id=below></div> + </div> + </div> + </div> + <div id=adjacent></div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('#above'); + assert_not_green('#adjacent'); + assert_not_green('.a'); + assert_green('.a > div'); + assert_not_green('.b'); + assert_not_green('#below'); +}, 'The scoping limit is not in scope'); +</script> + +<template> + <style> + @scope (.a) to (.b > *) { + * { background-color: green; } + } + </style> + <div id=above> + <div class=a> + <div> + <div class=b> + <div id=limit></div> + </div> + </div> + </div> + <div id=adjacent></div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('#above'); + assert_not_green('#adjacent'); + assert_not_green('.a'); + assert_green('.a > div'); + assert_green('.b'); + assert_not_green('#limit'); +}, 'Simulated inclusive scoping limit'); +</script> + +<template> + <style> + @scope (.a) to (:scope) { + * { background-color: green; } + } + </style> + <div id=above> + <div class=a> + <div> + <div class=b> + <div id=inner></div> + </div> + </div> + </div> + <div id=adjacent></div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('#above'); + assert_not_green('#adjacent'); + assert_not_green('.a'); + assert_not_green('.a > div'); + assert_not_green('.b'); + assert_not_green('#inner'); +}, 'Scope with no elements'); +</script> + + +<template> + <style> + @scope (.a) { + :scope + .c { background-color: green; } + } + </style> + <div class=a> + <div class=a></div> + <div class=c></div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + // A :scope sibling can never match, as the scoping element must + // be on the ancestor chain. + assert_not_green('.c'); +}, ':scope direct adjacent sibling'); +</script> + + +<template> + <style> + @scope (.a) { + :scope + .c { background-color: green; } + } + </style> + <div class=a> + <div class=a></div> + <div></div> + <div class=c></div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + // A :scope sibling can never match, as the scoping element must + // be on the ancestor chain. + assert_not_green('.c'); +}, ':scope indirect adjacent sibling'); +</script> + + +<template> + <style> + @scope (.a) { + > span { background-color: green; } + } + </style> + <div class=a> + <span>green</span> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_green('.a > span'); +}, 'Relative selector inside @scope'); +</script> + + +<template> + <style> + @scope (.a) { + /* Can never match anything. */ + :scope > :scope { background-color: green; } + } + </style> + <div class=a> + <div id=inner class=a> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.a'); + assert_not_green('#inner'); +}, ':scope in two different compounds'); +</script> + + +<template> + <style> + @scope (.a:has(.c)) { + .b { background-color:green; } + } + </style> + <div class=first> + <div class=a> + <div class=b> + <div class=c></div> + </div> + </div> + </div> + <div class=second> + <div class=a> + <div class=b> + <div class=d></div> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_not_green('.first .a'); + assert_green('.first .b'); + assert_not_green('.first .c'); + + assert_not_green('.second .a'); + assert_not_green('.second .b'); + assert_not_green('.second .d'); +}, 'Scope root with :has()'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-focus.html b/testing/web-platform/tests/css/css-cascade/scope-focus.html new file mode 100644 index 0000000000..578a7702c1 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-focus.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<title>@scope and :focus</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<main id=main></main> + +<template id=test_subject> + <div> + <style> + @scope (.a:focus) { + :scope { z-index: 1; } + } + </style> + <div class=a tabindex=0>1</div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_subject.content.cloneNode(true)); + + let a = main.querySelector('.a'); + + assert_equals(getComputedStyle(a).zIndex, 'auto'); + await test_driver.bless('focus', () => a.focus()); + assert_equals(getComputedStyle(a).zIndex, '1'); +}, ':focus via :scope in subject'); +</script> + +<template id=test_non_subject> + <div> + <style> + @scope (.a:focus) { + :scope .b { z-index: 1; } + } + </style> + <div class=a tabindex=0> + <div class=b>2</div> + </div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_non_subject.content.cloneNode(true)); + + let a = main.querySelector('.a'); + let b = main.querySelector('.b'); + + assert_equals(getComputedStyle(b).zIndex, 'auto'); + await test_driver.bless('focus', () => a.focus()); + assert_equals(getComputedStyle(b).zIndex, '1'); +}, ':focus via :scope in non-subject'); +</script> + +<template id=test_subject_limit> + <div> + <style> + @scope (.a) to (:scope:focus) { + :scope { z-index: 1; } + } + </style> + <div class=a tabindex=0>3</div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_subject_limit.content.cloneNode(true)); + + let a = main.querySelector('.a'); + + assert_equals(getComputedStyle(a).zIndex, '1'); + await test_driver.bless('focus', () => a.focus()); + // After focus(), we're no longer in scope because the limit (to-selector) + // kicks in. + assert_equals(getComputedStyle(a).zIndex, 'auto'); +}, ':focus in limit, :scope in subject'); +</script> + +<template id=test_non_subject_limit> + <div> + <style> + @scope (.a) to (.b:focus) { + .b { z-index: 1; } + } + </style> + <div class=a tabindex=0> + <div class=b tabindex=1>4</div> + </div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_non_subject_limit.content.cloneNode(true)); + + let a = main.querySelector('.a'); + let b = main.querySelector('.b'); + + assert_equals(getComputedStyle(b).zIndex, '1'); + await test_driver.bless('focus', () => b.focus()); + // After focus(), we're no longer in scope because the limit (to-selector) + // kicks in. + assert_equals(getComputedStyle(b).zIndex, 'auto'); +}, ':focus in intermediate limit, :scope in subject'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-hover.html b/testing/web-platform/tests/css/css-cascade/scope-hover.html new file mode 100644 index 0000000000..648fb361d6 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-hover.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<title>@scope and :hover</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<main id=main></main> + +<script> + async function hover(element) { + let actions = new test_driver.Actions().pointerMove(0, 0, {origin: element}); + await actions.send(); + } +</script> + +<template id=test_subject> + <div> + <style> + @scope (.a:hover) { + :scope { z-index: 1; } + } + </style> + <div class=a>1</div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_subject.content.cloneNode(true)); + let a = main.querySelector('.a'); + assert_equals(getComputedStyle(a).zIndex, 'auto'); + await hover(a); + assert_equals(getComputedStyle(a).zIndex, '1'); +}, ':hover via :scope in subject'); +</script> + +<template id=test_non_subject> + <div> + <style> + @scope (.a:hover) { + :scope .b { z-index: 1; } + } + </style> + <div class=a> + <div class=b>2</div> + </div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_non_subject.content.cloneNode(true)); + + let a = main.querySelector('.a'); + let b = main.querySelector('.b'); + + assert_equals(getComputedStyle(b).zIndex, 'auto'); + await hover(a); + assert_equals(getComputedStyle(b).zIndex, '1'); +}, ':hover via :scope in non-subject'); +</script> + +<template id=test_subject_limit> + <div> + <style> + @scope (.a) to (:scope:hover) { + :scope { z-index: 1; } + } + </style> + <div class=a tabindex=0>3</div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_subject_limit.content.cloneNode(true)); + + let a = main.querySelector('.a'); + + assert_equals(getComputedStyle(a).zIndex, '1'); + await hover(a); + // After hover, we're no longer in scope because the limit (to-selector) + // kicks in. + assert_equals(getComputedStyle(a).zIndex, 'auto'); +}, ':hover in limit, :scope in subject'); +</script> + +<template id=test_non_subject_limit> + <div> + <style> + @scope (.a) to (.b:hover) { + .b { z-index: 1; } + } + </style> + <div class=a tabindex=0> + <div class=b tabindex=1>4</div> + </div> +</template> +<script> +promise_test(async (t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_non_subject_limit.content.cloneNode(true)); + + let a = main.querySelector('.a'); + let b = main.querySelector('.b'); + + assert_equals(getComputedStyle(b).zIndex, '1'); + await hover(b); + // After hover, we're no longer in scope because the limit (to-selector) + // kicks in. + assert_equals(getComputedStyle(b).zIndex, 'auto'); +}, ':hover in intermediate limit, :scope in subject'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html b/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html new file mode 100644 index 0000000000..d1ac738b77 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-implicit-external.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> + <head> + <title>@scope - implicit scope root (external sheet)</title> + <link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div class="a outside"><div> + <div id=root> + <link rel="stylesheet" href="resources/scope.css"> + <div class=a></div> + </div> + <div class="a outside"><div> + + <script> + test((t) => { + assert_equals(getComputedStyle(root).zIndex, '1'); + assert_equals(getComputedStyle(document.querySelector('#root > .a')).zIndex, '2'); + + let outside = document.querySelectorAll('.outside'); + assert_equals(outside.length, 2); + for (let div of outside) { + assert_equals(getComputedStyle(div).zIndex, 'auto'); + } + }, '@scope with external stylesheet'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/scope-implicit.html b/testing/web-platform/tests/css/css-cascade/scope-implicit.html new file mode 100644 index 0000000000..9add25fc9a --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-implicit.html @@ -0,0 +1,199 @@ +<!DOCTYPE html> +<title>@scope - implicit scope root</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<main id=main></main> + +<template id=test_basic> + <div> + <style> + @scope { + .a { z-index:1; } + } + </style> + <div id=inner class=a></div> + </div> + <div id=outer class=a></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_basic.content.cloneNode(true)); + + assert_equals(getComputedStyle(inner).zIndex, '1'); + assert_equals(getComputedStyle(outer).zIndex, 'auto'); +}, '@scope without prelude implicitly scopes to parent of owner node'); +</script> + +<template id=test_scope_pseudo> + <div> + <div></div> + </div> + <div> + <div id=root> + <style> + @scope { + :scope { z-index:1; } + } + </style> + <div> + <div></div> + </div> + </div> + </div> + <div> + <div></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_scope_pseudo.content.cloneNode(true)); + + assert_equals(getComputedStyle(root).zIndex, '1'); + + // Only #root should be affected. + for (let div of main.querySelectorAll('div:not(#root)')) { + assert_equals(getComputedStyle(div).zIndex, 'auto'); + } +}, ':scope can style implicit root'); +</script> + +<template id=test_duplicate> + <div> + <style> + @scope { + .a { z-index:1; } + } + </style> + <div id=first class=a></div> + </div> + <div> + <style> + @scope { + .a { z-index:1; } + } + </style> + <div id=second class=a></div> + </div> + <div id=outer class=a></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_duplicate.content.cloneNode(true)); + + assert_equals(getComputedStyle(first).zIndex, '1'); + assert_equals(getComputedStyle(second).zIndex, '1'); + assert_equals(getComputedStyle(outer).zIndex, 'auto'); +}, '@scope works with two identical stylesheets'); +</script> + + +<template id=test_forgiving> + <div> + <style> + @scope ($invalid) { + #a { z-index:1; } + } + </style> + <div id=a></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_forgiving.content.cloneNode(true)); + + assert_equals(getComputedStyle(a).zIndex, 'auto'); +}, '@scope with effectively empty :is() must not match anything'); +</script> + +<template id=test_implicit_descendant> + <div id=div> + <style> + @scope { + #div { z-index:1; } + } + </style> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_implicit_descendant.content.cloneNode(true)); + + assert_equals(getComputedStyle(div).zIndex, 'auto'); +}, 'Implicit @scope has implicitly added :scope descendant combinator'); +</script> + +<template id=test_implicit_relative> + <div id=outer> + <style> + @scope { + > div { z-index:1; } + } + </style> + <div id=child> + <div id=inner></div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_implicit_relative.content.cloneNode(true)); + + assert_equals(getComputedStyle(outer).zIndex, 'auto'); + assert_equals(getComputedStyle(child).zIndex, '1'); + assert_equals(getComputedStyle(inner).zIndex, 'auto'); +}, 'Implicit @scope with inner relative selector'); +</script> + +<template id=test_implicit_descendant_nesting_selector> + <div id=div> + <style> + @scope { + /* Behaves like :scope */ + & { z-index:1; } + } + </style> + <div id=inner></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_implicit_descendant_nesting_selector.content.cloneNode(true)); + + assert_equals(getComputedStyle(div).zIndex, '1'); + assert_equals(getComputedStyle(inner).zIndex, 'auto'); +}, 'Implicit @scope with inner nesting selector'); +</script> + +<template id=test_limit> + <div> + <style> + @scope to (.b) { + .a { z-index:1; } + } + </style> + <div id=inner class=a> + <div class=b> + <div id=outside_limit class=a></div> + </div> + </div> + </div> + <div id=outer class=a></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_limit.content.cloneNode(true)); + + assert_equals(getComputedStyle(inner).zIndex, '1'); + assert_equals(getComputedStyle(outer).zIndex, 'auto'); + assert_equals(getComputedStyle(outside_limit).zIndex, 'auto'); +}, 'Implicit @scope with limit'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/css/css-cascade/scope-invalidation.html b/testing/web-platform/tests/css/css-cascade/scope-invalidation.html new file mode 100644 index 0000000000..d53257e894 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-invalidation.html @@ -0,0 +1,782 @@ +<!DOCTYPE html> +<title>@scope - invalidation</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +function test_scope_invalidation(script_element, callback_fn, description) { + test((t) => { + // The provided <script> element must be an immedate subsequent sibling of + // a <template> element. + let template_element = script_element.previousElementSibling; + assert_equals(template_element.tagName, 'TEMPLATE'); + + t.add_cleanup(() => { + while (main.firstChild) + main.firstChild.remove() + }); + + main.append(template_element.content.cloneNode(true)); + + callback_fn(); + }, description); +} + +function assert_green(element) { + assert_equals(getComputedStyle(element).backgroundColor, 'rgb(0, 128, 0)'); +} +function assert_not_green(element) { + assert_equals(getComputedStyle(element).backgroundColor, 'rgb(0, 0, 0)'); +} +</script> +<style> + main * { + background-color: black; + } +</style> +<main id=main> +</main> + +<!-- Tests follow --> + +<template> + <style> + @scope (.a) { + span { background-color: green; } + } + </style> + <div> + <span></span> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let div = main.querySelector('div'); + let span = main.querySelector('div > span'); + assert_not_green(span); + div.classList.add('a'); + assert_green(span); + div.classList.remove('a'); + assert_not_green(span); +}, 'Element becoming scope root'); +</script> + +<template> + <style> + @scope (.a, .b) { + span { background-color: green; } + } + </style> + <div> + <span></span> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let div = main.querySelector('div'); + let span = main.querySelector('div > span'); + + // .a + assert_not_green(span); + div.classList.add('a'); + assert_green(span); + div.classList.remove('a'); + assert_not_green(span); + + // .b + assert_not_green(span); + div.classList.add('b'); + assert_green(span); + div.classList.remove('b'); + assert_not_green(span); +}, 'Element becoming scope root (selector list)'); +</script> + +<template> + <style> + @scope (.a) { + :scope { background-color: green; } + } + </style> + <div class=b></div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let b = main.querySelector('.b'); + assert_not_green(b); + b.classList.add('a'); + assert_green(b); + b.classList.remove('a'); + assert_not_green(b); +}, 'Element becoming scope root, with inner :scope rule'); +</script> + +<template> + <style> + @scope (.a) to (.b) { + span { background-color: green; } + } + </style> + <div class=a> + <div> + <span></span> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let inner_div = main.querySelector('.a > div'); + let span = main.querySelector('.a > div > span'); + assert_green(span); + inner_div.classList.add('b'); + assert_not_green(span); + inner_div.classList.remove('b'); + assert_green(span); +}, 'Parent element becoming scope limit'); +</script> + +<template> + <style> + @scope (.a) to (.b, .c) { + span { background-color: green; } + } + </style> + <div class=a> + <div> + <span></span> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let inner_div = main.querySelector('.a > div'); + let span = main.querySelector('.a > div > span'); + + // .b + assert_green(span); + inner_div.classList.add('b'); + assert_not_green(span); + inner_div.classList.remove('b'); + assert_green(span); + + // .c + assert_green(span); + inner_div.classList.add('c'); + assert_not_green(span); + inner_div.classList.remove('c'); + assert_green(span); +}, 'Parent element becoming scope limit (selector list)'); +</script> + +<template> + <style> + @scope (.a) to (.b) { + span { background-color: green; } + } + </style> + <div class=a> + <div> + <span></span> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let span = main.querySelector('.a > div > span'); + assert_green(span); + span.classList.add('b'); + assert_not_green(span); + span.classList.remove('b'); + assert_green(span); +}, 'Subject element becoming scope limit'); +</script> + +<template> + <style> + @scope (.a) to (.b .c) { + span { background-color: green; } + } + </style> + <div class=a> + <div> + <div class=c> + <span></span> + </div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let intermediate_div = main.querySelector('.a > div'); + let span = main.querySelector('span'); + assert_green(span); + intermediate_div.classList.add('b'); + assert_not_green(span); + intermediate_div.classList.remove('b'); + assert_green(span); +}, 'Parent element affecting scope limit'); +</script> + +<template> + <style> + @scope (.a) to (.b ~ .c) { + span { background-color: green; } + } + </style> + <div class=a> + <div></div> + <div></div> + <div></div> + <div></div> + <div class=c> + <span></span> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let sibling_div = main.querySelector('.a > div'); + let span = main.querySelector('span'); + assert_green(span); + sibling_div.classList.add('b'); + assert_not_green(span); + sibling_div.classList.remove('b'); + assert_green(span); +}, 'Sibling element affecting scope limit'); +</script> + +<template> + <style> + @scope (.a) { + @scope (.b) { + span { background-color: green; } + } + } + </style> + <div> + <div> + <span></span> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let outer_div = main.querySelector(':scope > div'); + let inner_div = main.querySelector(':scope > div > div'); + let span = main.querySelector('div > div > span'); + + assert_not_green(span); + + outer_div.classList.add('a'); + assert_not_green(span); + + inner_div.classList.add('b'); + assert_green(span); + + // Toggle .b while .a remains. + inner_div.classList.remove('b'); + assert_not_green(span); + inner_div.classList.add('b'); + assert_green(span); + + // Toggle .a while .b remains. + outer_div.classList.remove('a'); + assert_not_green(span); + outer_div.classList.add('a'); + assert_green(span); +}, 'Toggling inner/outer scope roots'); +</script> + + +<template> + <style> + @scope (.a) { + :scope { background-color:green; } + } + </style> + <div></div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let div = main.querySelector('main > div'); + assert_not_green(div); + div.classList.add('a'); + assert_green(div); + div.classList.remove('a'); + assert_not_green(div); +}, 'Element becoming root, with :scope in subject'); +</script> + + +<template> + <style> + @scope (.a:has(.c)) { + .b { background-color:green; } + } + </style> + <div class=a> + <div class=b> + <div></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let b = main.querySelector('.b'); + let innermost = main.querySelector('.b > div'); + assert_not_green(b); + innermost.classList.add('c'); + assert_green(b); + innermost.classList.remove('c'); + assert_not_green(b); +}, 'Scope root with :has()'); +</script> + + +<template> + <style> + @scope (.a:has(.c)) { + :scope { background-color:green; } + } + </style> + <div class=a> + <div class=b> + <div></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let a = main.querySelector('.a'); + let innermost = main.querySelector('.b > div'); + assert_not_green(a); + innermost.classList.add('c'); + assert_green(a); + innermost.classList.remove('c'); + assert_not_green(a); +}, 'Scope root with :has(), :scope subject'); +</script> + + +<template> + <style> + @scope (.a:has(.c)) { + :scope { background-color:green; } + :scope .b { background-color:green; } + } + </style> + <div class=a> + <div class=b> + <div></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let a = main.querySelector('.a'); + let b = main.querySelector('.b'); + let innermost = main.querySelector('.b > div'); + assert_not_green(a); + assert_not_green(b); + innermost.classList.add('c'); + assert_green(a); + assert_green(b); + innermost.classList.remove('c'); + assert_not_green(a); + assert_not_green(b); +}, 'Scope root with :has(), :scope both subject and non-subject'); +</script> + + +<template> + <style> + @scope (.a) to (.b:has(.c)) { + .b { background-color:green; } + } + </style> + <div class=a> + <div class=b> + <div></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let b = main.querySelector('.b'); + let innermost = main.querySelector('.b > div'); + assert_green(b); + innermost.classList.add('c'); + assert_not_green(b); + innermost.classList.remove('c'); + assert_green(b); +}, 'Scope limit with :has()'); +</script> + +<template> + <style> + @scope (.a) { + .b ~ :scope { background-color:green; } + } + </style> + <div></div> + <div></div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let div1 = main.querySelector('main > div:nth-of-type(1)'); + let div2 = main.querySelector('main > div:nth-of-type(2)'); + + assert_not_green(div2); + div1.classList.add('b'); + assert_not_green(div2); + div2.classList.add('a'); + assert_green(div2); + div1.classList.remove('b'); + assert_not_green(div2); +}, 'Element becoming root, with :scope selected by ~ combinator'); +</script> + +<template> + <style> + @scope (.a ~ .b) { + .c { background-color:green; } + } + </style> + <div> + <div></div> + <div></div> + <div></div> + <div class=b> + <div class=c></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('div > div:first-child'); + let c = main.querySelector('.c'); + assert_not_green(c); + root.classList.add('a'); + assert_green(c); + root.classList.remove('a'); + assert_not_green(c); +}, 'Element becoming root via ~ combinator'); +</script> + +<template> + <style> + @scope (.a + .b) { + .c { background-color:green; } + } + </style> + <div> + <div></div> + <div class=b> + <div class=c></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('div > div:first-child'); + let c = main.querySelector('.c'); + assert_not_green(c); + root.classList.add('a'); + assert_green(c); + root.classList.remove('a'); + assert_not_green(c); +}, 'Element becoming root via + combinator'); +</script> + +<template> + <style> + @scope (.root) { + :not(:scope) { background-color:green; } + } + </style> + <div class=root> + <div class=a></div> + <div class=b></div> + <div class=c></div> + </div> + <div class=a></div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('.root'); + let a1 = main.querySelector('.root > .a'); + let b = main.querySelector('.root > .b'); + let c = main.querySelector('.root > .c'); + let a2 = main.querySelector('main > .a'); + + assert_not_green(root); + assert_green(a1); + assert_green(b); + assert_green(c); + assert_not_green(a2); + + root.classList.remove('root'); + assert_not_green(root); + assert_not_green(a1); + assert_not_green(b); + assert_not_green(c); + assert_not_green(a2); + + root.classList.add('root'); + assert_not_green(root); + assert_green(a1); + assert_green(b); + assert_green(c); + assert_not_green(a2); +}, ':not(scope) in subject'); +</script> + +<template> + <style> + @scope (.root) { + :not(:scope) > .a { background-color:green; } + } + </style> + <div class=root> + <div class=a></div> + <div> + <div class=a></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('.root'); + let outer_a = main.querySelector('.root > .a'); + let inner_a = main.querySelector('.root > div > .a'); + + assert_not_green(outer_a); + assert_green(inner_a); + + root.classList.remove('root'); + assert_not_green(outer_a); + assert_not_green(inner_a); + + root.classList.add('root'); + assert_not_green(outer_a); + assert_green(inner_a); +}, ':not(scope) in ancestor'); +</script> + +<template> + <style> + @scope (.root) to (:not(:scope)) { + :is(div, :scope) { background-color: green; } + } + </style> + <div class=root> + <div class=a></div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('.root'); + let a = main.querySelector('.root > .a'); + + assert_green(root); + assert_not_green(a); + + root.classList.remove('root'); + assert_not_green(root); + assert_not_green(a); + + root.classList.add('root'); + assert_green(root); + assert_not_green(a); +}, ':not(scope) in limit subject'); +</script> + +<template> + <style> + @scope (.root) to (:not(:scope) > .a) { + :is(div, :scope) { background-color: green; } + } + </style> + <div class=root> + <div class=a> + <div class=a></div> + </div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let root = main.querySelector('.root'); + let outer_a = main.querySelector('.root > .a'); + let inner_a = main.querySelector('.root > .a > .a'); + + assert_green(root); + assert_green(outer_a); + assert_not_green(inner_a); + + root.classList.remove('root'); + assert_not_green(root); + assert_not_green(outer_a); + assert_not_green(inner_a); + + root.classList.add('root'); + assert_green(root); + assert_green(outer_a); + assert_not_green(inner_a); +}, ':not(scope) in limit ancestor'); +</script> + +<template> + <style> + @scope (:nth-child(2n of .a)) { + :scope { background-color: green; } + } + </style> + <div id=wrapper> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let e = main.querySelectorAll('#wrapper > div'); + assert_equals(e.length, 8); + + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + assert_not_green(e[0]); + assert_not_green(e[1]); + assert_green(e[2]); + assert_not_green(e[3]); + assert_not_green(e[4]); + assert_not_green(e[5]); + assert_green(e[6]); + assert_not_green(e[7]); + + e[1].classList.add('a'); + // <div class=a></div> + // <div class=a></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + assert_not_green(e[0]); + assert_green(e[1]); + assert_not_green(e[2]); + assert_not_green(e[3]); + assert_green(e[4]); + assert_not_green(e[5]); + assert_not_green(e[6]); + assert_not_green(e[7]); + + e[1].classList.remove('a'); + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + assert_not_green(e[0]); + assert_not_green(e[1]); + assert_green(e[2]); + assert_not_green(e[3]); + assert_not_green(e[4]); + assert_not_green(e[5]); + assert_green(e[6]); + assert_not_green(e[7]); +}, ':nth-child() in scope root'); +</script> + +<template> + <style> + @scope (#wrapper) to (:nth-child(4n of .a)) { + div { background-color: green; } + } + </style> + <div id=wrapper> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + <div class=a></div> + <div></div> + </div> +</template> +<script> +test_scope_invalidation(document.currentScript, () => { + let e = main.querySelectorAll('#wrapper > div'); + assert_equals(e.length, 8); + + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> <= limit + // <div></div> + assert_green(e[0]); + assert_green(e[1]); + assert_green(e[2]); + assert_green(e[3]); + assert_green(e[4]); + assert_green(e[5]); + assert_not_green(e[6]); + assert_green(e[7]); + + e[1].classList.add('a'); + // <div class=a></div> + // <div class=a></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> <= limit + // <div></div> + // <div class=a></div> + // <div></div> + assert_green(e[0]); + assert_green(e[1]); + assert_green(e[2]); + assert_green(e[3]); + assert_not_green(e[4]); + assert_green(e[5]); + assert_green(e[6]); + assert_green(e[7]); + + e[1].classList.remove('a'); + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> + // <div></div> + // <div class=a></div> <= limit + // <div></div> + assert_green(e[0]); + assert_green(e[1]); + assert_green(e[2]); + assert_green(e[3]); + assert_green(e[4]); + assert_green(e[5]); + assert_not_green(e[6]); + assert_green(e[7]); +}, ':nth-child() in scope limit'); + +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-layer.html b/testing/web-platform/tests/css/css-cascade/scope-layer.html new file mode 100644 index 0000000000..e8a89ba68c --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-layer.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>@scope - inner @layer</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#layering"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @scope (.a) { + /* The theme layer wins over the base layer. Note that @layer statements + are allowed here, but aren't affected by the enclosing @scope. */ + @layer base, theme; + + @layer theme { + :scope { + z-index: 1; + } + + .b { + background-color: green; + } + } + } + + @layer base { + .a { + z-index: 0; + } + .a .b { + background-color: red; + } + } +</style> +<main> + <div class=a> + <div class=b> + </div> + </div> + <div class=b></div> +</main> +<script> + test(() => { + let a = document.querySelector('main > .a'); + let b = document.querySelector('main > .a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).backgroundColor, 'rgb(0, 128, 0)'); + + let out_of_scope_b = document.querySelector('main > .b'); + assert_equals(getComputedStyle(out_of_scope_b).backgroundColor, 'rgba(0, 0, 0, 0)'); + }, 'Style rules within @layer are scoped'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-media.html b/testing/web-platform/tests/css/css-cascade/scope-media.html new file mode 100644 index 0000000000..ae2e7694f7 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-media.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>@scope - inner @media</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://drafts.csswg.org/css-conditional-3/#at-ruledef-media"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @scope (.a) { + @media (width > 0px) { + :scope { + z-index: 1; + } + + .b { + background-color: green; + } + } + } +</style> +<main> + <div class=a> + <div class=b> + </div> + </div> + <div class=b></div> +</main> +<script> + test(() => { + let a = document.querySelector('main > .a'); + let b = document.querySelector('main > .a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).backgroundColor, 'rgb(0, 128, 0)'); + + let out_of_scope_b = document.querySelector('main > .b'); + assert_equals(getComputedStyle(out_of_scope_b).backgroundColor, 'rgba(0, 0, 0, 0)'); + }, 'Style rules within @media are scoped'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-name-defining-rules.html b/testing/web-platform/tests/css/css-cascade/scope-name-defining-rules.html new file mode 100644 index 0000000000..9f4b342887 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-name-defining-rules.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<title>@scope - name-defining at-rules</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<main id=main></main> + +<template id=test_keyframes> + <style> + @scope (#inner) { + @keyframes --my-anim { + from { background-color: rgb(0, 0, 255); } + to { background-color: rgb(0, 0, 255); } + } + } + @scope (#outer) { + @keyframes --my-anim { + from { background-color: rgb(0, 128, 0); } + to { background-color: rgb(0, 128, 0); } + } + } + #animating { + animation: --my-anim 1000s linear; + } + </style> + <div id=outer> + <div id=inner> + <div id=animating></div> + <div> + </div> +</template> +<script> + test((t) => { + main.append(test_keyframes.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(animating).backgroundColor, 'rgb(0, 128, 0)'); + }, '@keyframes is unaffected by @scope'); +</script> + +<template id=test_keyframes_non_matching> + <style> + @scope (#nomatch) { + @keyframes --my-anim { + from { background-color: rgb(0, 128, 0); } + to { background-color: rgb(0, 128, 0); } + } + } + #animating { + animation: --my-anim 1000s linear; + } + </style> + <div id=animating></div> +</template> +<script> + test((t) => { + main.append(test_keyframes_non_matching.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(animating).backgroundColor, 'rgb(0, 128, 0)'); + }, '@keyframes is unaffected by non-matching @scope'); +</script> + +<template id=test_property> + <style> + @scope (#inner) { + @property --my-prop { + syntax: "<length>"; + initial-value: 1px; + inherits: false; + } + } + @scope (#outer) { + @property --my-prop { + syntax: "<length>"; + initial-value: 2px; + inherits: false; + } + } + </style> + <div id=outer> + <div id=inner> + <div id=subject></div> + <div> + </div> +</template> +<script> + test((t) => { + main.append(test_property.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(subject).getPropertyValue('--my-prop'), '2px'); + }, '@property is unaffected by @scope'); +</script> + +<template id=test_property_non_matching> + <style> + @scope (#nomatch) { + @property --my-prop { + syntax: "<length>"; + initial-value: 2px; + inherits: false; + } + } + </style> + <div id=subject></div> +</template> +<script> + test((t) => { + main.append(test_property_non_matching.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(subject).getPropertyValue('--my-prop'), '2px'); + }, '@property is unaffected by non-matching @scope'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-nesting.html b/testing/web-platform/tests/css/css-cascade/scope-nesting.html new file mode 100644 index 0000000000..d299ba3037 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-nesting.html @@ -0,0 +1,545 @@ +<!DOCTYPE html> +<title>@scope - nesting (&)</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://drafts.csswg.org/css-nesting-1/#nest-selector"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<main id=main></main> + +<template id=test_nest_scope_end> + <div> + <style> + @scope (.a) to (& > &) { + * { z-index:1; } + } + </style> + <div class=a> <!-- This scope is limited by the element below. --> + <div class=a> <!-- This scope is limited by its own root. --> + <div id=below></div> + </div> + </div> + </div> + <div id=outside></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nest_scope_end.content.cloneNode(true)); + + assert_equals(getComputedStyle(below).zIndex, 'auto'); + assert_equals(getComputedStyle(outside).zIndex, 'auto'); +}, 'Nesting-selector in <scope-end>'); +</script> + +<template id=test_nest_scope_end_implicit_scope> + <div> + <style> + /* (.b) behaves like (:scope .b), due :scope being prepended + implicitly. */ + @scope (.a) to (.b) { + :scope { z-index:1; } + } + + /* Should not match, since <scope-end> refers to the scope itself. */ + @scope (.a) to (.b:scope) { + :scope { z-index:42; } + } + </style> + <div class="a b"> + <div class=b> + <div id=below></div> + </div> + </div> + </div> + <div id=outside></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nest_scope_end_implicit_scope.content.cloneNode(true)); + let a = document.querySelector('.a'); + let b = document.querySelector('.a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).zIndex, 'auto'); + assert_equals(getComputedStyle(below).zIndex, 'auto'); + assert_equals(getComputedStyle(outside).zIndex, 'auto'); +}, 'Implicit :scope in <scope-end>'); +</script> + +<template id=test_relative_selector_scope_end> + <div> + <style> + @scope (.a) to (> .b) { + *, :scope { z-index:1; } + } + </style> + <div class="a b"> + <div class=b> + <div id=below></div> + </div> + </div> + </div> + <div id=outside></div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_relative_selector_scope_end.content.cloneNode(true)); + let a = document.querySelector('.a'); + let b = document.querySelector('.a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).zIndex, 'auto'); + assert_equals(getComputedStyle(below).zIndex, 'auto'); + assert_equals(getComputedStyle(outside).zIndex, 'auto'); +}, 'Relative selectors in <scope-end>'); +</script> + +<template id=test_inner_nest> + <div> + <style> + @scope (.a) { + & + & { + z-index:1; + } + } + </style> + <div class=a> + <div id=inner1 class=a></div> + <div id=inner2 class=a></div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_inner_nest.content.cloneNode(true)); + + assert_equals(getComputedStyle(inner1).zIndex, 'auto'); + assert_equals(getComputedStyle(inner2).zIndex, '1'); +}, 'Nesting-selector in the scope\'s <stylesheet>'); +</script> + +<template id=test_parent_in_pseudo_scope> + <div> + <style> + @scope (#div) { + :scope { + z-index: 1; + & { + z-index: 2; + } + } + } + </style> + <div id=div></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_parent_in_pseudo_scope.content.cloneNode(true)); + + assert_equals(getComputedStyle(div).zIndex, '2'); +}, 'Nesting-selector within :scope rule'); +</script> + +<template id=test_parent_in_pseudo_scope_double> + <div> + <style> + @scope (#div) { + :scope { + z-index: 1; + & { + & { + z-index: 2; + } + } + } + } + </style> + <div id=div></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_parent_in_pseudo_scope_double.content.cloneNode(true)); + + assert_equals(getComputedStyle(div).zIndex, '2'); +}, 'Nesting-selector within :scope rule (double nested)'); +</script> + +<template id=test_scope_within_style_rule> + <div> + <style> + .a { + @scope (.b) { + .c { z-index: 1; } + } + } + </style> + <div class=a> + <div class=b> + <div class=c> + </div> + </div> + <div id=out_of_scope class=c> + </div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_scope_within_style_rule.content.cloneNode(true)); + + let c = document.querySelector('.c'); + assert_equals(getComputedStyle(c).zIndex, '1'); + assert_equals(getComputedStyle(out_of_scope).zIndex, 'auto'); +}, '@scope nested within style rule'); +</script> + +<template id=test_parent_pseudo_in_nested_scope_start> + <div> + <style> + .a { + @scope (&.b) { + :scope { z-index: 1; } + } + } + </style> + <div class=a></div> + <div class=b></div> + <div class="a b"></div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_parent_pseudo_in_nested_scope_start.content.cloneNode(true)); + + let a = document.querySelector('.a:not(.b)'); + let b = document.querySelector('.b:not(.a)'); + let ab = document.querySelector('.a.b'); + assert_equals(getComputedStyle(a).zIndex, 'auto'); + assert_equals(getComputedStyle(b).zIndex, 'auto'); + assert_equals(getComputedStyle(ab).zIndex, '1'); +}, 'Parent pseudo class within scope-start'); +</script> + +<template id=test_parent_pseudo_in_nested_scope_end> + <div> + <style> + .a { + /* Note that & in <scope-end> refers to <scope-start>, + not the outer style rule. */ + @scope (&.b) to (&.c) { + :scope, * { z-index: 1; } + } + } + </style> + <div class="a b"> + <div class="a c"> + <div class="a b c"> + </div> + </div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_parent_pseudo_in_nested_scope_end.content.cloneNode(true)); + + let ab = document.querySelector('.a.b:not(.c)'); + let ac = document.querySelector('.a.c:not(.b)'); + let abc = document.querySelector('.a.b.c'); + assert_equals(getComputedStyle(ab).zIndex, '1'); + assert_equals(getComputedStyle(ac).zIndex, '1'); + assert_equals(getComputedStyle(abc).zIndex, 'auto', 'limit element is not in scope'); +}, 'Parent pseudo class within scope-end'); +</script> + +<template id=test_parent_pseudo_in_nested_scope_body> + <div> + <style> + .a { + @scope (.b) { + /* The & points to <scope-start>, which contains an implicit & + which points to .a. */ + &.c { z-index: 1; } + } + } + </style> + <div class=a> + <div class=b> + <div class="c"></div> + <div class="a c"></div> + <div class="a b c" matching></div> + </div> + </div> + <div> + <div class=a></div> + <div class=b></div> + <div class=c></div> + <div class="a b"></div> + <div class="a c"></div> + <div class="b c"></div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_parent_pseudo_in_nested_scope_body.content.cloneNode(true)); + + let matching = main.querySelectorAll("div[matching]"); + let non_matching = main.querySelectorAll("div:not([matching])"); + + for (let m of matching) { + assert_equals(getComputedStyle(m).zIndex, '1', `matching: ${m.nodeName}${m.className}`); + } + for (let m of non_matching) { + assert_equals(getComputedStyle(m).zIndex, 'auto', `non-matching: ${m.nodeName}${m.className}`); + } +}, 'Parent pseudo class within body of nested @scope'); +</script> + +<template id=test_direct_declarations_in_nested_scope> + <div> + <style> + .a { + @scope (.b) { + z-index: 1; + } + } + </style> + <div class=a> + <div class=b> + <div class="c"></div> + </div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_direct_declarations_in_nested_scope.content.cloneNode(true)); + + let a = document.querySelector('.a'); + let b = document.querySelector('.b'); + let c = document.querySelector('.c'); + assert_equals(getComputedStyle(a).zIndex, 'auto'); + assert_equals(getComputedStyle(b).zIndex, '1'); + assert_equals(getComputedStyle(c).zIndex, 'auto'); +}, 'Implicit rule within nested @scope '); +</script> + +<template id=test_direct_declarations_in_nested_scope_proximity> + <div> + <style> + .a { + /* We're supposed to prepend :scope to this declaration. If we do that, + then :where() does not matter, since :scope does not gain any + specificity from the enclosing @scope rule. However, if an + implementation incorrectly prepends & instead, then :where() is + needed to avoid the test incorrectly passing due to specificity. */ + @scope (:where(&) .b) { + z-index: 1; /* Should win due to proximity */ + } + } + .b { z-index: 2; } + </style> + <div class=a> + <div class="b x"> + <div class=c> + </div> + </div> + </div> + </div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_direct_declarations_in_nested_scope_proximity.content.cloneNode(true)); + + let a = document.querySelector('.a'); + let b = document.querySelector('.b'); + let c = document.querySelector('.c'); + assert_equals(getComputedStyle(a).zIndex, 'auto'); + assert_equals(getComputedStyle(b).zIndex, '1'); + assert_equals(getComputedStyle(c).zIndex, 'auto'); +}, 'Implicit rule within nested @scope (proximity)'); +</script> + +<template id=test_nested_scope_inside_an_is> + <div> + <style> + @scope (.a) { + .b { + /* When nesting, because we’re inside a defined scope, + the `:scope` should reference the scoping root node properly, and + check for the presence of an extra class on it, essentially + being equal to `:scope.x .b { z-index: 1 }`. */ + &:is(:scope.x *) { + z-index: 1; + } + /* This should not match, as we have a defined scope, and should + not skip to the root. */ + &:is(:root:scope *) { + z-index: 2; + } + } + /* The nested case can be though of the following when expanded: */ + .c:is(:scope.x *) { + z-index: 3; + } + } + </style> + <div class="b"> + </div> + <div class="a x"> + <div class="b"> + </div> + <div class="c"> + </div> + </div> +</div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nested_scope_inside_an_is.content.cloneNode(true)); + + let b_outside = document.querySelector('.b'); + let b_inside = document.querySelector('.a .b'); + let c = document.querySelector('.c'); + assert_equals(getComputedStyle(b_outside).zIndex, 'auto'); + assert_equals(getComputedStyle(b_inside).zIndex, '1'); + assert_equals(getComputedStyle(c).zIndex, '3'); +}, 'Nested :scope inside an :is'); +</script> + +<template id=test_nested_scope_pseudo> + <div> + <style> + @scope (.b) { + .a:not(:scope) { + & :scope { + z-index: 1; + } + } + } + </style> + <div class="b"> + </div> + <div class="a"> + <div class="b"> + </div> + </div> +</div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nested_scope_pseudo.content.cloneNode(true)); + + let b_outside = document.querySelector('.b'); + let b_inside = document.querySelector('.a .b'); + assert_equals(getComputedStyle(b_outside).zIndex, 'auto'); + assert_equals(getComputedStyle(b_inside).zIndex, '1'); +}, ':scope within nested and scoped rule'); +</script> + +<template id=test_nested_scope_pseudo_implied> + <div> + <style> + @scope (.b) { + .a:not(:scope) { + :scope { /* & implied */ + z-index: 1; + } + } + } + </style> + <div class="b"> + </div> + <div class="a"> + <div class="b"> + </div> + </div> +</div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nested_scope_pseudo_implied.content.cloneNode(true)); + + let b_outside = document.querySelector('.b'); + let b_inside = document.querySelector('.a .b'); + assert_equals(getComputedStyle(b_outside).zIndex, 'auto'); + assert_equals(getComputedStyle(b_inside).zIndex, '1'); +}, ':scope within nested and scoped rule (implied &)'); +</script> + +<template id=test_nested_scope_pseudo_relative> + <div> + <style> + @scope (.b) { + .a:not(:scope) { + > :scope { /* & implied */ + z-index: 1; + } + } + } + </style> + <div class="b"> + </div> + <div class="a"> + <div class="b"> + </div> + </div> +</div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_nested_scope_pseudo_relative.content.cloneNode(true)); + + let b_outside = document.querySelector('.b'); + let b_inside = document.querySelector('.a .b'); + assert_equals(getComputedStyle(b_outside).zIndex, 'auto'); + assert_equals(getComputedStyle(b_inside).zIndex, '1'); +}, ':scope within nested and scoped rule (relative)'); +</script> + +<template id=test_scoped_nested_group_rule> + <div> + <style> + @scope (.a) { + .b:not(:scope) { + @media (width) { + z-index: 1; + } + } + } + </style> + <div class="b"> + </div> + <div class="a"> + <div class="b"> + </div> + </div> +</div> +</template> +<script> +test((t) => { + t.add_cleanup(() => main.replaceChildren()); + main.append(test_scoped_nested_group_rule.content.cloneNode(true)); + + let b_outside = document.querySelector('.b'); + let b_inside = document.querySelector('.a .b'); + assert_equals(getComputedStyle(b_outside).zIndex, 'auto'); + assert_equals(getComputedStyle(b_inside).zIndex, '1'); +}, 'Scoped nested group rule'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-proximity.html b/testing/web-platform/tests/css/css-cascade/scope-proximity.html new file mode 100644 index 0000000000..c133a71e9a --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-proximity.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<title>@scope - proximity to root</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-proximity"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +function test_scope(script_element, callback_fn, description) { + test((t) => { + // The provided <script> element must be an immedate subsequent sibling of + // a <template> element. + let template_element = script_element.previousElementSibling; + assert_equals(template_element.tagName, 'TEMPLATE'); + + t.add_cleanup(() => { + while (main.firstChild) + main.firstChild.remove() + }); + + main.append(template_element.content.cloneNode(true)); + + callback_fn(); + }, description); +} + +function assert_green(selector) { + assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 128, 0)'); +} +function assert_not_green(selector) { + assert_equals(getComputedStyle(main.querySelector(selector)).backgroundColor, 'rgb(0, 0, 0)'); +} +</script> +<style> + main * { + background-color: black; + } +</style> +<main id=main> +</main> + +<template> + <style> + .item { + padding: 0px; + border: 5px solid red; + } + + @scope (.light) { + [id] { border-color: rgb(100, 100, 100); } + } + + @scope (.dark) { + [id] { border-color: rgb(200, 200, 200); } + } + </style> + <div class=light> + <div id=item1> + <div class=dark> + <div id=item2> + <div class=light> + <div id=item3> + <div class=dark> + <div id=item4></div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_equals(getComputedStyle(item1).borderColor, 'rgb(100, 100, 100)'); + assert_equals(getComputedStyle(item2).borderColor, 'rgb(200, 200, 200)'); + assert_equals(getComputedStyle(item3).borderColor, 'rgb(100, 100, 100)'); + assert_equals(getComputedStyle(item4).borderColor, 'rgb(200, 200, 200)'); +}, 'Alternating light/dark'); +</script> + + +<template> + <style> + @scope (.b) { + [id] { border-color:green; } + } + @scope (.a) { + [id] { border-color:red; } + } + </style> + <div class=a> + <div class=b> + <span id=item></span> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)'); +}, 'Proximity wins over order of appearance'); +</script> + + +<template> + <style> + @scope (.a) { + span[id] { border-color:green; } + } + @scope (.b) { + [id] { border-color:red; } + } + </style> + <div class=a> + <div class=b> + <span id=item></span> + </div> + </div> +</template> +<script> +test_scope(document.currentScript, () => { + assert_equals(getComputedStyle(item).borderColor, 'rgb(0, 128, 0)'); +}, 'Specificity wins over proximity'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-shadow.tentative.html b/testing/web-platform/tests/css/css-cascade/scope-shadow.tentative.html new file mode 100644 index 0000000000..83a468bd07 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-shadow.tentative.html @@ -0,0 +1,163 @@ +<!DOCTYPE html> +<title>@scope - ShadowDOM</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9025"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id=host_plain> + <template shadowrootmode=open> + <style> + @scope (:host) { + .a { + z-index: 1; + } + } + </style> + <div class=a> + </div> + </template> +</div> +<script> + test(() => { + let a = host_plain.shadowRoot.querySelector('.a'); + assert_equals(getComputedStyle(a).zIndex, '1'); + }, '@scope can match :host'); +</script> + +<div id=host_functional> + <template shadowrootmode=open> + <style> + @scope (:host(div)) { + .a { + z-index: 1; + } + } + /* Should not match: */ + @scope (:host(span)) { + .a { + z-index: 42; + } + } + </style> + <div class=a> + </div> + </template> +</div> +<script> + test(() => { + let a = host_functional.shadowRoot.querySelector('.a'); + assert_equals(getComputedStyle(a).zIndex, '1'); + }, '@scope can match :host(...)'); +</script> + +<div id=host_scope_subject> + <template shadowrootmode=open> + <style> + @scope (:host) { + :scope { + z-index: 1; + } + } + </style> + <div class=a> + </div> + </template> +</div> +<script> + test(() => { + assert_equals(getComputedStyle(host_scope_subject).zIndex, '1'); + }, ':scope matches host via the scoping root'); +</script> + +<div id=host_scope_subject_is> + <div class=host> + <template shadowrootmode=open> + <style> + /* Should not match host, nor outside shadow. */ + :is(:scope, .a, .host) { + z-index: 2; + } + + @scope (:host) { + :is(:scope, .a) { + z-index: 1; + } + } + </style> + <div class=a> + </div> + </template> + </div> + <div class=a> + </div> +</div> +<script> + test(() => { + let host = host_scope_subject_is.querySelector('.host'); + assert_equals(getComputedStyle(host).zIndex, '1'); + let a = host.shadowRoot.querySelector('.a'); + assert_equals(getComputedStyle(a).zIndex, '1'); + + let a_outside = host_scope_subject_is.querySelector('.a'); + assert_equals(getComputedStyle(a_outside).zIndex, 'auto'); + }, ':scope within :is() matches host via the scoping root'); +</script> + +<!-- Tentative. https://github.com/w3c/csswg-drafts/issues/9178 --> +<div id=implicit_scope_shadow_parent> + <div class=host> + <template shadowrootmode=open> + <style> + @scope { + /* Matches host */ + :scope { + z-index: 1; + } + :scope > .a { + z-index: 2; + } + } + </style> + <div class=a> + </div> + </template> + </div> +</div> +<script> + test(() => { + let host = implicit_scope_shadow_parent.querySelector('.host'); + let a = host.shadowRoot.querySelector('.a'); + assert_equals(getComputedStyle(host).zIndex, '1'); + assert_equals(getComputedStyle(a).zIndex, '2'); + }, 'Implicit @scope as direct child of shadow root'); +</script> + +<!-- Tentative. https://github.com/w3c/csswg-drafts/issues/9178 --> +<div id=implicit_scope_constructed> + <div class=host> + <template shadowrootmode=open> + <div class=a> + </div> + </template> + </div> +</div> +<script> + test(() => { + let host = implicit_scope_constructed.querySelector('.host'); + let sheet = new CSSStyleSheet(); + sheet.replaceSync(` + @scope { + :scope { + z-index: 1; + } + :scope .a { + z-index: 2; + } + `); + host.shadowRoot.adoptedStyleSheets = [sheet]; + let a = host.shadowRoot.querySelector('.a'); + assert_equals(getComputedStyle(host).zIndex, '1'); + assert_equals(getComputedStyle(a).zIndex, '2'); + }, 'Implicit @scope in construted stylesheet'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-specificity.html b/testing/web-platform/tests/css/css-cascade/scope-specificity.html new file mode 100644 index 0000000000..0f48c605a8 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-specificity.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<title>@scope - specificty</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style id=style> +</style> +<main id=main> + <div id=a class=a> + <div id=b class=b> + </div> + </div> +</main> +<script> + +// Format a scoped style rule using the selector at scoped_selector's last element, +// with each preceding array item representing an enclosing @scope rule. +// +// Example: +// +// scoped_selector=['@scope (foo)', '@scope (bar)', 'div'] +// declarations='z-index:42' +// => '@scope (foo) { @scope (bar) { div { z-index:42 } } }' +function format_scoped_rule(scoped_selector, declarations) { + if (scoped_selector.length < 2) { + throw "Fail"; + } + let scope_prelude = scoped_selector[0]; + let remainder = scoped_selector.slice(1); + let content = remainder.length == 1 + ? `${remainder[0]} { ${declarations} }` + : format_scoped_rule(remainder, declarations); + return `${scope_prelude} { ${content} }`; +} + +// Verify that the specificity of 'scoped_selector' is the same +// as the specificity of 'ref_selector'. Both selectors must select +// an element within #main. +function test_scope_specificity(scoped_selector, ref_selector) { + test(() => { + let element = main.querySelector(ref_selector); + assert_not_equals(element, null); + + let scoped_rule = format_scoped_rule(scoped_selector, 'z-index:1'); + let ref_rule = `:is(${ref_selector}) { z-index:2 }`; + + style.textContent = `${scoped_rule}`; + assert_equals(getComputedStyle(element).zIndex, '1', 'scoped rule'); + + style.textContent = `${ref_rule}`; + assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped rule'); + + // The scoped rule should win due to proximity. + style.textContent = `${scoped_rule} ${ref_rule}`; + assert_equals(getComputedStyle(element).zIndex, '1', 'scoped + unscoped'); + + // The scoped rule should win due to proximity (reverse). + style.textContent = `${ref_rule} ${scoped_rule}`; + assert_equals(getComputedStyle(element).zIndex, '1', 'unscoped + scoped'); + + // Add one (1) to the specificty of the unscoped rule. This should + // cause the unscoped rule to win instead. + style.textContent = `div${ref_rule} ${scoped_rule}`; + assert_equals(getComputedStyle(element).zIndex, '2', 'unscoped + scoped'); + }, format_scoped_rule(scoped_selector, '')); +} + +test_scope_specificity(['@scope (#main)', '.b'], '.b'); +test_scope_specificity(['@scope (#main) to (.b)', '.a'], '.a'); +test_scope_specificity(['@scope (#main, .foo, .bar)', '#a'], '#a'); +test_scope_specificity(['@scope (#main)', 'div.b'], 'div.b'); +test_scope_specificity(['@scope (#main)', ':scope .b'], '.a .b'); +test_scope_specificity(['@scope (#main)', '& .b'], '#main .b'); +test_scope_specificity(['@scope (#main)', 'div .b'], 'div .b'); +test_scope_specificity(['@scope (#main)', '@scope (.a)', '.b'], '.b'); + +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-starting-style.html b/testing/web-platform/tests/css/css-cascade/scope-starting-style.html new file mode 100644 index 0000000000..b9b0580b38 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-starting-style.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>@scope - inner @starting-style</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/pull/8876"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @scope (.a) { + @starting-style { + :scope { + width: 100px; + } + + .b { + width: 100px; + } + } + } + + .a, .b { + transition: width 100s steps(2, start); /* 50% progress */ + width: 200px; + } +</style> +<main> + <div class=a> + <div class=b> + </div> + </div> + <div class=b></div> +</main> +<script> + test(() => { + let a = document.querySelector('main > .a'); + let b = document.querySelector('main > .a > .b'); + assert_equals(getComputedStyle(a).width, '150px'); + assert_equals(getComputedStyle(b).width, '150px'); + + let out_of_scope_b = document.querySelector('main > .b'); + assert_equals(getComputedStyle(out_of_scope_b).width, '200px'); + }, 'Style rules within @starting-style are scoped'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-supports.html b/testing/web-platform/tests/css/css-cascade/scope-supports.html new file mode 100644 index 0000000000..9be41de0e9 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-supports.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>@scope - inner @supports</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scope-atrule"> +<link rel="help" href="https://drafts.csswg.org/css-conditional-3/#at-supports"> +<link rel="help" href="https://drafts.csswg.org/css-cascade-5/#scope-scope"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + @scope (.a) { + @supports (width:0px) { + :scope { + z-index: 1; + } + + .b { + background-color: green; + } + } + } +</style> +<main> + <div class=a> + <div class=b> + </div> + </div> + <div class=b></div> +</main> +<script> + test(() => { + let a = document.querySelector('main > .a'); + let b = document.querySelector('main > .a > .b'); + assert_equals(getComputedStyle(a).zIndex, '1'); + assert_equals(getComputedStyle(b).backgroundColor, 'rgb(0, 128, 0)'); + + let out_of_scope_b = document.querySelector('main > .b'); + assert_equals(getComputedStyle(out_of_scope_b).backgroundColor, 'rgba(0, 0, 0, 0)'); + }, 'Style rules within @supports are scoped'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-visited-cssom.html b/testing/web-platform/tests/css/css-cascade/scope-visited-cssom.html new file mode 100644 index 0000000000..aba4b752b2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-visited-cssom.html @@ -0,0 +1,314 @@ +<!DOCTYPE html> +<title>@scope - :visited and CSSOM</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<main id=main></main> + +<style> + :where(:visited, :link), :where(div) { + background-color: white; + } +</style> + +<!-- + Both #visited and #unvisited should appear to be in an unvisited state + through getComputedStyle. +--> + +<!-- :visited/:link in scoped selector --> + +<template id=test_link> + <style> + @scope (main) { + :link { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_link.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':link as scoped selector'); +</script> + +<template id=test_visited> + <style> + @scope (main) { + :visited { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_visited.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':visited as scoped selector'); +</script> + +<template id=test_not_link> + <style> + @scope (main) { + :not(:link) { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_link.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':not(:link) as scoped selector'); +</script> + +<template id=test_not_visited> + <style> + @scope (main) { + :not(:visited) { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_visited.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':not(:visited) as scoped selector'); +</script> + +<!-- :visited/:link in root --> + +<template id=test_link_root> + <style> + @scope (main :link) { + div { background-color: green; } + } + </style> + <a id=visited href=""><div></div></a> + <a id=unvisited href="x"><div></div></a> +</template> +<script> +test((t) => { + main.append(test_link_root.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited.firstElementChild).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited.firstElementChild).backgroundColor, 'rgb(0, 128, 0)'); +}, ':link as scoping root'); +</script> + +<template id=test_visited_root> + <style> + @scope (main :visited) { + div { background-color: green; } + } + </style> + <a id=visited href=""><div></div></a> + <a id=unvisited href="x"><div></div></a> +</template> +<script> +test((t) => { + main.append(test_visited_root.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited.firstElementChild).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited.firstElementChild).backgroundColor, 'rgb(255, 255, 255)'); +}, ':visited as scoping root'); +</script> + +<template id=test_not_visited_root> + <style> + @scope (main :not(:visited)) { + div { background-color: green; } + } + </style> + <a id=visited href=""><div></div></a> + <a id=unvisited href="x"><div></div></a> +</template> +<script> +test((t) => { + main.append(test_not_visited_root.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited.firstElementChild).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited.firstElementChild).backgroundColor, 'rgb(0, 128, 0)'); +}, ':not(:visited) as scoping root'); +</script> + +<template id=test_not_link_root> + <style> + @scope (main :not(:link)) { + div { background-color: green; } + } + </style> + <a id=visited href=""><div></div></a> + <a id=unvisited href="x"><div></div></a> +</template> +<script> +test((t) => { + main.append(test_not_link_root.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited.firstElementChild).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited.firstElementChild).backgroundColor, 'rgb(255, 255, 255)'); +}, ':not(:link) as scoping root'); +</script> + +<!-- :visited/:link in scoping root, with inner :scope --> + +<template id=test_link_root_scope> + <style> + @scope (main :link) { + :scope { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_link_root_scope.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':link as scoping root, :scope'); +</script> + +<template id=test_visited_root_scope> + <style> + @scope (main :visited) { + :scope { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_visited_root_scope.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':visited as scoping root, :scope'); +</script> + +<template id=test_not_visited_root_scope> + <style> + @scope (main :not(:visited)) { + :scope { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_visited_root_scope.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':not(:visited) as scoping root, :scope'); +</script> + +<template id=test_not_link_root_scope> + <style> + @scope (main :not(:link)) { + :scope { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_link_root_scope.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':not(:link) as scoping root, :scope'); +</script> + +<!-- :visited/:link in scoping limit --> + +<template id=test_link_scoping_limit> + <style> + @scope (main) to (:link) { + * { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_link_scoping_limit.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':link as scoping limit'); +</script> + +<template id=test_visited_scoping_limit> + <style> + @scope (main) to (:visited) { + * { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_visited_scoping_limit.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':visited as scoping limit'); +</script> + +<template id=test_not_link_scoping_limit> + <style> + @scope (main) to (:not(:link)) { + * { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_link_scoping_limit.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(0, 128, 0)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(0, 128, 0)'); +}, ':not(:link) as scoping limit'); +</script> + +<template id=test_not_visited_scoping_limit> + <style> + @scope (main) to (:not(:visited)) { + * { background-color: green; } + } + </style> + <a id=visited href=""></a> + <a id=unvisited href="x"></a> +</template> +<script> +test((t) => { + main.append(test_not_visited_scoping_limit.content.cloneNode(true)); + t.add_cleanup(() => main.replaceChildren()); + assert_equals(getComputedStyle(visited).backgroundColor, 'rgb(255, 255, 255)'); + assert_equals(getComputedStyle(unvisited).backgroundColor, 'rgb(255, 255, 255)'); +}, ':not(:visited) as scoping limit'); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-visited-ref.html b/testing/web-platform/tests/css/css-cascade/scope-visited-ref.html new file mode 100644 index 0000000000..91efd65921 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-visited-ref.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<title>@scope - :visited</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> + +<style> + /* The visited background-color magically gets the alpha of the + unvisited color, which by default is rgba(0, 0, 0, 0). Set alpha to + 255 so that visited colors also gets this alpha. */ + * { background-color: rgba(255, 255, 255, 255); } +</style> + +<!-- :visited via :scope in subject --> +<div> + <a id=visited href="" style="background-color:coral"> + Visited <span>Span</span> + </a> +</div> + +<!-- :link via :scope in subject --> +<div> + <a id=unvisited href="x" style="background-color:skyblue"> + Unvisited <span>Span</span> + </a> +</div> + +<!-- :visited via :scope in non-subject --> +<div> + <a id=visited href=""> + Visited <span style="background-color:coral">Span</span> + </a> +</div> + +<!-- :link via :scope in non-subject --> +<div> + <a id=unvisited href="x"> + Unvisited <span style="background-color:skyblue">Span</span> + </a> +</div> + +<!-- :visited in scope-end --> +<div> + <main> + Main + <a id=visited href=""> + Visited <span>Span</span> + </a> + </main> +</div> + +<!-- :visited in scope-end, unvisited link --> +<div> + <main> + Main + <a id=unvisited href="x" style="background-color:skyblue"> + Unvisited <span>Span</span> + </a> + </main> +</div> + +<!-- :link in scope-end --> +<div> + <main> + Main + <a id=unvisited href="x"> + Unvisited <span>Span</span> + </a> + </main> +</div> + +<!-- :link in scope-end, visited link --> +<div> + <main> + Main + <a id=visited href="" style="background-color:coral"> + Visited <span>Span</span> + </a> + </main> +</div> + +<!-- :link in scope-end, visited link --> +<div> + <main> + Main + <a id=outer_visited href=""> + Visited1 + </a> + </main> +</div> +<script> + // Insert inner <a> with JS, since the parser can't produce this result. + let inner_a = document.createElement('a'); + inner_a.setAttribute('href', ''); + inner_a.textContent = 'Visited2'; + outer_visited.append(inner_a); +</script> diff --git a/testing/web-platform/tests/css/css-cascade/scope-visited.html b/testing/web-platform/tests/css/css-cascade/scope-visited.html new file mode 100644 index 0000000000..392aeb667b --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/scope-visited.html @@ -0,0 +1,215 @@ +<!DOCTYPE html> +<title>@scope - :visited</title> +<link rel="help" href="https://drafts.csswg.org/css-cascade-6/#scoped-styles"> +<link rel="help" href="https://drafts.csswg.org/selectors-4/#link"> +<link rel="match" href="scope-visited-ref.html"> + +<!-- Sub-tests use ShadowDOM to stay isolated from eachother. --> + +<!-- :visited via :scope in subject --> +<div> + <template shadowrootmode=open> + <a id=visited href=""> + Visited <span>Span</span> + </a> + <style> + /* The visited background-color magically gets the alpha of the + unvisited color, which by default is rgba(0, 0, 0, 0). Set alpha to + 255 so that visited colors also gets this alpha. */ + * { background-color: rgba(255, 255, 255, 255); } + + @scope (:visited) { + :scope { background-color: coral; } + } + @scope (:link) { + :scope { background-color: skyblue; } /* Does not match. */ + } + </style> + </template> +</div> + +<!-- :link via :scope in subject --> +<div> + <template shadowrootmode=open> + <a id=unvisited href="x"> + Unvisited <span>Span</span> + </a> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (:link) { + :scope { background-color: skyblue; } + } + @scope (:visited) { + :scope { background-color: coral; } /* Does not match. */ + } + </style> + </template> +</div> + +<!-- :visited via :scope in non-subject --> +<div> + <template shadowrootmode=open> + <a id=visited href=""> + Visited <span>Span</span> + </a> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (:visited) { + :scope span { background-color: coral; } + } + @scope (:link) { + :scope span { background-color: skyblue; } /* Does not match. */ + } + </style> + </template> +</div> + +<!-- :link via :scope in non-subject --> +<div> + <template shadowrootmode=open> + <a id=unvisited href="x"> + Unvisited <span>Span</span> + </a> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (:link) { + :scope span { background-color: skyblue; } + } + @scope (:visited) { + :scope span { background-color: coral; } /* Does not match. */ + } + </style> + </template> +</div> + +<!-- :visited in scope-end --> +<div> + <template shadowrootmode=open> + <main> + Main + <a id=visited href=""> + Visited <span>Span</span> + </a> + </main> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (main) to (:visited) { + /* Does not match, because #visited is not in scope. */ + :scope :visited { background-color: coral; } + } + @scope (main) { + :scope :link { background-color: skyblue; } /* Also doesn't match. */ + } + </style> + </template> +</div> + +<!-- :visited in scope-end, unvisited link --> +<div> + <template shadowrootmode=open> + <main> + Main + <a id=unvisited href="x"> + Unvisited <span>Span</span> + </a> + </main> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (main) to (:visited) { + /* Does not match, because #unvisited does not match it. */ + :scope :visited { background-color: coral; } + } + @scope (main) { + /* Should match, because the scope-end selector (:visited) does not + match anything, hence we are in-scope. */ + :scope :link { background-color: skyblue; } + } + </style> + </template> +</div> + +<!-- :link in scope-end --> +<div> + <template shadowrootmode=open> + <main> + Main + <a id=unvisited href="x"> + Unvisited <span>Span</span> + </a> + </main> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (main) to (:link) { + /* Does not match, because #unvisited is not in scope. */ + :scope :link { background-color: skyblue; } + } + @scope (main) { + :scope :visited { background-color: coral; } /* Also doesn't match. */ + } + </style> + </template> +</div> + +<!-- :link in scope-end, visited link --> +<div> + <template shadowrootmode=open> + <main> + Main + <a id=visited href=""> + Visited <span>Span</span> + </a> + </main> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + @scope (main) to (:link) { + /* Does not match, because #visited does not match it. */ + :scope :link { background-color: skyblue; } + } + @scope (main) { + /* Should match, because the scope-end selector (:visited) does not + match anything, hence we are in-scope. */ + :scope :visited { background-color: coral; } + } + </style> + </template> +</div> + +<!-- :visited within :visited --> +<div id=visited_in_visited> + <template shadowrootmode=open> + <main> + Main + <a href=""> + Visited1 + </a> + </main> + <style> + * { background-color: rgba(255, 255, 255, 255); } + + /* Should not match since visited-link matching stops applying + once a link is seen. */ + @scope (:visited) { + :scope > :visited { background-color: coral; } + } + </style> + </template> +</div> + +<script> + window.onload = () => { + // Insert the inner :visited link with JS, since the parser + // can't produce this. + let outer_a = visited_in_visited.shadowRoot.querySelector('main > a'); + let inner_a = document.createElement('a'); + inner_a.setAttribute('href', ''); + inner_a.textContent = 'Visited2'; + outer_a.append(inner_a); + } +</script> diff --git a/testing/web-platform/tests/css/css-cascade/support/test-green.css b/testing/web-platform/tests/css/css-cascade/support/test-green.css new file mode 100644 index 0000000000..da8e1014d2 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/support/test-green.css @@ -0,0 +1,4 @@ +.test { + background: green; + color: green; +} diff --git a/testing/web-platform/tests/css/css-cascade/support/test-red.css b/testing/web-platform/tests/css/css-cascade/support/test-red.css new file mode 100644 index 0000000000..bb309fcd57 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/support/test-red.css @@ -0,0 +1,4 @@ +.test { + background: red; + color: red; +} diff --git a/testing/web-platform/tests/css/css-cascade/tons-of-declarations-crash.html b/testing/web-platform/tests/css/css-cascade/tons-of-declarations-crash.html new file mode 100644 index 0000000000..50a32c1938 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/tons-of-declarations-crash.html @@ -0,0 +1,6 @@ +<!doctype html> +<style></style> +<script> +document.querySelector('style').innerText = `input { border: 1px solid }`.repeat(2 << 16); +</script> +<input> diff --git a/testing/web-platform/tests/css/css-cascade/unset-val-001.html b/testing/web-platform/tests/css/css-cascade/unset-val-001.html new file mode 100644 index 0000000000..857cb5d40d --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/unset-val-001.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: the "unset" value</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="author" title="Elika J. Etemad" href="http://fantasai.inkedblade.net/contact"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#inherit-initial"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#inherit-initial"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="color:unset is the same as color:inherit since color is an inherited property. background-color:unset is the same as background-color:initial since background-color is a non-inherited property."> + <style> +.square { + width: 100px; + height: 100px; +} +.under { + background-color: green; + margin-bottom: -100px; +} +.outer { + color: green; + background: red; +} +.inner { + color: red; + background-color: red; + font-size: 40px; + text-align: center; +} +.inner { + color: unset;/* inherit from .outer */ + background-color: unset;/* initial, transparent, .under shows thru */ +} + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div class="outer square"> + <div class="under square"></div> + <div class="inner square">XX</div> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/unset-val-002.html b/testing/web-platform/tests/css/css-cascade/unset-val-002.html new file mode 100644 index 0000000000..61f941a038 --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/unset-val-002.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Cascade: the "unset" value</title> + <link rel="author" title="Chris Rebert" href="http://chrisrebert.com"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-3/#inherit-initial"> + <link rel="help" href="http://www.w3.org/TR/css-cascade-4/#inherit-initial"> + <link rel="match" href="reference/ref-filled-green-100px-square.xht"> + <meta name="assert" content="display:unset should be the same as display:initial since 'display' is not an inherited property. display:unset should be the same as display:inline since 'inline' is the initial value of 'display'."> + <style> +.square { + width: 100px; + height: 100px; +} +.green { + background-color: green; +} +.top { + position: absolute; +} +.fail { + background-color: red; + display: inline-block; + display: unset;/* initial, inline */ +} + </style> +</head> +<body> + <p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + + <div class="top"><span class="square fail"></span></div> + <div class="green square"></div> +</body> +</html> diff --git a/testing/web-platform/tests/css/css-cascade/unset-value-storage.html b/testing/web-platform/tests/css/css-cascade/unset-value-storage.html new file mode 100644 index 0000000000..97a27ff67b --- /dev/null +++ b/testing/web-platform/tests/css/css-cascade/unset-value-storage.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Storage of "unset" value</title> +<meta name="author" title="Xidorn Quan" href="https://www.upsuper.org"> +<link rel="help" href="https://www.w3.org/TR/css-cascade-3/#inherit-initial"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + div { + color: unset; + border: unset; + } +</style> +<body> + <div id="log"></div> + <script> + test(function() { + let properties = ["color", "border", "border-left", "border-color", "border-right-style"]; + let style = document.styleSheets[0].cssRules[0].style; + for (let prop of properties) { + assert_equals(style.getPropertyValue(prop), "unset", `${prop} is expected to be "unset"`); + } + }); + </script> |