Merge pull request #563 from openreplay/tracker-pf-fix
Tracker 3.5.13: performance improvements for a case of extensive dom * use of `.nodeType` property instead of `instanceof` when possible * use of `.nodeName` property instead of `instanceof` for Elements * 2 previous also solve issue of non-function `instanceof` under different iframe contexts * use map instead of array for storing recent nodes * check node scrolls on start only
This commit is contained in:
commit
99e71ba04b
5 changed files with 41 additions and 40 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "3.5.12",
|
||||
"version": "3.5.13",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default class App {
|
|||
}
|
||||
// TODO: keep better tactics, discard others (look https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon)
|
||||
this.attachEventListener(window, 'beforeunload', alertWorker, false);
|
||||
this.attachEventListener(document, 'mouseleave', alertWorker, false, false);
|
||||
this.attachEventListener(document.body, 'mouseleave', alertWorker, false, false);
|
||||
this.attachEventListener(document, 'visibilitychange', alertWorker, false);
|
||||
} catch (e) {
|
||||
this._debug("worker_start", e);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
type NodeCallback = (node: Node) => void;
|
||||
type NodeCallback = (node: Node, isStart: boolean) => void;
|
||||
type ElementListener = [string, EventListener];
|
||||
|
||||
export default class Nodes {
|
||||
|
|
@ -57,8 +57,8 @@ export default class Nodes {
|
|||
}
|
||||
return id;
|
||||
}
|
||||
callNodeCallbacks(node: Node): void {
|
||||
this.nodeCallbacks.forEach((cb) => cb(node));
|
||||
callNodeCallbacks(node: Node, isStart: boolean): void {
|
||||
this.nodeCallbacks.forEach((cb) => cb(node, isStart));
|
||||
}
|
||||
getID(node: Node): number | undefined {
|
||||
return (node as any)[this.node_id];
|
||||
|
|
|
|||
|
|
@ -45,16 +45,21 @@ function isObservable(node: Node): boolean {
|
|||
/*
|
||||
TODO:
|
||||
- fix unbinding logic + send all removals first (ensure sequence is correct)
|
||||
- use document as a 0-node in the upper context
|
||||
- use document as a 0-node in the upper context (should be updated in player at first)
|
||||
*/
|
||||
|
||||
enum RecentsType {
|
||||
New,
|
||||
Removed,
|
||||
Changed,
|
||||
}
|
||||
|
||||
export default abstract class Observer {
|
||||
private readonly observer: MutationObserver;
|
||||
private readonly commited: Array<boolean | undefined> = [];
|
||||
private readonly recents: Array<boolean | undefined> = [];
|
||||
private readonly myNodes: Array<boolean | undefined> = [];
|
||||
private readonly recents: Map<number, RecentsType> = new Map()
|
||||
private readonly indexes: Array<number> = [];
|
||||
private readonly attributesList: Array<Set<string> | undefined> = [];
|
||||
private readonly attributesMap: Map<number, Set<string>> = new Map();
|
||||
private readonly textSet: Set<number> = new Set();
|
||||
constructor(protected readonly app: App, protected readonly isTopContext = false) {
|
||||
this.observer = new MutationObserver(
|
||||
|
|
@ -79,17 +84,17 @@ export default abstract class Observer {
|
|||
if (id === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (id >= this.recents.length) { // TODO: something more convinient
|
||||
this.recents[id] = undefined;
|
||||
if (!this.recents.has(id)) {
|
||||
this.recents.set(id, RecentsType.Changed) // TODO only when altered
|
||||
}
|
||||
if (type === 'attributes') {
|
||||
const name = mutation.attributeName;
|
||||
if (name === null) {
|
||||
continue;
|
||||
}
|
||||
let attr = this.attributesList[id];
|
||||
let attr = this.attributesMap.get(id)
|
||||
if (attr === undefined) {
|
||||
this.attributesList[id] = attr = new Set();
|
||||
this.attributesMap.set(id, attr = new Set())
|
||||
}
|
||||
attr.add(name);
|
||||
continue;
|
||||
|
|
@ -105,9 +110,9 @@ export default abstract class Observer {
|
|||
}
|
||||
private clear(): void {
|
||||
this.commited.length = 0;
|
||||
this.recents.length = 0;
|
||||
this.recents.clear()
|
||||
this.indexes.length = 1;
|
||||
this.attributesList.length = 0;
|
||||
this.attributesMap.clear();
|
||||
this.textSet.clear();
|
||||
}
|
||||
|
||||
|
|
@ -176,11 +181,12 @@ export default abstract class Observer {
|
|||
}
|
||||
|
||||
private bindNode(node: Node): void {
|
||||
const r = this.app.nodes.registerNode(node);
|
||||
const id = r[0];
|
||||
this.recents[id] = r[1] || this.recents[id] || false;
|
||||
|
||||
this.myNodes[id] = true;
|
||||
const [ id, isNew ]= this.app.nodes.registerNode(node);
|
||||
if (isNew){
|
||||
this.recents.set(id, RecentsType.New)
|
||||
} else if (!this.recents.has(id)) {
|
||||
this.recents.set(id, RecentsType.Removed)
|
||||
}
|
||||
}
|
||||
|
||||
private bindTree(node: Node): void {
|
||||
|
|
@ -207,7 +213,7 @@ export default abstract class Observer {
|
|||
|
||||
private unbindNode(node: Node): void {
|
||||
const id = this.app.nodes.unregisterNode(node);
|
||||
if (id !== undefined && this.recents[id] === false) {
|
||||
if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
|
||||
this.app.send(new RemoveNode(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -256,12 +262,13 @@ export default abstract class Observer {
|
|||
if (sibling === null) {
|
||||
this.indexes[id] = 0;
|
||||
}
|
||||
const isNew = this.recents[id];
|
||||
const index = this.indexes[id];
|
||||
const recentsType = this.recents.get(id)
|
||||
const isNew = recentsType === RecentsType.New
|
||||
const index = this.indexes[id]
|
||||
if (index === undefined) {
|
||||
throw 'commitNode: missing node index';
|
||||
}
|
||||
if (isNew === true) {
|
||||
if (isNew) {
|
||||
if (isElementNode(node)) {
|
||||
let el: Element = node
|
||||
if (parentID !== undefined) {
|
||||
|
|
@ -294,10 +301,10 @@ export default abstract class Observer {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
if (isNew === false && parentID !== undefined) {
|
||||
if (recentsType === RecentsType.Removed && parentID !== undefined) {
|
||||
this.app.send(new MoveNode(id, parentID, index));
|
||||
}
|
||||
const attr = this.attributesList[id];
|
||||
const attr = this.attributesMap.get(id);
|
||||
if (attr !== undefined) {
|
||||
if (!isElementNode(node)) {
|
||||
throw 'commitNode: node is not an element';
|
||||
|
|
@ -326,19 +333,14 @@ export default abstract class Observer {
|
|||
}
|
||||
return (this.commited[id] = this._commitNode(id, node));
|
||||
}
|
||||
private commitNodes(): void {
|
||||
private commitNodes(isStart: boolean = false): void {
|
||||
let node;
|
||||
for (let id = 0; id < this.recents.length; id++) {
|
||||
// TODO: make things/logic nice here.
|
||||
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
||||
// Possible solution: separate new node commit (recents) and new attribute/move node commit
|
||||
// Otherwise commitNode is called on each node, which might be a lot
|
||||
if (!this.myNodes[id]) { continue }
|
||||
this.recents.forEach((type, id) => {
|
||||
this.commitNode(id);
|
||||
if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
|
||||
this.app.nodes.callNodeCallbacks(node);
|
||||
if (type === RecentsType.New && (node = this.app.nodes.getNode(id))) {
|
||||
this.app.nodes.callNodeCallbacks(node, isStart)
|
||||
}
|
||||
}
|
||||
})
|
||||
this.clear();
|
||||
}
|
||||
|
||||
|
|
@ -354,12 +356,11 @@ export default abstract class Observer {
|
|||
});
|
||||
this.bindTree(nodeToBind);
|
||||
beforeCommit(this.app.nodes.getID(node))
|
||||
this.commitNodes();
|
||||
this.commitNodes(true)
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.observer.disconnect();
|
||||
this.clear();
|
||||
this.myNodes.length = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export default function (app: App): void {
|
|||
nodeScroll.clear();
|
||||
});
|
||||
|
||||
app.nodes.attachNodeCallback(node => {
|
||||
if (isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
|
||||
app.nodes.attachNodeCallback((node, isStart) => {
|
||||
if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
|
||||
nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue