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
11 changes: 11 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,17 @@ function record<T = eventWithTime>(
},
}),
),
selectionCb: (p) => {
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Selection,
...p,
},
}),
);
},
blockClass,
ignoreClass,
maskTextClass,
Expand Down
53 changes: 53 additions & 0 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
styleDeclarationCallback,
IWindow,
MutationBufferParam,
SelectionRange,
selectionCallback,
} from '../types';
import MutationBuffer from './mutation';

Expand Down Expand Up @@ -752,6 +754,47 @@ function initFontObserver({ fontCb, doc }: observerParam): listenerHandler {
};
}

function initSelectionObserver(param: observerParam): listenerHandler {
const { doc, mirror, blockClass, selectionCb } = param;
let collapsed = true;

const updateSelection = () => {
const selection = doc.getSelection();

if (!selection || (collapsed && selection?.isCollapsed)) return;

collapsed = selection.isCollapsed || false;

const ranges: SelectionRange[] = [];
const count = selection.rangeCount || 0;

for (let i = 0; i < count; i++) {
const range = selection.getRangeAt(i);

const { startContainer, startOffset, endContainer, endOffset } = range;

const blocked =
isBlocked(startContainer, blockClass, true) ||
isBlocked(endContainer, blockClass, true);

if (blocked) continue;

ranges.push({
start: mirror.getId(startContainer),
startOffset,
end: mirror.getId(endContainer),
endOffset,
});
}

selectionCb({ ranges });
};

updateSelection();

return on('selectionchange', updateSelection);
Copy link
Contributor

Choose a reason for hiding this comment

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

The on function here expects a third 'target' argument; I presume it should be doc?

Copy link
Contributor

Choose a reason for hiding this comment

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

@0jinxing can you confirm that it should be doc passed as the third argument?
Otherwise it's trying to addEventListener on undefined

Copy link
Contributor

Choose a reason for hiding this comment

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

@YunFeng0817 I'm not sure how this has escaped everyone's notice if it's been merged!

}

function mergeHooks(o: observerParam, hooks: hooksParam) {
const {
mutationCb,
Expand All @@ -765,6 +808,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
styleDeclarationCb,
canvasMutationCb,
fontCb,
selectionCb,
} = o;
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
if (hooks.mutation) {
Expand Down Expand Up @@ -832,6 +876,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
}
fontCb(...p);
};
o.selectionCb = (...p: Arguments<selectionCallback>) => {
if (hooks.selection) {
hooks.selection(...p);
}
selectionCb(...p);
};
}

export function initObservers(
Expand Down Expand Up @@ -863,6 +913,8 @@ export function initObservers(
: () => {
//
};
const selectionObserver = initSelectionObserver(o);

// plugins
const pluginHandlers: listenerHandler[] = [];
for (const plugin of o.plugins) {
Expand All @@ -883,6 +935,7 @@ export function initObservers(
styleSheetObserver();
styleDeclarationObserver();
fontObserver();
selectionObserver();
pluginHandlers.forEach((h) => h());
};
}
29 changes: 29 additions & 0 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,35 @@ export class Replayer {
}
break;
}
case IncrementalSource.Selection: {
const selectionSet = new Set<Selection>();
const ranges = d.ranges.map(
({ start, startOffset, end, endOffset }) => {
const startContainer = this.mirror.getNode(start);
const endContainer = this.mirror.getNode(end);

if (!startContainer || !endContainer) return;

const result = new Range();

result.setStart(startContainer, startOffset);
result.setEnd(endContainer, endOffset);
const doc = startContainer.ownerDocument;
const selection = doc?.getSelection();
selection && selectionSet.add(selection);

return {
range: result,
selection,
};
},
);

selectionSet.forEach((s) => s.removeAllRanges());

ranges.forEach((r) => r && r.selection?.addRange(r.range));
break;
}
default:
}
}
Expand Down
21 changes: 21 additions & 0 deletions packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export enum IncrementalSource {
Log,
Drag,
StyleDeclaration,
Selection,
}

