From 9a5356f93b561eae3950805456322a7d87955e43 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 25 Sep 2023 14:34:17 -0700 Subject: [PATCH] fix(snapshot): invalidate style sheet upon CSSGroupingRule changes (#27296) Previously, snapshotter listened to CSSStyleSheet modifications, but one can also modify the list of rules inside CSSGroupingRule. Fixes #27288. --- .../trace/recorder/snapshotterInjected.ts | 7 +++++++ tests/library/snapshotter.spec.ts | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 458c393695..921b7dd561 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -89,6 +89,10 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { private _observer: MutationObserver; constructor() { + const invalidateCSSGroupingRule = (rule: CSSGroupingRule) => { + if (rule.parentStyleSheet) + this._invalidateStyleSheet(rule.parentStyleSheet); + }; this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'insertRule', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'deleteRule', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'addRule', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); @@ -96,6 +100,9 @@ export function frameSnapshotStreamer(snapshotStreamer: string) { this._interceptNativeGetter(window.CSSStyleSheet.prototype, 'rules', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeGetter(window.CSSStyleSheet.prototype, 'cssRules', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); this._interceptNativeMethod(window.CSSStyleSheet.prototype, 'replaceSync', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); + this._interceptNativeMethod(window.CSSGroupingRule.prototype, 'insertRule', invalidateCSSGroupingRule); + this._interceptNativeMethod(window.CSSGroupingRule.prototype, 'deleteRule', invalidateCSSGroupingRule); + this._interceptNativeGetter(window.CSSGroupingRule.prototype, 'cssRules', invalidateCSSGroupingRule); this._interceptNativeAsyncMethod(window.CSSStyleSheet.prototype, 'replace', (sheet: CSSStyleSheet) => this._invalidateStyleSheet(sheet)); this._fakeBase = document.createElement('base'); diff --git a/tests/library/snapshotter.spec.ts b/tests/library/snapshotter.spec.ts index 0d19b811e7..b0866a5d3f 100644 --- a/tests/library/snapshotter.spec.ts +++ b/tests/library/snapshotter.spec.ts @@ -72,6 +72,24 @@ it.describe('snapshots', () => { expect(distillSnapshot(snapshot2)).toBe(''); }); + it('should respect CSSOM change through CSSGroupingRule', async ({ page, toImpl, snapshotter }) => { + await page.setContent(''); + await page.evaluate(() => { + window['rule'] = document.styleSheets[0].cssRules[0]; + void 0; + }); + const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1'); + expect(distillSnapshot(snapshot1)).toBe(''); + + await page.evaluate(() => { window['rule'].cssRules[0].style.color = 'blue'; }); + const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'call@2', 'snapshot@call@2'); + expect(distillSnapshot(snapshot2)).toBe(''); + + await page.evaluate(() => { window['rule'].insertRule('button { color: green; }', 1); }); + const snapshot3 = await snapshotter.captureSnapshot(toImpl(page), 'call@3', 'snapshot@call@3'); + expect(distillSnapshot(snapshot3)).toBe(''); + }); + it('should respect node removal', async ({ page, toImpl, snapshotter }) => { await page.setContent('
'); const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1');