tracker: fix spritemap parser, add svgdoc cache

This commit is contained in:
nick-delirium 2025-03-04 16:37:40 +01:00
parent c025b2f1a5
commit 0ba1382c16
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
4 changed files with 51 additions and 8 deletions

View file

@ -8,6 +8,14 @@
- **[breaking]** new string dictionary message format
## 15.0.7
- fix for svg sprite handling
## 15.0.6
- fix for batch sending to prevent proxy wrappers
## 15.0.5
- update medv/finder to 4.0.2 for better support of css-in-js libs

View file

@ -324,6 +324,7 @@ export default class App {
fixedCanvasScaling: false,
disableCanvas: false,
captureIFrames: true,
disableSprites: false,
obscureTextEmails: true,
obscureTextNumbers: false,
crossdomain: {

View file

@ -23,6 +23,7 @@ import {
} from '../guards.js'
const iconCache = {}
const svgUrlCache = {}
const domParser = new DOMParser()
async function parseUseEl(useElement: SVGUseElement, mode: 'inline' | 'dataurl' | 'svgtext') {
@ -43,15 +44,42 @@ async function parseUseEl(useElement: SVGUseElement, mode: 'inline' | 'dataurl'
return iconCache[symbolId]
}
const response = await fetch(url)
const svgText = await response.text()
let svgDoc: Document
if (svgUrlCache[url]) {
if (svgUrlCache[url] === 1) {
await new Promise((resolve) => {
let tries = 0
const interval = setInterval(() => {
if (tries > 100) {
clearInterval(interval)
resolve(false)
}
if (svgUrlCache[url] !== 1) {
svgDoc = svgUrlCache[url]
clearInterval(interval)
resolve(true)
} else {
tries++
}
}, 100)
})
} else {
svgDoc = svgUrlCache[url] ?? `<svg xmlns="http://www.w3.org/2000/svg"></svg>`
}
} else {
svgUrlCache[url] = 1
const response = await fetch(url)
const svgText = await response.text()
svgDoc = domParser.parseFromString(svgText, 'image/svg+xml')
svgUrlCache[url] = svgDoc
}
const svgDoc = domParser.parseFromString(svgText, 'image/svg+xml')
// @ts-ignore
const symbol = svgDoc.getElementById(symbolId)
if (!symbol) {
console.debug('Openreplay: Symbol not found in SVG.')
return
return ''
}
if (mode === 'inline') {
@ -136,10 +164,13 @@ export default abstract class Observer {
private readonly indexes: Array<number> = []
private readonly attributesMap: Map<number, Set<string>> = new Map()
private readonly textSet: Set<number> = new Set()
private readonly disableSprites: boolean = false
constructor(
protected readonly app: App,
protected readonly isTopContext = false,
options: { disableSprites: boolean } = { disableSprites: false },
) {
this.disableSprites = options.disableSprites
this.observer = createMutationObserver(
this.app.safe((mutations) => {
for (const mutation of mutations) {
@ -249,7 +280,7 @@ export default abstract class Observer {
this.app.send(RemoveNodeAttribute(id, name))
}
if (isUseElement(node) && name === 'href') {
if (isUseElement(node) && name === 'href' && !this.disableSprites) {
parseUseEl(node, 'svgtext')
.then((svgData) => {
if (svgData) {

View file

@ -11,6 +11,7 @@ import { IN_BROWSER, hasOpenreplayAttribute, canAccessIframe } from '../../utils
export interface Options {
captureIFrames: boolean
disableSprites: boolean
}
type Context = Window & typeof globalThis
@ -24,14 +25,16 @@ export default class TopObserver extends Observer {
readonly app: App
constructor(params: { app: App; options: Partial<Options> }) {
super(params.app, true)
this.app = params.app
this.options = Object.assign(
const opts = Object.assign(
{
captureIFrames: true,
disableSprites: false,
},
params.options,
)
super(params.app, true, opts)
this.app = params.app
this.options = opts
// IFrames
this.app.nodes.attachNodeCallback((node) => {
if (