Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/rrweb-snapshot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import snapshot, {
classMatchesRegex,
IGNORED_NODE,
genId,
filterCSSPropertiesFromInlineStyle,
} from './snapshot';
import rebuild, {
buildNodeWithSN,
Expand All @@ -36,4 +37,5 @@ export {
classMatchesRegex,
IGNORED_NODE,
genId,
filterCSSPropertiesFromInlineStyle,
};
67 changes: 66 additions & 1 deletion packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,48 @@
const URL_PROTOCOL_MATCH = /^(?:[a-z+]+:)?\/\//i;
const URL_WWW_MATCH = /^www\..*/i;
const DATA_URI = /^(data:)([^,]*),(.*)/i;
export function filterCSSPropertiesFromInlineStyle(
cssText: string,
ignoredProperties: Set<string>,
): string {
if (!cssText || ignoredProperties.size === 0) {
return cssText;
}

try {
// Split CSS by semicolons to get individual property-value pairs
const properties = cssText.split(';');
const filteredProperties = [];

for (let property of properties) {
property = property.trim();
if (!property) continue;

const colonIndex = property.indexOf(':');
if (colonIndex === -1) {
// Invalid property, keep it as is
filteredProperties.push(property);
continue;
}

const propertyName = property.slice(0, colonIndex).trim();

// If this property is not in the ignore set, keep it
if (!ignoredProperties.has(propertyName)) {
filteredProperties.push(property);
}
}

return (
filteredProperties.join('; ') +
(filteredProperties.length > 0 && cssText.endsWith(';') ? ';' : '')
);
} catch (error) {
console.warn('Error filtering CSS properties:', error);
return cssText;
}
}

export function absoluteToStylesheet(
cssText: string | null,
href: string,
Expand Down Expand Up @@ -238,6 +280,7 @@
value: string | null,
element: HTMLElement,
maskAttributeFn: MaskAttributeFn | undefined,
ignoreCSSAttributes?: Set<string>,
): string | null {
if (!value) {
return value;
Expand All @@ -261,7 +304,14 @@
} else if (name === 'srcset') {
return getAbsoluteSrcsetString(doc, value);
} else if (name === 'style') {
return absoluteToStylesheet(value, getHref(doc));
let processedStyle = absoluteToStylesheet(value, getHref(doc));
if (ignoreCSSAttributes && ignoreCSSAttributes.size > 0) {
processedStyle = filterCSSPropertiesFromInlineStyle(
processedStyle,
ignoreCSSAttributes,
);
}
return processedStyle;
} else if (tagName === 'object' && name === 'data') {
return absoluteToDoc(doc, value);
}
Expand Down Expand Up @@ -570,6 +620,7 @@
* `newlyAddedElement: true` skips scrollTop and scrollLeft check
*/
newlyAddedElement?: boolean;
ignoreCSSAttributes?: Set<string>;
},
): serializedNode | false {
const {
Expand All @@ -593,6 +644,7 @@
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
ignoreCSSAttributes,
} = options;
// Only record root id when document object is not the base document
const rootId = getRootId(doc, mirror);
Expand Down Expand Up @@ -639,6 +691,7 @@
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
ignoreCSSAttributes,
});
case n.TEXT_NODE:
return serializeTextNode(n as Text, {
Expand Down Expand Up @@ -719,7 +772,7 @@
// So we'll be conservative and keep textContent as-is.
} else if ((n.parentNode as HTMLStyleElement).sheet?.cssRules) {
textContent = stringifyStylesheet(
(n.parentNode as HTMLStyleElement).sheet!,

Check warning on line 775 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 775 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L775

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
);
}
} catch (err) {
Expand Down Expand Up @@ -809,6 +862,7 @@
unmaskTextClass: string | RegExp | null;
maskTextSelector: string | null;
unmaskTextSelector: string | null;
ignoreCSSAttributes?: Set<string>;
},
): serializedNode | false {
const {
Expand All @@ -830,6 +884,7 @@
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
ignoreCSSAttributes,
} = options;
const needBlock = _isBlockedElement(
n,
Expand All @@ -852,6 +907,7 @@
attr.value,
n,
maskAttributeFn,
ignoreCSSAttributes,
);
}
}
Expand All @@ -868,7 +924,7 @@
attributes.rel = null;
attributes.href = null;
attributes.crossorigin = null;
attributes._cssText = absoluteToStylesheet(cssText, stylesheet!.href!);

Check warning on line 927 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 927 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 927 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L927

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.

Check warning on line 927 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L927

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
}
}
// dynamic stylesheet
Expand Down Expand Up @@ -980,10 +1036,10 @@
const recordInlineImage = () => {
image.removeEventListener('load', recordInlineImage);
try {
canvasService!.width = image.naturalWidth;

Check warning on line 1039 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 1039 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L1039

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
canvasService!.height = image.naturalHeight;

Check warning on line 1040 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 1040 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L1040

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
canvasCtx!.drawImage(image, 0, 0);

Check warning on line 1041 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 1041 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L1041

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
attributes.rr_dataURL = canvasService!.toDataURL(

Check warning on line 1042 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 1042 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L1042

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
dataURLOptions.type,
dataURLOptions.quality,
);
Expand Down Expand Up @@ -1213,6 +1269,7 @@
node: serializedElementNodeWithId,
) => unknown;
stylesheetLoadTimeout?: number;
ignoreCSSAttributes?: Set<string>;
},
): serializedNodeWithId | null {
const {
Expand Down Expand Up @@ -1244,6 +1301,7 @@
stylesheetLoadTimeout = 5000,
keepIframeSrcFn = () => false,
newlyAddedElement = false,
ignoreCSSAttributes,
} = options;
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
Expand All @@ -1267,6 +1325,7 @@
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
ignoreCSSAttributes,
});
if (!_serializedNode) {
// TODO: dev only
Expand Down Expand Up @@ -1350,6 +1409,7 @@
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes,
};
const childNodes = n.childNodes ? Array.from(n.childNodes) : [];
for (const childN of childNodes) {
Expand Down Expand Up @@ -1417,6 +1477,7 @@
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes,
});

