Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mask value attribute changes for elements in maskInputOptions #602

Merged
merged 8 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ function record<T = eventWithTime>(
inlineStylesheet,
maskInputOptions,
maskTextFn,
maskInputFn,
recordCanvas,
sampling,
slimDOMOptions,
Expand Down
16 changes: 15 additions & 1 deletion src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import {
addedNodeMutation,
MaskTextFn,
Mirror,
MaskInputFn,
} from '../types';
import {
isBlocked,
isAncestorRemoved,
isIgnored,
isIframeINode,
hasShadowRoot,
maskInputValue,
} from '../utils';
import { IframeManager } from './iframe-manager';
import { ShadowDomManager } from './shadow-dom-manager';
Expand Down Expand Up @@ -167,6 +169,7 @@ export default class MutationBuffer {
private inlineStylesheet: boolean;
private maskInputOptions: MaskInputOptions;
private maskTextFn: MaskTextFn | undefined;
private maskInputFn: MaskInputFn | undefined;
private recordCanvas: boolean;
private slimDOMOptions: SlimDOMOptions;
private doc: Document;
Expand All @@ -184,6 +187,7 @@ export default class MutationBuffer {
inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions,
maskTextFn: MaskTextFn | undefined,
maskInputFn: MaskInputFn | undefined,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
doc: Document,
Expand All @@ -198,6 +202,7 @@ export default class MutationBuffer {
this.inlineStylesheet = inlineStylesheet;
this.maskInputOptions = maskInputOptions;
this.maskTextFn = maskTextFn;
this.maskInputFn = maskInputFn;
this.recordCanvas = recordCanvas;
this.slimDOMOptions = slimDOMOptions;
this.emissionCallback = cb;
Expand Down Expand Up @@ -443,7 +448,16 @@ export default class MutationBuffer {
break;
}
case 'attributes': {
const value = (m.target as HTMLElement).getAttribute(m.attributeName!);
let value = (m.target as HTMLElement).getAttribute(m.attributeName!);
if (m.attributeName === 'value') {
value = maskInputValue({
maskInputOptions: this.maskInputOptions,
tagName: (m.target as HTMLElement).tagName,
type: (m.target as HTMLElement).getAttribute('type'),
value,
maskInputFn: this.maskInputFn,
});
}
if (isBlocked(m.target, this.blockClass) || value === m.oldValue) {
return;
}
Expand Down
26 changes: 16 additions & 10 deletions src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isBlocked,
isTouchEvent,
patch,
maskInputValue,
} from '../utils';
import {
mutationCallBack,
Expand Down Expand Up @@ -83,6 +84,7 @@ export function initMutationObserver(
inlineStylesheet: boolean,
maskInputOptions: MaskInputOptions,
maskTextFn: MaskTextFn | undefined,
maskInputFn: MaskInputFn | undefined,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
mirror: Mirror,
Expand All @@ -102,6 +104,7 @@ export function initMutationObserver(
inlineStylesheet,
maskInputOptions,
maskTextFn,
maskInputFn,
recordCanvas,
slimDOMOptions,
doc,
Expand Down Expand Up @@ -366,11 +369,13 @@ function initInputObserver(
] ||
maskInputOptions[type as keyof MaskInputOptions]
) {
if (maskInputFn) {
text = maskInputFn(text);
} else {
text = '*'.repeat(text.length);
}
text = maskInputValue({
maskInputOptions,
tagName: (target as HTMLElement).tagName,
type,
value: text,
maskInputFn,
});
}
cbWithDedup(target, { text, isChecked });
// if a radio was checked
Expand Down Expand Up @@ -476,21 +481,21 @@ function initMediaInteractionObserver(
blockClass: blockClass,
mirror: Mirror,
): listenerHandler {
const handler = (type: MediaInteractions ) => (event: Event) => {
const handler = (type: MediaInteractions) => (event: Event) => {
const target = getEventTarget(event);
if (!target || isBlocked(target as Node, blockClass)) {
return;
}
mediaInteractionCb({
type,
id: mirror.getId(target as INode),
currentTime: (target as HTMLMediaElement).currentTime
currentTime: (target as HTMLMediaElement).currentTime,
});
};
const handlers = [
on('play', handler(MediaInteractions.Play)),
on('pause', handler(MediaInteractions.Pause)),
on('seeked', handler(MediaInteractions.Seeked))
on('play', handler(MediaInteractions.Play)),
on('pause', handler(MediaInteractions.Pause)),
on('seeked', handler(MediaInteractions.Seeked)),
];
return () => {
handlers.forEach((h) => h());
Expand Down Expand Up @@ -716,6 +721,7 @@ export function initObservers(
o.inlineStylesheet,
o.maskInputOptions,
o.maskTextFn,
o.maskInputFn,
o.recordCanvas,
o.slimDOMOptions,
o.mirror,
Expand Down
3 changes: 3 additions & 0 deletions src/record/shadow-dom-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Mirror,
scrollCallback,
SamplingStrategy,
MaskInputFn,
} from '../types';
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
import { IframeManager } from './iframe-manager';
Expand All @@ -19,6 +20,7 @@ type BypassOptions = {
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
maskTextFn: MaskTextFn | undefined;
maskInputFn: MaskInputFn | undefined;
recordCanvas: boolean;
sampling: SamplingStrategy;
slimDOMOptions: SlimDOMOptions;
Expand Down Expand Up @@ -54,6 +56,7 @@ export class ShadowDomManager {
this.bypassOptions.inlineStylesheet,
this.bypassOptions.maskInputOptions,
this.bypassOptions.maskTextFn,
this.bypassOptions.maskInputFn,
this.bypassOptions.recordCanvas,
this.bypassOptions.slimDOMOptions,
this.mirror,
Expand Down
55 changes: 45 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import {
scrollData,
inputData,
DocumentDimension,
MaskInputFn,
} from './types';
import {
INode,
IGNORED_NODE,
serializedNodeWithId,
NodeType,
isShadowRoot,
MaskInputOptions,
} from 'rrweb-snapshot';

export function on(
Expand Down Expand Up @@ -53,7 +55,7 @@ export function createMirror(): Mirror {
delete this.map[id];
if (n.childNodes) {
n.childNodes.forEach((child) =>
this.removeNodeFromMap(child as Node as INode),
this.removeNodeFromMap((child as Node) as INode),
);
}
},
Expand Down Expand Up @@ -275,7 +277,7 @@ export function isAncestorRemoved(target: INode, mirror: Mirror): boolean {
if (!target.parentNode) {
return true;
}
return isAncestorRemoved(target.parentNode as unknown as INode, mirror);
return isAncestorRemoved((target.parentNode as unknown) as INode, mirror);
}

export function isTouchEvent(
Expand All @@ -286,13 +288,13 @@ export function isTouchEvent(

export function polyfill(win = window) {
if ('NodeList' in win && !win.NodeList.prototype.forEach) {
win.NodeList.prototype.forEach = Array.prototype
.forEach as unknown as NodeList['forEach'];
win.NodeList.prototype.forEach = (Array.prototype
.forEach as unknown) as NodeList['forEach'];
}

if ('DOMTokenList' in win && !win.DOMTokenList.prototype.forEach) {
win.DOMTokenList.prototype.forEach = Array.prototype
.forEach as unknown as DOMTokenList['forEach'];
win.DOMTokenList.prototype.forEach = (Array.prototype
.forEach as unknown) as DOMTokenList['forEach'];
}

// https://github.com/Financial-Times/polyfill-service/pull/183
Expand Down Expand Up @@ -396,7 +398,7 @@ export class TreeIndex {
const node = mirror.getNode(id);
node?.childNodes.forEach((childNode) => {
if ('__sn' in childNode) {
deepRemoveFromMirror((childNode as unknown as INode).__sn.id);
deepRemoveFromMirror(((childNode as unknown) as INode).__sn.id);
}
});
};
Expand Down Expand Up @@ -460,8 +462,12 @@ export class TreeIndex {
scrollMap: TreeIndex['scrollMap'];
inputMap: TreeIndex['inputMap'];
} {
const { tree, removeNodeMutations, textMutations, attributeMutations } =
this;
const {
tree,
removeNodeMutations,
textMutations,
attributeMutations,
} = this;

const batchMutationData: mutationData = {
source: IncrementalSource.Mutation,
Expand Down Expand Up @@ -650,5 +656,34 @@ export function getBaseDimension(
export function hasShadowRoot<T extends Node>(
n: T,
): n is T & { shadowRoot: ShadowRoot } {
return Boolean((n as unknown as Element)?.shadowRoot);
return Boolean(((n as unknown) as Element)?.shadowRoot);
}

// TODO: move me to rrweb-snapshot
// we are doing similar things there as well, would be best to keep things consistent
export function maskInputValue({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to move this code now? I can release a new version of rrweb-snapshot when this gets merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm out of office all day today, earliest I can do this would be about 24 hours from now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can merge this PR and I can follow up with a cleanup PR that moves this to rrweb-snapshot tomorrow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maskInputOptions,
tagName,
type,
value,
maskInputFn,
}: {
maskInputOptions: MaskInputOptions;
tagName: string;
type: string | null;
value: string | null;
maskInputFn?: MaskInputFn;
}): string {
let text = value || '';
if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
maskInputOptions[type as keyof MaskInputOptions]
) {
if (maskInputFn) {
text = maskInputFn(text);
} else {
text = '*'.repeat(text.length);
}
}
return text;
}
Loading