export type mutationData = {
Expand Down Expand Up @@ -142,6 +143,10 @@ export type fontData = {
source: IncrementalSource.Font;
} & fontParam;

export type selectionData = {
source: IncrementalSource.Selection;
} & selectionParam;

export type incrementalData =
| mutationData
| mousemoveData
Expand All @@ -153,6 +158,7 @@ export type incrementalData =
| styleSheetRuleData
| canvasMutationData
| fontData
| selectionData
| styleDeclarationData;

export type event =
Expand Down Expand Up @@ -261,6 +267,7 @@ export type observerParam = {
viewportResizeCb: viewportResizeCallback;
inputCb: inputCallback;
mediaInteractionCb: mediaInteractionCallback;
selectionCb: selectionCallback;
blockClass: blockClass;
blockSelector: string | null;
ignoreClass: string;
Expand Down Expand Up @@ -331,6 +338,7 @@ export type hooksParam = {
styleDeclaration?: styleDeclarationCallback;
canvasMutation?: canvasMutationCallback;
font?: fontCallback;
selection?: selectionCallback;
};

// https://dom.spec.whatwg.org/#interface-mutationrecord
Expand Down Expand Up @@ -619,6 +627,19 @@ export type DocumentDimension = {
absoluteScale: number;
};

export type SelectionRange = {
start: number;
startOffset: number;
end: number;
endOffset: number;
};

export type selectionParam = {
ranges: Array<SelectionRange>;
};

export type selectionCallback = (p: selectionParam) => void;

export type DeprecatedMirror = {
map: {
[key: number]: INode;
Expand Down
179 changes: 179 additions & 0 deletions packages/rrweb/test/events/selection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { EventType, eventWithTime, IncrementalSource } from '../../src/types';

const now = Date.now();

const events: eventWithTime[] = [
{
type: EventType.DomContentLoaded,
data: {},
timestamp: now,
},
{
type: EventType.Load,
data: {},
timestamp: now + 100,
},
{
type: EventType.Meta,
data: {
href: 'about:blank',
width: 1920,
height: 1080,
},
timestamp: now + 200,
},
{
type: EventType.FullSnapshot,
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: 3,
textContent: '\\\\n ',
id: 5,
},
{
type: 2,
tagName: 'meta',
attributes: {
charset: 'UTF-8',
},
childNodes: [],
id: 6,
},
{
type: 3,
textContent: '\\\\n ',
id: 7,
},
],
id: 4,
},
{
type: 3,
textContent: '\\\\n ',
id: 8,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 3,
textContent: '\\\\n Lorem, ipsum\\\\n ',
id: 10,
},
{
type: 2,
tagName: 'span',
attributes: {
id: 'startNode',
},
childNodes: [
{
type: 3,
textContent:
'\\\\n Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores culpa\\\\n corporis voluptas odit nobis recusandae inventore, magni praesentium\\\\n maiores perferendis quaerat excepturi officia minus velit voluptate\\\\n placeat minima? Nesciunt, eum!\\\\n ',
id: 12,
},
],
id: 11,
},
{
type: 3,
textContent:
'\\\\n dolor sit amet consectetur adipisicing elit. Ad repellendus quas hic\\\\n deleniti, delectus consequatur voluptas aliquam dolore voluptates repellat\\\\n perferendis aperiam saepe maxime officia rem corporis beatae, assumenda\\\\n doloribus.\\\\n ',
id: 13,
},
{
type: 2,
tagName: 'span',
attributes: {
id: 'endNode',
},
childNodes: [
{
type: 3,
textContent:
'\\\\n Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae\\\\n explicabo omnis dolores magni, ea doloribus possimus debitis reiciendis\\\\n distinctio perferendis nihil ipsum officiis pariatur laboriosam quas,\\\\n corrupti vero vitae minus.\\\\n ',
id: 15,
},
],
id: 14,
},
{
type: 3,
textContent: '\\\\n \\\\n ',
id: 16,
},
{
type: 2,
tagName: 'script',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'SCRIPT_PLACEHOLDER',
id: 18,
},
],
id: 17,
},
{
type: 3,
textContent: '\\\\n \\\\n \\\\n\\\\n',
id: 19,
},
],
id: 9,
},
],
id: 3,
},
],
id: 1,
},
initialOffset: {
left: 0,
top: 0,
},
},
timestamp: now + 300,
},
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Selection,
ranges: [
{
start: 12,
startOffset: 11,
end: 15,
endOffset: 6,
},
],
},
timestamp: now + 400,
},
];

export default events;
Loading