if (serializedIframeNode) {
Expand Down Expand Up @@ -1502,6 +1563,7 @@
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes,
});

if (serializedLinkNode) {
Expand Down Expand Up @@ -1563,6 +1625,7 @@
) => unknown;
stylesheetLoadTimeout?: number;
keepIframeSrcFn?: KeepIframeSrcFn;
ignoreCSSAttributes?: Set<string>;
},
): serializedNodeWithId | null {
const {
Expand Down Expand Up @@ -1592,6 +1655,7 @@
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn = () => false,
ignoreCSSAttributes = new Set([]),
} = options || {};
const maskInputOptions: MaskInputOptions =
maskAllInputs === true
Expand Down Expand Up @@ -1663,6 +1727,7 @@
stylesheetLoadTimeout,
keepIframeSrcFn,
newlyAddedElement: false,
ignoreCSSAttributes,
});
}

Expand Down
2 changes: 2 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@
canvasManager,
keepIframeSrcFn,
processedNodeManager,
ignoreCSSAttributes,
},
mirror,
});
Expand Down Expand Up @@ -489,6 +490,7 @@
});
},
keepIframeSrcFn,
ignoreCSSAttributes,
});

if (!node) {
Expand Down Expand Up @@ -641,7 +643,7 @@
plugins
?.filter((p) => p.observer)
?.map((p) => ({
observer: p.observer!,

Check warning on line 646 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 646 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 646 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/index.ts#L646

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
options: p.options,
callback: (payload: object) =>
wrappedEmit({
Expand All @@ -659,7 +661,7 @@

iframeManager.addLoadListener((iframeEl) => {
try {
handlers.push(observe(iframeEl.contentDocument!));

Check warning on line 664 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 664 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 664 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/index.ts#L664

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
} catch (error) {
// TODO: handle internal error
console.warn(error);
Expand Down
3 changes: 3 additions & 0 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
private shadowDomManager: observerParam['shadowDomManager'];
private canvasManager: observerParam['canvasManager'];
private processedNodeManager: observerParam['processedNodeManager'];
private ignoreCSSAttributes: observerParam['ignoreCSSAttributes'];
private unattachedDoc: HTMLDocument;

public init(options: MutationBufferParam) {
Expand Down Expand Up @@ -230,6 +231,7 @@
'shadowDomManager',
'canvasManager',
'processedNodeManager',
'ignoreCSSAttributes',
] as const
).forEach((key) => {
// just a type trick, the runtime result is correct
Expand Down Expand Up @@ -393,6 +395,7 @@
],
});
},
ignoreCSSAttributes: this.ignoreCSSAttributes,
});
if (sn) {
adds.push({
Expand All @@ -405,13 +408,13 @@
};

while (this.mapRemoves.length) {
this.mirror.removeNodeFromMap(this.mapRemoves.shift()!);

Check warning on line 411 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 411 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/mutation.ts#L411

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
}

for (const n of this.movedSet) {
if (
isParentRemoved(this.removes, n, this.mirror) &&
!this.movedSet.has(n.parentNode!)

Check warning on line 417 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Check and Report Upload

Forbidden non-null assertion

Check warning on line 417 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/mutation.ts#L417

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
) {
continue;
}
Expand Down
1 change: 1 addition & 0 deletions packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export type MutationBufferParam = Pick<
| 'shadowDomManager'
| 'canvasManager'
| 'processedNodeManager'
| 'ignoreCSSAttributes'
>;

export type ReplayPlugin = {
Expand Down
101 changes: 101 additions & 0 deletions packages/rrweb/test/__snapshots__/record.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,107 @@ exports[`record > handles \`!important\` with "all" CSS property 1`] = `
]"
`;

exports[`record > ignoreCSSAttributes works on inline styles for snapshots and mutations 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"style\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"id\\": 5
}
],
\\"id\\": 4
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 7
},
{
\\"type\\": 2,
\\"tagName\\": \\"input\\",
\\"attributes\\": {
\\"type\\": \\"text\\",
\\"size\\": \\"40\\"
},
\\"childNodes\\": [],
\\"id\\": 8
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
\\"id\\": 9
},
{
\\"type\\": 2,
\\"tagName\\": \\"div\\",
\\"attributes\\": {
\\"style\\": \\"margin: 1px\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"Button\\",
\\"id\\": 11
}
],
\\"id\\": 10
}
],
\\"id\\": 6
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
}
]"
`;

exports[`record > is safe to checkout during async callbacks 1`] = `
"[
{
Expand Down
Loading
Loading