@appsurify-testmap/rrweb-utils
Version:
This package contains the shared utility functions used across rrweb packages. See the [guide](../../guide.md) for more info on rrweb.
8 lines (7 loc) • 16 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/index.ts"],
"sourcesContent": ["type PrototypeOwner = Node | ShadowRoot | MutationObserver | Element;\ntype TypeofPrototypeOwner =\n | typeof Node\n | typeof ShadowRoot\n | typeof MutationObserver\n | typeof Element;\n\ntype BasePrototypeCache = {\n Node: typeof Node.prototype;\n ShadowRoot: typeof ShadowRoot.prototype;\n MutationObserver: typeof MutationObserver.prototype;\n Element: typeof Element.prototype;\n};\n\nconst testableAccessors = {\n Node: ['childNodes', 'parentNode', 'parentElement', 'textContent'] as const,\n ShadowRoot: ['host', 'styleSheets'] as const,\n Element: ['shadowRoot', 'querySelector', 'querySelectorAll'] as const,\n MutationObserver: [] as const,\n} as const;\n\nconst testableMethods = {\n Node: ['contains', 'getRootNode'] as const,\n ShadowRoot: ['getSelection'],\n Element: [],\n MutationObserver: ['constructor'],\n} as const;\n\nconst untaintedBasePrototype: Partial<BasePrototypeCache> = {};\n\n/*\n When angular patches things - particularly the MutationObserver -\n they pass the `isNativeFunction` check\n That then causes performance issues\n because Angular's change detection\n doesn't like sharing a mutation observer\n Checking for the presence of the Zone object\n on global is a good-enough proxy for Angular\n to cover most cases\n (you can configure zone.js to have a different name\n on the global object and should then manually run rrweb\n outside the Zone)\n */\nexport const isAngularZonePresent = (): boolean => {\n return !!(globalThis as { Zone?: unknown }).Zone;\n};\n\nexport function getUntaintedPrototype<T extends keyof BasePrototypeCache>(\n key: T,\n): BasePrototypeCache[T] {\n if (untaintedBasePrototype[key])\n return untaintedBasePrototype[key] as BasePrototypeCache[T];\n\n const defaultObj = globalThis[key] as TypeofPrototypeOwner;\n const defaultPrototype = defaultObj.prototype as BasePrototypeCache[T];\n\n // use list of testable accessors to check if the prototype is tainted\n const accessorNames =\n key in testableAccessors ? testableAccessors[key] : undefined;\n const isUntaintedAccessors = Boolean(\n accessorNames &&\n // @ts-expect-error 2345\n accessorNames.every((accessor: keyof typeof defaultPrototype) =>\n Boolean(\n Object.getOwnPropertyDescriptor(defaultPrototype, accessor)\n ?.get?.toString()\n .includes('[native code]'),\n ),\n ),\n );\n\n const methodNames = key in testableMethods ? testableMethods[key] : undefined;\n const isUntaintedMethods = Boolean(\n methodNames &&\n methodNames.every(\n // @ts-expect-error 2345\n (method: keyof typeof defaultPrototype) =>\n typeof defaultPrototype[method] === 'function' &&\n defaultPrototype[method]?.toString().includes('[native code]'),\n ),\n );\n\n if (isUntaintedAccessors && isUntaintedMethods && !isAngularZonePresent()) {\n untaintedBasePrototype[key] = defaultObj.prototype as BasePrototypeCache[T];\n return defaultObj.prototype as BasePrototypeCache[T];\n }\n\n try {\n const iframeEl = document.createElement('iframe');\n document.body.appendChild(iframeEl);\n const win = iframeEl.contentWindow;\n if (!win) return defaultObj.prototype as BasePrototypeCache[T];\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n const untaintedObject = (win as any)[key]\n .prototype as BasePrototypeCache[T];\n // cleanup\n document.body.removeChild(iframeEl);\n\n if (!untaintedObject) return defaultPrototype;\n\n return (untaintedBasePrototype[key] = untaintedObject);\n } catch {\n return defaultPrototype;\n }\n}\n\nconst untaintedAccessorCache: Record<\n string,\n (this: PrototypeOwner, ...args: unknown[]) => unknown\n> = {};\n\nexport function getUntaintedAccessor<\n K extends keyof BasePrototypeCache,\n T extends keyof BasePrototypeCache[K],\n>(\n key: K,\n instance: BasePrototypeCache[K],\n accessor: T,\n): BasePrototypeCache[K][T] {\n const cacheKey = `${key}.${String(accessor)}`;\n if (untaintedAccessorCache[cacheKey])\n return untaintedAccessorCache[cacheKey].call(\n instance,\n ) as BasePrototypeCache[K][T];\n\n const untaintedPrototype = getUntaintedPrototype(key);\n // eslint-disable-next-line @typescript-eslint/unbound-method\n const untaintedAccessor = Object.getOwnPropertyDescriptor(\n untaintedPrototype,\n accessor,\n )?.get;\n\n if (!untaintedAccessor) return instance[accessor];\n\n untaintedAccessorCache[cacheKey] = untaintedAccessor;\n\n return untaintedAccessor.call(instance) as BasePrototypeCache[K][T];\n}\n\ntype BaseMethod<K extends keyof BasePrototypeCache> = (\n this: BasePrototypeCache[K],\n ...args: unknown[]\n) => unknown;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst untaintedMethodCache: Record<string, BaseMethod<any>> = {};\nexport function getUntaintedMethod<\n K extends keyof BasePrototypeCache,\n T extends keyof BasePrototypeCache[K],\n>(\n key: K,\n instance: BasePrototypeCache[K],\n method: T,\n): BasePrototypeCache[K][T] {\n const cacheKey = `${key}.${String(method)}`;\n if (untaintedMethodCache[cacheKey])\n return untaintedMethodCache[cacheKey].bind(\n instance,\n ) as BasePrototypeCache[K][T];\n\n const untaintedPrototype = getUntaintedPrototype(key);\n const untaintedMethod = untaintedPrototype[method];\n\n if (typeof untaintedMethod !== 'function') return instance[method];\n\n untaintedMethodCache[cacheKey] = untaintedMethod as BaseMethod<K>;\n\n return untaintedMethod.bind(instance) as BasePrototypeCache[K][T];\n}\n\nexport function childNodes(n: Node): NodeListOf<Node> {\n return getUntaintedAccessor('Node', n, 'childNodes');\n}\n\nexport function parentNode(n: Node): ParentNode | null {\n return getUntaintedAccessor('Node', n, 'parentNode');\n}\n\nexport function parentElement(n: Node): HTMLElement | null {\n return getUntaintedAccessor('Node', n, 'parentElement');\n}\n\nexport function textContent(n: Node): string | null {\n return getUntaintedAccessor('Node', n, 'textContent');\n}\n\nexport function contains(n: Node, other: Node): boolean {\n return getUntaintedMethod('Node', n, 'contains')(other);\n}\n\nexport function getRootNode(n: Node): Node {\n return getUntaintedMethod('Node', n, 'getRootNode')();\n}\n\nexport function host(n: ShadowRoot): Element | null {\n if (!n || !('host' in n)) return null;\n return getUntaintedAccessor('ShadowRoot', n, 'host');\n}\n\nexport function styleSheets(n: ShadowRoot): StyleSheetList {\n return n.styleSheets;\n}\n\nexport function shadowRoot(n: Node): ShadowRoot | null {\n if (!n || !('shadowRoot' in n)) return null;\n return getUntaintedAccessor('Element', n as Element, 'shadowRoot');\n}\n\nexport function querySelector(n: Element, selectors: string): Element | null {\n return getUntaintedAccessor('Element', n, 'querySelector')(selectors);\n}\n\nexport function querySelectorAll(\n n: Element,\n selectors: string,\n): NodeListOf<Element> {\n return getUntaintedAccessor('Element', n, 'querySelectorAll')(selectors);\n}\n\nexport function mutationObserverCtor(): (typeof MutationObserver)['prototype']['constructor'] {\n return getUntaintedPrototype('MutationObserver').constructor;\n}\n\n// copy from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts\nexport function patch(\n source: { [key: string]: any },\n name: string,\n replacement: (...args: unknown[]) => unknown,\n): () => void {\n try {\n if (!(name in source)) {\n return () => {\n //\n };\n }\n\n const original = source[name] as () => unknown;\n const wrapped = replacement(original);\n\n // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work\n // otherwise it'll throw \"TypeError: Object.defineProperties called on non-object\"\n if (typeof wrapped === 'function') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n wrapped.prototype = wrapped.prototype || {};\n Object.defineProperties(wrapped, {\n __rrweb_original__: {\n enumerable: false,\n value: original,\n },\n });\n }\n\n source[name] = wrapped;\n\n return () => {\n source[name] = original;\n };\n } catch {\n return () => {\n //\n };\n // This can throw if multiple fill happens on a global object like XMLHttpRequest\n // Fixes https://github.com/getsentry/sentry-javascript/issues/2043\n }\n}\n\nexport function describeNode(el: Element): string {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : '';\n const classes = el.classList.length\n ? '.' + Array.from(el.classList).join('.')\n : '';\n return `${tag}${id}${classes}`;\n}\n\nexport function getElementVisibility(el: Element): {\n isVisible: boolean;\n ratio: number;\n} {\n const win = el.ownerDocument?.defaultView ?? window;\n const rect = el.getBoundingClientRect();\n\n const viewportWidth =\n win.innerWidth || win.document.documentElement.clientWidth || 0;\n const viewportHeight =\n win.innerHeight || win.document.documentElement.clientHeight || 0;\n\n const isRectVisible =\n rect.width > 0 &&\n rect.height > 0 &&\n rect.bottom > 0 &&\n rect.right > 0 &&\n rect.top < viewportHeight &&\n rect.left < viewportWidth;\n\n const style = win.getComputedStyle?.(el);\n const isStyleVisible =\n !!style &&\n style.display !== 'none' &&\n style.visibility !== 'hidden' &&\n (parseFloat(style.opacity) || 0) > 0;\n\n const isVisible = isStyleVisible && isRectVisible;\n\n let ratio = 0;\n if (isVisible) {\n const xOverlap = Math.max(\n 0,\n Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0),\n );\n const yOverlap = Math.max(\n 0,\n Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0),\n );\n const intersectionArea = xOverlap * yOverlap;\n const elementArea = rect.width * rect.height;\n ratio = elementArea > 0 ? intersectionArea / elementArea : 0;\n }\n\n return {\n isVisible,\n ratio,\n };\n}\n\nexport default {\n childNodes,\n parentNode,\n parentElement,\n textContent,\n contains,\n getRootNode,\n host,\n styleSheets,\n shadowRoot,\n querySelector,\n querySelectorAll,\n mutationObserver: mutationObserverCtor,\n patch,\n describeNode,\n getElementVisibility,\n};\n"],
"mappings": ";;;;;;;;;;;;;4GAcA,MAAMA,EAAoB,CACxB,KAAM,CAAC,aAAc,aAAc,gBAAiB,aAAa,EACjE,WAAY,CAAC,OAAQ,aAAa,EAClC,QAAS,CAAC,aAAc,gBAAiB,kBAAkB,EAC3D,iBAAkB,CAAC,CACrB,EAEMC,EAAkB,CACtB,KAAM,CAAC,WAAY,aAAa,EAChC,WAAY,CAAC,cAAc,EAC3B,QAAS,CAAC,EACV,iBAAkB,CAAC,aAAa,CAClC,EAEMC,EAAsD,CAAA,EAe/CC,EAAuB,IAC3B,CAAC,CAAE,WAAkC,KAGvC,SAASC,EACdC,EACuB,CACvB,GAAIH,EAAuBG,CAAG,EAC5B,OAAOH,EAAuBG,CAAG,EAE7B,MAAAC,EAAa,WAAWD,CAAG,EAC3BE,EAAmBD,EAAW,UAG9BE,EACJH,KAAOL,EAAoBA,EAAkBK,CAAG,EAAI,OAChDI,EAAuB,GAC3BD,GAEEA,EAAc,MAAOE,GACnB,SAAA,MAAA,IACEC,GAAAC,EAAA,OAAO,yBAAyBL,EAAkBG,CAAQ,IAA1D,KAAA,OAAAE,EACI,MADJ,MAAAD,EACS,SAAA,EACN,SAAS,eAAA,EACd,CACF,GAGEE,EAAcR,KAAOJ,EAAkBA,EAAgBI,CAAG,EAAI,OAC9DS,EAAqB,GACzBD,GACEA,EAAY,MAETE,GACC,OAAA,OAAA,OAAOR,EAAiBQ,CAAM,GAAM,cACpCH,EAAAL,EAAiBQ,CAAM,IAAvB,KAAA,OAAAH,EAA0B,SAAA,EAAW,SAAS,eAAA,EAAA,CAClD,GAGJ,GAAIH,GAAwBK,GAAsB,CAACX,EAAA,EAC1B,OAAAD,EAAAG,CAAG,EAAIC,EAAW,UAClCA,EAAW,UAGhB,GAAA,CACI,MAAAU,EAAW,SAAS,cAAc,QAAQ,EACvC,SAAA,KAAK,YAAYA,CAAQ,EAClC,MAAMC,EAAMD,EAAS,cACjB,GAAA,CAACC,EAAK,OAAOX,EAAW,UAGtB,MAAAY,EAAmBD,EAAYZ,CAAG,EACrC,UAIC,OAFK,SAAA,KAAK,YAAYW,CAAQ,EAE7BE,EAEGhB,EAAuBG,CAAG,EAAIa,EAFTX,CAES,OAChCY,EADgC,CAE/B,OAAAZ,CACT,CACF,CAEA,MAAMa,EAGF,CAAA,EAEY,SAAAC,EAIdhB,EACAiB,EACAZ,EAC0B,OAC1B,MAAMa,EAAW,GAAGlB,KAAO,OAAOK,CAAQ,IAC1C,GAAIU,EAAuBG,CAAQ,EAC1B,OAAAH,EAAuBG,CAAQ,EAAE,KACtCD,CAAA,EAGE,MAAAE,EAAqBpB,EAAsBC,CAAG,EAE9CoB,GAAoBb,EAAA,OAAO,yBAC/BY,EACAd,CACC,IAHuB,KAAA,OAAAE,EAGvB,IAEH,OAAKa,GAELL,EAAuBG,CAAQ,EAAIE,EAE5BA,EAAkB,KAAKH,CAAQ,GAJPA,EAASZ,CAAQ,CAKlD,CAQA,MAAMgB,EAAwD,CAAA,EAC9C,SAAAC,EAIdtB,EACAiB,EACAP,EAC0B,CAC1B,MAAMQ,EAAW,GAAGlB,KAAO,OAAOU,CAAM,IACxC,GAAIW,EAAqBH,CAAQ,EACxB,OAAAG,EAAqBH,CAAQ,EAAE,KACpCD,CAAA,EAIE,MAAAM,EADqBxB,EAAsBC,CAAG,EACTU,CAAM,EAEjD,OAAI,OAAOa,GAAoB,WAAmBN,EAASP,CAAM,GAEjEW,EAAqBH,CAAQ,EAAIK,EAE1BA,EAAgB,KAAKN,CAAQ,EACtC,CAEO,SAASO,EAAWC,EAA2B,CAC7C,OAAAT,EAAqB,OAAQS,EAAG,YAAY,CACrD,CAEO,SAASC,EAAWD,EAA4B,CAC9C,OAAAT,EAAqB,OAAQS,EAAG,YAAY,CACrD,CAEO,SAASE,EAAcF,EAA6B,CAClD,OAAAT,EAAqB,OAAQS,EAAG,eAAe,CACxD,CAEO,SAASG,EAAYH,EAAwB,CAC3C,OAAAT,EAAqB,OAAQS,EAAG,aAAa,CACtD,CAEgB,SAAAI,EAASJ,EAASK,EAAsB,CACtD,OAAOR,EAAmB,OAAQG,EAAG,UAAU,EAAEK,CAAK,CACxD,CAEO,SAASC,EAAYN,EAAe,CACzC,OAAOH,EAAmB,OAAQG,EAAG,aAAa,EAAE,CACtD,CAEO,SAASO,EAAKP,EAA+B,CAClD,MAAI,CAACA,GAAK,EAAE,SAAUA,GAAW,KAC1BT,EAAqB,aAAcS,EAAG,MAAM,CACrD,CAEO,SAASQ,EAAYR,EAA+B,CACzD,OAAOA,EAAE,WACX,CAEO,SAASS,EAAWT,EAA4B,CACrD,MAAI,CAACA,GAAK,EAAE,eAAgBA,GAAW,KAChCT,EAAqB,UAAWS,EAAc,YAAY,CACnE,CAEgB,SAAAU,EAAcV,EAAYW,EAAmC,CAC3E,OAAOpB,EAAqB,UAAWS,EAAG,eAAe,EAAEW,CAAS,CACtE,CAEgB,SAAAC,EACdZ,EACAW,EACqB,CACrB,OAAOpB,EAAqB,UAAWS,EAAG,kBAAkB,EAAEW,CAAS,CACzE,CAEO,SAASE,GAA8E,CACrF,OAAAvC,EAAsB,kBAAkB,EAAE,WACnD,CAGgB,SAAAwC,EACdC,EACAC,EACAC,EACY,CACR,GAAA,CACE,GAAA,EAAED,KAAQD,GACZ,MAAO,IAAM,CAAA,EAKT,MAAAG,EAAWH,EAAOC,CAAI,EACtBG,EAAUF,EAAYC,CAAQ,EAIhC,OAAA,OAAOC,GAAY,aAEbA,EAAA,UAAYA,EAAQ,WAAa,CAAA,EACzC,OAAO,iBAAiBA,EAAS,CAC/B,mBAAoB,CAClB,WAAY,GACZ,MAAOD,CACT,CAAA,CACD,GAGHH,EAAOC,CAAI,EAAIG,EAER,IAAM,CACXJ,EAAOC,CAAI,EAAIE,CAAA,CACjB,OACM7B,EADN,CAEA,MAAO,IAAM,CAAA,CAKf,CACF,CAEO,SAAS+B,EAAaC,EAAqB,CAC1C,MAAAC,EAAMD,EAAG,QAAQ,YAAY,EAC7BE,EAAKF,EAAG,GAAK,IAAIA,EAAG,KAAO,GAC3BG,EAAUH,EAAG,UAAU,OACzB,IAAM,MAAM,KAAKA,EAAG,SAAS,EAAE,KAAK,GAAG,EACvC,GACJ,MAAO,GAAGC,IAAMC,IAAKC,GACvB,CAEO,SAASC,EAAqBJ,EAGnC,eACM,MAAAlC,GAAML,KAAAuC,EAAG,gBAAH,KAAA,OAAAvC,EAAkB,cAAlB,KAAAA,EAAiC,OACvC4C,EAAOL,EAAG,sBAAA,EAEVM,EACJxC,EAAI,YAAcA,EAAI,SAAS,gBAAgB,aAAe,EAC1DyC,EACJzC,EAAI,aAAeA,EAAI,SAAS,gBAAgB,cAAgB,EAE5D0C,EACJH,EAAK,MAAQ,GACbA,EAAK,OAAS,GACdA,EAAK,OAAS,GACdA,EAAK,MAAQ,GACbA,EAAK,IAAME,GACXF,EAAK,KAAOC,EAERG,GAAQjD,EAAAM,EAAI,mBAAJ,KAAA,OAAAN,EAAA,KAAAM,EAAuBkC,CAAA,EAO/BU,EALJ,CAAC,CAACD,GACFA,EAAM,UAAY,QAClBA,EAAM,aAAe,WACpB,WAAWA,EAAM,OAAO,GAAK,GAAK,GAEDD,EAEpC,IAAIG,EAAQ,EACZ,GAAID,EAAW,CACb,MAAME,EAAW,KAAK,IACpB,EACA,KAAK,IAAIP,EAAK,MAAOC,CAAa,EAAI,KAAK,IAAID,EAAK,KAAM,CAAC,CAAA,EAEvDQ,EAAW,KAAK,IACpB,EACA,KAAK,IAAIR,EAAK,OAAQE,CAAc,EAAI,KAAK,IAAIF,EAAK,IAAK,CAAC,CAAA,EAExDS,EAAmBF,EAAWC,EAC9BE,EAAcV,EAAK,MAAQA,EAAK,OAC9BM,EAAAI,EAAc,EAAID,EAAmBC,EAAc,EAGtD,MAAA,CACL,UAAAL,EACA,MAAAC,CAAA,CAEJ,CAEA,MAAeK,EAAA,CACb,WAAAtC,EACA,WAAAE,EACA,cAAAC,EACA,YAAAC,EACA,SAAAC,EACA,YAAAE,EACA,KAAAC,EACA,YAAAC,EACA,WAAAC,EACA,cAAAC,EACA,iBAAAE,EACA,iBAAkBC,EAClB,MAAAC,EACA,aAAAM,EACA,qBAAAK,CACF",
"names": ["testableAccessors", "testableMethods", "untaintedBasePrototype", "isAngularZonePresent", "getUntaintedPrototype", "key", "defaultObj", "defaultPrototype", "accessorNames", "isUntaintedAccessors", "accessor", "_b", "_a", "methodNames", "isUntaintedMethods", "method", "iframeEl", "win", "untaintedObject", "e", "untaintedAccessorCache", "getUntaintedAccessor", "instance", "cacheKey", "untaintedPrototype", "untaintedAccessor", "untaintedMethodCache", "getUntaintedMethod", "untaintedMethod", "childNodes", "n", "parentNode", "parentElement", "textContent", "contains", "other", "getRootNode", "host", "styleSheets", "shadowRoot", "querySelector", "selectors", "querySelectorAll", "mutationObserverCtor", "patch", "source", "name", "replacement", "original", "wrapped", "describeNode", "el", "tag", "id", "classes", "getElementVisibility", "rect", "viewportWidth", "viewportHeight", "isRectVisible", "style", "isVisible", "ratio", "xOverlap", "yOverlap", "intersectionArea", "elementArea", "index"]
}