| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <title>Adoptedstylesheets as ObservableArray</title> | |
| <link rel="author" href="mailto:masonf@chromium.org"> | |
| <link rel="help" href="https://drafts.csswg.org/cssom/#extensions-to-the-document-or-shadow-root-interface"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <span id=target>Test Span</span> | |
| <div id="divNonConstructed" class="nonConstructed"></div> | |
| <style> | |
| #target {background-color: red;} | |
| </style> | |
| <script> | |
| const shadowRootNonConstructed = divNonConstructed.attachShadow({mode:'open'}) | |
| nonConstructedStyle = document.createElement("style"); | |
| shadowRootNonConstructed.appendChild(nonConstructedStyle); | |
| nonConstructedStyle.sheet.insertRule(".nonConstructed { color: red; }", 0); | |
| const nonConstructedStyleSheet = nonConstructedStyle.sheet; | |
| function assert_is(targetStyle, color) { | |
| assert_equals(targetStyle.getPropertyValue('background-color'), color); | |
| } | |
| function testRoot(d, targetStyle) { | |
| const red = 'rgb(255, 0, 0)'; | |
| const green = 'rgb(0, 255, 0)'; | |
| const blue = 'rgb(0, 0, 255)'; | |
| const sheet1 = new CSSStyleSheet(); | |
| sheet1.replaceSync('#target {background-color:lime !important;}'); | |
| const sheet2 = new CSSStyleSheet(); | |
| sheet2.replaceSync('#target {background-color:blue !important;}'); | |
| assert_equals(d.adoptedStyleSheets.length, 0); | |
| assert_is(targetStyle, red); | |
| d.adoptedStyleSheets = [sheet1]; | |
| assert_equals(d.adoptedStyleSheets.length, 1); | |
| assert_is(targetStyle, green); | |
| d.adoptedStyleSheets.push(sheet2); | |
| assert_equals(d.adoptedStyleSheets.length, 2); | |
| assert_is(targetStyle, blue); | |
| d.adoptedStyleSheets.pop(); | |
| assert_equals(d.adoptedStyleSheets.length, 1); | |
| assert_is(targetStyle, green); | |
| d.adoptedStyleSheets.push(sheet2); | |
| d.adoptedStyleSheets.reverse(); | |
| assert_equals(d.adoptedStyleSheets.length, 2); | |
| assert_is(targetStyle, green); | |
| d.adoptedStyleSheets.splice(1, 1); | |
| assert_equals(d.adoptedStyleSheets.length, 1); | |
| assert_is(targetStyle, blue); | |
| d.adoptedStyleSheets.splice(0, 1, sheet1); | |
| assert_equals(d.adoptedStyleSheets.length, 1); | |
| assert_is(targetStyle, green); | |
| // Adding non-constructed stylesheet to AdoptedStyleSheets is not allowed. | |
| assert_throws_dom('NotAllowedError', () => { document.adoptedStyleSheets.push(nonConstructedStyleSheet); }); | |
| assert_throws_js(TypeError, () => { document.adoptedStyleSheets.push("foo"); }); | |
| } | |
| test(function() { | |
| const target = document.querySelector('#target'); | |
| const targetStyle = window.getComputedStyle(target); | |
| testRoot(document, targetStyle); | |
| }, "document.adoptedStyleSheets should allow mutation in-place"); | |
| test(function() { | |
| const host = document.createElement('div'); | |
| document.body.appendChild(host); | |
| const shadow = host.attachShadow({mode: 'open'}); | |
| shadow.innerHTML = '<span id=target>Test Shadow Span</span><style>#target{background-color: red;}</style>'; | |
| const target = shadow.querySelector('#target'); | |
| const targetStyle = window.getComputedStyle(target); | |
| testRoot(shadow, targetStyle); | |
| }, "shadowRoot.adoptedStyleSheets should allow mutation in-place"); | |
| test(function() { | |
| assert_true(Array.isArray(document.adoptedStyleSheets)); | |
| const host = document.createElement('div'); | |
| document.body.appendChild(host); | |
| const shadow = host.attachShadow({mode: 'open'}); | |
| assert_true(Array.isArray(shadow.adoptedStyleSheets)); | |
| }, "adoptedStyleSheets should return true for isArray()"); | |
| test(function() { | |
| let backing = null; | |
| Object.defineProperty(Array.prototype, "1", { | |
| configurable: true, | |
| set(v) { backing = this; } | |
| }); | |
| document.adoptedStyleSheets = [new CSSStyleSheet(), new CSSStyleSheet()]; | |
| delete Array.prototype["1"]; | |
| assert_equals(backing, null); | |
| }, "adoptedStyleSheets should not expose its backing object via prototype"); | |
| test(function() { | |
| // A prototype-less descriptor keeps Object.defineProperty() below working | |
| // once Object.prototype.get is a number. | |
| const numeric = Object.create(null); | |
| numeric.value = 1; | |
| numeric.writable = true; | |
| numeric.configurable = true; | |
| const descriptor = new Proxy(Object.create(null), { | |
| has(target, key) { | |
| if (key === "set") { | |
| // Runs while the descriptor is being converted, after the initial | |
| // conversion has already looked up "get". | |
| Object.defineProperty(Object.prototype, "get", numeric); | |
| Object.defineProperty(Object.prototype, "set", numeric); | |
| } | |
| return false; | |
| }, | |
| }); | |
| try { | |
| assert_throws_js(TypeError, () => { | |
| Reflect.defineProperty(document.adoptedStyleSheets, "reentrantAccessor", | |
| descriptor); | |
| }); | |
| } finally { | |
| delete Object.prototype.get; | |
| delete Object.prototype.set; | |
| } | |
| assert_equals( | |
| Object.getOwnPropertyDescriptor(document.adoptedStyleSheets, | |
| "reentrantAccessor"), | |
| undefined); | |
| }, "adoptedStyleSheets defineProperty should reject accessors that become non-callable during descriptor conversion"); | |
| </script> |