tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 780dd7e9c236b7eccaefbced542e91bbca5fc26f
parent 6cb49e422cad3e0bba008296df7d5929b25e8b43
Author: Denis Palmeiro <dpalmeiro@mozilla.com>
Date:   Tue, 30 Dec 2025 15:43:11 +0000

Bug 2006172: Add a few tests to make sure style sharing does not happen in certain cases such as :host rule mismatch.  r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D277358

Diffstat:
Atesting/web-platform/tests/css/css-scoping/shadow-host-style-sharing.html | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 227 insertions(+), 0 deletions(-)

diff --git a/testing/web-platform/tests/css/css-scoping/shadow-host-style-sharing.html b/testing/web-platform/tests/css/css-scoping/shadow-host-style-sharing.html @@ -0,0 +1,227 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Shadow Host Style Sharing Tests</title> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="container"></div> + +<script> +const container = document.getElementById('container'); + +function cleanup() { + container.innerHTML = ''; +} + +// Test 1: Different :host rules in different stylesheets +test(() => { + cleanup(); + + const redSheet = new CSSStyleSheet(); + redSheet.replaceSync(':host { color: rgb(255, 0, 0); }'); + + const blueSheet = new CSSStyleSheet(); + blueSheet.replaceSync(':host { color: rgb(0, 0, 255); }'); + + let useRed = true; + + class DynamicHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.innerHTML = '<div>content</div>'; + shadow.adoptedStyleSheets = [useRed ? redSheet : blueSheet]; + } + } + customElements.define('dynamic-host-1', DynamicHost); + + const parent = document.createElement('div'); + + // Create siblings: first 10 with red sheet + useRed = true; + const redHosts = []; + for (let i = 0; i < 10; i++) { + const host = document.createElement('dynamic-host-1'); + redHosts.push(host); + parent.appendChild(host); + } + + // Then 1 with blue sheet as a sibling + useRed = false; + const blueHost = document.createElement('dynamic-host-1'); + parent.appendChild(blueHost); + + // Add to document and force style computation + container.appendChild(parent); + container.offsetHeight; + + assert_equals(getComputedStyle(blueHost).color, 'rgb(0, 0, 255)', 'Must be blue, not red'); + + for (const host of redHosts) { + assert_equals(getComputedStyle(host).color, 'rgb(255, 0, 0)', 'Must be red'); + } +}, 'Different CascadeData must block sharing despite being same element type'); + +// Test 2: Same :host rules, different inherited styles +test(() => { + cleanup(); + + const sharedSheet = new CSSStyleSheet(); + sharedSheet.replaceSync(':host { font-size: 20px; }'); + + class SharedHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sharedSheet]; + shadow.innerHTML = '<span>content</span>'; + } + } + customElements.define('shared-host-2', SharedHost); + + const parent1 = document.createElement('div'); + parent1.style.color = 'rgb(255, 0, 0)'; + const host1 = document.createElement('shared-host-2'); + parent1.appendChild(host1); + container.appendChild(parent1); + + const parent2 = document.createElement('div'); + parent2.style.color = 'rgb(0, 255, 0)'; + const host2 = document.createElement('shared-host-2'); + parent2.appendChild(host2); + container.appendChild(parent2); + + assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)'); + assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)'); +}, 'Same :host rules but different inherited styles'); + +// Test 3: :host with class selector +test(() => { + cleanup(); + + const classSheet = new CSSStyleSheet(); + classSheet.replaceSync(` + :host(.active) { background-color: rgb(255, 0, 0); } + :host(.inactive) { background-color: rgb(0, 0, 255); } + `); + + class ClassHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [classSheet]; + shadow.innerHTML = '<span>content</span>'; + } + } + customElements.define('class-host-3', ClassHost); + + const active = document.createElement('class-host-3'); + active.className = 'active'; + const inactive = document.createElement('class-host-3'); + inactive.className = 'inactive'; + + container.appendChild(active); + container.appendChild(inactive); + + assert_equals(getComputedStyle(active).backgroundColor, 'rgb(255, 0, 0)'); + assert_equals(getComputedStyle(inactive).backgroundColor, 'rgb(0, 0, 255)'); +}, ':host with class selector should not share across different classes'); + +// Test 4: :host with attribute selector +test(() => { + cleanup(); + + const attrSheet = new CSSStyleSheet(); + attrSheet.replaceSync(` + :host([data-theme="dark"]) { color: rgb(0, 0, 0); } + :host([data-theme="light"]) { color: rgb(255, 255, 255); } + `); + + class AttrHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [attrSheet]; + shadow.innerHTML = '<span>content</span>'; + } + } + customElements.define('attr-host-4', AttrHost); + + const dark = document.createElement('attr-host-4'); + dark.setAttribute('data-theme', 'dark'); + const light = document.createElement('attr-host-4'); + light.setAttribute('data-theme', 'light'); + + container.appendChild(dark); + container.appendChild(light); + + assert_equals(getComputedStyle(dark).color, 'rgb(0, 0, 0)'); + assert_equals(getComputedStyle(light).color, 'rgb(255, 255, 255)'); +}, ':host with attribute selector should not share across different attributes'); + +// Test 5: Same stylesheet, different inline styles +test(() => { + cleanup(); + + const sharedSheet = new CSSStyleSheet(); + sharedSheet.replaceSync(':host { display: block; }'); + + class InlineHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sharedSheet]; + shadow.innerHTML = '<span>content</span>'; + } + } + customElements.define('inline-host-5', InlineHost); + + const host1 = document.createElement('inline-host-5'); + host1.style.color = 'rgb(255, 0, 0)'; + + const host2 = document.createElement('inline-host-5'); + host2.style.color = 'rgb(0, 255, 0)'; + + container.appendChild(host1); + container.appendChild(host2); + + assert_equals(getComputedStyle(host1).color, 'rgb(255, 0, 0)'); + assert_equals(getComputedStyle(host2).color, 'rgb(0, 255, 0)'); +}, 'Same CascadeData but different inline styles should not share'); + +// Test 6: Same :host rules but different shadow stylesheets +test(() => { + cleanup(); + + const sheetA = new CSSStyleSheet(); + sheetA.replaceSync(':host { color: rgb(255, 0, 0); }'); + + const sheetB = new CSSStyleSheet(); + sheetB.replaceSync(':host { color: rgb(255, 0, 0); } span { font-weight: bold; }'); + + let whichSheet = 'a'; + + class SubtleHost extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [whichSheet === 'a' ? sheetA : sheetB]; + shadow.innerHTML = '<span>content</span>'; + } + } + customElements.define('subtle-host-6', SubtleHost); + + whichSheet = 'a'; + const hostA = document.createElement('subtle-host-6'); + + whichSheet = 'b'; + const hostB = document.createElement('subtle-host-6'); + + container.appendChild(hostA); + container.appendChild(hostB); + + assert_equals(getComputedStyle(hostA).color, 'rgb(255, 0, 0)'); + assert_equals(getComputedStyle(hostB).color, 'rgb(255, 0, 0)'); +}, 'Same :host rules but different CascadeData should prevent sharing'); +</script>