From 23ffe0d9c43d12ae4c918a4327fb7fbe0d9971ed Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 Feb 2023 11:06:24 +0000 Subject: [PATCH 1/5] fix: Ensure CSS support is checked more robustly --- packages/rrweb/src/record/observer.ts | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index 324342e1db..b75fac11b2 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -54,10 +54,34 @@ type WindowWithAngularZone = IWindow & { export const mutationBuffers: MutationBuffer[] = []; export const processedNodeManager = new ProcessedNodeManager(); -const isCSSGroupingRuleSupported = typeof CSSGroupingRule !== 'undefined'; -const isCSSMediaRuleSupported = typeof CSSMediaRule !== 'undefined'; -const isCSSSupportsRuleSupported = typeof CSSSupportsRule !== 'undefined'; -const isCSSConditionRuleSupported = typeof CSSConditionRule !== 'undefined'; +function isCSSStyleSheetMonkeyPatchable( + prop: + | 'CSSGroupingRule' + | 'CSSMediaRule' + | 'CSSSupportsRule' + | 'CSSConditionRule', +): boolean { + return Boolean( + typeof window[prop] !== 'undefined' && + // Note: Generally, this check _shouldn't_ be necessary + // However, in some scenarios (e.g. jsdom) this can sometimes fail, so we check for it here + window[prop].prototype && + // @ts-ignore ensure it is actually set + window[prop].prototype.insertRule && + window[prop].prototype.deleteRule + ); +} + +const isCSSGroupingRuleSupported = isCSSStyleSheetMonkeyPatchable( + 'CSSGroupingRule', +); +const isCSSMediaRuleSupported = isCSSStyleSheetMonkeyPatchable('CSSMediaRule'); +const isCSSSupportsRuleSupported = isCSSStyleSheetMonkeyPatchable( + 'CSSSupportsRule', +); +const isCSSConditionRuleSupported = isCSSStyleSheetMonkeyPatchable( + 'CSSConditionRule', +); // Event.path is non-standard and used in some older browsers type NonStandardEvent = Omit & { From 07cc95d0a64facdc5a1d3eea22bae0b4e0212d78 Mon Sep 17 00:00:00 2001 From: mydea Date: Fri, 3 Feb 2023 11:12:43 +0000 Subject: [PATCH 2/5] Apply formatting changes --- packages/rrweb/src/record/observer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index b75fac11b2..c16ebd8352 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -68,7 +68,7 @@ function isCSSStyleSheetMonkeyPatchable( window[prop].prototype && // @ts-ignore ensure it is actually set window[prop].prototype.insertRule && - window[prop].prototype.deleteRule + window[prop].prototype.deleteRule, ); } From fa80db11976baa9cb103e66c4627643e011f5342 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 Feb 2023 11:27:23 +0000 Subject: [PATCH 3/5] apply eslint changes --- packages/rrweb/src/record/observer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index c16ebd8352..b47363ab8e 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -61,6 +61,7 @@ function isCSSStyleSheetMonkeyPatchable( | 'CSSSupportsRule' | 'CSSConditionRule', ): boolean { + /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/unbound-method */ return Boolean( typeof window[prop] !== 'undefined' && // Note: Generally, this check _shouldn't_ be necessary @@ -70,6 +71,7 @@ function isCSSStyleSheetMonkeyPatchable( window[prop].prototype.insertRule && window[prop].prototype.deleteRule, ); + /* eslint-enable @typescript-eslint/ban-ts-comment,@typescript-eslint/unbound-method */ } const isCSSGroupingRuleSupported = isCSSStyleSheetMonkeyPatchable( From d47711fa477ea1cff3438b58e6978bd2ef9f9a38 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 Feb 2023 12:59:33 +0000 Subject: [PATCH 4/5] Update packages/rrweb/src/record/observer.ts Co-authored-by: Justin Halsall --- packages/rrweb/src/record/observer.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index b47363ab8e..4dca2a26ab 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -61,17 +61,14 @@ function isCSSStyleSheetMonkeyPatchable( | 'CSSSupportsRule' | 'CSSConditionRule', ): boolean { - /* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/unbound-method */ return Boolean( typeof window[prop] !== 'undefined' && // Note: Generally, this check _shouldn't_ be necessary // However, in some scenarios (e.g. jsdom) this can sometimes fail, so we check for it here window[prop].prototype && - // @ts-ignore ensure it is actually set - window[prop].prototype.insertRule && - window[prop].prototype.deleteRule, + 'insertRule' in window[prop].prototype && + 'deleteRule' in window[prop].prototype, ); - /* eslint-enable @typescript-eslint/ban-ts-comment,@typescript-eslint/unbound-method */ } const isCSSGroupingRuleSupported = isCSSStyleSheetMonkeyPatchable( From a242e1b992fcdbba8e49601b468e6b4efbf5f1e8 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 8 Feb 2023 17:15:05 +0100 Subject: [PATCH 5/5] fix: do not use window in module scope --- packages/rrweb/src/record/observer.ts | 65 ++++++++++++--------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index 4dca2a26ab..e7284104b9 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -54,34 +54,6 @@ type WindowWithAngularZone = IWindow & { export const mutationBuffers: MutationBuffer[] = []; export const processedNodeManager = new ProcessedNodeManager(); -function isCSSStyleSheetMonkeyPatchable( - prop: - | 'CSSGroupingRule' - | 'CSSMediaRule' - | 'CSSSupportsRule' - | 'CSSConditionRule', -): boolean { - return Boolean( - typeof window[prop] !== 'undefined' && - // Note: Generally, this check _shouldn't_ be necessary - // However, in some scenarios (e.g. jsdom) this can sometimes fail, so we check for it here - window[prop].prototype && - 'insertRule' in window[prop].prototype && - 'deleteRule' in window[prop].prototype, - ); -} - -const isCSSGroupingRuleSupported = isCSSStyleSheetMonkeyPatchable( - 'CSSGroupingRule', -); -const isCSSMediaRuleSupported = isCSSStyleSheetMonkeyPatchable('CSSMediaRule'); -const isCSSSupportsRuleSupported = isCSSStyleSheetMonkeyPatchable( - 'CSSSupportsRule', -); -const isCSSConditionRuleSupported = isCSSStyleSheetMonkeyPatchable( - 'CSSConditionRule', -); - // Event.path is non-standard and used in some older browsers type NonStandardEvent = Omit & { path: EventTarget[]; @@ -511,13 +483,13 @@ function getNestedCSSRulePositions(rule: CSSRule): number[] { const positions: number[] = []; function recurse(childRule: CSSRule, pos: number[]) { if ( - (isCSSGroupingRuleSupported && + (hasNestedCSSRule('CSSGroupingRule') && childRule.parentRule instanceof CSSGroupingRule) || - (isCSSMediaRuleSupported && + (hasNestedCSSRule('CSSMediaRule') && childRule.parentRule instanceof CSSMediaRule) || - (isCSSSupportsRuleSupported && + (hasNestedCSSRule('CSSSupportsRule') && childRule.parentRule instanceof CSSSupportsRule) || - (isCSSConditionRuleSupported && + (hasNestedCSSRule('CSSConditionRule') && childRule.parentRule instanceof CSSConditionRule) ) { const rules = Array.from( @@ -666,20 +638,20 @@ function initStyleSheetObserver( const supportedNestedCSSRuleTypes: { [key: string]: GroupingCSSRuleTypes; } = {}; - if (isCSSGroupingRuleSupported) { + if (canMonkeyPatchNestedCSSRule('CSSGroupingRule')) { supportedNestedCSSRuleTypes.CSSGroupingRule = win.CSSGroupingRule; } else { // Some browsers (Safari) don't support CSSGroupingRule // https://caniuse.com/?search=cssgroupingrule // fall back to monkey patching classes that would have inherited from CSSGroupingRule - if (isCSSMediaRuleSupported) { + if (canMonkeyPatchNestedCSSRule('CSSMediaRule')) { supportedNestedCSSRuleTypes.CSSMediaRule = win.CSSMediaRule; } - if (isCSSConditionRuleSupported) { + if (canMonkeyPatchNestedCSSRule('CSSConditionRule')) { supportedNestedCSSRuleTypes.CSSConditionRule = win.CSSConditionRule; } - if (isCSSSupportsRuleSupported) { + if (canMonkeyPatchNestedCSSRule('CSSSupportsRule')) { supportedNestedCSSRuleTypes.CSSSupportsRule = win.CSSSupportsRule; } } @@ -1191,3 +1163,24 @@ export function initObservers( pluginHandlers.forEach((h) => h()); }; } + +type CSSGroupingProp = + | 'CSSGroupingRule' + | 'CSSMediaRule' + | 'CSSSupportsRule' + | 'CSSConditionRule'; + +function hasNestedCSSRule(prop: CSSGroupingProp): boolean { + return typeof window[prop] !== 'undefined'; +} + +function canMonkeyPatchNestedCSSRule(prop: CSSGroupingProp): boolean { + return Boolean( + typeof window[prop] !== 'undefined' && + // Note: Generally, this check _shouldn't_ be necessary + // However, in some scenarios (e.g. jsdom) this can sometimes fail, so we check for it here + window[prop].prototype && + 'insertRule' in window[prop].prototype && + 'deleteRule' in window[prop].prototype, + ); +}