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.
This commit is contained in:
Dmitry Gozman 2023-09-25 14:34:17 -07:00 committed by GitHub
parent 522782cce6
commit 9a5356f93b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 0 deletions

View File

@ -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');

View File

@ -72,6 +72,24 @@ it.describe('snapshots', () => {
expect(distillSnapshot(snapshot2)).toBe('<STYLE>button { color: blue; }</STYLE><BUTTON>Hello</BUTTON>');
});
it('should respect CSSOM change through CSSGroupingRule', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<style>@media { button { color: red; } }</style><button>Hello</button>');
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('<STYLE>@media {\n button { color: red; }\n}</STYLE><BUTTON>Hello</BUTTON>');
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('<STYLE>@media {\n button { color: blue; }\n}</STYLE><BUTTON>Hello</BUTTON>');
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('<STYLE>@media {\n button { color: blue; }\n button { color: green; }\n}</STYLE><BUTTON>Hello</BUTTON>');
});
it('should respect node removal', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<div><button id="button1"></button><button id="button2"></button></div>');
const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1');