UNPKG

@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) 15.9 kB
{ "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 type ElementVisibilityResult = {\n isVisible: boolean;\n isCSSVisible: boolean;\n isViewportVisible: boolean;\n hasSize: boolean;\n ratio: number;\n};\n\nfunction isAncestorOpacityVisible(el: Element, win: Window & typeof globalThis): boolean {\n let node: Element | null = el.parentElement;\n while (node) {\n const s = win.getComputedStyle?.(node);\n if (s && (parseFloat(s.opacity) || 0) <= 0) {\n return false;\n }\n node = node.parentElement;\n }\n return true;\n}\n\nexport function getElementVisibility(el: Element): ElementVisibilityResult {\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 elHasSize = rect.width > 0 && rect.height > 0;\n\n const isViewportVisible =\n elHasSize &&\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 isOwnStyleVisible =\n !!style &&\n style.display !== 'none' &&\n style.visibility !== 'hidden' &&\n (parseFloat(style.opacity) || 0) > 0;\n\n const isCSSVisible = isOwnStyleVisible && isAncestorOpacityVisible(el, win);\n\n const isVisible = isCSSVisible && isViewportVisible;\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 isCSSVisible,\n isViewportVisible,\n hasSize: elHasSize,\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": "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", "names": ["_a"] }