Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
78849f3
WIP dialog recording
Juice10 Jun 10, 2024
c1d850f
chore: its important to run `yarn build:all` before running `yarn dev`
Juice10 Jun 10, 2024
b657233
feat: trigger showModal from rrdom and rrweb
Juice10 Jun 10, 2024
337b4c4
feat: Add support for replaying modal and non modal dialog elements
Juice10 Jun 11, 2024
29d4827
chore: Update dev script to remove CLEAR_DIST_DIR flag
Juice10 Jun 11, 2024
dcc867c
Get modal recording and replay working
Juice10 Jun 12, 2024
1144c9d
DRY up dialog test and dedupe snapshot images
Juice10 Jun 12, 2024
7507f4d
Fix eslint error
Juice10 Jun 12, 2024
b8679a0
feat: Refactor dialog test to use updated attribute name
Juice10 Jun 12, 2024
6ec4727
feat: Update dialog test to include rr_open attribute
Juice10 Jun 12, 2024
f7db71c
chore: Add npm dependency [email protected]
Juice10 Jun 12, 2024
b300674
Fix dialog test
Juice10 Jun 12, 2024
178ec1f
Add more test cases for dialog
Juice10 Jun 12, 2024
4229391
Add changesets
Juice10 Jun 12, 2024
b0065ce
Clean up naming
Juice10 Jun 12, 2024
25678f4
Apply formatting changes
Juice10 Jun 12, 2024
b71c8b7
Refactor dialog open code
Juice10 Jun 12, 2024
05d4cf0
Merge branch 'juice10/record-dialog' of https://github.com/rrweb-io/r…
Juice10 Jun 12, 2024
e731636
Revert changed code that doesn't do anything
Juice10 Jun 12, 2024
184e4fa
Add documentation for unimplemented type
Juice10 Jun 12, 2024
6a58edc
chore: Remove unnecessary comments in dialog.test.ts
Juice10 Jun 12, 2024
8c51129
Merge branch 'master' of https://github.com/rrweb-io/rrweb into juice…
Juice10 Jun 26, 2024
0df7994
rename rr_open to rr_openMode
Juice10 Jun 26, 2024
1ce8f70
Replace todo with a skipped test
Juice10 Jun 26, 2024
8aeb47f
Add better logging for CI
Juice10 Jun 26, 2024
985ac9f
Rename rr_openMode to rr_open_mode
Juice10 Jun 26, 2024
d693da9
Remove unused images
Juice10 Jun 26, 2024
760908f
Move after iframe append based on @YunFeng0817's comment
Juice10 Jul 9, 2024
2dfeb01
Remove redundant dialog handling from rrdom.
Juice10 Jul 9, 2024
63c0def
Merge branch 'master' into juice10/record-dialog
Juice10 Jul 9, 2024
21c4826
Rename variables for dialog handling in rrweb replay module
Juice10 Jul 9, 2024
424e4c8
Merge branch 'juice10/record-dialog' of https://github.com/rrweb-io/r…
Juice10 Jul 9, 2024
30c7780
Merge branch 'master' into juice10/record-dialog
eoghanmurray Jul 31, 2024
9f2a27c
Update packages/rrdom/src/document.ts
Juice10 Aug 2, 2024
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
Prev Previous commit
Next Next commit
feat: trigger showModal from rrdom and rrweb
  • Loading branch information
Juice10 committed Jun 10, 2024
commit b657233eac3dbb0e161a9fe98de911ac34e9d052
38 changes: 35 additions & 3 deletions packages/rrdom/src/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from './document';
import type {
RRCanvasElement,
RRDialogElement,
RRElement,
RRIFrameElement,
RRMediaElement,
Expand Down Expand Up @@ -285,6 +286,24 @@ function diffAfterUpdatingChildren(
);
break;
}
case 'DIALOG': {
const dialog = oldElement as HTMLDialogElement;
const rrDialog = newRRElement as unknown as RRDialogElement;
const wasOpen = dialog.open;
const wasModal = dialog.matches('dialog:modal');
const isOpen = rrDialog.open;
const { isModal } = rrDialog;

const modeChanged = (wasModal && !isModal) || (!wasModal && isModal);

if (wasOpen && modeChanged) dialog.close();
if ((wasOpen && modeChanged) || (isOpen && !wasOpen)) {
if (isModal) dialog.showModal();
else dialog.show();
}

break;
}
}
break;
}
Expand Down Expand Up @@ -330,12 +349,25 @@ function diffProps(
}
};
} else if (newTree.tagName === 'IFRAME' && name === 'srcdoc') continue;
else oldTree.setAttribute(name, newValue);
else if (
newTree.tagName === 'DIALOG' &&
(name === 'rr_open' || name === 'open')
) {
const rrDialog = newTree as RRDialogElement;
const isModal = newAttributes.rr_open === 'modal';
const isOpen = isModal || newAttributes.open === '';
if (isModal) rrDialog.showModal();
else if (isOpen) rrDialog.show();
else rrDialog.close();
continue;
} else oldTree.setAttribute(name, newValue);
}

