Skip to content

Commit

Permalink
fix: Ensure text masking for updated attributes works (#83)
Browse files Browse the repository at this point in the history
We had inconsistent casing for the tagName, leading to some masking
slipping through.
  • Loading branch information
mydea authored Mar 17, 2023
1 parent 0eb738a commit 950bd2c
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 16 deletions.
7 changes: 5 additions & 2 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ function getHref() {
export function transformAttribute(
doc: Document,
element: HTMLElement,
tagName: string,
name: string,
_tagName: string,
_name: string,
value: string | null,
maskAllText: boolean,
unmaskTextSelector: string | undefined | null,
Expand All @@ -248,6 +248,9 @@ export function transformAttribute(
return value;
}

const name = _name.toLowerCase();
const tagName = _tagName.toLowerCase();

// relative path in attribute
if (name === 'src' || name === 'href') {
return absoluteToDoc(doc, value);
Expand Down
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/typings/snapshot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { serializedNodeWithId, INode, idNodeMap, MaskInputOptions, SlimDOMOption
export declare const IGNORED_NODE = -2;
export declare function absoluteToStylesheet(cssText: string | null, href: string): string;
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
export declare function transformAttribute(doc: Document, element: HTMLElement, tagName: string, name: string, value: string | null, maskAllText: boolean, unmaskTextSelector: string | undefined | null, maskTextFn: MaskTextFn | undefined): string | null;
export declare function transformAttribute(doc: Document, element: HTMLElement, _tagName: string, _name: string, value: string | null, maskAllText: boolean, unmaskTextSelector: string | undefined | null, maskTextFn: MaskTextFn | undefined): string | null;
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null, unblockSelector: string | null): boolean;
export declare function needMaskingText(node: Node | null, maskTextClass: string | RegExp, maskTextSelector: string | null, unmaskTextSelector: string | null, maskAllText: boolean): boolean;
export declare function serializeNodeWithId(n: Node | INode, options: {
Expand Down
37 changes: 26 additions & 11 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function initMutationObserver(
// If this callback returns `false`, we do not want to process the mutations
// This can be used to e.g. do a manual full snapshot when mutations become too large, or similar.
if (options.onMutation && options.onMutation(mutations) === false) {
return;
return;
}
mutationBuffer.processMutations(mutations);
}),
Expand Down Expand Up @@ -231,7 +231,9 @@ function initMouseInteractionObserver({
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
return (event: MouseEvent | TouchEvent) => {
const target = getEventTarget(event) as Node;
if (isBlocked(target as Node, blockClass, blockSelector, unblockSelector)) {
if (
isBlocked(target as Node, blockClass, blockSelector, unblockSelector)
) {
return;
}
const e = isTouchEvent(event) ? event.changedTouches[0] : event;
Expand Down Expand Up @@ -275,11 +277,20 @@ export function initScrollObserver({
sampling,
}: Pick<
observerParam,
'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'blockSelector' | 'unblockSelector' | 'sampling'
| 'scrollCb'
| 'doc'
| 'mirror'
| 'blockClass'
| 'blockSelector'
| 'unblockSelector'
| 'sampling'
>): listenerHandler {
const updatePosition = throttle<UIEvent>((evt) => {
const target = getEventTarget(evt);
if (!target || isBlocked(target as Node, blockClass, blockSelector, unblockSelector)) {
if (
!target ||
isBlocked(target as Node, blockClass, blockSelector, unblockSelector)
) {
return;
}
const id = mirror.getId(target as INode);
Expand Down Expand Up @@ -350,17 +361,18 @@ function initInputObserver({
}: observerParam): listenerHandler {
function eventHandler(event: Event) {
let target = getEventTarget(event);
const tagName = target && (target as Element).tagName;

const userTriggered = event.isTrusted;
/**
* If a site changes the value 'selected' of an option element, the value of its parent element, usually a select element, will be changed as well.
* We can treat this change as a value change of the select element the current target belongs to.
*/
if (target && (target as Element).tagName === 'OPTION')
target = (target as Element).parentElement;
if (tagName === 'OPTION') target = (target as Element).parentElement;
if (
!target ||
!(target as Element).tagName ||
INPUT_TAGS.indexOf((target as Element).tagName) < 0 ||
!tagName ||
INPUT_TAGS.indexOf(tagName) < 0 ||
isBlocked(target as Node, blockClass, blockSelector, unblockSelector)
) {
return;
Expand All @@ -386,7 +398,7 @@ function initInputObserver({
hasInputMaskOptions({
maskInputOptions,
maskInputSelector,
tagName: (target as HTMLElement).tagName,
tagName,
type,
})
) {
Expand All @@ -395,7 +407,7 @@ function initInputObserver({
maskInputOptions,
maskInputSelector,
unmaskInputSelector,
tagName: (target as HTMLElement).tagName,
tagName,
type,
value: text,
maskInputFn,
Expand Down Expand Up @@ -754,7 +766,10 @@ function initMediaInteractionObserver({
throttle(
callbackWrapper((event: Event) => {
const target = getEventTarget(event);
if (!target || isBlocked(target as Node, blockClass, blockSelector, unblockSelector)) {
if (
!target ||
isBlocked(target as Node, blockClass, blockSelector, unblockSelector)
) {
return;
}
const { currentTime, volume, muted } = target as HTMLMediaElement;
Expand Down
21 changes: 19 additions & 2 deletions packages/rrweb/test/__snapshots__/integration.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4440,13 +4440,13 @@ exports[`record integration tests correctly masks & unmasks attribute values 1`]
{
"id": 32,
"attributes": {
"value": "new value"
"value": "*** *****"
}
},
{
"id": 34,
"attributes": {
"value": "new value"
"value": "*** *****"
}
}
],
Expand Down Expand Up @@ -6524,6 +6524,23 @@ exports[`record integration tests should mask text in form elements 1`] = `
"top": 0
}
}
},
{
"type": 3,
"data": {
"source": 0,
"texts": [],
"attributes": [
{
"id": 87,
"attributes": {
"value": "*** *****"
}
}
],
"removes": [],
"adds": []
}
}
]"
`;
Expand Down
7 changes: 7 additions & 0 deletions packages/rrweb/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,13 @@ describe('record integration tests', function (this: ISuite) {
getHtml.call(this, 'form.html', { maskAllText: true }),
);

// Ensure also masked when we change stuff
await page.evaluate(() => {
document
.querySelector('input[type="submit"]')
?.setAttribute('value', 'new value');
});

const snapshots = await page.evaluate('window.snapshots');
assertSnapshot(snapshots);
});
Expand Down

0 comments on commit 950bd2c

Please sign in to comment.