Skip to content

Commit

Permalink
Fix: Trigger mouse movement & hover with mouse up/down in sync mode (r…
Browse files Browse the repository at this point in the history
…rweb-io#1191)

* Trigger mouse movement & hover with mouse up/down in sync mode

* Trigger touchActive and mouseDown on flush
  • Loading branch information
Juice10 authored Mar 28, 2023
1 parent 0138ab8 commit 1e6f71b
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-impalas-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rrweb': patch
---

Only apply touch-active styling on flush
5 changes: 5 additions & 0 deletions .changeset/violet-zebras-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rrweb': patch
---

Trigger mouse movement and hover with mouse up and mouse down events when replayer.pause(...) is called.
33 changes: 26 additions & 7 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class Replayer {

private mousePos: mouseMovePos | null = null;
private touchActive: boolean | null = null;
private lastMouseDownEvent: [Node, Event] | null = null;

// Keep the rootNode of the last hovered element. So when hovering a new element, we can remove the last hovered element's :hover style.
private lastHoveredRootNode: Document | ShadowRoot;
Expand Down Expand Up @@ -299,6 +300,20 @@ export class Replayer {
);
this.mousePos = null;
}

if (this.touchActive === true) {
this.mouse.classList.add('touch-active');
} else if (this.touchActive === false) {
this.mouse.classList.remove('touch-active');
}
this.touchActive = null;

if (this.lastMouseDownEvent) {
const [target, event] = this.lastMouseDownEvent;
target.dispatchEvent(event);
}
this.lastMouseDownEvent = null;

if (this.lastSelectionData) {
this.applySelection(this.lastSelectionData);
this.lastSelectionData = null;
Expand Down Expand Up @@ -614,12 +629,6 @@ export class Replayer {
const castFn = this.getCastFn(event, true);
castFn();
}
if (this.touchActive === true) {
this.mouse.classList.add('touch-active');
} else if (this.touchActive === false) {
this.mouse.classList.remove('touch-active');
}
this.touchActive = null;
};

private getCastFn = (event: eventWithTime, isSync = false) => {
Expand Down Expand Up @@ -1108,7 +1117,7 @@ export class Replayer {
/**
* Same as the situation of missing input target.
*/
if (d.id === -1 || isSync) {
if (d.id === -1) {
break;
}
const event = new Event(MouseInteractions[d.type].toLowerCase());
Expand Down Expand Up @@ -1137,12 +1146,19 @@ export class Replayer {
case MouseInteractions.Click:
case MouseInteractions.TouchStart:
case MouseInteractions.TouchEnd:
case MouseInteractions.MouseDown:
case MouseInteractions.MouseUp:
if (isSync) {
if (d.type === MouseInteractions.TouchStart) {
this.touchActive = true;
} else if (d.type === MouseInteractions.TouchEnd) {
this.touchActive = false;
}
if (d.type === MouseInteractions.MouseDown) {
this.lastMouseDownEvent = [target, event];
} else if (d.type === MouseInteractions.MouseUp) {
this.lastMouseDownEvent = null;
}
this.mousePos = {
x: d.x,
y: d.y,
Expand Down Expand Up @@ -1172,6 +1188,9 @@ export class Replayer {
this.mouse.classList.add('touch-active');
} else if (d.type === MouseInteractions.TouchEnd) {
this.mouse.classList.remove('touch-active');
} else {
// for MouseDown & MouseUp also invoke default behavior
target.dispatchEvent(event);
}
}
break;
Expand Down
113 changes: 113 additions & 0 deletions packages/rrweb/test/events/hover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { IncrementalSource, MouseInteractions } from '@rrweb/types';
import type { eventWithTime } from '../../../types/src';

const events: eventWithTime[] = [
{
type: 4,
data: {
href: '',
width: 1600,
height: 900,
},
timestamp: 0,
},
{
type: 2,
data: {
node: {
type: 0,
childNodes: [
{ type: 1, name: 'html', publicId: '', systemId: '', id: 2 },
{
type: 2,
tagName: 'html',
attributes: { lang: 'en' },
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [
{
id: 101,
type: 2,
tagName: 'style',
attributes: {},
childNodes: [
{
id: 102,
type: 3,
isStyle: true,
textContent: 'div:hover { background-color: gold; }',
},
],
},
],
id: 4,
},
{ type: 3, textContent: '\n ', id: 13 },
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{ type: 3, textContent: '\n ', id: 15 },
{
type: 2,
tagName: 'div',
attributes: {
style:
'border: 1px solid #000000; width: 100px; height: 100px;',
},
childNodes: [{ type: 3, textContent: '\n ', id: 17 }],
id: 16,
},
],
id: 14,
},
],
id: 3,
},
],
id: 1,
},
initialOffset: { left: 0, top: 0 },
},
timestamp: 10,
},
{
type: 3,
data: {
source: IncrementalSource.MouseInteraction,
type: MouseInteractions.MouseDown,
id: 16,
x: 30,
y: 30,
},
timestamp: 100,
},
{
type: 3,
data: {
source: IncrementalSource.MouseInteraction,
type: MouseInteractions.MouseUp,
id: 16,
x: 30,
y: 30,
},
timestamp: 150,
},
{
type: 3,
data: {
source: IncrementalSource.MouseInteraction,
type: MouseInteractions.Click,
id: 16,
x: 30,
y: 30,
},
timestamp: 155,
},
];

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.
71 changes: 71 additions & 0 deletions packages/rrweb/test/replay/hover.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as fs from 'fs';
import * as path from 'path';
import { launchPuppeteer, waitForRAF } from '../utils';
import { toMatchImageSnapshot } from 'jest-image-snapshot';
import type * as puppeteer from 'puppeteer';
import events from '../events/hover';

interface ISuite {
code: string;
styles: string;
browser: puppeteer.Browser;
page: puppeteer.Page;
}

expect.extend({ toMatchImageSnapshot });

describe('replayer', function () {
jest.setTimeout(10_000);

let code: ISuite['code'];
let styles: ISuite['styles'];
let browser: ISuite['browser'];
let page: ISuite['page'];

beforeAll(async () => {
browser = await launchPuppeteer({ devtools: true });

const bundlePath = path.resolve(__dirname, '../../dist/rrweb.js');
const stylePath = path.resolve(
__dirname,
'../../src/replay/styles/style.css',
);
code = fs.readFileSync(bundlePath, 'utf8');
styles = fs.readFileSync(stylePath, 'utf8');
});

beforeEach(async () => {
page = await browser.newPage();
await page.goto('about:blank');
await page.addStyleTag({
content: styles,
});
await page.evaluate(code);
await page.evaluate(`let events = ${JSON.stringify(events)}`);

page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
});

afterEach(async () => {
await page.close();
});

afterAll(async () => {
await browser.close();
});

describe('hover', () => {
it('should trigger hover on mouseDown', async () => {
await page.evaluate(`
const { Replayer } = rrweb;
const replayer = new Replayer(events);
replayer.pause(110); // mouseDown event is at 100
`);

await waitForRAF(page);

const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
});
});

0 comments on commit 1e6f71b

Please sign in to comment.