for (const { name } of Array.from(oldAttributes))
for (const { name } of Array.from(oldAttributes)) {
if (newTree.tagName === 'DIALOG' && (name === 'rr_open' || name === 'open'))
continue; // attributes are handled in diffAfterUpdatingChildren for Dialog elements
if (!(name in newAttributes)) oldTree.removeAttribute(name);

}
newTree.scrollLeft && (oldTree.scrollLeft = newTree.scrollLeft);
newTree.scrollTop && (oldTree.scrollTop = newTree.scrollTop);
}
Expand Down
22 changes: 22 additions & 0 deletions packages/rrdom/src/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,28 @@ export class BaseRRMediaElement extends BaseRRElement {
}
}

export class BaseRRDialogElement extends BaseRRElement {
public readonly tagName = 'DIALOG' as const;
public readonly nodeName = 'DIALOG' as const;
public open: boolean = false;
private _isModal: boolean = false;
get isModal() {
return this._isModal;
}
public close() {
this.open = false;
this._isModal = false;
}
public show() {
this.open = true;
this._isModal = false;
}
public showModal() {
this.open = true;
this._isModal = true;
}
}

export class BaseRRText extends BaseRRNode implements IRRText {
public readonly nodeType: number = NodeType.TEXT_NODE;
public readonly nodeName = '#text' as const;
Expand Down
6 changes: 6 additions & 0 deletions packages/rrdom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
type IRRDocumentType,
type IRRText,
type IRRComment,
BaseRRDialogElement,
} from './document';

export class RRDocument extends BaseRRDocument {
Expand Down Expand Up @@ -104,6 +105,9 @@ export class RRDocument extends BaseRRDocument {
case 'STYLE':
element = new RRStyleElement(upperTagName);
break;
case 'DIALOG':
element = new RRDialogElement(upperTagName);
break;
default:
element = new RRElement(upperTagName);
break;
Expand Down Expand Up @@ -151,6 +155,8 @@ export class RRElement extends BaseRRElement {

export class RRMediaElement extends BaseRRMediaElement {}

export class RRDialogElement extends BaseRRDialogElement {}

export class RRCanvasElement extends RRElement implements IRRElement {
public rr_dataURL: string | null = null;
public canvasMutations: {
Expand Down
112 changes: 112 additions & 0 deletions packages/rrdom/test/diff/dialog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @vitest-environment happy-dom
*/
import { vi, MockInstance } from 'vitest';
import {
NodeType as RRNodeType,
createMirror,
Mirror as NodeMirror,
serializedNodeWithId,
} from 'rrweb-snapshot';
import { RRDocument } from '../../src';
import { diff, ReplayerHandler } from '../../src/diff';

describe('diff algorithm for rrdom', () => {
let mirror: NodeMirror;
let replayer: ReplayerHandler;
let warn: MockInstance;
let elementSn: serializedNodeWithId;
let elementSn2: serializedNodeWithId;

beforeEach(() => {
mirror = createMirror();
replayer = {
mirror,
applyCanvas: () => {},
applyInput: () => {},
applyScroll: () => {},
applyStyleSheetMutation: () => {},
afterAppend: () => {},
};
document.write('<!DOCTYPE html><html><head></head><body></body></html>');
// Mock the original console.warn function to make the test fail once console.warn is called.
warn = vi.spyOn(console, 'warn');

elementSn = {
type: RRNodeType.Element,
tagName: 'DIALOG',
attributes: {},
childNodes: [],
id: 1,
};

elementSn2 = {
...elementSn,
attributes: {},
};
});

afterEach(() => {
// Check that warn was not called (fail on warning)
expect(warn).not.toBeCalled();
vi.resetAllMocks();
});
describe('diff dialog elements', () => {
vi.setConfig({ testTimeout: 60_000 });

it('should trigger `showModal` on rr_open:modal attributes', () => {
const tagName = 'DIALOG';
const node = document.createElement(tagName) as HTMLDialogElement;
vi.spyOn(node, 'matches').mockReturnValue(false); // matches is used to check if the dialog was opened with showModal
const showModalFn = vi.spyOn(node, 'showModal');

const rrDocument = new RRDocument();
const rrNode = rrDocument.createElement(tagName);
rrNode.attributes = { rr_open: 'modal' };

mirror.add(node, elementSn);
rrDocument.mirror.add(rrNode, elementSn);
diff(node, rrNode, replayer);

expect(showModalFn).toBeCalled();
});

it('should trigger `close` on rr_open removed', () => {
const tagName = 'DIALOG';
const node = document.createElement(tagName) as HTMLDialogElement;
node.showModal();
console.log('node', { x: node.getAttribute('open') });
vi.spyOn(node, 'matches').mockReturnValue(true); // matches is used to check if the dialog was opened with showModal
const closeFn = vi.spyOn(node, 'close');

const rrDocument = new RRDocument();
const rrNode = rrDocument.createElement(tagName);
rrNode.attributes = {};

mirror.add(node, elementSn);
rrDocument.mirror.add(rrNode, elementSn);
diff(node, rrNode, replayer);

expect(closeFn).toBeCalled();
});

it('should not trigger `close` on rr_open is kept', () => {
const tagName = 'DIALOG';
const node = document.createElement(tagName) as HTMLDialogElement;
node.setAttribute('rr_open', 'modal');
node.setAttribute('open', '');
const closeFn = vi.spyOn(node, 'close');

const rrDocument = new RRDocument();
const rrNode = rrDocument.createElement(tagName);
rrNode.attributes = { rr_open: 'modal' };

mirror.add(node, elementSn);
rrDocument.mirror.add(rrNode, elementSn);
diff(node, rrNode, replayer);

expect(closeFn).not.toBeCalled();
expect(node.open).toBe(true);
});
});
});
11 changes: 11 additions & 0 deletions packages/rrweb/src/replay/dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { RRNode } from 'rrdom';

export function triggerShowModalForModals(
node: HTMLDialogElement | Node | RRNode,
) {
if (node.nodeName !== 'DIALOG' || node instanceof RRNode) return;
const dialog = node as HTMLDialogElement;
console.log('dialog', dialog.getAttribute('rr_open'));
if (dialog.getAttribute('rr_open') !== 'modal') return;
dialog.showModal();
}
8 changes: 8 additions & 0 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,14 @@ export class Replayer {
value,
);
}

if (
attributeName === 'rr_open' &&
target.nodeName === 'DIALOG' &&
value === 'modal'
) {
triggerShowModalForModals(target);
}
} catch (error) {
this.warn(
'An error occurred may due to the checkout feature.',
Expand Down
38 changes: 37 additions & 1 deletion packages/rrweb/test/events/dialog-playback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,57 @@ const events: eventWithTime[] = [
},
timestamp: 1900000136,
},
// open dialog with .show()
{
type: 3,
data: {
source: IncrementalSource.Mutation,
adds: [],
removes: [],
texts: [],
attributes: [
{
id: 27,
attributes: { rr_open: 'modal' },
attributes: { open: '' },
},
],
},
timestamp: 1900001500,
},
// close dialog with .close()
{
type: 3,
data: {
source: IncrementalSource.Mutation,
adds: [],
removes: [],
texts: [],
attributes: [
{
id: 27,
attributes: { open: null },
},
],
},
timestamp: 1900002000,
},
// open dialog with .showModal()
{
type: 3,
data: {
source: IncrementalSource.Mutation,
adds: [],
removes: [],
texts: [],
attributes: [
{
id: 27,
attributes: { rr_open: 'modal' },
},
],
},
timestamp: 1900002500,
},
];

export default events;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading