fix(tracker): use node guards instead of instanceof in some cases; import type App
This commit is contained in:
parent
1495f3bc5d
commit
e57d90e5a1
14 changed files with 74 additions and 36 deletions
32
tracker/tracker/src/main/app/guards.ts
Normal file
32
tracker/tracker/src/main/app/guards.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export function isSVGElement(node: Element): node is SVGElement {
|
||||
return node.namespaceURI === 'http://www.w3.org/2000/svg';
|
||||
}
|
||||
|
||||
export function isElementNode(node: Node): node is Element {
|
||||
return node.nodeType === Node.ELEMENT_NODE
|
||||
}
|
||||
|
||||
export function isTextNode(node: Node): node is Text {
|
||||
return node.nodeType === Node.TEXT_NODE
|
||||
}
|
||||
|
||||
export function isRootNode(node: Node): boolean {
|
||||
return node.nodeType === Node.DOCUMENT_NODE ||
|
||||
node.nodeType === Node.DOCUMENT_FRAGMENT_NODE
|
||||
}
|
||||
|
||||
|
||||
type TagTypeMap = {
|
||||
HTML: HTMLHtmlElement
|
||||
IMG: HTMLImageElement
|
||||
INPUT: HTMLInputElement
|
||||
TEXTAREA: HTMLTextAreaElement
|
||||
SELECT: HTMLSelectElement
|
||||
LABEL: HTMLLabelElement
|
||||
IFRAME: HTMLIFrameElement
|
||||
STYLE: HTMLStyleElement | SVGStyleElement
|
||||
LINK: HTMLLinkElement
|
||||
}
|
||||
export function hasTag<T extends keyof TagTypeMap>(el: Node, tagName: T): el is TagTypeMap[typeof tagName] {
|
||||
return el.nodeName.toUpperCase() === tagName
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ export default class Nodes {
|
|||
listeners.push([type, elementListener]);
|
||||
}
|
||||
|
||||
registerNode(node: Node): [number, boolean] {
|
||||
registerNode(node: Node): [id: number, isNew: boolean] {
|
||||
let id: number = (node as any)[this.node_id];
|
||||
const isNew = id === undefined;
|
||||
if (isNew) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type App from "./index.js";
|
||||
import { stars, hasOpenreplayAttribute } from "../utils.js";
|
||||
import App from "./index.js";
|
||||
import { isInstance } from "./context.js";
|
||||
import { isElementNode } from "./guards.js";
|
||||
|
||||
export interface Options {
|
||||
obscureTextEmails: boolean;
|
||||
|
|
@ -21,7 +21,7 @@ export default class Sanitizer {
|
|||
handleNode(id: number, parentID: number, node: Node) {
|
||||
if (
|
||||
this.masked.has(parentID) ||
|
||||
(isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))
|
||||
(isElementNode(node) && hasOpenreplayAttribute(node, 'masked'))
|
||||
) {
|
||||
this.masked.add(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import { hasTag } from "../app/guards.js";
|
||||
import { IN_BROWSER } from "../utils.js";
|
||||
import { ConsoleLog } from "../../common/messages.js";
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
patchConsole(window.console);
|
||||
|
||||
app.nodes.attachNodeCallback(app.safe(node => {
|
||||
if (node instanceof HTMLIFrameElement) {
|
||||
if (hasTag(node, "IFRAME")) { // TODO: newContextCallback
|
||||
let context = node.contentWindow
|
||||
if (context) {
|
||||
patchConsole((context as (Window & typeof globalThis)).console)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from "../../common/messages.js";
|
||||
import { hasTag } from "../app/guards.js";
|
||||
|
||||
|
||||
export default function(app: App | null) {
|
||||
if (app === null) {
|
||||
|
|
@ -41,10 +43,7 @@ export default function(app: App | null) {
|
|||
};
|
||||
|
||||
app.nodes.attachNodeCallback((node: Node): void => {
|
||||
if (!(node instanceof HTMLStyleElement)) {
|
||||
return;
|
||||
}
|
||||
if (!(node.sheet instanceof CSSStyleSheet)) {
|
||||
if (!hasTag(node, "STYLE") || !node.sheet) {
|
||||
return;
|
||||
}
|
||||
if (node.textContent !== null && node.textContent.trim().length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type App from "../app/index.js";
|
||||
import type Message from "../../common/messages.js";
|
||||
import App from "../app/index.js";
|
||||
import { JSException } from "../../common/messages.js";
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type App from "../app/index.js";
|
||||
import { timestamp, isURL } from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../../common/messages.js";
|
||||
import { hasTag } from "../app/guards.js";
|
||||
|
||||
|
||||
const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ export default function (app: App): void {
|
|||
});
|
||||
|
||||
app.nodes.attachNodeCallback((node: Node): void => {
|
||||
if (!(node instanceof HTMLImageElement)) {
|
||||
if (!hasTag(node, "IMG")) {
|
||||
return;
|
||||
}
|
||||
app.nodes.attachElementListener('error', node, sendImgSrc);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import type App from "../app/index.js";
|
||||
import {
|
||||
normSpaces,
|
||||
IN_BROWSER,
|
||||
getLabelAttribute,
|
||||
hasOpenreplayAttribute,
|
||||
} from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { hasTag } from "../app/guards.js";
|
||||
import { SetInputTarget, SetInputValue, SetInputChecked } from "../../common/messages.js";
|
||||
|
||||
// TODO: take into consideration "contenteditable" attribute
|
||||
type TextEditableElement = HTMLInputElement | HTMLTextAreaElement
|
||||
function isTextEditable(node: any): node is TextEditableElement {
|
||||
if (node instanceof HTMLTextAreaElement) {
|
||||
if (hasTag(node, "TEXTAREA")) {
|
||||
return true;
|
||||
}
|
||||
if (!(node instanceof HTMLInputElement)) {
|
||||
if (!hasTag(node, "INPUT")) {
|
||||
return false;
|
||||
}
|
||||
const type = node.type;
|
||||
|
|
@ -28,7 +29,7 @@ function isTextEditable(node: any): node is TextEditableElement {
|
|||
}
|
||||
|
||||
function isCheckable(node: any): node is HTMLInputElement {
|
||||
if (!(node instanceof HTMLInputElement)) {
|
||||
if (!hasTag(node, "INPUT")) {
|
||||
return false;
|
||||
}
|
||||
const type = node.type;
|
||||
|
|
@ -42,7 +43,7 @@ const labelElementFor: (
|
|||
? (node) => {
|
||||
let p: Node | null = node;
|
||||
while ((p = p.parentNode) !== null) {
|
||||
if (p instanceof HTMLLabelElement) {
|
||||
if (hasTag(p, "LABEL")) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +55,7 @@ const labelElementFor: (
|
|||
: (node) => {
|
||||
let p: Node | null = node;
|
||||
while ((p = p.parentNode) !== null) {
|
||||
if (p instanceof HTMLLabelElement) {
|
||||
if (hasTag(p, "LABEL")) {
|
||||
return p as HTMLLabelElement;
|
||||
}
|
||||
}
|
||||
|
|
@ -183,7 +184,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
return;
|
||||
}
|
||||
// TODO: support multiple select (?): use selectedOptions; Need send target?
|
||||
if (node instanceof HTMLSelectElement) {
|
||||
if (hasTag(node, "SELECT")) {
|
||||
sendInputValue(id, node)
|
||||
app.attachEventListener(node, "change", () => {
|
||||
sendInputValue(id, node)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import { LongTask } from "../../common/messages.js";
|
||||
|
||||
// https://w3c.github.io/performance-timeline/#the-performanceentry-interface
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import type App from "../app/index.js";
|
||||
import { hasTag, isSVGElement } from "../app/guards.js";
|
||||
import {
|
||||
normSpaces,
|
||||
hasOpenreplayAttribute,
|
||||
getLabelAttribute,
|
||||
} from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { MouseMove, MouseClick } from "../../common/messages.js";
|
||||
import { getInputLabel } from "./input.js";
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ function _getTarget(target: Element): Element | null {
|
|||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
if (target instanceof SVGElement) {
|
||||
if (isSVGElement(target)) {
|
||||
let owner = target.ownerSVGElement;
|
||||
while (owner !== null) {
|
||||
target = owner;
|
||||
|
|
@ -89,7 +90,7 @@ export default function (app: App): void {
|
|||
if (dl !== null) {
|
||||
return dl;
|
||||
}
|
||||
if (target instanceof HTMLInputElement) {
|
||||
if (hasTag(target, "INPUT")) {
|
||||
return getInputLabel(target)
|
||||
}
|
||||
if (isClickable(target)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import { IN_BROWSER } from "../utils.js";
|
||||
import { PerformanceTrack } from "../../common/messages.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import { SetViewportScroll, SetNodeScroll } from "../../common/messages.js";
|
||||
import { isElementNode } from "../app/guards.js";
|
||||
|
||||
export default function (app: App): void {
|
||||
let documentScroll = false;
|
||||
|
|
@ -34,11 +35,11 @@ export default function (app: App): void {
|
|||
nodeScroll.clear();
|
||||
});
|
||||
|
||||
app.nodes.attachNodeCallback(node => {
|
||||
if (node instanceof Element && node.scrollLeft + node.scrollTop > 0) {
|
||||
nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
|
||||
}
|
||||
})
|
||||
// app.nodes.attachNodeCallback(node => {
|
||||
// if (isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
|
||||
// nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
|
||||
// }
|
||||
// })
|
||||
|
||||
app.attachEventListener(window, 'scroll', (e: Event): void => {
|
||||
const target = e.target;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import type Message from "../../common/messages.js";
|
||||
import type App from "../app/index.js";
|
||||
import { hasTag } from "../app/guards.js";
|
||||
import { isURL } from "../utils.js";
|
||||
import App from "../app/index.js";
|
||||
import { ResourceTiming, PageLoadTiming, PageRenderTiming } from "../../common/messages.js";
|
||||
|
||||
|
||||
// Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js
|
||||
|
||||
interface ResourcesTimeMap {
|
||||
|
|
@ -21,7 +22,7 @@ function getPaintBlocks(resources: ResourcesTimeMap): Array<PaintBlock> {
|
|||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
let src = '';
|
||||
if (element instanceof HTMLImageElement) {
|
||||
if (hasTag(element, "IMG")) {
|
||||
src = element.currentSrc || element.src;
|
||||
}
|
||||
if (!src) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import App from "../app/index.js";
|
||||
import type App from "../app/index.js";
|
||||
import {
|
||||
SetPageLocation,
|
||||
SetViewportSize,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue