diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index 324342e1db..e7284104b9 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -54,11 +54,6 @@ 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'; - // Event.path is non-standard and used in some older browsers type NonStandardEvent = Omit & { path: EventTarget[]; @@ -488,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( @@ -643,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; } } @@ -1168,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, + ); +}