From 63da3e2425e3e013cbf5788f90d9340ee031960e Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 8 Jun 2023 16:12:54 +0200
Subject: [PATCH 001/102] Add Asset event type and capture assets

---
 packages/rrweb-snapshot/package.json          |   2 +-
 packages/rrweb-snapshot/src/snapshot.ts       |  53 +-
 packages/rrweb-snapshot/src/utils.ts          |  19 +
 packages/rrweb-snapshot/test/snapshot.test.ts |  86 ++-
 packages/rrweb/src/record/index.ts            |  26 +
 packages/rrweb/src/record/mutation.ts         |  13 +
 .../src/record/observers/asset-manager.ts     | 180 ++++++
 packages/rrweb/src/types.ts                   |  18 +
 packages/rrweb/test/record/asset.test.ts      | 558 ++++++++++++++++++
 .../test/record/cross-origin-iframes.test.ts  |   7 +-
 packages/types/src/index.ts                   |  19 +-
 yarn.lock                                     |   7 +-
 12 files changed, 979 insertions(+), 9 deletions(-)
 create mode 100644 packages/rrweb/src/record/observers/asset-manager.ts
 create mode 100644 packages/rrweb/test/record/asset.test.ts

diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index 14eb0fe0d9..e92c170a19 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -58,6 +58,6 @@
     "rollup-plugin-typescript2": "^0.31.2",
     "ts-jest": "^27.0.5",
     "ts-node": "^7.0.1",
-    "tslib": "^1.9.3"
+    "tslib": "^2.5.3"
   }
 }
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index d323e1af8c..fe9e27edf4 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -25,6 +25,7 @@ import {
   getInputType,
   toLowerCase,
   extractFileExtension,
+  getUrlsFromSrcset,
 } from './utils';
 
 let _id = 1;
@@ -447,6 +448,13 @@ function serializeNode(
      * `newlyAddedElement: true` skips scrollTop and scrollLeft check
      */
     newlyAddedElement?: boolean;
+    /**
+     * Called when an asset is detected.
+     * Example of assets:
+     *  - `src` attribute in `img` tags.
+     *  - `srcset` attribute in `img` tags.
+     */
+    onAssetDetected?: (result: { urls: string[] }) => unknown;
   },
 ): serializedNode | false {
   const {
@@ -464,6 +472,7 @@ function serializeNode(
     recordCanvas,
     keepIframeSrcFn,
     newlyAddedElement = false,
+    onAssetDetected,
   } = options;
   // Only record root id when document object is not the base document
   const rootId = getRootId(doc, mirror);
@@ -503,6 +512,7 @@ function serializeNode(
         keepIframeSrcFn,
         newlyAddedElement,
         rootId,
+        onAssetDetected,
       });
     case n.TEXT_NODE:
       return serializeTextNode(n as Text, {
@@ -604,6 +614,13 @@ function serializeElementNode(
      */
     newlyAddedElement?: boolean;
     rootId: number | undefined;
+    /**
+     * Called when an asset is detected.
+     * Example of assets:
+     *  - `src` attribute in `img` tags.
+     *  - `srcset` attribute in `img` tags.
+     */
+    onAssetDetected?: (result: { urls: string[] }) => unknown;
   },
 ): serializedNode | false {
   const {
@@ -619,10 +636,12 @@ function serializeElementNode(
     keepIframeSrcFn,
     newlyAddedElement = false,
     rootId,
+    onAssetDetected = false,
   } = options;
   const needBlock = _isBlockedElement(n, blockClass, blockSelector);
   const tagName = getValidTagName(n);
   let attributes: attributes = {};
+  const assets: string[] = [];
   const len = n.attributes.length;
   for (let i = 0; i < len; i++) {
     const attr = n.attributes[i];
@@ -729,7 +748,16 @@ function serializeElementNode(
     }
   }
   // save image offline
-  if (tagName === 'img' && inlineImages) {
+  if (tagName === 'img' && onAssetDetected) {
+    if (attributes.src) {
+      assets.push(attributes.src.toString());
+    }
+    if (attributes.srcset) {
+      assets.push(...getUrlsFromSrcset(attributes.srcset.toString()));
+    }
+    // TODO: decide if inlineImages should still be supported,
+    // and if so if it should be moved into `rrweb` package.
+  } else if (tagName === 'img' && inlineImages) {
     if (!canvasService) {
       canvasService = doc.createElement('canvas');
       canvasCtx = canvasService.getContext('2d');
@@ -811,6 +839,10 @@ function serializeElementNode(
     // In case old browsers don't support customElements
   }
 
+  if (assets.length && onAssetDetected) {
+    onAssetDetected({ urls: assets });
+  }
+
   return {
     type: NodeType.Element,
     tagName,
@@ -958,6 +990,13 @@ export function serializeNodeWithId(
       node: serializedElementNodeWithId,
     ) => unknown;
     stylesheetLoadTimeout?: number;
+    /**
+     * Called when an asset is detected.
+     * Example of assets:
+     *  - `src` attribute in `img` tags.
+     *  - `srcset` attribute in `img` tags.
+     */
+    onAssetDetected?: (result: { urls: string[] }) => unknown;
   },
 ): serializedNodeWithId | null {
   const {
@@ -983,6 +1022,7 @@ export function serializeNodeWithId(
     stylesheetLoadTimeout = 5000,
     keepIframeSrcFn = () => false,
     newlyAddedElement = false,
+    onAssetDetected,
   } = options;
   let { needsMask } = options;
   let { preserveWhiteSpace = true } = options;
@@ -1016,6 +1056,7 @@ export function serializeNodeWithId(
     recordCanvas,
     keepIframeSrcFn,
     newlyAddedElement,
+    onAssetDetected,
   });
   if (!_serializedNode) {
     // TODO: dev only
@@ -1096,6 +1137,7 @@ export function serializeNodeWithId(
       onStylesheetLoad,
       stylesheetLoadTimeout,
       keepIframeSrcFn,
+      onAssetDetected,
     };
 
     if (
@@ -1264,6 +1306,13 @@ function snapshot(
     ) => unknown;
     stylesheetLoadTimeout?: number;
     keepIframeSrcFn?: KeepIframeSrcFn;
+    /**
+     * Called when an asset is detected.
+     * Example of assets:
+     *  - `src` attribute in `img` tags.
+     *  - `srcset` attribute in `img` tags.
+     */
+    onAssetDetected?: (result: { urls: string[] }) => unknown;
   },
 ): serializedNodeWithId | null {
   const {
@@ -1286,6 +1335,7 @@ function snapshot(
     iframeLoadTimeout,
     onStylesheetLoad,
     stylesheetLoadTimeout,
+    onAssetDetected,
     keepIframeSrcFn = () => false,
   } = options || {};
   const maskInputOptions: MaskInputOptions =
@@ -1355,6 +1405,7 @@ function snapshot(
     stylesheetLoadTimeout,
     keepIframeSrcFn,
     newlyAddedElement: false,
+    onAssetDetected,
   });
 }
 
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 5ccc9082ed..e650fc5316 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -351,3 +351,22 @@ export function extractFileExtension(
   const match = url.pathname.match(regex);
   return match?.[1] ?? null;
 }
+
+export function getUrlsFromSrcset(srcset: string): string[] {
+  const urls: string[] = [];
+  const parts = srcset.split(',');
+  for (let i = 0; i < parts.length; i++) {
+    const trimmed = parts[i].trim();
+    const spaceIndex = trimmed.indexOf(' ');
+    if (spaceIndex === -1) {
+      // If no descriptor is specified, it's a single URL.
+      urls.push(trimmed);
+    } else {
+      // Otherwise, it's one or more URLs followed by a single descriptor.
+      // Since we don't know how long the URL will be, we'll assume it's everything
+      // after the first space.
+      urls.push(trimmed.substring(0, spaceIndex));
+    }
+  }
+  return urls;
+}
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index aa4bb428ee..faf23c9c86 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -205,7 +205,7 @@ describe('scrollTop/scrollLeft', () => {
   };
 
   it('should serialize scroll positions', () => {
-    const el = render(`<div stylel='overflow: auto; width: 1px; height: 1px;'>
+    const el = render(`<div style='overflow: auto; width: 1px; height: 1px;'>
       Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
     </div>`);
     el.scrollTop = 10;
@@ -257,3 +257,87 @@ describe('form', () => {
     expect(sel?.childNodes).toEqual([]); // shouldn't be stored in childNodes while in transit
   });
 });
+
+describe('onAssetDetected callback', () => {
+  const serializeNode = (
+    node: Node,
+    onAssetDetected: (result: { urls: string[] }) => void,
+  ): serializedNodeWithId | null => {
+    return serializeNodeWithId(node, {
+      doc: document,
+      mirror: new Mirror(),
+      blockClass: 'blockblock',
+      blockSelector: null,
+      maskTextClass: 'maskmask',
+      maskTextSelector: null,
+      skipChild: false,
+      inlineStylesheet: true,
+      maskTextFn: undefined,
+      maskInputFn: undefined,
+      slimDOMOptions: {},
+      newlyAddedElement: false,
+      inlineImages: false,
+      onAssetDetected,
+    });
+  };
+
+  const render = (html: string): HTMLDivElement => {
+    document.write(html);
+    return document.querySelector('div')!;
+  };
+
+  it('should detect `src` attribute in image', () => {
+    const el = render(`<div>
+      <img src="https://example.com/image.png" />
+    </div>`);
+
+    const callback = jest.fn();
+    serializeNode(el, callback);
+    expect(callback).toHaveBeenCalledWith({
+      urls: ['https://example.com/image.png'],
+    });
+  });
+
+  it('should detect `set` attribute in image with ObjectURL', () => {
+    const el = render(`<div>
+      <img src="blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4" />
+    </div>`);
+
+    const callback = jest.fn();
+    serializeNode(el, callback);
+    expect(callback).toHaveBeenCalledWith({
+      urls: ['blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4'],
+    });
+  });
+  it('should detect `srcset` attribute in image', () => {
+    const el = render(`<div>
+      <img srcset="https://example.com/images/team-photo.jpg, https://example.com/images/team-photo-retina.jpg 2x" />
+    </div>`);
+
+    const callback = jest.fn();
+    serializeNode(el, callback);
+    expect(callback).toHaveBeenCalledWith({
+      urls: [
+        'https://example.com/images/team-photo.jpg',
+        'https://example.com/images/team-photo-retina.jpg',
+      ],
+    });
+  });
+
+  it('should detect `src` attribute in two images', () => {
+    const el = render(`<div>
+      <img src="https://example.com/image.png" />
+      <img src="https://example.com/image2.png" />
+    </div>`);
+
+    const callback = jest.fn();
+    serializeNode(el, callback);
+    expect(callback).toBeCalledTimes(2);
+    expect(callback).toHaveBeenCalledWith({
+      urls: ['https://example.com/image.png'],
+    });
+    expect(callback).toHaveBeenCalledWith({
+      urls: ['https://example.com/image2.png'],
+    });
+  });
+});
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 7be978199d..e4c34f2ba6 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -27,6 +27,7 @@ import {
   scrollCallback,
   canvasMutationParam,
   adoptedStyleSheetParam,
+  assetParam,
 } from '@rrweb/types';
 import type { CrossOriginIframeMessageEventContent } from '../types';
 import { IframeManager } from './iframe-manager';
@@ -39,11 +40,13 @@ import {
   registerErrorHandler,
   unregisterErrorHandler,
 } from './error-handler';
+import AssetManager from './observers/asset-manager';
 
 let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void;
 
 let takeFullSnapshot!: (isCheckout?: boolean) => void;
 let canvasManager!: CanvasManager;
+let assetManager!: AssetManager;
 let recording = false;
 
 const mirror = createMirror();
@@ -80,6 +83,10 @@ function record<T = eventWithTime>(
     userTriggeredOnInput = false,
     collectFonts = false,
     inlineImages = false,
+    assetCaptureConfig = {
+      captureObjectURLs: true,
+      captureOrigins: false,
+    },
     plugins,
     keepIframeSrcFn = () => false,
     ignoreCSSAttributes = new Set([]),
@@ -258,6 +265,12 @@ function record<T = eventWithTime>(
       },
     });
 
+  const wrappedAssetEmit = (p: assetParam) =>
+    wrappedEmit({
+      type: EventType.Asset,
+      data: p,
+    });
+
   const wrappedAdoptedStyleSheetEmit = (a: adoptedStyleSheetParam) =>
     wrappedEmit({
       type: EventType.IncrementalSnapshot,
@@ -306,6 +319,12 @@ function record<T = eventWithTime>(
     dataURLOptions,
   });
 
+  assetManager = new AssetManager({
+    mutationCb: wrappedAssetEmit,
+    win: window,
+    assetCaptureConfig,
+  });
+
   const shadowDomManager = new ShadowDomManager({
     mutationCb: wrappedMutationEmit,
     scrollCb: wrappedScrollEmit,
@@ -328,6 +347,7 @@ function record<T = eventWithTime>(
       canvasManager,
       keepIframeSrcFn,
       processedNodeManager,
+      assetManager,
     },
     mirror,
   });
@@ -385,6 +405,11 @@ function record<T = eventWithTime>(
       onStylesheetLoad: (linkEl, childSn) => {
         stylesheetManager.attachLinkElement(linkEl, childSn);
       },
+      onAssetDetected: (assets) => {
+        assets.urls.forEach((url) => {
+          assetManager.capture(url);
+        });
+      },
       keepIframeSrcFn,
     });
 
@@ -529,6 +554,7 @@ function record<T = eventWithTime>(
           shadowDomManager,
           processedNodeManager,
           canvasManager,
+          assetManager,
           ignoreCSSAttributes,
           plugins:
             plugins
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index f4267af340..8189bfb36e 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -191,6 +191,7 @@ export default class MutationBuffer {
   private canvasManager: observerParam['canvasManager'];
   private processedNodeManager: observerParam['processedNodeManager'];
   private unattachedDoc: HTMLDocument;
+  private assetManager: observerParam['assetManager'];
 
   public init(options: MutationBufferParam) {
     (
@@ -216,6 +217,7 @@ export default class MutationBuffer {
         'shadowDomManager',
         'canvasManager',
         'processedNodeManager',
+        'assetManager',
       ] as const
     ).forEach((key) => {
       // just a type trick, the runtime result is correct
@@ -336,6 +338,11 @@ export default class MutationBuffer {
         onStylesheetLoad: (link, childSn) => {
           this.stylesheetManager.attachLinkElement(link, childSn);
         },
+        onAssetDetected: (assets) => {
+          assets.urls.forEach((url) => {
+            this.assetManager.capture(url);
+          });
+        },
       });
       if (sn) {
         adds.push({
@@ -595,6 +602,12 @@ export default class MutationBuffer {
           } else {
             return;
           }
+        } else if (
+          target.tagName === 'IMG' &&
+          (attributeName === 'src' || attributeName === 'srcset') &&
+          value
+        ) {
+          this.assetManager.capture(value);
         }
         if (!item) {
           item = {
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
new file mode 100644
index 0000000000..0f5b1486aa
--- /dev/null
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -0,0 +1,180 @@
+import type {
+  IWindow,
+  SerializedCanvasArg,
+  eventWithTime,
+  listenerHandler,
+} from '@rrweb/types';
+import type { assetCallback } from '@rrweb/types';
+import { encode } from 'base64-arraybuffer';
+
+import { patch } from '../../utils';
+import type { recordOptions } from '../../types';
+
+export default class AssetManager {
+  private urlObjectMap = new Map<string, File | Blob | MediaSource>();
+  private capturedURLs = new Set<string>();
+  private capturingURLs = new Set<string>();
+  private failedURLs = new Set<string>();
+  private resetHandlers: listenerHandler[] = [];
+  private mutationCb: assetCallback;
+  public readonly config: Exclude<
+    recordOptions<eventWithTime>['assetCaptureConfig'],
+    undefined
+  >;
+
+  public reset() {
+    this.urlObjectMap.clear();
+    this.capturedURLs.clear();
+    this.capturingURLs.clear();
+    this.failedURLs.clear();
+    this.resetHandlers.forEach((h) => h());
+  }
+
+  constructor(options: {
+    mutationCb: assetCallback;
+    win: IWindow;
+    assetCaptureConfig: Exclude<
+      recordOptions<eventWithTime>['assetCaptureConfig'],
+      undefined
+    >;
+  }) {
+    const { win } = options;
+
+    this.mutationCb = options.mutationCb;
+    this.config = options.assetCaptureConfig;
+
+    const urlObjectMap = this.urlObjectMap;
+
+    if (this.config.captureObjectURLs) {
+      try {
+        const restoreHandler = patch(
+          win.URL,
+          'createObjectURL',
+          function (original: (obj: File | Blob | MediaSource) => string) {
+            return function (obj: File | Blob | MediaSource) {
+              const url = original.apply(this, [obj]);
+              urlObjectMap.set(url, obj);
+              return url;
+            };
+          },
+        );
+        this.resetHandlers.push(restoreHandler);
+      } catch {
+        console.error('failed to patch URL.createObjectURL');
+      }
+
+      try {
+        const restoreHandler = patch(
+          win.URL,
+          'revokeObjectURL',
+          function (original: (objectURL: string) => void) {
+            return function (objectURL: string) {
+              urlObjectMap.delete(objectURL);
+              return original.apply(this, [objectURL]);
+            };
+          },
+        );
+        this.resetHandlers.push(restoreHandler);
+      } catch {
+        console.error('failed to patch URL.revokeObjectURL');
+      }
+    }
+  }
+
+  public shouldIgnore(url: string): boolean {
+    const originsToIgnore = ['data:'];
+    const urlIsBlob = url.startsWith(`blob:${window.location.origin}/`);
+
+    // Check if url is a blob and we should ignore blobs
+    if (urlIsBlob) return !this.config.captureObjectURLs;
+
+    // Check if url matches any ignorable origins
+    for (const origin of originsToIgnore) {
+      if (url.startsWith(origin)) return true;
+    }
+
+    // Check the captureOrigins
+    const captureOrigins = this.config.captureOrigins;
+    if (typeof captureOrigins === 'boolean') {
+      return !captureOrigins;
+    } else if (Array.isArray(captureOrigins)) {
+      const urlOrigin = new URL(url).origin;
+      return !captureOrigins.includes(urlOrigin);
+    }
+
+    return false;
+  }
+
+  public async getURLObject(
+    url: string,
+  ): Promise<File | Blob | MediaSource | null> {
+    const object = this.urlObjectMap.get(url);
+    if (object) {
+      return object;
+    }
+
+    try {
+      const response = await fetch(url);
+      const blob = await response.blob();
+      console.log('getURLObject', url, blob);
+      return blob;
+    } catch (e) {
+      console.warn(`getURLObject failed for ${url}`);
+      throw e;
+    }
+  }
+
+  public capture(url: string): {
+    status: 'capturing' | 'captured' | 'error' | 'refused';
+  } {
+    console.log('capture', url, this.shouldIgnore(url));
+    if (this.shouldIgnore(url)) return { status: 'refused' };
+
+    if (this.capturedURLs.has(url)) {
+      return { status: 'captured' };
+    } else if (this.capturingURLs.has(url)) {
+      return { status: 'capturing' };
+    } else if (this.failedURLs.has(url)) {
+      return { status: 'error' };
+    }
+    this.capturingURLs.add(url);
+    console.log('capturing');
+    void this.getURLObject(url)
+      .then(async (object) => {
+        console.log('captured', url);
+        if (object) {
+          let payload: SerializedCanvasArg;
+          if (object instanceof File || object instanceof Blob) {
+            const arrayBuffer = await object.arrayBuffer();
+            const base64 = encode(arrayBuffer); // cpu intensive, probably good idea to move all of this to a webworker
+
+            payload = {
+              rr_type: 'Blob',
+              type: object.type,
+              data: [
+                {
+                  rr_type: 'ArrayBuffer',
+                  base64, // base64
+                },
+              ],
+            };
+
+            this.capturedURLs.add(url);
+            this.capturingURLs.delete(url);
+
+            this.mutationCb({
+              url,
+              payload,
+            });
+          }
+        }
+      })
+      .catch(() => {
+        // TODO: add mutationCb for failed urls
+        this.failedURLs.add(url);
+        this.capturingURLs.delete(url);
+      });
+
+    return { status: 'capturing' };
+  }
+}
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index 10ffb62b43..d53ab90e15 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -39,6 +39,7 @@ import type {
   viewportResizeCallback,
 } from '@rrweb/types';
 import type ProcessedNodeManager from './record/processed-node-manager';
+import type AssetManager from './record/observers/asset-manager';
 
 export type recordOptions<T> = {
   emit?: (e: T, isCheckout?: boolean) => void;
@@ -68,6 +69,21 @@ export type recordOptions<T> = {
   userTriggeredOnInput?: boolean;
   collectFonts?: boolean;
   inlineImages?: boolean;
+  assetCaptureConfig?: {
+    /**
+     * Captures object URLs (blobs, files, media sources).
+     * More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
+     */
+    captureObjectURLs: boolean;
+    /**
+     * Allowlist of origins to capture object URLs from.
+     * [origin, origin, ...] to capture from specific origins.
+     *   e.g. ['https://example.com', 'https://www.example.com']
+     * Set to `true` capture from all origins.
+     * Set to `false` or `[]` to disable capturing from any origin apart from object URLs.
+     */
+    captureOrigins: string[] | true | false;
+  };
   plugins?: RecordPlugin[];
   // departed, please use sampling options
   mousemoveWait?: number;
@@ -115,6 +131,7 @@ export type observerParam = {
   shadowDomManager: ShadowDomManager;
   canvasManager: CanvasManager;
   processedNodeManager: ProcessedNodeManager;
+  assetManager: AssetManager;
   ignoreCSSAttributes: Set<string>;
   plugins: Array<{
     observer: (
@@ -150,6 +167,7 @@ export type MutationBufferParam = Pick<
   | 'shadowDomManager'
   | 'canvasManager'
   | 'processedNodeManager'
+  | 'assetManager'
 >;
 
 export type ReplayPlugin = {
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
new file mode 100644
index 0000000000..dfe486b406
--- /dev/null
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -0,0 +1,558 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import type * as puppeteer from 'puppeteer';
+import type { recordOptions } from '../../src/types';
+import type { listenerHandler, eventWithTime, assetEvent } from '@rrweb/types';
+import { EventType } from '@rrweb/types';
+import {
+  getServerURL,
+  launchPuppeteer,
+  startServer,
+  waitForRAF,
+} from '../utils';
+import type * as http from 'http';
+
+interface ISuite {
+  code: string;
+  browser: puppeteer.Browser;
+  page: puppeteer.Page;
+  events: eventWithTime[];
+  server: http.Server;
+  serverURL: string;
+  serverB: http.Server;
+  serverBURL: string;
+}
+
+interface IWindow extends Window {
+  rrweb: {
+    record: (
+      options: recordOptions<eventWithTime>,
+    ) => listenerHandler | undefined;
+    addCustomEvent<T>(tag: string, payload: T): void;
+    pack: (e: eventWithTime) => string;
+  };
+  emit: (e: eventWithTime) => undefined;
+  snapshots: eventWithTime[];
+}
+type ExtraOptions = {
+  assetCaptureConfig?: recordOptions<eventWithTime>['assetCaptureConfig'];
+};
+
+const BASE64_PNG_RECTANGLE =
+  'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAWtJREFUeF7t1cEJAEAIxEDtv2gProo8xgpCwuLezI3LGFhBMi0+iCCtHoLEeggiSM1AjMcPESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4TwVjsedWCiXGAAAAABJRU5ErkJggg==';
+
+async function injectRecordScript(
+  frame: puppeteer.Frame,
+  options?: ExtraOptions,
+) {
+  await frame.addScriptTag({
+    path: path.resolve(__dirname, '../../dist/rrweb-all.js'),
+  });
+  options = options || {};
+  await frame.evaluate((options) => {
+    (window as unknown as IWindow).snapshots = [];
+    const { record, pack } = (window as unknown as IWindow).rrweb;
+    const config: recordOptions<eventWithTime> = {
+      assetCaptureConfig: options.assetCaptureConfig,
+      emit(event) {
+        (window as unknown as IWindow).snapshots.push(event);
+        (window as unknown as IWindow).emit(event);
+      },
+    };
+    record(config);
+  }, options);
+
+  for (const child of frame.childFrames()) {
+    await injectRecordScript(child, options);
+  }
+}
+
+const setup = function (
+  this: ISuite,
+  content: string,
+  options?: ExtraOptions,
+): ISuite {
+  const ctx = {} as ISuite;
+  beforeAll(async () => {
+    ctx.browser = await launchPuppeteer();
+    ctx.server = await startServer();
+    ctx.serverURL = getServerURL(ctx.server);
+    ctx.serverB = await startServer();
+    ctx.serverBURL = getServerURL(ctx.serverB);
+
+    const bundlePath = path.resolve(__dirname, '../../dist/rrweb.js');
+    ctx.code = fs.readFileSync(bundlePath, 'utf8');
+  });
+
+  beforeEach(async () => {
+    ctx.page = await ctx.browser.newPage();
+    await ctx.page.goto('about:blank');
+    await ctx.page.setContent(
+      content
+        .replace(/\{SERVER_URL\}/g, ctx.serverURL)
+        .replace(/\{SERVER_B_URL\}/g, ctx.serverBURL),
+    );
+    // await ctx.page.evaluate(ctx.code);
+    await waitForRAF(ctx.page);
+    await ctx.page.waitForTimeout(500); // FIXME!!
+    ctx.events = [];
+    await ctx.page.exposeFunction('emit', (e: eventWithTime) => {
+      if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) {
+        return;
+      }
+      ctx.events.push(e);
+    });
+
+    ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
+    if (
+      options?.assetCaptureConfig?.captureOrigins &&
+      Array.isArray(options.assetCaptureConfig.captureOrigins)
+    ) {
+      options.assetCaptureConfig.captureOrigins =
+        options.assetCaptureConfig.captureOrigins.map((origin) =>
+          origin.replace(/\{SERVER_URL\}/g, ctx.serverURL),
+        );
+    }
+    await injectRecordScript(ctx.page.mainFrame(), options);
+  });
+
+  afterEach(async () => {
+    await ctx.page.close();
+  });
+
+  afterAll(async () => {
+    await ctx.browser.close();
+    ctx.server.close();
+    ctx.serverB.close();
+  });
+
+  return ctx;
+};
+
+describe('asset caching', function (this: ISuite) {
+  jest.setTimeout(100_000);
+
+  describe('captureObjectURLs: true with incremental snapshots', function (this: ISuite) {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body></body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureObjectURLs: true,
+          captureOrigins: false,
+        },
+      },
+    );
+
+    it('will emit asset when included as img attribute mutation', async () => {
+      const url = (await ctx.page.evaluate(() => {
+        return new Promise((resolve) => {
+          // create a blob of an image, then create an object URL for the blob
+          // and append it to the DOM as `src` attribute of an existing image
+          const img = document.createElement('img');
+          document.body.appendChild(img);
+
+          const canvas = document.createElement('canvas');
+          canvas.width = 100;
+          canvas.height = 100;
+          const context = canvas.getContext('2d')!;
+          context.fillStyle = 'red';
+          context.fillRect(0, 0, 100, 100);
+
+          canvas.toBlob((blob) => {
+            if (!blob) return;
+
+            const url = URL.createObjectURL(blob);
+            img.src = url;
+            resolve(url);
+          });
+        });
+      })) as string;
+      await waitForRAF(ctx.page);
+      // await ctx.page.waitForTimeout(40_000);
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+      const expected: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url,
+          payload: {
+            rr_type: 'Blob',
+            data: [
+              {
+                rr_type: 'ArrayBuffer',
+                base64: BASE64_PNG_RECTANGLE, // base64
+              },
+            ],
+          },
+        },
+      };
+      console.log(events);
+      expect(events[events.length - 1]).toMatchObject(expected);
+    });
+
+    it('will emit asset when included with new img', async () => {
+      const url = (await ctx.page.evaluate(() => {
+        return new Promise((resolve) => {
+          // create a blob of an image, then create an object URL for the blob and append it to the DOM as image `src` attribute
+          const canvas = document.createElement('canvas');
+          canvas.width = 100;
+          canvas.height = 100;
+          const context = canvas.getContext('2d')!;
+          context.fillStyle = 'red';
+          context.fillRect(0, 0, 100, 100);
+
+          canvas.toBlob((blob) => {
+            if (!blob) return;
+
+            const url = URL.createObjectURL(blob);
+            const img = document.createElement('img');
+            img.src = url;
+            document.body.appendChild(img);
+            resolve(url);
+          });
+        });
+      })) as string;
+      await waitForRAF(ctx.page);
+      // await ctx.page.waitForTimeout(40_000);
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+      const expected: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url,
+          payload: {
+            rr_type: 'Blob',
+            data: [
+              {
+                rr_type: 'ArrayBuffer',
+                base64: BASE64_PNG_RECTANGLE, // base64
+              },
+            ],
+          },
+        },
+      };
+      console.log(events);
+      expect(events[events.length - 1]).toMatchObject(expected);
+    });
+  });
+
+  describe('captureObjectURLs: true with fullSnapshot', function (this: ISuite) {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+          <script>
+            const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
+              const byteCharacters = atob(b64Data);
+              const byteArrays = [];
+
+              for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
+                const slice = byteCharacters.slice(offset, offset + sliceSize);
+
+                const byteNumbers = new Array(slice.length);
+                for (let i = 0; i < slice.length; i++) {
+                  byteNumbers[i] = slice.charCodeAt(i);
+                }
+
+                const byteArray = new Uint8Array(byteNumbers);
+                byteArrays.push(byteArray);
+              }
+
+              const blob = new Blob(byteArrays, {type: contentType});
+              return blob;
+            }
+
+            const base64 = "${BASE64_PNG_RECTANGLE}";
+            const contentType = 'image/png';
+            const blob = b64toBlob(base64, contentType);
+            const url = URL.createObjectURL(blob);
+            const img = document.createElement('img');
+            img.src = url;
+            document.body.appendChild(img);
+          </script>
+          </body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureObjectURLs: true,
+          captureOrigins: false,
+        },
+      },
+    );
+
+    it('will emit asset when included with existing img', async () => {
+      await waitForRAF(ctx.page);
+      const url = (await ctx.page.evaluate(() => {
+        return document.querySelector('img')?.src;
+      })) as string;
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+      const expected: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url,
+          payload: {
+            rr_type: 'Blob',
+            data: [
+              {
+                rr_type: 'ArrayBuffer',
+                base64: BASE64_PNG_RECTANGLE, // base64
+              },
+            ],
+          },
+        },
+      };
+      expect(events[events.length - 1]).toMatchObject(expected);
+    });
+  });
+  describe('captureObjectURLs: false', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body></body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureObjectURLs: false,
+          captureOrigins: false,
+        },
+      },
+    );
+    it("shouldn't capture ObjectURLs when its turned off in config", async () => {
+      const url = (await ctx.page.evaluate(() => {
+        return new Promise((resolve) => {
+          // create a blob of an image, then create an object URL for the blob and append it to the DOM as image `src` attribute
+          const canvas = document.createElement('canvas');
+          canvas.width = 100;
+          canvas.height = 100;
+          const context = canvas.getContext('2d')!;
+          context.fillStyle = 'red';
+          context.fillRect(0, 0, 100, 100);
+
+          canvas.toBlob((blob) => {
+            if (!blob) return;
+
+            const url = URL.createObjectURL(blob);
+            const img = document.createElement('img');
+            img.src = url;
+            document.body.appendChild(img);
+            resolve(url);
+          });
+        });
+      })) as string;
+      await waitForRAF(ctx.page);
+      // await ctx.page.waitForTimeout(40_000);
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+        }),
+      );
+    });
+  });
+  describe('data urls', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+          <img src="data:image/png;${BASE64_PNG_RECTANGLE}" />
+          </body>
+        </html>
+      `,
+    );
+
+    it("shouldn't re-capture data:urls", async () => {
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect no event to be emitted with `event.type` === EventType.Asset
+      console.log(events);
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+        }),
+      );
+    });
+  });
+  describe('captureOrigins: false', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+            <img src="{SERVER_URL}/html/assets/robot.png" />
+          </body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureOrigins: false,
+          captureObjectURLs: false,
+        },
+      },
+    );
+
+    it("shouldn't capture any urls", async () => {
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect no event to be emitted with `event.type` === EventType.Asset
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+        }),
+      );
+    });
+  });
+  describe('captureOrigins: []', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+            <img src="{SERVER_URL}/html/assets/robot.png" />
+          </body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureOrigins: [],
+          captureObjectURLs: false,
+        },
+      },
+    );
+
+    it("shouldn't capture any urls", async () => {
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect no event to be emitted with `event.type` === EventType.Asset
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+        }),
+      );
+    });
+  });
+  describe('captureOrigins: true', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+            <img src="{SERVER_URL}/html/assets/robot.png" />
+          </body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureOrigins: true,
+          captureObjectURLs: false,
+        },
+      },
+    );
+
+    it('capture all urls', async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+        }),
+      );
+    });
+  });
+  describe('captureOrigins: ["http://localhost:xxxxx/"]', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+            <img src="{SERVER_URL}/html/assets/robot.png" />
+            <img src="{SERVER_B_URL}/html/assets/robot.png" />
+          </body>
+        </html>
+      `,
+      {
+        assetCaptureConfig: {
+          captureOrigins: ['{SERVER_URL}'],
+          captureObjectURLs: false,
+        },
+      },
+    );
+
+    it('should capture assets with origin defined in config', async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+          data: {
+            url: `${ctx.serverURL}/html/assets/robot.png`,
+            payload: expect.any(Object),
+          },
+        }),
+      );
+    });
+    it("shouldn't capture assets with origin not defined in config", async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+          data: {
+            url: `${ctx.serverBURL}/html/assets/robot.png`,
+            payload: expect.any(Object),
+          },
+        }),
+      );
+    });
+  });
+});
diff --git a/packages/rrweb/test/record/cross-origin-iframes.test.ts b/packages/rrweb/test/record/cross-origin-iframes.test.ts
index 1b0d00f2d0..2d243fa95f 100644
--- a/packages/rrweb/test/record/cross-origin-iframes.test.ts
+++ b/packages/rrweb/test/record/cross-origin-iframes.test.ts
@@ -25,6 +25,8 @@ interface ISuite {
   events: eventWithTime[];
   server: http.Server;
   serverURL: string;
+  serverB: http.Server;
+  serverBURL: string;
 }
 
 interface IWindow extends Window {
@@ -77,10 +79,7 @@ const setup = function (
   content: string,
   options?: ExtraOptions,
 ): ISuite {
-  const ctx = {} as ISuite & {
-    serverB: http.Server;
-    serverBURL: string;
-  };
+  const ctx = {} as ISuite;
 
   beforeAll(async () => {
     ctx.browser = await launchPuppeteer();
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 0476ce2d68..43f9be4d1f 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -13,6 +13,7 @@ export enum EventType {
   Meta,
   Custom,
   Plugin,
+  Asset,
 }
 
 export type domContentLoadedEvent = {
@@ -66,6 +67,14 @@ export type pluginEvent<T = unknown> = {
   };
 };
 
+export type assetEvent = {
+  type: EventType.Asset;
+  data: {
+    url: string;
+    payload: SerializedCanvasArg;
+  };
+};
+
 export enum IncrementalSource {
   Mutation,
   MouseMove,
@@ -170,7 +179,8 @@ export type eventWithoutTime =
   | incrementalSnapshotEvent
   | metaEvent
   | customEvent
-  | pluginEvent;
+  | pluginEvent
+  | assetEvent;
 
 /**
  * @deprecated intended for internal use
@@ -615,6 +625,13 @@ export type customElementParam = {
 
 export type customElementCallback = (c: customElementParam) => void;
 
+export type assetParam = {
+  url: string;
+  payload: SerializedCanvasArg;
+};
+
+export type assetCallback = (d: assetParam) => void;
+
 export type DeprecatedMirror = {
   map: {
     [key: number]: INode;
diff --git a/yarn.lock b/yarn.lock
index 40f8fd25f3..e35b5dfdce 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14379,7 +14379,7 @@ tslib@2.4.0, tslib@^2.0.3, tslib@^2.1.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
   integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
 
-tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.8.1, tslib@^1.9.0:
   version "1.14.1"
   resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -14399,6 +14399,11 @@ tslib@^2.3.1:
   resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz"
   integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
 
+tslib@^2.5.3:
+  version "2.5.3"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
+  integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
+
 tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"

From ddab1da59baab718ade5baf9ace2e94e11955c86 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 8 Jun 2023 16:57:50 +0200
Subject: [PATCH 002/102] Add test to prove player works

---
 packages/rrweb/test/events/assets.ts          | 124 ++++++++++++++++++
 ...ncorporate-assets-emitted-later-1-snap.png | Bin 0 -> 10796 bytes
 packages/rrweb/test/replay/asset.test.ts      |  65 +++++++++
 3 files changed, 189 insertions(+)
 create mode 100644 packages/rrweb/test/events/assets.ts
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png
 create mode 100644 packages/rrweb/test/replay/asset.test.ts

diff --git a/packages/rrweb/test/events/assets.ts b/packages/rrweb/test/events/assets.ts
new file mode 100644
index 0000000000..3c7b36b3de
--- /dev/null
+++ b/packages/rrweb/test/events/assets.ts
@@ -0,0 +1,124 @@
+import { EventType, type eventWithTime } from '@rrweb/types';
+
+const events: eventWithTime[] = [
+  {
+    type: 4,
+    data: {
+      href: '',
+      width: 1600,
+      height: 900,
+    },
+    timestamp: 1636379531385,
+  },
+  {
+    type: 2,
+    data: {
+      node: {
+        type: 0,
+        childNodes: [
+          { type: 1, name: 'html', publicId: '', systemId: '', id: 2 },
+          {
+            type: 2,
+            tagName: 'html',
+            attributes: { lang: 'en' },
+            childNodes: [
+              {
+                type: 2,
+                tagName: 'head',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 5 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: { charset: 'UTF-8' },
+                    childNodes: [],
+                    id: 6,
+                  },
+                  { type: 3, textContent: '\n    ', id: 7 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: {
+                      name: 'viewport',
+                      content: 'width=device-width, initial-scale=1.0',
+                    },
+                    childNodes: [],
+                    id: 8,
+                  },
+                  { type: 3, textContent: '\n    ', id: 9 },
+                  {
+                    type: 2,
+                    tagName: 'title',
+                    attributes: {},
+                    childNodes: [{ type: 3, textContent: 'assets', id: 11 }],
+                    id: 10,
+                  },
+                  { type: 3, textContent: '\n  ', id: 12 },
+                ],
+                id: 4,
+              },
+              { type: 3, textContent: '\n  ', id: 13 },
+              {
+                type: 2,
+                tagName: 'body',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 15 },
+                  {
+                    type: 2,
+                    tagName: 'img',
+                    attributes: {
+                      width: '100',
+                      height: '100',
+                      style: 'border: 1px solid #000000',
+                      src: 'httpx://example.com/image.png',
+                    },
+                    childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
+                    id: 16,
+                  },
+                  { type: 3, textContent: '\n    ', id: 18 },
+                  {
+                    type: 2,
+                    tagName: 'script',
+                    attributes: {},
+                    childNodes: [
+                      { type: 3, textContent: 'SCRIPT_PLACEHOLDER', id: 20 },
+                    ],
+                    id: 19,
+                  },
+                  { type: 3, textContent: '\n  \n\n', id: 21 },
+                ],
+                id: 14,
+              },
+            ],
+            id: 3,
+          },
+        ],
+        id: 1,
+      },
+      initialOffset: { left: 0, top: 0 },
+    },
+    timestamp: 1636379531389,
+  },
+  {
+    type: EventType.Asset,
+    data: {
+      url: 'httpx://example.com/image.png',
+      payload: {
+        rr_type: 'Blob',
+        type: 'image/png',
+        data: [
+          {
+            rr_type: 'ArrayBuffer',
+            base64:
+              'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAWtJREFUeF7t1cEJAEAIxEDtv2gProo8xgpCwuLezI3LGFhBMi0+iCCtHoLEeggiSM1AjMcPESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4TwVjsedWCiXGAAAAABJRU5ErkJggg==', // base64
+          },
+        ],
+      },
+    },
+    timestamp: 1636379532355,
+  },
+];
+
+export default events;
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset.test.ts b/packages/rrweb/test/replay/asset.test.ts
new file mode 100644
index 0000000000..ba44cdda20
--- /dev/null
+++ b/packages/rrweb/test/replay/asset.test.ts
@@ -0,0 +1,65 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import { launchPuppeteer } from '../utils';
+import { toMatchImageSnapshot } from 'jest-image-snapshot';
+import type * as puppeteer from 'puppeteer';
+import events from '../events/assets';
+
+interface ISuite {
+  code: string;
+  browser: puppeteer.Browser;
+  page: puppeteer.Page;
+}
+
+expect.extend({ toMatchImageSnapshot });
+
+describe('replayer', function () {
+  jest.setTimeout(10_000);
+
+  let code: ISuite['code'];
+  let browser: ISuite['browser'];
+  let page: ISuite['page'];
+
+  beforeAll(async () => {
+    browser = await launchPuppeteer();
+
+    const bundlePath = path.resolve(__dirname, '../../dist/rrweb.js');
+    code = fs.readFileSync(bundlePath, 'utf8');
+  });
+
+  beforeEach(async () => {
+    page = await browser.newPage();
+    await page.goto('about:blank');
+    // mouse cursor canvas is large and pushes the replayer below the fold
+    // lets hide it...
+    await page.addStyleTag({
+      content: '.replayer-mouse-tail{display: none !important;}',
+    });
+    await page.evaluate(code);
+    await page.evaluate(`let events = ${JSON.stringify(events)}`);
+
+    page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
+  });
+
+  afterEach(async () => {
+    await page.close();
+  });
+
+  afterAll(async () => {
+    await browser.close();
+  });
+
+  describe('asset', () => {
+    it('should incorporate assets emitted later', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      const replayer = new Replayer(events, {
+      });
+      replayer.pause(0);
+    `);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+  });
+});

From 16d89dba2dd39130ed68463aebf6b84135952a95 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 9 Jun 2023 15:27:52 +0200
Subject: [PATCH 003/102] Rename `assetCaptureConfig` to `assetCapture`

---
 packages/rrweb/src/record/index.ts            |  4 +--
 .../src/record/observers/asset-manager.ts     |  8 +++---
 packages/rrweb/src/types.ts                   |  2 +-
 packages/rrweb/test/record/asset.test.ts      | 26 +++++++++----------
 4 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index e4c34f2ba6..5b6d6959de 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -83,7 +83,7 @@ function record<T = eventWithTime>(
     userTriggeredOnInput = false,
     collectFonts = false,
     inlineImages = false,
-    assetCaptureConfig = {
+    assetCapture = {
       captureObjectURLs: true,
       captureOrigins: false,
     },
@@ -322,7 +322,7 @@ function record<T = eventWithTime>(
   assetManager = new AssetManager({
     mutationCb: wrappedAssetEmit,
     win: window,
-    assetCaptureConfig,
+    assetCapture,
   });
 
   const shadowDomManager = new ShadowDomManager({
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 0f5b1486aa..595aa3665f 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -18,7 +18,7 @@ export default class AssetManager {
   private resetHandlers: listenerHandler[] = [];
   private mutationCb: assetCallback;
   public readonly config: Exclude<
-    recordOptions<eventWithTime>['assetCaptureConfig'],
+    recordOptions<eventWithTime>['assetCapture'],
     undefined
   >;
 
@@ -33,15 +33,15 @@ export default class AssetManager {
   constructor(options: {
     mutationCb: assetCallback;
     win: IWindow;
-    assetCaptureConfig: Exclude<
-      recordOptions<eventWithTime>['assetCaptureConfig'],
+    assetCapture: Exclude<
+      recordOptions<eventWithTime>['assetCapture'],
       undefined
     >;
   }) {
     const { win } = options;
 
     this.mutationCb = options.mutationCb;
-    this.config = options.assetCaptureConfig;
+    this.config = options.assetCapture;
 
     const urlObjectMap = this.urlObjectMap;
 
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index d53ab90e15..cf86304b7e 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -69,7 +69,7 @@ export type recordOptions<T> = {
   userTriggeredOnInput?: boolean;
   collectFonts?: boolean;
   inlineImages?: boolean;
-  assetCaptureConfig?: {
+  assetCapture?: {
     /**
      * Captures object URLs (blobs, files, media sources).
      * More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index dfe486b406..a34db4e86f 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -35,7 +35,7 @@ interface IWindow extends Window {
   snapshots: eventWithTime[];
 }
 type ExtraOptions = {
-  assetCaptureConfig?: recordOptions<eventWithTime>['assetCaptureConfig'];
+  assetCapture?: recordOptions<eventWithTime>['assetCapture'];
 };
 
 const BASE64_PNG_RECTANGLE =
@@ -53,7 +53,7 @@ async function injectRecordScript(
     (window as unknown as IWindow).snapshots = [];
     const { record, pack } = (window as unknown as IWindow).rrweb;
     const config: recordOptions<eventWithTime> = {
-      assetCaptureConfig: options.assetCaptureConfig,
+      assetCapture: options.assetCapture,
       emit(event) {
         (window as unknown as IWindow).snapshots.push(event);
         (window as unknown as IWindow).emit(event);
@@ -105,11 +105,11 @@ const setup = function (
 
     ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
     if (
-      options?.assetCaptureConfig?.captureOrigins &&
-      Array.isArray(options.assetCaptureConfig.captureOrigins)
+      options?.assetCapture?.captureOrigins &&
+      Array.isArray(options.assetCapture.captureOrigins)
     ) {
-      options.assetCaptureConfig.captureOrigins =
-        options.assetCaptureConfig.captureOrigins.map((origin) =>
+      options.assetCapture.captureOrigins =
+        options.assetCapture.captureOrigins.map((origin) =>
           origin.replace(/\{SERVER_URL\}/g, ctx.serverURL),
         );
     }
@@ -142,7 +142,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureObjectURLs: true,
           captureOrigins: false,
         },
@@ -284,7 +284,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureObjectURLs: true,
           captureOrigins: false,
         },
@@ -329,7 +329,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureObjectURLs: false,
           captureOrigins: false,
         },
@@ -409,7 +409,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureOrigins: false,
           captureObjectURLs: false,
         },
@@ -441,7 +441,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureOrigins: [],
           captureObjectURLs: false,
         },
@@ -473,7 +473,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureOrigins: true,
           captureObjectURLs: false,
         },
@@ -509,7 +509,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCaptureConfig: {
+        assetCapture: {
           captureOrigins: ['{SERVER_URL}'],
           captureObjectURLs: false,
         },

From 2986546b16b97022bb8948d385ee179996900b35 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 9 Jun 2023 15:28:09 +0200
Subject: [PATCH 004/102] Document `assetCapture` config

---
 docs/recipes/assets.md | 40 ++++++++++++++++++++++++++++
 guide.md               | 59 +++++++++++++++++++++---------------------
 2 files changed, 70 insertions(+), 29 deletions(-)
 create mode 100644 docs/recipes/assets.md

diff --git a/docs/recipes/assets.md b/docs/recipes/assets.md
new file mode 100644
index 0000000000..c6b08a2b01
--- /dev/null
+++ b/docs/recipes/assets.md
@@ -0,0 +1,40 @@
+# Asset Capture Methods & Configuration in rrweb
+
+[rrweb](https://rrweb.io/) is a JavaScript library that allows you to record and replay user interactions on your website. It provides various configuration options for capturing assets (such as images) during the recording process. In this document, we will explore the different asset capture methods and their configuration options in rrweb.
+
+## Inline Images (Deprecated)
+
+The `inlineImages` configuration option is deprecated and should not be used anymore. It has some issues, namely rewriting events that are already emitted which might make you miss the inlined image if the event has already been sent to the server. Instead, use the `assetCapture` option to configure asset capture.
+
+## Asset Capture Configuration
+
+The `assetCapture` configuration option allows you to customize the asset capture process. It is an object with the following properties:
+
+- `captureObjectURLs` (default: `true`): This property specifies whether to capture same-origin `blob:` assets using object URLs. Object URLs are created using the `URL.createObjectURL()` method. Setting `captureObjectURLs` to `true` enables the capture of object URLs.
+
+- `captureOrigins` (default: `false`): This property determines which origins to capture assets from. It can have the following values:
+  - `false` or `[]`: Disables capturing any assets apart from object URLs.
+  - `true`: Captures assets from all origins.
+  - `[origin1, origin2, ...]`: Captures assets only from the specified origins. For example, `captureOrigins: ['https://s3.example.com/']` captures all assets from the origin `https://s3.example.com/`.
+
+## TypeScript Type Definition
+
+Here is the TypeScript type definition for the `recordOptions` object, which includes the asset capture configuration options:
+
+```typescript
+export type recordOptions<T> = {
+  // Other configuration options...
+  inlineImages?: boolean;
+  assetCapture?: {
+    captureObjectURLs: boolean;
+    captureOrigins: string[] | true | false;
+  };
+  // Other configuration options...
+};
+```
+
+This type definition shows that `assetCapture` is an optional property of the `recordOptions` object. It contains the `captureObjectURLs` and `captureOrigins` properties, which have the same meanings as described above.
+
+## Conclusion
+
+By configuring the `assetCapture` option in rrweb, you can control how assets like images are captured during the recording process. This allows you to customize which assets are included in the recorded interactions on your website.
diff --git a/guide.md b/guide.md
index 5693b26d29..93636881ff 100644
--- a/guide.md
+++ b/guide.md
@@ -135,36 +135,37 @@ setInterval(save, 10 * 1000);
 
 The parameter of `rrweb.record` accepts the following options.
 
-| key                      | default            | description                                                                                                                                                                                   |
-| ------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| emit                     | required           | the callback function to get emitted events                                                                                                                                                   |
-| checkoutEveryNth         | -                  | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter                                                                                                     |
-| checkoutEveryNms         | -                  | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter                                                                                                         |
-| blockClass               | 'rr-block'         | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter                                                                                |
-| blockSelector            | null               | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter                                                                                          |
-| ignoreClass              | 'rr-ignore'        | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter                                                                                |
+| key                      | default                                            | description                                                                                                                                                                                   |
+| ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| emit                     | required                                           | the callback function to get emitted events                                                                                                                                                   |
+| checkoutEveryNth         | -                                                  | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter                                                                                                     |
+| checkoutEveryNms         | -                                                  | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter                                                                                                         |
+| blockClass               | 'rr-block'                                         | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter                                                                                |
+| blockSelector            | null                                               | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter                                                                                          |
+| ignoreClass              | 'rr-ignore'                                        | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter                                                                                |
 | ignoreSelector           | null               | Use a string to configure which selector should be ignored, refer to the [privacy](#privacy) chapter                                                                                          |
-| ignoreCSSAttributes      | null               | array of CSS attributes that should be ignored                                                                                                                                                |
-| maskTextClass            | 'rr-mask'          | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter                                                                                 |
-| maskTextSelector         | null               | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter                                                                                           |
-| maskAllInputs            | false              | mask all input content as \*                                                                                                                                                                  |
-| maskInputOptions         | { password: true } | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95)            |
-| maskInputFn              | -                  | customize mask input content recording logic                                                                                                                                                  |
-| maskTextFn               | -                  | customize mask text content recording logic                                                                                                                                                   |
-| slimDOMOptions           | {}                 | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)  |
-| dataURLOptions           | {}                 | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data                  |
-| inlineStylesheet         | true               | whether to inline the stylesheet in the events                                                                                                                                                |
-| hooks                    | {}                 | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                  |
-| packFn                   | -                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
-| sampling                 | -                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
-| recordCanvas             | false              | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
-| recordCrossOriginIframes | false              | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
-| recordAfter              | 'load'             | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
-| inlineImages             | false              | whether to record the image content                                                                                                                                                           |
-| collectFonts             | false              | whether to collect fonts in the website                                                                                                                                                       |
-| userTriggeredOnInput     | false              | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
-| plugins                  | []                 | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md)                                                                                               |
-| errorHandler             | -                  | A callback that is called if something inside of rrweb throws an error. The callback receives the error as argument.                                                                          |
+| ignoreCSSAttributes      | null                                               | array of CSS attributes that should be ignored                                                                                                                                                |
+| maskTextClass            | 'rr-mask'                                          | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter                                                                                 |
+| maskTextSelector         | null                                               | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter                                                                                           |
+| maskAllInputs            | false                                              | mask all input content as \*                                                                                                                                                                  |
+| maskInputOptions         | { password: true }                                 | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95)            |
+| maskInputFn              | -                                                  | customize mask input content recording logic                                                                                                                                                  |
+| maskTextFn               | -                                                  | customize mask text content recording logic                                                                                                                                                   |
+| slimDOMOptions           | {}                                                 | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)  |
+| dataURLOptions           | {}                                                 | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data                  |
+| inlineStylesheet         | true                                               | whether to inline the stylesheet in the events                                                                                                                                                |
+| hooks                    | {}                                                 | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                  |
+| packFn                   | -                                                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
+| sampling                 | -                                                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
+| recordCanvas             | false                                              | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
+| recordCrossOriginIframes | false                                              | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
+| recordAfter              | 'load'                                             | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
+| inlineImages             | false                                              | whether to record the image content (deprecated, use `assetCapture` instead)                                                                                                                  |
+| assetCapture             | { captureObjectURLs: true, captureOrigins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
+| collectFonts             | false                                              | whether to collect fonts in the website                                                                                                                                                       |
+| userTriggeredOnInput     | false                                              | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
+| plugins                  | []                                                 | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md)                                                                                               |
+| errorHandler             | -                                                  | A callback that is called if something inside of rrweb throws an error. The callback receives the error as argument.                                                                          |
 
 #### Privacy
 

From 401b2fdaa9380d91657203de7b9b9ad9d3519ad9 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 13 Jun 2023 15:39:26 +0200
Subject: [PATCH 005/102] Create asset event for assets that failed to load

---
 .../__snapshots__/integration.test.ts.snap    |  6 ++
 .../src/record/observers/asset-manager.ts     | 18 ++++-
 packages/rrweb/test/integration.test.ts       | 17 +++--
 packages/rrweb/test/record/asset.test.ts      | 72 ++++++++++++++++++-
 packages/rrweb/test/utils.ts                  |  3 +-
 packages/types/src/index.ts                   | 21 +++---
 6 files changed, 121 insertions(+), 16 deletions(-)

diff --git a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
index f537fc7b29..39c8c49ee1 100644
--- a/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb-snapshot/test/__snapshots__/integration.test.ts.snap
@@ -364,6 +364,12 @@ exports[`integration tests [html file]: picture-in-frame.html 1`] = `
   </body></html>"
 `;
 
+exports[`integration tests [html file]: picture-with-inline-onload.html 1`] = `
+"<html xmlns=\\"http://www.w3.org/1999/xhtml\\"><head></head><body>
+    <img src=\\"http://localhost:3030/images/robot.png\\" alt=\\"This is a robot\\" style=\\"opacity: 1;\\" _onload=\\"this.style.opacity=1\\" />
+  </body></html>"
+`;
+
 exports[`integration tests [html file]: preload.html 1`] = `
 "<!DOCTYPE html><html lang=\\"en\\"><head>
     <meta charset=\\"UTF-8\\" />
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 595aa3665f..4249ef014d 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -169,8 +169,22 @@ export default class AssetManager {
           }
         }
       })
-      .catch(() => {
-        // TODO: add mutationCb for failed urls
+      .catch((e: unknown) => {
+        let message = '';
+        if (e instanceof Error) {
+          message = e.message;
+        } else if (typeof e === 'string') {
+          message = e;
+        } else if (e && typeof e === 'object' && 'toString' in e) {
+          message = (e as { toString(): string }).toString();
+        }
+        this.mutationCb({
+          url,
+          failed: {
+            message,
+          },
+        });
+
         this.failedURLs.add(url);
         this.capturingURLs.delete(url);
       });
diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index 616b6e91cb..0525bd04e8 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -77,7 +77,7 @@ describe('record integration tests', function (this: ISuite) {
         x: Math.round(x + width / 2),
         y: Math.round(y + height / 2),
       };
-    }, span);
+    }, span!);
     await page.touchscreen.tap(center.x, center.y);
 
     await page.click('a');
@@ -836,7 +836,10 @@ describe('record integration tests', function (this: ISuite) {
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     page.setContent(
-      getHtml.call(this, 'image-blob-url.html', { inlineImages: true }),
+      getHtml.call(this, 'image-blob-url.html', {
+        inlineImages: true,
+        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+      }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
     await page.waitForSelector('img'); // wait for image to get added
@@ -853,7 +856,10 @@ describe('record integration tests', function (this: ISuite) {
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     await page.setContent(
-      getHtml.call(this, 'frame-image-blob-url.html', { inlineImages: true }),
+      getHtml.call(this, 'frame-image-blob-url.html', {
+        inlineImages: true,
+        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+      }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
     await page.waitForTimeout(50); // wait for image to get added
@@ -870,7 +876,10 @@ describe('record integration tests', function (this: ISuite) {
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     await page.setContent(
-      getHtml.call(this, 'frame2.html', { inlineImages: true }),
+      getHtml.call(this, 'frame2.html', {
+        inlineImages: true,
+        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+      }),
     );
     await page.waitForSelector('iframe'); // wait for iframe to get added
     await waitForRAF(page); // wait for iframe to load
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index a34db4e86f..dafd7648d0 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -94,7 +94,6 @@ const setup = function (
     );
     // await ctx.page.evaluate(ctx.code);
     await waitForRAF(ctx.page);
-    await ctx.page.waitForTimeout(500); // FIXME!!
     ctx.events = [];
     await ctx.page.exposeFunction('emit', (e: eventWithTime) => {
       if (e.type === EventType.DomContentLoaded || e.type === EventType.Load) {
@@ -496,6 +495,77 @@ describe('asset caching', function (this: ISuite) {
       );
     });
   });
+
+  describe('captureOrigins: true with invalid urls', () => {
+    const ctx: ISuite = setup.call(
+      this,
+      `
+        <!DOCTYPE html>
+        <html>
+          <body>
+            <img src="failprotocol://example.com/image.png" />
+            <img src="https://example.com/image.png" />
+          </body>
+        </html>
+      `,
+      {
+        assetCapture: {
+          captureOrigins: true,
+          captureObjectURLs: false,
+        },
+      },
+    );
+
+    it('capture invalid url', async () => {
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+          data: {
+            url: `failprotocol://example.com/image.png`,
+            failed: {
+              message: 'Failed to fetch',
+            },
+          },
+        }),
+      );
+    });
+
+    it('capture url failed due to CORS', async () => {
+      // Puppeteer has issues with failed requests below 19.8.0 (more info: https://github.com/puppeteer/puppeteer/pull/9883)
+      // TODO: re-enable next line after upgrading to puppeteer 19.8.0
+      // await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+
+      // TODO: remove next line after upgrading to puppeteer 19.8.0
+      await ctx.page.waitForTimeout(500);
+
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+          data: {
+            url: `https://example.com/image.png`,
+            failed: {
+              message: 'Failed to fetch',
+            },
+          },
+        }),
+      );
+    });
+  });
+
   describe('captureOrigins: ["http://localhost:xxxxx/"]', () => {
     const ctx: ISuite = setup.call(
       this,
diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index 30a36abfcb..2be7531830 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -701,7 +701,8 @@ export function generateRecordSnippet(options: recordOptions<eventWithTime>) {
     recordCanvas: ${options.recordCanvas},
     recordAfter: '${options.recordAfter || 'load'}',
     inlineImages: ${options.inlineImages},
-    plugins: ${options.plugins}
+    plugins: ${options.plugins},
+    assetCapture: ${JSON.stringify(options.assetCapture)},
   });
   `;
 }
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 43f9be4d1f..186fe084bb 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -69,10 +69,7 @@ export type pluginEvent<T = unknown> = {
 
 export type assetEvent = {
   type: EventType.Asset;
-  data: {
-    url: string;
-    payload: SerializedCanvasArg;
-  };
+  data: assetParam;
 };
 
 export enum IncrementalSource {
@@ -625,10 +622,18 @@ export type customElementParam = {
 
 export type customElementCallback = (c: customElementParam) => void;
 
-export type assetParam = {
-  url: string;
-  payload: SerializedCanvasArg;
-};
+export type assetParam =
+  | {
+      url: string;
+      payload: SerializedCanvasArg;
+    }
+  | {
+      url: string;
+      failed: {
+        status?: number;
+        message: string;
+      };
+    };
 
 export type assetCallback = (d: assetParam) => void;
 

From 0ffb6199bd7c3728e2186928784d6635632e4a88 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 13 Jun 2023 15:40:27 +0200
Subject: [PATCH 006/102] Allow other `yarn dev` commands to depend on
 @rrweb/types

---
 packages/types/package.json   | 2 +-
 packages/types/vite.config.js | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/types/package.json b/packages/types/package.json
index 2d61372168..4fa83988f5 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -9,7 +9,7 @@
     "@rrweb/types"
   ],
   "scripts": {
-    "dev": "vite",
+    "dev": "vite build -w",
     "build": "tsc -noEmit && vite build",
     "check-types": "tsc -noEmit",
     "prepublish": "npm run build",
diff --git a/packages/types/vite.config.js b/packages/types/vite.config.js
index f84b8f9fde..c4cd2de445 100644
--- a/packages/types/vite.config.js
+++ b/packages/types/vite.config.js
@@ -16,6 +16,9 @@ export default {
     minify: false,
 
     sourcemap: true,
+    rollupOptions: {
+      input: path.resolve(__dirname, 'src/index.ts'),
+    },
   },
   plugins: [dts()],
 };

From 2fab181d5e521dc957253eb980e6018347d5a626 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Wed, 14 Jun 2023 00:05:52 +0200
Subject: [PATCH 007/102] Move types from rrweb-snapshot to @rrweb/types

 - Fix declaration emit with vite
   Workaround for https://github.com/qmhc/vite-plugin-dts/issues/193
---
 .changeset/yellow-vans-protect.md             |   7 +
 .vscode/rrweb-monorepo.code-workspace         |   3 +-
 packages/rrdom-nodejs/src/document-nodejs.ts  |   2 +-
 packages/rrdom-nodejs/tsconfig.json           |   2 +-
 packages/rrdom/src/diff.ts                    |   7 +-
 packages/rrdom/src/document.ts                |   2 +-
 packages/rrdom/src/index.ts                   |  12 +-
 packages/rrdom/test/diff.test.ts              |   9 +-
 packages/rrweb-snapshot/package.json          |  14 +-
 packages/rrweb-snapshot/rollup.config.js      |  11 +-
 packages/rrweb-snapshot/src/rebuild.ts        |   9 +-
 packages/rrweb-snapshot/src/snapshot.ts       |  12 +-
 packages/rrweb-snapshot/src/types.ts          | 106 +---------------
 packages/rrweb-snapshot/src/utils.ts          |   8 +-
 packages/rrweb-snapshot/tsconfig.json         |   6 +-
 .../src/plugins/canvas-webrtc/record/index.ts |   9 +-
 packages/rrweb/src/record/iframe-manager.ts   |  11 +-
 .../record/observers/canvas/canvas-manager.ts |   3 +-
 .../rrweb/src/record/stylesheet-manager.ts    |   3 +-
 .../workers/image-bitmap-data-url-worker.ts   |   2 +-
 packages/rrweb/src/replay/canvas/2d.ts        |   2 +-
 .../src/replay/canvas/deserialize-args.ts     |  29 ++++-
 packages/rrweb/src/replay/canvas/webgl.ts     |  12 +-
 packages/rrweb/src/replay/index.ts            |  26 ++--
 packages/rrweb/src/types.ts                   |   2 +-
 packages/rrweb/src/utils.ts                   |   3 +-
 packages/types/package.json                   |  12 +-
 packages/types/src/index.ts                   | 120 ++++++++++++++++--
 packages/types/tsconfig.json                  |   6 +-
 packages/types/vite.config.js                 |   7 +-
 yarn.lock                                     |  28 ++--
 31 files changed, 268 insertions(+), 217 deletions(-)
 create mode 100644 .changeset/yellow-vans-protect.md

diff --git a/.changeset/yellow-vans-protect.md b/.changeset/yellow-vans-protect.md
new file mode 100644
index 0000000000..8a1c706956
--- /dev/null
+++ b/.changeset/yellow-vans-protect.md
@@ -0,0 +1,7 @@
+---
+'rrweb-snapshot': major
+'@rrweb/types': patch
+---
+
+`NodeType` enum was moved from rrweb-snapshot to @rrweb/types
+The following types where moved from rrweb-snapshot to @rrweb/types: `documentNode`, `documentTypeNode`, `attributes`, `legacyAttributes`, `elementNode`, `textNode`, `cdataNode`, `commentNode`, `serializedNode`, `serializedNodeWithId` and `DataURLOptions`
diff --git a/.vscode/rrweb-monorepo.code-workspace b/.vscode/rrweb-monorepo.code-workspace
index ee31ea35ee..aa6a759381 100644
--- a/.vscode/rrweb-monorepo.code-workspace
+++ b/.vscode/rrweb-monorepo.code-workspace
@@ -36,6 +36,7 @@
       " rrweb monorepo",
       "rrweb-player (package)",
       "@rrweb/types"
-    ]
+    ],
+    "typescript.tsdk": " rrweb monorepo/node_modules/typescript/lib"
   }
 }
diff --git a/packages/rrdom-nodejs/src/document-nodejs.ts b/packages/rrdom-nodejs/src/document-nodejs.ts
index 1d13970d34..15a805d6fd 100644
--- a/packages/rrdom-nodejs/src/document-nodejs.ts
+++ b/packages/rrdom-nodejs/src/document-nodejs.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-unsafe-assignment */
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@rrweb/types';
 import type { NWSAPI } from 'nwsapi';
 import type { CSSStyleDeclaration as CSSStyleDeclarationType } from 'cssstyle';
 import {
diff --git a/packages/rrdom-nodejs/tsconfig.json b/packages/rrdom-nodejs/tsconfig.json
index 0c2f119853..e4e98a64bd 100644
--- a/packages/rrdom-nodejs/tsconfig.json
+++ b/packages/rrdom-nodejs/tsconfig.json
@@ -28,7 +28,7 @@
       "path": "../rrdom"
     },
     {
-      "path": "../rrweb-snapshot"
+      "path": "../types"
     }
   ]
 }
diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index 8ccf81f8c6..56f9414c62 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -1,9 +1,7 @@
+import type { Mirror as NodeMirror } from 'rrweb-snapshot';
 import {
   NodeType as RRNodeType,
-  Mirror as NodeMirror,
   elementNode,
-} from 'rrweb-snapshot';
-import type {
   canvasMutationData,
   canvasEventWithTime,
   inputData,
@@ -540,6 +538,9 @@ export function createOrGetNode(
     case RRNodeType.CDATA:
       node = document.createCDATASection((rrNode as IRRCDATASection).data);
       break;
+    default:
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      throw new Error(`Unknown node type ${rrNode.RRNodeType}`);
   }
 
   if (sn) domMirror.add(node, { ...sn });
diff --git a/packages/rrdom/src/document.ts b/packages/rrdom/src/document.ts
index befdadb86d..35487e5116 100644
--- a/packages/rrdom/src/document.ts
+++ b/packages/rrdom/src/document.ts
@@ -1,4 +1,4 @@
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@rrweb/types';
 import { parseCSSText, camelize, toCSSText } from './style';
 export interface IRRNode {
   parentElement: IRRNode | null;
diff --git a/packages/rrdom/src/index.ts b/packages/rrdom/src/index.ts
index eeddb03e1c..019a16e582 100644
--- a/packages/rrdom/src/index.ts
+++ b/packages/rrdom/src/index.ts
@@ -1,13 +1,9 @@
-import {
-  NodeType as RRNodeType,
-  createMirror as createNodeMirror,
-} from 'rrweb-snapshot';
+import { createMirror as createNodeMirror } from 'rrweb-snapshot';
+import type { Mirror as NodeMirror } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@rrweb/types';
 import type {
-  Mirror as NodeMirror,
   IMirror,
   serializedNodeWithId,
-} from 'rrweb-snapshot';
-import type {
   canvasMutationData,
   canvasEventWithTime,
   inputData,
@@ -451,6 +447,8 @@ export function getDefaultSN(node: IRRNode, id: number): serializedNodeWithId {
         type: node.RRNodeType,
         textContent: '',
       };
+    default:
+      throw new Error(`Unknown node type`);
   }
 }
 
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index fc76f48bd9..551a76fe0f 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -3,12 +3,7 @@
  */
 import * as path from 'path';
 import * as puppeteer from 'puppeteer';
-import {
-  NodeType as RRNodeType,
-  serializedNodeWithId,
-  createMirror,
-  Mirror as NodeMirror,
-} from 'rrweb-snapshot';
+import { createMirror, Mirror as NodeMirror } from 'rrweb-snapshot';
 import {
   buildFromDom,
   getDefaultSN,
@@ -27,6 +22,8 @@ import {
 import type { IRRElement, IRRNode } from '../src/document';
 import { Replayer } from 'rrweb';
 import type {
+  NodeType as RRNodeType,
+  serializedNodeWithId,
   eventWithTime,
   canvasMutationData,
   styleDeclarationData,
diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index e92c170a19..097264fd58 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -11,8 +11,8 @@
     "test:update": "jest --updateSnapshot",
     "bundle": "rollup --config",
     "bundle:es-only": "cross-env ES_ONLY=true rollup --config",
-    "dev": "yarn bundle:es-only --watch",
-    "typings": "tsc -d --declarationDir typings",
+    "dev": "yarn bundle:es-only --watch & yarn typings -w",
+    "typings": "tsc -d --declarationDir typings --emitDeclarationOnly",
     "prepublish": "yarn typings && yarn bundle",
     "lint": "yarn eslint src"
   },
@@ -42,6 +42,9 @@
     "url": "https://github.com/rrweb-io/rrweb/issues"
   },
   "homepage": "https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot#readme",
+  "dependencies": {
+    "@rrweb/types": "^2.0.0-alpha.8"
+  },
   "devDependencies": {
     "@types/chai": "^4.1.4",
     "@types/jest": "^27.0.2",
@@ -53,11 +56,12 @@
     "jest-snapshot": "^23.6.0",
     "jsdom": "^16.4.0",
     "puppeteer": "^17.1.3",
-    "rollup": "^2.45.2",
+    "rollup": "^2.46.0",
     "rollup-plugin-terser": "^7.0.2",
-    "rollup-plugin-typescript2": "^0.31.2",
+    "rollup-plugin-typescript2": "^0.34.1",
     "ts-jest": "^27.0.5",
     "ts-node": "^7.0.1",
-    "tslib": "^2.5.3"
+    "tslib": "^2.5.3",
+    "typescript": "^4.7.3"
   }
 }
diff --git a/packages/rrweb-snapshot/rollup.config.js b/packages/rrweb-snapshot/rollup.config.js
index b13f447864..1e07a43575 100644
--- a/packages/rrweb-snapshot/rollup.config.js
+++ b/packages/rrweb-snapshot/rollup.config.js
@@ -1,4 +1,5 @@
 import typescript from 'rollup-plugin-typescript2';
+import resolve from '@rollup/plugin-node-resolve';
 import { terser } from 'rollup-plugin-terser';
 import pkg from './package.json';
 
@@ -10,7 +11,7 @@ let configs = [
   // ES module - for building rrweb
   {
     input: './src/index.ts',
-    plugins: [typescript()],
+    plugins: [resolve(), typescript()],
     output: [
       {
         format: 'esm',
@@ -23,7 +24,7 @@ let extra_configs = [
   // browser
   {
     input: './src/index.ts',
-    plugins: [typescript()],
+    plugins: [resolve(), typescript()],
     output: [
       {
         name: 'rrwebSnapshot',
@@ -34,7 +35,7 @@ let extra_configs = [
   },
   {
     input: './src/index.ts',
-    plugins: [typescript(), terser()],
+    plugins: [resolve(), typescript(), terser()],
     output: [
       {
         name: 'rrwebSnapshot',
@@ -47,7 +48,7 @@ let extra_configs = [
   // CommonJS
   {
     input: './src/index.ts',
-    plugins: [typescript()],
+    plugins: [resolve(), typescript()],
     output: [
       {
         format: 'cjs',
@@ -58,7 +59,7 @@ let extra_configs = [
   // ES module (packed)
   {
     input: './src/index.ts',
-    plugins: [typescript(), terser()],
+    plugins: [resolve(), typescript(), terser()],
     output: [
       {
         format: 'esm',
diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index 7c6ed948e6..a52d8a9b4b 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -1,12 +1,11 @@
-import { Rule, Media, NodeWithRules, parse } from './css';
 import {
   serializedNodeWithId,
-  NodeType,
-  tagMap,
   elementNode,
-  BuildCache,
   legacyAttributes,
-} from './types';
+  NodeType,
+} from '@rrweb/types';
+import { Rule, Media, NodeWithRules, parse } from './css';
+import { tagMap, BuildCache } from './types';
 import { isElement, Mirror, isNodeMetaEqual } from './utils';
 
 const tagMap: tagMap = {
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index fe9e27edf4..5fe8e56f2b 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -1,11 +1,6 @@
 import {
-  serializedNode,
-  serializedNodeWithId,
-  NodeType,
-  attributes,
   MaskInputOptions,
   SlimDOMOptions,
-  DataURLOptions,
   MaskTextFn,
   MaskInputFn,
   KeepIframeSrcFn,
@@ -14,6 +9,13 @@ import {
   serializedElementNodeWithId,
   type mediaAttributes,
 } from './types';
+import {
+  serializedNode,
+  serializedNodeWithId,
+  NodeType,
+  attributes,
+  DataURLOptions,
+} from '@rrweb/types';
 import {
   Mirror,
   is2DCanvasBlank,
diff --git a/packages/rrweb-snapshot/src/types.ts b/packages/rrweb-snapshot/src/types.ts
index 1abfe4d6c0..8e4caea835 100644
--- a/packages/rrweb-snapshot/src/types.ts
+++ b/packages/rrweb-snapshot/src/types.ts
@@ -1,77 +1,4 @@
-export enum NodeType {
-  Document,
-  DocumentType,
-  Element,
-  Text,
-  CDATA,
-  Comment,
-}
-
-export type documentNode = {
-  type: NodeType.Document;
-  childNodes: serializedNodeWithId[];
-  compatMode?: string;
-};
-
-export type documentTypeNode = {
-  type: NodeType.DocumentType;
-  name: string;
-  publicId: string;
-  systemId: string;
-};
-
-export type attributes = {
-  [key: string]: string | number | true | null;
-};
-export type legacyAttributes = {
-  /**
-   * @deprecated old bug in rrweb was causing these to always be set
-   * @see https://github.com/rrweb-io/rrweb/pull/651
-   */
-  selected: false;
-};
-
-export type elementNode = {
-  type: NodeType.Element;
-  tagName: string;
-  attributes: attributes;
-  childNodes: serializedNodeWithId[];
-  isSVG?: true;
-  needBlock?: boolean;
-  // This is a custom element or not.
-  isCustom?: true;
-};
-
-export type textNode = {
-  type: NodeType.Text;
-  textContent: string;
-  isStyle?: true;
-};
-
-export type cdataNode = {
-  type: NodeType.CDATA;
-  textContent: '';
-};
-
-export type commentNode = {
-  type: NodeType.Comment;
-  textContent: string;
-};
-
-export type serializedNode = (
-  | documentNode
-  | documentTypeNode
-  | elementNode
-  | textNode
-  | cdataNode
-  | commentNode
-) & {
-  rootId?: number;
-  isShadowHost?: boolean;
-  isShadow?: boolean;
-};
-
-export type serializedNodeWithId = serializedNode & { id: number };
+import { serializedNodeWithId, NodeType } from '@rrweb/types';
 
 export type serializedElementNodeWithId = Extract<
   serializedNodeWithId,
@@ -103,7 +30,9 @@ export type mediaAttributes = {
   rr_mediaVolume?: number;
 };
 
-// @deprecated
+/**
+ * @deprecated
+ */
 export interface INode extends Node {
   __sn: serializedNodeWithId;
 }
@@ -112,28 +41,6 @@ export interface ICanvas extends HTMLCanvasElement {
   __context: string;
 }
 
-export interface IMirror<TNode> {
-  getId(n: TNode | undefined | null): number;
-
-  getNode(id: number): TNode | null;
-
-  getIds(): number[];
-
-  getMeta(n: TNode): serializedNodeWithId | null;
-
-  removeNodeFromMap(n: TNode): void;
-
-  has(id: number): boolean;
-
-  hasNode(node: TNode): boolean;
-
-  add(n: TNode, meta: serializedNodeWithId): void;
-
-  replace(id: number, n: TNode): void;
-
-  reset(): void;
-}
-
 export type idNodeMap = Map<number, Node>;
 
 export type nodeMetaMap = WeakMap<Node, serializedNodeWithId>;
@@ -171,11 +78,6 @@ export type SlimDOMOptions = Partial<{
   headMetaVerification: boolean;
 }>;
 
-export type DataURLOptions = Partial<{
-  type: string;
-  quality: number;
-}>;
-
 export type MaskTextFn = (text: string, element: HTMLElement | null) => string;
 export type MaskInputFn = (text: string, element: HTMLElement) => string;
 
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index e650fc5316..99fab105f6 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -1,8 +1,6 @@
+import { MaskInputFn, MaskInputOptions, idNodeMap, nodeMetaMap } from './types';
+
 import {
-  idNodeMap,
-  MaskInputFn,
-  MaskInputOptions,
-  nodeMetaMap,
   IMirror,
   serializedNodeWithId,
   serializedNode,
@@ -11,7 +9,7 @@ import {
   documentTypeNode,
   textNode,
   elementNode,
-} from './types';
+} from '@rrweb/types';
 
 export function isElement(n: Node): n is Element {
   return n.nodeType === n.ELEMENT_NODE;
diff --git a/packages/rrweb-snapshot/tsconfig.json b/packages/rrweb-snapshot/tsconfig.json
index 58577aa6a9..035c8baa15 100644
--- a/packages/rrweb-snapshot/tsconfig.json
+++ b/packages/rrweb-snapshot/tsconfig.json
@@ -14,5 +14,9 @@
   },
   "exclude": ["test"],
   "include": ["src"],
-  "references": []
+  "references": [
+    {
+      "path": "../types"
+    }
+  ]
 }
diff --git a/packages/rrweb/src/plugins/canvas-webrtc/record/index.ts b/packages/rrweb/src/plugins/canvas-webrtc/record/index.ts
index 03aad10c9c..7074ac4056 100644
--- a/packages/rrweb/src/plugins/canvas-webrtc/record/index.ts
+++ b/packages/rrweb/src/plugins/canvas-webrtc/record/index.ts
@@ -1,6 +1,9 @@
-import type { Mirror } from 'rrweb-snapshot';
 import SimplePeer from 'simple-peer-light';
-import type { RecordPlugin, ICrossOriginIframeMirror } from '@rrweb/types';
+import type {
+  RecordPlugin,
+  ICrossOriginIframeMirror,
+  IMirror,
+} from '@rrweb/types';
 import type { WebRTCDataChannel } from '../types';
 
 export const PLUGIN_NAME = 'rrweb/canvas-webrtc@1';
@@ -25,7 +28,7 @@ export type CrossOriginIframeMessageEventContent = {
 
 export class RRWebPluginCanvasWebRTCRecord {
   private peer: SimplePeer.Instance | null = null;
-  private mirror: Mirror;
+  private mirror: IMirror<Node>;
   private crossOriginIframeMirror: ICrossOriginIframeMirror;
   private streamMap: Map<number, MediaStream> = new Map();
   private incomingStreams = new Set<MediaStream>();
diff --git a/packages/rrweb/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts
index 2d9205c0cb..c82a8e6563 100644
--- a/packages/rrweb/src/record/iframe-manager.ts
+++ b/packages/rrweb/src/record/iframe-manager.ts
@@ -1,9 +1,12 @@
-import type { Mirror, serializedNodeWithId } from 'rrweb-snapshot';
-import { genId, NodeType } from 'rrweb-snapshot';
+import type { Mirror } from 'rrweb-snapshot';
+import { genId } from 'rrweb-snapshot';
 import type { CrossOriginIframeMessageEvent } from '../types';
 import CrossOriginIframeMirror from './cross-origin-iframe-mirror';
-import { EventType, IncrementalSource } from '@rrweb/types';
-import type {
+import {
+  EventType,
+  NodeType,
+  IncrementalSource,
+  serializedNodeWithId,
   eventWithTime,
   eventWithoutTime,
   mutationCallBack,
diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
index f825877f69..6e367ed970 100644
--- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
+++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
@@ -1,4 +1,4 @@
-import type { ICanvas, Mirror, DataURLOptions } from 'rrweb-snapshot';
+import type { ICanvas, Mirror } from 'rrweb-snapshot';
 import type {
   blockClass,
   canvasManagerMutationCallback,
@@ -8,6 +8,7 @@ import type {
   IWindow,
   listenerHandler,
   CanvasArg,
+  DataURLOptions,
 } from '@rrweb/types';
 import { isBlocked } from '../../../utils';
 import { CanvasContext } from '@rrweb/types';
diff --git a/packages/rrweb/src/record/stylesheet-manager.ts b/packages/rrweb/src/record/stylesheet-manager.ts
index 6e0a8077b4..4e4e00221c 100644
--- a/packages/rrweb/src/record/stylesheet-manager.ts
+++ b/packages/rrweb/src/record/stylesheet-manager.ts
@@ -1,6 +1,7 @@
-import type { elementNode, serializedNodeWithId } from 'rrweb-snapshot';
 import { stringifyRule } from 'rrweb-snapshot';
 import type {
+  elementNode,
+  serializedNodeWithId,
   adoptedStyleSheetCallback,
   adoptedStyleSheetParam,
   attributeMutation,
diff --git a/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts b/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts
index 374edfe1b0..b5aed49634 100644
--- a/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts
+++ b/packages/rrweb/src/record/workers/image-bitmap-data-url-worker.ts
@@ -1,6 +1,6 @@
 import { encode } from 'base64-arraybuffer';
-import type { DataURLOptions } from 'rrweb-snapshot';
 import type {
+  DataURLOptions,
   ImageBitmapDataURLWorkerParams,
   ImageBitmapDataURLWorkerResponse,
 } from '@rrweb/types';
diff --git a/packages/rrweb/src/replay/canvas/2d.ts b/packages/rrweb/src/replay/canvas/2d.ts
index 93d852399d..e797ba518f 100644
--- a/packages/rrweb/src/replay/canvas/2d.ts
+++ b/packages/rrweb/src/replay/canvas/2d.ts
@@ -28,7 +28,7 @@ export default async function canvasMutation({
       return Promise.all(mutation.args.map(deserializeArg(imageMap, ctx)));
     },
   );
-  const args = await Promise.all(mutationArgsPromises);
+  const args = (await Promise.all(mutationArgsPromises)) as unknown[];
   // step 2 apply all mutations
   args.forEach((args, index) => {
     const mutation = mutations[index];
diff --git a/packages/rrweb/src/replay/canvas/deserialize-args.ts b/packages/rrweb/src/replay/canvas/deserialize-args.ts
index a690d7986f..7f5d8f98be 100644
--- a/packages/rrweb/src/replay/canvas/deserialize-args.ts
+++ b/packages/rrweb/src/replay/canvas/deserialize-args.ts
@@ -31,6 +31,21 @@ export function isSerializedArg(arg: unknown): arg is SerializedCanvasArg {
   return Boolean(arg && typeof arg === 'object' && 'rr_type' in arg);
 }
 
+type deserializeArgOutput =
+  | ImageBitmap
+  | ArrayBuffer
+  | HTMLImageElement
+  | Blob
+  | {
+      rr_type: string;
+      index: number;
+    }
+  | string
+  | number
+  | boolean
+  | null
+  | deserializeArgOutput[];
+
 export function deserializeArg(
   imageMap: Replayer['imageMap'],
   ctx:
@@ -41,13 +56,17 @@ export function deserializeArg(
   preload?: {
     isUnchanged: boolean;
   },
-): (arg: CanvasArg) => Promise<any> {
-  return async (arg: CanvasArg): Promise<any> => {
+): (arg: CanvasArg) => Promise<deserializeArgOutput> {
+  return async (arg: CanvasArg): Promise<deserializeArgOutput> => {
     if (arg && typeof arg === 'object' && 'rr_type' in arg) {
       if (preload) preload.isUnchanged = false;
       if (arg.rr_type === 'ImageBitmap' && 'args' in arg) {
         // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-        const args = await deserializeArg(imageMap, ctx, preload)(arg.args);
+        const args = (await deserializeArg(
+          imageMap,
+          ctx,
+          preload,
+        )(arg.args)) as unknown as Parameters<typeof createImageBitmap>;
         // eslint-disable-next-line prefer-spread
         return await createImageBitmap.apply(null, args);
       } else if ('index' in arg) {
@@ -79,9 +98,9 @@ export function deserializeArg(
           return image;
         }
       } else if ('data' in arg && arg.rr_type === 'Blob') {
-        const blobContents = await Promise.all(
+        const blobContents = (await Promise.all(
           arg.data.map(deserializeArg(imageMap, ctx, preload)),
-        );
+        )) as BlobPart[];
         const blob = new Blob(blobContents, {
           type: arg.type,
         });
diff --git a/packages/rrweb/src/replay/canvas/webgl.ts b/packages/rrweb/src/replay/canvas/webgl.ts
index 85ff81f8d6..fec72b4b09 100644
--- a/packages/rrweb/src/replay/canvas/webgl.ts
+++ b/packages/rrweb/src/replay/canvas/webgl.ts
@@ -88,7 +88,7 @@ export default async function webglMutation({
     const args = await Promise.all(
       mutation.args.map(deserializeArg(imageMap, ctx)),
     );
-    const result = original.apply(ctx, args);
+    const result = original.apply(ctx, args as unknown[]);
     saveToWebGLVarMap(ctx, result);
 
     // Slows down replay considerably, only use for debugging
@@ -96,21 +96,21 @@ export default async function webglMutation({
     if (debugMode) {
       if (mutation.property === 'compileShader') {
         // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-        if (!ctx.getShaderParameter(args[0], ctx.COMPILE_STATUS))
+        if (!ctx.getShaderParameter(args[0] as WebGLShader, ctx.COMPILE_STATUS))
           console.warn(
             'something went wrong in replay',
             // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-            ctx.getShaderInfoLog(args[0]),
+            ctx.getShaderInfoLog(args[0] as WebGLShader),
           );
       } else if (mutation.property === 'linkProgram') {
         // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-        ctx.validateProgram(args[0]);
+        ctx.validateProgram(args[0] as WebGLProgram);
         // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-        if (!ctx.getProgramParameter(args[0], ctx.LINK_STATUS))
+        if (!ctx.getProgramParameter(args[0] as WebGLProgram, ctx.LINK_STATUS))
           console.warn(
             'something went wrong in replay',
             // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
-            ctx.getProgramInfoLog(args[0]),
+            ctx.getProgramInfoLog(args[0] as WebGLProgram),
           );
       }
       const webglError = ctx.getError();
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 89aa66d933..30f824f16f 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -1,12 +1,10 @@
 import {
   rebuild,
   buildNodeWithSN,
-  NodeType,
   BuildCache,
   createCache,
   Mirror,
   createMirror,
-  attributes,
   serializedElementNodeWithId,
   toLowerCase,
 } from 'rrweb-snapshot';
@@ -34,6 +32,8 @@ import { Timer } from './timer';
 import { createPlayerService, createSpeedService } from './machine';
 import type { playerConfig, missingNodeMap } from '../types';
 import {
+  NodeType,
+  attributes,
   EventType,
   IncrementalSource,
   fullSnapshotEvent,
@@ -81,6 +81,7 @@ import './styles/style.css';
 import canvasMutation from './canvas';
 import { deserializeArg } from './canvas/deserialize-args';
 import { MediaManager } from './media';
+import AssetManager from './assets';
 
 const SKIP_TIME_INTERVAL = 5 * 1000;
 
@@ -855,6 +856,7 @@ export class Replayer {
     if (this.config.UNSAFE_replayCanvas) {
       void this.preloadAllImages();
     }
+    void this.preloadAllAssets();
   }
 
   private insertStyleRules(
@@ -1022,16 +1024,24 @@ export class Replayer {
     }
   }
 
+  /**
+   * Process all asset events and preload them
+   */
+  private async preloadAllAssets(): Promise<void[]> {
+    const assetManager = new AssetManager();
+    const promises: Promise<void>[] = [];
+    for (const event of this.service.state.context.events) {
+      if (event.type === EventType.Asset) {
+        promises.push(assetManager.add(event));
+      }
+    }
+    return Promise.all(promises);
+  }
+
   /**
    * pause when there are some canvas drawImage args need to be loaded
    */
   private async preloadAllImages(): Promise<void[]> {
-    let beforeLoadState = this.service.state;
-    const stateHandler = () => {
-      beforeLoadState = this.service.state;
-    };
-    this.emitter.on(ReplayerEvents.Start, stateHandler);
-    this.emitter.on(ReplayerEvents.Pause, stateHandler);
     const promises: Promise<void>[] = [];
     for (const event of this.service.state.context.events) {
       if (
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index cf86304b7e..2b2a4a5611 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -4,7 +4,6 @@ import type {
   SlimDOMOptions,
   MaskInputFn,
   MaskTextFn,
-  DataURLOptions,
 } from 'rrweb-snapshot';
 import type { PackFn, UnpackFn } from './packer/base';
 import type { IframeManager } from './record/iframe-manager';
@@ -14,6 +13,7 @@ import type { RRNode } from 'rrdom';
 import type { CanvasManager } from './record/observers/canvas/canvas-manager';
 import type { StylesheetManager } from './record/stylesheet-manager';
 import type {
+  DataURLOptions,
   addedNodeMutation,
   blockClass,
   canvasMutationCallback,
diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts
index f426689d2f..7f74c4cc06 100644
--- a/packages/rrweb/src/utils.ts
+++ b/packages/rrweb/src/utils.ts
@@ -8,8 +8,9 @@ import type {
   IWindow,
   DeprecatedMirror,
   textMutation,
+  IMirror,
 } from '@rrweb/types';
-import type { IMirror, Mirror } from 'rrweb-snapshot';
+import type { Mirror } from 'rrweb-snapshot';
 import { isShadowRoot, IGNORED_NODE, classMatchesRegex } from 'rrweb-snapshot';
 import type { RRNode, RRIFrameElement } from 'rrdom';
 
diff --git a/packages/types/package.json b/packages/types/package.json
index 4fa83988f5..a5b7ac61a1 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -9,9 +9,10 @@
     "@rrweb/types"
   ],
   "scripts": {
-    "dev": "vite build -w",
-    "build": "tsc -noEmit && vite build",
+    "dev": "vite build -w & yarn typings -w",
+    "build": "yarn typings && vite build",
     "check-types": "tsc -noEmit",
+    "typings": "tsc -d --declarationDir typings --emitDeclarationOnly",
     "prepublish": "npm run build",
     "lint": "yarn eslint src/**/*.ts"
   },
@@ -27,7 +28,7 @@
   "type": "module",
   "main": "./dist/types.umd.cjs",
   "module": "./dist/types.js",
-  "typings": "dist/index.d.ts",
+  "typings": "typings/index.d.ts",
   "exports": {
     ".": {
       "types": "./dist/index.d.ts",
@@ -36,16 +37,13 @@
     }
   },
   "files": [
-    "build",
+    "typings",
     "dist"
   ],
   "devDependencies": {
     "vite": "^3.2.0-beta.2",
     "vite-plugin-dts": "^1.7.3"
   },
-  "dependencies": {
-    "rrweb-snapshot": "^2.0.0-alpha.13"
-  },
   "browserslist": [
     "supports es6-class"
   ]
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 186fe084bb..5fdb43f228 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -1,10 +1,3 @@
-import type {
-  serializedNodeWithId,
-  Mirror,
-  INode,
-  DataURLOptions,
-} from 'rrweb-snapshot';
-
 export enum EventType {
   DomContentLoaded,
   Load,
@@ -261,7 +254,7 @@ export type RecordPlugin<TOptions = unknown> = {
   ) => listenerHandler;
   eventProcessor?: <TExtend>(event: eventWithTime) => eventWithTime & TExtend;
   getMirror?: (mirrors: {
-    nodeMirror: Mirror;
+    nodeMirror: IMirror<Node>;
     crossOriginIframeMirror: ICrossOriginIframeMirror;
     crossOriginIframeStyleMirror: ICrossOriginIframeMirror;
   }) => void;
@@ -637,6 +630,13 @@ export type assetParam =
 
 export type assetCallback = (d: assetParam) => void;
 
+/**
+ *  @deprecated
+ */
+interface INode extends Node {
+  __sn: serializedNodeWithId;
+}
+
 export type DeprecatedMirror = {
   map: {
     [key: number]: INode;
@@ -726,3 +726,107 @@ export type TakeTypedKeyValues<Obj extends object, Type> = Pick<
   Obj,
   TakeTypeHelper<Obj, Type>[keyof TakeTypeHelper<Obj, Type>]
 >;
+
+
+export enum NodeType {
+  Document,
+  DocumentType,
+  Element,
+  Text,
+  CDATA,
+  Comment,
+}
+
+export type documentNode = {
+  type: NodeType.Document;
+  childNodes: serializedNodeWithId[];
+  compatMode?: string;
+};
+
+export type documentTypeNode = {
+  type: NodeType.DocumentType;
+  name: string;
+  publicId: string;
+  systemId: string;
+};
+
+export type attributes = {
+  [key: string]: string | number | true | null;
+};
+
+export type legacyAttributes = {
+  /**
+   * @deprecated old bug in rrweb was causing these to always be set
+   * @see https://github.com/rrweb-io/rrweb/pull/651
+   */
+  selected: false;
+};
+
+export type elementNode = {
+  type: NodeType.Element;
+  tagName: string;
+  attributes: attributes;
+  childNodes: serializedNodeWithId[];
+  isSVG?: true;
+  needBlock?: boolean;
+  // This is a custom element or not.
+  isCustom?: true;
+};
+
+export type textNode = {
+  type: NodeType.Text;
+  textContent: string;
+  isStyle?: true;
+};
+
+export type cdataNode = {
+  type: NodeType.CDATA;
+  textContent: '';
+};
+
+export type commentNode = {
+  type: NodeType.Comment;
+  textContent: string;
+};
+
+export type serializedNode = (
+  | documentNode
+  | documentTypeNode
+  | elementNode
+  | textNode
+  | cdataNode
+  | commentNode
+) & {
+  rootId?: number;
+  isShadowHost?: boolean;
+  isShadow?: boolean;
+};
+
+export type serializedNodeWithId = serializedNode & { id: number };
+
+export interface IMirror<TNode> {
+  getId(n: TNode | undefined | null): number;
+
+  getNode(id: number): TNode | null;
+
+  getIds(): number[];
+
+  getMeta(n: TNode): serializedNodeWithId | null;
+
+  removeNodeFromMap(n: TNode): void;
+
+  has(id: number): boolean;
+
+  hasNode(node: TNode): boolean;
+
+  add(n: TNode, meta: serializedNodeWithId): void;
+
+  replace(id: number, n: TNode): void;
+
+  reset(): void;
+}
+
+export type DataURLOptions = Partial<{
+  type: string;
+  quality: number;
+}>;
diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json
index ecde939d12..517fd48488 100644
--- a/packages/types/tsconfig.json
+++ b/packages/types/tsconfig.json
@@ -18,9 +18,5 @@
   "compileOnSave": true,
   "exclude": ["test"],
   "include": ["src"],
-  "references": [
-    {
-      "path": "../rrweb-snapshot"
-    }
-  ]
+  "references": []
 }
diff --git a/packages/types/vite.config.js b/packages/types/vite.config.js
index c4cd2de445..1e097aa01b 100644
--- a/packages/types/vite.config.js
+++ b/packages/types/vite.config.js
@@ -1,5 +1,5 @@
 import path from 'path';
-import dts from 'vite-plugin-dts';
+// import dts from 'vite-plugin-dts'; // disabled because of https://github.com/qmhc/vite-plugin-dts/issues/193
 /**
  * @type {import('vite').UserConfig}
  */
@@ -16,9 +16,6 @@ export default {
     minify: false,
 
     sourcemap: true,
-    rollupOptions: {
-      input: path.resolve(__dirname, 'src/index.ts'),
-    },
   },
-  plugins: [dts()],
+  // plugins: [dts()],
 };
diff --git a/yarn.lock b/yarn.lock
index e35b5dfdce..57cdd4e2b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13106,6 +13106,17 @@ rollup-plugin-typescript2@^0.31.2:
     resolve "^1.20.0"
     tslib "^2.3.1"
 
+rollup-plugin-typescript2@^0.34.1:
+  version "0.34.1"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.34.1.tgz#c457f155a71d133c142689213fce78694e30d0be"
+  integrity sha512-P4cHLtGikESmqi1CA+tdMDUv8WbQV48mzPYt77TSTOPJpERyZ9TXdDgjSDix8Fkqce6soYz3+fa4lrC93IEkcw==
+  dependencies:
+    "@rollup/pluginutils" "^4.1.2"
+    find-cache-dir "^3.3.2"
+    fs-extra "^10.0.0"
+    semver "^7.3.7"
+    tslib "^2.4.0"
+
 rollup-plugin-web-worker-loader@^1.6.1:
   version "1.6.1"
   resolved "https://registry.npmjs.org/rollup-plugin-web-worker-loader/-/rollup-plugin-web-worker-loader-1.6.1.tgz"
@@ -13118,10 +13129,10 @@ rollup-pluginutils@^2.8.2:
   dependencies:
     estree-walker "^0.6.1"
 
-rollup@^2.45.2:
-  version "2.53.3"
-  resolved "https://registry.npmjs.org/rollup/-/rollup-2.53.3.tgz"
-  integrity sha512-79QIGP5DXz5ZHYnCPi3tLz+elOQi6gudp9YINdaJdjG0Yddubo6JRFUM//qCZ0Bap/GJrsUoEBVdSOc4AkMlRA==
+rollup@^2.46.0, rollup@^2.79.1:
+  version "2.79.1"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
+  integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
   optionalDependencies:
     fsevents "~2.3.2"
 
@@ -13146,13 +13157,6 @@ rollup@^2.71.1:
   optionalDependencies:
     fsevents "~2.3.2"
 
-rollup@^2.79.1:
-  version "2.79.1"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
-  integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
-  optionalDependencies:
-    fsevents "~2.3.2"
-
 rollup@~2.78.0:
   version "2.78.1"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f"
@@ -14399,7 +14403,7 @@ tslib@^2.3.1:
   resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz"
   integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
 
-tslib@^2.5.3:
+tslib@^2.4.0, tslib@^2.5.3:
   version "2.5.3"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
   integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==

From 57d46195a4dd012227458d11ce4946628c0d1fe4 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 15:21:11 +0100
Subject: [PATCH 008/102] Remove unused import statement

---
 packages/rrweb-snapshot/src/snapshot.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 5fe8e56f2b..0465384a08 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -5,7 +5,6 @@ import {
   MaskInputFn,
   KeepIframeSrcFn,
   ICanvas,
-  elementNode,
   serializedElementNodeWithId,
   type mediaAttributes,
 } from './types';
@@ -14,6 +13,7 @@ import {
   serializedNodeWithId,
   NodeType,
   attributes,
+  elementNode,
   DataURLOptions,
 } from '@rrweb/types';
 import {

From 2f76faef55bbe2fd1a88b8c6ddd0df39ae997a18 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Wed, 14 Jun 2023 00:30:52 +0200
Subject: [PATCH 009/102] WIP asset manager for replay

---
 packages/rrweb-snapshot/src/rebuild.ts        |  10 ++
 packages/rrweb/src/replay/assets/index.ts     |  65 ++++++++++++
 ...corporate-assets-emitted-later-1-snap.png} | Bin
 ...sset.test.ts => asset-integration.test.ts} |   0
 packages/rrweb/test/replay/asset-unit.test.ts | 100 ++++++++++++++++++
 packages/types/src/index.ts                   |  23 +++-
 6 files changed, 193 insertions(+), 5 deletions(-)
 create mode 100644 packages/rrweb/src/replay/assets/index.ts
 rename packages/rrweb/test/replay/__image_snapshots__/{asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png => asset-integration-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png} (100%)
 rename packages/rrweb/test/replay/{asset.test.ts => asset-integration.test.ts} (100%)
 create mode 100644 packages/rrweb/test/replay/asset-unit.test.ts

diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index a52d8a9b4b..4c9d9ae00c 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -1,4 +1,5 @@
 import {
+  RebuildAssetManagerInterface,
   serializedNodeWithId,
   elementNode,
   legacyAttributes,
@@ -150,6 +151,7 @@ function buildNode(
     doc: Document;
     hackCss: boolean;
     cache: BuildCache;
+    assetManager?: RebuildAssetManagerInterface;
   },
 ): Node | null {
   const { doc, hackCss, cache } = options;
@@ -281,6 +283,13 @@ function buildNode(
               'rrweb-original-srcset',
               n.attributes.srcset as string,
             );
+          } else if (
+            tagName === 'img' &&
+            n.attributes.src &&
+            options.assetManager
+          ) {
+            // TODO: do something with the asset manager
+            console.log('WIP! Please implement me!');
           } else {
             node.setAttribute(name, value.toString());
           }
@@ -404,6 +413,7 @@ export function buildNodeWithSN(
      */
     afterAppend?: (n: Node, id: number) => unknown;
     cache: BuildCache;
+    assetManager?: RebuildAssetManagerInterface;
   },
 ): Node | null {
   const {
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
new file mode 100644
index 0000000000..5d898d2b89
--- /dev/null
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -0,0 +1,65 @@
+import type { RebuildAssetManagerInterface, assetEvent } from '@rrweb/types';
+import { deserializeArg } from '../canvas/deserialize-args';
+
+export default class AssetManager implements RebuildAssetManagerInterface {
+  private originalToObjectURLMap: Map<string, string> = new Map();
+  private loadingURLs: Set<string> = new Set();
+  private failedURLs: Set<string> = new Set();
+
+  public async add(event: assetEvent) {
+    const { data } = event;
+    const { url, payload, failed } = { payload: false, failed: false, ...data };
+    if (failed) {
+      this.failedURLs.add(url);
+      return;
+    }
+    this.loadingURLs.add(url);
+
+    // tracks if deserializing did anything, not really needed for AssetManager
+    const status = {
+      isUnchanged: true,
+    };
+
+    // TODO: extract the logic only needed for assets from deserializeArg
+    const result = (await deserializeArg(new Map(), null, status)(payload)) as
+      | Blob
+      | MediaSource;
+
+    const objectURL = URL.createObjectURL(result);
+    this.originalToObjectURLMap.set(url, objectURL);
+    this.loadingURLs.delete(url);
+  }
+
+  public get(
+    url: string,
+  ):
+    | { status: 'loading' }
+    | { status: 'loaded'; url: string }
+    | { status: 'failed' }
+    | { status: 'unknown' } {
+    const result = this.originalToObjectURLMap.get(url);
+
+    if (result) {
+      return {
+        status: 'loaded',
+        url: result,
+      };
+    }
+
+    if (this.loadingURLs.has(url)) {
+      return {
+        status: 'loading',
+      };
+    }
+
+    if (this.failedURLs.has(url)) {
+      return {
+        status: 'failed',
+      };
+    }
+
+    return {
+      status: 'unknown',
+    };
+  }
+}
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png
similarity index 100%
rename from packages/rrweb/test/replay/__image_snapshots__/asset-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png
rename to packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-incorporate-assets-emitted-later-1-snap.png
diff --git a/packages/rrweb/test/replay/asset.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
similarity index 100%
rename from packages/rrweb/test/replay/asset.test.ts
rename to packages/rrweb/test/replay/asset-integration.test.ts
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
new file mode 100644
index 0000000000..788251de2a
--- /dev/null
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -0,0 +1,100 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import AssetManager from '../../src/replay/assets';
+import { EventType, SerializedBlobArg, assetEvent } from '@rrweb/types';
+
+describe('AssetManager', () => {
+  let assetManager: AssetManager;
+  let useURLPolyfill = false;
+  const examplePayload: SerializedBlobArg = {
+    rr_type: 'Blob',
+    type: 'image/png',
+    data: [
+      {
+        rr_type: 'ArrayBuffer',
+        base64: 'fake-base64-abcd',
+      },
+    ],
+  };
+
+  beforeAll(() => {
+    // https://github.com/jsdom/jsdom/issues/1721
+    if (typeof window.URL.createObjectURL === 'undefined') {
+      useURLPolyfill = true;
+      window.URL.createObjectURL = () => '';
+    }
+  });
+
+  beforeEach(() => {
+    assetManager = new AssetManager();
+  });
+
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
+  afterAll(() => {
+    if (useURLPolyfill) {
+      delete (window.URL as any).createObjectURL;
+    }
+  });
+
+  it('should add an asset to the manager', async () => {
+    const url = 'https://example.com/image.png';
+
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    const createObjectURLSpy = jest
+      .spyOn(URL, 'createObjectURL')
+      .mockReturnValue('objectURL');
+
+    await assetManager.add(event);
+
+    expect(createObjectURLSpy).toHaveBeenCalledWith(expect.any(Blob));
+    expect(assetManager.get(url)).toEqual({
+      status: 'loaded',
+      url: 'objectURL',
+    });
+  });
+
+  it('should not add a failed asset to the manager', async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: { url, failed: { message: 'failed to load file' } },
+    };
+    const createObjectURLSpy = jest.spyOn(URL, 'createObjectURL');
+
+    await assetManager.add(event);
+
+    expect(createObjectURLSpy).not.toHaveBeenCalled();
+    expect(assetManager.get(url)).toEqual({ status: 'failed' });
+  });
+
+  it('should return the correct status for a loading asset', () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    void assetManager.add(event);
+
+    expect(assetManager.get(url)).toEqual({ status: 'loading' });
+  });
+
+  it('should return the correct status for an unknown asset', () => {
+    const url = 'https://example.com/image.png';
+
+    expect(assetManager.get(url)).toEqual({ status: 'unknown' });
+  });
+});
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 5fdb43f228..28a85f4673 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -389,16 +389,18 @@ export enum CanvasContext {
   WebGL2,
 }
 
+export type SerializedBlobArg = {
+  rr_type: 'Blob';
+  data: Array<CanvasArg>;
+  type?: string;
+};
+
 export type SerializedCanvasArg =
   | {
       rr_type: 'ArrayBuffer';
       base64: string; // base64
     }
-  | {
-      rr_type: 'Blob';
-      data: Array<CanvasArg>;
-      type?: string;
-    }
+  | SerializedBlobArg
   | {
       rr_type: string;
       src: string; // url of image
@@ -727,6 +729,17 @@ export type TakeTypedKeyValues<Obj extends object, Type> = Pick<
   TakeTypeHelper<Obj, Type>[keyof TakeTypeHelper<Obj, Type>]
 >;
 
+export abstract class RebuildAssetManagerInterface {
+  abstract add(event: assetEvent): Promise<void>;
+
+  abstract get(
+    url: string,
+  ):
+    | { status: 'loading' }
+    | { status: 'loaded'; url: string }
+    | { status: 'failed' }
+    | { status: 'unknown' };
+}
 
 export enum NodeType {
   Document,

From 56f5aa88a91574fd4f3ee1b19eaee01cc7984d5a Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Wed, 14 Jun 2023 00:31:04 +0200
Subject: [PATCH 010/102] NodeType was moved to @rrweb/types

---
 packages/rrweb/test/utils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index 2be7531830..27b721dda4 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -1,4 +1,4 @@
-import { NodeType } from 'rrweb-snapshot';
+import { NodeType } from '@rrweb/types';
 import {
   EventType,
   IncrementalSource,

From cbeeb01b109ff50edec986327190b2bdbd532d78 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 23 Nov 2023 14:29:15 +0100
Subject: [PATCH 011/102] Add placeholder tests

---
 ...corporate-assets-streamed-later-1-snap.png | Bin 0 -> 10796 bytes
 .../test/replay/asset-integration.test.ts     |  28 +++++++++++++++++-
 2 files changed, 27 insertions(+), 1 deletion(-)
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-incorporate-assets-streamed-later-1-snap.png

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-incorporate-assets-streamed-later-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-incorporate-assets-streamed-later-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index ba44cdda20..e9060a2021 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -1,6 +1,6 @@
 import * as fs from 'fs';
 import * as path from 'path';
-import { launchPuppeteer } from '../utils';
+import { launchPuppeteer, waitForRAF } from '../utils';
 import { toMatchImageSnapshot } from 'jest-image-snapshot';
 import type * as puppeteer from 'puppeteer';
 import events from '../events/assets';
@@ -61,5 +61,31 @@ describe('replayer', function () {
       const image = await page.screenshot();
       expect(image).toMatchImageSnapshot();
     });
+
+    // FIXME: test not finished yet
+    it('should incorporate assets streamed later', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([], {
+        liveMode: true,
+      });
+      replayer.startLive();
+      window.replayer.addEvent(events[0]);
+      window.replayer.addEvent(events[1]);
+    `);
+
+      await waitForRAF(page);
+
+      await page.evaluate(`
+        window.replayer.addEvent(events[2]);
+      `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    test.todo('should support video');
   });
 });

From a18d0e6183aea586843f518ac4135b2fed07b670 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 23 Nov 2023 15:44:50 +0100
Subject: [PATCH 012/102] Fix image source attribute in rebuild function

---
 packages/rrweb-snapshot/src/rebuild.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index 4c9d9ae00c..beadb213cb 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -283,9 +283,10 @@ function buildNode(
               'rrweb-original-srcset',
               n.attributes.srcset as string,
             );
+            continue;
           } else if (
             tagName === 'img' &&
-            n.attributes.src &&
+            name === 'src' &&
             options.assetManager
           ) {
             // TODO: do something with the asset manager

From ae020d71ad739deb1226062fd38aba6aaa91bda9 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 23 Nov 2023 17:52:35 +0100
Subject: [PATCH 013/102] Update path in tsconfig.json

---
 packages/rrdom-nodejs/tsconfig.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrdom-nodejs/tsconfig.json b/packages/rrdom-nodejs/tsconfig.json
index e4e98a64bd..0c2f119853 100644
--- a/packages/rrdom-nodejs/tsconfig.json
+++ b/packages/rrdom-nodejs/tsconfig.json
@@ -28,7 +28,7 @@
       "path": "../rrdom"
     },
     {
-      "path": "../types"
+      "path": "../rrweb-snapshot"
     }
   ]
 }

From d3c7b7e7380a70d5d4fa3d9aa22d9a4abfdf9a77 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 23 Nov 2023 17:55:50 +0100
Subject: [PATCH 014/102] Fix asset loading in Replayer

---
 packages/rrweb-snapshot/src/rebuild.ts        | 25 ++++++-
 packages/rrweb/src/replay/assets/index.ts     | 70 +++++++++++++++++--
 packages/rrweb/src/replay/index.ts            | 15 +++-
 packages/rrweb/test/replay/asset-unit.test.ts | 43 ++++++++++++
 packages/types/src/index.ts                   | 27 ++++---
 5 files changed, 158 insertions(+), 22 deletions(-)

diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index beadb213cb..f9d9533f90 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -289,8 +289,22 @@ function buildNode(
             name === 'src' &&
             options.assetManager
           ) {
-            // TODO: do something with the asset manager
-            console.log('WIP! Please implement me!');
+            const originalValue = value.toString();
+            node.setAttribute(name, originalValue);
+            void options.assetManager
+              .whenReady(value.toString())
+              .then((status) => {
+                if (
+                  status.status === 'loaded' &&
+                  node.getAttribute('src') === originalValue
+                ) {
+                  node.setAttribute(name, status.url);
+                } else {
+                  console.log(
+                    `failed to load asset: ${originalValue}, ${status.status}`,
+                  );
+                }
+              });
           } else {
             node.setAttribute(name, value.toString());
           }
@@ -424,6 +438,7 @@ export function buildNodeWithSN(
     hackCss = true,
     afterAppend,
     cache,
+    assetManager,
   } = options;
   /**
    * Add a check to see if the node is already in the mirror. If it is, we can skip the whole process.
@@ -438,7 +453,7 @@ export function buildNodeWithSN(
     // For safety concern, check if the node in mirror is the same as the node we are trying to build
     if (isNodeMetaEqual(meta, n)) return mirror.getNode(n.id);
   }
-  let node = buildNode(n, { doc, hackCss, cache });
+  let node = buildNode(n, { doc, hackCss, cache, assetManager });
   if (!node) {
     return null;
   }
@@ -490,6 +505,7 @@ export function buildNodeWithSN(
         hackCss,
         afterAppend,
         cache,
+        assetManager,
       });
       if (!childNode) {
         console.warn('Failed to rebuild', childN);
@@ -578,6 +594,7 @@ function rebuild(
     afterAppend?: (n: Node, id: number) => unknown;
     cache: BuildCache;
     mirror: Mirror;
+    assetManager?: RebuildAssetManagerInterface;
   },
 ): Node | null {
   const {
@@ -587,6 +604,7 @@ function rebuild(
     afterAppend,
     cache,
     mirror = new Mirror(),
+    assetManager,
   } = options;
   const node = buildNodeWithSN(n, {
     doc,
@@ -595,6 +613,7 @@ function rebuild(
     hackCss,
     afterAppend,
     cache,
+    assetManager,
   });
   visit(mirror, (visitedNode) => {
     if (onVisit) {
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 5d898d2b89..27391371cb 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -1,16 +1,26 @@
-import type { RebuildAssetManagerInterface, assetEvent } from '@rrweb/types';
+import type {
+  RebuildAssetManagerFinalStatus,
+  RebuildAssetManagerInterface,
+  RebuildAssetManagerStatus,
+  assetEvent,
+} from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
 
 export default class AssetManager implements RebuildAssetManagerInterface {
   private originalToObjectURLMap: Map<string, string> = new Map();
   private loadingURLs: Set<string> = new Set();
   private failedURLs: Set<string> = new Set();
+  private callbackMap: Map<
+    string,
+    Array<(status: RebuildAssetManagerFinalStatus) => void>
+  > = new Map();
 
   public async add(event: assetEvent) {
     const { data } = event;
     const { url, payload, failed } = { payload: false, failed: false, ...data };
     if (failed) {
       this.failedURLs.add(url);
+      this.executeCallbacks(url, { status: 'failed' });
       return;
     }
     this.loadingURLs.add(url);
@@ -28,15 +38,45 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     const objectURL = URL.createObjectURL(result);
     this.originalToObjectURLMap.set(url, objectURL);
     this.loadingURLs.delete(url);
+    this.executeCallbacks(url, { status: 'loaded', url: objectURL });
   }
 
-  public get(
+  private executeCallbacks(
     url: string,
-  ):
-    | { status: 'loading' }
-    | { status: 'loaded'; url: string }
-    | { status: 'failed' }
-    | { status: 'unknown' } {
+    status: RebuildAssetManagerFinalStatus,
+  ) {
+    const callbacks = this.callbackMap.get(url);
+    while (callbacks && callbacks.length > 0) {
+      const callback = callbacks.pop();
+      if (!callback) {
+        break;
+      }
+      callback(status);
+    }
+  }
+
+  public async whenReady(url: string): Promise<RebuildAssetManagerFinalStatus> {
+    const currentStatus = this.get(url);
+    if (
+      currentStatus.status === 'loaded' ||
+      currentStatus.status === 'failed'
+    ) {
+      return currentStatus;
+    }
+    let resolve: (status: RebuildAssetManagerFinalStatus) => void;
+    const promise = new Promise<RebuildAssetManagerFinalStatus>((r) => {
+      resolve = r;
+    });
+    if (!this.callbackMap.has(url)) {
+      this.callbackMap.set(url, []);
+    }
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.callbackMap.get(url)!.push(resolve!);
+
+    return promise;
+  }
+
+  public get(url: string): RebuildAssetManagerStatus {
     const result = this.originalToObjectURLMap.get(url);
 
     if (result) {
@@ -62,4 +102,20 @@ export default class AssetManager implements RebuildAssetManagerInterface {
       status: 'unknown',
     };
   }
+
+  public reset(): void {
+    this.originalToObjectURLMap.forEach((objectURL) => {
+      URL.revokeObjectURL(objectURL);
+    });
+    this.originalToObjectURLMap.clear();
+    this.loadingURLs.clear();
+    this.failedURLs.clear();
+    this.callbackMap.forEach((callbacks) => {
+      while (callbacks.length > 0) {
+        const cb = callbacks.pop();
+        if (cb) cb({ status: 'reset' });
+      }
+    });
+    this.callbackMap.clear();
+  }
 }
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 30f824f16f..5082c187dd 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -136,6 +136,9 @@ export class Replayer {
   private cache: BuildCache = createCache();
 
   private imageMap: Map<eventWithTime | string, HTMLImageElement> = new Map();
+
+  private assetManager = new AssetManager();
+
   private canvasEventMap: Map<eventWithTime, canvasMutationParam> = new Map();
 
   private mirror: Mirror = createMirror();
@@ -738,6 +741,11 @@ export class Replayer {
           }
         };
         break;
+      case EventType.Asset:
+        castFn = () => {
+          void this.assetManager.add(event);
+        };
+        break;
       default:
     }
     const wrappedCastFn = () => {
@@ -817,6 +825,8 @@ export class Replayer {
       }
     };
 
+    void this.preloadAllAssets();
+
     /**
      * Normally rebuilding full snapshot should not be under virtual dom environment.
      * But if the order of data events has some issues, it might be possible.
@@ -833,6 +843,7 @@ export class Replayer {
       afterAppend,
       cache: this.cache,
       mirror: this.mirror,
+      assetManager: this.assetManager,
     });
     afterAppend(this.iframe.contentDocument, event.data.node.id);
 
@@ -856,7 +867,6 @@ export class Replayer {
     if (this.config.UNSAFE_replayCanvas) {
       void this.preloadAllImages();
     }
-    void this.preloadAllAssets();
   }
 
   private insertStyleRules(
@@ -1028,11 +1038,10 @@ export class Replayer {
    * Process all asset events and preload them
    */
   private async preloadAllAssets(): Promise<void[]> {
-    const assetManager = new AssetManager();
     const promises: Promise<void>[] = [];
     for (const event of this.service.state.context.events) {
       if (event.type === EventType.Asset) {
-        promises.push(assetManager.add(event));
+        promises.push(this.assetManager.add(event));
       }
     }
     return Promise.all(promises);
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 788251de2a..62a0ecc32e 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -33,6 +33,7 @@ describe('AssetManager', () => {
 
   afterEach(() => {
     jest.restoreAllMocks();
+    jest.useRealTimers();
   });
 
   afterAll(() => {
@@ -97,4 +98,46 @@ describe('AssetManager', () => {
 
     expect(assetManager.get(url)).toEqual({ status: 'unknown' });
   });
+
+  it('should execute hook when an asset is added', async () => {
+    jest.useFakeTimers();
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    void assetManager.add(event);
+    const promise = assetManager.whenReady(url);
+
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+
+    jest.runAllTimers();
+
+    await expect(promise).resolves.toEqual({
+      status: 'loaded',
+      url: 'objectURL',
+    });
+  });
+
+  it('should send status reset to callbacks when reset', async () => {
+    jest.useFakeTimers();
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    void assetManager.add(event);
+    const promise = assetManager.whenReady(url);
+
+    assetManager.reset();
+    jest.runAllTimers();
+
+    await expect(promise).resolves.toEqual({ status: 'reset' });
+  });
 });
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 28a85f4673..e887b7ee48 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -729,16 +729,25 @@ export type TakeTypedKeyValues<Obj extends object, Type> = Pick<
   TakeTypeHelper<Obj, Type>[keyof TakeTypeHelper<Obj, Type>]
 >;
 
-export abstract class RebuildAssetManagerInterface {
+export type RebuildAssetManagerResetStatus = { status: 'reset' };
+export type RebuildAssetManagerUnknownStatus = { status: 'unknown' };
+export type RebuildAssetManagerLoadingStatus = { status: 'loading' };
+export type RebuildAssetManagerLoadedStatus = { status: 'loaded'; url: string };
+export type RebuildAssetManagerFailedStatus = { status: 'failed' };
+export type RebuildAssetManagerFinalStatus =
+  | RebuildAssetManagerLoadedStatus
+  | RebuildAssetManagerFailedStatus
+  | RebuildAssetManagerResetStatus;
+export type RebuildAssetManagerStatus =
+  | RebuildAssetManagerUnknownStatus
+  | RebuildAssetManagerLoadingStatus
+  | RebuildAssetManagerFinalStatus;
+
+export declare abstract class RebuildAssetManagerInterface {
   abstract add(event: assetEvent): Promise<void>;
-
-  abstract get(
-    url: string,
-  ):
-    | { status: 'loading' }
-    | { status: 'loaded'; url: string }
-    | { status: 'failed' }
-    | { status: 'unknown' };
+  abstract get(url: string): RebuildAssetManagerStatus;
+  abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
+  abstract reset(): void;
 }
 
 export enum NodeType {

From 841cd8c170532ad839d9caf12e9ce6e8482ba920 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 23 Nov 2023 17:56:15 +0100
Subject: [PATCH 015/102] Add todo tests

---
 packages/rrweb/test/replay/asset-integration.test.ts | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index e9060a2021..8d5b609d0f 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -62,7 +62,6 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
-    // FIXME: test not finished yet
     it('should incorporate assets streamed later', async () => {
       await page.evaluate(`
       const { Replayer } = rrweb;
@@ -86,6 +85,14 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
-    test.todo('should support video');
+    test.todo('should support urls src modified via mutation');
+
+    test.todo('should support video elements');
+    test.todo('should support audio elements');
+    test.todo('should support embed elements');
+    test.todo('should support source elements');
+    test.todo('should support track elements');
+    test.todo('should support input#type=image elements');
+    test.todo('should support img srcset');
   });
 });

From 607ce6db559d917b1097bbcd74f7ab7532ee2efd Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 24 Nov 2023 12:31:33 +0100
Subject: [PATCH 016/102] Add asset mutation events for replayer test

---
 packages/rrweb/test/events/assets-mutation.ts | 141 ++++++++++++++++++
 ...-urls-src-modified-via-mutation-1-snap.png | Bin 0 -> 10796 bytes
 .../test/replay/asset-integration.test.ts     |  26 +++-
 3 files changed, 166 insertions(+), 1 deletion(-)
 create mode 100644 packages/rrweb/test/events/assets-mutation.ts
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-mutation-1-snap.png

diff --git a/packages/rrweb/test/events/assets-mutation.ts b/packages/rrweb/test/events/assets-mutation.ts
new file mode 100644
index 0000000000..b318183900
--- /dev/null
+++ b/packages/rrweb/test/events/assets-mutation.ts
@@ -0,0 +1,141 @@
+import { EventType, IncrementalSource, type eventWithTime } from '@rrweb/types';
+
+const events: eventWithTime[] = [
+  {
+    type: 4,
+    data: {
+      href: '',
+      width: 1600,
+      height: 900,
+    },
+    timestamp: 1636379531385,
+  },
+  {
+    type: 2,
+    data: {
+      node: {
+        type: 0,
+        childNodes: [
+          { type: 1, name: 'html', publicId: '', systemId: '', id: 2 },
+          {
+            type: 2,
+            tagName: 'html',
+            attributes: { lang: 'en' },
+            childNodes: [
+              {
+                type: 2,
+                tagName: 'head',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 5 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: { charset: 'UTF-8' },
+                    childNodes: [],
+                    id: 6,
+                  },
+                  { type: 3, textContent: '\n    ', id: 7 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: {
+                      name: 'viewport',
+                      content: 'width=device-width, initial-scale=1.0',
+                    },
+                    childNodes: [],
+                    id: 8,
+                  },
+                  { type: 3, textContent: '\n    ', id: 9 },
+                  {
+                    type: 2,
+                    tagName: 'title',
+                    attributes: {},
+                    childNodes: [{ type: 3, textContent: 'assets', id: 11 }],
+                    id: 10,
+                  },
+                  { type: 3, textContent: '\n  ', id: 12 },
+                ],
+                id: 4,
+              },
+              { type: 3, textContent: '\n  ', id: 13 },
+              {
+                type: 2,
+                tagName: 'body',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 15 },
+                  {
+                    type: 2,
+                    tagName: 'img',
+                    attributes: {
+                      width: '100',
+                      height: '100',
+                      style: 'border: 1px solid #000000',
+                    },
+                    childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
+                    id: 16,
+                  },
+                  { type: 3, textContent: '\n    ', id: 18 },
+                  {
+                    type: 2,
+                    tagName: 'script',
+                    attributes: {},
+                    childNodes: [
+                      { type: 3, textContent: 'SCRIPT_PLACEHOLDER', id: 20 },
+                    ],
+                    id: 19,
+                  },
+                  { type: 3, textContent: '\n  \n\n', id: 21 },
+                ],
+                id: 14,
+              },
+            ],
+            id: 3,
+          },
+        ],
+        id: 1,
+      },
+      initialOffset: { left: 0, top: 0 },
+    },
+    timestamp: 1636379531389,
+  },
+  {
+    type: EventType.IncrementalSnapshot,
+    data: {
+      source: IncrementalSource.Mutation,
+      texts: [],
+      attributes: [
+        {
+          id: 16,
+          attributes: {
+            src: 'httpx://example.com/image.png',
+          },
+        },
+      ],
+      removes: [],
+      adds: [],
+    },
+    timestamp: 1636379531390,
+  },
+  {
+    type: EventType.Asset,
+    data: {
+      url: 'httpx://example.com/image.png',
+      payload: {
+        rr_type: 'Blob',
+        type: 'image/png',
+        data: [
+          {
+            rr_type: 'ArrayBuffer',
+            base64:
+              'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAWtJREFUeF7t1cEJAEAIxEDtv2gProo8xgpCwuLezI3LGFhBMi0+iCCtHoLEeggiSM1AjMcPESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4TwVjsedWCiXGAAAAABJRU5ErkJggg==', // base64
+          },
+        ],
+      },
+    },
+    timestamp: 1636379532355,
+  },
+];
+
+export default events;
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-mutation-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-mutation-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 8d5b609d0f..aef58882b9 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -4,6 +4,7 @@ import { launchPuppeteer, waitForRAF } from '../utils';
 import { toMatchImageSnapshot } from 'jest-image-snapshot';
 import type * as puppeteer from 'puppeteer';
 import events from '../events/assets';
+import mutationEvents from '../events/assets-mutation';
 
 interface ISuite {
   code: string;
@@ -37,6 +38,7 @@ describe('replayer', function () {
     });
     await page.evaluate(code);
     await page.evaluate(`let events = ${JSON.stringify(events)}`);
+    await page.evaluate(`let events2 = ${JSON.stringify(mutationEvents)}`);
 
     page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
   });
@@ -85,7 +87,29 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
-    test.todo('should support urls src modified via mutation');
+    it('should support urls src modified via incremental mutation', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([], {
+        liveMode: true,
+      });
+      replayer.startLive();
+      window.replayer.addEvent(events2[0]);
+      window.replayer.addEvent(events2[1]);
+      window.replayer.addEvent(events2[2]);
+    `);
+
+      await waitForRAF(page);
+
+      await page.evaluate(`
+        window.replayer.addEvent(events2[3]);
+      `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
 
     test.todo('should support video elements');
     test.todo('should support audio elements');

From e4ecc0ab41b51896c409c0a46aa989463ae602bb Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 24 Nov 2023 17:38:29 +0100
Subject: [PATCH 017/102] Add replay for cached asset on attribute changes

---
 packages/rrweb-snapshot/src/index.ts          |   2 +
 packages/rrweb-snapshot/src/rebuild.ts        |  25 +----
 packages/rrweb-snapshot/src/snapshot.ts       |  25 ++++-
 packages/rrweb/src/record/mutation.ts         |  33 ++++---
 .../src/record/observers/asset-manager.ts     |   6 +-
 packages/rrweb/src/replay/assets/index.ts     |  41 ++++++++
 packages/rrweb/src/replay/index.ts            |  14 ++-
 packages/rrweb/src/utils.ts                   |  22 +++++
 packages/rrweb/test/events/assets-mutation.ts |   2 +-
 ...dified-via-incremental-mutation-1-snap.png | Bin 0 -> 10796 bytes
 .../test/replay/asset-integration.test.ts     |  15 +--
 packages/rrweb/test/replay/asset-unit.test.ts |  90 ++++++++++++++++++
 12 files changed, 227 insertions(+), 48 deletions(-)
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-incremental-mutation-1-snap.png

diff --git a/packages/rrweb-snapshot/src/index.ts b/packages/rrweb-snapshot/src/index.ts
index c9f91a9100..8e00cd4803 100644
--- a/packages/rrweb-snapshot/src/index.ts
+++ b/packages/rrweb-snapshot/src/index.ts
@@ -8,6 +8,7 @@ import snapshot, {
   classMatchesRegex,
   IGNORED_NODE,
   genId,
+  getSourcesFromSrcset,
 } from './snapshot';
 import rebuild, {
   buildNodeWithSN,
@@ -32,4 +33,5 @@ export {
   classMatchesRegex,
   IGNORED_NODE,
   genId,
+  getSourcesFromSrcset,
 };
diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index f9d9533f90..5815ea60c4 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -284,29 +284,12 @@ function buildNode(
               n.attributes.srcset as string,
             );
             continue;
-          } else if (
-            tagName === 'img' &&
-            name === 'src' &&
-            options.assetManager
-          ) {
-            const originalValue = value.toString();
-            node.setAttribute(name, originalValue);
-            void options.assetManager
-              .whenReady(value.toString())
-              .then((status) => {
-                if (
-                  status.status === 'loaded' &&
-                  node.getAttribute('src') === originalValue
-                ) {
-                  node.setAttribute(name, status.url);
-                } else {
-                  console.log(
-                    `failed to load asset: ${originalValue}, ${status.status}`,
-                  );
-                }
-              });
           } else {
             node.setAttribute(name, value.toString());
+
+            if (options.assetManager?.isAttributeCacheable(node, name)) {
+              options.assetManager.manageAttribute(node, name);
+            }
           }
         } catch (error) {
           // skip invalid attribute
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 0465384a08..81369e37ee 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -125,7 +125,11 @@ export function absoluteToStylesheet(
 const SRCSET_NOT_SPACES = /^[^ \t\n\r\u000c]+/; // Don't use \s, to avoid matching non-breaking space
 // eslint-disable-next-line no-control-regex
 const SRCSET_COMMAS_OR_SPACES = /^[, \t\n\r\u000c]+/;
-function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
+function parseSrcsetString(
+  doc: Document,
+  attributeValue: string,
+  urlCallback: (doc: Document, url: string) => string,
+) {
   /*
     run absoluteToDoc over every url in the srcset
 
@@ -162,13 +166,13 @@ function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
     let url = collectCharacters(SRCSET_NOT_SPACES);
     if (url.slice(-1) === ',') {
       // aside: according to spec more than one comma at the end is a parse error, but we ignore that
-      url = absoluteToDoc(doc, url.substring(0, url.length - 1));
+      url = urlCallback(doc, url.substring(0, url.length - 1));
       // the trailing comma splits the srcset, so the interpretion is that
       // another url will follow, and the descriptor is empty
       output.push(url);
     } else {
       let descriptorsStr = '';
-      url = absoluteToDoc(doc, url);
+      url = urlCallback(doc, url);
       let inParens = false;
       // eslint-disable-next-line no-constant-condition
       while (true) {
@@ -199,6 +203,21 @@ function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
   return output.join(', ');
 }
 
+function getAbsoluteSrcsetString(doc: Document, attributeValue: string) {
+  return parseSrcsetString(doc, attributeValue, (doc, url) =>
+    absoluteToDoc(doc, url),
+  );
+}
+
+export function getSourcesFromSrcset(attributeValue: string): string[] {
+  const urls = new Set<string>();
+  parseSrcsetString(document, attributeValue, (_, url) => {
+    urls.add(url);
+    return url;
+  });
+  return Array.from(urls);
+}
+
 export function absoluteToDoc(doc: Document, attributeValue: string): string {
   if (!attributeValue || attributeValue.trim() === '') {
     return attributeValue;
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 8189bfb36e..9d72c74b5a 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -10,6 +10,7 @@ import {
   isNativeShadowDom,
   getInputType,
   toLowerCase,
+  getSourcesFromSrcset,
 } from 'rrweb-snapshot';
 import type { observerParam, MutationBufferParam } from '../types';
 import type {
@@ -602,12 +603,6 @@ export default class MutationBuffer {
           } else {
             return;
           }
-        } else if (
-          target.tagName === 'IMG' &&
-          (attributeName === 'src' || attributeName === 'srcset') &&
-          value
-        ) {
-          this.assetManager.capture(value);
         }
         if (!item) {
           item = {
@@ -632,12 +627,26 @@ export default class MutationBuffer {
 
         if (!ignoreAttribute(target.tagName, attributeName, value)) {
           // overwrite attribute if the mutations was triggered in same time
-          item.attributes[attributeName] = transformAttribute(
-            this.doc,
-            toLowerCase(target.tagName),
-            toLowerCase(attributeName),
-            value,
-          );
+          const transformedValue = (item.attributes[attributeName] =
+            transformAttribute(
+              this.doc,
+              toLowerCase(target.tagName),
+              toLowerCase(attributeName),
+              value,
+            ));
+          if (
+            transformedValue &&
+            this.assetManager.isAttributeCacheable(target, attributeName)
+          ) {
+            if (attributeName === 'srcset') {
+              getSourcesFromSrcset(transformedValue).forEach((url) => {
+                this.assetManager.capture(url);
+              });
+            } else {
+              this.assetManager.capture(transformedValue);
+            }
+          }
+
           if (attributeName === 'style') {
             if (!this.unattachedDoc) {
               try {
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 4249ef014d..c459485ebb 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -7,7 +7,7 @@ import type {
 import type { assetCallback } from '@rrweb/types';
 import { encode } from 'base64-arraybuffer';
 
-import { patch } from '../../utils';
+import { isAttributeCacheable, patch } from '../../utils';
 import type { recordOptions } from '../../types';
 
 export default class AssetManager {
@@ -191,4 +191,8 @@ export default class AssetManager {
 
     return { status: 'capturing' };
   }
+
+  public isAttributeCacheable(n: Element, attribute: string): boolean {
+    return isAttributeCacheable(n, attribute);
+  }
 }
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 27391371cb..2c9488ec5c 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -5,6 +5,9 @@ import type {
   assetEvent,
 } from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
+import { isAttributeCacheable } from '../../utils';
+import { getSourcesFromSrcset } from 'rrweb-snapshot';
+import type { RRElement } from 'rrdom';
 
 export default class AssetManager implements RebuildAssetManagerInterface {
   private originalToObjectURLMap: Map<string, string> = new Map();
@@ -55,6 +58,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     }
   }
 
+  // TODO: turn this into a true promise that throws if the asset fails to load
   public async whenReady(url: string): Promise<RebuildAssetManagerFinalStatus> {
     const currentStatus = this.get(url);
     if (
@@ -103,6 +107,43 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     };
   }
 
+  public isAttributeCacheable(
+    n: RRElement | Element,
+    attribute: string,
+  ): boolean {
+    return isAttributeCacheable(n as Element, attribute);
+  }
+
+  public async manageAttribute(
+    node: RRElement | Element,
+    attribute: string,
+  ): Promise<unknown> {
+    const originalValue = node.getAttribute(attribute);
+    if (!originalValue) return false;
+
+    const promises = [];
+
+    const values =
+      attribute === 'srcset'
+        ? getSourcesFromSrcset(originalValue)
+        : [originalValue];
+    for (const value of values) {
+      promises.push(
+        this.whenReady(value).then((status) => {
+          if (
+            status.status === 'loaded' &&
+            node.getAttribute(attribute) === originalValue
+          ) {
+            node.setAttribute(attribute, status.url);
+          } else {
+            // failed to load asset, or the attribute was changed
+          }
+        }),
+      );
+    }
+    return Promise.all(promises);
+  }
+
   public reset(): void {
     this.originalToObjectURLMap.forEach((objectURL) => {
       URL.revokeObjectURL(objectURL);
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 5082c187dd..f8bf1c5809 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -950,6 +950,7 @@ export class Replayer {
       skipChild: false,
       afterAppend,
       cache: this.cache,
+      assetManager: this.assetManager,
     });
     afterAppend(iframeEl.contentDocument! as Document, mutation.node.id);
 
@@ -1565,6 +1566,7 @@ export class Replayer {
         skipChild: true,
         hackCss: true,
         cache: this.cache,
+        assetManager: this.assetManager,
         /**
          * caveat: `afterAppend` only gets called on child nodes of target
          * we have to call it again below when this target was added to the DOM
@@ -1779,6 +1781,7 @@ export class Replayer {
                     skipChild: true,
                     hackCss: true,
                     cache: this.cache,
+                    assetManager: this.assetManager,
                   });
                   const siblingNode = target.nextSibling;
                   const parentNode = target.parentNode;
@@ -1795,6 +1798,7 @@ export class Replayer {
                   // for safe
                 }
               }
+              const targetEl = target as Element | RRElement;
               if (attributeName === 'value' && target.nodeName === 'TEXTAREA') {
                 // this may or may not have an effect on the value property (which is what is displayed)
                 // depending on whether the textarea has been modified by the user yet
@@ -1808,10 +1812,12 @@ export class Replayer {
                   textarea.appendChild(tn as TNode);
                 }
               } else {
-                (target as Element | RRElement).setAttribute(
-                  attributeName,
-                  value,
-                );
+                targetEl.setAttribute(attributeName, value);
+              }
+              if (
+                this.assetManager.isAttributeCacheable(targetEl, attributeName)
+              ) {
+                void this.assetManager.manageAttribute(targetEl, attributeName);
               }
             } catch (error) {
               this.warn(
diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts
index 7f74c4cc06..d289e69638 100644
--- a/packages/rrweb/src/utils.ts
+++ b/packages/rrweb/src/utils.ts
@@ -590,3 +590,25 @@ export function inDom(n: Node): boolean {
   if (!doc) return false;
   return doc.contains(n) || shadowHostInDom(n);
 }
+
+export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
+  ['IMG', new Set(['src', 'srcset'])],
+  ['VIDEO', new Set(['src'])],
+  ['AUDIO', new Set(['src'])],
+  ['EMBED', new Set(['src'])],
+  ['SOURCE', new Set(['src'])],
+  ['TRACK', new Set(['src'])],
+  ['INPUT', new Set(['src'])],
+  ['IFRAME', new Set(['src'])],
+  ['OBJECT', new Set(['src'])],
+]);
+
+export function isAttributeCacheable(n: Element, attribute: string): boolean {
+  const acceptedAttributesSet = CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
+    n.nodeName,
+  );
+  if (!acceptedAttributesSet) {
+    return false;
+  }
+  return acceptedAttributesSet.has(attribute);
+}
diff --git a/packages/rrweb/test/events/assets-mutation.ts b/packages/rrweb/test/events/assets-mutation.ts
index b318183900..966c29678a 100644
--- a/packages/rrweb/test/events/assets-mutation.ts
+++ b/packages/rrweb/test/events/assets-mutation.ts
@@ -134,7 +134,7 @@ const events: eventWithTime[] = [
         ],
       },
     },
-    timestamp: 1636379532355,
+    timestamp: 1636379531391,
   },
 ];
 
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-incremental-mutation-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-support-urls-src-modified-via-incremental-mutation-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index aef58882b9..02e650fa68 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -38,7 +38,9 @@ describe('replayer', function () {
     });
     await page.evaluate(code);
     await page.evaluate(`let events = ${JSON.stringify(events)}`);
-    await page.evaluate(`let events2 = ${JSON.stringify(mutationEvents)}`);
+    await page.evaluate(
+      `let mutationEvents = ${JSON.stringify(mutationEvents)}`,
+    );
 
     page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
   });
@@ -93,16 +95,16 @@ describe('replayer', function () {
       window.replayer = new Replayer([], {
         liveMode: true,
       });
-      replayer.startLive();
-      window.replayer.addEvent(events2[0]);
-      window.replayer.addEvent(events2[1]);
-      window.replayer.addEvent(events2[2]);
+      replayer.startLive(mutationEvents[0].timestamp);
+      window.replayer.addEvent(mutationEvents[0]);
+      window.replayer.addEvent(mutationEvents[1]);
+      window.replayer.addEvent(mutationEvents[2]);
     `);
 
       await waitForRAF(page);
 
       await page.evaluate(`
-        window.replayer.addEvent(events2[3]);
+        window.replayer.addEvent(mutationEvents[3]);
       `);
 
       await waitForRAF(page);
@@ -111,6 +113,7 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
+    test.todo("should support work through rrelement's too");
     test.todo('should support video elements');
     test.todo('should support audio elements');
     test.todo('should support embed elements');
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 62a0ecc32e..2e821f01e5 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -140,4 +140,94 @@ describe('AssetManager', () => {
 
     await expect(promise).resolves.toEqual({ status: 'reset' });
   });
+
+  const validCombinations = [
+    ['img', ['src', 'srcset']],
+    ['video', ['src']],
+    ['audio', ['src']],
+    ['embed', ['src']],
+    ['source', ['src']],
+    ['track', ['src']],
+    ['input', ['src']],
+    ['iframe', ['src']],
+    ['object', ['src']],
+  ] as const;
+
+  const invalidCombinations = [
+    ['img', ['href']],
+    ['script', ['href']],
+    ['link', ['src']],
+    ['video', ['href']],
+    ['audio', ['href']],
+    ['div', ['src']],
+    ['source', ['href']],
+    ['track', ['href']],
+    ['input', ['href']],
+    ['iframe', ['href']],
+    ['object', ['href']],
+  ] as const;
+
+  validCombinations.forEach(([tagName, attributes]) => {
+    const element = document.createElement(tagName);
+    attributes.forEach((attribute) => {
+      it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
+        expect(assetManager.isAttributeCacheable(element, attribute)).toBe(
+          true,
+        );
+      });
+    });
+  });
+
+  invalidCombinations.forEach(([tagName, attributes]) => {
+    const element = document.createElement(tagName);
+    attributes.forEach((attribute) => {
+      it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
+        expect(assetManager.isAttributeCacheable(element, attribute)).toBe(
+          false,
+        );
+      });
+    });
+  });
+
+  it("should be able to modify a node's attribute once asset is loaded", async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+
+    const element = document.createElement('img');
+    element.setAttribute('src', url);
+
+    const promise = assetManager.manageAttribute(element, 'src');
+
+    await assetManager.add(event);
+    await promise;
+
+    expect(element.getAttribute('src')).toBe('objectURL');
+  });
+
+  it("should be able to modify a node's attribute for previously loaded assets", async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+    await assetManager.add(event);
+
+    const element = document.createElement('img');
+    element.setAttribute('src', url);
+
+    await assetManager.manageAttribute(element, 'src');
+
+    expect(element.getAttribute('src')).toBe('objectURL');
+  });
 });

From 12c18e6f5c83deb3cd7d0bd57cd7bbd948a0462d Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 15:56:41 +0100
Subject: [PATCH 018/102] Add isAttributeCacheable and manageAttribute methods
 to RebuildAssetManagerInterface

---
 packages/types/src/index.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index e887b7ee48..2f6e47cb40 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -748,6 +748,8 @@ export declare abstract class RebuildAssetManagerInterface {
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
   abstract reset(): void;
+  abstract isAttributeCacheable(n: Element, attribute: string): boolean;
+  abstract manageAttribute(n: Element, attribute: string): void;
 }
 
 export enum NodeType {

From 99d2b21a348628082717b6b5c5c2dc20dcd6f72d Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 17:07:02 +0100
Subject: [PATCH 019/102] Move assetCapture params to @rrweb/types and add them
 to meta events

---
 docs/recipes/assets.md                        | 12 +--
 guide.md                                      |  2 +-
 packages/rrweb/src/record/index.ts            |  4 +-
 .../src/record/observers/asset-manager.ts     |  8 +-
 packages/rrweb/src/types.ts                   | 17 +---
 packages/rrweb/test/record/asset.test.ts      | 89 ++++++++++++-------
 packages/types/src/index.ts                   | 17 ++++
 7 files changed, 91 insertions(+), 58 deletions(-)

diff --git a/docs/recipes/assets.md b/docs/recipes/assets.md
index c6b08a2b01..8f57fc4539 100644
--- a/docs/recipes/assets.md
+++ b/docs/recipes/assets.md
@@ -10,12 +10,12 @@ The `inlineImages` configuration option is deprecated and should not be used any
 
 The `assetCapture` configuration option allows you to customize the asset capture process. It is an object with the following properties:
 
-- `captureObjectURLs` (default: `true`): This property specifies whether to capture same-origin `blob:` assets using object URLs. Object URLs are created using the `URL.createObjectURL()` method. Setting `captureObjectURLs` to `true` enables the capture of object URLs.
+- `objectURLs` (default: `true`): This property specifies whether to capture same-origin `blob:` assets using object URLs. Object URLs are created using the `URL.createObjectURL()` method. Setting `objectURLs` to `true` enables the capture of object URLs.
 
-- `captureOrigins` (default: `false`): This property determines which origins to capture assets from. It can have the following values:
+- `origins` (default: `false`): This property determines which origins to capture assets from. It can have the following values:
   - `false` or `[]`: Disables capturing any assets apart from object URLs.
   - `true`: Captures assets from all origins.
-  - `[origin1, origin2, ...]`: Captures assets only from the specified origins. For example, `captureOrigins: ['https://s3.example.com/']` captures all assets from the origin `https://s3.example.com/`.
+  - `[origin1, origin2, ...]`: Captures assets only from the specified origins. For example, `origins: ['https://s3.example.com/']` captures all assets from the origin `https://s3.example.com/`.
 
 ## TypeScript Type Definition
 
@@ -26,14 +26,14 @@ export type recordOptions<T> = {
   // Other configuration options...
   inlineImages?: boolean;
   assetCapture?: {
-    captureObjectURLs: boolean;
-    captureOrigins: string[] | true | false;
+    objectURLs: boolean;
+    origins: string[] | true | false;
   };
   // Other configuration options...
 };
 ```
 
-This type definition shows that `assetCapture` is an optional property of the `recordOptions` object. It contains the `captureObjectURLs` and `captureOrigins` properties, which have the same meanings as described above.
+This type definition shows that `assetCapture` is an optional property of the `recordOptions` object. It contains the `objectURLs` and `origins` properties, which have the same meanings as described above.
 
 ## Conclusion
 
diff --git a/guide.md b/guide.md
index 93636881ff..5881f3b86f 100644
--- a/guide.md
+++ b/guide.md
@@ -161,7 +161,7 @@ The parameter of `rrweb.record` accepts the following options.
 | recordCrossOriginIframes | false                                              | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
 | recordAfter              | 'load'                                             | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
 | inlineImages             | false                                              | whether to record the image content (deprecated, use `assetCapture` instead)                                                                                                                  |
-| assetCapture             | { captureObjectURLs: true, captureOrigins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
+| assetCapture             | { objectURLs: true, origins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
 | collectFonts             | false                                              | whether to collect fonts in the website                                                                                                                                                       |
 | userTriggeredOnInput     | false                                              | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
 | plugins                  | []                                                 | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md)                                                                                               |
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 5b6d6959de..21b0e24f83 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -84,8 +84,8 @@ function record<T = eventWithTime>(
     collectFonts = false,
     inlineImages = false,
     assetCapture = {
-      captureObjectURLs: true,
-      captureOrigins: false,
+      objectURLs: true,
+      origins: false,
     },
     plugins,
     keepIframeSrcFn = () => false,
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index c459485ebb..5f92cd1da4 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -45,7 +45,7 @@ export default class AssetManager {
 
     const urlObjectMap = this.urlObjectMap;
 
-    if (this.config.captureObjectURLs) {
+    if (this.config.objectURLs) {
       try {
         const restoreHandler = patch(
           win.URL,
@@ -86,15 +86,15 @@ export default class AssetManager {
     const urlIsBlob = url.startsWith(`blob:${window.location.origin}/`);
 
     // Check if url is a blob and we should ignore blobs
-    if (urlIsBlob) return !this.config.captureObjectURLs;
+    if (urlIsBlob) return !this.config.objectURLs;
 
     // Check if url matches any ignorable origins
     for (const origin of originsToIgnore) {
       if (url.startsWith(origin)) return true;
     }
 
-    // Check the captureOrigins
-    const captureOrigins = this.config.captureOrigins;
+    // Check the origins
+    const captureOrigins = this.config.origins;
     if (typeof captureOrigins === 'boolean') {
       return !captureOrigins;
     } else if (Array.isArray(captureOrigins)) {
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index 2b2a4a5611..ba1f8c05aa 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -37,6 +37,7 @@ import type {
   styleDeclarationCallback,
   styleSheetRuleCallback,
   viewportResizeCallback,
+  assetCaptureParam,
 } from '@rrweb/types';
 import type ProcessedNodeManager from './record/processed-node-manager';
 import type AssetManager from './record/observers/asset-manager';
@@ -69,21 +70,7 @@ export type recordOptions<T> = {
   userTriggeredOnInput?: boolean;
   collectFonts?: boolean;
   inlineImages?: boolean;
-  assetCapture?: {
-    /**
-     * Captures object URLs (blobs, files, media sources).
-     * More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
-     */
-    captureObjectURLs: boolean;
-    /**
-     * Allowlist of origins to capture object URLs from.
-     * [origin, origin, ...] to capture from specific origins.
-     *   e.g. ['https://example.com', 'https://www.example.com']
-     * Set to `true` capture from all origins.
-     * Set to `false` or `[]` to disable capturing from any origin apart from object URLs.
-     */
-    captureOrigins: string[] | true | false;
-  };
+  assetCapture?: assetCaptureParam;
   plugins?: RecordPlugin[];
   // departed, please use sampling options
   mousemoveWait?: number;
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index dafd7648d0..8aa54d6261 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -104,13 +104,12 @@ const setup = function (
 
     ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
     if (
-      options?.assetCapture?.captureOrigins &&
-      Array.isArray(options.assetCapture.captureOrigins)
+      options?.assetCapture?.origins &&
+      Array.isArray(options.assetCapture.origins)
     ) {
-      options.assetCapture.captureOrigins =
-        options.assetCapture.captureOrigins.map((origin) =>
-          origin.replace(/\{SERVER_URL\}/g, ctx.serverURL),
-        );
+      options.assetCapture.origins = options.assetCapture.origins.map(
+        (origin) => origin.replace(/\{SERVER_URL\}/g, ctx.serverURL),
+      );
     }
     await injectRecordScript(ctx.page.mainFrame(), options);
   });
@@ -131,7 +130,7 @@ const setup = function (
 describe('asset caching', function (this: ISuite) {
   jest.setTimeout(100_000);
 
-  describe('captureObjectURLs: true with incremental snapshots', function (this: ISuite) {
+  describe('objectURLs: true with incremental snapshots', function (this: ISuite) {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -142,8 +141,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureObjectURLs: true,
-          captureOrigins: false,
+          objectURLs: true,
+          origins: false,
         },
       },
     );
@@ -243,7 +242,7 @@ describe('asset caching', function (this: ISuite) {
     });
   });
 
-  describe('captureObjectURLs: true with fullSnapshot', function (this: ISuite) {
+  describe('objectURLs: true with fullSnapshot', function (this: ISuite) {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -284,8 +283,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureObjectURLs: true,
-          captureOrigins: false,
+          objectURLs: true,
+          origins: false,
         },
       },
     );
@@ -318,7 +317,7 @@ describe('asset caching', function (this: ISuite) {
       expect(events[events.length - 1]).toMatchObject(expected);
     });
   });
-  describe('captureObjectURLs: false', () => {
+  describe('objectURLs: false', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -329,8 +328,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureObjectURLs: false,
-          captureOrigins: false,
+          objectURLs: false,
+          origins: false,
         },
       },
     );
@@ -396,7 +395,7 @@ describe('asset caching', function (this: ISuite) {
       );
     });
   });
-  describe('captureOrigins: false', () => {
+  describe('origins: false', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -409,8 +408,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureOrigins: false,
-          captureObjectURLs: false,
+          origins: false,
+          objectURLs: false,
         },
       },
     );
@@ -428,7 +427,7 @@ describe('asset caching', function (this: ISuite) {
       );
     });
   });
-  describe('captureOrigins: []', () => {
+  describe('origins: []', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -441,8 +440,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureOrigins: [],
-          captureObjectURLs: false,
+          origins: [],
+          objectURLs: false,
         },
       },
     );
@@ -460,7 +459,7 @@ describe('asset caching', function (this: ISuite) {
       );
     });
   });
-  describe('captureOrigins: true', () => {
+  describe('origins: true', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -473,8 +472,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureOrigins: true,
-          captureObjectURLs: false,
+          origins: true,
+          objectURLs: false,
         },
       },
     );
@@ -496,7 +495,7 @@ describe('asset caching', function (this: ISuite) {
     });
   });
 
-  describe('captureOrigins: true with invalid urls', () => {
+  describe('origins: true with invalid urls', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -510,8 +509,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureOrigins: true,
-          captureObjectURLs: false,
+          origins: true,
+          objectURLs: false,
         },
       },
     );
@@ -566,7 +565,7 @@ describe('asset caching', function (this: ISuite) {
     });
   });
 
-  describe('captureOrigins: ["http://localhost:xxxxx/"]', () => {
+  describe('origins: ["http://localhost:xxxxx/"]', () => {
     const ctx: ISuite = setup.call(
       this,
       `
@@ -580,8 +579,8 @@ describe('asset caching', function (this: ISuite) {
       `,
       {
         assetCapture: {
-          captureOrigins: ['{SERVER_URL}'],
-          captureObjectURLs: false,
+          origins: ['{SERVER_URL}'],
+          objectURLs: false,
         },
       },
     );
@@ -624,5 +623,35 @@ describe('asset caching', function (this: ISuite) {
         }),
       );
     });
+
+    it('add recorded origins to meta event', async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Meta,
+          data: {
+            assetCapture: {
+              origins: ['{SERVER_URL}'],
+              objectURLs: false,
+            },
+          },
+        }),
+      );
+    });
   });
+
+  test.todo('should support video elements');
+  test.todo('should support audio elements');
+  test.todo('should support embed elements');
+  test.todo('should support source elements');
+  test.todo('should support track elements');
+  test.todo('should support input#type=image elements');
+  test.todo('should support img srcset');
 });
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 2f6e47cb40..92200402b2 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -41,6 +41,7 @@ export type metaEvent = {
     href: string;
     width: number;
     height: number;
+    assetCapture?: assetCaptureParam;
   };
 };
 
@@ -60,6 +61,22 @@ export type pluginEvent<T = unknown> = {
   };
 };
 
+export type assetCaptureParam = {
+  /**
+   * Captures object URLs (blobs, files, media sources).
+   * More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
+   */
+  objectURLs: boolean;
+  /**
+   * Allowlist of origins to capture object URLs from.
+   * [origin, origin, ...] to capture from specific origins.
+   *   e.g. ['https://example.com', 'https://www.example.com']
+   * Set to `true` capture from all origins.
+   * Set to `false` or `[]` to disable capturing from any origin apart from object URLs.
+   */
+  origins: string[] | true | false;
+};
+
 export type assetEvent = {
   type: EventType.Asset;
   data: assetParam;

From 645c75a57549467ffb23abd7bb9d9461bc8e63f0 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 17:26:07 +0100
Subject: [PATCH 020/102] Add asset capture config to meta events

---
 packages/rrweb/src/record/index.ts       |  1 +
 packages/rrweb/test/integration.test.ts  | 10 +++++-----
 packages/rrweb/test/record/asset.test.ts |  5 ++++-
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 21b0e24f83..1410c45396 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -363,6 +363,7 @@ function record<T = eventWithTime>(
           href: window.location.href,
           width: getWindowWidth(),
           height: getWindowHeight(),
+          assetCapture,
         },
       },
       isCheckout,
diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index 0525bd04e8..24953ac938 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -13,8 +13,8 @@ import {
   ISuite,
 } from './utils';
 import type { recordOptions } from '../src/types';
-import { eventWithTime, EventType, RecordPlugin } from '@rrweb/types';
-import { visitSnapshot, NodeType } from 'rrweb-snapshot';
+import { eventWithTime, NodeType, EventType, RecordPlugin } from '@rrweb/types';
+import { visitSnapshot } from 'rrweb-snapshot';
 
 describe('record integration tests', function (this: ISuite) {
   jest.setTimeout(10_000);
@@ -838,7 +838,7 @@ describe('record integration tests', function (this: ISuite) {
     page.setContent(
       getHtml.call(this, 'image-blob-url.html', {
         inlineImages: true,
-        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+        assetCapture: { objectURLs: false, origins: false },
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
@@ -858,7 +858,7 @@ describe('record integration tests', function (this: ISuite) {
     await page.setContent(
       getHtml.call(this, 'frame-image-blob-url.html', {
         inlineImages: true,
-        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+        assetCapture: { objectURLs: false, origins: false },
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
@@ -878,7 +878,7 @@ describe('record integration tests', function (this: ISuite) {
     await page.setContent(
       getHtml.call(this, 'frame2.html', {
         inlineImages: true,
-        assetCapture: { captureObjectURLs: false, captureOrigins: false },
+        assetCapture: { objectURLs: false, origins: false },
       }),
     );
     await page.waitForSelector('iframe'); // wait for iframe to get added
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index 8aa54d6261..956ef2958e 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -637,8 +637,11 @@ describe('asset caching', function (this: ISuite) {
         expect.objectContaining({
           type: EventType.Meta,
           data: {
+            href: expect.any(String),
+            width: expect.any(Number),
+            height: expect.any(Number),
             assetCapture: {
-              origins: ['{SERVER_URL}'],
+              origins: [ctx.serverURL],
               objectURLs: false,
             },
           },

From 046d179a1b22bfc809dc8edbd0f310784623ac64 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 17:28:09 +0100
Subject: [PATCH 021/102] NodeType was moved to @rrweb/types

---
 packages/rrdom-nodejs/test/document-nodejs.test.ts | 2 +-
 packages/rrdom/test/document.test.ts               | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/rrdom-nodejs/test/document-nodejs.test.ts b/packages/rrdom-nodejs/test/document-nodejs.test.ts
index ba3c6144d8..ac6088165f 100644
--- a/packages/rrdom-nodejs/test/document-nodejs.test.ts
+++ b/packages/rrdom-nodejs/test/document-nodejs.test.ts
@@ -3,7 +3,7 @@
  */
 import * as fs from 'fs';
 import * as path from 'path';
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@rrweb/types';
 import {
   RRCanvasElement,
   RRCDATASection,
diff --git a/packages/rrdom/test/document.test.ts b/packages/rrdom/test/document.test.ts
index ad3b02134e..91ccb1ece1 100644
--- a/packages/rrdom/test/document.test.ts
+++ b/packages/rrdom/test/document.test.ts
@@ -1,7 +1,7 @@
 /**
  * @jest-environment jsdom
  */
-import { NodeType as RRNodeType } from 'rrweb-snapshot';
+import { NodeType as RRNodeType } from '@rrweb/types';
 import {
   BaseRRDocumentImpl,
   BaseRRDocumentTypeImpl,

From d3694f780dd036d7b84efc7da71c4bb4e2fc924e Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:36:42 +0100
Subject: [PATCH 022/102] NodeType was moved to @rrweb/types

---
 packages/rrdom-nodejs/package.json               |  3 ++-
 packages/rrdom/test/diff.test.ts                 |  7 +++++--
 packages/rrdom/test/virtual-dom.test.ts          |  4 ++--
 packages/rrweb-snapshot/package.json             |  2 +-
 packages/rrweb-snapshot/test/integration.test.ts | 12 +++++++-----
 packages/rrweb-snapshot/test/rebuild.test.ts     |  2 +-
 packages/rrweb-snapshot/test/snapshot.test.ts    |  2 +-
 packages/rrweb-snapshot/test/utils.test.ts       |  3 +--
 8 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/packages/rrdom-nodejs/package.json b/packages/rrdom-nodejs/package.json
index 09a69a7ffd..51a82993df 100644
--- a/packages/rrdom-nodejs/package.json
+++ b/packages/rrdom-nodejs/package.json
@@ -49,7 +49,8 @@
     "cssstyle": "^2.3.0",
     "nwsapi": "^2.2.0",
     "rrdom": "^2.0.0-alpha.13",
-    "rrweb-snapshot": "^2.0.0-alpha.13"
+    "rrweb-snapshot": "^2.0.0-alpha.13",
+    "@rrweb/types": "^2.0.0-alpha.13"
   },
   "browserslist": [
     "supports es6-class"
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index 551a76fe0f..77e5717904 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -22,14 +22,17 @@ import {
 import type { IRRElement, IRRNode } from '../src/document';
 import { Replayer } from 'rrweb';
 import type {
-  NodeType as RRNodeType,
   serializedNodeWithId,
   eventWithTime,
   canvasMutationData,
   styleDeclarationData,
   styleSheetRuleData,
 } from '@rrweb/types';
-import { EventType, IncrementalSource } from '@rrweb/types';
+import {
+  NodeType as RRNodeType,
+  EventType,
+  IncrementalSource,
+} from '@rrweb/types';
 import { compileTSCode } from './utils';
 
 const elementSn = {
diff --git a/packages/rrdom/test/virtual-dom.test.ts b/packages/rrdom/test/virtual-dom.test.ts
index b99aca5ae4..701ec1f207 100644
--- a/packages/rrdom/test/virtual-dom.test.ts
+++ b/packages/rrdom/test/virtual-dom.test.ts
@@ -5,17 +5,17 @@ import * as fs from 'fs';
 import * as path from 'path';
 import * as puppeteer from 'puppeteer';
 import { JSDOM } from 'jsdom';
+import { Mirror } from 'rrweb-snapshot';
 import {
   cdataNode,
   commentNode,
   documentNode,
   documentTypeNode,
   elementNode,
-  Mirror,
   NodeType,
   NodeType as RRNodeType,
   textNode,
-} from 'rrweb-snapshot';
+} from '@rrweb/types';
 import {
   buildFromDom,
   buildFromNode,
diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index 097264fd58..cc66419722 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -43,7 +43,7 @@
   },
   "homepage": "https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot#readme",
   "dependencies": {
-    "@rrweb/types": "^2.0.0-alpha.8"
+    "@rrweb/types": "^2.0.0-alpha.11"
   },
   "devDependencies": {
     "@types/chai": "^4.1.4",
diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts
index dcc6a3ec0b..10a3a38eed 100644
--- a/packages/rrweb-snapshot/test/integration.test.ts
+++ b/packages/rrweb-snapshot/test/integration.test.ts
@@ -4,10 +4,12 @@ import * as http from 'http';
 import * as url from 'url';
 import * as puppeteer from 'puppeteer';
 import * as rollup from 'rollup';
-import * as typescript from 'rollup-plugin-typescript2';
-import * as assert from 'assert';
+import resolve from '@rollup/plugin-node-resolve';
+import typescript from 'rollup-plugin-typescript2';
+import assert from 'assert';
 import { waitForRAF } from './utils';
 
+const _resolve = resolve as unknown as () => rollup.Plugin;
 const _typescript = typescript as unknown as () => rollup.Plugin;
 
 const htmlFolder = path.join(__dirname, 'html');
@@ -74,7 +76,7 @@ describe('integration tests', function (this: ISuite) {
 
     const bundle = await rollup.rollup({
       input: path.resolve(__dirname, '../src/index.ts'),
-      plugins: [_typescript()],
+      plugins: [_resolve(), _typescript()],
     });
     const {
       output: [{ code: _code }],
@@ -341,7 +343,7 @@ describe('iframe integration tests', function (this: ISuite) {
 
     const bundle = await rollup.rollup({
       input: path.resolve(__dirname, '../src/index.ts'),
-      plugins: [_typescript()],
+      plugins: [_resolve(), _typescript()],
     });
     const {
       output: [{ code: _code }],
@@ -389,7 +391,7 @@ describe('shadow DOM integration tests', function (this: ISuite) {
 
     const bundle = await rollup.rollup({
       input: path.resolve(__dirname, '../src/index.ts'),
-      plugins: [_typescript()],
+      plugins: [_resolve(), _typescript()],
     });
     const {
       output: [{ code: _code }],
diff --git a/packages/rrweb-snapshot/test/rebuild.test.ts b/packages/rrweb-snapshot/test/rebuild.test.ts
index 097ff0989a..8287285596 100644
--- a/packages/rrweb-snapshot/test/rebuild.test.ts
+++ b/packages/rrweb-snapshot/test/rebuild.test.ts
@@ -8,7 +8,7 @@ import {
   buildNodeWithSN,
   createCache,
 } from '../src/rebuild';
-import { NodeType } from '../src/types';
+import { NodeType } from '@rrweb/types';
 import { createMirror, Mirror } from '../src/utils';
 
 function getDuration(hrtime: [number, number]) {
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index faf23c9c86..325bfff9a5 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -7,7 +7,7 @@ import {
   serializeNodeWithId,
   _isBlockedElement,
 } from '../src/snapshot';
-import { serializedNodeWithId, elementNode } from '../src/types';
+import { serializedNodeWithId, elementNode } from '@rrweb/types';
 import { Mirror } from '../src/utils';
 
 describe('absolute url to stylesheet', () => {
diff --git a/packages/rrweb-snapshot/test/utils.test.ts b/packages/rrweb-snapshot/test/utils.test.ts
index afbdda2f42..051853fea0 100644
--- a/packages/rrweb-snapshot/test/utils.test.ts
+++ b/packages/rrweb-snapshot/test/utils.test.ts
@@ -1,9 +1,8 @@
 /**
  * @jest-environment jsdom
  */
-import { NodeType, serializedNode } from '../src/types';
 import { extractFileExtension, isNodeMetaEqual } from '../src/utils';
-import { serializedNodeWithId } from 'rrweb-snapshot';
+import { NodeType, serializedNode, serializedNodeWithId } from '@rrweb/types';
 
 describe('utils', () => {
   describe('isNodeMetaEqual()', () => {

From 66e13e0bc9da2918f52595d94a4ca002d406acc8 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:40:14 +0100
Subject: [PATCH 023/102] Upgrade jest and rollup in rrweb-snapshot

---
 packages/rrweb-snapshot/jest.config.js |   12 +-
 packages/rrweb-snapshot/jest.setup.ts  |    3 +
 packages/rrweb-snapshot/package.json   |   16 +-
 packages/rrweb-snapshot/tsconfig.json  |    1 +
 yarn.lock                              | 1358 +++++++++++++-----------
 5 files changed, 755 insertions(+), 635 deletions(-)
 create mode 100644 packages/rrweb-snapshot/jest.setup.ts

diff --git a/packages/rrweb-snapshot/jest.config.js b/packages/rrweb-snapshot/jest.config.js
index e6cdf180ee..da04b18de6 100644
--- a/packages/rrweb-snapshot/jest.config.js
+++ b/packages/rrweb-snapshot/jest.config.js
@@ -1,6 +1,16 @@
-/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
 export default {
   preset: 'ts-jest',
   testEnvironment: 'node',
   testMatch: ['**/**.test.ts'],
+  setupFiles: ['./jest.setup.ts'],
+  /**
+   * Keeps old (pre-jest 29) snapshot format
+   * its a bit ugly and harder to read than the new format,
+   * so we might want to remove this in its own PR
+   */
+  snapshotFormat: {
+    escapeString: true,
+    printBasicPrototype: true,
+  },
 };
diff --git a/packages/rrweb-snapshot/jest.setup.ts b/packages/rrweb-snapshot/jest.setup.ts
new file mode 100644
index 0000000000..7c97c034f2
--- /dev/null
+++ b/packages/rrweb-snapshot/jest.setup.ts
@@ -0,0 +1,3 @@
+import { TextEncoder, TextDecoder } from 'util';
+
+Object.assign(global, { TextDecoder, TextEncoder });
diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index cc66419722..5229e9e364 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -12,6 +12,7 @@
     "bundle": "rollup --config",
     "bundle:es-only": "cross-env ES_ONLY=true rollup --config",
     "dev": "yarn bundle:es-only --watch & yarn typings -w",
+    "check-types": "tsc --noEmit",
     "typings": "tsc -d --declarationDir typings --emitDeclarationOnly",
     "prepublish": "yarn typings && yarn bundle",
     "lint": "yarn eslint src"
@@ -47,19 +48,20 @@
   },
   "devDependencies": {
     "@types/chai": "^4.1.4",
-    "@types/jest": "^27.0.2",
-    "@types/jsdom": "^20.0.0",
+    "@types/jest": "^29.5.10",
+    "@types/jsdom": "^21.1.6",
     "@types/node": "^18.15.11",
     "@types/puppeteer": "^1.12.4",
     "cross-env": "^5.2.0",
-    "jest": "^27.2.4",
+    "jest": "^29.7.0",
     "jest-snapshot": "^23.6.0",
-    "jsdom": "^16.4.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "jsdom": "^23.0.0",
     "puppeteer": "^17.1.3",
-    "rollup": "^2.46.0",
+    "rollup": "^2.47.0",
     "rollup-plugin-terser": "^7.0.2",
-    "rollup-plugin-typescript2": "^0.34.1",
-    "ts-jest": "^27.0.5",
+    "rollup-plugin-typescript2": "^0.36.0",
+    "ts-jest": "^29.1.1",
     "ts-node": "^7.0.1",
     "tslib": "^2.5.3",
     "typescript": "^4.7.3"
diff --git a/packages/rrweb-snapshot/tsconfig.json b/packages/rrweb-snapshot/tsconfig.json
index 035c8baa15..000278dddc 100644
--- a/packages/rrweb-snapshot/tsconfig.json
+++ b/packages/rrweb-snapshot/tsconfig.json
@@ -4,6 +4,7 @@
     "module": "ESNext",
     "target": "ES6",
     "moduleResolution": "Node",
+    "esModuleInterop": true,
     "noImplicitAny": true,
     "strictNullChecks": true,
     "removeComments": true,
diff --git a/yarn.lock b/yarn.lock
index 57cdd4e2b5..f830c341c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -71,7 +71,7 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730"
   integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
 
-"@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0":
+"@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
   version "7.15.5"
   resolved "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz"
   integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==
@@ -636,7 +636,7 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5", "@babel/parser@^7.7.2":
+"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5":
   version "7.15.7"
   resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz"
   integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==
@@ -850,7 +850,7 @@
     "@babel/parser" "^7.22.5"
     "@babel/types" "^7.22.5"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.15.4", "@babel/traverse@^7.7.2":
+"@babel/traverse@^7.15.4", "@babel/traverse@^7.7.2":
   version "7.15.4"
   resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz"
   integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==
@@ -2174,18 +2174,6 @@
   resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz"
   integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
 
-"@jest/console@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/console/-/console-27.2.4.tgz"
-  integrity sha512-94znCKynPZpDpYHQ6esRJSc11AmONrVkBOBZiD7S+bSubHhrUfbS95EY5HIOxhm4PQO7cnvZkL3oJcY0oMA+Wg==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    jest-message-util "^27.2.4"
-    jest-util "^27.2.4"
-    slash "^3.0.0"
-
 "@jest/console@^27.5.1":
   version "27.5.1"
   resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz"
@@ -2210,39 +2198,17 @@
     jest-util "^29.6.2"
     slash "^3.0.0"
 
-"@jest/core@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/core/-/core-27.2.4.tgz"
-  integrity sha512-UNQLyy+rXoojNm2MGlapgzWhZD1CT1zcHZQYeiD0xE7MtJfC19Q6J5D/Lm2l7i4V97T30usKDoEtjI8vKwWcLg==
+"@jest/console@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc"
+  integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==
   dependencies:
-    "@jest/console" "^27.2.4"
-    "@jest/reporters" "^27.2.4"
-    "@jest/test-result" "^27.2.4"
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
+    "@jest/types" "^29.6.3"
     "@types/node" "*"
-    ansi-escapes "^4.2.1"
     chalk "^4.0.0"
-    emittery "^0.8.1"
-    exit "^0.1.2"
-    graceful-fs "^4.2.4"
-    jest-changed-files "^27.2.4"
-    jest-config "^27.2.4"
-    jest-haste-map "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-regex-util "^27.0.6"
-    jest-resolve "^27.2.4"
-    jest-resolve-dependencies "^27.2.4"
-    jest-runner "^27.2.4"
-    jest-runtime "^27.2.4"
-    jest-snapshot "^27.2.4"
-    jest-util "^27.2.4"
-    jest-validate "^27.2.4"
-    jest-watcher "^27.2.4"
-    micromatch "^4.0.4"
-    rimraf "^3.0.0"
+    jest-message-util "^29.7.0"
+    jest-util "^29.7.0"
     slash "^3.0.0"
-    strip-ansi "^6.0.0"
 
 "@jest/core@^27.5.1":
   version "27.5.1"
@@ -2312,15 +2278,39 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.2.4.tgz"
-  integrity sha512-wkuui5yr3SSQW0XD0Qm3TATUbL/WE3LDEM3ulC+RCQhMf2yxhci8x7svGkZ4ivJ6Pc94oOzpZ6cdHBAMSYd1ew==
+"@jest/core@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f"
+  integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==
   dependencies:
-    "@jest/fake-timers" "^27.2.4"
-    "@jest/types" "^27.2.4"
+    "@jest/console" "^29.7.0"
+    "@jest/reporters" "^29.7.0"
+    "@jest/test-result" "^29.7.0"
+    "@jest/transform" "^29.7.0"
+    "@jest/types" "^29.6.3"
     "@types/node" "*"
-    jest-mock "^27.2.4"
+    ansi-escapes "^4.2.1"
+    chalk "^4.0.0"
+    ci-info "^3.2.0"
+    exit "^0.1.2"
+    graceful-fs "^4.2.9"
+    jest-changed-files "^29.7.0"
+    jest-config "^29.7.0"
+    jest-haste-map "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-regex-util "^29.6.3"
+    jest-resolve "^29.7.0"
+    jest-resolve-dependencies "^29.7.0"
+    jest-runner "^29.7.0"
+    jest-runtime "^29.7.0"
+    jest-snapshot "^29.7.0"
+    jest-util "^29.7.0"
+    jest-validate "^29.7.0"
+    jest-watcher "^29.7.0"
+    micromatch "^4.0.4"
+    pretty-format "^29.7.0"
+    slash "^3.0.0"
+    strip-ansi "^6.0.0"
 
 "@jest/environment@^27.5.1":
   version "27.5.1"
@@ -2342,6 +2332,16 @@
     "@types/node" "*"
     jest-mock "^29.6.2"
 
+"@jest/environment@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7"
+  integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==
+  dependencies:
+    "@jest/fake-timers" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
+    jest-mock "^29.7.0"
+
 "@jest/expect-utils@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.2.tgz#1b97f290d0185d264dd9fdec7567a14a38a90534"
@@ -2349,6 +2349,13 @@
   dependencies:
     jest-get-type "^29.4.3"
 
+"@jest/expect-utils@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6"
+  integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==
+  dependencies:
+    jest-get-type "^29.6.3"
+
 "@jest/expect@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28"
@@ -2357,17 +2364,13 @@
     expect "^29.6.2"
     jest-snapshot "^29.6.2"
 
-"@jest/fake-timers@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.4.tgz"
-  integrity sha512-cs/TzvwWUM7kAA6Qm/890SK6JJ2pD5RfDNM3SSEom6BmdyV6OiWP1qf/pqo6ts6xwpcM36oN0wSEzcZWc6/B6w==
+"@jest/expect@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2"
+  integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==
   dependencies:
-    "@jest/types" "^27.2.4"
-    "@sinonjs/fake-timers" "^8.0.1"
-    "@types/node" "*"
-    jest-message-util "^27.2.4"
-    jest-mock "^27.2.4"
-    jest-util "^27.2.4"
+    expect "^29.7.0"
+    jest-snapshot "^29.7.0"
 
 "@jest/fake-timers@^27.5.1":
   version "27.5.1"
@@ -2393,14 +2396,17 @@
     jest-mock "^29.6.2"
     jest-util "^29.6.2"
 
-"@jest/globals@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.2.4.tgz"
-  integrity sha512-DRsRs5dh0i+fA9mGHylTU19+8fhzNJoEzrgsu+zgJoZth3x8/0juCQ8nVVdW1er4Cqifb/ET7/hACYVPD0dBEA==
+"@jest/fake-timers@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565"
+  integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==
   dependencies:
-    "@jest/environment" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    expect "^27.2.4"
+    "@jest/types" "^29.6.3"
+    "@sinonjs/fake-timers" "^10.0.2"
+    "@types/node" "*"
+    jest-message-util "^29.7.0"
+    jest-mock "^29.7.0"
+    jest-util "^29.7.0"
 
 "@jest/globals@^27.5.1":
   version "27.5.1"
@@ -2421,35 +2427,15 @@
     "@jest/types" "^29.6.1"
     jest-mock "^29.6.2"
 
-"@jest/reporters@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.4.tgz"
-  integrity sha512-LHeSdDnDZkDnJ8kvnjcqV8P1Yv/32yL4d4XfR5gBiy3xGO0onwll1QEbvtW96fIwhx2nejug0GTaEdNDoyr3fQ==
+"@jest/globals@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d"
+  integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==
   dependencies:
-    "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^27.2.4"
-    "@jest/test-result" "^27.2.4"
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    chalk "^4.0.0"
-    collect-v8-coverage "^1.0.0"
-    exit "^0.1.2"
-    glob "^7.1.2"
-    graceful-fs "^4.2.4"
-    istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-instrument "^4.0.3"
-    istanbul-lib-report "^3.0.0"
-    istanbul-lib-source-maps "^4.0.0"
-    istanbul-reports "^3.0.2"
-    jest-haste-map "^27.2.4"
-    jest-resolve "^27.2.4"
-    jest-util "^27.2.4"
-    jest-worker "^27.2.4"
-    slash "^3.0.0"
-    source-map "^0.6.0"
-    string-length "^4.0.1"
-    terminal-link "^2.0.0"
-    v8-to-istanbul "^8.1.0"
+    "@jest/environment" "^29.7.0"
+    "@jest/expect" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    jest-mock "^29.7.0"
 
 "@jest/reporters@^27.5.1":
   version "27.5.1"
@@ -2512,6 +2498,36 @@
     strip-ansi "^6.0.0"
     v8-to-istanbul "^9.0.1"
 
+"@jest/reporters@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7"
+  integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==
+  dependencies:
+    "@bcoe/v8-coverage" "^0.2.3"
+    "@jest/console" "^29.7.0"
+    "@jest/test-result" "^29.7.0"
+    "@jest/transform" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@jridgewell/trace-mapping" "^0.3.18"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    collect-v8-coverage "^1.0.0"
+    exit "^0.1.2"
+    glob "^7.1.3"
+    graceful-fs "^4.2.9"
+    istanbul-lib-coverage "^3.0.0"
+    istanbul-lib-instrument "^6.0.0"
+    istanbul-lib-report "^3.0.0"
+    istanbul-lib-source-maps "^4.0.0"
+    istanbul-reports "^3.1.3"
+    jest-message-util "^29.7.0"
+    jest-util "^29.7.0"
+    jest-worker "^29.7.0"
+    slash "^3.0.0"
+    string-length "^4.0.1"
+    strip-ansi "^6.0.0"
+    v8-to-istanbul "^9.0.1"
+
 "@jest/schemas@^29.6.0":
   version "29.6.0"
   resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040"
@@ -2519,14 +2535,12 @@
   dependencies:
     "@sinclair/typebox" "^0.27.8"
 
-"@jest/source-map@^27.0.6":
-  version "27.0.6"
-  resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz"
-  integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==
+"@jest/schemas@^29.6.3":
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
+  integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
   dependencies:
-    callsites "^3.0.0"
-    graceful-fs "^4.2.4"
-    source-map "^0.6.0"
+    "@sinclair/typebox" "^0.27.8"
 
 "@jest/source-map@^27.5.1":
   version "27.5.1"
@@ -2546,15 +2560,14 @@
     callsites "^3.0.0"
     graceful-fs "^4.2.9"
 
-"@jest/test-result@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.4.tgz"
-  integrity sha512-eU+PRo0+lIS01b0dTmMdVZ0TtcRSxEaYquZTRFMQz6CvsehGhx9bRzi9Zdw6VROviJyv7rstU+qAMX5pNBmnfQ==
+"@jest/source-map@^29.6.3":
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4"
+  integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==
   dependencies:
-    "@jest/console" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    collect-v8-coverage "^1.0.0"
+    "@jridgewell/trace-mapping" "^0.3.18"
+    callsites "^3.0.0"
+    graceful-fs "^4.2.9"
 
 "@jest/test-result@^27.5.1":
   version "27.5.1"
@@ -2576,15 +2589,15 @@
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.4.tgz"
-  integrity sha512-fpk5eknU3/DXE2QCCG1wv/a468+cfPo3Asu6d6yUtM9LOPh709ubZqrhuUOYfM8hXMrIpIdrv1CdCrWWabX0rQ==
+"@jest/test-result@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c"
+  integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==
   dependencies:
-    "@jest/test-result" "^27.2.4"
-    graceful-fs "^4.2.4"
-    jest-haste-map "^27.2.4"
-    jest-runtime "^27.2.4"
+    "@jest/console" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    collect-v8-coverage "^1.0.0"
 
 "@jest/test-sequencer@^27.5.1":
   version "27.5.1"
@@ -2606,26 +2619,15 @@
     jest-haste-map "^29.6.2"
     slash "^3.0.0"
 
-"@jest/transform@^27.2.4":
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.2.4.tgz"
-  integrity sha512-n5FlX2TH0oQGwyVDKPxdJ5nI2sO7TJBFe3u3KaAtt7TOiV4yL+Y+rSFDl+Ic5MpbiA/eqXmLAQxjnBmWgS2rEA==
+"@jest/test-sequencer@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce"
+  integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==
   dependencies:
-    "@babel/core" "^7.1.0"
-    "@jest/types" "^27.2.4"
-    babel-plugin-istanbul "^6.0.0"
-    chalk "^4.0.0"
-    convert-source-map "^1.4.0"
-    fast-json-stable-stringify "^2.0.0"
-    graceful-fs "^4.2.4"
-    jest-haste-map "^27.2.4"
-    jest-regex-util "^27.0.6"
-    jest-util "^27.2.4"
-    micromatch "^4.0.4"
-    pirates "^4.0.1"
+    "@jest/test-result" "^29.7.0"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^29.7.0"
     slash "^3.0.0"
-    source-map "^0.6.1"
-    write-file-atomic "^3.0.0"
 
 "@jest/transform@^27.5.1":
   version "27.5.1"
@@ -2669,6 +2671,27 @@
     slash "^3.0.0"
     write-file-atomic "^4.0.2"
 
+"@jest/transform@^29.7.0":
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c"
+  integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==
+  dependencies:
+    "@babel/core" "^7.11.6"
+    "@jest/types" "^29.6.3"
+    "@jridgewell/trace-mapping" "^0.3.18"
+    babel-plugin-istanbul "^6.1.1"
+    chalk "^4.0.0"
+    convert-source-map "^2.0.0"
+    fast-json-stable-stringify "^2.1.0"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^29.7.0"
+    jest-regex-util "^29.6.3"
+    jest-util "^29.7.0"
+    micromatch "^4.0.4"
+    pirates "^4.0.4"
+    slash "^3.0.0"
+    write-file-atomic "^4.0.2"
+
 "@jest/types@^27.2.4":
   version "27.2.4"
   resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.4.tgz"
@@ -2703,6 +2726,18 @@
     "@types/yargs" "^17.0.8"
     chalk "^4.0.0"
 
+"@jest/types@^29.6.3":
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59"
+  integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
+  dependencies:
+    "@jest/schemas" "^29.6.3"
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^17.0.8"
+    chalk "^4.0.0"
+
 "@jridgewell/gen-mapping@^0.1.0":
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
@@ -3372,7 +3407,7 @@
     "@types/pixelmatch" "*"
     ssim.js "^3.1.1"
 
-"@types/jest@*", "@types/jest@^27.0.2":
+"@types/jest@*":
   version "27.0.2"
   resolved "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz"
   integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==
@@ -3396,6 +3431,14 @@
     expect "^29.0.0"
     pretty-format "^29.0.0"
 
+"@types/jest@^29.5.10":
+  version "29.5.10"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.10.tgz#a10fc5bab9e426081c12b2ef73d24d4f0c9b7f50"
+  integrity sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==
+  dependencies:
+    expect "^29.0.0"
+    pretty-format "^29.0.0"
+
 "@types/jsdom@^20.0.0":
   version "20.0.0"
   resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.0.tgz#4414fb629465167f8b7b3804b9e067bdd99f1791"
@@ -3405,6 +3448,15 @@
     "@types/tough-cookie" "*"
     parse5 "^7.0.0"
 
+"@types/jsdom@^21.1.6":
+  version "21.1.6"
+  resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.6.tgz#bcbc7b245787ea863f3da1ef19aa1dcfb9271a1b"
+  integrity sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==
+  dependencies:
+    "@types/node" "*"
+    "@types/tough-cookie" "*"
+    parse5 "^7.0.0"
+
 "@types/json-schema@^7.0.9":
   version "7.0.11"
   resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz"
@@ -4280,20 +4332,6 @@ b4a@^1.6.4:
   resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9"
   integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==
 
-babel-jest@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz"
-  integrity sha512-f24OmxyWymk5jfgLdlCMu4fTs4ldxFBIdn5sJdhvGC1m08rSkJ5hYbWkNmfBSvE/DjhCVNSHXepxsI6THGfGsg==
-  dependencies:
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/babel__core" "^7.1.14"
-    babel-plugin-istanbul "^6.0.0"
-    babel-preset-jest "^27.2.0"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.4"
-    slash "^3.0.0"
-
 babel-jest@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz"
@@ -4321,16 +4359,18 @@ babel-jest@^29.6.2:
     graceful-fs "^4.2.9"
     slash "^3.0.0"
 
-babel-plugin-istanbul@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz"
-  integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==
+babel-jest@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
+  integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==
   dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@istanbuljs/load-nyc-config" "^1.0.0"
-    "@istanbuljs/schema" "^0.1.2"
-    istanbul-lib-instrument "^4.0.0"
-    test-exclude "^6.0.0"
+    "@jest/transform" "^29.7.0"
+    "@types/babel__core" "^7.1.14"
+    babel-plugin-istanbul "^6.1.1"
+    babel-preset-jest "^29.6.3"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    slash "^3.0.0"
 
 babel-plugin-istanbul@^6.1.1:
   version "6.1.1"
@@ -4343,16 +4383,6 @@ babel-plugin-istanbul@^6.1.1:
     istanbul-lib-instrument "^5.0.4"
     test-exclude "^6.0.0"
 
-babel-plugin-jest-hoist@^27.2.0:
-  version "27.2.0"
-  resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz"
-  integrity sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==
-  dependencies:
-    "@babel/template" "^7.3.3"
-    "@babel/types" "^7.3.3"
-    "@types/babel__core" "^7.0.0"
-    "@types/babel__traverse" "^7.0.6"
-
 babel-plugin-jest-hoist@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz"
@@ -4373,6 +4403,16 @@ babel-plugin-jest-hoist@^29.5.0:
     "@types/babel__core" "^7.1.14"
     "@types/babel__traverse" "^7.0.6"
 
+babel-plugin-jest-hoist@^29.6.3:
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626"
+  integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==
+  dependencies:
+    "@babel/template" "^7.3.3"
+    "@babel/types" "^7.3.3"
+    "@types/babel__core" "^7.1.14"
+    "@types/babel__traverse" "^7.0.6"
+
 babel-plugin-macros@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
@@ -4400,14 +4440,6 @@ babel-preset-current-node-syntax@^1.0.0:
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
     "@babel/plugin-syntax-top-level-await" "^7.8.3"
 
-babel-preset-jest@^27.2.0:
-  version "27.2.0"
-  resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz"
-  integrity sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==
-  dependencies:
-    babel-plugin-jest-hoist "^27.2.0"
-    babel-preset-current-node-syntax "^1.0.0"
-
 babel-preset-jest@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz"
@@ -4424,6 +4456,14 @@ babel-preset-jest@^29.5.0:
     babel-plugin-jest-hoist "^29.5.0"
     babel-preset-current-node-syntax "^1.0.0"
 
+babel-preset-jest@^29.6.3:
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c"
+  integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==
+  dependencies:
+    babel-plugin-jest-hoist "^29.6.3"
+    babel-preset-current-node-syntax "^1.0.0"
+
 babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz"
@@ -5320,6 +5360,19 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     yaml "^1.10.0"
 
+create-jest@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320"
+  integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==
+  dependencies:
+    "@jest/types" "^29.6.3"
+    chalk "^4.0.0"
+    exit "^0.1.2"
+    graceful-fs "^4.2.9"
+    jest-config "^29.7.0"
+    jest-util "^29.7.0"
+    prompts "^2.0.1"
+
 create-require@^1.1.0:
   version "1.1.1"
   resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
@@ -5580,6 +5633,13 @@ cssstyle@^2.3.0:
   dependencies:
     cssom "~0.3.6"
 
+cssstyle@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a"
+  integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==
+  dependencies:
+    rrweb-cssom "^0.6.0"
+
 csstype@^3.0.11, csstype@^3.0.2:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
@@ -5640,6 +5700,14 @@ data-urls@^3.0.2:
     whatwg-mimetype "^3.0.0"
     whatwg-url "^11.0.0"
 
+data-urls@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde"
+  integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==
+  dependencies:
+    whatwg-mimetype "^4.0.0"
+    whatwg-url "^14.0.0"
+
 dataloader@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
@@ -5699,7 +5767,7 @@ decimal.js@^10.2.1:
   resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz"
   integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
 
-decimal.js@^10.4.2:
+decimal.js@^10.4.2, decimal.js@^10.4.3:
   version "10.4.3"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
   integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
@@ -5851,6 +5919,11 @@ diff-sequences@^29.4.3:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
   integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
 
+diff-sequences@^29.6.3:
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
+  integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
+
 diff@^3.1.0, diff@^3.2.0:
   version "3.5.0"
   resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz"
@@ -6910,18 +6983,6 @@ expand-range@^1.8.1:
   dependencies:
     fill-range "^2.1.0"
 
-expect@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/expect/-/expect-27.2.4.tgz"
-  integrity sha512-gOtuonQ8TCnbNNCSw2fhVzRf8EFYDII4nB5NmG4IEV0rbUnW1I5zXvoTntU4iicB/Uh0oZr20NGlOLdJiwsOZA==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    ansi-styles "^5.0.0"
-    jest-get-type "^27.0.6"
-    jest-matcher-utils "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-regex-util "^27.0.6"
-
 expect@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz"
@@ -6944,6 +7005,17 @@ expect@^29.0.0, expect@^29.6.2:
     jest-message-util "^29.6.2"
     jest-util "^29.6.2"
 
+expect@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc"
+  integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==
+  dependencies:
+    "@jest/expect-utils" "^29.7.0"
+    jest-get-type "^29.6.3"
+    jest-matcher-utils "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-util "^29.7.0"
+
 express@^4.16.4:
   version "4.17.1"
   resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz"
@@ -7972,6 +8044,13 @@ html-encoding-sniffer@^3.0.0:
   dependencies:
     whatwg-encoding "^2.0.0"
 
+html-encoding-sniffer@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448"
+  integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==
+  dependencies:
+    whatwg-encoding "^3.1.1"
+
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
@@ -8073,6 +8152,14 @@ https-proxy-agent@^7.0.0:
     agent-base "^7.0.2"
     debug "4"
 
+https-proxy-agent@^7.0.2:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b"
+  integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==
+  dependencies:
+    agent-base "^7.0.2"
+    debug "4"
+
 human-id@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/human-id/-/human-id-1.0.2.tgz#e654d4b2b0d8b07e45da9f6020d8af17ec0a5df3"
@@ -8780,16 +8867,6 @@ istanbul-lib-coverage@^3.2.0:
   resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz"
   integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
 
-istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz"
-  integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
-  dependencies:
-    "@babel/core" "^7.7.5"
-    "@istanbuljs/schema" "^0.1.2"
-    istanbul-lib-coverage "^3.0.0"
-    semver "^6.3.0"
-
 istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
   version "5.1.0"
   resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz"
@@ -8801,6 +8878,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
     istanbul-lib-coverage "^3.2.0"
     semver "^6.3.0"
 
+istanbul-lib-instrument@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz#71e87707e8041428732518c6fb5211761753fbdf"
+  integrity sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==
+  dependencies:
+    "@babel/core" "^7.12.3"
+    "@babel/parser" "^7.14.7"
+    "@istanbuljs/schema" "^0.1.2"
+    istanbul-lib-coverage "^3.2.0"
+    semver "^7.5.4"
+
 istanbul-lib-report@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz"
@@ -8819,14 +8907,6 @@ istanbul-lib-source-maps@^4.0.0:
     istanbul-lib-coverage "^3.0.0"
     source-map "^0.6.1"
 
-istanbul-reports@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz"
-  integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
-  dependencies:
-    html-escaper "^2.0.0"
-    istanbul-lib-report "^3.0.0"
-
 istanbul-reports@^3.1.3:
   version "3.1.3"
   resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz"
@@ -8840,15 +8920,6 @@ jed@1.1.1:
   resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
   integrity sha512-z35ZSEcXHxLW4yumw0dF6L464NT36vmx3wxJw8MDpraBcWuNVgUPZgPJKcu1HekNgwlMFNqol7i/IpSbjhqwqA==
 
-jest-changed-files@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.2.4.tgz"
-  integrity sha512-eeO1C1u4ex7pdTroYXezr+rbr957myyVoKGjcY4R1TJi3A+9v+4fu1Iv9J4eLq1bgFyT3O3iRWU9lZsEE7J72Q==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    execa "^5.0.0"
-    throat "^6.0.1"
-
 jest-changed-files@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz"
@@ -8866,30 +8937,14 @@ jest-changed-files@^29.5.0:
     execa "^5.0.0"
     p-limit "^3.1.0"
 
-jest-circus@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.4.tgz"
-  integrity sha512-TtheheTElrGjlsY9VxkzUU1qwIx05ItIusMVKnvNkMt4o/PeegLRcjq3Db2Jz0GGdBalJdbzLZBgeulZAJxJWA==
+jest-changed-files@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
+  integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==
   dependencies:
-    "@jest/environment" "^27.2.4"
-    "@jest/test-result" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    co "^4.6.0"
-    dedent "^0.7.0"
-    expect "^27.2.4"
-    is-generator-fn "^2.0.0"
-    jest-each "^27.2.4"
-    jest-matcher-utils "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-runtime "^27.2.4"
-    jest-snapshot "^27.2.4"
-    jest-util "^27.2.4"
-    pretty-format "^27.2.4"
-    slash "^3.0.0"
-    stack-utils "^2.0.3"
-    throat "^6.0.1"
+    execa "^5.0.0"
+    jest-util "^29.7.0"
+    p-limit "^3.1.0"
 
 jest-circus@^27.5.1:
   version "27.5.1"
@@ -8942,23 +8997,31 @@ jest-circus@^29.6.2:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-cli@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.4.tgz"
-  integrity sha512-4kpQQkg74HYLaXo3nzwtg4PYxSLgL7puz1LXHj5Tu85KmlIpxQFjRkXlx4V47CYFFIDoyl3rHA/cXOxUWyMpNg==
+jest-circus@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a"
+  integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==
   dependencies:
-    "@jest/core" "^27.2.4"
-    "@jest/test-result" "^27.2.4"
-    "@jest/types" "^27.2.4"
+    "@jest/environment" "^29.7.0"
+    "@jest/expect" "^29.7.0"
+    "@jest/test-result" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
     chalk "^4.0.0"
-    exit "^0.1.2"
-    graceful-fs "^4.2.4"
-    import-local "^3.0.2"
-    jest-config "^27.2.4"
-    jest-util "^27.2.4"
-    jest-validate "^27.2.4"
-    prompts "^2.0.1"
-    yargs "^16.2.0"
+    co "^4.6.0"
+    dedent "^1.0.0"
+    is-generator-fn "^2.0.0"
+    jest-each "^29.7.0"
+    jest-matcher-utils "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-runtime "^29.7.0"
+    jest-snapshot "^29.7.0"
+    jest-util "^29.7.0"
+    p-limit "^3.1.0"
+    pretty-format "^29.7.0"
+    pure-rand "^6.0.0"
+    slash "^3.0.0"
+    stack-utils "^2.0.3"
 
 jest-cli@^27.5.1:
   version "27.5.1"
@@ -8996,32 +9059,22 @@ jest-cli@^29.6.2:
     prompts "^2.0.1"
     yargs "^17.3.1"
 
-jest-config@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.2.4.tgz"
-  integrity sha512-tWy0UxhdzqiKyp4l5Vq4HxLyD+gH5td+GCF3c22/DJ0bYAOsMo+qi2XtbJI6oYMH5JOJQs9nLW/r34nvFCehjA==
+jest-cli@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995"
+  integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==
   dependencies:
-    "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    babel-jest "^27.2.4"
+    "@jest/core" "^29.7.0"
+    "@jest/test-result" "^29.7.0"
+    "@jest/types" "^29.6.3"
     chalk "^4.0.0"
-    deepmerge "^4.2.2"
-    glob "^7.1.1"
-    graceful-fs "^4.2.4"
-    is-ci "^3.0.0"
-    jest-circus "^27.2.4"
-    jest-environment-jsdom "^27.2.4"
-    jest-environment-node "^27.2.4"
-    jest-get-type "^27.0.6"
-    jest-jasmine2 "^27.2.4"
-    jest-regex-util "^27.0.6"
-    jest-resolve "^27.2.4"
-    jest-runner "^27.2.4"
-    jest-util "^27.2.4"
-    jest-validate "^27.2.4"
-    micromatch "^4.0.4"
-    pretty-format "^27.2.4"
+    create-jest "^29.7.0"
+    exit "^0.1.2"
+    import-local "^3.0.2"
+    jest-config "^29.7.0"
+    jest-util "^29.7.0"
+    jest-validate "^29.7.0"
+    yargs "^17.3.1"
 
 jest-config@^27.5.1:
   version "27.5.1"
@@ -9081,6 +9134,34 @@ jest-config@^29.6.2:
     slash "^3.0.0"
     strip-json-comments "^3.1.1"
 
+jest-config@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f"
+  integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==
+  dependencies:
+    "@babel/core" "^7.11.6"
+    "@jest/test-sequencer" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    babel-jest "^29.7.0"
+    chalk "^4.0.0"
+    ci-info "^3.2.0"
+    deepmerge "^4.2.2"
+    glob "^7.1.3"
+    graceful-fs "^4.2.9"
+    jest-circus "^29.7.0"
+    jest-environment-node "^29.7.0"
+    jest-get-type "^29.6.3"
+    jest-regex-util "^29.6.3"
+    jest-resolve "^29.7.0"
+    jest-runner "^29.7.0"
+    jest-util "^29.7.0"
+    jest-validate "^29.7.0"
+    micromatch "^4.0.4"
+    parse-json "^5.2.0"
+    pretty-format "^29.7.0"
+    slash "^3.0.0"
+    strip-json-comments "^3.1.1"
+
 jest-diff@^23.6.0:
   version "23.6.0"
   resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz"
@@ -9091,7 +9172,7 @@ jest-diff@^23.6.0:
     jest-get-type "^22.1.0"
     pretty-format "^23.6.0"
 
-jest-diff@^27.0.0, jest-diff@^27.2.4:
+jest-diff@^27.0.0:
   version "27.2.4"
   resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.4.tgz"
   integrity sha512-bLAVlDSCR3gqUPGv+4nzVpEXGsHh98HjUL7Vb2hVyyuBDoQmja8eJb0imUABsuxBeUVmf47taJSAd9nDrwWKEg==
@@ -9121,12 +9202,15 @@ jest-diff@^29.6.2:
     jest-get-type "^29.4.3"
     pretty-format "^29.6.2"
 
-jest-docblock@^27.0.6:
-  version "27.0.6"
-  resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz"
-  integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==
+jest-diff@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
+  integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==
   dependencies:
-    detect-newline "^3.0.0"
+    chalk "^4.0.0"
+    diff-sequences "^29.6.3"
+    jest-get-type "^29.6.3"
+    pretty-format "^29.7.0"
 
 jest-docblock@^27.5.1:
   version "27.5.1"
@@ -9142,16 +9226,12 @@ jest-docblock@^29.4.3:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.2.4.tgz"
-  integrity sha512-w9XVc+0EDBUTJS4xBNJ7N2JCcWItFd006lFjz77OarAQcQ10eFDBMrfDv2GBJMKlXe9aq0HrIIF51AXcZrRJyg==
+jest-docblock@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a"
+  integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==
   dependencies:
-    "@jest/types" "^27.2.4"
-    chalk "^4.0.0"
-    jest-get-type "^27.0.6"
-    jest-util "^27.2.4"
-    pretty-format "^27.2.4"
+    detect-newline "^3.0.0"
 
 jest-each@^27.5.1:
   version "27.5.1"
@@ -9175,18 +9255,16 @@ jest-each@^29.6.2:
     jest-util "^29.6.2"
     pretty-format "^29.6.2"
 
-jest-environment-jsdom@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.4.tgz"
-  integrity sha512-X70pTXFSypD7AIzKT1mLnDi5hP9w9mdTRcOGOmoDoBrNyNEg4rYm6d4LQWFLc9ps1VnMuDOkFSG0wjSNYGjkng==
+jest-each@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1"
+  integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==
   dependencies:
-    "@jest/environment" "^27.2.4"
-    "@jest/fake-timers" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    jest-mock "^27.2.4"
-    jest-util "^27.2.4"
-    jsdom "^16.6.0"
+    "@jest/types" "^29.6.3"
+    chalk "^4.0.0"
+    jest-get-type "^29.6.3"
+    jest-util "^29.7.0"
+    pretty-format "^29.7.0"
 
 jest-environment-jsdom@^27.5.1:
   version "27.5.1"
@@ -9215,17 +9293,19 @@ jest-environment-jsdom@^29.6.0:
     jest-util "^29.6.2"
     jsdom "^20.0.0"
 
-jest-environment-node@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.4.tgz"
-  integrity sha512-ZbVbFSnbzTvhLOIkqh5lcLuGCCFvtG4xTXIRPK99rV2KzQT3kNg16KZwfTnLNlIiWCE8do960eToeDfcqmpSAw==
+jest-environment-jsdom@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f"
+  integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==
   dependencies:
-    "@jest/environment" "^27.2.4"
-    "@jest/fake-timers" "^27.2.4"
-    "@jest/types" "^27.2.4"
+    "@jest/environment" "^29.7.0"
+    "@jest/fake-timers" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/jsdom" "^20.0.0"
     "@types/node" "*"
-    jest-mock "^27.2.4"
-    jest-util "^27.2.4"
+    jest-mock "^29.7.0"
+    jest-util "^29.7.0"
+    jsdom "^20.0.0"
 
 jest-environment-node@^27.5.1:
   version "27.5.1"
@@ -9251,6 +9331,18 @@ jest-environment-node@^29.6.2:
     jest-mock "^29.6.2"
     jest-util "^29.6.2"
 
+jest-environment-node@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376"
+  integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==
+  dependencies:
+    "@jest/environment" "^29.7.0"
+    "@jest/fake-timers" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
+    jest-mock "^29.7.0"
+    jest-util "^29.7.0"
+
 jest-get-type@^22.1.0:
   version "22.4.3"
   resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz"
@@ -9271,25 +9363,10 @@ jest-get-type@^29.4.3:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5"
   integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==
 
-jest-haste-map@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.4.tgz"
-  integrity sha512-bkJ4bT00T2K+1NZXbRcyKnbJ42I6QBvoDNMTAQQDBhaGNnZreiQKUNqax0e6hLTx7E75pKDeltVu3V1HAdu+YA==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    "@types/graceful-fs" "^4.1.2"
-    "@types/node" "*"
-    anymatch "^3.0.3"
-    fb-watchman "^2.0.0"
-    graceful-fs "^4.2.4"
-    jest-regex-util "^27.0.6"
-    jest-serializer "^27.0.6"
-    jest-util "^27.2.4"
-    jest-worker "^27.2.4"
-    micromatch "^4.0.4"
-    walker "^1.0.7"
-  optionalDependencies:
-    fsevents "^2.3.2"
+jest-get-type@^29.6.3:
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
+  integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
 
 jest-haste-map@^27.5.1:
   version "27.5.1"
@@ -9330,6 +9407,25 @@ jest-haste-map@^29.6.2:
   optionalDependencies:
     fsevents "^2.3.2"
 
+jest-haste-map@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104"
+  integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==
+  dependencies:
+    "@jest/types" "^29.6.3"
+    "@types/graceful-fs" "^4.1.3"
+    "@types/node" "*"
+    anymatch "^3.0.3"
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.2.9"
+    jest-regex-util "^29.6.3"
+    jest-util "^29.7.0"
+    jest-worker "^29.7.0"
+    micromatch "^4.0.4"
+    walker "^1.0.8"
+  optionalDependencies:
+    fsevents "^2.3.2"
+
 jest-image-snapshot@^6.2.0:
   version "6.2.0"
   resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-6.2.0.tgz#b6bfc3e1585ec14b2b0595daf537ae1b4d7f8d13"
@@ -9344,30 +9440,6 @@ jest-image-snapshot@^6.2.0:
     rimraf "^2.6.2"
     ssim.js "^3.1.1"
 
-jest-jasmine2@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.4.tgz"
-  integrity sha512-fcffjO/xLWLVnW2ct3No4EksxM5RyPwHDYu9QU+90cC+/eSMLkFAxS55vkqsxexOO5zSsZ3foVpMQcg/amSeIQ==
-  dependencies:
-    "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^27.2.4"
-    "@jest/source-map" "^27.0.6"
-    "@jest/test-result" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    co "^4.6.0"
-    expect "^27.2.4"
-    is-generator-fn "^2.0.0"
-    jest-each "^27.2.4"
-    jest-matcher-utils "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-runtime "^27.2.4"
-    jest-snapshot "^27.2.4"
-    jest-util "^27.2.4"
-    pretty-format "^27.2.4"
-    throat "^6.0.1"
-
 jest-jasmine2@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz"
@@ -9391,14 +9463,6 @@ jest-jasmine2@^27.5.1:
     pretty-format "^27.5.1"
     throat "^6.0.1"
 
-jest-leak-detector@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.4.tgz"
-  integrity sha512-SrcHWbe0EHg/bw2uBjVoHacTo5xosl068x2Q0aWsjr2yYuW2XwqrSkZV4lurUop0jhv1709ymG4or+8E4sH27Q==
-  dependencies:
-    jest-get-type "^27.0.6"
-    pretty-format "^27.2.4"
-
 jest-leak-detector@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz"
@@ -9415,6 +9479,14 @@ jest-leak-detector@^29.6.2:
     jest-get-type "^29.4.3"
     pretty-format "^29.6.2"
 
+jest-leak-detector@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728"
+  integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==
+  dependencies:
+    jest-get-type "^29.6.3"
+    pretty-format "^29.7.0"
+
 jest-matcher-utils@^23.6.0:
   version "23.6.0"
   resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz"
@@ -9434,16 +9506,6 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
     jest-get-type "^27.5.1"
     pretty-format "^27.5.1"
 
-jest-matcher-utils@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.4.tgz"
-  integrity sha512-nQeLfFAIPPkyhkDfifAPfP/U5wm1x0fLtAzqXZSSKckXDNuk2aaOfQiDYv1Mgf5GY6yOsxfUnvNm3dDjXM+BXw==
-  dependencies:
-    chalk "^4.0.0"
-    jest-diff "^27.2.4"
-    jest-get-type "^27.0.6"
-    pretty-format "^27.2.4"
-
 jest-matcher-utils@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535"
@@ -9454,6 +9516,16 @@ jest-matcher-utils@^29.6.2:
     jest-get-type "^29.4.3"
     pretty-format "^29.6.2"
 
+jest-matcher-utils@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12"
+  integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
+  dependencies:
+    chalk "^4.0.0"
+    jest-diff "^29.7.0"
+    jest-get-type "^29.6.3"
+    pretty-format "^29.7.0"
+
 jest-message-util@^23.4.0:
   version "23.4.0"
   resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz"
@@ -9465,21 +9537,6 @@ jest-message-util@^23.4.0:
     slash "^1.0.0"
     stack-utils "^1.0.1"
 
-jest-message-util@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.4.tgz"
-  integrity sha512-wbKT/BNGnBVB9nzi+IoaLkXt6fbSvqUxx+IYY66YFh96J3goY33BAaNG3uPqaw/Sh/FR9YpXGVDfd5DJdbh4nA==
-  dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@jest/types" "^27.2.4"
-    "@types/stack-utils" "^2.0.0"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.4"
-    micromatch "^4.0.4"
-    pretty-format "^27.2.4"
-    slash "^3.0.0"
-    stack-utils "^2.0.3"
-
 jest-message-util@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz"
@@ -9510,13 +9567,20 @@ jest-message-util@^29.6.2:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-mock@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.2.4.tgz"
-  integrity sha512-iVRU905rutaAoUcrt5Tm1JoHHWi24YabqEGXjPJI4tAyA6wZ7mzDi3GrZ+M7ebgWBqUkZE93GAx1STk7yCMIQA==
+jest-message-util@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
+  integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==
   dependencies:
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
+    "@babel/code-frame" "^7.12.13"
+    "@jest/types" "^29.6.3"
+    "@types/stack-utils" "^2.0.0"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    micromatch "^4.0.4"
+    pretty-format "^29.7.0"
+    slash "^3.0.0"
+    stack-utils "^2.0.3"
 
 jest-mock@^27.5.1:
   version "27.5.1"
@@ -9535,16 +9599,20 @@ jest-mock@^29.6.2:
     "@types/node" "*"
     jest-util "^29.6.2"
 
+jest-mock@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347"
+  integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==
+  dependencies:
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
+    jest-util "^29.7.0"
+
 jest-pnp-resolver@^1.2.2:
   version "1.2.2"
   resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz"
   integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
 
-jest-regex-util@^27.0.6:
-  version "27.0.6"
-  resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz"
-  integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==
-
 jest-regex-util@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz"
@@ -9555,14 +9623,10 @@ jest-regex-util@^29.4.3:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8"
   integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==
 
-jest-resolve-dependencies@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.4.tgz"
-  integrity sha512-i5s7Uh9B3Q6uwxLpMhNKlgBf6pcemvWaORxsW1zNF/YCY3jd5EftvnGBI+fxVwJ1CBxkVfxqCvm1lpZkbaoGmg==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    jest-regex-util "^27.0.6"
-    jest-snapshot "^27.2.4"
+jest-regex-util@^29.6.3:
+  version "29.6.3"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52"
+  integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==
 
 jest-resolve-dependencies@^27.5.1:
   version "27.5.1"
@@ -9581,6 +9645,14 @@ jest-resolve-dependencies@^29.6.2:
     jest-regex-util "^29.4.3"
     jest-snapshot "^29.6.2"
 
+jest-resolve-dependencies@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428"
+  integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==
+  dependencies:
+    jest-regex-util "^29.6.3"
+    jest-snapshot "^29.7.0"
+
 jest-resolve@^23.6.0:
   version "23.6.0"
   resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz"
@@ -9590,22 +9662,6 @@ jest-resolve@^23.6.0:
     chalk "^2.0.1"
     realpath-native "^1.0.0"
 
-jest-resolve@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.4.tgz"
-  integrity sha512-IsAO/3+3BZnKjI2I4f3835TBK/90dxR7Otgufn3mnrDFTByOSXclDi3G2XJsawGV4/18IMLARJ+V7Wm7t+J89Q==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    chalk "^4.0.0"
-    escalade "^3.1.1"
-    graceful-fs "^4.2.4"
-    jest-haste-map "^27.2.4"
-    jest-pnp-resolver "^1.2.2"
-    jest-util "^27.2.4"
-    jest-validate "^27.2.4"
-    resolve "^1.20.0"
-    slash "^3.0.0"
-
 jest-resolve@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz"
@@ -9637,33 +9693,20 @@ jest-resolve@^29.6.2:
     resolve.exports "^2.0.0"
     slash "^3.0.0"
 
-jest-runner@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.4.tgz"
-  integrity sha512-hIo5PPuNUyVDidZS8EetntuuJbQ+4IHWxmHgYZz9FIDbG2wcZjrP6b52uMDjAEQiHAn8yn8ynNe+TL8UuGFYKg==
+jest-resolve@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30"
+  integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==
   dependencies:
-    "@jest/console" "^27.2.4"
-    "@jest/environment" "^27.2.4"
-    "@jest/test-result" "^27.2.4"
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
     chalk "^4.0.0"
-    emittery "^0.8.1"
-    exit "^0.1.2"
-    graceful-fs "^4.2.4"
-    jest-docblock "^27.0.6"
-    jest-environment-jsdom "^27.2.4"
-    jest-environment-node "^27.2.4"
-    jest-haste-map "^27.2.4"
-    jest-leak-detector "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-resolve "^27.2.4"
-    jest-runtime "^27.2.4"
-    jest-util "^27.2.4"
-    jest-worker "^27.2.4"
-    source-map-support "^0.5.6"
-    throat "^6.0.1"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^29.7.0"
+    jest-pnp-resolver "^1.2.2"
+    jest-util "^29.7.0"
+    jest-validate "^29.7.0"
+    resolve "^1.20.0"
+    resolve.exports "^2.0.0"
+    slash "^3.0.0"
 
 jest-runner@^27.5.1:
   version "27.5.1"
@@ -9719,38 +9762,32 @@ jest-runner@^29.6.2:
     p-limit "^3.1.0"
     source-map-support "0.5.13"
 
-jest-runtime@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.4.tgz"
-  integrity sha512-ICKzzYdjIi70P17MZsLLIgIQFCQmIjMFf+xYww3aUySiUA/QBPUTdUqo5B2eg4HOn9/KkUsV0z6GVgaqAPBJvg==
-  dependencies:
-    "@jest/console" "^27.2.4"
-    "@jest/environment" "^27.2.4"
-    "@jest/fake-timers" "^27.2.4"
-    "@jest/globals" "^27.2.4"
-    "@jest/source-map" "^27.0.6"
-    "@jest/test-result" "^27.2.4"
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/yargs" "^16.0.0"
+jest-runner@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e"
+  integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==
+  dependencies:
+    "@jest/console" "^29.7.0"
+    "@jest/environment" "^29.7.0"
+    "@jest/test-result" "^29.7.0"
+    "@jest/transform" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
     chalk "^4.0.0"
-    cjs-module-lexer "^1.0.0"
-    collect-v8-coverage "^1.0.0"
-    execa "^5.0.0"
-    exit "^0.1.2"
-    glob "^7.1.3"
-    graceful-fs "^4.2.4"
-    jest-haste-map "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-mock "^27.2.4"
-    jest-regex-util "^27.0.6"
-    jest-resolve "^27.2.4"
-    jest-snapshot "^27.2.4"
-    jest-util "^27.2.4"
-    jest-validate "^27.2.4"
-    slash "^3.0.0"
-    strip-bom "^4.0.0"
-    yargs "^16.2.0"
+    emittery "^0.13.1"
+    graceful-fs "^4.2.9"
+    jest-docblock "^29.7.0"
+    jest-environment-node "^29.7.0"
+    jest-haste-map "^29.7.0"
+    jest-leak-detector "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-resolve "^29.7.0"
+    jest-runtime "^29.7.0"
+    jest-util "^29.7.0"
+    jest-watcher "^29.7.0"
+    jest-worker "^29.7.0"
+    p-limit "^3.1.0"
+    source-map-support "0.5.13"
 
 jest-runtime@^27.5.1:
   version "27.5.1"
@@ -9808,13 +9845,33 @@ jest-runtime@^29.6.2:
     slash "^3.0.0"
     strip-bom "^4.0.0"
 
-jest-serializer@^27.0.6:
-  version "27.0.6"
-  resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz"
-  integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==
-  dependencies:
+jest-runtime@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817"
+  integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==
+  dependencies:
+    "@jest/environment" "^29.7.0"
+    "@jest/fake-timers" "^29.7.0"
+    "@jest/globals" "^29.7.0"
+    "@jest/source-map" "^29.6.3"
+    "@jest/test-result" "^29.7.0"
+    "@jest/transform" "^29.7.0"
+    "@jest/types" "^29.6.3"
     "@types/node" "*"
-    graceful-fs "^4.2.4"
+    chalk "^4.0.0"
+    cjs-module-lexer "^1.0.0"
+    collect-v8-coverage "^1.0.0"
+    glob "^7.1.3"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-mock "^29.7.0"
+    jest-regex-util "^29.6.3"
+    jest-resolve "^29.7.0"
+    jest-snapshot "^29.7.0"
+    jest-util "^29.7.0"
+    slash "^3.0.0"
+    strip-bom "^4.0.0"
 
 jest-serializer@^27.5.1:
   version "27.5.1"
@@ -9840,36 +9897,6 @@ jest-snapshot@^23.6.0:
     pretty-format "^23.6.0"
     semver "^5.5.0"
 
-jest-snapshot@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.4.tgz"
-  integrity sha512-5DFxK31rYS8X8C6WXsFx8XxrxW3PGa6+9IrUcZdTLg1aEyXDGIeiBh4jbwvh655bg/9vTETbEj/njfZicHTZZw==
-  dependencies:
-    "@babel/core" "^7.7.2"
-    "@babel/generator" "^7.7.2"
-    "@babel/parser" "^7.7.2"
-    "@babel/plugin-syntax-typescript" "^7.7.2"
-    "@babel/traverse" "^7.7.2"
-    "@babel/types" "^7.0.0"
-    "@jest/transform" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/babel__traverse" "^7.0.4"
-    "@types/prettier" "^2.1.5"
-    babel-preset-current-node-syntax "^1.0.0"
-    chalk "^4.0.0"
-    expect "^27.2.4"
-    graceful-fs "^4.2.4"
-    jest-diff "^27.2.4"
-    jest-get-type "^27.0.6"
-    jest-haste-map "^27.2.4"
-    jest-matcher-utils "^27.2.4"
-    jest-message-util "^27.2.4"
-    jest-resolve "^27.2.4"
-    jest-util "^27.2.4"
-    natural-compare "^1.4.0"
-    pretty-format "^27.2.4"
-    semver "^7.3.2"
-
 jest-snapshot@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz"
@@ -9924,7 +9951,33 @@ jest-snapshot@^29.6.2:
     pretty-format "^29.6.2"
     semver "^7.5.3"
 
-jest-util@^27.0.0, jest-util@^27.2.4:
+jest-snapshot@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5"
+  integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==
+  dependencies:
+    "@babel/core" "^7.11.6"
+    "@babel/generator" "^7.7.2"
+    "@babel/plugin-syntax-jsx" "^7.7.2"
+    "@babel/plugin-syntax-typescript" "^7.7.2"
+    "@babel/types" "^7.3.3"
+    "@jest/expect-utils" "^29.7.0"
+    "@jest/transform" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    babel-preset-current-node-syntax "^1.0.0"
+    chalk "^4.0.0"
+    expect "^29.7.0"
+    graceful-fs "^4.2.9"
+    jest-diff "^29.7.0"
+    jest-get-type "^29.6.3"
+    jest-matcher-utils "^29.7.0"
+    jest-message-util "^29.7.0"
+    jest-util "^29.7.0"
+    natural-compare "^1.4.0"
+    pretty-format "^29.7.0"
+    semver "^7.5.3"
+
+jest-util@^27.0.0:
   version "27.2.4"
   resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.2.4.tgz"
   integrity sha512-mW++4u+fSvAt3YBWm5IpbmRAceUqa2B++JlUZTiuEt2AmNYn0Yw5oay4cP17TGsMINRNPSGiJ2zNnX60g+VbFg==
@@ -9960,17 +10013,17 @@ jest-util@^29.0.0, jest-util@^29.6.2:
     graceful-fs "^4.2.9"
     picomatch "^2.2.3"
 
-jest-validate@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.4.tgz"
-  integrity sha512-VMtbxbkd7LHnIH7PChdDtrluCFRJ4b1YV2YJzNwwsASMWftq/HgqiqjvptBOWyWOtevgO3f14wPxkPcLlVBRog==
+jest-util@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
+  integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
   dependencies:
-    "@jest/types" "^27.2.4"
-    camelcase "^6.2.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
     chalk "^4.0.0"
-    jest-get-type "^27.0.6"
-    leven "^3.1.0"
-    pretty-format "^27.2.4"
+    ci-info "^3.2.0"
+    graceful-fs "^4.2.9"
+    picomatch "^2.2.3"
 
 jest-validate@^27.5.1:
   version "27.5.1"
@@ -9996,18 +10049,17 @@ jest-validate@^29.6.2:
     leven "^3.1.0"
     pretty-format "^29.6.2"
 
-jest-watcher@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.4.tgz"
-  integrity sha512-LXC/0+dKxhK7cfF7reflRYlzDIaQE+fL4ynhKhzg8IMILNMuI4xcjXXfUJady7OR4/TZeMg7X8eHx8uan9vqaQ==
+jest-validate@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c"
+  integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==
   dependencies:
-    "@jest/test-result" "^27.2.4"
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    ansi-escapes "^4.2.1"
+    "@jest/types" "^29.6.3"
+    camelcase "^6.2.0"
     chalk "^4.0.0"
-    jest-util "^27.2.4"
-    string-length "^4.0.1"
+    jest-get-type "^29.6.3"
+    leven "^3.1.0"
+    pretty-format "^29.7.0"
 
 jest-watcher@^27.5.1:
   version "27.5.1"
@@ -10036,6 +10088,20 @@ jest-watcher@^29.6.2:
     jest-util "^29.6.2"
     string-length "^4.0.1"
 
+jest-watcher@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2"
+  integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==
+  dependencies:
+    "@jest/test-result" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    "@types/node" "*"
+    ansi-escapes "^4.2.1"
+    chalk "^4.0.0"
+    emittery "^0.13.1"
+    jest-util "^29.7.0"
+    string-length "^4.0.1"
+
 jest-worker@^26.2.1:
   version "26.6.2"
   resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz"
@@ -10045,15 +10111,6 @@ jest-worker@^26.2.1:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
-jest-worker@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.4.tgz"
-  integrity sha512-Zq9A2Pw59KkVjBBKD1i3iE2e22oSjXhUKKuAK1HGX8flGwkm6NMozyEYzKd41hXc64dbd/0eWFeEEuxqXyhM+g==
-  dependencies:
-    "@types/node" "*"
-    merge-stream "^2.0.0"
-    supports-color "^8.0.0"
-
 jest-worker@^27.5.1:
   version "27.5.1"
   resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz"
@@ -10073,14 +10130,15 @@ jest-worker@^29.6.2:
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest@^27.2.4:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest/-/jest-27.2.4.tgz"
-  integrity sha512-h4uqb1EQLfPulWyUFFWv9e9Nn8sCqsJ/j3wk/KCY0p4s4s0ICCfP3iMf6hRf5hEhsDyvyrCgKiZXma63gMz16A==
+jest-worker@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a"
+  integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
   dependencies:
-    "@jest/core" "^27.2.4"
-    import-local "^3.0.2"
-    jest-cli "^27.2.4"
+    "@types/node" "*"
+    jest-util "^29.7.0"
+    merge-stream "^2.0.0"
+    supports-color "^8.0.0"
 
 jest@^27.5.1:
   version "27.5.1"
@@ -10101,6 +10159,16 @@ jest@^29.6.0:
     import-local "^3.0.2"
     jest-cli "^29.6.2"
 
+jest@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613"
+  integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
+  dependencies:
+    "@jest/core" "^29.7.0"
+    "@jest/types" "^29.6.3"
+    import-local "^3.0.2"
+    jest-cli "^29.7.0"
+
 jju@~1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
@@ -10136,39 +10204,6 @@ jsbn@~0.1.0:
   resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jsdom@^16.4.0:
-  version "16.6.0"
-  resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz"
-  integrity sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg==
-  dependencies:
-    abab "^2.0.5"
-    acorn "^8.2.4"
-    acorn-globals "^6.0.0"
-    cssom "^0.4.4"
-    cssstyle "^2.3.0"
-    data-urls "^2.0.0"
-    decimal.js "^10.2.1"
-    domexception "^2.0.1"
-    escodegen "^2.0.0"
-    form-data "^3.0.0"
-    html-encoding-sniffer "^2.0.1"
-    http-proxy-agent "^4.0.1"
-    https-proxy-agent "^5.0.0"
-    is-potential-custom-element-name "^1.0.1"
-    nwsapi "^2.2.0"
-    parse5 "6.0.1"
-    saxes "^5.0.1"
-    symbol-tree "^3.2.4"
-    tough-cookie "^4.0.0"
-    w3c-hr-time "^1.0.2"
-    w3c-xmlserializer "^2.0.0"
-    webidl-conversions "^6.1.0"
-    whatwg-encoding "^1.0.5"
-    whatwg-mimetype "^2.3.0"
-    whatwg-url "^8.5.0"
-    ws "^7.4.5"
-    xml-name-validator "^3.0.0"
-
 jsdom@^16.6.0:
   version "16.7.0"
   resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz"
@@ -10234,6 +10269,33 @@ jsdom@^20.0.0:
     ws "^8.11.0"
     xml-name-validator "^4.0.0"
 
+jsdom@^23.0.0:
+  version "23.0.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-23.0.0.tgz#7c8bac82e32e1ac3eef29096ea59d519c72ce4eb"
+  integrity sha512-cbL/UCtohJguhFC7c2/hgW6BeZCNvP7URQGnx9tSJRYKCdnfbfWOrtuLTMfiB2VxKsx5wPHVsh/J0aBy9lIIhQ==
+  dependencies:
+    cssstyle "^3.0.0"
+    data-urls "^5.0.0"
+    decimal.js "^10.4.3"
+    form-data "^4.0.0"
+    html-encoding-sniffer "^4.0.0"
+    http-proxy-agent "^7.0.0"
+    https-proxy-agent "^7.0.2"
+    is-potential-custom-element-name "^1.0.1"
+    nwsapi "^2.2.7"
+    parse5 "^7.1.2"
+    rrweb-cssom "^0.6.0"
+    saxes "^6.0.0"
+    symbol-tree "^3.2.4"
+    tough-cookie "^4.1.3"
+    w3c-xmlserializer "^5.0.0"
+    webidl-conversions "^7.0.0"
+    whatwg-encoding "^3.1.1"
+    whatwg-mimetype "^4.0.0"
+    whatwg-url "^14.0.0"
+    ws "^8.14.2"
+    xml-name-validator "^5.0.0"
+
 jsesc@^2.5.1:
   version "2.5.2"
   resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
@@ -10618,7 +10680,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@4.x, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.15:
+lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.15:
   version "4.17.21"
   resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -11166,11 +11228,6 @@ node-int64@^0.4.0:
   resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
   integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
 
-node-modules-regexp@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz"
-  integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
-
 node-notifier@9.0.0:
   version "9.0.0"
   resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-9.0.0.tgz#46c5bbecbb796d4a803f646cea5bc91403f2ff38"
@@ -11266,7 +11323,7 @@ nwsapi@^2.2.0:
   resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz"
   integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
 
-nwsapi@^2.2.2:
+nwsapi@^2.2.2, nwsapi@^2.2.7:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
   integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
@@ -11650,7 +11707,7 @@ parse5@^7.0.0:
   dependencies:
     entities "^4.3.0"
 
-parse5@^7.1.1:
+parse5@^7.1.1, parse5@^7.1.2:
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
   integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
@@ -11807,13 +11864,6 @@ pino@7.9.1:
     sonic-boom "^2.2.1"
     thread-stream "^0.13.0"
 
-pirates@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz"
-  integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
-  dependencies:
-    node-modules-regexp "^1.0.0"
-
 pirates@^4.0.4:
   version "4.0.4"
   resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz"
@@ -12335,6 +12385,15 @@ pretty-format@^29.0.0, pretty-format@^29.6.2:
     ansi-styles "^5.0.0"
     react-is "^18.0.0"
 
+pretty-format@^29.7.0:
+  version "29.7.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
+  integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
+  dependencies:
+    "@jest/schemas" "^29.6.3"
+    ansi-styles "^5.0.0"
+    react-is "^18.0.0"
+
 process-nextick-args@~2.0.0:
   version "2.0.1"
   resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
@@ -12429,6 +12488,11 @@ punycode@^2.1.0, punycode@^2.1.1:
   resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
+punycode@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+  integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
 pupa@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
@@ -13106,16 +13170,16 @@ rollup-plugin-typescript2@^0.31.2:
     resolve "^1.20.0"
     tslib "^2.3.1"
 
-rollup-plugin-typescript2@^0.34.1:
-  version "0.34.1"
-  resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.34.1.tgz#c457f155a71d133c142689213fce78694e30d0be"
-  integrity sha512-P4cHLtGikESmqi1CA+tdMDUv8WbQV48mzPYt77TSTOPJpERyZ9TXdDgjSDix8Fkqce6soYz3+fa4lrC93IEkcw==
+rollup-plugin-typescript2@^0.36.0:
+  version "0.36.0"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz#309564eb70d710412f5901344ca92045e180ed53"
+  integrity sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==
   dependencies:
     "@rollup/pluginutils" "^4.1.2"
     find-cache-dir "^3.3.2"
     fs-extra "^10.0.0"
-    semver "^7.3.7"
-    tslib "^2.4.0"
+    semver "^7.5.4"
+    tslib "^2.6.2"
 
 rollup-plugin-web-worker-loader@^1.6.1:
   version "1.6.1"
@@ -13129,7 +13193,7 @@ rollup-pluginutils@^2.8.2:
   dependencies:
     estree-walker "^0.6.1"
 
-rollup@^2.46.0, rollup@^2.79.1:
+rollup@^2.47.0, rollup@^2.79.1:
   version "2.79.1"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
   integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
@@ -13164,6 +13228,11 @@ rollup@~2.78.0:
   optionalDependencies:
     fsevents "~2.3.2"
 
+rrweb-cssom@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1"
+  integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==
+
 run-async@^2.4.0:
   version "2.4.1"
   resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz"
@@ -14241,7 +14310,7 @@ tough-cookie@^4.0.0:
     punycode "^2.1.1"
     universalify "^0.1.2"
 
-tough-cookie@^4.1.2:
+tough-cookie@^4.1.2, tough-cookie@^4.1.3:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
   integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
@@ -14273,6 +14342,13 @@ tr46@^3.0.0:
   dependencies:
     punycode "^2.1.1"
 
+tr46@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec"
+  integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==
+  dependencies:
+    punycode "^2.3.1"
+
 tr46@~0.0.3:
   version "0.0.3"
   resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
@@ -14295,20 +14371,6 @@ trim-repeated@^1.0.0:
   dependencies:
     escape-string-regexp "^1.0.2"
 
-ts-jest@^27.0.5:
-  version "27.0.5"
-  resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.5.tgz"
-  integrity sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w==
-  dependencies:
-    bs-logger "0.x"
-    fast-json-stable-stringify "2.x"
-    jest-util "^27.0.0"
-    json5 "2.x"
-    lodash "4.x"
-    make-error "1.x"
-    semver "7.x"
-    yargs-parser "20.x"
-
 ts-jest@^27.1.3:
   version "27.1.3"
   resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz"
@@ -14403,11 +14465,16 @@ tslib@^2.3.1:
   resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz"
   integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
 
-tslib@^2.4.0, tslib@^2.5.3:
+tslib@^2.5.3:
   version "2.5.3"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
   integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
 
+tslib@^2.6.2:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+  integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
 tsutils@^3.21.0:
   version "3.21.0"
   resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
@@ -14973,6 +15040,13 @@ w3c-xmlserializer@^4.0.0:
   dependencies:
     xml-name-validator "^4.0.0"
 
+w3c-xmlserializer@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"
+  integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==
+  dependencies:
+    xml-name-validator "^5.0.0"
+
 walker@^1.0.7:
   version "1.0.7"
   resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz"
@@ -15077,6 +15151,13 @@ whatwg-encoding@^2.0.0:
   dependencies:
     iconv-lite "0.6.3"
 
+whatwg-encoding@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5"
+  integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==
+  dependencies:
+    iconv-lite "0.6.3"
+
 whatwg-mimetype@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
@@ -15087,6 +15168,11 @@ whatwg-mimetype@^3.0.0:
   resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
   integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
 
+whatwg-mimetype@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a"
+  integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==
+
 whatwg-url@^11.0.0:
   version "11.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
@@ -15095,6 +15181,14 @@ whatwg-url@^11.0.0:
     tr46 "^3.0.0"
     webidl-conversions "^7.0.0"
 
+whatwg-url@^14.0.0:
+  version "14.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6"
+  integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==
+  dependencies:
+    tr46 "^5.0.0"
+    webidl-conversions "^7.0.0"
+
 whatwg-url@^5.0.0:
   version "5.0.0"
   resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
@@ -15262,7 +15356,7 @@ ws@^7.2.3:
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz"
   integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
 
-ws@^7.4.3, ws@^7.4.5:
+ws@^7.4.3:
   version "7.5.3"
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz"
   integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
@@ -15272,6 +15366,11 @@ ws@^7.4.6:
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz"
   integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
 
+ws@^8.14.2:
+  version "8.14.2"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
+  integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
+
 xdg-basedir@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
@@ -15287,6 +15386,11 @@ xml-name-validator@^4.0.0:
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
   integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
 
+xml-name-validator@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673"
+  integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==
+
 xml2js@~0.4.23:
   version "0.4.23"
   resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"

From 67a779e83eb93862dd201c92b71d01408569532c Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 27 Nov 2023 23:40:34 +0100
Subject: [PATCH 024/102] Add check types

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index e210dda8ce..8240ee85e0 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "test:update": "yarn turbo run test:update",
     "format": "yarn prettier --write '**/*.{ts,md}'",
     "dev": "yarn turbo run dev",
+    "check-types": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references --check' 'yarn turbo run check-types'",
     "repl": "cd packages/rrweb && npm run repl",
     "live-stream": "cd packages/rrweb && yarn live-stream",
     "lint": "yarn run concurrently --success=all -r -m=1 'yarn run markdownlint docs' 'yarn eslint packages/*/src --ext .ts,.tsx,.js,.jsx,.svelte'",

From 71d87d488d6b43c9882f9e357cf35374f054f678 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 14:44:39 +0100
Subject: [PATCH 025/102] Update snapshots to include assetCapture settings

---
 .../__snapshots__/integration.test.ts.snap    | 276 +++++++++++++++---
 .../test/__snapshots__/record.test.ts.snap    | 132 +++++++--
 .../cross-origin-iframes.test.ts.snap         |  90 +++++-
 .../record/__snapshots__/webgl.test.ts.snap   |  48 ++-
 4 files changed, 455 insertions(+), 91 deletions(-)

diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index ae1aa7bf54..3f15913274 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -15,7 +15,11 @@ exports[`record integration tests can correctly serialize a shader and multiple
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -429,7 +433,11 @@ exports[`record integration tests can freeze mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -609,7 +617,11 @@ exports[`record integration tests can mask character data mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1049,7 +1061,11 @@ exports[`record integration tests can record attribute mutation 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1220,7 +1236,11 @@ exports[`record integration tests can record character data muatations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1398,7 +1418,11 @@ exports[`record integration tests can record childList mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1574,7 +1598,11 @@ exports[`record integration tests can record clicks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1856,7 +1884,11 @@ exports[`record integration tests can record form interactions 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2639,7 +2671,11 @@ exports[`record integration tests can record node mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3614,7 +3650,11 @@ exports[`record integration tests can record style changes compactly and preserv
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4104,7 +4144,11 @@ exports[`record integration tests can use maskInputOptions to configure which ty
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4975,7 +5019,11 @@ exports[`record integration tests handles null attribute values 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -5166,7 +5214,11 @@ exports[`record integration tests mutations should work when blocked class is un
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -5931,7 +5983,11 @@ exports[`record integration tests should handle recursive console messages 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -6111,7 +6167,11 @@ exports[`record integration tests should mask inputs via function call 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -6981,7 +7041,11 @@ exports[`record integration tests should mask password value attribute with mask
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -7428,7 +7492,11 @@ exports[`record integration tests should mask texts 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -7726,7 +7794,11 @@ exports[`record integration tests should mask texts using maskTextFn 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -8024,7 +8096,11 @@ exports[`record integration tests should nest record iframe 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -8783,7 +8859,11 @@ exports[`record integration tests should not record blocked elements and its chi
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -8963,7 +9043,11 @@ exports[`record integration tests should not record blocked elements dynamically
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -9169,7 +9253,11 @@ exports[`record integration tests should not record input events on ignored elem
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -9580,7 +9668,11 @@ exports[`record integration tests should not record input values if dynamically
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -9812,7 +9904,11 @@ exports[`record integration tests should not record input values if maskAllInput
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -10682,7 +10778,11 @@ exports[`record integration tests should record DOM node movement 1 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -10949,7 +11049,11 @@ exports[`record integration tests should record DOM node movement 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -11223,7 +11327,11 @@ exports[`record integration tests should record after DOMContentLoaded event 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -11310,7 +11418,11 @@ exports[`record integration tests should record canvas mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -11529,7 +11641,11 @@ exports[`record integration tests should record console messages 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -12035,7 +12151,11 @@ exports[`record integration tests should record dynamic CSS changes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -12432,7 +12552,11 @@ exports[`record integration tests should record images inside iframe with blob u
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -12825,7 +12949,11 @@ exports[`record integration tests should record images inside iframe with blob u
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -13293,7 +13421,11 @@ exports[`record integration tests should record images with blob url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -13534,7 +13666,11 @@ exports[`record integration tests should record input userTriggered values if us
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -14434,7 +14570,11 @@ exports[`record integration tests should record moved shadow DOM 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -14603,7 +14743,11 @@ exports[`record integration tests should record moved shadow DOM 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -14786,7 +14930,11 @@ exports[`record integration tests should record mutations in iframes accross pag
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -15095,7 +15243,11 @@ exports[`record integration tests should record nested iframes and shadow doms 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -15442,7 +15594,11 @@ exports[`record integration tests should record shadow DOM 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -15892,7 +16048,11 @@ exports[`record integration tests should record shadow DOM 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -16010,7 +16170,11 @@ exports[`record integration tests should record shadow DOM 3 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -16128,7 +16292,11 @@ exports[`record integration tests should record shadow doms polyfilled by shadyd
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -16374,7 +16542,11 @@ exports[`record integration tests should record shadow doms polyfilled by synthe
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -16648,7 +16820,11 @@ exports[`record integration tests should record webgl canvas mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -16863,7 +17039,11 @@ exports[`record integration tests should unmask texts using maskTextFn 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -17161,7 +17341,11 @@ exports[`record integration tests will serialize node before record 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap
index cb40f3328d..0bd7c0def4 100644
--- a/packages/rrweb/test/__snapshots__/record.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap
@@ -7,7 +7,11 @@ exports[`record aggregates mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -93,7 +97,11 @@ exports[`record can add custom event 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -187,7 +195,11 @@ exports[`record captures CORS stylesheets that are still loading 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -290,7 +302,11 @@ exports[`record captures adopted stylesheets in nested shadow doms and iframes 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -756,7 +772,11 @@ exports[`record captures adopted stylesheets in shadow doms and iframe 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1152,7 +1172,11 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1252,7 +1276,11 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1357,7 +1385,11 @@ exports[`record captures inserted style text nodes correctly 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1487,7 +1519,11 @@ exports[`record captures mutations on adopted stylesheets 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1787,7 +1823,11 @@ exports[`record captures nested stylesheet rules 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -1936,7 +1976,11 @@ exports[`record captures style property changes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2065,7 +2109,11 @@ exports[`record captures stylesheet rules 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2203,7 +2251,11 @@ exports[`record captures stylesheets in iframes with \`blob:\` url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2347,7 +2399,11 @@ exports[`record captures stylesheets with \`blob:\` url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2435,7 +2491,11 @@ exports[`record iframes captures stylesheet mutations in iframes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2670,7 +2730,11 @@ exports[`record is safe to checkout during async callbacks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2801,7 +2865,11 @@ exports[`record is safe to checkout during async callbacks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2926,7 +2994,11 @@ exports[`record loading stylesheets captures stylesheets in iframes that are sti
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3262,7 +3334,11 @@ exports[`record loading stylesheets captures stylesheets that are still loading
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3442,7 +3518,11 @@ exports[`record no need for attribute mutations on adds 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3562,7 +3642,11 @@ exports[`record should record scroll position 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3682,7 +3766,11 @@ exports[`record without CSSGroupingRule support captures nested stylesheet rules
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
diff --git a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
index e160fb38d6..35ba745157 100644
--- a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
@@ -7,7 +7,11 @@ exports[`cross origin iframes audio.html should emit contents of iframe once 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -345,7 +349,11 @@ exports[`cross origin iframes blank.html should filter out forwarded cross origi
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -607,7 +615,11 @@ exports[`cross origin iframes blank.html should record same-origin iframe in cro
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -851,7 +863,11 @@ exports[`cross origin iframes blank.html should support packFn option in record(
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     },
     \\"v\\": \\"v1\\"
   },
@@ -1020,7 +1036,11 @@ exports[`cross origin iframes form.html should map input events correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2033,7 +2053,11 @@ exports[`cross origin iframes form.html should map scroll events correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -2689,7 +2713,11 @@ exports[`cross origin iframes move-node.html captures mutations on adopted style
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3103,7 +3131,11 @@ exports[`cross origin iframes move-node.html captures mutations on stylesheets 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3522,7 +3554,11 @@ exports[`cross origin iframes move-node.html should record DOM attribute changes
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -3810,7 +3846,11 @@ exports[`cross origin iframes move-node.html should record DOM node movement 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4195,7 +4235,11 @@ exports[`cross origin iframes move-node.html should record DOM node removal 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4481,7 +4525,11 @@ exports[`cross origin iframes move-node.html should record DOM text changes 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4767,7 +4815,11 @@ exports[`cross origin iframes move-node.html should record canvas elements 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -5090,7 +5142,11 @@ exports[`cross origin iframes move-node.html should record custom events 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -5372,7 +5428,11 @@ exports[`same origin iframes should emit contents of iframe once 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
diff --git a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
index c7aeb17e1c..d0b0ccbb23 100644
--- a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
@@ -7,7 +7,11 @@ exports[`record webgl recordCanvas FPS should record snapshots 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -166,7 +170,11 @@ exports[`record webgl should batch events by RAF 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -311,7 +319,11 @@ exports[`record webgl will record changes to a canvas element 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -404,7 +416,11 @@ exports[`record webgl will record changes to a canvas element before the canvas
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -532,7 +548,11 @@ exports[`record webgl will record changes to a canvas element before the canvas
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -660,7 +680,11 @@ exports[`record webgl will record changes to a webgl2 canvas element 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -753,7 +777,11 @@ exports[`record webgl will record webgl variables 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -866,7 +894,11 @@ exports[`record webgl will record webgl variables in reverse order 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"assetCapture\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {

From 5b4a5b5ed907e955977f8f3232ab859e06e8a0f2 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 21:05:06 +0100
Subject: [PATCH 026/102] Mark `inlineImages` as deprecated in favor of
 `assetCapture`

---
 docs/recipes/assets.md                  |  2 +-
 docs/recipes/assets.zh_CN.md            | 40 +++++++++++++++++++
 guide.md                                | 52 ++++++++++++-------------
 guide.zh_CN.md                          |  3 +-
 packages/rrweb-snapshot/src/snapshot.ts | 12 ++++++
 packages/rrweb/scripts/stream.js        |  5 ++-
 packages/rrweb/src/record/mutation.ts   |  4 ++
 packages/rrweb/src/types.ts             |  6 +++
 8 files changed, 95 insertions(+), 29 deletions(-)
 create mode 100644 docs/recipes/assets.zh_CN.md

diff --git a/docs/recipes/assets.md b/docs/recipes/assets.md
index 8f57fc4539..e364b7eae8 100644
--- a/docs/recipes/assets.md
+++ b/docs/recipes/assets.md
@@ -24,11 +24,11 @@ Here is the TypeScript type definition for the `recordOptions` object, which inc
 ```typescript
 export type recordOptions<T> = {
   // Other configuration options...
-  inlineImages?: boolean;
   assetCapture?: {
     objectURLs: boolean;
     origins: string[] | true | false;
   };
+  inlineImages?: boolean; // Deprecated, don't use it anymore
   // Other configuration options...
 };
 ```
diff --git a/docs/recipes/assets.zh_CN.md b/docs/recipes/assets.zh_CN.md
new file mode 100644
index 0000000000..fcd496b14a
--- /dev/null
+++ b/docs/recipes/assets.zh_CN.md
@@ -0,0 +1,40 @@
+# rrweb 中的资源捕获方法和配置
+
+[rrweb](https://rrweb.io/) 是一个 JavaScript 库,允许您记录并重放您网站上的用户互动。它为捕获资产(如图像)提供了各种配置选项。在本文档中,我们将探讨 rrweb 中不同的资源捕获方法及其配置选项。
+
+## 内联图像(已弃用)
+
+`inlineImages` 配置选项已被弃用,不应再使用。它存在一些问题,即重写已经发出的事件,这可能使您错过已发送到服务器的内联图像。相反,请使用 `assetCapture` 选项来配置资源捕获。
+
+## 资源捕获配置
+
+`assetCapture` 配置选项允许您自定义资源捕获过程。它是一个具有以下属性的对象:
+
+- `objectURLs`(默认值:`true`):此属性指定是否使用对象 URL 捕获同源 `blob:` 资源。对象 URL 是使用 `URL.createObjectURL()` 方法创建的。将 `objectURLs` 设置为 `true` 可以启用对象 URL 的捕获。
+
+- `origins`(默认值:`false`):此属性确定从哪些来源捕获资源。它可以有以下值:
+  - `false` 或 `[]`:除了对象 URL 之外,不捕获任何资源。
+  - `true`:从所有来源捕获资源。
+  - `[origin1, origin2, ...]`:仅从指定的来源捕获资源。例如,`origins: ['https://s3.example.com/']` 从 `https://s3.example.com/` 来源捕获所有资源。
+
+## TypeScript 类型定义
+
+这是 `recordOptions` 对象的 TypeScript 类型定义,其中包括资源捕获配置选项:
+
+```typescript
+export type recordOptions<T> = {
+  // 其他配置选项...
+  assetCapture?: {
+    objectURLs: boolean;
+    origins: string[] | true | false;
+  };
+  inlineImages?: boolean; // 已弃用
+  // 其他配置选项...
+};
+```
+
+这种类型定义表明 assetCapture 是 recordOptions 对象的一个可选属性。它包含 objectURLs 和 origins 属性,其含义与上述相同。
+
+## 结论
+
+通过在 rrweb 中配置 assetCapture 选项,您可以控制在记录过程中如何捕获像图像这样的资源。这允许您
diff --git a/guide.md b/guide.md
index 5881f3b86f..6469582d76 100644
--- a/guide.md
+++ b/guide.md
@@ -135,32 +135,32 @@ setInterval(save, 10 * 1000);
 
 The parameter of `rrweb.record` accepts the following options.
 
-| key                      | default                                            | description                                                                                                                                                                                   |
-| ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| emit                     | required                                           | the callback function to get emitted events                                                                                                                                                   |
-| checkoutEveryNth         | -                                                  | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter                                                                                                     |
-| checkoutEveryNms         | -                                                  | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter                                                                                                         |
-| blockClass               | 'rr-block'                                         | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter                                                                                |
-| blockSelector            | null                                               | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter                                                                                          |
-| ignoreClass              | 'rr-ignore'                                        | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter                                                                                |
-| ignoreSelector           | null               | Use a string to configure which selector should be ignored, refer to the [privacy](#privacy) chapter                                                                                          |
-| ignoreCSSAttributes      | null                                               | array of CSS attributes that should be ignored                                                                                                                                                |
-| maskTextClass            | 'rr-mask'                                          | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter                                                                                 |
-| maskTextSelector         | null                                               | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter                                                                                           |
-| maskAllInputs            | false                                              | mask all input content as \*                                                                                                                                                                  |
-| maskInputOptions         | { password: true }                                 | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95)            |
-| maskInputFn              | -                                                  | customize mask input content recording logic                                                                                                                                                  |
-| maskTextFn               | -                                                  | customize mask text content recording logic                                                                                                                                                   |
-| slimDOMOptions           | {}                                                 | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)  |
-| dataURLOptions           | {}                                                 | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data                  |
-| inlineStylesheet         | true                                               | whether to inline the stylesheet in the events                                                                                                                                                |
-| hooks                    | {}                                                 | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                  |
-| packFn                   | -                                                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
-| sampling                 | -                                                  | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
-| recordCanvas             | false                                              | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
-| recordCrossOriginIframes | false                                              | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
-| recordAfter              | 'load'                                             | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
-| inlineImages             | false                                              | whether to record the image content (deprecated, use `assetCapture` instead)                                                                                                                  |
+| key                      | default                              | description                                                                                                                                                                                   |
+| ------------------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| emit                     | required                             | the callback function to get emitted events                                                                                                                                                   |
+| checkoutEveryNth         | -                                    | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter                                                                                                     |
+| checkoutEveryNms         | -                                    | take a full snapshot after every N ms<br />refer to the [checkout](#checkout) chapter                                                                                                         |
+| blockClass               | 'rr-block'                           | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter                                                                                |
+| blockSelector            | null                                 | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter                                                                                          |
+| ignoreClass              | 'rr-ignore'                          | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter                                                                                |
+| ignoreSelector           | null                                 | Use a string to configure which selector should be ignored, refer to the [privacy](#privacy) chapter                                                                                          |
+| ignoreCSSAttributes      | null                                 | array of CSS attributes that should be ignored                                                                                                                                                |
+| maskTextClass            | 'rr-mask'                            | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter                                                                                 |
+| maskTextSelector         | null                                 | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter                                                                                           |
+| maskAllInputs            | false                                | mask all input content as \*                                                                                                                                                                  |
+| maskInputOptions         | { password: true }                   | mask some kinds of input \*<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95)            |
+| maskInputFn              | -                                    | customize mask input content recording logic                                                                                                                                                  |
+| maskTextFn               | -                                    | customize mask text content recording logic                                                                                                                                                   |
+| slimDOMOptions           | {}                                   | remove unnecessary parts of the DOM <br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)  |
+| dataURLOptions           | {}                                   | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data                  |
+| inlineStylesheet         | true                                 | whether to inline the stylesheet in the events                                                                                                                                                |
+| hooks                    | {}                                   | hooks for events<br />refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                  |
+| packFn                   | -                                    | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
+| sampling                 | -                                    | refer to the [storage optimization recipe](./docs/recipes/optimize-storage.md)                                                                                                                |
+| recordCanvas             | false                                | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
+| recordCrossOriginIframes | false                                | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
+| recordAfter              | 'load'                               | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
+| ~inlineImages~           | false                                | whether to record the image content (deprecated, use `assetCapture` instead)                                                                                                                  |
 | assetCapture             | { objectURLs: true, origins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
 | collectFonts             | false                                              | whether to collect fonts in the website                                                                                                                                                       |
 | userTriggeredOnInput     | false                                              | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
diff --git a/guide.zh_CN.md b/guide.zh_CN.md
index dda679006c..c5f37faf7b 100644
--- a/guide.zh_CN.md
+++ b/guide.zh_CN.md
@@ -155,7 +155,8 @@ setInterval(save, 10 * 1000);
 | recordCanvas             | false              | 是否记录 canvas 内容, 可用选项:`false`, `true`                                                                                                                                       |
 | recordCrossOriginIframes | false              | 是否记录 cross origin iframes。 必须在每个子 iframe 中注入 rrweb 才能使其工作。 可用选项:`false`, `true`                                                                             |
 | recordAfter              | 'load'             | 如果 document 还没有加载完成,recorder 将会在指定的事件触发后开始录制。可用选项: `DOMContentLoaded`, `load`                                                                          |
-| inlineImages             | false              | 是否将图片内容记内联录制                                                                                                                                                              |
+| ~inlineImages~           | false              | 是否将图片内容记内联录制                                                                                                                                                              |
+| assetCapture | { objectURLs: true, origins: false } | 配置资源(图像)捕获并生成异步资源事件。<br/>有关更多信息,请参阅[资源捕获文档](./docs/recipes/assets.zh_CN.md) |
 | collectFonts             | false              | 是否记录页面中的字体文件                                                                                                                                                              |
 | userTriggeredOnInput     | false              | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495)                                                                                                                  |
 | plugins                  | []                 | 加载插件以获得额外的录制功能. [什么是插件?](./docs/recipes/plugin.zh_CN.md)                                                                                                          |
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 81369e37ee..d125434df1 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -462,6 +462,9 @@ function serializeNode(
     maskTextFn: MaskTextFn | undefined;
     maskInputFn: MaskInputFn | undefined;
     dataURLOptions?: DataURLOptions;
+    /**
+     * @deprecated please use `assetCapture` instead
+     */
     inlineImages: boolean;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
@@ -627,6 +630,9 @@ function serializeElementNode(
     maskInputOptions: MaskInputOptions;
     maskInputFn: MaskInputFn | undefined;
     dataURLOptions?: DataURLOptions;
+    /**
+     * @deprecated please use `assetCapture` instead
+     */
     inlineImages: boolean;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
@@ -997,6 +1003,9 @@ export function serializeNodeWithId(
     slimDOMOptions: SlimDOMOptions;
     dataURLOptions?: DataURLOptions;
     keepIframeSrcFn?: KeepIframeSrcFn;
+    /**
+     * @deprecated please use `assetCapture` instead
+     */
     inlineImages?: boolean;
     recordCanvas?: boolean;
     preserveWhiteSpace?: boolean;
@@ -1312,6 +1321,9 @@ function snapshot(
     maskInputFn?: MaskTextFn;
     slimDOM?: 'all' | boolean | SlimDOMOptions;
     dataURLOptions?: DataURLOptions;
+    /**
+     * @deprecated please use `assetCapture` instead
+     */
     inlineImages?: boolean;
     recordCanvas?: boolean;
     preserveWhiteSpace?: boolean;
diff --git a/packages/rrweb/scripts/stream.js b/packages/rrweb/scripts/stream.js
index 39693b1192..7b66c551e9 100644
--- a/packages/rrweb/scripts/stream.js
+++ b/packages/rrweb/scripts/stream.js
@@ -66,7 +66,10 @@ async function injectRecording(frame) {
             recordCanvas: false,
             recordCrossOriginIframes: true,
             collectFonts: true,
-            inlineImages: true,
+            assetCapture: {
+              objectURLs: true,
+              origins: false,
+            },
           });
         })();
       },
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 9d72c74b5a..cedf260425 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -181,6 +181,9 @@ export default class MutationBuffer {
   private maskInputFn: observerParam['maskInputFn'];
   private keepIframeSrcFn: observerParam['keepIframeSrcFn'];
   private recordCanvas: observerParam['recordCanvas'];
+  /**
+   * @deprecated please use `assetCapture` instead
+   */
   private inlineImages: observerParam['inlineImages'];
   private slimDOMOptions: observerParam['slimDOMOptions'];
   private dataURLOptions: observerParam['dataURLOptions'];
@@ -302,6 +305,7 @@ export default class MutationBuffer {
       if (parentId === -1 || nextId === -1) {
         return addList.addNode(n);
       }
+
       const sn = serializeNodeWithId(n, {
         doc: this.doc,
         mirror: this.mirror,
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index ba1f8c05aa..66a3bc03de 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -69,6 +69,9 @@ export type recordOptions<T> = {
   recordAfter?: 'DOMContentLoaded' | 'load';
   userTriggeredOnInput?: boolean;
   collectFonts?: boolean;
+  /**
+   * @deprecated please use `assetCapture` instead
+   */
   inlineImages?: boolean;
   assetCapture?: assetCaptureParam;
   plugins?: RecordPlugin[];
@@ -106,6 +109,9 @@ export type observerParam = {
   sampling: SamplingStrategy;
   recordDOM: boolean;
   recordCanvas: boolean;
+  /**
+   * @deprecated please use `assetCapture` instead
+   */
   inlineImages: boolean;
   userTriggeredOnInput: boolean;
   collectFonts: boolean;

From 180f7fd266e9d73387fb58d675bedce536ad59d1 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 21:05:22 +0100
Subject: [PATCH 027/102] Fix rrdom build

---
 packages/rrdom-nodejs/tsconfig.json |  3 +++
 packages/rrdom/jest.config.js       | 10 ++++++++++
 packages/rrdom/jest.setup.ts        |  3 +++
 3 files changed, 16 insertions(+)
 create mode 100644 packages/rrdom/jest.setup.ts

diff --git a/packages/rrdom-nodejs/tsconfig.json b/packages/rrdom-nodejs/tsconfig.json
index 0c2f119853..b59df3f7ef 100644
--- a/packages/rrdom-nodejs/tsconfig.json
+++ b/packages/rrdom-nodejs/tsconfig.json
@@ -29,6 +29,9 @@
     },
     {
       "path": "../rrweb-snapshot"
+    },
+    {
+      "path": "../types"
     }
   ]
 }
diff --git a/packages/rrdom/jest.config.js b/packages/rrdom/jest.config.js
index e5841e937d..3483edbdc2 100644
--- a/packages/rrdom/jest.config.js
+++ b/packages/rrdom/jest.config.js
@@ -2,4 +2,14 @@
 export default {
   preset: 'ts-jest',
   testEnvironment: 'node',
+  setupFiles: ['./jest.setup.ts'],
+  /**
+   * Keeps old (pre-jest 29) snapshot format
+   * its a bit ugly and harder to read than the new format,
+   * so we might want to remove this in its own PR
+   */
+  snapshotFormat: {
+    escapeString: true,
+    printBasicPrototype: true,
+  },
 };
diff --git a/packages/rrdom/jest.setup.ts b/packages/rrdom/jest.setup.ts
new file mode 100644
index 0000000000..7c97c034f2
--- /dev/null
+++ b/packages/rrdom/jest.setup.ts
@@ -0,0 +1,3 @@
+import { TextEncoder, TextDecoder } from 'util';
+
+Object.assign(global, { TextDecoder, TextEncoder });

From 015e412fe6bee2c626232dcbc9d28477a864ae7a Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 21:09:57 +0100
Subject: [PATCH 028/102] Fix deprecated test

---
 packages/rrweb-snapshot/test/integration.test.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts
index 10a3a38eed..9fb0144bce 100644
--- a/packages/rrweb-snapshot/test/integration.test.ts
+++ b/packages/rrweb-snapshot/test/integration.test.ts
@@ -264,7 +264,7 @@ iframe.contentDocument.querySelector('center').clientHeight
     assert(snapshot.includes('data:image/webp;base64,'));
   });
 
-  it('correctly saves blob:images in iframes offline', async () => {
+  it('[deprecated] correctly saves blob:images in iframes offline', async () => {
     const page: puppeteer.Page = await browser.newPage();
 
     await page.goto('http://localhost:3030/html/picture-blob-in-frame.html', {
@@ -279,6 +279,10 @@ iframe.contentDocument.querySelector('center').clientHeight
         inlineStylesheet: false,
         onIframeLoad: function(iframe, sn) {
           window.snapshot = sn;
+        },
+        assetCapture: {
+          origin: false,
+          objectURLs: false
         }
     })`);
     await waitForRAF(page);

From 27663f36a70c3a88b2b5a2ee517e4782510e94a1 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 21:47:37 +0100
Subject: [PATCH 029/102] Rename `assetCapture` to `captureAssets`

Also deprecate a couple inlineImages tests
---
 docs/recipes/assets.md                        |   10 +-
 docs/recipes/assets.zh_CN.md                  |   10 +-
 guide.md                                      |   12 +-
 guide.zh_CN.md                                |   60 +-
 packages/rrweb-snapshot/src/snapshot.ts       |    8 +-
 .../rrweb-snapshot/test/integration.test.ts   |    2 +-
 packages/rrweb/scripts/stream.js              |    2 +-
 packages/rrweb/src/record/index.ts            |    6 +-
 packages/rrweb/src/record/mutation.ts         |    2 +-
 .../src/record/observers/asset-manager.ts     |    8 +-
 packages/rrweb/src/types.ts                   |    8 +-
 .../__snapshots__/integration.test.ts.snap    | 1342 +++++++++++++++--
 .../test/__snapshots__/record.test.ts.snap    |   44 +-
 packages/rrweb/test/integration.test.ts       |   75 +-
 .../cross-origin-iframes.test.ts.snap         |   30 +-
 .../record/__snapshots__/webgl.test.ts.snap   |   16 +-
 packages/rrweb/test/record/asset.test.ts      |   28 +-
 packages/rrweb/test/utils.ts                  |    2 +-
 packages/types/src/index.ts                   |    4 +-
 19 files changed, 1394 insertions(+), 275 deletions(-)

diff --git a/docs/recipes/assets.md b/docs/recipes/assets.md
index e364b7eae8..0fc0c103cd 100644
--- a/docs/recipes/assets.md
+++ b/docs/recipes/assets.md
@@ -4,11 +4,11 @@
 
 ## Inline Images (Deprecated)
 
-The `inlineImages` configuration option is deprecated and should not be used anymore. It has some issues, namely rewriting events that are already emitted which might make you miss the inlined image if the event has already been sent to the server. Instead, use the `assetCapture` option to configure asset capture.
+The `inlineImages` configuration option is deprecated and should not be used anymore. It has some issues, namely rewriting events that are already emitted which might make you miss the inlined image if the event has already been sent to the server. Instead, use the `captureAssets` option to configure asset capture.
 
 ## Asset Capture Configuration
 
-The `assetCapture` configuration option allows you to customize the asset capture process. It is an object with the following properties:
+The `captureAssets` configuration option allows you to customize the asset capture process. It is an object with the following properties:
 
 - `objectURLs` (default: `true`): This property specifies whether to capture same-origin `blob:` assets using object URLs. Object URLs are created using the `URL.createObjectURL()` method. Setting `objectURLs` to `true` enables the capture of object URLs.
 
@@ -24,7 +24,7 @@ Here is the TypeScript type definition for the `recordOptions` object, which inc
 ```typescript
 export type recordOptions<T> = {
   // Other configuration options...
-  assetCapture?: {
+  captureAssets?: {
     objectURLs: boolean;
     origins: string[] | true | false;
   };
@@ -33,8 +33,8 @@ export type recordOptions<T> = {
 };
 ```
 
-This type definition shows that `assetCapture` is an optional property of the `recordOptions` object. It contains the `objectURLs` and `origins` properties, which have the same meanings as described above.
+This type definition shows that `captureAssets` is an optional property of the `recordOptions` object. It contains the `objectURLs` and `origins` properties, which have the same meanings as described above.
 
 ## Conclusion
 
-By configuring the `assetCapture` option in rrweb, you can control how assets like images are captured during the recording process. This allows you to customize which assets are included in the recorded interactions on your website.
+By configuring the `captureAssets` option in rrweb, you can control how assets like images are captured during the recording process. This allows you to customize which assets are included in the recorded interactions on your website.
diff --git a/docs/recipes/assets.zh_CN.md b/docs/recipes/assets.zh_CN.md
index fcd496b14a..7f0b6a9943 100644
--- a/docs/recipes/assets.zh_CN.md
+++ b/docs/recipes/assets.zh_CN.md
@@ -4,11 +4,11 @@
 
 ## 内联图像(已弃用)
 
-`inlineImages` 配置选项已被弃用,不应再使用。它存在一些问题,即重写已经发出的事件,这可能使您错过已发送到服务器的内联图像。相反,请使用 `assetCapture` 选项来配置资源捕获。
+`inlineImages` 配置选项已被弃用,不应再使用。它存在一些问题,即重写已经发出的事件,这可能使您错过已发送到服务器的内联图像。相反,请使用 `captureAssets` 选项来配置资源捕获。
 
 ## 资源捕获配置
 
-`assetCapture` 配置选项允许您自定义资源捕获过程。它是一个具有以下属性的对象:
+`captureAssets` 配置选项允许您自定义资源捕获过程。它是一个具有以下属性的对象:
 
 - `objectURLs`(默认值:`true`):此属性指定是否使用对象 URL 捕获同源 `blob:` 资源。对象 URL 是使用 `URL.createObjectURL()` 方法创建的。将 `objectURLs` 设置为 `true` 可以启用对象 URL 的捕获。
 
@@ -24,7 +24,7 @@
 ```typescript
 export type recordOptions<T> = {
   // 其他配置选项...
-  assetCapture?: {
+  captureAssets?: {
     objectURLs: boolean;
     origins: string[] | true | false;
   };
@@ -33,8 +33,8 @@ export type recordOptions<T> = {
 };
 ```
 
-这种类型定义表明 assetCapture 是 recordOptions 对象的一个可选属性。它包含 objectURLs 和 origins 属性,其含义与上述相同。
+这种类型定义表明 captureAssets 是 recordOptions 对象的一个可选属性。它包含 objectURLs 和 origins 属性,其含义与上述相同。
 
 ## 结论
 
-通过在 rrweb 中配置 assetCapture 选项,您可以控制在记录过程中如何捕获像图像这样的资源。这允许您
+通过在 rrweb 中配置 captureAssets 选项,您可以控制在记录过程中如何捕获像图像这样的资源。这允许您
diff --git a/guide.md b/guide.md
index 6469582d76..0a40cf2a4e 100644
--- a/guide.md
+++ b/guide.md
@@ -160,12 +160,12 @@ The parameter of `rrweb.record` accepts the following options.
 | recordCanvas             | false                                | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
 | recordCrossOriginIframes | false                                | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
 | recordAfter              | 'load'                               | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
-| ~inlineImages~           | false                                | whether to record the image content (deprecated, use `assetCapture` instead)                                                                                                                  |
-| assetCapture             | { objectURLs: true, origins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
-| collectFonts             | false                                              | whether to collect fonts in the website                                                                                                                                                       |
-| userTriggeredOnInput     | false                                              | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
-| plugins                  | []                                                 | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md)                                                                                               |
-| errorHandler             | -                                                  | A callback that is called if something inside of rrweb throws an error. The callback receives the error as argument.                                                                          |
+| ~inlineImages~           | false                                | whether to record the image content (deprecated, use `captureAssets` instead)                                                                                                                 |
+| captureAssets            | { objectURLs: true, origins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
+| collectFonts             | false                                | whether to collect fonts in the website                                                                                                                                                       |
+| userTriggeredOnInput     | false                                | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
+| plugins                  | []                                   | load plugins to provide extended record functions. [What is plugins?](./docs/recipes/plugin.md)                                                                                               |
+| errorHandler             | -                                    | A callback that is called if something inside of rrweb throws an error. The callback receives the error as argument.                                                                          |
 
 #### Privacy
 
diff --git a/guide.zh_CN.md b/guide.zh_CN.md
index c5f37faf7b..09f57e442d 100644
--- a/guide.zh_CN.md
+++ b/guide.zh_CN.md
@@ -131,36 +131,36 @@ setInterval(save, 10 * 1000);
 
 `rrweb.record(config)` 的 config 部分接受以下参数
 
-| key                      | 默认值             | 功能                                                                                                                                                                                  |
-| ------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| emit                     | 必填               | 获取当前录制的数据                                                                                                                                                                    |
-| checkoutEveryNth         | -                  | 每 N 次事件重新制作一次全量快照<br />详见[“重新制作快照”](#重新制作快照)章节                                                                                                          |
-| checkoutEveryNms         | -                  | 每 N 毫秒重新制作一次全量快照<br />详见[“重新制作快照”](#重新制作快照)章节                                                                                                            |
-| blockClass               | 'rr-block'         | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节                                                                                                               |
-| blockSelector            | null               | 所有 element.matches(blockSelector)为 true 的元素都不会被录制,回放时取而代之的是一个同等宽高的占位元素                                                                               |
-| ignoreClass              | 'rr-ignore'        | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节                                                                                                               |
-| ignoreCSSAttributes      | null               | 应该被忽略的 CSS 属性数组                                                                                                                                                             |
-| maskTextClass            | 'rr-mask'          | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节                                                                                                     |
-| maskTextSelector         | null               | 所有 element.matches(maskTextSelector)为 true 的元素及其子元素的 text 内容将会被屏蔽                                                                                                  |
-| maskAllInputs            | false              | 将所有输入内容记录为 \*                                                                                                                                                               |
-| maskInputOptions         | { password: true } | 选择将特定类型的输入框内容记录为 \*<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
-| maskInputFn              | -                  | 自定义特定类型的输入框内容记录逻辑                                                                                                                                                    |
-| maskTextFn               | -                  | 自定义文字内容的记录逻辑                                                                                                                                                              |
-| slimDOMOptions           | {}                 | 去除 DOM 中不必要的部分 <br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)           |
-| inlineStylesheet         | true               | 是否将样式表内联                                                                                                                                                                      |
-| hooks                    | {}                 | 各类事件的回调<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                 |
-| packFn                   | -                  | 数据压缩函数,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md)                                                                                                            |
-| sampling                 | -                  | 数据抽样策略,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md)                                                                                                            |
-| dataURLOptions           | {}                 | Canvas 图像快照的格式和质量,这个参数将传递给 OffscreenCanvas.convertToBlob(),使用这个参数能有效减小录制数据的大小                                                                    |
-| recordCanvas             | false              | 是否记录 canvas 内容, 可用选项:`false`, `true`                                                                                                                                       |
-| recordCrossOriginIframes | false              | 是否记录 cross origin iframes。 必须在每个子 iframe 中注入 rrweb 才能使其工作。 可用选项:`false`, `true`                                                                             |
-| recordAfter              | 'load'             | 如果 document 还没有加载完成,recorder 将会在指定的事件触发后开始录制。可用选项: `DOMContentLoaded`, `load`                                                                          |
-| ~inlineImages~           | false              | 是否将图片内容记内联录制                                                                                                                                                              |
-| assetCapture | { objectURLs: true, origins: false } | 配置资源(图像)捕获并生成异步资源事件。<br/>有关更多信息,请参阅[资源捕获文档](./docs/recipes/assets.zh_CN.md) |
-| collectFonts             | false              | 是否记录页面中的字体文件                                                                                                                                                              |
-| userTriggeredOnInput     | false              | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495)                                                                                                                  |
-| plugins                  | []                 | 加载插件以获得额外的录制功能. [什么是插件?](./docs/recipes/plugin.zh_CN.md)                                                                                                          |
-| errorHandler             | -                  | 一个可以定制化处理错误的回调函数,它的参数是错误对象。如果 rrweb recorder 内部的某些内容抛出错误,则会调用该回调。                                                                    |
+| key                      | 默认值                               | 功能                                                                                                                                                                                  |
+| ------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| emit                     | 必填                                 | 获取当前录制的数据                                                                                                                                                                    |
+| checkoutEveryNth         | -                                    | 每 N 次事件重新制作一次全量快照<br />详见[“重新制作快照”](#重新制作快照)章节                                                                                                          |
+| checkoutEveryNms         | -                                    | 每 N 毫秒重新制作一次全量快照<br />详见[“重新制作快照”](#重新制作快照)章节                                                                                                            |
+| blockClass               | 'rr-block'                           | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节                                                                                                               |
+| blockSelector            | null                                 | 所有 element.matches(blockSelector)为 true 的元素都不会被录制,回放时取而代之的是一个同等宽高的占位元素                                                                               |
+| ignoreClass              | 'rr-ignore'                          | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节                                                                                                               |
+| ignoreCSSAttributes      | null                                 | 应该被忽略的 CSS 属性数组                                                                                                                                                             |
+| maskTextClass            | 'rr-mask'                            | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节                                                                                                     |
+| maskTextSelector         | null                                 | 所有 element.matches(maskTextSelector)为 true 的元素及其子元素的 text 内容将会被屏蔽                                                                                                  |
+| maskAllInputs            | false                                | 将所有输入内容记录为 \*                                                                                                                                                               |
+| maskInputOptions         | { password: true }                   | 选择将特定类型的输入框内容记录为 \*<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) |
+| maskInputFn              | -                                    | 自定义特定类型的输入框内容记录逻辑                                                                                                                                                    |
+| maskTextFn               | -                                    | 自定义文字内容的记录逻辑                                                                                                                                                              |
+| slimDOMOptions           | {}                                   | 去除 DOM 中不必要的部分 <br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108)           |
+| inlineStylesheet         | true                                 | 是否将样式表内联                                                                                                                                                                      |
+| hooks                    | {}                                   | 各类事件的回调<br />类型详见[列表](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207)                                                 |
+| packFn                   | -                                    | 数据压缩函数,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md)                                                                                                            |
+| sampling                 | -                                    | 数据抽样策略,详见[优化存储策略](./docs/recipes/optimize-storage.zh_CN.md)                                                                                                            |
+| dataURLOptions           | {}                                   | Canvas 图像快照的格式和质量,这个参数将传递给 OffscreenCanvas.convertToBlob(),使用这个参数能有效减小录制数据的大小                                                                    |
+| recordCanvas             | false                                | 是否记录 canvas 内容, 可用选项:`false`, `true`                                                                                                                                       |
+| recordCrossOriginIframes | false                                | 是否记录 cross origin iframes。 必须在每个子 iframe 中注入 rrweb 才能使其工作。 可用选项:`false`, `true`                                                                             |
+| recordAfter              | 'load'                               | 如果 document 还没有加载完成,recorder 将会在指定的事件触发后开始录制。可用选项: `DOMContentLoaded`, `load`                                                                          |
+| ~inlineImages~           | false                                | 是否将图片内容记内联录制                                                                                                                                                              |
+| captureAssets            | { objectURLs: true, origins: false } | 配置资源(图像)捕获并生成异步资源事件。<br/>有关更多信息,请参阅[资源捕获文档](./docs/recipes/assets.zh_CN.md)                                                                       |
+| collectFonts             | false                                | 是否记录页面中的字体文件                                                                                                                                                              |
+| userTriggeredOnInput     | false                                | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495)                                                                                                                  |
+| plugins                  | []                                   | 加载插件以获得额外的录制功能. [什么是插件?](./docs/recipes/plugin.zh_CN.md)                                                                                                          |
+| errorHandler             | -                                    | 一个可以定制化处理错误的回调函数,它的参数是错误对象。如果 rrweb recorder 内部的某些内容抛出错误,则会调用该回调。                                                                    |
 
 #### 隐私
 
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index d125434df1..04155f3518 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -463,7 +463,7 @@ function serializeNode(
     maskInputFn: MaskInputFn | undefined;
     dataURLOptions?: DataURLOptions;
     /**
-     * @deprecated please use `assetCapture` instead
+     * @deprecated please use `captureAssets` instead
      */
     inlineImages: boolean;
     recordCanvas: boolean;
@@ -631,7 +631,7 @@ function serializeElementNode(
     maskInputFn: MaskInputFn | undefined;
     dataURLOptions?: DataURLOptions;
     /**
-     * @deprecated please use `assetCapture` instead
+     * @deprecated please use `captureAssets` instead
      */
     inlineImages: boolean;
     recordCanvas: boolean;
@@ -1004,7 +1004,7 @@ export function serializeNodeWithId(
     dataURLOptions?: DataURLOptions;
     keepIframeSrcFn?: KeepIframeSrcFn;
     /**
-     * @deprecated please use `assetCapture` instead
+     * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
     recordCanvas?: boolean;
@@ -1322,7 +1322,7 @@ function snapshot(
     slimDOM?: 'all' | boolean | SlimDOMOptions;
     dataURLOptions?: DataURLOptions;
     /**
-     * @deprecated please use `assetCapture` instead
+     * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
     recordCanvas?: boolean;
diff --git a/packages/rrweb-snapshot/test/integration.test.ts b/packages/rrweb-snapshot/test/integration.test.ts
index 9fb0144bce..989e48cf54 100644
--- a/packages/rrweb-snapshot/test/integration.test.ts
+++ b/packages/rrweb-snapshot/test/integration.test.ts
@@ -280,7 +280,7 @@ iframe.contentDocument.querySelector('center').clientHeight
         onIframeLoad: function(iframe, sn) {
           window.snapshot = sn;
         },
-        assetCapture: {
+        captureAssets: {
           origin: false,
           objectURLs: false
         }
diff --git a/packages/rrweb/scripts/stream.js b/packages/rrweb/scripts/stream.js
index 7b66c551e9..108f8f303a 100644
--- a/packages/rrweb/scripts/stream.js
+++ b/packages/rrweb/scripts/stream.js
@@ -66,7 +66,7 @@ async function injectRecording(frame) {
             recordCanvas: false,
             recordCrossOriginIframes: true,
             collectFonts: true,
-            assetCapture: {
+            captureAssets: {
               objectURLs: true,
               origins: false,
             },
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 1410c45396..e361101bb3 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -83,7 +83,7 @@ function record<T = eventWithTime>(
     userTriggeredOnInput = false,
     collectFonts = false,
     inlineImages = false,
-    assetCapture = {
+    captureAssets = {
       objectURLs: true,
       origins: false,
     },
@@ -322,7 +322,7 @@ function record<T = eventWithTime>(
   assetManager = new AssetManager({
     mutationCb: wrappedAssetEmit,
     win: window,
-    assetCapture,
+    captureAssets,
   });
 
   const shadowDomManager = new ShadowDomManager({
@@ -363,7 +363,7 @@ function record<T = eventWithTime>(
           href: window.location.href,
           width: getWindowWidth(),
           height: getWindowHeight(),
-          assetCapture,
+          captureAssets,
         },
       },
       isCheckout,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index cedf260425..bbce696ec5 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -182,7 +182,7 @@ export default class MutationBuffer {
   private keepIframeSrcFn: observerParam['keepIframeSrcFn'];
   private recordCanvas: observerParam['recordCanvas'];
   /**
-   * @deprecated please use `assetCapture` instead
+   * @deprecated please use `captureAssets` instead
    */
   private inlineImages: observerParam['inlineImages'];
   private slimDOMOptions: observerParam['slimDOMOptions'];
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 5f92cd1da4..6ce88c2143 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -18,7 +18,7 @@ export default class AssetManager {
   private resetHandlers: listenerHandler[] = [];
   private mutationCb: assetCallback;
   public readonly config: Exclude<
-    recordOptions<eventWithTime>['assetCapture'],
+    recordOptions<eventWithTime>['captureAssets'],
     undefined
   >;
 
@@ -33,15 +33,15 @@ export default class AssetManager {
   constructor(options: {
     mutationCb: assetCallback;
     win: IWindow;
-    assetCapture: Exclude<
-      recordOptions<eventWithTime>['assetCapture'],
+    captureAssets: Exclude<
+      recordOptions<eventWithTime>['captureAssets'],
       undefined
     >;
   }) {
     const { win } = options;
 
     this.mutationCb = options.mutationCb;
-    this.config = options.assetCapture;
+    this.config = options.captureAssets;
 
     const urlObjectMap = this.urlObjectMap;
 
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index 66a3bc03de..3cf0cc2fe8 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -37,7 +37,7 @@ import type {
   styleDeclarationCallback,
   styleSheetRuleCallback,
   viewportResizeCallback,
-  assetCaptureParam,
+  captureAssetsParam,
 } from '@rrweb/types';
 import type ProcessedNodeManager from './record/processed-node-manager';
 import type AssetManager from './record/observers/asset-manager';
@@ -70,10 +70,10 @@ export type recordOptions<T> = {
   userTriggeredOnInput?: boolean;
   collectFonts?: boolean;
   /**
-   * @deprecated please use `assetCapture` instead
+   * @deprecated please use `captureAssets` instead
    */
   inlineImages?: boolean;
-  assetCapture?: assetCaptureParam;
+  captureAssets?: captureAssetsParam;
   plugins?: RecordPlugin[];
   // departed, please use sampling options
   mousemoveWait?: number;
@@ -110,7 +110,7 @@ export type observerParam = {
   recordDOM: boolean;
   recordCanvas: boolean;
   /**
-   * @deprecated please use `assetCapture` instead
+   * @deprecated please use `captureAssets` instead
    */
   inlineImages: boolean;
   userTriggeredOnInput: boolean;
diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 3f15913274..27d55dff60 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -1,5 +1,1119 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`record integration tests [DEPRECATED] should record images inside iframe with blob url 1`] = `
+"[
+  {
+    \\"type\\": 0,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 1,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 4,
+    \\"data\\": {
+      \\"href\\": \\"about:blank\\",
+      \\"width\\": 1920,
+      \\"height\\": 1080,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
+    }
+  },
+  {
+    \\"type\\": 2,
+    \\"data\\": {
+      \\"node\\": {
+        \\"type\\": 0,
+        \\"childNodes\\": [
+          {
+            \\"type\\": 1,
+            \\"name\\": \\"html\\",
+            \\"publicId\\": \\"\\",
+            \\"systemId\\": \\"\\",
+            \\"id\\": 2
+          },
+          {
+            \\"type\\": 2,
+            \\"tagName\\": \\"html\\",
+            \\"attributes\\": {
+              \\"lang\\": \\"en\\"
+            },
+            \\"childNodes\\": [
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"head\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 5
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"charset\\": \\"UTF-8\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 6
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 7
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"name\\": \\"viewport\\",
+                      \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 8
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 9
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"title\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"Frame with image\\",
+                        \\"id\\": 11
+                      }
+                    ],
+                    \\"id\\": 10
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\",
+                    \\"id\\": 12
+                  }
+                ],
+                \\"id\\": 4
+              },
+              {
+                \\"type\\": 3,
+                \\"textContent\\": \\"\\\\n  \\",
+                \\"id\\": 13
+              },
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"body\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 15
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"iframe\\",
+                    \\"attributes\\": {
+                      \\"id\\": \\"four\\",
+                      \\"frameborder\\": \\"0\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 16
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\\\n    \\",
+                    \\"id\\": 17
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"script\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                        \\"id\\": 19
+                      }
+                    ],
+                    \\"id\\": 18
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\\\n    \\\\n\\\\n\\",
+                    \\"id\\": 20
+                  }
+                ],
+                \\"id\\": 14
+              }
+            ],
+            \\"id\\": 3
+          }
+        ],
+        \\"id\\": 1
+      },
+      \\"initialOffset\\": {
+        \\"left\\": 0,
+        \\"top\\": 0
+      }
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"adds\\": [
+        {
+          \\"parentId\\": 16,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 0,
+            \\"childNodes\\": [
+              {
+                \\"type\\": 1,
+                \\"name\\": \\"html\\",
+                \\"publicId\\": \\"\\",
+                \\"systemId\\": \\"\\",
+                \\"rootId\\": 21,
+                \\"id\\": 22
+              },
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"html\\",
+                \\"attributes\\": {
+                  \\"lang\\": \\"en\\"
+                },
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"head\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 25
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"charset\\": \\"UTF-8\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 21,
+                        \\"id\\": 26
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 27
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"http-equiv\\": \\"X-UA-Compatible\\",
+                          \\"content\\": \\"IE=edge\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 21,
+                        \\"id\\": 28
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 29
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"name\\": \\"viewport\\",
+                          \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 21,
+                        \\"id\\": 30
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 31
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"title\\",
+                        \\"attributes\\": {},
+                        \\"childNodes\\": [
+                          {
+                            \\"type\\": 3,
+                            \\"textContent\\": \\"Image with blob:url\\",
+                            \\"rootId\\": 21,
+                            \\"id\\": 33
+                          }
+                        ],
+                        \\"rootId\\": 21,
+                        \\"id\\": 32
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n  \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 34
+                      }
+                    ],
+                    \\"rootId\\": 21,
+                    \\"id\\": 24
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\",
+                    \\"rootId\\": 21,
+                    \\"id\\": 35
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"body\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 37
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"script\\",
+                        \\"attributes\\": {},
+                        \\"childNodes\\": [
+                          {
+                            \\"type\\": 3,
+                            \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                            \\"rootId\\": 21,
+                            \\"id\\": 39
+                          }
+                        ],
+                        \\"rootId\\": 21,
+                        \\"id\\": 38
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n  \\\\n\\\\n\\",
+                        \\"rootId\\": 21,
+                        \\"id\\": 40
+                      }
+                    ],
+                    \\"rootId\\": 21,
+                    \\"id\\": 36
+                  }
+                ],
+                \\"rootId\\": 21,
+                \\"id\\": 23
+              }
+            ],
+            \\"id\\": 21
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"isAttachIframe\\": true
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"removes\\": [],
+      \\"adds\\": [
+        {
+          \\"parentId\\": 36,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 2,
+            \\"tagName\\": \\"img\\",
+            \\"attributes\\": {
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
+              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+            },
+            \\"childNodes\\": [],
+            \\"rootId\\": 21,
+            \\"id\\": 41
+          }
+        }
+      ]
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 41,
+          \\"attributes\\": {
+            \\"crossorigin\\": \\"anonymous\\"
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 41,
+          \\"attributes\\": {
+            \\"crossorigin\\": null
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  }
+]"
+`;
+
+exports[`record integration tests [DEPRECATED] should record images inside iframe with blob url after iframe was reloaded 1`] = `
+"[
+  {
+    \\"type\\": 0,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 1,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 4,
+    \\"data\\": {
+      \\"href\\": \\"about:blank\\",
+      \\"width\\": 1920,
+      \\"height\\": 1080,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
+    }
+  },
+  {
+    \\"type\\": 2,
+    \\"data\\": {
+      \\"node\\": {
+        \\"type\\": 0,
+        \\"childNodes\\": [
+          {
+            \\"type\\": 1,
+            \\"name\\": \\"html\\",
+            \\"publicId\\": \\"\\",
+            \\"systemId\\": \\"\\",
+            \\"id\\": 2
+          },
+          {
+            \\"type\\": 2,
+            \\"tagName\\": \\"html\\",
+            \\"attributes\\": {
+              \\"lang\\": \\"en\\"
+            },
+            \\"childNodes\\": [
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"head\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 5
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"charset\\": \\"UTF-8\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 6
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 7
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"name\\": \\"viewport\\",
+                      \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 8
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 9
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"title\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"Frame 2\\",
+                        \\"id\\": 11
+                      }
+                    ],
+                    \\"id\\": 10
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\",
+                    \\"id\\": 12
+                  }
+                ],
+                \\"id\\": 4
+              },
+              {
+                \\"type\\": 3,
+                \\"textContent\\": \\"\\\\n  \\",
+                \\"id\\": 13
+              },
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"body\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    frame 2\\\\n  \\\\n    \\",
+                    \\"id\\": 15
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"script\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                        \\"id\\": 17
+                      }
+                    ],
+                    \\"id\\": 16
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\\\n    \\\\n  \\",
+                    \\"id\\": 18
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"script\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                        \\"id\\": 20
+                      }
+                    ],
+                    \\"id\\": 19
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n\\\\n\\",
+                    \\"id\\": 21
+                  }
+                ],
+                \\"id\\": 14
+              }
+            ],
+            \\"id\\": 3
+          }
+        ],
+        \\"id\\": 1
+      },
+      \\"initialOffset\\": {
+        \\"left\\": 0,
+        \\"top\\": 0
+      }
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"removes\\": [],
+      \\"adds\\": [
+        {
+          \\"parentId\\": 14,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 2,
+            \\"tagName\\": \\"iframe\\",
+            \\"attributes\\": {
+              \\"id\\": \\"five\\"
+            },
+            \\"childNodes\\": [],
+            \\"id\\": 22
+          }
+        }
+      ]
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"adds\\": [
+        {
+          \\"parentId\\": 22,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 0,
+            \\"childNodes\\": [
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"html\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"head\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [],
+                    \\"rootId\\": 23,
+                    \\"id\\": 25
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"body\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [],
+                    \\"rootId\\": 23,
+                    \\"id\\": 26
+                  }
+                ],
+                \\"rootId\\": 23,
+                \\"id\\": 24
+              }
+            ],
+            \\"compatMode\\": \\"BackCompat\\",
+            \\"id\\": 23
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"isAttachIframe\\": true
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"adds\\": [
+        {
+          \\"parentId\\": 22,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 0,
+            \\"childNodes\\": [
+              {
+                \\"type\\": 1,
+                \\"name\\": \\"html\\",
+                \\"publicId\\": \\"\\",
+                \\"systemId\\": \\"\\",
+                \\"rootId\\": 27,
+                \\"id\\": 28
+              },
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"html\\",
+                \\"attributes\\": {
+                  \\"lang\\": \\"en\\"
+                },
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"head\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 31
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"charset\\": \\"UTF-8\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 27,
+                        \\"id\\": 32
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 33
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"http-equiv\\": \\"X-UA-Compatible\\",
+                          \\"content\\": \\"IE=edge\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 27,
+                        \\"id\\": 34
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 35
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"meta\\",
+                        \\"attributes\\": {
+                          \\"name\\": \\"viewport\\",
+                          \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+                        },
+                        \\"childNodes\\": [],
+                        \\"rootId\\": 27,
+                        \\"id\\": 36
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 37
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"title\\",
+                        \\"attributes\\": {},
+                        \\"childNodes\\": [
+                          {
+                            \\"type\\": 3,
+                            \\"textContent\\": \\"Image with blob:url\\",
+                            \\"rootId\\": 27,
+                            \\"id\\": 39
+                          }
+                        ],
+                        \\"rootId\\": 27,
+                        \\"id\\": 38
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n  \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 40
+                      }
+                    ],
+                    \\"rootId\\": 27,
+                    \\"id\\": 30
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\",
+                    \\"rootId\\": 27,
+                    \\"id\\": 41
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"body\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n    \\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 43
+                      },
+                      {
+                        \\"type\\": 2,
+                        \\"tagName\\": \\"script\\",
+                        \\"attributes\\": {},
+                        \\"childNodes\\": [
+                          {
+                            \\"type\\": 3,
+                            \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                            \\"rootId\\": 27,
+                            \\"id\\": 45
+                          }
+                        ],
+                        \\"rootId\\": 27,
+                        \\"id\\": 44
+                      },
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"\\\\n  \\\\n\\\\n\\",
+                        \\"rootId\\": 27,
+                        \\"id\\": 46
+                      }
+                    ],
+                    \\"rootId\\": 27,
+                    \\"id\\": 42
+                  }
+                ],
+                \\"rootId\\": 27,
+                \\"id\\": 29
+              }
+            ],
+            \\"id\\": 27
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"isAttachIframe\\": true
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"removes\\": [],
+      \\"adds\\": [
+        {
+          \\"parentId\\": 42,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 2,
+            \\"tagName\\": \\"img\\",
+            \\"attributes\\": {
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
+              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+            },
+            \\"childNodes\\": [],
+            \\"rootId\\": 27,
+            \\"id\\": 47
+          }
+        }
+      ]
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 47,
+          \\"attributes\\": {
+            \\"crossorigin\\": \\"anonymous\\"
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 47,
+          \\"attributes\\": {
+            \\"crossorigin\\": null
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  }
+]"
+`;
+
+exports[`record integration tests [DEPRECATED] should record images with blob url 1`] = `
+"[
+  {
+    \\"type\\": 0,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 1,
+    \\"data\\": {}
+  },
+  {
+    \\"type\\": 4,
+    \\"data\\": {
+      \\"href\\": \\"about:blank\\",
+      \\"width\\": 1920,
+      \\"height\\": 1080,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": false,
+        \\"origins\\": false
+      }
+    }
+  },
+  {
+    \\"type\\": 2,
+    \\"data\\": {
+      \\"node\\": {
+        \\"type\\": 0,
+        \\"childNodes\\": [
+          {
+            \\"type\\": 1,
+            \\"name\\": \\"html\\",
+            \\"publicId\\": \\"\\",
+            \\"systemId\\": \\"\\",
+            \\"id\\": 2
+          },
+          {
+            \\"type\\": 2,
+            \\"tagName\\": \\"html\\",
+            \\"attributes\\": {
+              \\"lang\\": \\"en\\"
+            },
+            \\"childNodes\\": [
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"head\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 5
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"charset\\": \\"UTF-8\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 6
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 7
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"http-equiv\\": \\"X-UA-Compatible\\",
+                      \\"content\\": \\"IE=edge\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 8
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 9
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"meta\\",
+                    \\"attributes\\": {
+                      \\"name\\": \\"viewport\\",
+                      \\"content\\": \\"width=device-width, initial-scale=1.0\\"
+                    },
+                    \\"childNodes\\": [],
+                    \\"id\\": 10
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 11
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"title\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"Image with blob:url\\",
+                        \\"id\\": 13
+                      }
+                    ],
+                    \\"id\\": 12
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\",
+                    \\"id\\": 14
+                  }
+                ],
+                \\"id\\": 4
+              },
+              {
+                \\"type\\": 3,
+                \\"textContent\\": \\"\\\\n  \\",
+                \\"id\\": 15
+              },
+              {
+                \\"type\\": 2,
+                \\"tagName\\": \\"body\\",
+                \\"attributes\\": {},
+                \\"childNodes\\": [
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\",
+                    \\"id\\": 17
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"script\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                        \\"id\\": 19
+                      }
+                    ],
+                    \\"id\\": 18
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n  \\\\n    \\",
+                    \\"id\\": 20
+                  },
+                  {
+                    \\"type\\": 2,
+                    \\"tagName\\": \\"script\\",
+                    \\"attributes\\": {},
+                    \\"childNodes\\": [
+                      {
+                        \\"type\\": 3,
+                        \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
+                        \\"id\\": 22
+                      }
+                    ],
+                    \\"id\\": 21
+                  },
+                  {
+                    \\"type\\": 3,
+                    \\"textContent\\": \\"\\\\n    \\\\n    \\\\n\\\\n\\",
+                    \\"id\\": 23
+                  }
+                ],
+                \\"id\\": 16
+              }
+            ],
+            \\"id\\": 3
+          }
+        ],
+        \\"id\\": 1
+      },
+      \\"initialOffset\\": {
+        \\"left\\": 0,
+        \\"top\\": 0
+      }
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [],
+      \\"removes\\": [],
+      \\"adds\\": [
+        {
+          \\"parentId\\": 16,
+          \\"nextId\\": null,
+          \\"node\\": {
+            \\"type\\": 2,
+            \\"tagName\\": \\"img\\",
+            \\"attributes\\": {
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
+              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+            },
+            \\"childNodes\\": [],
+            \\"id\\": 24
+          }
+        }
+      ]
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 24,
+          \\"attributes\\": {
+            \\"crossorigin\\": \\"anonymous\\"
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  },
+  {
+    \\"type\\": 3,
+    \\"data\\": {
+      \\"source\\": 0,
+      \\"texts\\": [],
+      \\"attributes\\": [
+        {
+          \\"id\\": 24,
+          \\"attributes\\": {
+            \\"crossorigin\\": null
+          }
+        }
+      ],
+      \\"removes\\": [],
+      \\"adds\\": []
+    }
+  }
+]"
+`;
+
 exports[`record integration tests can correctly serialize a shader and multiple webgl contexts 1`] = `
 "[
   {
@@ -16,7 +1130,7 @@ exports[`record integration tests can correctly serialize a shader and multiple
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -434,7 +1548,7 @@ exports[`record integration tests can freeze mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -618,7 +1732,7 @@ exports[`record integration tests can mask character data mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1062,7 +2176,7 @@ exports[`record integration tests can record attribute mutation 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1237,7 +2351,7 @@ exports[`record integration tests can record character data muatations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1419,7 +2533,7 @@ exports[`record integration tests can record childList mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1599,7 +2713,7 @@ exports[`record integration tests can record clicks 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1885,7 +2999,7 @@ exports[`record integration tests can record form interactions 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2672,7 +3786,7 @@ exports[`record integration tests can record node mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3651,7 +4765,7 @@ exports[`record integration tests can record style changes compactly and preserv
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -4145,7 +5259,7 @@ exports[`record integration tests can use maskInputOptions to configure which ty
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -5020,7 +6134,7 @@ exports[`record integration tests handles null attribute values 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -5215,7 +6329,7 @@ exports[`record integration tests mutations should work when blocked class is un
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -5984,7 +7098,7 @@ exports[`record integration tests should handle recursive console messages 1`] =
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -6168,7 +7282,7 @@ exports[`record integration tests should mask inputs via function call 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -7042,7 +8156,7 @@ exports[`record integration tests should mask password value attribute with mask
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -7493,7 +8607,7 @@ exports[`record integration tests should mask texts 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -7795,7 +8909,7 @@ exports[`record integration tests should mask texts using maskTextFn 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -8097,7 +9211,7 @@ exports[`record integration tests should nest record iframe 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -8860,7 +9974,7 @@ exports[`record integration tests should not record blocked elements and its chi
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -9044,7 +10158,7 @@ exports[`record integration tests should not record blocked elements dynamically
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -9254,7 +10368,7 @@ exports[`record integration tests should not record input events on ignored elem
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -9669,7 +10783,7 @@ exports[`record integration tests should not record input values if dynamically
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -9905,7 +11019,7 @@ exports[`record integration tests should not record input values if maskAllInput
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -10779,7 +11893,7 @@ exports[`record integration tests should record DOM node movement 1 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -11050,7 +12164,7 @@ exports[`record integration tests should record DOM node movement 2 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -11328,7 +12442,7 @@ exports[`record integration tests should record after DOMContentLoaded event 1`]
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -11419,7 +12533,7 @@ exports[`record integration tests should record canvas mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -11642,7 +12756,7 @@ exports[`record integration tests should record console messages 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -12152,7 +13266,7 @@ exports[`record integration tests should record dynamic CSS changes 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -12536,7 +13650,6 @@ exports[`record integration tests should record dynamic CSS changes 1`] = `
   }
 ]"
 `;
-
 exports[`record integration tests should record images inside iframe with blob url 1`] = `
 "[
   {
@@ -12553,8 +13666,8 @@ exports[`record integration tests should record images inside iframe with blob u
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
-        \\"objectURLs\\": false,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": true,
         \\"origins\\": false
       }
     }
@@ -12886,8 +13999,7 @@ exports[`record integration tests should record images inside iframe with blob u
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 21,
@@ -12898,37 +14010,19 @@ exports[`record integration tests should record images inside iframe with blob u
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 41,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 41,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/2191dd12-9b7d-440a-88a1-60451e2cbae5\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -12950,8 +14044,8 @@ exports[`record integration tests should record images inside iframe with blob u
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
-        \\"objectURLs\\": false,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": true,
         \\"origins\\": false
       }
     }
@@ -13358,8 +14452,7 @@ exports[`record integration tests should record images inside iframe with blob u
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 27,
@@ -13370,37 +14463,19 @@ exports[`record integration tests should record images inside iframe with blob u
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 47,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 47,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/1786bb8a-e2e5-4c5c-b2a5-ba9875e40c65\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -13422,8 +14497,8 @@ exports[`record integration tests should record images with blob url 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
-        \\"objectURLs\\": false,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": true,
         \\"origins\\": false
       }
     }
@@ -13604,8 +14679,7 @@ exports[`record integration tests should record images with blob url 1`] = `
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"id\\": 24
@@ -13615,37 +14689,19 @@ exports[`record integration tests should record images with blob url 1`] = `
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 24,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 24,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/ed823e22-cc3c-436a-a460-d6dce1bd1ee9\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -13667,7 +14723,7 @@ exports[`record integration tests should record input userTriggered values if us
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -14571,7 +15627,7 @@ exports[`record integration tests should record moved shadow DOM 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -14744,7 +15800,7 @@ exports[`record integration tests should record moved shadow DOM 2 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -14931,7 +15987,7 @@ exports[`record integration tests should record mutations in iframes accross pag
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -15244,7 +16300,7 @@ exports[`record integration tests should record nested iframes and shadow doms 1
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -15595,7 +16651,7 @@ exports[`record integration tests should record shadow DOM 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -16049,7 +17105,7 @@ exports[`record integration tests should record shadow DOM 2 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -16171,7 +17227,7 @@ exports[`record integration tests should record shadow DOM 3 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -16293,7 +17349,7 @@ exports[`record integration tests should record shadow doms polyfilled by shadyd
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -16543,7 +17599,7 @@ exports[`record integration tests should record shadow doms polyfilled by synthe
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -16821,7 +17877,7 @@ exports[`record integration tests should record webgl canvas mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -17040,7 +18096,7 @@ exports[`record integration tests should unmask texts using maskTextFn 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -17342,7 +18398,7 @@ exports[`record integration tests will serialize node before record 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap
index 0bd7c0def4..01a3796d2a 100644
--- a/packages/rrweb/test/__snapshots__/record.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap
@@ -8,7 +8,7 @@ exports[`record aggregates mutations 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -98,7 +98,7 @@ exports[`record can add custom event 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -196,7 +196,7 @@ exports[`record captures CORS stylesheets that are still loading 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -303,7 +303,7 @@ exports[`record captures adopted stylesheets in nested shadow doms and iframes 1
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -773,7 +773,7 @@ exports[`record captures adopted stylesheets in shadow doms and iframe 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1173,7 +1173,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1277,7 +1277,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1386,7 +1386,7 @@ exports[`record captures inserted style text nodes correctly 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1520,7 +1520,7 @@ exports[`record captures mutations on adopted stylesheets 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1824,7 +1824,7 @@ exports[`record captures nested stylesheet rules 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1977,7 +1977,7 @@ exports[`record captures style property changes 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2110,7 +2110,7 @@ exports[`record captures stylesheet rules 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2252,7 +2252,7 @@ exports[`record captures stylesheets in iframes with \`blob:\` url 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2400,7 +2400,7 @@ exports[`record captures stylesheets with \`blob:\` url 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2492,7 +2492,7 @@ exports[`record iframes captures stylesheet mutations in iframes 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2731,7 +2731,7 @@ exports[`record is safe to checkout during async callbacks 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2866,7 +2866,7 @@ exports[`record is safe to checkout during async callbacks 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2995,7 +2995,7 @@ exports[`record loading stylesheets captures stylesheets in iframes that are sti
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3335,7 +3335,7 @@ exports[`record loading stylesheets captures stylesheets that are still loading
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3519,7 +3519,7 @@ exports[`record no need for attribute mutations on adds 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3643,7 +3643,7 @@ exports[`record should record scroll position 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3767,7 +3767,7 @@ exports[`record without CSSGroupingRule support captures nested stylesheet rules
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index 24953ac938..7b81823414 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -831,14 +831,14 @@ describe('record integration tests', function (this: ISuite) {
     assertSnapshot(snapshots);
   });
 
-  it('should record images with blob url', async () => {
+  it('[DEPRECATED] should record images with blob url', async () => {
     const page: puppeteer.Page = await browser.newPage();
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     page.setContent(
       getHtml.call(this, 'image-blob-url.html', {
         inlineImages: true,
-        assetCapture: { objectURLs: false, origins: false },
+        captureAssets: { objectURLs: false, origins: false },
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
@@ -851,14 +851,14 @@ describe('record integration tests', function (this: ISuite) {
     assertSnapshot(snapshots);
   });
 
-  it('should record images inside iframe with blob url', async () => {
+  it('[DEPRECATED] should record images inside iframe with blob url', async () => {
     const page: puppeteer.Page = await browser.newPage();
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     await page.setContent(
       getHtml.call(this, 'frame-image-blob-url.html', {
         inlineImages: true,
-        assetCapture: { objectURLs: false, origins: false },
+        captureAssets: { objectURLs: false, origins: false },
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
@@ -871,14 +871,77 @@ describe('record integration tests', function (this: ISuite) {
     assertSnapshot(snapshots);
   });
 
-  it('should record images inside iframe with blob url after iframe was reloaded', async () => {
+  it('[DEPRECATED] should record images inside iframe with blob url after iframe was reloaded', async () => {
     const page: puppeteer.Page = await browser.newPage();
     page.on('console', (msg) => console.log(msg.text()));
     await page.goto(`${serverURL}/html`);
     await page.setContent(
       getHtml.call(this, 'frame2.html', {
         inlineImages: true,
-        assetCapture: { objectURLs: false, origins: false },
+        captureAssets: { objectURLs: false, origins: false },
+      }),
+    );
+    await page.waitForSelector('iframe'); // wait for iframe to get added
+    await waitForRAF(page); // wait for iframe to load
+    page.evaluate(() => {
+      const iframe = document.querySelector('iframe')!;
+      iframe.setAttribute('src', '/html/image-blob-url.html');
+    });
+    await page.waitForResponse(`${serverURL}/html/assets/robot.png`); // wait for image to get loaded
+    await page.waitForTimeout(50); // wait for image to get added
+    await waitForRAF(page); // wait for image to be captured
+
+    const snapshots = (await page.evaluate(
+      'window.snapshots',
+    )) as eventWithTime[];
+    assertSnapshot(snapshots);
+  });
+
+  it('should record images with blob url', async () => {
+    const page: puppeteer.Page = await browser.newPage();
+    page.on('console', (msg) => console.log(msg.text()));
+    await page.goto(`${serverURL}/html`);
+    page.setContent(
+      getHtml.call(this, 'image-blob-url.html', {
+        captureAssets: { objectURLs: true, origins: false },
+      }),
+    );
+    await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
+    await page.waitForSelector('img'); // wait for image to get added
+    await waitForRAF(page); // wait for image to be captured
+
+    const snapshots = (await page.evaluate(
+      'window.snapshots',
+    )) as eventWithTime[];
+    assertSnapshot(snapshots);
+  });
+
+  it('should record images inside iframe with blob url', async () => {
+    const page: puppeteer.Page = await browser.newPage();
+    page.on('console', (msg) => console.log(msg.text()));
+    await page.goto(`${serverURL}/html`);
+    await page.setContent(
+      getHtml.call(this, 'frame-image-blob-url.html', {
+        captureAssets: { objectURLs: true, origins: false },
+      }),
+    );
+    await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
+    await page.waitForTimeout(50); // wait for image to get added
+    await waitForRAF(page); // wait for image to be captured
+
+    const snapshots = (await page.evaluate(
+      'window.snapshots',
+    )) as eventWithTime[];
+    assertSnapshot(snapshots);
+  });
+
+  it('should record images inside iframe with blob url after iframe was reloaded', async () => {
+    const page: puppeteer.Page = await browser.newPage();
+    page.on('console', (msg) => console.log(msg.text()));
+    await page.goto(`${serverURL}/html`);
+    await page.setContent(
+      getHtml.call(this, 'frame2.html', {
+        captureAssets: { objectURLs: true, origins: false },
       }),
     );
     await page.waitForSelector('iframe'); // wait for iframe to get added
diff --git a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
index 35ba745157..1dfc3d7e72 100644
--- a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
@@ -8,7 +8,7 @@ exports[`cross origin iframes audio.html should emit contents of iframe once 1`]
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -350,7 +350,7 @@ exports[`cross origin iframes blank.html should filter out forwarded cross origi
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -616,7 +616,7 @@ exports[`cross origin iframes blank.html should record same-origin iframe in cro
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -864,7 +864,7 @@ exports[`cross origin iframes blank.html should support packFn option in record(
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -1037,7 +1037,7 @@ exports[`cross origin iframes form.html should map input events correctly 1`] =
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2054,7 +2054,7 @@ exports[`cross origin iframes form.html should map scroll events correctly 1`] =
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -2714,7 +2714,7 @@ exports[`cross origin iframes move-node.html captures mutations on adopted style
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3132,7 +3132,7 @@ exports[`cross origin iframes move-node.html captures mutations on stylesheets 1
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3555,7 +3555,7 @@ exports[`cross origin iframes move-node.html should record DOM attribute changes
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -3847,7 +3847,7 @@ exports[`cross origin iframes move-node.html should record DOM node movement 1`]
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -4236,7 +4236,7 @@ exports[`cross origin iframes move-node.html should record DOM node removal 1`]
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -4526,7 +4526,7 @@ exports[`cross origin iframes move-node.html should record DOM text changes 1`]
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -4816,7 +4816,7 @@ exports[`cross origin iframes move-node.html should record canvas elements 1`] =
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -5143,7 +5143,7 @@ exports[`cross origin iframes move-node.html should record custom events 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -5429,7 +5429,7 @@ exports[`same origin iframes should emit contents of iframe once 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
diff --git a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
index d0b0ccbb23..7dfc3d0155 100644
--- a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
@@ -8,7 +8,7 @@ exports[`record webgl recordCanvas FPS should record snapshots 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -171,7 +171,7 @@ exports[`record webgl should batch events by RAF 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -320,7 +320,7 @@ exports[`record webgl will record changes to a canvas element 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -417,7 +417,7 @@ exports[`record webgl will record changes to a canvas element before the canvas
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -549,7 +549,7 @@ exports[`record webgl will record changes to a canvas element before the canvas
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -681,7 +681,7 @@ exports[`record webgl will record changes to a webgl2 canvas element 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -778,7 +778,7 @@ exports[`record webgl will record webgl variables 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
@@ -895,7 +895,7 @@ exports[`record webgl will record webgl variables in reverse order 1`] = `
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
       \\"height\\": 1080,
-      \\"assetCapture\\": {
+      \\"captureAssets\\": {
         \\"objectURLs\\": true,
         \\"origins\\": false
       }
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index 956ef2958e..9cb7d2ff5d 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -35,7 +35,7 @@ interface IWindow extends Window {
   snapshots: eventWithTime[];
 }
 type ExtraOptions = {
-  assetCapture?: recordOptions<eventWithTime>['assetCapture'];
+  captureAssets?: recordOptions<eventWithTime>['captureAssets'];
 };
 
 const BASE64_PNG_RECTANGLE =
@@ -53,7 +53,7 @@ async function injectRecordScript(
     (window as unknown as IWindow).snapshots = [];
     const { record, pack } = (window as unknown as IWindow).rrweb;
     const config: recordOptions<eventWithTime> = {
-      assetCapture: options.assetCapture,
+      captureAssets: options.captureAssets,
       emit(event) {
         (window as unknown as IWindow).snapshots.push(event);
         (window as unknown as IWindow).emit(event);
@@ -104,10 +104,10 @@ const setup = function (
 
     ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
     if (
-      options?.assetCapture?.origins &&
-      Array.isArray(options.assetCapture.origins)
+      options?.captureAssets?.origins &&
+      Array.isArray(options.captureAssets.origins)
     ) {
-      options.assetCapture.origins = options.assetCapture.origins.map(
+      options.captureAssets.origins = options.captureAssets.origins.map(
         (origin) => origin.replace(/\{SERVER_URL\}/g, ctx.serverURL),
       );
     }
@@ -140,7 +140,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           objectURLs: true,
           origins: false,
         },
@@ -282,7 +282,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           objectURLs: true,
           origins: false,
         },
@@ -327,7 +327,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           objectURLs: false,
           origins: false,
         },
@@ -407,7 +407,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           origins: false,
           objectURLs: false,
         },
@@ -439,7 +439,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           origins: [],
           objectURLs: false,
         },
@@ -471,7 +471,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           origins: true,
           objectURLs: false,
         },
@@ -508,7 +508,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           origins: true,
           objectURLs: false,
         },
@@ -578,7 +578,7 @@ describe('asset caching', function (this: ISuite) {
         </html>
       `,
       {
-        assetCapture: {
+        captureAssets: {
           origins: ['{SERVER_URL}'],
           objectURLs: false,
         },
@@ -640,7 +640,7 @@ describe('asset caching', function (this: ISuite) {
             href: expect.any(String),
             width: expect.any(Number),
             height: expect.any(Number),
-            assetCapture: {
+            captureAssets: {
               origins: [ctx.serverURL],
               objectURLs: false,
             },
diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index 27b721dda4..f87c9a3fbb 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -702,7 +702,7 @@ export function generateRecordSnippet(options: recordOptions<eventWithTime>) {
     recordAfter: '${options.recordAfter || 'load'}',
     inlineImages: ${options.inlineImages},
     plugins: ${options.plugins},
-    assetCapture: ${JSON.stringify(options.assetCapture)},
+    captureAssets: ${JSON.stringify(options.captureAssets)},
   });
   `;
 }
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 92200402b2..e5fa4bac99 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -41,7 +41,7 @@ export type metaEvent = {
     href: string;
     width: number;
     height: number;
-    assetCapture?: assetCaptureParam;
+    captureAssets?: captureAssetsParam;
   };
 };
 
@@ -61,7 +61,7 @@ export type pluginEvent<T = unknown> = {
   };
 };
 
-export type assetCaptureParam = {
+export type captureAssetsParam = {
   /**
    * Captures object URLs (blobs, files, media sources).
    * More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL

From b873923f37bd8655df70f253f79ac5431f31af1d Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 28 Nov 2023 21:48:41 +0100
Subject: [PATCH 030/102] Remove console.log statements from AssetManager class

---
 packages/rrweb/src/record/observers/asset-manager.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 6ce88c2143..1f465b557e 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -116,7 +116,6 @@ export default class AssetManager {
     try {
       const response = await fetch(url);
       const blob = await response.blob();
-      console.log('getURLObject', url, blob);
       return blob;
     } catch (e) {
       console.warn(`getURLObject failed for ${url}`);
@@ -127,7 +126,6 @@ export default class AssetManager {
   public capture(url: string): {
     status: 'capturing' | 'captured' | 'error' | 'refused';
   } {
-    console.log('capture', url, this.shouldIgnore(url));
     if (this.shouldIgnore(url)) return { status: 'refused' };
 
     if (this.capturedURLs.has(url)) {
@@ -138,10 +136,8 @@ export default class AssetManager {
       return { status: 'error' };
     }
     this.capturingURLs.add(url);
-    console.log('capturing');
     void this.getURLObject(url)
       .then(async (object) => {
-        console.log('captured', url);
         if (object) {
           let payload: SerializedCanvasArg;
           if (object instanceof File || object instanceof Blob) {

From f1c263b527bb56b9492eed2b7e7766a997f30933 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 30 Nov 2023 16:42:27 +0100
Subject: [PATCH 031/102] Check if url is of cacheable origin

---
 packages/rrweb-snapshot/src/rebuild.ts        |  4 +-
 packages/rrweb/src/replay/assets/index.ts     | 30 +++++++++
 packages/rrweb/src/replay/index.ts            |  6 +-
 packages/rrweb/test/replay/asset-unit.test.ts | 62 +++++++++++++++++--
 packages/types/src/index.ts                   |  2 +
 5 files changed, 90 insertions(+), 14 deletions(-)

diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index 5815ea60c4..d212e42b5e 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -287,9 +287,7 @@ function buildNode(
           } else {
             node.setAttribute(name, value.toString());
 
-            if (options.assetManager?.isAttributeCacheable(node, name)) {
-              options.assetManager.manageAttribute(node, name);
-            }
+            options.assetManager?.manageAttribute(node, name);
           }
         } catch (error) {
           // skip invalid attribute
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 2c9488ec5c..50f71b78e1 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -3,6 +3,7 @@ import type {
   RebuildAssetManagerInterface,
   RebuildAssetManagerStatus,
   assetEvent,
+  captureAssetsParam,
 } from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
 import { isAttributeCacheable } from '../../utils';
@@ -17,6 +18,11 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     string,
     Array<(status: RebuildAssetManagerFinalStatus) => void>
   > = new Map();
+  private config: captureAssetsParam;
+
+  constructor(config: captureAssetsParam) {
+    this.config = config;
+  }
 
   public async add(event: assetEvent) {
     const { data } = event;
@@ -114,10 +120,32 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     return isAttributeCacheable(n as Element, attribute);
   }
 
+  public isURLOfCacheableOrigin(url: string): boolean {
+    if (url.startsWith('data:')) return false;
+
+    const { origins: cachedOrigins, objectURLs } = this.config;
+    if (objectURLs && url.startsWith(`blob:`)) {
+      return true;
+    }
+
+    if (Array.isArray(cachedOrigins)) {
+      try {
+        const { origin } = new URL(url);
+        return cachedOrigins.some((o) => o === origin);
+      } catch {
+        return false;
+      }
+    }
+
+    return cachedOrigins;
+  }
+
   public async manageAttribute(
     node: RRElement | Element,
     attribute: string,
   ): Promise<unknown> {
+    if (!this.isAttributeCacheable(node, attribute)) return false;
+
     const originalValue = node.getAttribute(attribute);
     if (!originalValue) return false;
 
@@ -128,6 +156,8 @@ export default class AssetManager implements RebuildAssetManagerInterface {
         ? getSourcesFromSrcset(originalValue)
         : [originalValue];
     for (const value of values) {
+      if (!this.isURLOfCacheableOrigin(value)) continue;
+
       promises.push(
         this.whenReady(value).then((status) => {
           if (
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index f8bf1c5809..9f9f52c8e5 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -1814,11 +1814,7 @@ export class Replayer {
               } else {
                 targetEl.setAttribute(attributeName, value);
               }
-              if (
-                this.assetManager.isAttributeCacheable(targetEl, attributeName)
-              ) {
-                void this.assetManager.manageAttribute(targetEl, attributeName);
-              }
+              void this.assetManager.manageAttribute(targetEl, attributeName);
             } catch (error) {
               this.warn(
                 'An error occurred may due to the checkout feature.',
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 2e821f01e5..3c00b5fca2 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -3,7 +3,12 @@
  */
 
 import AssetManager from '../../src/replay/assets';
-import { EventType, SerializedBlobArg, assetEvent } from '@rrweb/types';
+import {
+  EventType,
+  SerializedBlobArg,
+  assetEvent,
+  captureAssetsParam,
+} from '@rrweb/types';
 
 describe('AssetManager', () => {
   let assetManager: AssetManager;
@@ -28,7 +33,10 @@ describe('AssetManager', () => {
   });
 
   beforeEach(() => {
-    assetManager = new AssetManager();
+    assetManager = new AssetManager({
+      origins: true,
+      objectURLs: true,
+    });
   });
 
   afterEach(() => {
@@ -141,7 +149,7 @@ describe('AssetManager', () => {
     await expect(promise).resolves.toEqual({ status: 'reset' });
   });
 
-  const validCombinations = [
+  const validAttributeCombinations = [
     ['img', ['src', 'srcset']],
     ['video', ['src']],
     ['audio', ['src']],
@@ -153,7 +161,7 @@ describe('AssetManager', () => {
     ['object', ['src']],
   ] as const;
 
-  const invalidCombinations = [
+  const invalidAttributeCombinations = [
     ['img', ['href']],
     ['script', ['href']],
     ['link', ['src']],
@@ -167,7 +175,7 @@ describe('AssetManager', () => {
     ['object', ['href']],
   ] as const;
 
-  validCombinations.forEach(([tagName, attributes]) => {
+  validAttributeCombinations.forEach(([tagName, attributes]) => {
     const element = document.createElement(tagName);
     attributes.forEach((attribute) => {
       it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
@@ -178,7 +186,7 @@ describe('AssetManager', () => {
     });
   });
 
-  invalidCombinations.forEach(([tagName, attributes]) => {
+  invalidAttributeCombinations.forEach(([tagName, attributes]) => {
     const element = document.createElement(tagName);
     attributes.forEach((attribute) => {
       it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
@@ -189,6 +197,48 @@ describe('AssetManager', () => {
     });
   });
 
+  const validOriginCombinations: Array<
+    [captureAssetsParam['origins'], string[]]
+  > = [
+    [['http://example.com'], ['http://example.com/image.png']],
+    [['https://example.com'], ['https://example.com/image.png']],
+    [['https://example.com:80'], ['https://example.com:80/cgi-bin/image.png']],
+    [true, ['https://example.com:80/cgi-bin/image.png']],
+  ];
+
+  const invalidOriginCombinations: Array<
+    [captureAssetsParam['origins'], string[]]
+  > = [
+    [['http://example.com'], ['https://example.com/image.png']],
+    [['https://example.com'], ['https://example.org/image.png']],
+    [['https://example.com:80'], ['https://example.com:81/image.png']],
+    [false, ['https://example.com:81/image.png']],
+  ];
+
+  validOriginCombinations.forEach(([origin, urls]) => {
+    const assetManager = new AssetManager({
+      origins: origin,
+      objectURLs: false,
+    });
+    urls.forEach((url) => {
+      it(`should correctly identify ${url} as cacheable for origin ${origin}`, () => {
+        expect(assetManager.isURLOfCacheableOrigin(url)).toBe(true);
+      });
+    });
+  });
+
+  invalidOriginCombinations.forEach(([origin, urls]) => {
+    const assetManager = new AssetManager({
+      origins: origin,
+      objectURLs: false,
+    });
+    urls.forEach((url) => {
+      it(`should correctly identify ${url} as NOT cacheable for origin ${origin}`, () => {
+        expect(assetManager.isURLOfCacheableOrigin(url)).toBe(false);
+      });
+    });
+  });
+
   it("should be able to modify a node's attribute once asset is loaded", async () => {
     const url = 'https://example.com/image.png';
     const event: assetEvent = {
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index e5fa4bac99..204a7b679a 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -761,11 +761,13 @@ export type RebuildAssetManagerStatus =
   | RebuildAssetManagerFinalStatus;
 
 export declare abstract class RebuildAssetManagerInterface {
+  constructor(config: captureAssetsParam);
   abstract add(event: assetEvent): Promise<void>;
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
   abstract reset(): void;
   abstract isAttributeCacheable(n: Element, attribute: string): boolean;
+  abstract isURLOfCacheableOrigin(url: string): boolean;
   abstract manageAttribute(n: Element, attribute: string): void;
 }
 

From ffa46d5031b206d4b83d90dbbb8a6be41ca06e71 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 30 Nov 2023 16:43:42 +0100
Subject: [PATCH 032/102] Test: Add captureAssets config to meta event

---
 packages/rrweb/test/record/asset.test.ts | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index 9cb7d2ff5d..fb76101973 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -478,6 +478,30 @@ describe('asset caching', function (this: ISuite) {
       },
     );
 
+    it('adds captureAssets config to meta event', async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      expect(events).toContainEqual(
+        expect.objectContaining({
+          type: EventType.Meta,
+          data: {
+            href: expect.any(String),
+            width: expect.any(Number),
+            height: expect.any(Number),
+            captureAssets: {
+              origins: true,
+              objectURLs: false,
+            },
+          },
+        }),
+      );
+    });
+
     it('capture all urls', async () => {
       await ctx.page.waitForNetworkIdle({ idleTime: 100 });
       await waitForRAF(ctx.page);

From db9476532dbcf398c6ec946061e7e389a1f98584 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 30 Nov 2023 16:56:11 +0100
Subject: [PATCH 033/102] object urls aren't stable so lets remove them

---
 packages/rrweb/test/__snapshots__/integration.test.ts.snap | 6 +++---
 packages/rrweb/test/utils.ts                               | 3 +++
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 27d55dff60..042a6e45df 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -14012,7 +14012,7 @@ exports[`record integration tests should record images inside iframe with blob u
   {
     \\"type\\": 7,
     \\"data\\": {
-      \\"url\\": \\"blob:http://localhost:3030/2191dd12-9b7d-440a-88a1-60451e2cbae5\\",
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
       \\"payload\\": {
         \\"rr_type\\": \\"Blob\\",
         \\"type\\": \\"text/plain\\",
@@ -14465,7 +14465,7 @@ exports[`record integration tests should record images inside iframe with blob u
   {
     \\"type\\": 7,
     \\"data\\": {
-      \\"url\\": \\"blob:http://localhost:3030/1786bb8a-e2e5-4c5c-b2a5-ba9875e40c65\\",
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
       \\"payload\\": {
         \\"rr_type\\": \\"Blob\\",
         \\"type\\": \\"text/plain\\",
@@ -14691,7 +14691,7 @@ exports[`record integration tests should record images with blob url 1`] = `
   {
     \\"type\\": 7,
     \\"data\\": {
-      \\"url\\": \\"blob:http://localhost:3030/ed823e22-cc3c-436a-a460-d6dce1bd1ee9\\",
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
       \\"payload\\": {
         \\"rr_type\\": \\"Blob\\",
         \\"type\\": \\"text/plain\\",
diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index f87c9a3fbb..4afc2df1f2 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -132,6 +132,9 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
           delete (s.data as Optional<mouseInteractionData, 'x'>).x;
           delete (s.data as Optional<mouseInteractionData, 'y'>).y;
         }
+        if (s.type === EventType.Asset) {
+          s.data.url = s.data.url.replace(/\/[a-f0-9\-]+$/, '/...');
+        }
         if (
           s.type === EventType.IncrementalSnapshot &&
           s.data.source === IncrementalSource.Mutation

From 634c1aecbf950eaf9261c3ce67a73d16a969e6c3 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 30 Nov 2023 16:57:01 +0100
Subject: [PATCH 034/102] inlineImages should work next to captureAssets for
 now

---
 packages/rrweb-snapshot/src/snapshot.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 04155f3518..1c5ae940ac 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -782,9 +782,10 @@ function serializeElementNode(
     if (attributes.srcset) {
       assets.push(...getUrlsFromSrcset(attributes.srcset.toString()));
     }
-    // TODO: decide if inlineImages should still be supported,
-    // and if so if it should be moved into `rrweb` package.
-  } else if (tagName === 'img' && inlineImages) {
+  }
+
+  // `inlineImages` is deprecated and will be removed in rrweb 3.x.
+  if (tagName === 'img' && inlineImages) {
     if (!canvasService) {
       canvasService = doc.createElement('canvas');
       canvasCtx = canvasService.getContext('2d');

From d756e6104847ba5c14b4fe32268126e497a1de82 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 1 Dec 2023 10:55:29 +0100
Subject: [PATCH 035/102] Fix asset caching bug

---
 packages/rrweb-snapshot/src/snapshot.ts       | 23 +++----
 packages/rrweb-snapshot/src/utils.ts          | 22 +++++++
 .../src/record/observers/asset-manager.ts     |  4 +-
 packages/rrweb/src/replay/assets/index.ts     | 20 +++---
 packages/rrweb/src/replay/index.ts            |  8 ++-
 packages/rrweb/src/utils.ts                   | 22 -------
 packages/rrweb/test/events/assets-mutation.ts |  8 ++-
 packages/rrweb/test/html/assets/subtitles.vtt | 16 +++++
 packages/rrweb/test/record/asset.test.ts      | 65 +++++++++++++------
 .../test/replay/asset-integration.test.ts     |  2 +
 packages/types/src/index.ts                   |  4 +-
 11 files changed, 123 insertions(+), 71 deletions(-)
 create mode 100644 packages/rrweb/test/html/assets/subtitles.vtt

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 1c5ae940ac..6732bc89cb 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -28,6 +28,7 @@ import {
   toLowerCase,
   extractFileExtension,
   getUrlsFromSrcset,
+  isAttributeCacheable,
 } from './utils';
 
 let _id = 1;
@@ -673,12 +674,21 @@ function serializeElementNode(
   for (let i = 0; i < len; i++) {
     const attr = n.attributes[i];
     if (!ignoreAttribute(tagName, attr.name, attr.value)) {
-      attributes[attr.name] = transformAttribute(
+      const value = (attributes[attr.name] = transformAttribute(
         doc,
         tagName,
         toLowerCase(attr.name),
         attr.value,
-      );
+      ));
+
+      // save assets offline
+      if (value && onAssetDetected && isAttributeCacheable(n, attr.name)) {
+        if (attr.name === 'srcset') {
+          assets.push(...getUrlsFromSrcset(value));
+        } else {
+          assets.push(value);
+        }
+      }
     }
   }
   // remote css
@@ -774,15 +784,6 @@ function serializeElementNode(
       }
     }
   }
-  // save image offline
-  if (tagName === 'img' && onAssetDetected) {
-    if (attributes.src) {
-      assets.push(attributes.src.toString());
-    }
-    if (attributes.srcset) {
-      assets.push(...getUrlsFromSrcset(attributes.srcset.toString()));
-    }
-  }
 
   // `inlineImages` is deprecated and will be removed in rrweb 3.x.
   if (tagName === 'img' && inlineImages) {
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 99fab105f6..672c6ea723 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -368,3 +368,25 @@ export function getUrlsFromSrcset(srcset: string): string[] {
   }
   return urls;
 }
+
+export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
+  ['IMG', new Set(['src', 'srcset'])],
+  ['VIDEO', new Set(['src'])],
+  ['AUDIO', new Set(['src'])],
+  ['EMBED', new Set(['src'])],
+  ['SOURCE', new Set(['src'])],
+  ['TRACK', new Set(['src'])],
+  ['INPUT', new Set(['src'])],
+  ['IFRAME', new Set(['src'])],
+  ['OBJECT', new Set(['src'])],
+]);
+
+export function isAttributeCacheable(n: Element, attribute: string): boolean {
+  const acceptedAttributesSet = CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
+    n.nodeName,
+  );
+  if (!acceptedAttributesSet) {
+    return false;
+  }
+  return acceptedAttributesSet.has(attribute);
+}
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 1f465b557e..3dea10ddd1 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -7,8 +7,10 @@ import type {
 import type { assetCallback } from '@rrweb/types';
 import { encode } from 'base64-arraybuffer';
 
-import { isAttributeCacheable, patch } from '../../utils';
+import { patch } from '../../utils';
+
 import type { recordOptions } from '../../types';
+import { isAttributeCacheable } from 'rrweb-snapshot';
 
 export default class AssetManager {
   private urlObjectMap = new Map<string, File | Blob | MediaSource>();
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 50f71b78e1..5c4cad1123 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -6,8 +6,7 @@ import type {
   captureAssetsParam,
 } from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
-import { isAttributeCacheable } from '../../utils';
-import { getSourcesFromSrcset } from 'rrweb-snapshot';
+import { getSourcesFromSrcset, isAttributeCacheable } from 'rrweb-snapshot';
 import type { RRElement } from 'rrdom';
 
 export default class AssetManager implements RebuildAssetManagerInterface {
@@ -18,9 +17,9 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     string,
     Array<(status: RebuildAssetManagerFinalStatus) => void>
   > = new Map();
-  private config: captureAssetsParam;
+  private config: captureAssetsParam | undefined;
 
-  constructor(config: captureAssetsParam) {
+  constructor(config: captureAssetsParam | undefined) {
     this.config = config;
   }
 
@@ -123,7 +122,8 @@ export default class AssetManager implements RebuildAssetManagerInterface {
   public isURLOfCacheableOrigin(url: string): boolean {
     if (url.startsWith('data:')) return false;
 
-    const { origins: cachedOrigins, objectURLs } = this.config;
+    const { origins: cachedOrigins = false, objectURLs = false } =
+      this.config || {};
     if (objectURLs && url.startsWith(`blob:`)) {
       return true;
     }
@@ -149,14 +149,13 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     const originalValue = node.getAttribute(attribute);
     if (!originalValue) return false;
 
-    const promises = [];
+    const promises: Promise<unknown>[] = [];
 
     const values =
       attribute === 'srcset'
         ? getSourcesFromSrcset(originalValue)
         : [originalValue];
-    for (const value of values) {
-      if (!this.isURLOfCacheableOrigin(value)) continue;
+    values.forEach((value) => {
 
       promises.push(
         this.whenReady(value).then((status) => {
@@ -170,11 +169,12 @@ export default class AssetManager implements RebuildAssetManagerInterface {
           }
         }),
       );
-    }
+    });
     return Promise.all(promises);
   }
 
-  public reset(): void {
+  public reset(config: captureAssetsParam | undefined): void {
+    this.config = config;
     this.originalToObjectURLMap.forEach((objectURL) => {
       URL.revokeObjectURL(objectURL);
     });
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 9f9f52c8e5..a3f1222e72 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -391,7 +391,9 @@ export class Replayer {
       (e) => e.type === EventType.FullSnapshot,
     );
     if (firstMeta) {
-      const { width, height } = firstMeta.data as metaEvent['data'];
+      const { width, height, captureAssets } =
+        firstMeta.data as metaEvent['data'];
+      this.assetManager.reset(captureAssets);
       setTimeout(() => {
         this.emitter.emit(ReplayerEvents.Resize, {
           width,
@@ -673,11 +675,13 @@ export class Replayer {
         };
         break;
       case EventType.Meta:
-        castFn = () =>
+        castFn = () => {
+          this.assetManager.reset(event.data.captureAssets);
           this.emitter.emit(ReplayerEvents.Resize, {
             width: event.data.width,
             height: event.data.height,
           });
+        };
         break;
       case EventType.FullSnapshot:
         castFn = () => {
diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts
index d289e69638..7f74c4cc06 100644
--- a/packages/rrweb/src/utils.ts
+++ b/packages/rrweb/src/utils.ts
@@ -590,25 +590,3 @@ export function inDom(n: Node): boolean {
   if (!doc) return false;
   return doc.contains(n) || shadowHostInDom(n);
 }
-
-export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
-  ['IMG', new Set(['src', 'srcset'])],
-  ['VIDEO', new Set(['src'])],
-  ['AUDIO', new Set(['src'])],
-  ['EMBED', new Set(['src'])],
-  ['SOURCE', new Set(['src'])],
-  ['TRACK', new Set(['src'])],
-  ['INPUT', new Set(['src'])],
-  ['IFRAME', new Set(['src'])],
-  ['OBJECT', new Set(['src'])],
-]);
-
-export function isAttributeCacheable(n: Element, attribute: string): boolean {
-  const acceptedAttributesSet = CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
-    n.nodeName,
-  );
-  if (!acceptedAttributesSet) {
-    return false;
-  }
-  return acceptedAttributesSet.has(attribute);
-}
diff --git a/packages/rrweb/test/events/assets-mutation.ts b/packages/rrweb/test/events/assets-mutation.ts
index 966c29678a..dda8274f84 100644
--- a/packages/rrweb/test/events/assets-mutation.ts
+++ b/packages/rrweb/test/events/assets-mutation.ts
@@ -7,6 +7,10 @@ const events: eventWithTime[] = [
       href: '',
       width: 1600,
       height: 900,
+      captureAssets: {
+        origins: ['ftp://example.com'],
+        objectURLs: false,
+      },
     },
     timestamp: 1636379531385,
   },
@@ -109,7 +113,7 @@ const events: eventWithTime[] = [
         {
           id: 16,
           attributes: {
-            src: 'httpx://example.com/image.png',
+            src: 'ftp://example.com/image.png',
           },
         },
       ],
@@ -121,7 +125,7 @@ const events: eventWithTime[] = [
   {
     type: EventType.Asset,
     data: {
-      url: 'httpx://example.com/image.png',
+      url: 'ftp://example.com/image.png',
       payload: {
         rr_type: 'Blob',
         type: 'image/png',
diff --git a/packages/rrweb/test/html/assets/subtitles.vtt b/packages/rrweb/test/html/assets/subtitles.vtt
new file mode 100644
index 0000000000..c56d8d687d
--- /dev/null
+++ b/packages/rrweb/test/html/assets/subtitles.vtt
@@ -0,0 +1,16 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:00.999  line:80%
+Hildy!
+
+00:00:01.000 --> 00:00:01.499 line:80%
+How are you?
+
+00:00:01.500 --> 00:00:02.999 line:80%
+Tell me, is the lord of the universe in?
+
+00:00:03.000 --> 00:00:04.299 line:80%
+Yes, he's in - in a bad humor
+
+00:00:04.300 --> 00:00:06.000 line:80%
+Somebody must've stolen the crown jewels
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index fb76101973..1105d07326 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -596,8 +596,15 @@ describe('asset caching', function (this: ISuite) {
         <!DOCTYPE html>
         <html>
           <body>
-            <img src="{SERVER_URL}/html/assets/robot.png" />
-            <img src="{SERVER_B_URL}/html/assets/robot.png" />
+            <img src="{SERVER_URL}/html/assets/robot.png?img" />
+            <video><track default kind="captions" srclang="en" src="{SERVER_URL}/html/assets/subtitles.vtt" /><source src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?source" /></video>
+            <video src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video" type="audio/mp3" />
+            <audio src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio" type="audio/mp3" />
+            <embed type="video/webm" src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?embed" width="250" height="200" />
+            <img srcset="{SERVER_URL}/html/assets/robot.png?1x, {SERVER_URL}/html/assets/robot.png?2x 2x" />
+            <img src="{SERVER_B_URL}/html/assets/robot.png?img" />
+            <input type="image" id="image" alt="Login" src="{SERVER_URL}/html/assets/robot.png?input-type-image" />
+            <iframe src="{SERVER_URL}/html/assets/robot.png?iframe" />
           </body>
         </html>
       `,
@@ -609,25 +616,41 @@ describe('asset caching', function (this: ISuite) {
       },
     );
 
-    it('should capture assets with origin defined in config', async () => {
-      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
-      await waitForRAF(ctx.page);
-
-      const events = await ctx.page?.evaluate(
-        () => (window as unknown as IWindow).snapshots,
-      );
-
-      // expect an event to be emitted with `event.type` === EventType.Asset
-      expect(events).toContainEqual(
-        expect.objectContaining({
-          type: EventType.Asset,
-          data: {
-            url: `${ctx.serverURL}/html/assets/robot.png`,
-            payload: expect.any(Object),
-          },
-        }),
-      );
+    [
+      `{SERVER_URL}/html/assets/robot.png?img`,
+      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio`,
+      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video`,
+      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?source`,
+      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?embed`,
+      '{SERVER_URL}/html/assets/subtitles.vtt',
+      '{SERVER_URL}/html/assets/robot.png?1x',
+      '{SERVER_URL}/html/assets/robot.png?2x',
+      '{SERVER_URL}/html/assets/robot.png?input-type-image',
+      '{SERVER_URL}/html/assets/robot.png?iframe',
+    ].forEach((u) => {
+      it(`should capture ${u} with origin defined in config`, async () => {
+        const url = u.replace(/\{SERVER_URL\}/g, ctx.serverURL);
+        console.log(url, ctx.serverURL);
+        await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+        await waitForRAF(ctx.page);
+
+        const events = await ctx.page?.evaluate(
+          () => (window as unknown as IWindow).snapshots,
+        );
+
+        // expect an event to be emitted with `event.type` === EventType.Asset
+        expect(events).toContainEqual(
+          expect.objectContaining({
+            type: EventType.Asset,
+            data: {
+              url,
+              payload: expect.any(Object),
+            },
+          }),
+        );
+      });
     });
+
     it("shouldn't capture assets with origin not defined in config", async () => {
       await ctx.page.waitForNetworkIdle({ idleTime: 100 });
       await waitForRAF(ctx.page);
@@ -641,7 +664,7 @@ describe('asset caching', function (this: ISuite) {
         expect.objectContaining({
           type: EventType.Asset,
           data: {
-            url: `${ctx.serverBURL}/html/assets/robot.png`,
+            url: `${ctx.serverBURL}/html/assets/robot.png?img`,
             payload: expect.any(Object),
           },
         }),
diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 02e650fa68..1ab6f75e66 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -62,6 +62,8 @@ describe('replayer', function () {
       replayer.pause(0);
     `);
 
+      await waitForRAF(page);
+
       const image = await page.screenshot();
       expect(image).toMatchImageSnapshot();
     });
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 204a7b679a..2496846487 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -761,11 +761,11 @@ export type RebuildAssetManagerStatus =
   | RebuildAssetManagerFinalStatus;
 
 export declare abstract class RebuildAssetManagerInterface {
-  constructor(config: captureAssetsParam);
+  constructor(config: captureAssetsParam | undefined);
   abstract add(event: assetEvent): Promise<void>;
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
-  abstract reset(): void;
+  abstract reset(config: captureAssetsParam | undefined): void;
   abstract isAttributeCacheable(n: Element, attribute: string): boolean;
   abstract isURLOfCacheableOrigin(url: string): boolean;
   abstract manageAttribute(n: Element, attribute: string): void;

From 7ae4dd4a81f8c2bf9fc99fb1e9b0813bd1d477c1 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 1 Dec 2023 10:58:31 +0100
Subject: [PATCH 036/102] captureAssets on live-stream by default

---
 packages/rrweb/scripts/stream.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/scripts/stream.js b/packages/rrweb/scripts/stream.js
index 108f8f303a..5f838908f1 100644
--- a/packages/rrweb/scripts/stream.js
+++ b/packages/rrweb/scripts/stream.js
@@ -68,7 +68,7 @@ async function injectRecording(frame) {
             collectFonts: true,
             captureAssets: {
               objectURLs: true,
-              origins: false,
+              origins: true,
             },
           });
         })();

From c1d73ce3ecbc8b6d9daf8133f1cdfdb6f249fb1f Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 1 Dec 2023 11:19:01 +0100
Subject: [PATCH 037/102] Fix asset URLs in events file

---
 packages/rrweb/test/events/assets.ts | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb/test/events/assets.ts b/packages/rrweb/test/events/assets.ts
index 3c7b36b3de..b7bc9d516d 100644
--- a/packages/rrweb/test/events/assets.ts
+++ b/packages/rrweb/test/events/assets.ts
@@ -7,6 +7,10 @@ const events: eventWithTime[] = [
       href: '',
       width: 1600,
       height: 900,
+      captureAssets: {
+        origins: ['ftp://example.com'],
+        objectURLs: false,
+      },
     },
     timestamp: 1636379531385,
   },
@@ -72,7 +76,7 @@ const events: eventWithTime[] = [
                       width: '100',
                       height: '100',
                       style: 'border: 1px solid #000000',
-                      src: 'httpx://example.com/image.png',
+                      src: 'ftp://example.com/image.png',
                     },
                     childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
                     id: 16,
@@ -104,7 +108,7 @@ const events: eventWithTime[] = [
   {
     type: EventType.Asset,
     data: {
-      url: 'httpx://example.com/image.png',
+      url: 'ftp://example.com/image.png',
       payload: {
         rr_type: 'Blob',
         type: 'image/png',

From f0050df7449e66a3ab6280ec53c5430c51060787 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Mon, 22 Apr 2024 22:55:08 +0100
Subject: [PATCH 038/102] Update deprecated inlineImages option to use
 captureAssets directly

---
 docs/recipes/assets.md                  | 2 +-
 docs/recipes/assets.zh_CN.md            | 2 +-
 guide.md                                | 2 +-
 guide.zh_CN.md                          | 2 +-
 packages/rrweb-snapshot/src/snapshot.ts | 4 ++--
 packages/rrweb/src/record/index.ts      | 9 ++++++---
 packages/rrweb/src/record/mutation.ts   | 5 -----
 packages/rrweb/src/types.ts             | 5 -----
 8 files changed, 12 insertions(+), 19 deletions(-)

diff --git a/docs/recipes/assets.md b/docs/recipes/assets.md
index 0fc0c103cd..acad0cd418 100644
--- a/docs/recipes/assets.md
+++ b/docs/recipes/assets.md
@@ -4,7 +4,7 @@
 
 ## Inline Images (Deprecated)
 
-The `inlineImages` configuration option is deprecated and should not be used anymore. It has some issues, namely rewriting events that are already emitted which might make you miss the inlined image if the event has already been sent to the server. Instead, use the `captureAssets` option to configure asset capture.
+The `inlineImages` configuration option is deprecated and should not be used anymore. The previous implementation had some issues, namely rewriting events that are already emitted which might make you miss the inlined image if the event has already been sent to the server. Currently it just turns on `captureAssets`, so use please directly use the `captureAssets` option to configure asset capture.
 
 ## Asset Capture Configuration
 
diff --git a/docs/recipes/assets.zh_CN.md b/docs/recipes/assets.zh_CN.md
index 7f0b6a9943..1aed560b50 100644
--- a/docs/recipes/assets.zh_CN.md
+++ b/docs/recipes/assets.zh_CN.md
@@ -4,7 +4,7 @@
 
 ## 内联图像(已弃用)
 
-`inlineImages` 配置选项已被弃用,不应再使用。它存在一些问题,即重写已经发出的事件,这可能使您错过已发送到服务器的内联图像。相反,请使用 `captureAssets` 选项来配置资源捕获。
+`inlineImages` 配置选项已弃用,不应再使用。之前的实现存在一些问题,即重写已经发出的事件,这可能使您错过已经发送到服务器的内联图像。目前它只是开启了 `captureAssets`,所以请直接使用 `captureAssets` 选项来配置资源捕获。
 
 ## 资源捕获配置
 
diff --git a/guide.md b/guide.md
index 0a40cf2a4e..8565eaded9 100644
--- a/guide.md
+++ b/guide.md
@@ -160,7 +160,7 @@ The parameter of `rrweb.record` accepts the following options.
 | recordCanvas             | false                                | Whether to record the canvas element. Available options:<br/>`false`, <br/>`true`                                                                                                             |
 | recordCrossOriginIframes | false                                | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options:<br/>`false`, <br/>`true`                                           |
 | recordAfter              | 'load'                               | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load`                                        |
-| ~inlineImages~           | false                                | whether to record the image content (deprecated, use `captureAssets` instead)                                                                                                                 |
+| ~inlineImages~           | false                                | sets `captureAssets` to { objectURLs: true, origins: true } (deprecated, use `captureAssets` directly instead)                                                                                |
 | captureAssets            | { objectURLs: true, origins: false } | Configure the asset (image) capture and generates async asset events.<br/>Refer to the [asset capture documentation](./docs/recipes/assets.md) for more info.                                 |
 | collectFonts             | false                                | whether to collect fonts in the website                                                                                                                                                       |
 | userTriggeredOnInput     | false                                | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) |
diff --git a/guide.zh_CN.md b/guide.zh_CN.md
index 09f57e442d..c731a1ff1d 100644
--- a/guide.zh_CN.md
+++ b/guide.zh_CN.md
@@ -155,7 +155,7 @@ setInterval(save, 10 * 1000);
 | recordCanvas             | false                                | 是否记录 canvas 内容, 可用选项:`false`, `true`                                                                                                                                       |
 | recordCrossOriginIframes | false                                | 是否记录 cross origin iframes。 必须在每个子 iframe 中注入 rrweb 才能使其工作。 可用选项:`false`, `true`                                                                             |
 | recordAfter              | 'load'                               | 如果 document 还没有加载完成,recorder 将会在指定的事件触发后开始录制。可用选项: `DOMContentLoaded`, `load`                                                                          |
-| ~inlineImages~           | false                                | 是否将图片内容记内联录制                                                                                                                                                              |
+| ~inlineImages~           | false                                | 将 `captureAssets` 设置为 { objectURLs: true, origins: true }(已弃用,直接使用 `captureAssets` 代替)                                                                                |
 | captureAssets            | { objectURLs: true, origins: false } | 配置资源(图像)捕获并生成异步资源事件。<br/>有关更多信息,请参阅[资源捕获文档](./docs/recipes/assets.zh_CN.md)                                                                       |
 | collectFonts             | false                                | 是否记录页面中的字体文件                                                                                                                                                              |
 | userTriggeredOnInput     | false                                | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495)                                                                                                                  |
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 6732bc89cb..9e53fa679d 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -466,7 +466,7 @@ function serializeNode(
     /**
      * @deprecated please use `captureAssets` instead
      */
-    inlineImages: boolean;
+    inlineImages?: boolean;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
     /**
@@ -634,7 +634,7 @@ function serializeElementNode(
     /**
      * @deprecated please use `captureAssets` instead
      */
-    inlineImages: boolean;
+    inlineImages?: boolean;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
     /**
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index e361101bb3..e564bca7a8 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -93,6 +93,12 @@ function record<T = eventWithTime>(
     errorHandler,
   } = options;
 
+  // handle deprecated `inlineImages` option
+  if (inlineImages) {
+    captureAssets.objectURLs = true;
+    captureAssets.origins = true;
+  }
+
   registerErrorHandler(errorHandler);
 
   const inEmittingFrame = recordCrossOriginIframes
@@ -339,7 +345,6 @@ function record<T = eventWithTime>(
       maskTextFn,
       maskInputFn,
       recordCanvas,
-      inlineImages,
       sampling,
       slimDOMOptions,
       iframeManager,
@@ -387,7 +392,6 @@ function record<T = eventWithTime>(
       slimDOM: slimDOMOptions,
       dataURLOptions,
       recordCanvas,
-      inlineImages,
       onSerialize: (n) => {
         if (isSerializedIframe(n, mirror)) {
           iframeManager.addIframe(n as HTMLIFrameElement);
@@ -539,7 +543,6 @@ function record<T = eventWithTime>(
           sampling,
           recordDOM,
           recordCanvas,
-          inlineImages,
           userTriggeredOnInput,
           collectFonts,
           doc,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index bbce696ec5..e8bb8d2c36 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -181,10 +181,6 @@ export default class MutationBuffer {
   private maskInputFn: observerParam['maskInputFn'];
   private keepIframeSrcFn: observerParam['keepIframeSrcFn'];
   private recordCanvas: observerParam['recordCanvas'];
-  /**
-   * @deprecated please use `captureAssets` instead
-   */
-  private inlineImages: observerParam['inlineImages'];
   private slimDOMOptions: observerParam['slimDOMOptions'];
   private dataURLOptions: observerParam['dataURLOptions'];
   private doc: observerParam['doc'];
@@ -322,7 +318,6 @@ export default class MutationBuffer {
         slimDOMOptions: this.slimDOMOptions,
         dataURLOptions: this.dataURLOptions,
         recordCanvas: this.recordCanvas,
-        inlineImages: this.inlineImages,
         onSerialize: (currentN) => {
           if (isSerializedIframe(currentN, this.mirror)) {
             this.iframeManager.addIframe(currentN as HTMLIFrameElement);
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index 3cf0cc2fe8..e6a41df277 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -109,10 +109,6 @@ export type observerParam = {
   sampling: SamplingStrategy;
   recordDOM: boolean;
   recordCanvas: boolean;
-  /**
-   * @deprecated please use `captureAssets` instead
-   */
-  inlineImages: boolean;
   userTriggeredOnInput: boolean;
   collectFonts: boolean;
   slimDOMOptions: SlimDOMOptions;
@@ -150,7 +146,6 @@ export type MutationBufferParam = Pick<
   | 'maskInputFn'
   | 'keepIframeSrcFn'
   | 'recordCanvas'
-  | 'inlineImages'
   | 'slimDOMOptions'
   | 'dataURLOptions'
   | 'doc'

From 37af341d0c0d7fa1ba18dbc4893373a703afee52 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 15:09:10 +0100
Subject: [PATCH 039/102] Deprecate inlineImage config option.

---
 .changeset/yellow-vans-protect.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.changeset/yellow-vans-protect.md b/.changeset/yellow-vans-protect.md
index 8a1c706956..0d2ea4161e 100644
--- a/.changeset/yellow-vans-protect.md
+++ b/.changeset/yellow-vans-protect.md
@@ -5,3 +5,4 @@
 
 `NodeType` enum was moved from rrweb-snapshot to @rrweb/types
 The following types where moved from rrweb-snapshot to @rrweb/types: `documentNode`, `documentTypeNode`, `attributes`, `legacyAttributes`, `elementNode`, `textNode`, `cdataNode`, `commentNode`, `serializedNode`, `serializedNodeWithId` and `DataURLOptions`
+`inlineImage` config option is deprecated and in `rrweb` is an alias for `captureAssets` config option

From c47f9cd9ffa5f2f4cc72321689e4f798c624d7dc Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 15:30:13 +0100
Subject: [PATCH 040/102] Update snapshot to reflect current captureAssets

---
 .../__snapshots__/integration.test.ts.snap    | 170 +++++++-----------
 1 file changed, 69 insertions(+), 101 deletions(-)

diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 042a6e45df..c67355cda4 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -17,8 +17,8 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
       \\"width\\": 1920,
       \\"height\\": 1080,
       \\"captureAssets\\": {
-        \\"objectURLs\\": false,
-        \\"origins\\": false
+        \\"objectURLs\\": true,
+        \\"origins\\": true
       }
     }
   },
@@ -334,6 +334,22 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
       \\"isAttachIframe\\": true
     }
   },
+  {
+    \\"type\\": 7,
+    \\"data\\": {
+      \\"url\\": \\"http://localhost:3030/html/image-blob-url.html\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/html\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04IiAvPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+CiAgICA8dGl0bGU+SW1hZ2Ugd2l0aCBibG9iOnVybDwvdGl0bGU+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHNjcmlwdD4KICAgICAgc2V0VGltZW91dChhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgY29uc3Qgcm9ib3RGaWxlID0gYXdhaXQgZmV0Y2goJy9odG1sL2Fzc2V0cy9yb2JvdC5wbmcnKTsKICAgICAgICBjb25zdCByb2JvdEJsb2IgPSBhd2FpdCByb2JvdEZpbGUuYmxvYigpOwogICAgICAgIGNvbnN0IHJvYm90QmxvYlVybCA9IFVSTC5jcmVhdGVPYmplY3RVUkwocm9ib3RCbG9iKTsKICAgICAgICBjb25zdCBlbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2ltZycpOwogICAgICAgIGVsLnNyYyA9IHJvYm90QmxvYlVybDsKICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZChlbCk7CiAgICAgIH0sIDEwKTsKICAgIDwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=\\"
+          }
+        ]
+      }
+    }
+  },
   {
     \\"type\\": 3,
     \\"data\\": {
@@ -349,8 +365,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 21,
@@ -361,37 +376,19 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 41,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 41,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -414,8 +411,8 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
       \\"width\\": 1920,
       \\"height\\": 1080,
       \\"captureAssets\\": {
-        \\"objectURLs\\": false,
-        \\"origins\\": false
+        \\"objectURLs\\": true,
+        \\"origins\\": true
       }
     }
   },
@@ -821,8 +818,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 27,
@@ -833,37 +829,19 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 47,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 47,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -886,8 +864,8 @@ exports[`record integration tests [DEPRECATED] should record images with blob ur
       \\"width\\": 1920,
       \\"height\\": 1080,
       \\"captureAssets\\": {
-        \\"objectURLs\\": false,
-        \\"origins\\": false
+        \\"objectURLs\\": true,
+        \\"origins\\": true
       }
     }
   },
@@ -1067,8 +1045,7 @@ exports[`record integration tests [DEPRECATED] should record images with blob ur
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\",
-              \\"rr_dataURL\\": \\"data:image/png;base64,...\\"
+              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"id\\": 24
@@ -1078,37 +1055,19 @@ exports[`record integration tests [DEPRECATED] should record images with blob ur
     }
   },
   {
-    \\"type\\": 3,
-    \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 24,
-          \\"attributes\\": {
-            \\"crossorigin\\": \\"anonymous\\"
-          }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
-    }
-  },
-  {
-    \\"type\\": 3,
+    \\"type\\": 7,
     \\"data\\": {
-      \\"source\\": 0,
-      \\"texts\\": [],
-      \\"attributes\\": [
-        {
-          \\"id\\": 24,
-          \\"attributes\\": {
-            \\"crossorigin\\": null
+      \\"url\\": \\"blob:http://localhost:3030/...\\",
+      \\"payload\\": {
+        \\"rr_type\\": \\"Blob\\",
+        \\"type\\": \\"text/plain\\",
+        \\"data\\": [
+          {
+            \\"rr_type\\": \\"ArrayBuffer\\",
+            \\"base64\\": \\"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\\"
           }
-        }
-      ],
-      \\"removes\\": [],
-      \\"adds\\": []
+        ]
+      }
     }
   }
 ]"
@@ -1953,7 +1912,11 @@ exports[`record integration tests can mask character data mutations with regexp
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -4994,7 +4957,11 @@ exports[`record integration tests can record textarea mutations correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080
+      \\"height\\": 1080,
+      \\"captureAssets\\": {
+        \\"objectURLs\\": true,
+        \\"origins\\": false
+      }
     }
   },
   {
@@ -13650,6 +13617,7 @@ exports[`record integration tests should record dynamic CSS changes 1`] = `
   }
 ]"
 `;
+
 exports[`record integration tests should record images inside iframe with blob url 1`] = `
 "[
   {

From c171342aea54bd9f19f3ad3dbeaff2d5a132f2a3 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 15:43:09 +0100
Subject: [PATCH 041/102] Make test less flaky

---
 packages/rrweb/test/__snapshots__/integration.test.ts.snap | 1 +
 packages/rrweb/test/integration.test.ts                    | 1 +
 2 files changed, 2 insertions(+)

diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index c67355cda4..25477d9b71 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -10914,6 +10914,7 @@ exports[`record integration tests should not record input values if dynamically
             \\"tagName\\": \\"input\\",
             \\"attributes\\": {
               \\"id\\": \\"input\\",
+              \\"size\\": \\"50\\",
               \\"value\\": \\"**********************\\"
             },
             \\"childNodes\\": [],
diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index 7b81823414..a55ac5d0eb 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -640,6 +640,7 @@ describe('record integration tests', function (this: ISuite) {
     await page.evaluate(() => {
       const el = document.createElement('input');
       el.id = 'input';
+      el.setAttribute('size', '50');
       el.value = 'input should be masked';
 
       const nextElement = document.querySelector('#one')!;

From d5364e5e7bb1830b103eef6325ea8ad078e87670 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 16:53:29 +0100
Subject: [PATCH 042/102] Cache attribute support for BODY, TABLE, TD, TR, TH,
 TBODY, THEAD, image, feImage, and cursor elements

---
 packages/rrweb-snapshot/src/utils.ts     | 10 ++++++
 packages/rrweb/test/record/asset.test.ts | 42 +++++++++++++++++-------
 2 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 672c6ea723..c02dfdc413 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -379,6 +379,16 @@ export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['INPUT', new Set(['src'])],
   ['IFRAME', new Set(['src'])],
   ['OBJECT', new Set(['src'])],
+  ['BODY', new Set(['background'])],
+  ['TABLE', new Set(['background'])],
+  ['TD', new Set(['background'])],
+  ['TR', new Set(['background'])],
+  ['TH', new Set(['background'])],
+  ['TBODY', new Set(['background'])],
+  ['THEAD', new Set(['background'])],
+  ['image', new Set(['href', 'xlink:href'])],
+  ['feImage', new Set(['href', 'xlink:href'])],
+  ['cursor', new Set(['href', 'xlink:href'])],
 ]);
 
 export function isAttributeCacheable(n: Element, attribute: string): boolean {
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index 1105d07326..dfc7bb7ad7 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -595,16 +595,33 @@ describe('asset caching', function (this: ISuite) {
       `
         <!DOCTYPE html>
         <html>
-          <body>
+          <body background="{SERVER_URL}/html/assets/robot.png?body">
             <img src="{SERVER_URL}/html/assets/robot.png?img" />
             <video><track default kind="captions" srclang="en" src="{SERVER_URL}/html/assets/subtitles.vtt" /><source src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?source" /></video>
-            <video src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video" type="audio/mp3" />
-            <audio src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio" type="audio/mp3" />
+            <video src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video" type="audio/mp3"></video>
+            <audio src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio" type="audio/mp3"></audio>
             <embed type="video/webm" src="{SERVER_URL}/html/assets/1-minute-of-silence.mp3?embed" width="250" height="200" />
             <img srcset="{SERVER_URL}/html/assets/robot.png?1x, {SERVER_URL}/html/assets/robot.png?2x 2x" />
             <img src="{SERVER_B_URL}/html/assets/robot.png?img" />
             <input type="image" id="image" alt="Login" src="{SERVER_URL}/html/assets/robot.png?input-type-image" />
-            <iframe src="{SERVER_URL}/html/assets/robot.png?iframe" />
+            <iframe src="{SERVER_URL}/html/assets/robot.png?iframe"></iframe>
+            <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+              <image href="{SERVER_URL}/html/assets/robot.png?svg" xlink:href="{SERVER_URL}/html/assets/robot.png?svg2" width="100" height="100" />
+                <defs>
+                <filter id="image">
+                  <feImage href="{SERVER_URL}/html/assets/robot.png?svg3" xlink:href="{SERVER_URL}/html/assets/robot.png?svg4" />
+                </filter>
+              </defs>
+              <cursor href="{SERVER_URL}/html/assets/robot.png?svg5" xlink:href="{SERVER_URL}/html/assets/robot.png?svg6" >
+              <rect x="10%" y="10%" width="80%" height="80%" style="filter:url(#image);" />
+              </cursor>
+            </svg>
+            <table background="{SERVER_URL}/html/assets/robot.png?table">
+              <tr>
+                <td background="{SERVER_URL}/html/assets/robot.png?td">foo</td>
+                <td>bar</td>
+              </tr>
+            </table>
           </body>
         </html>
       `,
@@ -617,6 +634,7 @@ describe('asset caching', function (this: ISuite) {
     );
 
     [
+      '{SERVER_URL}/html/assets/robot.png?body',
       `{SERVER_URL}/html/assets/robot.png?img`,
       `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio`,
       `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video`,
@@ -627,6 +645,14 @@ describe('asset caching', function (this: ISuite) {
       '{SERVER_URL}/html/assets/robot.png?2x',
       '{SERVER_URL}/html/assets/robot.png?input-type-image',
       '{SERVER_URL}/html/assets/robot.png?iframe',
+      '{SERVER_URL}/html/assets/robot.png?svg',
+      '{SERVER_URL}/html/assets/robot.png?svg2',
+      '{SERVER_URL}/html/assets/robot.png?svg3',
+      '{SERVER_URL}/html/assets/robot.png?svg4',
+      '{SERVER_URL}/html/assets/robot.png?svg5',
+      '{SERVER_URL}/html/assets/robot.png?svg6',
+      '{SERVER_URL}/html/assets/robot.png?table',
+      '{SERVER_URL}/html/assets/robot.png?td',
     ].forEach((u) => {
       it(`should capture ${u} with origin defined in config`, async () => {
         const url = u.replace(/\{SERVER_URL\}/g, ctx.serverURL);
@@ -696,12 +722,4 @@ describe('asset caching', function (this: ISuite) {
       );
     });
   });
-
-  test.todo('should support video elements');
-  test.todo('should support audio elements');
-  test.todo('should support embed elements');
-  test.todo('should support source elements');
-  test.todo('should support track elements');
-  test.todo('should support input#type=image elements');
-  test.todo('should support img srcset');
 });

From 7c438909a4006f574540e3221addda8508dd396c Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 16:57:19 +0100
Subject: [PATCH 043/102] Remove unused 'inlineImages' option

---
 packages/rrweb/src/record/mutation.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index e8bb8d2c36..f1bfcc0cbe 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -207,7 +207,6 @@ export default class MutationBuffer {
         'maskInputFn',
         'keepIframeSrcFn',
         'recordCanvas',
-        'inlineImages',
         'slimDOMOptions',
         'dataURLOptions',
         'doc',

From 2ab47a2fac6b4c17ae65382bf8c02ddc216b780c Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 16:57:38 +0100
Subject: [PATCH 044/102] Refactor AssetManager constructor signature

---
 packages/rrweb/src/replay/assets/index.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 5c4cad1123..367f3609e4 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -19,7 +19,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
   > = new Map();
   private config: captureAssetsParam | undefined;
 
-  constructor(config: captureAssetsParam | undefined) {
+  constructor(config?: captureAssetsParam | undefined) {
     this.config = config;
   }
 

From 6546dbd78fc9c3fc4ecad64e8484c15437769003 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 17:03:52 +0100
Subject: [PATCH 045/102] Remove console.log statements in asset.test.ts

---
 packages/rrweb/test/record/asset.test.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index dfc7bb7ad7..8f87d11265 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -191,7 +191,6 @@ describe('asset caching', function (this: ISuite) {
           },
         },
       };
-      console.log(events);
       expect(events[events.length - 1]).toMatchObject(expected);
     });
 
@@ -237,7 +236,6 @@ describe('asset caching', function (this: ISuite) {
           },
         },
       };
-      console.log(events);
       expect(events[events.length - 1]).toMatchObject(expected);
     });
   });
@@ -387,7 +385,6 @@ describe('asset caching', function (this: ISuite) {
       );
 
       // expect no event to be emitted with `event.type` === EventType.Asset
-      console.log(events);
       expect(events).not.toContainEqual(
         expect.objectContaining({
           type: EventType.Asset,
@@ -656,7 +653,6 @@ describe('asset caching', function (this: ISuite) {
     ].forEach((u) => {
       it(`should capture ${u} with origin defined in config`, async () => {
         const url = u.replace(/\{SERVER_URL\}/g, ctx.serverURL);
-        console.log(url, ctx.serverURL);
         await ctx.page.waitForNetworkIdle({ idleTime: 100 });
         await waitForRAF(ctx.page);
 

From 55c24fac60eb5807df3810a2e888cc87458b8640 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 17:05:48 +0100
Subject: [PATCH 046/102] Refactor reset method signature to accept optional
 config parameter

---
 packages/rrweb/src/replay/assets/index.ts | 2 +-
 packages/types/src/index.ts               | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 367f3609e4..66de51e976 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -173,7 +173,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     return Promise.all(promises);
   }
 
-  public reset(config: captureAssetsParam | undefined): void {
+  public reset(config?: captureAssetsParam | undefined): void {
     this.config = config;
     this.originalToObjectURLMap.forEach((objectURL) => {
       URL.revokeObjectURL(objectURL);
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 2496846487..598d37e455 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -761,11 +761,11 @@ export type RebuildAssetManagerStatus =
   | RebuildAssetManagerFinalStatus;
 
 export declare abstract class RebuildAssetManagerInterface {
-  constructor(config: captureAssetsParam | undefined);
+  constructor(config?: captureAssetsParam | undefined);
   abstract add(event: assetEvent): Promise<void>;
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
-  abstract reset(config: captureAssetsParam | undefined): void;
+  abstract reset(config?: captureAssetsParam | undefined): void;
   abstract isAttributeCacheable(n: Element, attribute: string): boolean;
   abstract isURLOfCacheableOrigin(url: string): boolean;
   abstract manageAttribute(n: Element, attribute: string): void;

From fa6e086d690dccce8f0484212df0fb5b216a5a83 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 17:10:11 +0100
Subject: [PATCH 047/102] Fix deserialized size comparison in deserializeArg
 test

---
 packages/rrweb/test/replay/deserialize-args.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/test/replay/deserialize-args.test.ts b/packages/rrweb/test/replay/deserialize-args.test.ts
index 1343bd28b0..8d8256ca0a 100644
--- a/packages/rrweb/test/replay/deserialize-args.test.ts
+++ b/packages/rrweb/test/replay/deserialize-args.test.ts
@@ -153,7 +153,7 @@ describe('deserializeArg', () => {
     // more info: https://github.com/jsdom/jsdom/issues/2555
     expect(deserialized).toEqual(expected);
     // thats why we test size of the blob as well
-    expect(deserialized.size).toEqual(expected.size);
+    expect((deserialized as Blob)?.size).toEqual(expected.size);
   });
 
   describe('isUnchanged', () => {

From 55167807cdd21556eb4e3d73a8ca0737fe85d424 Mon Sep 17 00:00:00 2001
From: Juice10 <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 16:05:19 +0000
Subject: [PATCH 048/102] Apply formatting changes

---
 packages/rrweb/src/replay/assets/index.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 66de51e976..26a4c72a31 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -156,7 +156,6 @@ export default class AssetManager implements RebuildAssetManagerInterface {
         ? getSourcesFromSrcset(originalValue)
         : [originalValue];
     values.forEach((value) => {
-
       promises.push(
         this.whenReady(value).then((status) => {
           if (

From 8598eabc1f450e30ca12375979773f0133190ace Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 4 Dec 2023 17:12:34 +0100
Subject: [PATCH 049/102] Fix asset manager to only load assets from cacheable
 origins

---
 packages/rrweb/src/replay/assets/index.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index 26a4c72a31..bea6c09163 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -156,6 +156,8 @@ export default class AssetManager implements RebuildAssetManagerInterface {
         ? getSourcesFromSrcset(originalValue)
         : [originalValue];
     values.forEach((value) => {
+      if (!this.isURLOfCacheableOrigin(value)) return;
+
       promises.push(
         this.whenReady(value).then((status) => {
           if (

From 2c12cabbd9ecd26933bfd68db4051b1044ec08ca Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 5 Dec 2023 00:04:11 +0100
Subject: [PATCH 050/102] Add cacheRoot option to typescript plugin in
 rollup.config.js

---
 packages/rrdom/rollup.config.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/rrdom/rollup.config.js b/packages/rrdom/rollup.config.js
index 5bd346673f..dc9a7993dc 100644
--- a/packages/rrdom/rollup.config.js
+++ b/packages/rrdom/rollup.config.js
@@ -18,6 +18,7 @@ const basePlugins = [
 
   typescript({
     tsconfigOverride: { compilerOptions: { module: 'ESNext' } },
+    cacheRoot: `./node_modules/.cache/rrdom/`,
   }),
 ];
 

From 2a25fec026a8d3a2ae735ab55a293590e84e6f8a Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 5 Dec 2023 00:09:29 +0100
Subject: [PATCH 051/102] Add cacheRoot option to tsconfigOverride in
 compileTSCode function

---
 packages/rrdom/test/utils.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/rrdom/test/utils.ts b/packages/rrdom/test/utils.ts
index da616a64f5..15d22360a6 100644
--- a/packages/rrdom/test/utils.ts
+++ b/packages/rrdom/test/utils.ts
@@ -14,6 +14,7 @@ export async function compileTSCode(inputFilePath: string) {
       _typescript({
         tsconfigOverride: { compilerOptions: { module: 'ESNext' } },
         clean: true,
+        cacheRoot: `./node_modules/.cache/rrdom-test/${Date.now()}/`,
       }) as unknown as rollup.Plugin,
     ],
   });

From 44fbc1043d40160f8d5c8026ae64275a24ae88fe Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 5 Dec 2023 00:23:24 +0100
Subject: [PATCH 052/102] Update defaultPort in startServer function

---
 packages/rrweb/test/utils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index 4afc2df1f2..a263edbd1f 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -44,7 +44,7 @@ export interface ISuite {
   events: eventWithTime[];
 }
 
-export const startServer = (defaultPort: number = 3030) =>
+export const startServer = (defaultPort: number = 3031) =>
   new Promise<http.Server>((resolve) => {
     const mimeType: IMimeType = {
       '.html': 'text/html',

From 445c7bb2a0b50f004b1d238dbd8269c1adcbf9d9 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 5 Dec 2023 00:31:06 +0100
Subject: [PATCH 053/102] Make test more reliable

---
 packages/rrweb/test/integration.test.ts | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index a55ac5d0eb..297fd5044a 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -117,17 +117,20 @@ describe('record integration tests', function (this: ISuite) {
       ta.innerText = 'pre value';
       document.body.append(ta);
     });
+    await waitForRAF(page);
     await page.evaluate(() => {
       const t = document.querySelector('textarea') as HTMLTextAreaElement;
       t.innerText = 'ok'; // this mutation should be recorded
     });
+    await waitForRAF(page);
     await page.evaluate(() => {
       const t = document.querySelector('textarea') as HTMLTextAreaElement;
       (t.childNodes[0] as Text).appendData('3'); // this mutation is also valid
     });
+    await waitForRAF(page);
 
     await page.type('textarea', '1'); // types (inserts) at index 0, in front of existing text
-
+    await waitForRAF(page);
     await page.evaluate(() => {
       const t = document.querySelector('textarea') as HTMLTextAreaElement;
       // user has typed so childNode content should now be ignored
@@ -138,8 +141,9 @@ describe('record integration tests', function (this: ISuite) {
       // there is nothing explicit in rrweb which enforces this, but this test may protect against
       // a future change where a mutation on a textarea incorrectly updates the .value
     });
-
+    await waitForRAF(page);
     await page.type('textarea', '2'); // cursor is at index 1
+    await waitForRAF(page);
 
     const snapshots = (await page.evaluate(
       'window.snapshots',

From 9bc508ab917085d004626470d76b8a79fd2aede9 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 11 Dec 2023 14:50:45 +0100
Subject: [PATCH 054/102] Add failing test for src attribute

---
 .../assets-src-changed-before-asset-loaded.ts | 175 ++++++++++++++++++
 ...e-mode-when-asset-fails-to-load-1-snap.png | Bin 0 -> 11674 bytes
 ...de-when-asset-never-gets-loaded-1-snap.png | Bin 0 -> 11674 bytes
 ...ile-src-is-changed-in-live-mode-1-snap.png | Bin 0 -> 28398 bytes
 ...set-red-square-in-non-live-mode-1-snap.png | Bin 0 -> 10796 bytes
 ...ed-asset-robot-in-non-live-mode-1-snap.png | Bin 0 -> 28398 bytes
 .../test/replay/asset-integration.test.ts     | 162 +++++++++++++++-
 7 files changed, 329 insertions(+), 8 deletions(-)
 create mode 100644 packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-fails-to-load-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-never-gets-loaded-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-correct-asset-when-assets-are-loading-while-src-is-changed-in-live-mode-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-red-square-in-non-live-mode-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-robot-in-non-live-mode-1-snap.png

diff --git a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
new file mode 100644
index 0000000000..b533ff9d83
--- /dev/null
+++ b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
@@ -0,0 +1,175 @@
+import { EventType, IncrementalSource, type eventWithTime } from '@rrweb/types';
+
+const events: eventWithTime[] = [
+  {
+    type: 4,
+    data: {
+      href: '',
+      width: 1600,
+      height: 900,
+      captureAssets: {
+        origins: ['ftp://example.com'],
+        objectURLs: false,
+      },
+    },
+    timestamp: 1636379531385,
+  },
+  {
+    type: 2,
+    data: {
+      node: {
+        type: 0,
+        childNodes: [
+          { type: 1, name: 'html', publicId: '', systemId: '', id: 2 },
+          {
+            type: 2,
+            tagName: 'html',
+            attributes: { lang: 'en' },
+            childNodes: [
+              {
+                type: 2,
+                tagName: 'head',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 5 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: { charset: 'UTF-8' },
+                    childNodes: [],
+                    id: 6,
+                  },
+                  { type: 3, textContent: '\n    ', id: 7 },
+                  {
+                    type: 2,
+                    tagName: 'meta',
+                    attributes: {
+                      name: 'viewport',
+                      content: 'width=device-width, initial-scale=1.0',
+                    },
+                    childNodes: [],
+                    id: 8,
+                  },
+                  { type: 3, textContent: '\n    ', id: 9 },
+                  {
+                    type: 2,
+                    tagName: 'title',
+                    attributes: {},
+                    childNodes: [{ type: 3, textContent: 'assets', id: 11 }],
+                    id: 10,
+                  },
+                  { type: 3, textContent: '\n  ', id: 12 },
+                ],
+                id: 4,
+              },
+              { type: 3, textContent: '\n  ', id: 13 },
+              {
+                type: 2,
+                tagName: 'body',
+                attributes: {},
+                childNodes: [
+                  { type: 3, textContent: '\n    ', id: 15 },
+                  {
+                    type: 2,
+                    tagName: 'img',
+                    attributes: {
+                      width: '100',
+                      height: '100',
+                      style: 'border: 1px solid #000000',
+                      src: 'ftp://example.com/original-image.png',
+                    },
+                    childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
+                    id: 16,
+                  },
+                  { type: 3, textContent: '\n    ', id: 18 },
+                  {
+                    type: 2,
+                    tagName: 'script',
+                    attributes: {},
+                    childNodes: [
+                      { type: 3, textContent: 'SCRIPT_PLACEHOLDER', id: 20 },
+                    ],
+                    id: 19,
+                  },
+                  { type: 3, textContent: '\n  \n\n', id: 21 },
+                ],
+                id: 14,
+              },
+            ],
+            id: 3,
+          },
+        ],
+        id: 1,
+      },
+      initialOffset: { left: 0, top: 0 },
+    },
+    timestamp: 1636379531389,
+  },
+  {
+    type: EventType.IncrementalSnapshot,
+    data: {
+      source: IncrementalSource.Mutation,
+      texts: [],
+      attributes: [
+        {
+          id: 16,
+          attributes: {
+            src: 'ftp://example.com/new-image.png',
+          },
+        },
+      ],
+      removes: [],
+      adds: [],
+    },
+    timestamp: 1636379531390,
+  },
+  {
+    type: EventType.Asset,
+    data: {
+      url: 'ftp://example.com/original-image.png',
+      payload: {
+        rr_type: 'Blob',
+        type: 'image/png',
+        data: [
+          {
+            rr_type: 'ArrayBuffer',
+            base64:
+              'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAWtJREFUeF7t1cEJAEAIxEDtv2gProo8xgpCwuLezI3LGFhBMi0+iCCtHoLEeggiSM1AjMcPESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4ViIIDEDMRwLESRmIIZjIYLEDMRwLESQmIEYjoUIEjMQw7EQQWIGYjgWIkjMQAzHQgSJGYjhWIggMQMxHAsRJGYghmMhgsQMxHAsRJCYgRiOhQgSMxDDsRBBYgZiOBYiSMxADMdCBIkZiOFYiCAxAzEcCxEkZiCGYyGCxAzEcCxEkJiBGI6FCBIzEMOxEEFiBmI4FiJIzEAMx0IEiRmI4TwVjsedWCiXGAAAAABJRU5ErkJggg==', // base64
+          },
+        ],
+      },
+    },
+    timestamp: 1636379531391,
+  },
+  {
+    type: EventType.Asset,
+    data: {
+      url: 'ftp://example.com/original-image.png',
+      failed: {
+        status: 404,
+        message: 'Not Found',
+      },
+    },
+    timestamp: 1636379531391,
+  },
+  {
+    type: EventType.Asset,
+    data: {
+      url: 'ftp://example.com/new-image.png',
+      payload: {
+        rr_type: 'Blob',
+        type: 'image/png',
+        data: [
+          {
+            rr_type: 'ArrayBuffer',
+            base64:
+              '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', // base64 robot image
+          },
+        ],
+      },
+    },
+    timestamp: 1636379531392,
+  },
+];
+
+export default events;
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-fails-to-load-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-fails-to-load-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..44bc0f698cb597db9acd7022856bde59c629c4a7
GIT binary patch
literal 11674
zcmeHNYfMvT7(QGaRRmEo!HFPqYL*46R6ztpk*Vl_O*d`@%0wt(0wqLCfeKR+H7c^`
ziU*;M+msL#0v(iFX&p|;sI7q^RG?f6EefT@(n9IwjB*Z(Ka2sF%tX%jr*FSozdqmd
zKHu%V{B_V_t5u(@0sz1&;E4Zm0I<jb0P{VVkLGelJ02dK`!Gp6e%KEv=R3RrfXzUF
z|ABAQi^ltc@82axaVPuhruoIRJX+1s6<2rr1!M*mVghgeUTM4T;4k?4)U7L-<j?UT
zr^D<?-+AOGCt6v0gvCw;C7wFap}^Y5-s}#$6qq{70lhjh1!E_NY)JVvbewIIQu7%(
zl}`~#4eEYxIWvM5w_$D^#&m8%);n$j+d%hf6MNX)><1VJJeMxn<RHbLn#auN06Ca9
zRO{A;Thh~qE^pjvF<*-tm`Wn;xbFR`^@*cd%$6dgzvOTW3yU-oiNtIHx^FQkrZd?^
zUgYH4X>ixQBs<s0THzMp^i5kXoCT~k-2q$sbAi>cW#bH(!InRU3mb2&R>>;87E;!E
zujb?hT3p>+w{Y@keQc`W@dgJ6x3R>~i*X^-Bhwr%8u;jW0zd~T<CQJ$?5%RHMk!5E
zW8gBbRm~*ejZ+Ta2yPmwUgI<M)LX$dV{$m0Yu_L}CvU*7<hHpfEBm#2&7WTKLdID$
zSt897A`5tF0w0f*#?;h(y21X!j*{S%SO>UO4*n0Cslu&dlS>+<ntns2vQ?D!#g#i?
zm5Syyg3=Qu5+M=#-jO=>Q=P1)r)K9mxY>RY-`zcmri%28mRxHaqv#WSSn>*qVS3_8
zg^1}@;S{BxXdhAC?+Wemof){GytAgySKzJWMK-3VZrp-Q>TuGCxgvwjtK#w8l!gYf
z)Q3F5=ka9PGRkO^UZb`wAcCOyhS<~FyQsOj83ctwVQ~q4_KIeBJCv|77U|}<B-zJL
z+jv=uP_-nlql}?D^K2|GFE3caa?;Nf)lpN_XNhM+$|$l<OQi=vQ<iiU!8@OI*Oog5
z#z_cGWJ<Y1MMXnAkw}d8iI5Ea>_Mz96!!Fi;$KHzG7*3qlki?9jnTC5XKh9I3&zSl
z!?8-q<n?`WxeIe|bbNR@#}NS)>UkCfVB0z~a|#&ia<bd2c)fEokT%g<@*0961QT{N
zLEn%Fibq~G&PuZR6EFWR5^6_;QN{IY-X?9CMn!loe7pzwc~<TNwrK`f<~%bzd@7o%
z)GCWZ4H<Ok;Rz;_RS1F&>WSOed-WHUKajIdofG?z1r{Q5kNY76rrNmEBdxLwHeFT4
z%K#~pLoA6?wnC~@h0>eWXA|RH9U!eve~fWkh0Rh$=%|{J0;wfohWQ#?)wWz54!em;
zY?}Owz_+yHxZ@zL>wvGJSiozR>-#{EB1-_XGhYlp{}(eKe7#vZ*oB3<vcwk&bUQ4S
zT5gx^tF6THsbrBa4kzVkSxVg%XqOXpdV4&3{esJjF1r*1(E?^a)UIop#ivz8YAX9h
zXI}EM3}OPET)Y>D!x3gB41TCVRa!STrH%Kjcf)6Vv3r4`A*PDE#|q-7HMq3?tf`hJ
z&a@~~+FKVKrPWP!1Pd%7-K<Wcd)O%rDIh{q2KPgzaLd-7qkv(m1R8dEkg1z&VNJ>r
z1d{_qtR;%TBDDgoW`us}8jIrg{&0IFy654YZP49gEi7Ly-3h?HO!fFS7P3GNBz1=Y
z(X_Y|c*KJg*0pBuyvi?fGk{hza$@P5C=@pox3_PzQHG!lK^bD?z@ks4sLP-(gSrgr
zGH<-LLd5|U2UHwTaag=K+=Ale)<O1{e%y?S7)Wr38ePpjb8#5Pozjf4olyMRc`Sj&
z-cX~rCq}%`TPK(`|KX_vB@#-cw?Bq0Z-%f;(jTZES;Kw?Sl2D}ee-|ZC=R&;ayFwI
zxv%9q+i{Umqw+8h#D_=oQTy7?xqyKq##jL3UqshKnDHp#KTyJ>B>(V444u<uG^dRm
z$}U{RSp7Nn3RtRh%vg3@oU#zyUIS+Uq!<}{nsa#J8{dhGn5e4H1F?+Q^41`|<$W%B
z_uxhpH=`S&TV+^P-;by=)Z_b&fx<EQ9F<Q~`;mpP+^;Y(@UU`LnjqnNJDeAy3b&af
u0lnPZg;V$~8!lWQur)=3lVbedxid_aXXJUuj^_Cw=YTJR{LB4f&i?}mPC~-~

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-never-gets-loaded-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-list-original-url-in-non-live-mode-when-asset-never-gets-loaded-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..44bc0f698cb597db9acd7022856bde59c629c4a7
GIT binary patch
literal 11674
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

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-correct-asset-when-assets-are-loading-while-src-is-changed-in-live-mode-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-correct-asset-when-assets-are-loading-while-src-is-changed-in-live-mode-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f6ea7a349359d3a8fb905a40ec7f898d57c1d11
GIT binary patch
literal 28398
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
R;8{?~;_2$=vd$@?2>`+bk4yjn

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-red-square-in-non-live-mode-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-red-square-in-non-live-mode-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-robot-in-non-live-mode-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-show-the-loaded-asset-robot-in-non-live-mode-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f6ea7a349359d3a8fb905a40ec7f898d57c1d11
GIT binary patch
literal 28398
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
R;8{?~;_2$=vd$@?2>`+bk4yjn

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 1ab6f75e66..2c2eab4725 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -5,6 +5,8 @@ import { toMatchImageSnapshot } from 'jest-image-snapshot';
 import type * as puppeteer from 'puppeteer';
 import events from '../events/assets';
 import mutationEvents from '../events/assets-mutation';
+import assetsChangedEvents from '../events/assets-src-changed-before-asset-loaded';
+import type { assetEvent } from '@rrweb/types';
 
 interface ISuite {
   code: string;
@@ -41,6 +43,9 @@ describe('replayer', function () {
     await page.evaluate(
       `let mutationEvents = ${JSON.stringify(mutationEvents)}`,
     );
+    await page.evaluate(
+      `let assetsChangedEvents = ${JSON.stringify(assetsChangedEvents)}`,
+    );
 
     page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
   });
@@ -115,13 +120,154 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
-    test.todo("should support work through rrelement's too");
-    test.todo('should support video elements');
-    test.todo('should support audio elements');
-    test.todo('should support embed elements');
-    test.todo('should support source elements');
-    test.todo('should support track elements');
-    test.todo('should support input#type=image elements');
-    test.todo('should support img srcset');
+    it('on mutation should wait with adding src attribute until the asset is loaded', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([], {
+        liveMode: true,
+      });
+      replayer.startLive(mutationEvents[0].timestamp);
+      window.replayer.addEvent(mutationEvents[0]);
+      window.replayer.addEvent(mutationEvents[1]);
+      window.replayer.addEvent(mutationEvents[2]);
+    `);
+
+      await waitForRAF(page);
+
+      expect(
+        await page.evaluate(
+          `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
+        ),
+      ).toBe(null);
+
+      await page.evaluate(`
+        window.replayer.addEvent(mutationEvents[3]);
+      `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should wait with adding src attribute until the asset is loaded 2', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([], {
+        liveMode: true,
+      });
+      replayer.startLive(events[0].timestamp);
+      window.replayer.addEvent(events[0]);
+      window.replayer.addEvent(events[1]);
+    `);
+
+      await waitForRAF(page);
+
+      expect(
+        await page.evaluate(
+          `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
+        ),
+      ).toBe(null);
+
+      await page.evaluate(`
+        window.replayer.addEvent(events[2]);
+      `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should show the correct asset when assets are loading while src is changed in live mode', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([], {
+        liveMode: true,
+      });
+      replayer.startLive(assetsChangedEvents[0].timestamp);
+      window.replayer.addEvent(assetsChangedEvents[0]);
+      window.replayer.addEvent(assetsChangedEvents[1]);
+      window.replayer.addEvent(assetsChangedEvents[2]);
+      window.replayer.addEvent(assetsChangedEvents[3]);
+      window.replayer.addEvent(assetsChangedEvents[4]);
+    `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should show the loaded asset (robot) in non-live mode', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer(assetsChangedEvents);
+      replayer.pause(assetsChangedEvents[1].timestamp);
+    `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should show the loaded asset (red square) in non-live mode', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer(assetsChangedEvents);
+      replayer.pause(assetsChangedEvents[2].timestamp);
+    `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should list original url in non-live mode when asset never gets loaded', async () => {
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([assetsChangedEvents[0], assetsChangedEvents[1]]);
+      replayer.pause(assetsChangedEvents[1].timestamp);
+    `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+    });
+
+    it('should list original url in non-live mode when asset fails to load', async () => {
+      const failedEvent: assetEvent & { timestamp: number } = {
+        type: 7,
+        data: {
+          url: 'ftp://example.com/original-image.png',
+          failed: {
+            status: 404,
+            message: 'Not Found',
+          },
+        },
+        timestamp: assetsChangedEvents[2].timestamp,
+      };
+      await page.evaluate(`
+      const { Replayer } = rrweb;
+      window.replayer = new Replayer([assetsChangedEvents[0], assetsChangedEvents[1], ${JSON.stringify(
+        failedEvent,
+      )}]);
+      replayer.pause(assetsChangedEvents[1].timestamp);
+    `);
+
+      await waitForRAF(page);
+
+      const image = await page.screenshot();
+      expect(image).toMatchImageSnapshot();
+
+      expect(
+        await page.evaluate(
+          `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
+        ),
+      ).toMatchInlineSnapshot(`"ftp://example.com/original-image.png"`);
+    });
   });
 });

From a236dea9ea9ffc0da4e73e49b29ae4976cd52598 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 11 Dec 2023 15:32:22 +0100
Subject: [PATCH 055/102] Remove failed asset event

---
 .../events/assets-src-changed-before-asset-loaded.ts  | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
index b533ff9d83..3109e89442 100644
--- a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
+++ b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
@@ -141,17 +141,6 @@ const events: eventWithTime[] = [
     },
     timestamp: 1636379531391,
   },
-  {
-    type: EventType.Asset,
-    data: {
-      url: 'ftp://example.com/original-image.png',
-      failed: {
-        status: 404,
-        message: 'Not Found',
-      },
-    },
-    timestamp: 1636379531391,
-  },
   {
     type: EventType.Asset,
     data: {

From 6bc21641b758514911ffa12d1cbf8198bbabbe22 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 12 Dec 2023 16:25:28 +0100
Subject: [PATCH 056/102] Add support for asset manager in rrdom And remove
 attribute while asset is loading

---
 packages/rrdom/package.json                   |   1 +
 packages/rrdom/src/diff.ts                    |  10 +-
 packages/rrdom/test/diff.test.ts              |  36 +++
 packages/rrweb-snapshot/src/rebuild.ts        |   2 +-
 packages/rrweb/src/replay/assets/index.ts     |  91 +++++--
 packages/rrweb/src/replay/index.ts            |  15 +-
 .../assets-src-changed-before-asset-loaded.ts |  26 +-
 .../asset-integration-test-ts-loading.png     | Bin 0 -> 10923 bytes
 ...oesnt-display-broken-image-icon-1-snap.png | Bin 0 -> 10796 bytes
 ...oesnt-display-broken-image-icon-2-snap.png | Bin 0 -> 10796 bytes
 ...ibute-until-the-asset-is-loaded-1-snap.png | Bin 0 -> 10796 bytes
 ...ute-until-the-asset-is-loaded-2-1-snap.png | Bin 0 -> 10923 bytes
 .../test/replay/asset-integration.test.ts     |  23 +-
 packages/rrweb/test/replay/asset-unit.test.ts | 252 ++++++++++++++++--
 packages/types/src/index.ts                   |   9 +-
 yarn.lock                                     |   5 +
 16 files changed, 405 insertions(+), 65 deletions(-)
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-loading.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-2-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-1-snap.png
 create mode 100644 packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-2-1-snap.png

diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json
index 430620782c..b784907fec 100644
--- a/packages/rrdom/package.json
+++ b/packages/rrdom/package.json
@@ -37,6 +37,7 @@
     "@types/puppeteer": "^5.4.4",
     "@typescript-eslint/eslint-plugin": "^5.23.0",
     "@typescript-eslint/parser": "^5.23.0",
+    "@total-typescript/shoehorn": "^0.1.0",
     "eslint": "^8.15.0",
     "jest": "^27.5.1",
     "puppeteer": "^17.1.3",
diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index 56f9414c62..7568946ac3 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -8,6 +8,7 @@ import {
   scrollData,
   styleDeclarationData,
   styleSheetRuleData,
+  RebuildAssetManagerInterface,
 } from '@rrweb/types';
 import type {
   IRRCDATASection,
@@ -75,6 +76,7 @@ const SVGTagMap: Record<string, string> = {
 
 export type ReplayerHandler = {
   mirror: NodeMirror;
+  assetManager: RebuildAssetManagerInterface;
   applyCanvas: (
     canvasEvent: canvasEventWithTime,
     canvasMutationData: canvasMutationData,
@@ -200,7 +202,7 @@ function diffBeforeUpdatingChildren(
        * by running `diffProps` on the parent node before `diffChildren` is called,
        * we can ensure that the correct attributes (and therefore styles) have applied to parent nodes
        */
-      diffProps(oldElement, newRRElement, rrnodeMirror);
+      diffProps(oldElement, newRRElement, rrnodeMirror, replayer.assetManager);
       break;
     }
   }
@@ -309,6 +311,7 @@ function diffProps(
   oldTree: HTMLElement,
   newTree: IRRElement,
   rrnodeMirror: Mirror,
+  assetManager?: RebuildAssetManagerInterface,
 ) {
   const oldAttributes = oldTree.attributes;
   const newAttributes = newTree.attributes;
@@ -329,6 +332,11 @@ function diffProps(
       };
     } else if (newTree.tagName === 'IFRAME' && name === 'srcdoc') continue;
     else oldTree.setAttribute(name, newValue);
+
+    if (assetManager && assetManager.isCacheable(oldTree, name, newValue)) {
+      // can possibly remove the attribute again if it hasn't loaded yet
+      assetManager.manageAttribute(oldTree, rrnodeMirror.getId(newTree), name);
+    }
   }
 
   for (const { name } of Array.from(oldAttributes))
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index 77e5717904..b50646e25e 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -3,6 +3,7 @@
  */
 import * as path from 'path';
 import * as puppeteer from 'puppeteer';
+import { fromPartial } from '@total-typescript/shoehorn';
 import { createMirror, Mirror as NodeMirror } from 'rrweb-snapshot';
 import {
   buildFromDom,
@@ -27,6 +28,7 @@ import type {
   canvasMutationData,
   styleDeclarationData,
   styleSheetRuleData,
+  RebuildAssetManagerInterface,
 } from '@rrweb/types';
 import {
   NodeType as RRNodeType,
@@ -462,6 +464,40 @@ describe('diff algorithm for rrdom', () => {
       diff(element, rrIframe, replayer);
       expect(element.getAttribute('srcdoc')).toBe(null);
     });
+
+    describe('with asset manager', () => {
+      let assetManager: RebuildAssetManagerInterface;
+      beforeEach(() => {
+        assetManager = fromPartial({
+          manageAttribute: jest.fn(),
+          isCacheable: jest.fn(),
+        });
+        replayer.assetManager = assetManager;
+      });
+
+      it('new properties are managed by asset manager if cacheable', () => {
+        const tagName = 'IMG';
+        const node = document.createElement(tagName);
+        const sn = Object.assign({}, elementSn, { tagName });
+        mirror.add(node, sn);
+
+        const rrDocument = new RRDocument();
+        const rrNode = rrDocument.createElement(tagName);
+        const sn2 = Object.assign({}, elementSn, { tagName });
+        rrDocument.mirror.add(rrNode, sn2);
+
+        (assetManager.isCacheable as jest.Mock)
+          .mockReturnValueOnce(true)
+          .mockReturnValue(false);
+        rrNode.attributes = { src: 'image.png', class: 'node' };
+        diff(node, rrNode, replayer);
+        expect(assetManager.manageAttribute).toHaveBeenCalledWith(
+          node,
+          mirror.getId(node),
+          'src',
+        );
+      });
+    });
   });
 
   describe('diff children', () => {
diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index d212e42b5e..8289c262fa 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -287,7 +287,7 @@ function buildNode(
           } else {
             node.setAttribute(name, value.toString());
 
-            options.assetManager?.manageAttribute(node, name);
+            options.assetManager?.manageAttribute(node, n.id, name);
           }
         } catch (error) {
           // skip invalid attribute
diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/assets/index.ts
index bea6c09163..68df0965ef 100644
--- a/packages/rrweb/src/replay/assets/index.ts
+++ b/packages/rrweb/src/replay/assets/index.ts
@@ -11,6 +11,8 @@ import type { RRElement } from 'rrdom';
 
 export default class AssetManager implements RebuildAssetManagerInterface {
   private originalToObjectURLMap: Map<string, string> = new Map();
+  private nodeIdAttributeHijackedMap: Map<number, Map<string, string>> =
+    new Map();
   private loadingURLs: Set<string> = new Set();
   private failedURLs: Set<string> = new Set();
   private callbackMap: Map<
@@ -18,8 +20,13 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     Array<(status: RebuildAssetManagerFinalStatus) => void>
   > = new Map();
   private config: captureAssetsParam | undefined;
+  private liveMode: boolean;
 
-  constructor(config?: captureAssetsParam | undefined) {
+  constructor(
+    { liveMode }: { liveMode: boolean },
+    config?: captureAssetsParam | undefined,
+  ) {
+    this.liveMode = liveMode;
     this.config = config;
   }
 
@@ -112,11 +119,20 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     };
   }
 
-  public isAttributeCacheable(
+  public isCacheable(
     n: RRElement | Element,
     attribute: string,
+    value: string,
   ): boolean {
-    return isAttributeCacheable(n as Element, attribute);
+    if (!isAttributeCacheable(n as Element, attribute)) return false;
+
+    if (attribute === 'srcset') {
+      return getSourcesFromSrcset(value).some((source) =>
+        this.isURLOfCacheableOrigin(source),
+      );
+    } else {
+      return this.isURLOfCacheableOrigin(value);
+    }
   }
 
   public isURLOfCacheableOrigin(url: string): boolean {
@@ -142,35 +158,65 @@ export default class AssetManager implements RebuildAssetManagerInterface {
 
   public async manageAttribute(
     node: RRElement | Element,
+    nodeId: number,
     attribute: string,
   ): Promise<unknown> {
-    if (!this.isAttributeCacheable(node, attribute)) return false;
-
     const originalValue = node.getAttribute(attribute);
-    if (!originalValue) return false;
+    if (node.nodeName === 'IMG')
+      console.log(
+        'AssetManager.manageAttribute',
+        node.nodeName,
+        attribute,
+        originalValue,
+        'livemode',
+        this.liveMode,
+      );
+    if (!originalValue || !this.isCacheable(node, attribute, originalValue))
+      return false;
 
     const promises: Promise<unknown>[] = [];
 
-    const values =
-      attribute === 'srcset'
-        ? getSourcesFromSrcset(originalValue)
-        : [originalValue];
-    values.forEach((value) => {
-      if (!this.isURLOfCacheableOrigin(value)) return;
+    if (attribute === 'srcset') {
+      const values = getSourcesFromSrcset(originalValue);
+      values.forEach((value) => {
+        if (!this.isURLOfCacheableOrigin(value)) return;
+        // FIXME... this doesn't do anything yet...
+        // TODO: hijack also doesn't work for srcset
+      });
+    } else {
+      // In live mode we removes the attribute while it loads so it doesn't show the broken image icon
+      if (this.liveMode && nodeId > 0) {
+        let hijackedAttributes = this.nodeIdAttributeHijackedMap.get(nodeId);
+        if (!hijackedAttributes) {
+          hijackedAttributes = new Map();
+          this.nodeIdAttributeHijackedMap.set(nodeId, hijackedAttributes);
+        }
+        hijackedAttributes.set(attribute, originalValue);
+        if (node.tagName === 'IMG' && attribute === 'src') {
+          node.setAttribute('src', '//:0');
+        } else {
+          node.removeAttribute(attribute);
+        }
+      }
 
       promises.push(
-        this.whenReady(value).then((status) => {
-          if (
-            status.status === 'loaded' &&
-            node.getAttribute(attribute) === originalValue
-          ) {
-            node.setAttribute(attribute, status.url);
-          } else {
-            // failed to load asset, or the attribute was changed
-          }
+        this.whenReady(originalValue).then((status) => {
+          const isLoaded = status.status === 'loaded';
+          if (!isLoaded) return; // failed to load asset
+
+          const attributeUnchanged = this.liveMode
+            ? originalValue ===
+              this.nodeIdAttributeHijackedMap.get(nodeId)?.get(attribute)
+            : node.getAttribute(attribute) === originalValue;
+
+          if (!attributeUnchanged) return; // attribute was changed since we started loading the asset
+
+          // TODO: use setAttributeNS for svg, see rrdom's diff for example
+          node.setAttribute(attribute, status.url);
         }),
       );
-    });
+    }
+
     return Promise.all(promises);
   }
 
@@ -182,6 +228,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     this.originalToObjectURLMap.clear();
     this.loadingURLs.clear();
     this.failedURLs.clear();
+    this.nodeIdAttributeHijackedMap.clear();
     this.callbackMap.forEach((callbacks) => {
       while (callbacks.length > 0) {
         const cb = callbacks.pop();
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index a3f1222e72..28b49c6367 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -137,7 +137,7 @@ export class Replayer {
 
   private imageMap: Map<eventWithTime | string, HTMLImageElement> = new Map();
 
-  private assetManager = new AssetManager();
+  private assetManager: AssetManager;
 
   private canvasEventMap: Map<eventWithTime, canvasMutationParam> = new Map();
 
@@ -199,6 +199,7 @@ export class Replayer {
       logger: console,
     };
     this.config = Object.assign({}, defaultConfig, config);
+    this.assetManager = new AssetManager({ liveMode: this.config.liveMode });
 
     this.handleResize = this.handleResize.bind(this);
     this.getCastFn = this.getCastFn.bind(this);
@@ -218,6 +219,7 @@ export class Replayer {
       if (this.usingVirtualDom) {
         const replayerHandler: ReplayerHandler = {
           mirror: this.mirror,
+          assetManager: this.assetManager,
           applyCanvas: (
             canvasEvent: canvasEventWithTime,
             canvasMutationData: canvasMutationData,
@@ -829,7 +831,7 @@ export class Replayer {
       }
     };
 
-    void this.preloadAllAssets();
+    void this.preloadAllAssets(event.timestamp);
 
     /**
      * Normally rebuilding full snapshot should not be under virtual dom environment.
@@ -1042,9 +1044,11 @@ export class Replayer {
   /**
    * Process all asset events and preload them
    */
-  private async preloadAllAssets(): Promise<void[]> {
+  private async preloadAllAssets(timestamp: number): Promise<void[]> {
     const promises: Promise<void>[] = [];
     for (const event of this.service.state.context.events) {
+      if (event.timestamp <= timestamp) continue;
+      if (event.type === EventType.Meta && event.timestamp !== timestamp) break;
       if (event.type === EventType.Asset) {
         promises.push(this.assetManager.add(event));
       }
@@ -1817,6 +1821,11 @@ export class Replayer {
                 }
               } else {
                 targetEl.setAttribute(attributeName, value);
+                void this.assetManager.manageAttribute(
+                  targetEl,
+                  mutation.id,
+                  attributeName,
+                );
               }
               void this.assetManager.manageAttribute(targetEl, attributeName);
             } catch (error) {
diff --git a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
index 3109e89442..b6bb0b9992 100644
--- a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
+++ b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
@@ -1,4 +1,5 @@
 import { EventType, IncrementalSource, type eventWithTime } from '@rrweb/types';
+import { readFileSync } from 'fs';
 
 const events: eventWithTime[] = [
   {
@@ -12,7 +13,7 @@ const events: eventWithTime[] = [
         objectURLs: false,
       },
     },
-    timestamp: 1636379531385,
+    timestamp: 100000000,
   },
   {
     type: 2,
@@ -76,7 +77,7 @@ const events: eventWithTime[] = [
                       width: '100',
                       height: '100',
                       style: 'border: 1px solid #000000',
-                      src: 'ftp://example.com/original-image.png',
+                      src: 'ftp://example.com/red.png',
                     },
                     childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
                     id: 16,
@@ -103,8 +104,9 @@ const events: eventWithTime[] = [
       },
       initialOffset: { left: 0, top: 0 },
     },
-    timestamp: 1636379531389,
+    timestamp: 100000010,
   },
+  // 2 change to robot.png
   {
     type: EventType.IncrementalSnapshot,
     data: {
@@ -114,19 +116,20 @@ const events: eventWithTime[] = [
         {
           id: 16,
           attributes: {
-            src: 'ftp://example.com/new-image.png',
+            src: 'ftp://example.com/robot.png',
           },
         },
       ],
       removes: [],
       adds: [],
     },
-    timestamp: 1636379531390,
+    timestamp: 100000020,
   },
+  // 3
   {
     type: EventType.Asset,
     data: {
-      url: 'ftp://example.com/original-image.png',
+      url: 'ftp://example.com/red.png',
       payload: {
         rr_type: 'Blob',
         type: 'image/png',
@@ -139,25 +142,26 @@ const events: eventWithTime[] = [
         ],
       },
     },
-    timestamp: 1636379531391,
+    timestamp: 100000030,
   },
   {
     type: EventType.Asset,
     data: {
-      url: 'ftp://example.com/new-image.png',
+      url: 'ftp://example.com/robot.png',
       payload: {
         rr_type: 'Blob',
         type: 'image/png',
         data: [
           {
             rr_type: 'ArrayBuffer',
-            base64:
-              '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', // base64 robot image
+            base64: readFileSync('test/html/assets/robot.png').toString(
+              'base64',
+            ),
           },
         ],
       },
     },
-    timestamp: 1636379531392,
+    timestamp: 100000040,
   },
 ];
 
diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-loading.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-loading.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c55d13d1f41cb6bf02d20306f20f966356a8669
GIT binary patch
literal 10923
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YQa%b5-0094=-yRtl7;vs$GX^mH9Gm~xj#!fC$h?=8!(nSA*<&sJl~|BJ!N
z=Pdbw#(==T@BeP?g)kWyvN;YwnSx9P5SBv=0}q5Hpvb@sVYy&YcI5JVyB~l4>`8(c
z+@O2zvrSxleEE9D4K3ma!1@mKm(F`$IPdv8Q-*0j-*P|LUt3f2XL>cn%$Rq-e^-C!
zV(_kHB+5Sg&hlT)ws+5-HBv{Nw%^VLnhLSsbsFn~#}<9-*RP-Y_=os0iQ~6#-v&Ac
z3>vs~c@HG_JpTE!@~Owa3xDhG?`49>6d0N}FhZD0EC~=s0|%o7gr$&_$uO^YoeVpK
zdw_Wl11O?^phEzzZ5rDH!(*okA>s@+2Y3@8%mxm9xEh4M2q~E0jpO<YU{)jK5$0lX
zjuYH^pfS(qXdj2_|Io0Mv7%>Vkqp!k{2#b8V2*(M<q*PQ2tOk%E?|av8et2PV;-z9
zJoZi$YG^m-0jN!aOa)iw6vHFoC|*Y+8J7SxiG$H1t7l_1D}?(X2`OdDm_EOQh$gr<
zoEZ9wU{0-*BP@&=VOZVRCzbpr4jP<0W)PPx@F!CwuOJe=`&rBK_0X`3@qtGtX(59!
z4o{vX!*L_eG@G?@75ndp_p?CLpFA*M-M@c7ybl@_4;W7~eRx|IZ3-=A8tfmikyv()
z#sfHPNv?B7!)-L&@V81vQ^;rv8BHPJG(c=ael*LBW|`3}Gn!=>u=Vms3y0CdVYF}<
zEgVJ*2XGo-z?Bz1tln#Fk*L7dq>$0L_~xI_y9AyHoKvW2TlQ-0@jtU&!J0?QS8y=k
z3c}G$Hk!%sXR^^0GI&zRkz9tjY}<{{G0_HRJ=hcmwkZ>=vj+)~$VQr3p_Q^7ya%e^
ztCvCSV<<SmIyzhn42#j~2%I2Bt0QnQjE)vTiie>xC-J4?5OAdcS0rdH!06l`Fz$v*
z#EnMQXk=koSMxq!?(CV@yVk|$Ll+!8IPjMB!<jv4&pLG>B@=^8MD_c>?**Z}IiJ}_
zn=!z!7;VOY6U1mU1{@5d%@|1WFj^ae!(p^=AhU3&VNj9v+40KY@JWyXp00i_>zopr
E0LvOrlmGw#

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-2-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-add-bogus-src-attribute-until-the-asset-is-loaded-so-chrome-doesnt-display-broken-image-icon-2-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-on-mutation-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91056eb79bc92f28acef03c14da255a83e26
GIT binary patch
literal 10796
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YL?MbD0fASRLmr%(~I^Dlk+lNcF<R<b!N$H-{U4eE9e&_q~nRqL_iYfuN!O
z{o^-`5H`abMFyy_3&#Pdm>`n@gyqn}zyo0kU{f|dUH;Dd@8NGCN-U1fDUMlxeRDcP
z&O=s+0Ux$Ko0EL5@*7V>?!0{r|B80stt(~+t3GhrR<3`uIz!G=s8R+3cHwnWOqr}z
zU-ETLv#9L3woso{Ow47h=rLS!Y`^Oyh@B1JABkUpxRrreFXFY9<NzBr4HW#OC$!OU
z8x1!^QB-)d^39D(Xu;Hwe^adCdem+|Xtw{rT*t@@$>dnetkI-2nv@Vp30ti;nnFfX
z$Y=@y7onrABycc{Hdw&HFj}92gMpHc%4m-P6ojJ*X*3~?_5#6aU^Fj4f?>2!g@nUs
ziwhhMv}$q9e|z^JC$pgdizBzp*=O^ww<@$3ust+33C*{Cc3-DUpaD9Hpu~~@iH`;j
zMhOT@!HEGh2nz%qqsl<RFd87BP+%BMA>d$O7|k-^a2S}vVF&Ml?6vA;keFa7I6;j`
z6<YOGwlh|||9gZF>Q=s;qs1yPC`L;WNO~A8MZn=OFr~<7y9N~aL$6)4e`D<~1_sVs
zptS%A;Pf+Eo{To+hhj<|B&qk&-?NKnR>ofc`dSx~ofu?x>}8lY^ZFT^jjYfN#+?0r
z?|)k+D6jb6Xfp;F6r;@;NO~A;#(=|Nv>5{qhN0J@NPr~c(d-NfhXKyc9sG^SYMU-F
R%;x|($J5o%Wt~$(696>PBJ=<N

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-2-1-snap.png b/packages/rrweb/test/replay/__image_snapshots__/asset-integration-test-ts-replayer-asset-should-wait-with-adding-src-attribute-until-the-asset-is-loaded-2-1-snap.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c55d13d1f41cb6bf02d20306f20f966356a8669
GIT binary patch
literal 10923
zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9#$4F%}28J29*~C-ahlL4m>3
z#WAE}&YQa%b5-0094=-yRtl7;vs$GX^mH9Gm~xj#!fC$h?=8!(nSA*<&sJl~|BJ!N
z=Pdbw#(==T@BeP?g)kWyvN;YwnSx9P5SBv=0}q5Hpvb@sVYy&YcI5JVyB~l4>`8(c
z+@O2zvrSxleEE9D4K3ma!1@mKm(F`$IPdv8Q-*0j-*P|LUt3f2XL>cn%$Rq-e^-C!
zV(_kHB+5Sg&hlT)ws+5-HBv{Nw%^VLnhLSsbsFn~#}<9-*RP-Y_=os0iQ~6#-v&Ac
z3>vs~c@HG_JpTE!@~Owa3xDhG?`49>6d0N}FhZD0EC~=s0|%o7gr$&_$uO^YoeVpK
zdw_Wl11O?^phEzzZ5rDH!(*okA>s@+2Y3@8%mxm9xEh4M2q~E0jpO<YU{)jK5$0lX
zjuYH^pfS(qXdj2_|Io0Mv7%>Vkqp!k{2#b8V2*(M<q*PQ2tOk%E?|av8et2PV;-z9
zJoZi$YG^m-0jN!aOa)iw6vHFoC|*Y+8J7SxiG$H1t7l_1D}?(X2`OdDm_EOQh$gr<
zoEZ9wU{0-*BP@&=VOZVRCzbpr4jP<0W)PPx@F!CwuOJe=`&rBK_0X`3@qtGtX(59!
z4o{vX!*L_eG@G?@75ndp_p?CLpFA*M-M@c7ybl@_4;W7~eRx|IZ3-=A8tfmikyv()
z#sfHPNv?B7!)-L&@V81vQ^;rv8BHPJG(c=ael*LBW|`3}Gn!=>u=Vms3y0CdVYF}<
zEgVJ*2XGo-z?Bz1tln#Fk*L7dq>$0L_~xI_y9AyHoKvW2TlQ-0@jtU&!J0?QS8y=k
z3c}G$Hk!%sXR^^0GI&zRkz9tjY}<{{G0_HRJ=hcmwkZ>=vj+)~$VQr3p_Q^7ya%e^
ztCvCSV<<SmIyzhn42#j~2%I2Bt0QnQjE)vTiie>xC-J4?5OAdcS0rdH!06l`Fz$v*
z#EnMQXk=koSMxq!?(CV@yVk|$Ll+!8IPjMB!<jv4&pLG>B@=^8MD_c>?**Z}IiJ}_
zn=!z!7;VOY6U1mU1{@5d%@|1WFj^ae!(p^=AhU3&VNj9v+40KY@JWyXp00i_>zopr
E0LvOrlmGw#

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 2c2eab4725..2e49afb2ba 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -120,7 +120,7 @@ describe('replayer', function () {
       expect(image).toMatchImageSnapshot();
     });
 
-    it('on mutation should wait with adding src attribute until the asset is loaded', async () => {
+    it("on mutation should add bogus src attribute until the asset is loaded so chrome doesn't display broken image icon", async () => {
       await page.evaluate(`
       const { Replayer } = rrweb;
       window.replayer = new Replayer([], {
@@ -134,11 +134,16 @@ describe('replayer', function () {
 
       await waitForRAF(page);
 
+      const loadingImage = await page.screenshot();
+      expect(loadingImage).toMatchImageSnapshot({
+        customSnapshotIdentifier: 'asset-integration-test-ts-loading',
+      });
+
       expect(
         await page.evaluate(
           `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
         ),
-      ).toBe(null);
+      ).toBe('//:0');
 
       await page.evaluate(`
         window.replayer.addEvent(mutationEvents[3]);
@@ -167,7 +172,7 @@ describe('replayer', function () {
         await page.evaluate(
           `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
         ),
-      ).toBe(null);
+      ).toBe('//:0');
 
       await page.evaluate(`
         window.replayer.addEvent(events[2]);
@@ -203,7 +208,8 @@ describe('replayer', function () {
       await page.evaluate(`
       const { Replayer } = rrweb;
       window.replayer = new Replayer(assetsChangedEvents);
-      replayer.pause(assetsChangedEvents[1].timestamp);
+      console.log('pausing at', (assetsChangedEvents[2].timestamp - assetsChangedEvents[0].timestamp) + 1)
+      replayer.pause((assetsChangedEvents[2].timestamp - assetsChangedEvents[0].timestamp) + 1);
     `);
 
       await waitForRAF(page);
@@ -216,8 +222,9 @@ describe('replayer', function () {
       await page.evaluate(`
       const { Replayer } = rrweb;
       window.replayer = new Replayer(assetsChangedEvents);
-      replayer.pause(assetsChangedEvents[2].timestamp);
-    `);
+      console.log('pausing at', (assetsChangedEvents[1].timestamp - assetsChangedEvents[0].timestamp) + 1)
+      replayer.pause((assetsChangedEvents[1].timestamp - assetsChangedEvents[0].timestamp) + 1);
+  `);
 
       await waitForRAF(page);
 
@@ -242,7 +249,7 @@ describe('replayer', function () {
       const failedEvent: assetEvent & { timestamp: number } = {
         type: 7,
         data: {
-          url: 'ftp://example.com/original-image.png',
+          url: 'ftp://example.com/red.png',
           failed: {
             status: 404,
             message: 'Not Found',
@@ -267,7 +274,7 @@ describe('replayer', function () {
         await page.evaluate(
           `document.querySelector('iframe').contentDocument.querySelector('img').getAttribute('src')`,
         ),
-      ).toMatchInlineSnapshot(`"ftp://example.com/original-image.png"`);
+      ).toMatchInlineSnapshot(`"ftp://example.com/red.png"`);
     });
   });
 });
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 3c00b5fca2..a6ca2a5ff7 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -33,10 +33,13 @@ describe('AssetManager', () => {
   });
 
   beforeEach(() => {
-    assetManager = new AssetManager({
-      origins: true,
-      objectURLs: true,
-    });
+    assetManager = new AssetManager(
+      { liveMode: false },
+      {
+        origins: true,
+        objectURLs: true,
+      },
+    );
   });
 
   afterEach(() => {
@@ -179,7 +182,7 @@ describe('AssetManager', () => {
     const element = document.createElement(tagName);
     attributes.forEach((attribute) => {
       it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
-        expect(assetManager.isAttributeCacheable(element, attribute)).toBe(
+        expect(assetManager['isAttributeCacheable'](element, attribute)).toBe(
           true,
         );
       });
@@ -190,7 +193,7 @@ describe('AssetManager', () => {
     const element = document.createElement(tagName);
     attributes.forEach((attribute) => {
       it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
-        expect(assetManager.isAttributeCacheable(element, attribute)).toBe(
+        expect(assetManager['isAttributeCacheable'](element, attribute)).toBe(
           false,
         );
       });
@@ -216,10 +219,13 @@ describe('AssetManager', () => {
   ];
 
   validOriginCombinations.forEach(([origin, urls]) => {
-    const assetManager = new AssetManager({
-      origins: origin,
-      objectURLs: false,
-    });
+    const assetManager = new AssetManager(
+      { liveMode: false },
+      {
+        origins: origin,
+        objectURLs: false,
+      },
+    );
     urls.forEach((url) => {
       it(`should correctly identify ${url} as cacheable for origin ${origin}`, () => {
         expect(assetManager.isURLOfCacheableOrigin(url)).toBe(true);
@@ -228,10 +234,13 @@ describe('AssetManager', () => {
   });
 
   invalidOriginCombinations.forEach(([origin, urls]) => {
-    const assetManager = new AssetManager({
-      origins: origin,
-      objectURLs: false,
-    });
+    const assetManager = new AssetManager(
+      { liveMode: false },
+      {
+        origins: origin,
+        objectURLs: false,
+      },
+    );
     urls.forEach((url) => {
       it(`should correctly identify ${url} as NOT cacheable for origin ${origin}`, () => {
         expect(assetManager.isURLOfCacheableOrigin(url)).toBe(false);
@@ -239,6 +248,91 @@ describe('AssetManager', () => {
     });
   });
 
+  const validCombinations: Array<{
+    origins: captureAssetsParam['origins'];
+    tagName: string;
+    attribute: string;
+    value: string;
+  }> = [
+    {
+      origins: ['http://example.com'],
+      tagName: 'IMG',
+      attribute: 'src',
+      value: 'http://example.com/image.png',
+    },
+    {
+      origins: ['https://example.com'],
+      tagName: 'IMG',
+      attribute: 'srcset',
+      value:
+        'https://example.com/image.png x2, https://example.com/image2.png x3',
+    },
+  ];
+
+  const invalidCombinations: Array<{
+    origins: captureAssetsParam['origins'];
+    tagName: string;
+    attribute: string;
+    value: string;
+  }> = [
+    {
+      origins: ['http://example.com'],
+      tagName: 'IMG',
+      attribute: 'src',
+      value: 'http://google.com/image.png',
+    },
+    {
+      origins: ['https://example.com'],
+      tagName: 'IMG',
+      attribute: 'href',
+      value: 'https://example.com/image.png',
+    },
+    {
+      origins: ['https://duckduckgo.com'],
+      tagName: 'IMG',
+      attribute: 'srcset',
+      value:
+        'https://example.com/image.png x2, https://example.com/image2.png x3',
+    },
+    {
+      origins: false,
+      tagName: 'IMG',
+      attribute: 'srcset',
+      value:
+        'https://example.com/image.png x2, https://example.com/image2.png x3',
+    },
+  ];
+
+  validCombinations.forEach(({ origins, tagName, attribute, value }) => {
+    const element = document.createElement(tagName);
+    element.setAttribute(attribute, value);
+    const assetManager = new AssetManager(
+      { liveMode: false },
+      {
+        origins,
+        objectURLs: false,
+      },
+    );
+    it(`should correctly identify <${element} ${attribute}=${value} /> as cacheable for origins ${origins}`, () => {
+      expect(assetManager.isCacheable(element, attribute, value)).toBe(true);
+    });
+  });
+
+  invalidCombinations.forEach(({ origins, tagName, attribute, value }) => {
+    const element = document.createElement(tagName);
+    element.setAttribute(attribute, value);
+    const assetManager = new AssetManager(
+      { liveMode: false },
+      {
+        origins,
+        objectURLs: false,
+      },
+    );
+    it(`should correctly identify <${element} ${attribute}=${value} /> as NOT cacheable for origins ${origins}`, () => {
+      expect(assetManager.isCacheable(element, attribute, value)).toBe(false);
+    });
+  });
+
   it("should be able to modify a node's attribute once asset is loaded", async () => {
     const url = 'https://example.com/image.png';
     const event: assetEvent = {
@@ -253,7 +347,7 @@ describe('AssetManager', () => {
     const element = document.createElement('img');
     element.setAttribute('src', url);
 
-    const promise = assetManager.manageAttribute(element, 'src');
+    const promise = assetManager.manageAttribute(element, 1, 'src');
 
     await assetManager.add(event);
     await promise;
@@ -276,8 +370,134 @@ describe('AssetManager', () => {
     const element = document.createElement('img');
     element.setAttribute('src', url);
 
-    await assetManager.manageAttribute(element, 'src');
+    await assetManager.manageAttribute(element, 1, 'src');
 
     expect(element.getAttribute('src')).toBe('objectURL');
   });
+
+  it("should be able to modify a node's attribute for previously loaded assets", async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+    await assetManager.add(event);
+
+    const element = document.createElement('img');
+    element.setAttribute('srcset', url);
+
+    await assetManager.manageAttribute(element, 1, 'srcset');
+
+    expect(element.getAttribute('srcset')).toBe('objectURL');
+  });
+
+  describe('live mode', () => {
+    beforeEach(() => {
+      assetManager = new AssetManager(
+        { liveMode: true },
+        {
+          origins: true,
+          objectURLs: true,
+        },
+      );
+    });
+
+    it("should remove a node's attribute while asset is being loaded", async () => {
+      const url = 'https://example.com/image.png';
+      const element = document.createElement('embed');
+      element.setAttribute('src', url);
+
+      void assetManager.manageAttribute(element, 1, 'src');
+
+      expect(element.getAttribute('src')).toBeNull();
+    });
+
+    it("should set an image's src attribute to //:0 to prevent a broken image icon while asset is being loaded", async () => {
+      const url = 'https://example.com/image.png';
+      const element = document.createElement('img');
+      element.setAttribute('src', url);
+
+      void assetManager.manageAttribute(element, 1, 'src');
+
+      expect(element.getAttribute('src')).toBe('//:0');
+    });
+
+    it("should be able to modify a node's attribute multiple times", async () => {
+      const originalUrl = 'https://example.com/original-image.png';
+      const newUrl = 'https://example.com/new-image.png';
+      const originalAsset: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url: originalUrl,
+          payload: examplePayload,
+        },
+      };
+      const newAsset: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url: newUrl,
+          payload: examplePayload,
+        },
+      };
+      let i = 0;
+      jest
+        .spyOn(URL, 'createObjectURL')
+        .mockImplementation(() => `objectURL${(i += 1)}`);
+      const promises: Promise<unknown>[] = [];
+
+      const element = document.createElement('img');
+      element.setAttribute('src', originalUrl);
+      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+
+      element.setAttribute('src', newUrl);
+      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+
+      await assetManager.add(newAsset);
+      await assetManager.add(originalAsset);
+
+      await Promise.all(promises);
+      expect(element.getAttribute('src')).toBe('objectURL1');
+    });
+
+    it("should be able to modify a node's attribute multiple times 2", async () => {
+      const originalUrl = 'https://example.com/original-image.png';
+      const newUrl = 'https://example.com/new-image.png';
+      const originalAsset: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url: originalUrl,
+          payload: examplePayload,
+        },
+      };
+      const newAsset: assetEvent = {
+        type: EventType.Asset,
+        data: {
+          url: newUrl,
+          payload: examplePayload,
+        },
+      };
+      let i = 0;
+      jest
+        .spyOn(URL, 'createObjectURL')
+        .mockImplementation(() => `objectURL${(i += 1)}`);
+      const promises: Promise<unknown>[] = [];
+
+      const element = document.createElement('img');
+      element.setAttribute('src', originalUrl);
+      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+
+      element.setAttribute('src', newUrl);
+      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+
+      await assetManager.add(originalAsset);
+      await assetManager.add(newAsset);
+
+      await Promise.all(promises);
+      expect(element.getAttribute('src')).toBe('objectURL2');
+    });
+  });
 });
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 598d37e455..c5e774f50d 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -761,14 +761,17 @@ export type RebuildAssetManagerStatus =
   | RebuildAssetManagerFinalStatus;
 
 export declare abstract class RebuildAssetManagerInterface {
-  constructor(config?: captureAssetsParam | undefined);
+  constructor(
+    playerConfig: { liveMode: boolean },
+    assetManagerConfig?: captureAssetsParam | undefined,
+  );
   abstract add(event: assetEvent): Promise<void>;
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
   abstract reset(config?: captureAssetsParam | undefined): void;
-  abstract isAttributeCacheable(n: Element, attribute: string): boolean;
+  abstract isCacheable(n: Element, attribute: string, value: string): boolean;
   abstract isURLOfCacheableOrigin(url: string): boolean;
-  abstract manageAttribute(n: Element, attribute: string): void;
+  abstract manageAttribute(n: Element, id: number, attribute: string): void;
 }
 
 export enum NodeType {
diff --git a/yarn.lock b/yarn.lock
index f830c341c5..792d72ee81 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3229,6 +3229,11 @@
   resolved "https://registry.yarnpkg.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz#db4ecfd499a9765ab24002c3b696d02e6d32a12c"
   integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==
 
+"@total-typescript/shoehorn@^0.1.0":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@total-typescript/shoehorn/-/shoehorn-0.1.1.tgz#72d3ba9364faa4f6b8e66c57b7a9094457e3652b"
+  integrity sha512-XSPcazQsC2Cr7eCiAI+M2bTmMziBvFWYTYMgUDKLbU6i+7m3I2BF5gXF5vKDO8577fONs9CvmTvVa7+nMHMfxg==
+
 "@ts-morph/common@~0.18.0":
   version "0.18.1"
   resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.18.1.tgz#ca40c3a62c3f9e17142e0af42633ad63efbae0ec"

From 9c8a2fc84519ea116c1ea84d1f10efd90a611571 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:26:25 +0100
Subject: [PATCH 057/102] Change asset manager path

---
 packages/rrweb/src/replay/{assets => asset-manager}/index.ts | 0
 packages/rrweb/src/replay/index.ts                           | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename packages/rrweb/src/replay/{assets => asset-manager}/index.ts (100%)

diff --git a/packages/rrweb/src/replay/assets/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
similarity index 100%
rename from packages/rrweb/src/replay/assets/index.ts
rename to packages/rrweb/src/replay/asset-manager/index.ts
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 28b49c6367..fc62a8d88e 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -81,7 +81,7 @@ import './styles/style.css';
 import canvasMutation from './canvas';
 import { deserializeArg } from './canvas/deserialize-args';
 import { MediaManager } from './media';
-import AssetManager from './assets';
+import AssetManager from './asset-manager';
 
 const SKIP_TIME_INTERVAL = 5 * 1000;
 

From 4a937aa6eb9e3be7e4636a759e7277ec975098df Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:36:02 +0100
Subject: [PATCH 058/102] Fix import path for AssetManager in
 asset-unit.test.ts

---
 packages/rrweb/test/replay/asset-unit.test.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index a6ca2a5ff7..698a442181 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -2,7 +2,7 @@
  * @jest-environment jsdom
  */
 
-import AssetManager from '../../src/replay/assets';
+import AssetManager from '../../src/replay/asset-manager';
 import {
   EventType,
   SerializedBlobArg,

From a2d91b80d5a84bcc68859fabc647fd156393c68e Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:56:38 +0100
Subject: [PATCH 059/102] Move isAttributeCacheable tests to rrweb-snapshot

---
 packages/rrweb-snapshot/test/utils.test.ts    | 48 ++++++++++++++++++-
 packages/rrweb/test/replay/asset-unit.test.ts | 48 -------------------
 2 files changed, 47 insertions(+), 49 deletions(-)

diff --git a/packages/rrweb-snapshot/test/utils.test.ts b/packages/rrweb-snapshot/test/utils.test.ts
index 051853fea0..d1b74d50d5 100644
--- a/packages/rrweb-snapshot/test/utils.test.ts
+++ b/packages/rrweb-snapshot/test/utils.test.ts
@@ -1,7 +1,7 @@
 /**
  * @jest-environment jsdom
  */
-import { extractFileExtension, isNodeMetaEqual } from '../src/utils';
+import { extractFileExtension, isAttributeCacheable, isNodeMetaEqual } from '../src/utils';
 import { NodeType, serializedNode, serializedNodeWithId } from '@rrweb/types';
 
 describe('utils', () => {
@@ -197,4 +197,50 @@ describe('utils', () => {
       expect(extension).toBe('js');
     });
   });
+
+  describe('isAttributeCacheable()', () => {
+    const validAttributeCombinations = [
+      ['img', ['src', 'srcset']],
+      ['video', ['src']],
+      ['audio', ['src']],
+      ['embed', ['src']],
+      ['source', ['src']],
+      ['track', ['src']],
+      ['input', ['src']],
+      ['iframe', ['src']],
+      ['object', ['src']],
+    ] as const;
+
+    const invalidAttributeCombinations = [
+      ['img', ['href']],
+      ['script', ['href']],
+      ['link', ['src']],
+      ['video', ['href']],
+      ['audio', ['href']],
+      ['div', ['src']],
+      ['source', ['href']],
+      ['track', ['href']],
+      ['input', ['href']],
+      ['iframe', ['href']],
+      ['object', ['href']],
+    ] as const;
+
+    validAttributeCombinations.forEach(([tagName, attributes]) => {
+      const element = document.createElement(tagName);
+      attributes.forEach((attribute) => {
+        it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
+          expect(isAttributeCacheable(element, attribute)).toBe(true);
+        });
+      });
+    });
+
+    invalidAttributeCombinations.forEach(([tagName, attributes]) => {
+      const element = document.createElement(tagName);
+      attributes.forEach((attribute) => {
+        it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
+          expect(isAttributeCacheable(element, attribute)).toBe(false);
+        });
+      });
+    });
+  });
 });
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 698a442181..01beaaa388 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -152,54 +152,6 @@ describe('AssetManager', () => {
     await expect(promise).resolves.toEqual({ status: 'reset' });
   });
 
-  const validAttributeCombinations = [
-    ['img', ['src', 'srcset']],
-    ['video', ['src']],
-    ['audio', ['src']],
-    ['embed', ['src']],
-    ['source', ['src']],
-    ['track', ['src']],
-    ['input', ['src']],
-    ['iframe', ['src']],
-    ['object', ['src']],
-  ] as const;
-
-  const invalidAttributeCombinations = [
-    ['img', ['href']],
-    ['script', ['href']],
-    ['link', ['src']],
-    ['video', ['href']],
-    ['audio', ['href']],
-    ['div', ['src']],
-    ['source', ['href']],
-    ['track', ['href']],
-    ['input', ['href']],
-    ['iframe', ['href']],
-    ['object', ['href']],
-  ] as const;
-
-  validAttributeCombinations.forEach(([tagName, attributes]) => {
-    const element = document.createElement(tagName);
-    attributes.forEach((attribute) => {
-      it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
-        expect(assetManager['isAttributeCacheable'](element, attribute)).toBe(
-          true,
-        );
-      });
-    });
-  });
-
-  invalidAttributeCombinations.forEach(([tagName, attributes]) => {
-    const element = document.createElement(tagName);
-    attributes.forEach((attribute) => {
-      it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
-        expect(assetManager['isAttributeCacheable'](element, attribute)).toBe(
-          false,
-        );
-      });
-    });
-  });
-
   const validOriginCombinations: Array<
     [captureAssetsParam['origins'], string[]]
   > = [

From d81d6d3fb86749898ee0a536b645d62615a0bd91 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 13:57:17 +0100
Subject: [PATCH 060/102] Make assetManager optional

---
 packages/rrdom/src/diff.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index 7568946ac3..c1c241fdaa 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -76,7 +76,7 @@ const SVGTagMap: Record<string, string> = {
 
 export type ReplayerHandler = {
   mirror: NodeMirror;
-  assetManager: RebuildAssetManagerInterface;
+  assetManager?: RebuildAssetManagerInterface;
   applyCanvas: (
     canvasEvent: canvasEventWithTime,
     canvasMutationData: canvasMutationData,
@@ -333,7 +333,7 @@ function diffProps(
     } else if (newTree.tagName === 'IFRAME' && name === 'srcdoc') continue;
     else oldTree.setAttribute(name, newValue);
 
-    if (assetManager && assetManager.isCacheable(oldTree, name, newValue)) {
+    if (assetManager?.isCacheable(oldTree, name, newValue)) {
       // can possibly remove the attribute again if it hasn't loaded yet
       assetManager.manageAttribute(oldTree, rrnodeMirror.getId(newTree), name);
     }

From a4d7bdba00c0c3271ba469ef851f24a08d9164ed Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 15:03:42 +0100
Subject: [PATCH 061/102] upgrade rrdom-nodejs to jest 29

---
 packages/rrdom-nodejs/package.json | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/packages/rrdom-nodejs/package.json b/packages/rrdom-nodejs/package.json
index 51a82993df..62b9435d70 100644
--- a/packages/rrdom-nodejs/package.json
+++ b/packages/rrdom-nodejs/package.json
@@ -29,20 +29,21 @@
     "@rollup/plugin-node-resolve": "^13.0.4",
     "@types/cssom": "^0.4.1",
     "@types/cssstyle": "^2.2.1",
-    "@types/jest": "^27.4.1",
+    "@types/jest": "^29.5.11",
     "@types/nwsapi": "^2.2.2",
     "@types/puppeteer": "^5.4.4",
     "@typescript-eslint/eslint-plugin": "^5.23.0",
     "@typescript-eslint/parser": "^5.23.0",
     "compare-versions": "^4.1.3",
     "eslint": "^8.15.0",
-    "jest": "^27.5.1",
-    "puppeteer": "^9.1.1",
+    "jest": "^29.7.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "puppeteer": "^21.6.1",
     "rollup": "^2.56.3",
     "rollup-plugin-terser": "^7.0.2",
-    "rollup-plugin-typescript2": "^0.31.2",
+    "rollup-plugin-typescript2": "^0.36.0",
     "rollup-plugin-web-worker-loader": "^1.6.1",
-    "ts-jest": "^27.1.3"
+    "ts-jest": "^29.1.1"
   },
   "dependencies": {
     "cssom": "^0.5.0",

From 99a22ce167b9d33775a9fdd227c0afe99ea82cc2 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 15:09:28 +0100
Subject: [PATCH 062/102] Update Jest and ts-jest versions to 29 in rrvideo

---
 packages/rrvideo/package.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/rrvideo/package.json b/packages/rrvideo/package.json
index 4cbd92463d..fcc2288f0a 100644
--- a/packages/rrvideo/package.json
+++ b/packages/rrvideo/package.json
@@ -21,11 +21,11 @@
   "license": "MIT",
   "devDependencies": {
     "@types/fs-extra": "11.0.1",
-    "@types/jest": "^27.4.1",
+    "@types/jest": "^29.5.11",
     "@types/minimist": "^1.2.1",
     "@types/node": "^18.15.11",
-    "jest": "^27.5.1",
-    "ts-jest": "^27.1.3",
+    "jest": "^29.7.0",
+    "ts-jest": "^29.1.1",
     "@rrweb/types": "^2.0.0-alpha.13"
   },
   "dependencies": {

From b729dc9c2b739fcbc4ce59bb71c4d1606f2ced06 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 15:10:59 +0100
Subject: [PATCH 063/102] add @types/prettier dependency

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 8240ee85e0..4115e37ff8 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
     "eslint-plugin-tsdoc": "^0.2.17",
     "markdownlint": "^0.25.1",
     "markdownlint-cli": "^0.31.1",
+    "@types/prettier": "2.7.3",
     "prettier": "2.8.4",
     "turbo": "^1.2.4",
     "typescript": "^4.9.5"

From 8c69bd79f5aa30934e0f6606ac375f7046d1f2f9 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 15:16:43 +0100
Subject: [PATCH 064/102] Update tsconfig.json files with Node module
 resolution

---
 packages/rrdom-nodejs/tsconfig.json | 1 +
 packages/rrdom/tsconfig.json        | 1 +
 2 files changed, 2 insertions(+)

diff --git a/packages/rrdom-nodejs/tsconfig.json b/packages/rrdom-nodejs/tsconfig.json
index b59df3f7ef..223d62a020 100644
--- a/packages/rrdom-nodejs/tsconfig.json
+++ b/packages/rrdom-nodejs/tsconfig.json
@@ -3,6 +3,7 @@
     "composite": true,
     "target": "ES6",
     "module": "commonjs",
+    "moduleResolution": "Node",
     "noImplicitAny": true,
     "strictNullChecks": true,
     "removeComments": true,
diff --git a/packages/rrdom/tsconfig.json b/packages/rrdom/tsconfig.json
index 450e56e151..867cfe4497 100644
--- a/packages/rrdom/tsconfig.json
+++ b/packages/rrdom/tsconfig.json
@@ -3,6 +3,7 @@
     "composite": true,
     "target": "ES6",
     "module": "commonjs",
+    "moduleResolution": "Node",
     "noImplicitAny": true,
     "strictNullChecks": true,
     "removeComments": true,

From 535a1c2b375327ed8c81e21cae605050b51b5481 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 15:40:19 +0100
Subject: [PATCH 065/102] upgrade dependencies

---
 yarn.lock | 182 +++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 138 insertions(+), 44 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 792d72ee81..adeb976f56 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3016,6 +3016,19 @@
     unbzip2-stream "1.4.3"
     yargs "17.7.1"
 
+"@puppeteer/browsers@1.9.0":
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.9.0.tgz#dfd0aad0bdc039572f1b57648f189525d627b7ff"
+  integrity sha512-QwguOLy44YBGC8vuPP2nmpX4MUN2FzWbsnvZJtiCzecU3lHmVZkaC1tq6rToi9a200m8RzlVtDyxCS0UIDrxUg==
+  dependencies:
+    debug "4.3.4"
+    extract-zip "2.0.1"
+    progress "2.0.3"
+    proxy-agent "6.3.1"
+    tar-fs "3.0.4"
+    unbzip2-stream "1.4.3"
+    yargs "17.7.2"
+
 "@remix-run/router@1.0.2":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.2.tgz#1c17eadb2fa77f80a796ad5ea9bf108e6993ef06"
@@ -3444,6 +3457,14 @@
     expect "^29.0.0"
     pretty-format "^29.0.0"
 
+"@types/jest@^29.5.11":
+  version "29.5.11"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c"
+  integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==
+  dependencies:
+    expect "^29.0.0"
+    pretty-format "^29.0.0"
+
 "@types/jsdom@^20.0.0":
   version "20.0.0"
   resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.0.tgz#4414fb629465167f8b7b3804b9e067bdd99f1791"
@@ -3538,6 +3559,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/prettier@2.7.3":
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
+  integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
+
 "@types/prettier@^2.1.5":
   version "2.4.1"
   resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz"
@@ -4988,6 +5014,14 @@ chromium-bidi@0.4.16:
   dependencies:
     mitt "3.0.0"
 
+chromium-bidi@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.5.1.tgz#390c1af350c4887824a33d82190de1cc5c5680fc"
+  integrity sha512-dcCqOgq9fHKExc2R4JZs/oKbOghWpUNFAJODS8WKRtLhp3avtIH5UDCBrutdqZdh3pARogH8y1ObXm87emwb3g==
+  dependencies:
+    mitt "3.0.1"
+    urlpattern-polyfill "9.0.0"
+
 ci-info@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
@@ -5344,6 +5378,16 @@ cosmiconfig@8.2.0:
     parse-json "^5.0.0"
     path-type "^4.0.0"
 
+cosmiconfig@8.3.6:
+  version "8.3.6"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
+  integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
+  dependencies:
+    import-fresh "^3.3.0"
+    js-yaml "^4.1.0"
+    parse-json "^5.2.0"
+    path-type "^4.0.0"
+
 cosmiconfig@^5.0.0:
   version "5.2.1"
   resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz"
@@ -5904,10 +5948,10 @@ devtools-protocol@0.0.1147663:
   resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz#4ec5610b39a6250d1f87e6b9c7e16688ed0ac78e"
   integrity sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==
 
-devtools-protocol@0.0.869402:
-  version "0.0.869402"
-  resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz"
-  integrity sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==
+devtools-protocol@0.0.1203626:
+  version "0.0.1203626"
+  resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz#4366a4c81a7e0d4fd6924e9182c67f1e5941e820"
+  integrity sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==
 
 diff-sequences@^27.0.6:
   version "27.0.6"
@@ -7083,7 +7127,7 @@ extglob@^0.3.1:
   dependencies:
     is-extglob "^1.0.0"
 
-extract-zip@2.0.1, extract-zip@^2.0.0:
+extract-zip@2.0.1:
   version "2.0.1"
   resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz"
   integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
@@ -8257,7 +8301,7 @@ import-cwd@^3.0.0:
   dependencies:
     import-from "^3.0.0"
 
-import-fresh@3.3.0, import-fresh@^3.0.0, import-fresh@^3.2.1:
+import-fresh@3.3.0, import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
   version "3.3.0"
   resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
   integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -11071,6 +11115,11 @@ mitt@3.0.0, mitt@^3.0.0:
   resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
   integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
 
+mitt@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
+  integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
+
 mixme@^0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.5.tgz#bf8f67d8caf10fdb49fd23198fd1fa6d8e406627"
@@ -11202,7 +11251,7 @@ nice-try@^1.0.4:
   resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
-node-fetch@2.6.7, node-fetch@^2.6.1:
+node-fetch@2.6.7:
   version "2.6.7"
   resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
   integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
@@ -11634,6 +11683,20 @@ pac-proxy-agent@^7.0.0:
     pac-resolver "^7.0.0"
     socks-proxy-agent "^8.0.1"
 
+pac-proxy-agent@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75"
+  integrity sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==
+  dependencies:
+    "@tootallnate/quickjs-emscripten" "^0.23.0"
+    agent-base "^7.0.2"
+    debug "^4.3.4"
+    get-uri "^6.0.1"
+    http-proxy-agent "^7.0.0"
+    https-proxy-agent "^7.0.2"
+    pac-resolver "^7.0.0"
+    socks-proxy-agent "^8.0.2"
+
 pac-resolver@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.0.tgz#79376f1ca26baf245b96b34c339d79bff25e900c"
@@ -12409,7 +12472,7 @@ process-warning@^1.0.0:
   resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
   integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==
 
-progress@2.0.3, progress@^2.0.1:
+progress@2.0.3:
   version "2.0.3"
   resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
   integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
@@ -12465,6 +12528,20 @@ proxy-agent@6.3.0:
     proxy-from-env "^1.1.0"
     socks-proxy-agent "^8.0.1"
 
+proxy-agent@6.3.1:
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.1.tgz#40e7b230552cf44fd23ffaf7c59024b692612687"
+  integrity sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==
+  dependencies:
+    agent-base "^7.0.2"
+    debug "^4.3.4"
+    http-proxy-agent "^7.0.0"
+    https-proxy-agent "^7.0.2"
+    lru-cache "^7.14.1"
+    pac-proxy-agent "^7.0.1"
+    proxy-from-env "^1.1.0"
+    socks-proxy-agent "^8.0.2"
+
 proxy-from-env@1.1.0, proxy-from-env@^1.1.0:
   version "1.1.0"
   resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -12517,6 +12594,18 @@ puppeteer-core@20.9.0:
     devtools-protocol "0.0.1147663"
     ws "8.13.0"
 
+puppeteer-core@21.6.1:
+  version "21.6.1"
+  resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.6.1.tgz#10eccb4dc3167c8c26bc21122fabb45a9fda9ca7"
+  integrity sha512-0chaaK/RL9S1U3bsyR4fUeUfoj51vNnjWvXgG6DcsyMjwYNpLcAThv187i1rZCo7QhJP0wZN8plQkjNyrq2h+A==
+  dependencies:
+    "@puppeteer/browsers" "1.9.0"
+    chromium-bidi "0.5.1"
+    cross-fetch "4.0.0"
+    debug "4.3.4"
+    devtools-protocol "0.0.1203626"
+    ws "8.15.1"
+
 puppeteer@^17.1.3:
   version "17.1.3"
   resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-17.1.3.tgz#2814cf221925e19c681c69aa97401a68b30240c9"
@@ -12543,23 +12632,14 @@ puppeteer@^20.9.0:
     cosmiconfig "8.2.0"
     puppeteer-core "20.9.0"
 
-puppeteer@^9.1.1:
-  version "9.1.1"
-  resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz"
-  integrity sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==
+puppeteer@^21.6.1:
+  version "21.6.1"
+  resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-21.6.1.tgz#2ec0878906ff90b3a424f19e5eb006592abe25b6"
+  integrity sha512-O+pbc61oj8ln6m8EJKncrsQFmytgRyFYERtk190PeLbJn5JKpmmynn2p1PiFrlhCitAQXLJ0MOy7F0TeyCRqBg==
   dependencies:
-    debug "^4.1.0"
-    devtools-protocol "0.0.869402"
-    extract-zip "^2.0.0"
-    https-proxy-agent "^5.0.0"
-    node-fetch "^2.6.1"
-    pkg-dir "^4.2.0"
-    progress "^2.0.1"
-    proxy-from-env "^1.1.0"
-    rimraf "^3.0.2"
-    tar-fs "^2.0.0"
-    unbzip2-stream "^1.3.3"
-    ws "^7.2.3"
+    "@puppeteer/browsers" "1.9.0"
+    cosmiconfig "8.3.6"
+    puppeteer-core "21.6.1"
 
 pure-rand@^6.0.0:
   version "6.0.2"
@@ -13615,6 +13695,15 @@ socks-proxy-agent@^8.0.1:
     debug "^4.3.4"
     socks "^2.7.1"
 
+socks-proxy-agent@^8.0.2:
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz#5acbd7be7baf18c46a3f293a840109a430a640ad"
+  integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==
+  dependencies:
+    agent-base "^7.0.2"
+    debug "^4.3.4"
+    socks "^2.7.1"
+
 socks@^2.7.1:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55"
@@ -14128,7 +14217,7 @@ symbol-tree@^3.2.4:
   resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
-tar-fs@2.1.1, tar-fs@^2.0.0:
+tar-fs@2.1.1:
   version "2.1.1"
   resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz"
   integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
@@ -14719,7 +14808,7 @@ unbox-primitive@^1.0.2:
     has-symbols "^1.0.3"
     which-boxed-primitive "^1.0.2"
 
-unbzip2-stream@1.4.3, unbzip2-stream@^1.3.3:
+unbzip2-stream@1.4.3:
   version "1.4.3"
   resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz"
   integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
@@ -14854,6 +14943,11 @@ url-parse@^1.5.3:
     querystringify "^2.1.1"
     requires-port "^1.0.0"
 
+urlpattern-polyfill@9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz#bc7e386bb12fd7898b58d1509df21d3c29ab3460"
+  integrity sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==
+
 use-callback-ref@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5"
@@ -15351,16 +15445,16 @@ ws@8.13.0, ws@^8.11.0:
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
   integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
 
+ws@8.15.1:
+  version "8.15.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.15.1.tgz#271ba33a45ca0cc477940f7f200cd7fba7ee1997"
+  integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==
+
 ws@8.8.1:
   version "8.8.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
   integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==
 
-ws@^7.2.3:
-  version "7.5.7"
-  resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz"
-  integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
-
 ws@^7.4.3:
   version "7.5.3"
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz"
@@ -15501,6 +15595,19 @@ yargs@17.7.1:
     y18n "^5.0.5"
     yargs-parser "^21.1.1"
 
+yargs@17.7.2, yargs@^17.3.1:
+  version "17.7.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+  integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+  dependencies:
+    cliui "^8.0.1"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.3"
+    y18n "^5.0.5"
+    yargs-parser "^21.1.1"
+
 yargs@^15.1.0:
   version "15.4.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
@@ -15531,19 +15638,6 @@ yargs@^17.1.1:
     y18n "^5.0.5"
     yargs-parser "^21.1.1"
 
-yargs@^17.3.1:
-  version "17.7.2"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
-  integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
-  dependencies:
-    cliui "^8.0.1"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.3"
-    y18n "^5.0.5"
-    yargs-parser "^21.1.1"
-
 yauzl@2.10.0, yauzl@^2.10.0:
   version "2.10.0"
   resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"

From 3fe1e80a08009bfd30a508d8016d21ac47ff52c2 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 16:05:04 +0100
Subject: [PATCH 066/102] Update jest to 29 and rollup to 4

---
 packages/rrdom/jest.config.js   |    2 +-
 packages/rrdom/package.json     |   13 +-
 packages/rrdom/rollup.config.js |    2 +-
 packages/rrdom/test/utils.ts    |    5 +-
 packages/rrweb/jest.config.js   |    2 +-
 yarn.lock                       | 1367 +++++--------------------------
 6 files changed, 199 insertions(+), 1192 deletions(-)

diff --git a/packages/rrdom/jest.config.js b/packages/rrdom/jest.config.js
index 3483edbdc2..7893d1b3b9 100644
--- a/packages/rrdom/jest.config.js
+++ b/packages/rrdom/jest.config.js
@@ -1,4 +1,4 @@
-/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
 export default {
   preset: 'ts-jest',
   testEnvironment: 'node',
diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json
index b784907fec..f3133878d4 100644
--- a/packages/rrdom/package.json
+++ b/packages/rrdom/package.json
@@ -31,21 +31,22 @@
     "url": "https://github.com/rrweb-io/rrweb/issues"
   },
   "devDependencies": {
-    "@rollup/plugin-commonjs": "^20.0.0",
+    "@rollup/plugin-commonjs": "^25.0.7",
     "@rrweb/types": "^2.0.0-alpha.13",
-    "@types/jest": "^27.4.1",
+    "@types/jest": "^29.5.11",
     "@types/puppeteer": "^5.4.4",
     "@typescript-eslint/eslint-plugin": "^5.23.0",
     "@typescript-eslint/parser": "^5.23.0",
     "@total-typescript/shoehorn": "^0.1.0",
     "eslint": "^8.15.0",
-    "jest": "^27.5.1",
+    "jest": "^29.7.0",
+    "jest-environment-jsdom": "^29.7.0",
     "puppeteer": "^17.1.3",
-    "rollup": "^2.56.3",
+    "rollup": "^4.9.0",
     "rollup-plugin-terser": "^7.0.2",
-    "rollup-plugin-typescript2": "^0.31.2",
+    "rollup-plugin-typescript2": "^0.36.0",
     "rollup-plugin-web-worker-loader": "^1.6.1",
-    "ts-jest": "^27.1.3"
+    "ts-jest": "^29.1.1"
   },
   "dependencies": {
     "rrweb-snapshot": "^2.0.0-alpha.13"
diff --git a/packages/rrdom/rollup.config.js b/packages/rrdom/rollup.config.js
index dc9a7993dc..4ab4fd9f18 100644
--- a/packages/rrdom/rollup.config.js
+++ b/packages/rrdom/rollup.config.js
@@ -3,7 +3,7 @@ import commonjs from '@rollup/plugin-commonjs';
 import { terser } from 'rollup-plugin-terser';
 import typescript from 'rollup-plugin-typescript2';
 import webWorkerLoader from 'rollup-plugin-web-worker-loader';
-import pkg from './package.json';
+import pkg from './package.json' assert { type: 'json' };
 
 function toMinPath(path) {
   return path.replace(/\.js$/, '.min.js');
diff --git a/packages/rrdom/test/utils.ts b/packages/rrdom/test/utils.ts
index 15d22360a6..852990f576 100644
--- a/packages/rrdom/test/utils.ts
+++ b/packages/rrdom/test/utils.ts
@@ -1,7 +1,6 @@
 import * as rollup from 'rollup';
-import * as typescript from 'rollup-plugin-typescript2';
+import typescript from 'rollup-plugin-typescript2';
 import resolve from '@rollup/plugin-node-resolve';
-const _typescript = typescript as unknown as typeof typescript.default;
 
 /**
  * Use rollup to compile an input TS script into JS code string.
@@ -11,7 +10,7 @@ export async function compileTSCode(inputFilePath: string) {
     input: inputFilePath,
     plugins: [
       resolve() as unknown as rollup.Plugin,
-      _typescript({
+      typescript({
         tsconfigOverride: { compilerOptions: { module: 'ESNext' } },
         clean: true,
         cacheRoot: `./node_modules/.cache/rrdom-test/${Date.now()}/`,
diff --git a/packages/rrweb/jest.config.js b/packages/rrweb/jest.config.js
index 1d88a78e2b..34742a5fca 100644
--- a/packages/rrweb/jest.config.js
+++ b/packages/rrweb/jest.config.js
@@ -1,4 +1,4 @@
-/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
 export default {
   preset: 'ts-jest',
   testEnvironment: 'node',
diff --git a/yarn.lock b/yarn.lock
index adeb976f56..7f483b6611 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -51,11 +51,6 @@
   dependencies:
     "@babel/highlight" "^7.22.5"
 
-"@babel/compat-data@^7.15.0":
-  version "7.15.0"
-  resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz"
-  integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==
-
 "@babel/compat-data@^7.16.4":
   version "7.16.4"
   resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz"
@@ -71,27 +66,6 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730"
   integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
 
-"@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
-  version "7.15.5"
-  resolved "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz"
-  integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==
-  dependencies:
-    "@babel/code-frame" "^7.14.5"
-    "@babel/generator" "^7.15.4"
-    "@babel/helper-compilation-targets" "^7.15.4"
-    "@babel/helper-module-transforms" "^7.15.4"
-    "@babel/helpers" "^7.15.4"
-    "@babel/parser" "^7.15.5"
-    "@babel/template" "^7.15.4"
-    "@babel/traverse" "^7.15.4"
-    "@babel/types" "^7.15.4"
-    convert-source-map "^1.7.0"
-    debug "^4.1.0"
-    gensync "^1.0.0-beta.2"
-    json5 "^2.1.2"
-    semver "^6.3.0"
-    source-map "^0.5.0"
-
 "@babel/core@^7.11.6":
   version "7.22.9"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f"
@@ -155,15 +129,6 @@
     json5 "^2.2.1"
     semver "^6.3.0"
 
-"@babel/generator@^7.15.4", "@babel/generator@^7.7.2":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz"
-  integrity sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==
-  dependencies:
-    "@babel/types" "^7.15.4"
-    jsesc "^2.5.1"
-    source-map "^0.5.0"
-
 "@babel/generator@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz"
@@ -182,12 +147,12 @@
     "@jridgewell/gen-mapping" "^0.3.2"
     jsesc "^2.5.1"
 
-"@babel/generator@^7.22.7", "@babel/generator@^7.22.9":
-  version "7.22.9"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d"
-  integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==
+"@babel/generator@^7.22.7", "@babel/generator@^7.22.9", "@babel/generator@^7.7.2":
+  version "7.23.6"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e"
+  integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==
   dependencies:
-    "@babel/types" "^7.22.5"
+    "@babel/types" "^7.23.6"
     "@jridgewell/gen-mapping" "^0.3.2"
     "@jridgewell/trace-mapping" "^0.3.17"
     jsesc "^2.5.1"
@@ -199,16 +164,6 @@
   dependencies:
     "@babel/types" "^7.18.6"
 
-"@babel/helper-compilation-targets@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz"
-  integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==
-  dependencies:
-    "@babel/compat-data" "^7.15.0"
-    "@babel/helper-validator-option" "^7.14.5"
-    browserslist "^4.16.6"
-    semver "^6.3.0"
-
 "@babel/helper-compilation-targets@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz"
@@ -257,15 +212,6 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
   integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
 
-"@babel/helper-function-name@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz"
-  integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==
-  dependencies:
-    "@babel/helper-get-function-arity" "^7.15.4"
-    "@babel/template" "^7.15.4"
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-function-name@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz"
@@ -291,13 +237,6 @@
     "@babel/template" "^7.22.5"
     "@babel/types" "^7.22.5"
 
-"@babel/helper-get-function-arity@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz"
-  integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-get-function-arity@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz"
@@ -305,13 +244,6 @@
   dependencies:
     "@babel/types" "^7.16.7"
 
-"@babel/helper-hoist-variables@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz"
-  integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-hoist-variables@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz"
@@ -333,20 +265,6 @@
   dependencies:
     "@babel/types" "^7.22.5"
 
-"@babel/helper-member-expression-to-functions@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz"
-  integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
-"@babel/helper-module-imports@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz"
-  integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-module-imports@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz"
@@ -368,20 +286,6 @@
   dependencies:
     "@babel/types" "^7.22.5"
 
-"@babel/helper-module-transforms@^7.15.4":
-  version "7.15.7"
-  resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz"
-  integrity sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==
-  dependencies:
-    "@babel/helper-module-imports" "^7.15.4"
-    "@babel/helper-replace-supers" "^7.15.4"
-    "@babel/helper-simple-access" "^7.15.4"
-    "@babel/helper-split-export-declaration" "^7.15.4"
-    "@babel/helper-validator-identifier" "^7.15.7"
-    "@babel/template" "^7.15.4"
-    "@babel/traverse" "^7.15.4"
-    "@babel/types" "^7.15.6"
-
 "@babel/helper-module-transforms@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz"
@@ -421,13 +325,6 @@
     "@babel/helper-split-export-declaration" "^7.22.6"
     "@babel/helper-validator-identifier" "^7.22.5"
 
-"@babel/helper-optimise-call-expression@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz"
-  integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0":
   version "7.14.5"
   resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz"
@@ -443,23 +340,6 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
   integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
 
-"@babel/helper-replace-supers@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz"
-  integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==
-  dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.15.4"
-    "@babel/helper-optimise-call-expression" "^7.15.4"
-    "@babel/traverse" "^7.15.4"
-    "@babel/types" "^7.15.4"
-
-"@babel/helper-simple-access@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz"
-  integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-simple-access@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz"
@@ -481,13 +361,6 @@
   dependencies:
     "@babel/types" "^7.22.5"
 
-"@babel/helper-split-export-declaration@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz"
-  integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==
-  dependencies:
-    "@babel/types" "^7.15.4"
-
 "@babel/helper-split-export-declaration@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz"
@@ -519,12 +392,17 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
   integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
 
+"@babel/helper-string-parser@^7.23.4":
+  version "7.23.4"
+  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
+  integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
+
 "@babel/helper-validator-identifier@^7.14.5":
   version "7.14.8"
   resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz"
   integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==
 
-"@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7":
+"@babel/helper-validator-identifier@^7.14.9":
   version "7.15.7"
   resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz"
   integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
@@ -539,16 +417,16 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
   integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
 
+"@babel/helper-validator-identifier@^7.22.20":
+  version "7.22.20"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+  integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
 "@babel/helper-validator-identifier@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
   integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
 
-"@babel/helper-validator-option@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz"
-  integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
-
 "@babel/helper-validator-option@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz"
@@ -564,15 +442,6 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac"
   integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==
 
-"@babel/helpers@^7.15.4":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz"
-  integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==
-  dependencies:
-    "@babel/template" "^7.15.4"
-    "@babel/traverse" "^7.15.4"
-    "@babel/types" "^7.15.4"
-
 "@babel/helpers@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz"
@@ -636,7 +505,7 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5":
+"@babel/parser@^7.1.0", "@babel/parser@^7.15.4":
   version "7.15.7"
   resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz"
   integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==
@@ -814,15 +683,6 @@
   dependencies:
     regenerator-runtime "^0.13.11"
 
-"@babel/template@^7.15.4", "@babel/template@^7.3.3":
-  version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz"
-  integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==
-  dependencies:
-    "@babel/code-frame" "^7.14.5"
-    "@babel/parser" "^7.15.4"
-    "@babel/types" "^7.15.4"
-
 "@babel/template@^7.16.7":
   version "7.16.7"
   resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz"
@@ -850,20 +710,14 @@
     "@babel/parser" "^7.22.5"
     "@babel/types" "^7.22.5"
 
-"@babel/traverse@^7.15.4", "@babel/traverse@^7.7.2":
+"@babel/template@^7.3.3":
   version "7.15.4"
-  resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz"
-  integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==
+  resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz"
+  integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==
   dependencies:
     "@babel/code-frame" "^7.14.5"
-    "@babel/generator" "^7.15.4"
-    "@babel/helper-function-name" "^7.15.4"
-    "@babel/helper-hoist-variables" "^7.15.4"
-    "@babel/helper-split-export-declaration" "^7.15.4"
     "@babel/parser" "^7.15.4"
     "@babel/types" "^7.15.4"
-    debug "^4.1.0"
-    globals "^11.1.0"
 
 "@babel/traverse@^7.16.7":
   version "7.16.7"
@@ -913,7 +767,7 @@
     debug "^4.1.0"
     globals "^11.1.0"
 
-"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.15.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
+"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
   version "7.15.6"
   resolved "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz"
   integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==
@@ -947,6 +801,15 @@
     "@babel/helper-validator-identifier" "^7.22.5"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.23.6":
+  version "7.23.6"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd"
+  integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==
+  dependencies:
+    "@babel/helper-string-parser" "^7.23.4"
+    "@babel/helper-validator-identifier" "^7.22.20"
+    to-fast-properties "^2.0.0"
+
 "@bcoe/v8-coverage@^0.2.3":
   version "0.2.3"
   resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz"
@@ -2174,18 +2037,6 @@
   resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz"
   integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
 
-"@jest/console@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz"
-  integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    jest-message-util "^27.5.1"
-    jest-util "^27.5.1"
-    slash "^3.0.0"
-
 "@jest/console@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.2.tgz#bf1d4101347c23e07c029a1b1ae07d550f5cc541"
@@ -2210,40 +2061,6 @@
     jest-util "^29.7.0"
     slash "^3.0.0"
 
-"@jest/core@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz"
-  integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==
-  dependencies:
-    "@jest/console" "^27.5.1"
-    "@jest/reporters" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    ansi-escapes "^4.2.1"
-    chalk "^4.0.0"
-    emittery "^0.8.1"
-    exit "^0.1.2"
-    graceful-fs "^4.2.9"
-    jest-changed-files "^27.5.1"
-    jest-config "^27.5.1"
-    jest-haste-map "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-regex-util "^27.5.1"
-    jest-resolve "^27.5.1"
-    jest-resolve-dependencies "^27.5.1"
-    jest-runner "^27.5.1"
-    jest-runtime "^27.5.1"
-    jest-snapshot "^27.5.1"
-    jest-util "^27.5.1"
-    jest-validate "^27.5.1"
-    jest-watcher "^27.5.1"
-    micromatch "^4.0.4"
-    rimraf "^3.0.0"
-    slash "^3.0.0"
-    strip-ansi "^6.0.0"
-
 "@jest/core@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8"
@@ -2312,16 +2129,6 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz"
-  integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==
-  dependencies:
-    "@jest/fake-timers" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    jest-mock "^27.5.1"
-
 "@jest/environment@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9"
@@ -2372,18 +2179,6 @@
     expect "^29.7.0"
     jest-snapshot "^29.7.0"
 
-"@jest/fake-timers@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz"
-  integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    "@sinonjs/fake-timers" "^8.0.1"
-    "@types/node" "*"
-    jest-message-util "^27.5.1"
-    jest-mock "^27.5.1"
-    jest-util "^27.5.1"
-
 "@jest/fake-timers@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4"
@@ -2408,15 +2203,6 @@
     jest-mock "^29.7.0"
     jest-util "^29.7.0"
 
-"@jest/globals@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz"
-  integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    expect "^27.5.1"
-
 "@jest/globals@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a"
@@ -2437,37 +2223,6 @@
     "@jest/types" "^29.6.3"
     jest-mock "^29.7.0"
 
-"@jest/reporters@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz"
-  integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==
-  dependencies:
-    "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    collect-v8-coverage "^1.0.0"
-    exit "^0.1.2"
-    glob "^7.1.2"
-    graceful-fs "^4.2.9"
-    istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-instrument "^5.1.0"
-    istanbul-lib-report "^3.0.0"
-    istanbul-lib-source-maps "^4.0.0"
-    istanbul-reports "^3.1.3"
-    jest-haste-map "^27.5.1"
-    jest-resolve "^27.5.1"
-    jest-util "^27.5.1"
-    jest-worker "^27.5.1"
-    slash "^3.0.0"
-    source-map "^0.6.0"
-    string-length "^4.0.1"
-    terminal-link "^2.0.0"
-    v8-to-istanbul "^8.1.0"
-
 "@jest/reporters@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a"
@@ -2542,15 +2297,6 @@
   dependencies:
     "@sinclair/typebox" "^0.27.8"
 
-"@jest/source-map@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz"
-  integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==
-  dependencies:
-    callsites "^3.0.0"
-    graceful-fs "^4.2.9"
-    source-map "^0.6.0"
-
 "@jest/source-map@^29.6.0":
   version "29.6.0"
   resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1"
@@ -2569,16 +2315,6 @@
     callsites "^3.0.0"
     graceful-fs "^4.2.9"
 
-"@jest/test-result@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz"
-  integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==
-  dependencies:
-    "@jest/console" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    collect-v8-coverage "^1.0.0"
-
 "@jest/test-result@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed"
@@ -2599,16 +2335,6 @@
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz"
-  integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==
-  dependencies:
-    "@jest/test-result" "^27.5.1"
-    graceful-fs "^4.2.9"
-    jest-haste-map "^27.5.1"
-    jest-runtime "^27.5.1"
-
 "@jest/test-sequencer@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4"
@@ -2629,27 +2355,6 @@
     jest-haste-map "^29.7.0"
     slash "^3.0.0"
 
-"@jest/transform@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz"
-  integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==
-  dependencies:
-    "@babel/core" "^7.1.0"
-    "@jest/types" "^27.5.1"
-    babel-plugin-istanbul "^6.1.1"
-    chalk "^4.0.0"
-    convert-source-map "^1.4.0"
-    fast-json-stable-stringify "^2.0.0"
-    graceful-fs "^4.2.9"
-    jest-haste-map "^27.5.1"
-    jest-regex-util "^27.5.1"
-    jest-util "^27.5.1"
-    micromatch "^4.0.4"
-    pirates "^4.0.4"
-    slash "^3.0.0"
-    source-map "^0.6.1"
-    write-file-atomic "^3.0.0"
-
 "@jest/transform@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3"
@@ -2703,17 +2408,6 @@
     "@types/yargs" "^16.0.0"
     chalk "^4.0.0"
 
-"@jest/types@^27.5.1":
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz"
-  integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    "@types/istanbul-reports" "^3.0.0"
-    "@types/node" "*"
-    "@types/yargs" "^16.0.0"
-    chalk "^4.0.0"
-
 "@jest/types@^29.6.1":
   version "29.6.1"
   resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2"
@@ -2778,7 +2472,7 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
   integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
 
-"@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14":
+"@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
   version "1.4.15"
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
   integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
@@ -3060,6 +2754,18 @@
     magic-string "^0.25.7"
     resolve "^1.17.0"
 
+"@rollup/plugin-commonjs@^25.0.7":
+  version "25.0.7"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz#145cec7589ad952171aeb6a585bbeabd0fd3b4cf"
+  integrity sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==
+  dependencies:
+    "@rollup/pluginutils" "^5.0.1"
+    commondir "^1.0.1"
+    estree-walker "^2.0.2"
+    glob "^8.0.3"
+    is-reference "1.2.1"
+    magic-string "^0.30.3"
+
 "@rollup/plugin-node-resolve@^13.0.4", "@rollup/plugin-node-resolve@^13.2.1":
   version "13.2.1"
   resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.2.1.tgz"
@@ -3117,6 +2823,15 @@
     estree-walker "^2.0.1"
     picomatch "^2.2.2"
 
+"@rollup/pluginutils@^5.0.1":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0"
+  integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
+  dependencies:
+    "@types/estree" "^1.0.0"
+    estree-walker "^2.0.2"
+    picomatch "^2.3.1"
+
 "@rollup/pluginutils@^5.0.2":
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
@@ -3126,6 +2841,71 @@
     estree-walker "^2.0.2"
     picomatch "^2.3.1"
 
+"@rollup/rollup-android-arm-eabi@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz#0437b27edd7095d0b6d5db99d13af8157d7c58b0"
+  integrity sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==
+
+"@rollup/rollup-android-arm64@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz#d4c14ef9e45d5c46b8d1f611ab8124a611d5be5b"
+  integrity sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==
+
+"@rollup/rollup-darwin-arm64@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz#6f3fdf5712db6b5e3d8f62a86a09cd659dd871f9"
+  integrity sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==
+
+"@rollup/rollup-darwin-x64@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz#626d7786fe7c10b2e8533ad981b4a791fd72b9d0"
+  integrity sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz#57ece7bb1b7659a3ea2ace580a63b8f92b3161f1"
+  integrity sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==
+
+"@rollup/rollup-linux-arm64-gnu@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz#345b276b814a5377344adc5780c4dfb7cd0e8ba9"
+  integrity sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==
+
+"@rollup/rollup-linux-arm64-musl@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz#61cc6516e6e92e2205ea1d0ac30326379b0563c8"
+  integrity sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==
+
+"@rollup/rollup-linux-riscv64-gnu@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz#e9add70ddca7bd6f685ec447ae83eb3be552f211"
+  integrity sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==
+
+"@rollup/rollup-linux-x64-gnu@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz#ece153613f0cf2c864dbfc2076c579da8abd51a9"
+  integrity sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==
+
+"@rollup/rollup-linux-x64-musl@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz#2d2dbdf5fbf2c19d1f3d31b8a7850b57f5799037"
+  integrity sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==
+
+"@rollup/rollup-win32-arm64-msvc@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz#bf2dbad350376e46cb77fab408bb398ad5f3648d"
+  integrity sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==
+
+"@rollup/rollup-win32-ia32-msvc@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz#5c26b07f74f4054f3ecf202550100496ed2e73f3"
+  integrity sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==
+
+"@rollup/rollup-win32-x64-msvc@4.9.0":
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz#4ea610e0c40a07a8afa2977cbf80507f41c2271c"
+  integrity sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==
+
 "@rushstack/node-core-library@3.59.0":
   version "3.59.0"
   resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.59.0.tgz#f04db22575a242c30114b4723ba0580b6f2d8c85"
@@ -3180,13 +2960,6 @@
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
   integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
 
-"@sinonjs/commons@^1.7.0":
-  version "1.8.3"
-  resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz"
-  integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==
-  dependencies:
-    type-detect "4.0.8"
-
 "@sinonjs/commons@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72"
@@ -3201,13 +2974,6 @@
   dependencies:
     "@sinonjs/commons" "^3.0.0"
 
-"@sinonjs/fake-timers@^8.0.1":
-  version "8.0.1"
-  resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz"
-  integrity sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==
-  dependencies:
-    "@sinonjs/commons" "^1.7.0"
-
 "@szmarczak/http-timer@^1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
@@ -3227,11 +2993,6 @@
   resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.5.22.tgz#ba381db5f7f47558aba6e75cc94ee32a691defeb"
   integrity sha512-D3wDbVXl3Bi5PdGfle6DijhLzZxrvMyZsE1dSHH0xBsqEbu7Pkxn5EEd6CA9tGsCgXIEP1s4Yfy1cZ9xnMz1sQ==
 
-"@tootallnate/once@1":
-  version "1.1.2"
-  resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz"
-  integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
-
 "@tootallnate/once@2":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
@@ -3287,7 +3048,7 @@
   resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
   integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==
 
-"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
+"@types/babel__core@^7.1.14":
   version "7.1.16"
   resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz"
   integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==
@@ -3313,7 +3074,7 @@
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6":
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
   version "7.14.2"
   resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz"
   integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==
@@ -3375,13 +3136,6 @@
     "@types/jsonfile" "*"
     "@types/node" "*"
 
-"@types/graceful-fs@^4.1.2":
-  version "4.1.5"
-  resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz"
-  integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==
-  dependencies:
-    "@types/node" "*"
-
 "@types/graceful-fs@^4.1.3":
   version "4.1.6"
   resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
@@ -3433,14 +3187,6 @@
     jest-diff "^27.0.0"
     pretty-format "^27.0.0"
 
-"@types/jest@^27.4.1":
-  version "27.4.1"
-  resolved "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz"
-  integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==
-  dependencies:
-    jest-matcher-utils "^27.0.0"
-    pretty-format "^27.0.0"
-
 "@types/jest@^29.5.0":
   version "29.5.3"
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.3.tgz#7a35dc0044ffb8b56325c6802a4781a626b05777"
@@ -3564,11 +3310,6 @@
   resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
   integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
 
-"@types/prettier@^2.1.5":
-  version "2.4.1"
-  resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz"
-  integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==
-
 "@types/prop-types@*":
   version "15.7.5"
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
@@ -3936,11 +3677,6 @@
   resolved "https://registry.yarnpkg.com/@zag-js/focus-visible/-/focus-visible-0.1.0.tgz#9777bbaff8316d0b3a14a9095631e1494f69dbc7"
   integrity sha512-PeaBcTmdZWcFf7n1aM+oiOdZc+sy14qi0emPIeUuGMTjbP0xLGrZu43kdpHnWSXy7/r4Ubp/vlg50MCV8+9Isg==
 
-abab@^2.0.3, abab@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz"
-  integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
-
 abab@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
@@ -3954,14 +3690,6 @@ accepts@~1.3.7:
     mime-types "~2.1.24"
     negotiator "0.6.2"
 
-acorn-globals@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz"
-  integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
-  dependencies:
-    acorn "^7.1.1"
-    acorn-walk "^7.1.1"
-
 acorn-globals@^7.0.0:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3"
@@ -3975,27 +3703,17 @@ acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
   resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
   integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
-acorn-walk@^7.1.1:
-  version "7.2.0"
-  resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz"
-  integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
-
 acorn-walk@^8.0.2, acorn-walk@^8.1.1:
   version "8.2.0"
   resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz"
   integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
 
-acorn@^7.1.1:
-  version "7.4.1"
-  resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
-  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
-
 acorn@^8.1.0, acorn@^8.8.1:
   version "8.10.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
   integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
 
-acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1:
+acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1:
   version "8.7.1"
   resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz"
   integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
@@ -4363,20 +4081,6 @@ b4a@^1.6.4:
   resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9"
   integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==
 
-babel-jest@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz"
-  integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==
-  dependencies:
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/babel__core" "^7.1.14"
-    babel-plugin-istanbul "^6.1.1"
-    babel-preset-jest "^27.5.1"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.9"
-    slash "^3.0.0"
-
 babel-jest@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126"
@@ -4414,16 +4118,6 @@ babel-plugin-istanbul@^6.1.1:
     istanbul-lib-instrument "^5.0.4"
     test-exclude "^6.0.0"
 
-babel-plugin-jest-hoist@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz"
-  integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==
-  dependencies:
-    "@babel/template" "^7.3.3"
-    "@babel/types" "^7.3.3"
-    "@types/babel__core" "^7.0.0"
-    "@types/babel__traverse" "^7.0.6"
-
 babel-plugin-jest-hoist@^29.5.0:
   version "29.5.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a"
@@ -4471,14 +4165,6 @@ babel-preset-current-node-syntax@^1.0.0:
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
     "@babel/plugin-syntax-top-level-await" "^7.8.3"
 
-babel-preset-jest@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz"
-  integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==
-  dependencies:
-    babel-plugin-jest-hoist "^27.5.1"
-    babel-preset-current-node-syntax "^1.0.0"
-
 babel-preset-jest@^29.5.0:
   version "29.5.0"
   resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2"
@@ -4658,11 +4344,6 @@ breakword@^1.0.5:
   dependencies:
     wcwidth "^1.0.1"
 
-browser-process-hrtime@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz"
-  integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
-
 browser-resolve@^1.11.3:
   version "1.11.3"
   resolved "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz"
@@ -4681,17 +4362,6 @@ browserslist@^4.0.0:
     escalade "^3.1.1"
     node-releases "^1.1.71"
 
-browserslist@^4.16.6:
-  version "4.17.3"
-  resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz"
-  integrity sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==
-  dependencies:
-    caniuse-lite "^1.0.30001264"
-    electron-to-chromium "^1.3.857"
-    escalade "^3.1.1"
-    node-releases "^1.1.77"
-    picocolors "^0.2.1"
-
 browserslist@^4.17.5:
   version "4.19.1"
   resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz"
@@ -4877,7 +4547,7 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001264, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001400:
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001400:
   version "1.0.30001402"
   resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001402.tgz"
   integrity sha512-Mx4MlhXO5NwuvXGgVb+hg65HZ+bhUYsz8QtDGDo2QmaJS2GBX47Xfi2koL86lc8K+l+htXeTEB/Aeqvezoo6Ew==
@@ -5027,11 +4697,6 @@ ci-info@^2.0.0:
   resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
   integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
 
-ci-info@^3.1.1:
-  version "3.2.0"
-  resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz"
-  integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==
-
 ci-info@^3.2.0:
   version "3.3.0"
   resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz"
@@ -5314,18 +4979,18 @@ content-type@~1.0.4:
   resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
-convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
+convert-source-map@^1.5.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+  integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+convert-source-map@^1.6.0, convert-source-map@^1.7.0:
   version "1.8.0"
   resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz"
   integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
   dependencies:
     safe-buffer "~5.1.1"
 
-convert-source-map@^1.5.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
-  integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
-
 convert-source-map@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@@ -5666,7 +5331,7 @@ csso@^4.0.2:
   dependencies:
     css-tree "^1.1.2"
 
-cssom@^0.4.4, cssom@^0.5.0, "cssom@https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz":
+cssom@^0.5.0, "cssom@https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz":
   version "0.6.0"
   resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1"
 
@@ -5731,15 +5396,6 @@ data-uri-to-buffer@^5.0.1:
   resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz#db89a9e279c2ffe74f50637a59a32fb23b3e4d7c"
   integrity sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==
 
-data-urls@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz"
-  integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==
-  dependencies:
-    abab "^2.0.3"
-    whatwg-mimetype "^2.3.0"
-    whatwg-url "^8.0.0"
-
 data-urls@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
@@ -5811,11 +5467,6 @@ decamelize@^1.1.0, decamelize@^1.2.0:
   resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
-decimal.js@^10.2.1:
-  version "10.3.1"
-  resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz"
-  integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
-
 decimal.js@^10.4.2, decimal.js@^10.4.3:
   version "10.4.3"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
@@ -5828,11 +5479,6 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
-dedent@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz"
-  integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
-
 dedent@^1.0.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff"
@@ -5958,11 +5604,6 @@ diff-sequences@^27.0.6:
   resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz"
   integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==
 
-diff-sequences@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz"
-  integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
-
 diff-sequences@^29.4.3:
   version "29.4.3"
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
@@ -6024,13 +5665,6 @@ domelementtype@^2.0.1, domelementtype@^2.2.0:
   resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz"
   integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
 
-domexception@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz"
-  integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
-  dependencies:
-    webidl-conversions "^5.0.0"
-
 domexception@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
@@ -6126,11 +5760,6 @@ electron-to-chromium@^1.3.723:
   resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.784.tgz"
   integrity sha512-JTPxdUibkefeomWNaYs8lI/x/Zb4cOhZWX+d7kpzsNKzUd07pNuo/AcHeNJ/qgEchxM1IAxda9aaGUhKN/poOg==
 
-electron-to-chromium@^1.3.857:
-  version "1.3.860"
-  resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.860.tgz"
-  integrity sha512-gWwGZ+Wv4Mou2SJRH6JQzhTPjL5f95SX7n6VkLTQ/Q/INsZLZNQ1vH2GlZjozKyvT0kkFuCmWTwIoCj+/hUDPw==
-
 electron-to-chromium@^1.4.17:
   version "1.4.37"
   resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.37.tgz"
@@ -6156,11 +5785,6 @@ emittery@^0.13.1:
   resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
   integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
 
-emittery@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz"
-  integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
-
 emoji-regex@^8.0.0:
   version "8.0.0"
   resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
@@ -7032,16 +6656,6 @@ expand-range@^1.8.1:
   dependencies:
     fill-range "^2.1.0"
 
-expect@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz"
-  integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    jest-get-type "^27.5.1"
-    jest-matcher-utils "^27.5.1"
-    jest-message-util "^27.5.1"
-
 expect@^29.0.0, expect@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521"
@@ -7428,15 +7042,6 @@ forever-agent@~0.6.1:
   resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
   integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
 
-form-data@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz"
-  integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.8"
-    mime-types "^2.1.12"
-
 form-data@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@@ -7780,7 +7385,7 @@ glob@^6.0.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.1.7"
   resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
   integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
@@ -7792,6 +7397,17 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+glob@^8.0.3:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+  integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^5.0.1"
+    once "^1.3.0"
+
 glob@~7.2.0:
   version "7.2.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
@@ -7901,7 +7517,7 @@ got@^9.6.0:
     to-readable-stream "^1.0.0"
     url-parse-lax "^3.0.0"
 
-graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
+graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
   version "4.2.8"
   resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz"
   integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
@@ -8079,13 +7695,6 @@ hsla-regex@^1.0.0:
   resolved "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz"
   integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
 
-html-encoding-sniffer@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz"
-  integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
-  dependencies:
-    whatwg-encoding "^1.0.5"
-
 html-encoding-sniffer@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
@@ -8142,15 +7751,6 @@ http-errors@~1.7.2:
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
-http-proxy-agent@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz"
-  integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
-  dependencies:
-    "@tootallnate/once" "1"
-    agent-base "6"
-    debug "4"
-
 http-proxy-agent@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
@@ -8185,14 +7785,6 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.1:
     agent-base "6"
     debug "4"
 
-https-proxy-agent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz"
-  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
-  dependencies:
-    agent-base "6"
-    debug "4"
-
 https-proxy-agent@^7.0.0:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab"
@@ -8543,13 +8135,6 @@ is-ci@^2.0.0:
   dependencies:
     ci-info "^2.0.0"
 
-is-ci@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz"
-  integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==
-  dependencies:
-    ci-info "^3.1.1"
-
 is-color-stop@^1.0.0:
   version "1.1.0"
   resolved "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz"
@@ -8746,7 +8331,7 @@ is-primitive@^2.0.0:
   resolved "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz"
   integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
 
-is-reference@^1.2.1:
+is-reference@1.2.1, is-reference@^1.2.1:
   version "1.2.1"
   resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz"
   integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
@@ -8969,15 +8554,6 @@ jed@1.1.1:
   resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
   integrity sha512-z35ZSEcXHxLW4yumw0dF6L464NT36vmx3wxJw8MDpraBcWuNVgUPZgPJKcu1HekNgwlMFNqol7i/IpSbjhqwqA==
 
-jest-changed-files@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz"
-  integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    execa "^5.0.0"
-    throat "^6.0.1"
-
 jest-changed-files@^29.5.0:
   version "29.5.0"
   resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e"
@@ -8995,31 +8571,6 @@ jest-changed-files@^29.7.0:
     jest-util "^29.7.0"
     p-limit "^3.1.0"
 
-jest-circus@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz"
-  integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    co "^4.6.0"
-    dedent "^0.7.0"
-    expect "^27.5.1"
-    is-generator-fn "^2.0.0"
-    jest-each "^27.5.1"
-    jest-matcher-utils "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-runtime "^27.5.1"
-    jest-snapshot "^27.5.1"
-    jest-util "^27.5.1"
-    pretty-format "^27.5.1"
-    slash "^3.0.0"
-    stack-utils "^2.0.3"
-    throat "^6.0.1"
-
 jest-circus@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258"
@@ -9072,24 +8623,6 @@ jest-circus@^29.7.0:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-cli@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz"
-  integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==
-  dependencies:
-    "@jest/core" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    chalk "^4.0.0"
-    exit "^0.1.2"
-    graceful-fs "^4.2.9"
-    import-local "^3.0.2"
-    jest-config "^27.5.1"
-    jest-util "^27.5.1"
-    jest-validate "^27.5.1"
-    prompts "^2.0.1"
-    yargs "^16.2.0"
-
 jest-cli@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda"
@@ -9125,36 +8658,6 @@ jest-cli@^29.7.0:
     jest-validate "^29.7.0"
     yargs "^17.3.1"
 
-jest-config@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz"
-  integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==
-  dependencies:
-    "@babel/core" "^7.8.0"
-    "@jest/test-sequencer" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    babel-jest "^27.5.1"
-    chalk "^4.0.0"
-    ci-info "^3.2.0"
-    deepmerge "^4.2.2"
-    glob "^7.1.1"
-    graceful-fs "^4.2.9"
-    jest-circus "^27.5.1"
-    jest-environment-jsdom "^27.5.1"
-    jest-environment-node "^27.5.1"
-    jest-get-type "^27.5.1"
-    jest-jasmine2 "^27.5.1"
-    jest-regex-util "^27.5.1"
-    jest-resolve "^27.5.1"
-    jest-runner "^27.5.1"
-    jest-util "^27.5.1"
-    jest-validate "^27.5.1"
-    micromatch "^4.0.4"
-    parse-json "^5.2.0"
-    pretty-format "^27.5.1"
-    slash "^3.0.0"
-    strip-json-comments "^3.1.1"
-
 jest-config@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3"
@@ -9231,16 +8734,6 @@ jest-diff@^27.0.0:
     jest-get-type "^27.0.6"
     pretty-format "^27.2.4"
 
-jest-diff@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz"
-  integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==
-  dependencies:
-    chalk "^4.0.0"
-    diff-sequences "^27.5.1"
-    jest-get-type "^27.5.1"
-    pretty-format "^27.5.1"
-
 jest-diff@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46"
@@ -9261,13 +8754,6 @@ jest-diff@^29.7.0:
     jest-get-type "^29.6.3"
     pretty-format "^29.7.0"
 
-jest-docblock@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz"
-  integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==
-  dependencies:
-    detect-newline "^3.0.0"
-
 jest-docblock@^29.4.3:
   version "29.4.3"
   resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8"
@@ -9282,17 +8768,6 @@ jest-docblock@^29.7.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz"
-  integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    chalk "^4.0.0"
-    jest-get-type "^27.5.1"
-    jest-util "^27.5.1"
-    pretty-format "^27.5.1"
-
 jest-each@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce"
@@ -9315,19 +8790,6 @@ jest-each@^29.7.0:
     jest-util "^29.7.0"
     pretty-format "^29.7.0"
 
-jest-environment-jsdom@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz"
-  integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/fake-timers" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    jest-mock "^27.5.1"
-    jest-util "^27.5.1"
-    jsdom "^16.6.0"
-
 jest-environment-jsdom@^29.6.0:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.6.2.tgz#4fc68836a7774a771819a2f980cb47af3b1629da"
@@ -9356,18 +8818,6 @@ jest-environment-jsdom@^29.7.0:
     jest-util "^29.7.0"
     jsdom "^20.0.0"
 
-jest-environment-node@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz"
-  integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/fake-timers" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    jest-mock "^27.5.1"
-    jest-util "^27.5.1"
-
 jest-environment-node@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad"
@@ -9402,11 +8852,6 @@ jest-get-type@^27.0.6:
   resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz"
   integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==
 
-jest-get-type@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz"
-  integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==
-
 jest-get-type@^29.4.3:
   version "29.4.3"
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5"
@@ -9417,26 +8862,6 @@ jest-get-type@^29.6.3:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
   integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
 
-jest-haste-map@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz"
-  integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    "@types/graceful-fs" "^4.1.2"
-    "@types/node" "*"
-    anymatch "^3.0.3"
-    fb-watchman "^2.0.0"
-    graceful-fs "^4.2.9"
-    jest-regex-util "^27.5.1"
-    jest-serializer "^27.5.1"
-    jest-util "^27.5.1"
-    jest-worker "^27.5.1"
-    micromatch "^4.0.4"
-    walker "^1.0.7"
-  optionalDependencies:
-    fsevents "^2.3.2"
-
 jest-haste-map@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1"
@@ -9489,37 +8914,6 @@ jest-image-snapshot@^6.2.0:
     rimraf "^2.6.2"
     ssim.js "^3.1.1"
 
-jest-jasmine2@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz"
-  integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/source-map" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    co "^4.6.0"
-    expect "^27.5.1"
-    is-generator-fn "^2.0.0"
-    jest-each "^27.5.1"
-    jest-matcher-utils "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-runtime "^27.5.1"
-    jest-snapshot "^27.5.1"
-    jest-util "^27.5.1"
-    pretty-format "^27.5.1"
-    throat "^6.0.1"
-
-jest-leak-detector@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz"
-  integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==
-  dependencies:
-    jest-get-type "^27.5.1"
-    pretty-format "^27.5.1"
-
 jest-leak-detector@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38"
@@ -9545,16 +8939,6 @@ jest-matcher-utils@^23.6.0:
     jest-get-type "^22.1.0"
     pretty-format "^23.6.0"
 
-jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz"
-  integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
-  dependencies:
-    chalk "^4.0.0"
-    jest-diff "^27.5.1"
-    jest-get-type "^27.5.1"
-    pretty-format "^27.5.1"
-
 jest-matcher-utils@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz#39de0be2baca7a64eacb27291f0bd834fea3a535"
@@ -9586,21 +8970,6 @@ jest-message-util@^23.4.0:
     slash "^1.0.0"
     stack-utils "^1.0.1"
 
-jest-message-util@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz"
-  integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==
-  dependencies:
-    "@babel/code-frame" "^7.12.13"
-    "@jest/types" "^27.5.1"
-    "@types/stack-utils" "^2.0.0"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.9"
-    micromatch "^4.0.4"
-    pretty-format "^27.5.1"
-    slash "^3.0.0"
-    stack-utils "^2.0.3"
-
 jest-message-util@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.2.tgz#af7adc2209c552f3f5ae31e77cf0a261f23dc2bb"
@@ -9631,14 +9000,6 @@ jest-message-util@^29.7.0:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
-jest-mock@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz"
-  integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-
 jest-mock@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00"
@@ -9662,11 +9023,6 @@ jest-pnp-resolver@^1.2.2:
   resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz"
   integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
 
-jest-regex-util@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz"
-  integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==
-
 jest-regex-util@^29.4.3:
   version "29.4.3"
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8"
@@ -9677,15 +9033,6 @@ jest-regex-util@^29.6.3:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52"
   integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==
 
-jest-resolve-dependencies@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz"
-  integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    jest-regex-util "^27.5.1"
-    jest-snapshot "^27.5.1"
-
 jest-resolve-dependencies@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2"
@@ -9711,22 +9058,6 @@ jest-resolve@^23.6.0:
     chalk "^2.0.1"
     realpath-native "^1.0.0"
 
-jest-resolve@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz"
-  integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.9"
-    jest-haste-map "^27.5.1"
-    jest-pnp-resolver "^1.2.2"
-    jest-util "^27.5.1"
-    jest-validate "^27.5.1"
-    resolve "^1.20.0"
-    resolve.exports "^1.1.0"
-    slash "^3.0.0"
-
 jest-resolve@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838"
@@ -9757,33 +9088,6 @@ jest-resolve@^29.7.0:
     resolve.exports "^2.0.0"
     slash "^3.0.0"
 
-jest-runner@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz"
-  integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==
-  dependencies:
-    "@jest/console" "^27.5.1"
-    "@jest/environment" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    emittery "^0.8.1"
-    graceful-fs "^4.2.9"
-    jest-docblock "^27.5.1"
-    jest-environment-jsdom "^27.5.1"
-    jest-environment-node "^27.5.1"
-    jest-haste-map "^27.5.1"
-    jest-leak-detector "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-resolve "^27.5.1"
-    jest-runtime "^27.5.1"
-    jest-util "^27.5.1"
-    jest-worker "^27.5.1"
-    source-map-support "^0.5.6"
-    throat "^6.0.1"
-
 jest-runner@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01"
@@ -9838,34 +9142,6 @@ jest-runner@^29.7.0:
     p-limit "^3.1.0"
     source-map-support "0.5.13"
 
-jest-runtime@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz"
-  integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==
-  dependencies:
-    "@jest/environment" "^27.5.1"
-    "@jest/fake-timers" "^27.5.1"
-    "@jest/globals" "^27.5.1"
-    "@jest/source-map" "^27.5.1"
-    "@jest/test-result" "^27.5.1"
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    chalk "^4.0.0"
-    cjs-module-lexer "^1.0.0"
-    collect-v8-coverage "^1.0.0"
-    execa "^5.0.0"
-    glob "^7.1.3"
-    graceful-fs "^4.2.9"
-    jest-haste-map "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-mock "^27.5.1"
-    jest-regex-util "^27.5.1"
-    jest-resolve "^27.5.1"
-    jest-snapshot "^27.5.1"
-    jest-util "^27.5.1"
-    slash "^3.0.0"
-    strip-bom "^4.0.0"
-
 jest-runtime@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545"
@@ -9922,14 +9198,6 @@ jest-runtime@^29.7.0:
     slash "^3.0.0"
     strip-bom "^4.0.0"
 
-jest-serializer@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz"
-  integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==
-  dependencies:
-    "@types/node" "*"
-    graceful-fs "^4.2.9"
-
 jest-snapshot@^23.6.0:
   version "23.6.0"
   resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz"
@@ -9946,34 +9214,6 @@ jest-snapshot@^23.6.0:
     pretty-format "^23.6.0"
     semver "^5.5.0"
 
-jest-snapshot@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz"
-  integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==
-  dependencies:
-    "@babel/core" "^7.7.2"
-    "@babel/generator" "^7.7.2"
-    "@babel/plugin-syntax-typescript" "^7.7.2"
-    "@babel/traverse" "^7.7.2"
-    "@babel/types" "^7.0.0"
-    "@jest/transform" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/babel__traverse" "^7.0.4"
-    "@types/prettier" "^2.1.5"
-    babel-preset-current-node-syntax "^1.0.0"
-    chalk "^4.0.0"
-    expect "^27.5.1"
-    graceful-fs "^4.2.9"
-    jest-diff "^27.5.1"
-    jest-get-type "^27.5.1"
-    jest-haste-map "^27.5.1"
-    jest-matcher-utils "^27.5.1"
-    jest-message-util "^27.5.1"
-    jest-util "^27.5.1"
-    natural-compare "^1.4.0"
-    pretty-format "^27.5.1"
-    semver "^7.3.2"
-
 jest-snapshot@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c"
@@ -10026,30 +9266,6 @@ jest-snapshot@^29.7.0:
     pretty-format "^29.7.0"
     semver "^7.5.3"
 
-jest-util@^27.0.0:
-  version "27.2.4"
-  resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.2.4.tgz"
-  integrity sha512-mW++4u+fSvAt3YBWm5IpbmRAceUqa2B++JlUZTiuEt2AmNYn0Yw5oay4cP17TGsMINRNPSGiJ2zNnX60g+VbFg==
-  dependencies:
-    "@jest/types" "^27.2.4"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    graceful-fs "^4.2.4"
-    is-ci "^3.0.0"
-    picomatch "^2.2.3"
-
-jest-util@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz"
-  integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    chalk "^4.0.0"
-    ci-info "^3.2.0"
-    graceful-fs "^4.2.9"
-    picomatch "^2.2.3"
-
 jest-util@^29.0.0, jest-util@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d"
@@ -10074,18 +9290,6 @@ jest-util@^29.7.0:
     graceful-fs "^4.2.9"
     picomatch "^2.2.3"
 
-jest-validate@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz"
-  integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==
-  dependencies:
-    "@jest/types" "^27.5.1"
-    camelcase "^6.2.0"
-    chalk "^4.0.0"
-    jest-get-type "^27.5.1"
-    leven "^3.1.0"
-    pretty-format "^27.5.1"
-
 jest-validate@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082"
@@ -10110,19 +9314,6 @@ jest-validate@^29.7.0:
     leven "^3.1.0"
     pretty-format "^29.7.0"
 
-jest-watcher@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz"
-  integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==
-  dependencies:
-    "@jest/test-result" "^27.5.1"
-    "@jest/types" "^27.5.1"
-    "@types/node" "*"
-    ansi-escapes "^4.2.1"
-    chalk "^4.0.0"
-    jest-util "^27.5.1"
-    string-length "^4.0.1"
-
 jest-watcher@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088"
@@ -10160,15 +9351,6 @@ jest-worker@^26.2.1:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
-jest-worker@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz"
-  integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
-  dependencies:
-    "@types/node" "*"
-    merge-stream "^2.0.0"
-    supports-color "^8.0.0"
-
 jest-worker@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.2.tgz#682fbc4b6856ad0aa122a5403c6d048b83f3fb44"
@@ -10189,15 +9371,6 @@ jest-worker@^29.7.0:
     merge-stream "^2.0.0"
     supports-color "^8.0.0"
 
-jest@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz"
-  integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==
-  dependencies:
-    "@jest/core" "^27.5.1"
-    import-local "^3.0.2"
-    jest-cli "^27.5.1"
-
 jest@^29.6.0:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42"
@@ -10253,39 +9426,6 @@ jsbn@~0.1.0:
   resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jsdom@^16.6.0:
-  version "16.7.0"
-  resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz"
-  integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
-  dependencies:
-    abab "^2.0.5"
-    acorn "^8.2.4"
-    acorn-globals "^6.0.0"
-    cssom "^0.4.4"
-    cssstyle "^2.3.0"
-    data-urls "^2.0.0"
-    decimal.js "^10.2.1"
-    domexception "^2.0.1"
-    escodegen "^2.0.0"
-    form-data "^3.0.0"
-    html-encoding-sniffer "^2.0.1"
-    http-proxy-agent "^4.0.1"
-    https-proxy-agent "^5.0.0"
-    is-potential-custom-element-name "^1.0.1"
-    nwsapi "^2.2.0"
-    parse5 "6.0.1"
-    saxes "^5.0.1"
-    symbol-tree "^3.2.4"
-    tough-cookie "^4.0.0"
-    w3c-hr-time "^1.0.2"
-    w3c-xmlserializer "^2.0.0"
-    webidl-conversions "^6.1.0"
-    whatwg-encoding "^1.0.5"
-    whatwg-mimetype "^2.3.0"
-    whatwg-url "^8.5.0"
-    ws "^7.4.6"
-    xml-name-validator "^3.0.0"
-
 jsdom@^20.0.0:
   version "20.0.3"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
@@ -10397,13 +9537,6 @@ json-stringify-safe@~5.0.1:
   resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5@2.x, json5@^2.1.2:
-  version "2.2.0"
-  resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz"
-  integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
-  dependencies:
-    minimist "^1.2.5"
-
 json5@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
@@ -10411,6 +9544,13 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
+json5@^2.1.2:
+  version "2.2.0"
+  resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz"
+  integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
+  dependencies:
+    minimist "^1.2.5"
+
 json5@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
@@ -10729,7 +9869,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.15:
+lodash@^4.17.21, lodash@^4.17.4, lodash@~4.17.15:
   version "4.17.21"
   resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -10807,6 +9947,13 @@ magic-string@^0.27.0:
   dependencies:
     "@jridgewell/sourcemap-codec" "^1.4.13"
 
+magic-string@^0.30.3:
+  version "0.30.5"
+  resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
+  integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
+  dependencies:
+    "@jridgewell/sourcemap-codec" "^1.4.15"
+
 make-dir@^3.0.0, make-dir@^3.0.2:
   version "3.1.0"
   resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz"
@@ -10826,13 +9973,6 @@ makeerror@1.0.12:
   dependencies:
     tmpl "1.0.5"
 
-makeerror@1.0.x:
-  version "1.0.11"
-  resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz"
-  integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=
-  dependencies:
-    tmpl "1.0.x"
-
 map-age-cleaner@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
@@ -11082,7 +10222,7 @@ min-indent@^1.0.0:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimatch@^5.1.0:
+minimatch@^5.0.1, minimatch@^5.1.0:
   version "5.1.6"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
   integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -11299,11 +10439,6 @@ node-releases@^1.1.71:
   resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz"
   integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
 
-node-releases@^1.1.77:
-  version "1.1.77"
-  resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz"
-  integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==
-
 node-releases@^2.0.1:
   version "2.0.1"
   resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz"
@@ -11763,7 +10898,7 @@ parse5-htmlparser2-tree-adapter@^6.0.1:
   dependencies:
     parse5 "^6.0.1"
 
-parse5@6.0.1, parse5@^6.0.1:
+parse5@^6.0.1:
   version "6.0.1"
   resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz"
   integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
@@ -12435,15 +11570,6 @@ pretty-format@^27.0.0, pretty-format@^27.2.4:
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
 
-pretty-format@^27.5.1:
-  version "27.5.1"
-  resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz"
-  integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
-  dependencies:
-    ansi-regex "^5.0.1"
-    ansi-styles "^5.0.0"
-    react-is "^17.0.1"
-
 pretty-format@^29.0.0, pretty-format@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47"
@@ -13072,11 +12198,6 @@ resolve-from@^5.0.0:
   resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz"
   integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
 
-resolve.exports@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz"
-  integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
-
 resolve.exports@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
@@ -13306,6 +12427,26 @@ rollup@^2.71.1:
   optionalDependencies:
     fsevents "~2.3.2"
 
+rollup@^4.9.0:
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.0.tgz#94dff4070f106c1be6b2e88401a49b023c87fa88"
+  integrity sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==
+  optionalDependencies:
+    "@rollup/rollup-android-arm-eabi" "4.9.0"
+    "@rollup/rollup-android-arm64" "4.9.0"
+    "@rollup/rollup-darwin-arm64" "4.9.0"
+    "@rollup/rollup-darwin-x64" "4.9.0"
+    "@rollup/rollup-linux-arm-gnueabihf" "4.9.0"
+    "@rollup/rollup-linux-arm64-gnu" "4.9.0"
+    "@rollup/rollup-linux-arm64-musl" "4.9.0"
+    "@rollup/rollup-linux-riscv64-gnu" "4.9.0"
+    "@rollup/rollup-linux-x64-gnu" "4.9.0"
+    "@rollup/rollup-linux-x64-musl" "4.9.0"
+    "@rollup/rollup-win32-arm64-msvc" "4.9.0"
+    "@rollup/rollup-win32-ia32-msvc" "4.9.0"
+    "@rollup/rollup-win32-x64-msvc" "4.9.0"
+    fsevents "~2.3.2"
+
 rollup@~2.78.0:
   version "2.78.1"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f"
@@ -13415,13 +12556,6 @@ sax@>=0.6.0, sax@~1.2.4:
   resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
-saxes@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
-  integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
-  dependencies:
-    xmlchars "^2.2.0"
-
 saxes@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
@@ -13455,7 +12589,7 @@ semver-match@0.1.1:
   resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@7.3.5, semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
+semver@7.3.5, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
   version "7.3.5"
   resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -13768,11 +12902,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
   resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
-source-map@^0.7.3:
-  version "0.7.3"
-  resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz"
-  integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
 sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
   version "1.4.8"
   resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
@@ -14150,14 +13279,6 @@ supports-color@^8.0.0, supports-color@^8.1.0:
   dependencies:
     has-flag "^4.0.0"
 
-supports-hyperlinks@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz"
-  integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==
-  dependencies:
-    has-flag "^4.0.0"
-    supports-color "^7.0.0"
-
 supports-preserve-symlinks-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -14261,14 +13382,6 @@ term-size@^2.1.0:
   resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
   integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
 
-terminal-link@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz"
-  integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
-  dependencies:
-    ansi-escapes "^4.2.1"
-    supports-hyperlinks "^2.0.0"
-
 terser@^5.0.0:
   version "5.14.2"
   resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
@@ -14314,11 +13427,6 @@ thread-stream@^0.13.0:
   dependencies:
     real-require "^0.1.0"
 
-throat@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz"
-  integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
-
 through@2, through@^2.3.6, through@^2.3.8:
   version "2.3.8"
   resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
@@ -14353,7 +13461,7 @@ tmp@^0.0.33:
   dependencies:
     os-tmpdir "~1.0.2"
 
-tmpl@1.0.5, tmpl@1.0.x:
+tmpl@1.0.5:
   version "1.0.5"
   resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz"
   integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
@@ -14395,15 +13503,6 @@ tosource@1.0.0:
   resolved "https://registry.yarnpkg.com/tosource/-/tosource-1.0.0.tgz#42d88dd116618bcf00d6106dd5446f3427902ff1"
   integrity sha512-N6g8eQ1eerw6Y1pBhdgkubWIiPFwXa2POSUrlL8jth5CyyEWNWzoGKRkO3CaO7Jx27hlJP54muB3btIAbx4MPg==
 
-tough-cookie@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz"
-  integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==
-  dependencies:
-    psl "^1.1.33"
-    punycode "^2.1.1"
-    universalify "^0.1.2"
-
 tough-cookie@^4.1.2, tough-cookie@^4.1.3:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
@@ -14422,13 +13521,6 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
-tr46@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz"
-  integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
-  dependencies:
-    punycode "^2.1.1"
-
 tr46@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
@@ -14465,20 +13557,6 @@ trim-repeated@^1.0.0:
   dependencies:
     escape-string-regexp "^1.0.2"
 
-ts-jest@^27.1.3:
-  version "27.1.3"
-  resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz"
-  integrity sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==
-  dependencies:
-    bs-logger "0.x"
-    fast-json-stable-stringify "2.x"
-    jest-util "^27.0.0"
-    json5 "2.x"
-    lodash.memoize "4.x"
-    make-error "1.x"
-    semver "7.x"
-    yargs-parser "20.x"
-
 ts-jest@^29.1.1:
   version "29.1.1"
   resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b"
@@ -14833,7 +13911,7 @@ unique-string@^2.0.0:
   dependencies:
     crypto-random-string "^2.0.0"
 
-universalify@^0.1.0, universalify@^0.1.2:
+universalify@^0.1.0:
   version "0.1.2"
   resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
@@ -15014,15 +14092,6 @@ v8-compile-cache@^2.0.3:
   resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
   integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
 
-v8-to-istanbul@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz"
-  integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.1"
-    convert-source-map "^1.6.0"
-    source-map "^0.7.3"
-
 v8-to-istanbul@^9.0.1:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265"
@@ -15118,20 +14187,6 @@ vite@^3.2.0-beta.2:
   optionalDependencies:
     fsevents "~2.3.2"
 
-w3c-hr-time@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz"
-  integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
-  dependencies:
-    browser-process-hrtime "^1.0.0"
-
-w3c-xmlserializer@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz"
-  integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==
-  dependencies:
-    xml-name-validator "^3.0.0"
-
 w3c-xmlserializer@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"
@@ -15146,13 +14201,6 @@ w3c-xmlserializer@^5.0.0:
   dependencies:
     xml-name-validator "^5.0.0"
 
-walker@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz"
-  integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=
-  dependencies:
-    makeerror "1.0.x"
-
 walker@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
@@ -15221,28 +14269,11 @@ webidl-conversions@^3.0.0:
   resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
   integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
 
-webidl-conversions@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz"
-  integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
-
-webidl-conversions@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz"
-  integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
-
 webidl-conversions@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
   integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
 
-whatwg-encoding@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz"
-  integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
-  dependencies:
-    iconv-lite "0.4.24"
-
 whatwg-encoding@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
@@ -15257,11 +14288,6 @@ whatwg-encoding@^3.1.1:
   dependencies:
     iconv-lite "0.6.3"
 
-whatwg-mimetype@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz"
-  integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
-
 whatwg-mimetype@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
@@ -15296,15 +14322,6 @@ whatwg-url@^5.0.0:
     tr46 "~0.0.3"
     webidl-conversions "^3.0.0"
 
-whatwg-url@^8.0.0, whatwg-url@^8.5.0:
-  version "8.7.0"
-  resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz"
-  integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==
-  dependencies:
-    lodash "^4.7.0"
-    tr46 "^2.1.0"
-    webidl-conversions "^6.1.0"
-
 when@3.7.7:
   version "3.7.7"
   resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718"
@@ -15460,11 +14477,6 @@ ws@^7.4.3:
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz"
   integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
 
-ws@^7.4.6:
-  version "7.5.5"
-  resolved "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz"
-  integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
-
 ws@^8.14.2:
   version "8.14.2"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
@@ -15475,11 +14487,6 @@ xdg-basedir@^4.0.0:
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
 
-xml-name-validator@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"
-  integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
-
 xml-name-validator@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
@@ -15538,11 +14545,6 @@ yaml@^1.10.0:
   resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
   integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
 
-yargs-parser@20.x, yargs-parser@^20.2.2:
-  version "20.2.9"
-  resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
-  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
 yargs-parser@^18.1.2, yargs-parser@^18.1.3:
   version "18.1.3"
   resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz"
@@ -15551,6 +14553,11 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^20.2.2:
+  version "20.2.9"
+  resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
+  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
 yargs-parser@^21.0.0, yargs-parser@^21.0.1, yargs-parser@^21.1.1:
   version "21.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"

From 2593269a50d723dcb292d9fe7f3d19fd07a7c4a6 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Thu, 14 Dec 2023 16:07:15 +0100
Subject: [PATCH 067/102] Add esModuleInterop and allowSyntheticDefaultImports
 to tsconfig.json

---
 packages/rrdom/tsconfig.json | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/rrdom/tsconfig.json b/packages/rrdom/tsconfig.json
index 867cfe4497..4f95e4e374 100644
--- a/packages/rrdom/tsconfig.json
+++ b/packages/rrdom/tsconfig.json
@@ -8,6 +8,8 @@
     "strictNullChecks": true,
     "removeComments": true,
     "preserveConstEnums": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
     "sourceMap": true,
     "rootDir": "src",
     "outDir": "build",

From cfb0571d169f011d560348172a8fc8f6aa1148c3 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 14:42:49 +0100
Subject: [PATCH 068/102] Add support for updating srcset attribute in
 AssetManager

---
 .../rrweb/src/replay/asset-manager/index.ts   |  17 ++-
 .../src/replay/asset-manager/update-srcset.ts |  22 ++++
 packages/rrweb/test/replay/asset-unit.test.ts | 115 +++++++++++++++++-
 3 files changed, 151 insertions(+), 3 deletions(-)
 create mode 100644 packages/rrweb/src/replay/asset-manager/update-srcset.ts

diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index 68df0965ef..d59a09d0e9 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -8,6 +8,7 @@ import type {
 import { deserializeArg } from '../canvas/deserialize-args';
 import { getSourcesFromSrcset, isAttributeCacheable } from 'rrweb-snapshot';
 import type { RRElement } from 'rrdom';
+import { updateSrcset } from './update-srcset';
 
 export default class AssetManager implements RebuildAssetManagerInterface {
   private originalToObjectURLMap: Map<string, string> = new Map();
@@ -177,11 +178,23 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     const promises: Promise<unknown>[] = [];
 
     if (attribute === 'srcset') {
+      let expectedValue: string | void = originalValue;
       const values = getSourcesFromSrcset(originalValue);
       values.forEach((value) => {
         if (!this.isURLOfCacheableOrigin(value)) return;
-        // FIXME... this doesn't do anything yet...
-        // TODO: hijack also doesn't work for srcset
+        promises.push(
+          this.whenReady(value).then((status) => {
+            const isLoaded = status.status === 'loaded';
+            if (!isLoaded) return; // failed to load asset
+
+            const attributeUnchanged =
+              node.getAttribute(attribute) === expectedValue;
+
+            if (!attributeUnchanged) return; // attribute was changed since we started loading the asset
+
+            expectedValue = updateSrcset(node, value, status.url);
+          }),
+        );
       });
     } else {
       // In live mode we removes the attribute while it loads so it doesn't show the broken image icon
diff --git a/packages/rrweb/src/replay/asset-manager/update-srcset.ts b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
new file mode 100644
index 0000000000..1fcbbdadf4
--- /dev/null
+++ b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
@@ -0,0 +1,22 @@
+import type { RRElement } from 'rrdom/es';
+
+export function updateSrcset(
+  node: Element | RRElement,
+  urlToReplace: string,
+  newURL: string,
+): string | void {
+  const srcset = node.getAttribute('srcset');
+  if (!srcset) return;
+
+  // from https://stackoverflow.com/a/6969486/543604
+  const escapedUrlToReplace = urlToReplace.replace(
+    /[.*+?^${}()|[\]\\]/g,
+    '\\$&',
+  );
+  const matcher = new RegExp(`(?<=^|[\\s,])${escapedUrlToReplace}(?=[\\s,]|$)`);
+  const newSrcset = srcset.replace(matcher, newURL);
+
+  node.setAttribute('srcset', newSrcset);
+
+  return newSrcset;
+}
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 01beaaa388..e6bbf96dfb 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -9,6 +9,7 @@ import {
   assetEvent,
   captureAssetsParam,
 } from '@rrweb/types';
+import { updateSrcset } from '../../src/replay/asset-manager/update-srcset';
 
 describe('AssetManager', () => {
   let assetManager: AssetManager;
@@ -327,7 +328,7 @@ describe('AssetManager', () => {
     expect(element.getAttribute('src')).toBe('objectURL');
   });
 
-  it("should be able to modify a node's attribute for previously loaded assets", async () => {
+  it('should be support srcset for previously loaded assets', async () => {
     const url = 'https://example.com/image.png';
     const event: assetEvent = {
       type: EventType.Asset,
@@ -347,6 +348,63 @@ describe('AssetManager', () => {
     expect(element.getAttribute('srcset')).toBe('objectURL');
   });
 
+  it('should be support partial srcset updates for previously loaded assets', async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+    await assetManager.add(event);
+
+    const element = document.createElement('img');
+    element.setAttribute('srcset', `${url} x2, ${url}?x3 x3`);
+
+    void assetManager.manageAttribute(element, 1, 'srcset');
+    await assetManager.whenReady(url);
+
+    expect(element.getAttribute('srcset')).toBe(`objectURL x2, ${url}?x3 x3`);
+  });
+
+  it('should support updating srcset in chunks for every time an asset is loaded', async () => {
+    const url = 'https://example.com/image.png';
+    const url2 = `${url}?x3`;
+    const element = document.createElement('img');
+    element.setAttribute('srcset', `${url} x2, ${url2} x3`);
+
+    jest
+      .spyOn(URL, 'createObjectURL')
+      .mockReturnValueOnce('objectURL1')
+      .mockReturnValueOnce('objectURL2');
+    await assetManager.add({
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    });
+
+    void assetManager.manageAttribute(element, 1, 'srcset');
+    await assetManager.whenReady(url);
+
+    expect(element.getAttribute('srcset')).toBe(`objectURL1 x2, ${url2} x3`);
+
+    await assetManager.add({
+      type: EventType.Asset,
+      data: {
+        url: url2,
+        payload: examplePayload,
+      },
+    });
+
+    await assetManager.whenReady(url2);
+
+    expect(element.getAttribute('srcset')).toBe(`objectURL1 x2, objectURL2 x3`);
+  });
+
   describe('live mode', () => {
     beforeEach(() => {
       assetManager = new AssetManager(
@@ -452,4 +510,59 @@ describe('AssetManager', () => {
       expect(element.getAttribute('src')).toBe('objectURL2');
     });
   });
+
+  describe('updateSrcset()', () => {
+    it('should update srcset attribute', () => {
+      const element = document.createElement('img');
+      element.setAttribute(
+        'srcset',
+        'https://example.com/image.png x2, https://example.com/image2.png x3',
+      );
+      const oldURL = 'https://example.com/image.png';
+      const newURL = 'https://other-url.com/image.png';
+      updateSrcset(element, oldURL, newURL);
+      expect(element.getAttribute('srcset')).toBe(
+        'https://other-url.com/image.png x2, https://example.com/image2.png x3',
+      );
+    });
+
+    it('should update singular srcset attribute', () => {
+      const element = document.createElement('img');
+      element.setAttribute('srcset', 'https://example.com/image.png');
+      const oldURL = 'https://example.com/image.png';
+      const newURL = 'https://other-url.com/image.png';
+      updateSrcset(element, oldURL, newURL);
+      expect(element.getAttribute('srcset')).toBe(
+        'https://other-url.com/image.png',
+      );
+    });
+
+    it('should update srcset attribute with similar urls', () => {
+      const element = document.createElement('img');
+      element.setAttribute(
+        'srcset',
+        'https://example.com/image.png x2, https://example.com/image.png?x=3 x3',
+      );
+      const oldURL = 'https://example.com/image.png';
+      const newURL = 'https://other-url.com/image.png';
+      updateSrcset(element, oldURL, newURL);
+      expect(element.getAttribute('srcset')).toBe(
+        'https://other-url.com/image.png x2, https://example.com/image.png?x=3 x3',
+      );
+    });
+
+    it('should update srcset attribute with similar urls - second url', () => {
+      const element = document.createElement('img');
+      element.setAttribute(
+        'srcset',
+        'https://example.com/image.png?x=2 x2, https://example.com/image.png x3',
+      );
+      const oldURL = 'https://example.com/image.png';
+      const newURL = 'https://other-url.com/image.png';
+      updateSrcset(element, oldURL, newURL);
+      expect(element.getAttribute('srcset')).toBe(
+        'https://example.com/image.png?x=2 x2, https://other-url.com/image.png x3',
+      );
+    });
+  });
 });

From faff4b980102c09a2365503f001775ff4dcf2fa5 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 15:35:55 +0100
Subject: [PATCH 069/102] Add support for SVG elements in AssetManager

---
 packages/rrweb/test/replay/asset-unit.test.ts | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index e6bbf96dfb..8d87c61d94 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -405,6 +405,30 @@ describe('AssetManager', () => {
     expect(element.getAttribute('srcset')).toBe(`objectURL1 x2, objectURL2 x3`);
   });
 
+  it('should support svg elements', async () => {
+    const url = 'https://example.com/image.png';
+    const event: assetEvent = {
+      type: EventType.Asset,
+      data: {
+        url,
+        payload: examplePayload,
+      },
+    };
+    jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
+    await assetManager.add(event);
+
+    // create svg element `feImage`
+    const feImage = document.createElementNS(
+      'http://www.w3.org/2000/svg',
+      'feImage',
+    );
+    feImage.setAttribute('href', url);
+
+    await assetManager.manageAttribute(feImage, 1, 'href');
+
+    expect(feImage.getAttribute('href')).toBe('objectURL');
+  });
+
   describe('live mode', () => {
     beforeEach(() => {
       assetManager = new AssetManager(

From 1abf3dc1dd982bae0432f628ff48f7d923481d33 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 15:38:01 +0100
Subject: [PATCH 070/102] Drop support for caching xlink:href attributes
 xlink:href is deprecated Most modern browsers rewrite `xlink:href` to `href`
 which is currently supported Tested in Firefox, Safari & Chrome

---
 packages/rrweb-snapshot/src/utils.ts             | 6 +++---
 packages/rrweb/src/replay/asset-manager/index.ts | 1 -
 packages/rrweb/test/record/asset.test.ts         | 9 +++------
 3 files changed, 6 insertions(+), 10 deletions(-)

diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index c02dfdc413..bcbae1100d 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -386,9 +386,9 @@ export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['TH', new Set(['background'])],
   ['TBODY', new Set(['background'])],
   ['THEAD', new Set(['background'])],
-  ['image', new Set(['href', 'xlink:href'])],
-  ['feImage', new Set(['href', 'xlink:href'])],
-  ['cursor', new Set(['href', 'xlink:href'])],
+  ['image', new Set(['href'])],
+  ['feImage', new Set(['href'])],
+  ['cursor', new Set(['href'])],
 ]);
 
 export function isAttributeCacheable(n: Element, attribute: string): boolean {
diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index d59a09d0e9..55753d4cb2 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -224,7 +224,6 @@ export default class AssetManager implements RebuildAssetManagerInterface {
 
           if (!attributeUnchanged) return; // attribute was changed since we started loading the asset
 
-          // TODO: use setAttributeNS for svg, see rrdom's diff for example
           node.setAttribute(attribute, status.url);
         }),
       );
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index 8f87d11265..f6c41d75ca 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -603,13 +603,13 @@ describe('asset caching', function (this: ISuite) {
             <input type="image" id="image" alt="Login" src="{SERVER_URL}/html/assets/robot.png?input-type-image" />
             <iframe src="{SERVER_URL}/html/assets/robot.png?iframe"></iframe>
             <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
-              <image href="{SERVER_URL}/html/assets/robot.png?svg" xlink:href="{SERVER_URL}/html/assets/robot.png?svg2" width="100" height="100" />
+              <image href="{SERVER_URL}/html/assets/robot.png?svg" width="100" height="100" />
                 <defs>
                 <filter id="image">
-                  <feImage href="{SERVER_URL}/html/assets/robot.png?svg3" xlink:href="{SERVER_URL}/html/assets/robot.png?svg4" />
+                  <feImage href="{SERVER_URL}/html/assets/robot.png?svg2" />
                 </filter>
               </defs>
-              <cursor href="{SERVER_URL}/html/assets/robot.png?svg5" xlink:href="{SERVER_URL}/html/assets/robot.png?svg6" >
+              <cursor href="{SERVER_URL}/html/assets/robot.png?svg3"  >
               <rect x="10%" y="10%" width="80%" height="80%" style="filter:url(#image);" />
               </cursor>
             </svg>
@@ -645,9 +645,6 @@ describe('asset caching', function (this: ISuite) {
       '{SERVER_URL}/html/assets/robot.png?svg',
       '{SERVER_URL}/html/assets/robot.png?svg2',
       '{SERVER_URL}/html/assets/robot.png?svg3',
-      '{SERVER_URL}/html/assets/robot.png?svg4',
-      '{SERVER_URL}/html/assets/robot.png?svg5',
-      '{SERVER_URL}/html/assets/robot.png?svg6',
       '{SERVER_URL}/html/assets/robot.png?table',
       '{SERVER_URL}/html/assets/robot.png?td',
     ].forEach((u) => {

From d7030c0898cea178ef77e1e8c9b9fcfc1841bb3a Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 16:15:55 +0100
Subject: [PATCH 071/102] =?UTF-8?q?Fix=20rrdom=20error:=20"diff=20algorith?=
 =?UTF-8?q?m=20for=20rrdom=20=E2=80=BA=20apply=20virtual=20style=20rules?=
 =?UTF-8?q?=20to=20node=20=E2=80=BA=20should=20insert=20rule=20at=20index?=
 =?UTF-8?q?=20[0,0]=20and=20keep=20existing=20rules"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/rrdom/package.json | 3 +++
 yarn.lock                   | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json
index f3133878d4..38a77a3c76 100644
--- a/packages/rrdom/package.json
+++ b/packages/rrdom/package.json
@@ -50,5 +50,8 @@
   },
   "dependencies": {
     "rrweb-snapshot": "^2.0.0-alpha.13"
+  },
+  "resolutions": {
+    "**/jsdom/cssom": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index 7f483b6611..a88055e156 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5342,7 +5342,7 @@ cssom@~0.3.6:
 
 cssstyle@^2.3.0:
   version "2.3.0"
-  resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
   integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
   dependencies:
     cssom "~0.3.6"

From c04ef805519009a5928d568d3bee57ea0d7ee49b Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 16:35:31 +0100
Subject: [PATCH 072/102] Upgrade to jest-environment-jsdom 30-alpha.2 jsdom 22
 (required by jest-environment-jsdom 30) requires rrweb-cssom directly

---
 packages/rrdom/package.json |   5 +-
 yarn.lock                   | 183 +++++++++++++++++++++++++++++++++++-
 2 files changed, 180 insertions(+), 8 deletions(-)

diff --git a/packages/rrdom/package.json b/packages/rrdom/package.json
index 38a77a3c76..3f442ce9a1 100644
--- a/packages/rrdom/package.json
+++ b/packages/rrdom/package.json
@@ -40,7 +40,7 @@
     "@total-typescript/shoehorn": "^0.1.0",
     "eslint": "^8.15.0",
     "jest": "^29.7.0",
-    "jest-environment-jsdom": "^29.7.0",
+    "jest-environment-jsdom": "^30.0.0-alpha.2",
     "puppeteer": "^17.1.3",
     "rollup": "^4.9.0",
     "rollup-plugin-terser": "^7.0.2",
@@ -50,8 +50,5 @@
   },
   "dependencies": {
     "rrweb-snapshot": "^2.0.0-alpha.13"
-  },
-  "resolutions": {
-    "**/jsdom/cssom": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz"
   }
 }
diff --git a/yarn.lock b/yarn.lock
index a88055e156..af683cdfbb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2129,6 +2129,16 @@
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
+"@jest/environment@30.0.0-alpha.2":
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.0.0-alpha.2.tgz#3ac144fac515f09cd163c09ff40a32dfb2f2440d"
+  integrity sha512-rlSvTu+VmsTi9rhAVX691FdAGbYJKCS7nB9eImkxvIIfF5ebvQbh8Wzot8lRWB3mEzu9W0vLX3RoUzJXqI5W1w==
+  dependencies:
+    "@jest/fake-timers" "30.0.0-alpha.2"
+    "@jest/types" "30.0.0-alpha.2"
+    "@types/node" "*"
+    jest-mock "30.0.0-alpha.2"
+
 "@jest/environment@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9"
@@ -2179,6 +2189,18 @@
     expect "^29.7.0"
     jest-snapshot "^29.7.0"
 
+"@jest/fake-timers@30.0.0-alpha.2":
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.0.0-alpha.2.tgz#93f4d65ccf39a97855a22763dbfe27773424c683"
+  integrity sha512-jngoA5we8/41JzNK0Vq/C4s9cnjzcVufhMWrawF6EEY6N8O9hgDLn2um2R/3XDj85rvZWCl1dp3ca2PTPH0JLw==
+  dependencies:
+    "@jest/types" "30.0.0-alpha.2"
+    "@sinonjs/fake-timers" "^11.1.0"
+    "@types/node" "*"
+    jest-message-util "30.0.0-alpha.2"
+    jest-mock "30.0.0-alpha.2"
+    jest-util "30.0.0-alpha.2"
+
 "@jest/fake-timers@^29.6.2":
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4"
@@ -2283,6 +2305,13 @@
     strip-ansi "^6.0.0"
     v8-to-istanbul "^9.0.1"
 
+"@jest/schemas@30.0.0-alpha.2":
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.0-alpha.2.tgz#c8cb82e15e324777cc3ae1bc927ed9188b221c2d"
+  integrity sha512-cmXKHZ2oz0OK1aUg8HR3OT4jAUq+mBLtkOOwFdzmMFKk4gFjGcjevSMN/sLs1daMcXl0TMA1Algh9LVW0+bWwQ==
+  dependencies:
+    "@sinclair/typebox" "^0.31.0"
+
 "@jest/schemas@^29.6.0":
   version "29.6.0"
   resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040"
@@ -2397,6 +2426,18 @@
     slash "^3.0.0"
     write-file-atomic "^4.0.2"
 
+"@jest/types@30.0.0-alpha.2":
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.0-alpha.2.tgz#32722a6103a9963d531d5b502fb31623830a9417"
+  integrity sha512-kQ2aDSVtTqrglSgVMe7N11nQtSgy3Q2/Gm1uqDS7eRyD+UG6UFAiWmAQ43YmUkifQE6xtenMTTyuAiznRCMuFw==
+  dependencies:
+    "@jest/schemas" "30.0.0-alpha.2"
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^17.0.8"
+    chalk "^4.0.0"
+
 "@jest/types@^27.2.4":
   version "27.2.4"
   resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.4.tgz"
@@ -2955,6 +2996,11 @@
   resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
   integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
 
+"@sinclair/typebox@^0.31.0":
+  version "0.31.28"
+  resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.31.28.tgz#b68831e7bc7d09daac26968ea32f42bedc968ede"
+  integrity sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==
+
 "@sindresorhus/is@^0.14.0":
   version "0.14.0"
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -2974,6 +3020,13 @@
   dependencies:
     "@sinonjs/commons" "^3.0.0"
 
+"@sinonjs/fake-timers@^11.1.0":
+  version "11.2.2"
+  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699"
+  integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==
+  dependencies:
+    "@sinonjs/commons" "^3.0.0"
+
 "@szmarczak/http-timer@^1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
@@ -3220,7 +3273,7 @@
     "@types/tough-cookie" "*"
     parse5 "^7.0.0"
 
-"@types/jsdom@^21.1.6":
+"@types/jsdom@^21.1.1", "@types/jsdom@^21.1.6":
   version "21.1.6"
   resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.6.tgz#bcbc7b245787ea863f3da1ef19aa1dcfb9271a1b"
   integrity sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==
@@ -4707,6 +4760,11 @@ ci-info@^3.7.0:
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
   integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
 
+ci-info@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.0.0.tgz#65466f8b280fc019b9f50a5388115d17a63a44f2"
+  integrity sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==
+
 cjs-module-lexer@^1.0.0:
   version "1.2.2"
   resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
@@ -5405,6 +5463,15 @@ data-urls@^3.0.2:
     whatwg-mimetype "^3.0.0"
     whatwg-url "^11.0.0"
 
+data-urls@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4"
+  integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==
+  dependencies:
+    abab "^2.0.6"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^12.0.0"
+
 data-urls@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde"
@@ -8818,6 +8885,20 @@ jest-environment-jsdom@^29.7.0:
     jest-util "^29.7.0"
     jsdom "^20.0.0"
 
+jest-environment-jsdom@^30.0.0-alpha.2:
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-30.0.0-alpha.2.tgz#422e08c0c7fbd769c916ab92d5253b5607f7fba0"
+  integrity sha512-ZcU+Oo236NX9R4uGfNavhzxr2S2RwRFp8xZtZCbP2CZSYnm+x5YO75WbsQbfaZvNJxUeIBbOBhN1AlAn6sSDBQ==
+  dependencies:
+    "@jest/environment" "30.0.0-alpha.2"
+    "@jest/fake-timers" "30.0.0-alpha.2"
+    "@jest/types" "30.0.0-alpha.2"
+    "@types/jsdom" "^21.1.1"
+    "@types/node" "*"
+    jest-mock "30.0.0-alpha.2"
+    jest-util "30.0.0-alpha.2"
+    jsdom "^22.0.0"
+
 jest-environment-node@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad"
@@ -8959,6 +9040,21 @@ jest-matcher-utils@^29.7.0:
     jest-get-type "^29.6.3"
     pretty-format "^29.7.0"
 
+jest-message-util@30.0.0-alpha.2:
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.0.0-alpha.2.tgz#67a623aff92884f066aa3c21f54613606b63fc11"
+  integrity sha512-gAhiwgSIxcXtp6YFxF92abRhc16IJdWT4I318sJ5qo1cRZQPOOeIIUOVXQYeYRiEeo+okBqaY/KXLh5SiE+61A==
+  dependencies:
+    "@babel/code-frame" "^7.12.13"
+    "@jest/types" "30.0.0-alpha.2"
+    "@types/stack-utils" "^2.0.0"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    micromatch "^4.0.4"
+    pretty-format "30.0.0-alpha.2"
+    slash "^3.0.0"
+    stack-utils "^2.0.3"
+
 jest-message-util@^23.4.0:
   version "23.4.0"
   resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz"
@@ -9000,6 +9096,15 @@ jest-message-util@^29.7.0:
     slash "^3.0.0"
     stack-utils "^2.0.3"
 
+jest-mock@30.0.0-alpha.2:
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.0-alpha.2.tgz#5cc3a5cc22ce43fa403030bf02a33a282cbedba2"
+  integrity sha512-jXyAcNg+m42AZ7RMhBMY+zimdyYmv9/Xo7PICUXmYhcJR5Q5fpX9edA8a3zLZTz9+O3I/xxFOpk3ZuuLUfhJoQ==
+  dependencies:
+    "@jest/types" "30.0.0-alpha.2"
+    "@types/node" "*"
+    jest-util "30.0.0-alpha.2"
+
 jest-mock@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00"
@@ -9266,6 +9371,18 @@ jest-snapshot@^29.7.0:
     pretty-format "^29.7.0"
     semver "^7.5.3"
 
+jest-util@30.0.0-alpha.2:
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.0-alpha.2.tgz#1f2cf97bd66d02ca367fae21c594a2c051767dbc"
+  integrity sha512-W2slmlWItPPD7uOnVy8mkjshUrGCiqwtFUIyDE/wkg+mzp8hSpHOAwyxBvqI+UvO3Vpeuk0AL07DYEpUzMy/4g==
+  dependencies:
+    "@jest/types" "30.0.0-alpha.2"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    ci-info "^4.0.0"
+    graceful-fs "^4.2.9"
+    picomatch "^3.0.0"
+
 jest-util@^29.0.0, jest-util@^29.6.2:
   version "29.6.2"
   resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d"
@@ -9458,6 +9575,35 @@ jsdom@^20.0.0:
     ws "^8.11.0"
     xml-name-validator "^4.0.0"
 
+jsdom@^22.0.0:
+  version "22.1.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8"
+  integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==
+  dependencies:
+    abab "^2.0.6"
+    cssstyle "^3.0.0"
+    data-urls "^4.0.0"
+    decimal.js "^10.4.3"
+    domexception "^4.0.0"
+    form-data "^4.0.0"
+    html-encoding-sniffer "^3.0.0"
+    http-proxy-agent "^5.0.0"
+    https-proxy-agent "^5.0.1"
+    is-potential-custom-element-name "^1.0.1"
+    nwsapi "^2.2.4"
+    parse5 "^7.1.2"
+    rrweb-cssom "^0.6.0"
+    saxes "^6.0.0"
+    symbol-tree "^3.2.4"
+    tough-cookie "^4.1.2"
+    w3c-xmlserializer "^4.0.0"
+    webidl-conversions "^7.0.0"
+    whatwg-encoding "^2.0.0"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^12.0.1"
+    ws "^8.13.0"
+    xml-name-validator "^4.0.0"
+
 jsdom@^23.0.0:
   version "23.0.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-23.0.0.tgz#7c8bac82e32e1ac3eef29096ea59d519c72ce4eb"
@@ -10512,7 +10658,7 @@ nwsapi@^2.2.0:
   resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz"
   integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
 
-nwsapi@^2.2.2, nwsapi@^2.2.7:
+nwsapi@^2.2.2, nwsapi@^2.2.4, nwsapi@^2.2.7:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
   integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
@@ -11006,6 +11152,11 @@ picomatch@^2.3.1:
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
+picomatch@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516"
+  integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==
+
 pify@^2.0.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
@@ -11552,6 +11703,15 @@ prettier@2.8.4, prettier@^2.7.1:
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
   integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
 
+pretty-format@30.0.0-alpha.2:
+  version "30.0.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.0-alpha.2.tgz#f69024bc3cc0398fa4d86a9b44c32028006e94ea"
+  integrity sha512-9preHaHWIBEtQOkuN0vpCgfTo8X3vlWmDdCQHA1hSJ5vKNA1EGFr7iEQZDFLdqYe6DeJChdBqi+A+VFV98QGXQ==
+  dependencies:
+    "@jest/schemas" "30.0.0-alpha.2"
+    ansi-styles "^5.0.0"
+    react-is "^18.0.0"
+
 pretty-format@^23.6.0:
   version "23.6.0"
   resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz"
@@ -11696,7 +11856,7 @@ punycode@^2.1.0, punycode@^2.1.1:
   resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-punycode@^2.3.1:
+punycode@^2.3.0, punycode@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
   integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
@@ -13528,6 +13688,13 @@ tr46@^3.0.0:
   dependencies:
     punycode "^2.1.1"
 
+tr46@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469"
+  integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==
+  dependencies:
+    punycode "^2.3.0"
+
 tr46@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec"
@@ -14306,6 +14473,14 @@ whatwg-url@^11.0.0:
     tr46 "^3.0.0"
     webidl-conversions "^7.0.0"
 
+whatwg-url@^12.0.0, whatwg-url@^12.0.1:
+  version "12.0.1"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c"
+  integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==
+  dependencies:
+    tr46 "^4.1.1"
+    webidl-conversions "^7.0.0"
+
 whatwg-url@^14.0.0:
   version "14.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6"
@@ -14462,7 +14637,7 @@ ws@8.13.0, ws@^8.11.0:
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
   integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
 
-ws@8.15.1:
+ws@8.15.1, ws@^8.13.0:
   version "8.15.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.15.1.tgz#271ba33a45ca0cc477940f7f200cd7fba7ee1997"
   integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==

From 621f8c547e2f4accfda13fb8fa3d8ee8aadc2be3 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 16:45:31 +0100
Subject: [PATCH 073/102] Remove console.log statement in
 AssetManager.manageAttribute

---
 packages/rrweb/src/replay/asset-manager/index.ts | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index 55753d4cb2..e3c9fb0fb5 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -163,15 +163,6 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     attribute: string,
   ): Promise<unknown> {
     const originalValue = node.getAttribute(attribute);
-    if (node.nodeName === 'IMG')
-      console.log(
-        'AssetManager.manageAttribute',
-        node.nodeName,
-        attribute,
-        originalValue,
-        'livemode',
-        this.liveMode,
-      );
     if (!originalValue || !this.isCacheable(node, attribute, originalValue))
       return false;
 

From 7d7aac861356d8b6b8c2feeaed37fe0cac8a3407 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 16:58:32 +0100
Subject: [PATCH 074/102] Linux and Mac generate slightly different screenshots

---
 .../test/replay/asset-integration.test.ts     | 25 +++++++++++++++----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 2e49afb2ba..08b5041ca9 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -137,6 +137,8 @@ describe('replayer', function () {
       const loadingImage = await page.screenshot();
       expect(loadingImage).toMatchImageSnapshot({
         customSnapshotIdentifier: 'asset-integration-test-ts-loading',
+        failureThreshold: 0.02,
+        failureThresholdType: 'percent',
       });
 
       expect(
@@ -181,7 +183,10 @@ describe('replayer', function () {
       await waitForRAF(page);
 
       const image = await page.screenshot();
-      expect(image).toMatchImageSnapshot();
+      expect(image).toMatchImageSnapshot({
+        failureThreshold: 0.02,
+        failureThresholdType: 'percent',
+      });
     });
 
     it('should show the correct asset when assets are loading while src is changed in live mode', async () => {
@@ -201,7 +206,10 @@ describe('replayer', function () {
       await waitForRAF(page);
 
       const image = await page.screenshot();
-      expect(image).toMatchImageSnapshot();
+      expect(image).toMatchImageSnapshot({
+        failureThreshold: 0.04,
+        failureThresholdType: 'percent',
+      });
     });
 
     it('should show the loaded asset (robot) in non-live mode', async () => {
@@ -215,7 +223,10 @@ describe('replayer', function () {
       await waitForRAF(page);
 
       const image = await page.screenshot();
-      expect(image).toMatchImageSnapshot();
+      expect(image).toMatchImageSnapshot({
+        failureThreshold: 0.04,
+        failureThresholdType: 'percent',
+      });
     });
 
     it('should show the loaded asset (red square) in non-live mode', async () => {
@@ -242,7 +253,9 @@ describe('replayer', function () {
       await waitForRAF(page);
 
       const image = await page.screenshot();
-      expect(image).toMatchImageSnapshot();
+      expect(image).toMatchImageSnapshot({
+        failureThreshold: 30,
+      });
     });
 
     it('should list original url in non-live mode when asset fails to load', async () => {
@@ -268,7 +281,9 @@ describe('replayer', function () {
       await waitForRAF(page);
 
       const image = await page.screenshot();
-      expect(image).toMatchImageSnapshot();
+      expect(image).toMatchImageSnapshot({
+        failureThreshold: 30,
+      });
 
       expect(
         await page.evaluate(

From d1db95e07c904c9adeed757a33fb02bddb3dbac7 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 17:14:40 +0100
Subject: [PATCH 075/102] Add wait for RAF before evaluating snapshots

---
 packages/rrweb/test/record/cross-origin-iframes.test.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/rrweb/test/record/cross-origin-iframes.test.ts b/packages/rrweb/test/record/cross-origin-iframes.test.ts
index 2d243fa95f..65dbb02965 100644
--- a/packages/rrweb/test/record/cross-origin-iframes.test.ts
+++ b/packages/rrweb/test/record/cross-origin-iframes.test.ts
@@ -209,6 +209,8 @@ describe('cross origin iframes', function (this: ISuite) {
 
       await injectRecordScript(ctx.page.mainFrame().childFrames()[0]); // injects script into new iframe
 
+      await waitForRAF(ctx.page); // wait till script is loaded
+
       const events: eventWithTime[] = await ctx.page.evaluate(
         () => (window as unknown as IWindow).snapshots,
       );

From 9fcdeae7e8265dbc4b7934af3832b786b535b526 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 15 Dec 2023 17:58:02 +0100
Subject: [PATCH 076/102] Changeset: Added support for Asset Event and
 capturing many different types of assets (not just img#src)

---
 .changeset/pretty-candles-chew.md | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 .changeset/pretty-candles-chew.md

diff --git a/.changeset/pretty-candles-chew.md b/.changeset/pretty-candles-chew.md
new file mode 100644
index 0000000000..8bfa5dc29a
--- /dev/null
+++ b/.changeset/pretty-candles-chew.md
@@ -0,0 +1,8 @@
+---
+'rrweb-snapshot': minor
+'rrweb': minor
+'rrdom': patch
+'@rrweb/types': patch
+---
+
+Added support for Asset Event and capturing many different types of assets (not just img#src)

From f5af50ba0c7ff25101269c828148899cf24d1c87 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 16 Jan 2024 15:48:34 +0100
Subject: [PATCH 077/102] Update puppeteer version in package.json

---
 packages/rrweb/package.json |   2 +-
 yarn.lock                   | 184 ++++++++++++------------------------
 2 files changed, 62 insertions(+), 124 deletions(-)

diff --git a/packages/rrweb/package.json b/packages/rrweb/package.json
index 9fcf6bb5a6..2309c29df8 100644
--- a/packages/rrweb/package.json
+++ b/packages/rrweb/package.json
@@ -69,7 +69,7 @@
     "jest-environment-jsdom": "^29.6.0",
     "jest-image-snapshot": "^6.2.0",
     "jest-snapshot": "^29.6.2",
-    "puppeteer": "^20.9.0",
+    "puppeteer": "^21.7.0",
     "rollup": "^2.68.0",
     "rollup-plugin-esbuild": "^4.9.1",
     "rollup-plugin-postcss": "^3.1.1",
diff --git a/yarn.lock b/yarn.lock
index af683cdfbb..9a14e50f7e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2738,23 +2738,23 @@
   resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
   integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
 
-"@puppeteer/browsers@1.4.6":
-  version "1.4.6"
-  resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.4.6.tgz#1f70fd23d5d2ccce9d29b038e5039d7a1049ca77"
-  integrity sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==
+"@puppeteer/browsers@1.9.0":
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.9.0.tgz#dfd0aad0bdc039572f1b57648f189525d627b7ff"
+  integrity sha512-QwguOLy44YBGC8vuPP2nmpX4MUN2FzWbsnvZJtiCzecU3lHmVZkaC1tq6rToi9a200m8RzlVtDyxCS0UIDrxUg==
   dependencies:
     debug "4.3.4"
     extract-zip "2.0.1"
     progress "2.0.3"
-    proxy-agent "6.3.0"
+    proxy-agent "6.3.1"
     tar-fs "3.0.4"
     unbzip2-stream "1.4.3"
-    yargs "17.7.1"
+    yargs "17.7.2"
 
-"@puppeteer/browsers@1.9.0":
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.9.0.tgz#dfd0aad0bdc039572f1b57648f189525d627b7ff"
-  integrity sha512-QwguOLy44YBGC8vuPP2nmpX4MUN2FzWbsnvZJtiCzecU3lHmVZkaC1tq6rToi9a200m8RzlVtDyxCS0UIDrxUg==
+"@puppeteer/browsers@1.9.1":
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-1.9.1.tgz#384ee8b09786f0e8f62b1925e4c492424cb549ee"
+  integrity sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==
   dependencies:
     debug "4.3.4"
     extract-zip "2.0.1"
@@ -3847,7 +3847,7 @@ agent-base@6:
   dependencies:
     debug "4"
 
-agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0:
+agent-base@^7.0.2, agent-base@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434"
   integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==
@@ -4730,13 +4730,6 @@ chrome-launcher@0.15.0:
     is-wsl "^2.2.0"
     lighthouse-logger "^1.0.0"
 
-chromium-bidi@0.4.16:
-  version "0.4.16"
-  resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.4.16.tgz#8a67bfdf6bb8804efc22765a82859d20724b46ab"
-  integrity sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==
-  dependencies:
-    mitt "3.0.0"
-
 chromium-bidi@0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.5.1.tgz#390c1af350c4887824a33d82190de1cc5c5680fc"
@@ -4745,6 +4738,14 @@ chromium-bidi@0.5.1:
     mitt "3.0.1"
     urlpattern-polyfill "9.0.0"
 
+chromium-bidi@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.5.2.tgz#358b03bb7c53e0f8d0fd77d596ea67ee30f7ff06"
+  integrity sha512-PbVOSddxgKyj+JByqavWMNqWPCoCaT6XK5Z1EFe168sxnB/BM51LnZEPXSbFcFAJv/+u2B4XNTs9uXxy4GW3cQ==
+  dependencies:
+    mitt "3.0.1"
+    urlpattern-polyfill "9.0.0"
+
 ci-info@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
@@ -5091,16 +5092,6 @@ core-util-is@^1.0.2:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
   integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
 
-cosmiconfig@8.2.0:
-  version "8.2.0"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd"
-  integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==
-  dependencies:
-    import-fresh "^3.2.1"
-    js-yaml "^4.1.0"
-    parse-json "^5.0.0"
-    path-type "^4.0.0"
-
 cosmiconfig@8.3.6:
   version "8.3.6"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
@@ -5389,7 +5380,12 @@ csso@^4.0.2:
   dependencies:
     css-tree "^1.1.2"
 
-cssom@^0.5.0, "cssom@https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz":
+cssom@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+  integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
+"cssom@https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz":
   version "0.6.0"
   resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1"
 
@@ -5656,11 +5652,6 @@ devtools-protocol@0.0.1036444:
   resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1036444.tgz#a570d3cdde61527c82f9b03919847b8ac7b1c2b9"
   integrity sha512-0y4f/T8H9lsESV9kKP1HDUXgHxCdniFeJh6Erq+FbdOEvp/Ydp9t8kcAAM5gOd17pMrTDlFWntoHtzzeTUWKNw==
 
-devtools-protocol@0.0.1147663:
-  version "0.0.1147663"
-  resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz#4ec5610b39a6250d1f87e6b9c7e16688ed0ac78e"
-  integrity sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==
-
 devtools-protocol@0.0.1203626:
   version "0.0.1203626"
   resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz#4366a4c81a7e0d4fd6924e9182c67f1e5941e820"
@@ -7852,14 +7843,6 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.1:
     agent-base "6"
     debug "4"
 
-https-proxy-agent@^7.0.0:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab"
-  integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==
-  dependencies:
-    agent-base "^7.0.2"
-    debug "4"
-
 https-proxy-agent@^7.0.2:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b"
@@ -10396,16 +10379,16 @@ minimist@^1.2.0, minimist@^1.2.5:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
   integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
 
-mitt@3.0.0, mitt@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
-  integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
-
 mitt@3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
   integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
 
+mitt@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
+  integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
+
 mixme@^0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.5.tgz#bf8f67d8caf10fdb49fd23198fd1fa6d8e406627"
@@ -10950,20 +10933,6 @@ p-try@^2.0.0:
   resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
-pac-proxy-agent@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.0.tgz#db42120c64292685dafaf2bd921e223c56bfb13b"
-  integrity sha512-t4tRAMx0uphnZrio0S0Jw9zg3oDbz1zVhQ/Vy18FjLfP1XOLNUEjaVxYCYRI6NS+BsMBXKIzV6cTLOkO9AtywA==
-  dependencies:
-    "@tootallnate/quickjs-emscripten" "^0.23.0"
-    agent-base "^7.0.2"
-    debug "^4.3.4"
-    get-uri "^6.0.1"
-    http-proxy-agent "^7.0.0"
-    https-proxy-agent "^7.0.0"
-    pac-resolver "^7.0.0"
-    socks-proxy-agent "^8.0.1"
-
 pac-proxy-agent@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75"
@@ -11800,20 +11769,6 @@ proxy-addr@~2.0.5:
     forwarded "0.2.0"
     ipaddr.js "1.9.1"
 
-proxy-agent@6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.0.tgz#72f7bb20eb06049db79f7f86c49342c34f9ba08d"
-  integrity sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==
-  dependencies:
-    agent-base "^7.0.2"
-    debug "^4.3.4"
-    http-proxy-agent "^7.0.0"
-    https-proxy-agent "^7.0.0"
-    lru-cache "^7.14.1"
-    pac-proxy-agent "^7.0.0"
-    proxy-from-env "^1.1.0"
-    socks-proxy-agent "^8.0.1"
-
 proxy-agent@6.3.1:
   version "6.3.1"
   resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.1.tgz#40e7b230552cf44fd23ffaf7c59024b692612687"
@@ -11868,18 +11823,6 @@ pupa@^2.1.1:
   dependencies:
     escape-goat "^2.0.0"
 
-puppeteer-core@20.9.0:
-  version "20.9.0"
-  resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-20.9.0.tgz#6f4b420001b64419deab38d398a4d9cd071040e6"
-  integrity sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==
-  dependencies:
-    "@puppeteer/browsers" "1.4.6"
-    chromium-bidi "0.4.16"
-    cross-fetch "4.0.0"
-    debug "4.3.4"
-    devtools-protocol "0.0.1147663"
-    ws "8.13.0"
-
 puppeteer-core@21.6.1:
   version "21.6.1"
   resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.6.1.tgz#10eccb4dc3167c8c26bc21122fabb45a9fda9ca7"
@@ -11892,6 +11835,18 @@ puppeteer-core@21.6.1:
     devtools-protocol "0.0.1203626"
     ws "8.15.1"
 
+puppeteer-core@21.7.0:
+  version "21.7.0"
+  resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.7.0.tgz#c0abb98cbd17dbd7ee317b4257958337fa25d2c7"
+  integrity sha512-elPYPozrgiM3phSy7VDUJCVWQ07SPnOm78fpSaaSNFoQx5sur/MqhTSro9Wz8lOEjqCykGC6WRkwxDgmqcy1dQ==
+  dependencies:
+    "@puppeteer/browsers" "1.9.1"
+    chromium-bidi "0.5.2"
+    cross-fetch "4.0.0"
+    debug "4.3.4"
+    devtools-protocol "0.0.1203626"
+    ws "8.16.0"
+
 puppeteer@^17.1.3:
   version "17.1.3"
   resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-17.1.3.tgz#2814cf221925e19c681c69aa97401a68b30240c9"
@@ -11909,15 +11864,6 @@ puppeteer@^17.1.3:
     unbzip2-stream "1.4.3"
     ws "8.8.1"
 
-puppeteer@^20.9.0:
-  version "20.9.0"
-  resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-20.9.0.tgz#7bfb9e37deab9728e13b02ea3fb499b5560c79a7"
-  integrity sha512-kAglT4VZ9fWEGg3oLc4/de+JcONuEJhlh3J6f5R1TLkrY/EHHIHxWXDOzXvaxQCtedmyVXBwg8M+P8YCO/wZjw==
-  dependencies:
-    "@puppeteer/browsers" "1.4.6"
-    cosmiconfig "8.2.0"
-    puppeteer-core "20.9.0"
-
 puppeteer@^21.6.1:
   version "21.6.1"
   resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-21.6.1.tgz#2ec0878906ff90b3a424f19e5eb006592abe25b6"
@@ -11927,6 +11873,15 @@ puppeteer@^21.6.1:
     cosmiconfig "8.3.6"
     puppeteer-core "21.6.1"
 
+puppeteer@^21.7.0:
+  version "21.7.0"
+  resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-21.7.0.tgz#c4b46ef28a2986f9c536eb086ab47c8dea80e4f9"
+  integrity sha512-Yy+UUy0b9siJezbhHO/heYUoZQUwyqDK1yOQgblTt0l97tspvDVFkcW9toBlnSvSfkDmMI3Dx9cZL6R8bDArHA==
+  dependencies:
+    "@puppeteer/browsers" "1.9.1"
+    cosmiconfig "8.3.6"
+    puppeteer-core "21.7.0"
+
 pure-rand@^6.0.0:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306"
@@ -12980,15 +12935,6 @@ smartwrap@^2.0.2:
     wcwidth "^1.0.1"
     yargs "^15.1.0"
 
-socks-proxy-agent@^8.0.1:
-  version "8.0.1"
-  resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz#ffc5859a66dac89b0c4dab90253b96705f3e7120"
-  integrity sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==
-  dependencies:
-    agent-base "^7.0.1"
-    debug "^4.3.4"
-    socks "^2.7.1"
-
 socks-proxy-agent@^8.0.2:
   version "8.0.2"
   resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz#5acbd7be7baf18c46a3f293a840109a430a640ad"
@@ -14632,16 +14578,16 @@ ws@7.4.6:
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
   integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
 
-ws@8.13.0, ws@^8.11.0:
-  version "8.13.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
-  integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
-
 ws@8.15.1, ws@^8.13.0:
   version "8.15.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.15.1.tgz#271ba33a45ca0cc477940f7f200cd7fba7ee1997"
   integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==
 
+ws@8.16.0:
+  version "8.16.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
+  integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
+
 ws@8.8.1:
   version "8.8.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
@@ -14652,6 +14598,11 @@ ws@^7.4.3:
   resolved "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz"
   integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
 
+ws@^8.11.0:
+  version "8.13.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
+  integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
+
 ws@^8.14.2:
   version "8.14.2"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
@@ -14764,19 +14715,6 @@ yargs@17.4.0:
     y18n "^5.0.5"
     yargs-parser "^21.0.0"
 
-yargs@17.7.1:
-  version "17.7.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967"
-  integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==
-  dependencies:
-    cliui "^8.0.1"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.3"
-    y18n "^5.0.5"
-    yargs-parser "^21.1.1"
-
 yargs@17.7.2, yargs@^17.3.1:
   version "17.7.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"

From cf1f6a393d692680fe93b836f3a7e2fe65bc41d2 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 16 Jan 2024 15:49:10 +0100
Subject: [PATCH 078/102] Fix cross-origin iframe message forwarding test

---
 packages/rrweb/test/record/cross-origin-iframes.test.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb/test/record/cross-origin-iframes.test.ts b/packages/rrweb/test/record/cross-origin-iframes.test.ts
index 65dbb02965..d89c8a4311 100644
--- a/packages/rrweb/test/record/cross-origin-iframes.test.ts
+++ b/packages/rrweb/test/record/cross-origin-iframes.test.ts
@@ -539,7 +539,7 @@ describe('cross origin iframes', function (this: ISuite) {
     it('should filter out forwarded cross origin rrweb messages', async () => {
       const frame = ctx.page.mainFrame().childFrames()[0];
       const iframe2URL = `${ctx.serverBURL}/html/blank.html`;
-      await frame.evaluate((iframe2URL) => {
+      frame.evaluate((iframe2URL) => {
         // Add a message proxy to forward messages from child frames to its parent frame.
         window.addEventListener('message', (event) => {
           if (event.source !== window)
@@ -551,7 +551,7 @@ describe('cross origin iframes', function (this: ISuite) {
       }, iframe2URL);
 
       // Wait for iframe2 to load
-      await ctx.page.waitForFrame(iframe2URL);
+      await ctx.page.waitForFrame((iframe) => iframe.url() === iframe2URL);
       const iframe2 = frame.childFrames()[0];
       // Record iframe2
       await injectRecordScript(iframe2);

From 085e524aaed4d47e4f8aeb918b2a5925fffa2601 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 16 Jan 2024 16:22:55 +0100
Subject: [PATCH 079/102] Make test more robust

---
 packages/rrweb/test/record/cross-origin-iframes.test.ts | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/packages/rrweb/test/record/cross-origin-iframes.test.ts b/packages/rrweb/test/record/cross-origin-iframes.test.ts
index d89c8a4311..c587fa6d78 100644
--- a/packages/rrweb/test/record/cross-origin-iframes.test.ts
+++ b/packages/rrweb/test/record/cross-origin-iframes.test.ts
@@ -201,10 +201,13 @@ describe('cross origin iframes', function (this: ISuite) {
     });
 
     it('should replace the existing DOM nodes on iframe navigation with `isAttachIframe`', async () => {
-      await ctx.page.evaluate((url) => {
+      const newUrl = `${ctx.serverURL}/html/form.html?2`;
+      ctx.page.evaluate((url) => {
         const iframe = document.querySelector('iframe') as HTMLIFrameElement;
-        iframe.src = `${url}/html/form.html?2`;
-      }, ctx.serverURL);
+        iframe.src = url;
+      }, newUrl);
+      await ctx.page.waitForFrame((iframe) => iframe.url() === newUrl);
+
       await waitForRAF(ctx.page); // loads iframe
 
       await injectRecordScript(ctx.page.mainFrame().childFrames()[0]); // injects script into new iframe

From 57a167c23d8f88f49b55081488e3d6977d65e44c Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 5 Feb 2024 17:15:13 +0100
Subject: [PATCH 080/102] Deprecate `inlineImages` and introduce
 `captureAssets` option

---
 .changeset/wise-pens-peel.md | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 .changeset/wise-pens-peel.md

diff --git a/.changeset/wise-pens-peel.md b/.changeset/wise-pens-peel.md
new file mode 100644
index 0000000000..41a603b2e6
--- /dev/null
+++ b/.changeset/wise-pens-peel.md
@@ -0,0 +1,9 @@
+---
+'rrweb-snapshot': major
+'rrweb': major
+---
+
+`inlineImages` recording option has been deprecated and is now an alias for `captureAssets: { objectURLs: true, origins: true }`.
+Please see [asset recording documentation](/docs/recipes/assets.md) for more information.
+
+The reason we deprecated `inlineImages` is because it modified events after they where already emitted, which could lead to events being saved without the corresponding images. The new `captureAssets` option records assets as a separate event, ensuring that all assets are recorded and events are not modified after they are emitted.

From cd730d400931ad4dbad9ba578250b8a0a0866cbb Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 5 Feb 2024 17:40:04 +0100
Subject: [PATCH 081/102] Docs: add examples to `getUrlsFromSrcset`

---
 packages/rrweb-snapshot/src/utils.ts | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index bcbae1100d..2d6e2888d6 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -350,6 +350,11 @@ export function extractFileExtension(
   return match?.[1] ?? null;
 }
 
+/**
+ * Extracts the URLs from a srcset attribute.
+ * @param srcset - The srcset attribute value. eg. `image.jpg 2x, image2.jpg 3x`
+ * @returns An array of URLs. eg. `['image.jpg', 'image2.jpg']`
+ */
 export function getUrlsFromSrcset(srcset: string): string[] {
   const urls: string[] = [];
   const parts = srcset.split(',');
@@ -358,11 +363,13 @@ export function getUrlsFromSrcset(srcset: string): string[] {
     const spaceIndex = trimmed.indexOf(' ');
     if (spaceIndex === -1) {
       // If no descriptor is specified, it's a single URL.
+      // eg. `image.jpg`
       urls.push(trimmed);
     } else {
-      // Otherwise, it's one or more URLs followed by a single descriptor.
+      // Otherwise, it's a URL followed by a single descriptor.
       // Since we don't know how long the URL will be, we'll assume it's everything
-      // after the first space.
+      // before the first space.
+      // eg. `image.jpg 2x`
       urls.push(trimmed.substring(0, spaceIndex));
     }
   }

From 9532d77bcfbde7f027b56ad7c3d9bce959bcd13d Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Tue, 6 Feb 2024 17:50:32 +0100
Subject: [PATCH 082/102] Move @rrweb/types to devDependencies for
 rrweb-snapshot

---
 packages/rrweb-snapshot/package.json | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json
index 5229e9e364..69451c02b3 100644
--- a/packages/rrweb-snapshot/package.json
+++ b/packages/rrweb-snapshot/package.json
@@ -43,10 +43,8 @@
     "url": "https://github.com/rrweb-io/rrweb/issues"
   },
   "homepage": "https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-snapshot#readme",
-  "dependencies": {
-    "@rrweb/types": "^2.0.0-alpha.11"
-  },
   "devDependencies": {
+    "@rrweb/types": "^2.0.0-alpha.11",
     "@types/chai": "^4.1.4",
     "@types/jest": "^29.5.10",
     "@types/jsdom": "^21.1.6",

From a132f6398a2331f023d500e6820173e5b6133096 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Mon, 18 Mar 2024 11:17:03 +0100
Subject: [PATCH 083/102] Remove console.log statements in
 asset-integration.test.ts

---
 packages/rrweb/test/replay/asset-integration.test.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/packages/rrweb/test/replay/asset-integration.test.ts b/packages/rrweb/test/replay/asset-integration.test.ts
index 08b5041ca9..cc3dda84fc 100644
--- a/packages/rrweb/test/replay/asset-integration.test.ts
+++ b/packages/rrweb/test/replay/asset-integration.test.ts
@@ -216,7 +216,6 @@ describe('replayer', function () {
       await page.evaluate(`
       const { Replayer } = rrweb;
       window.replayer = new Replayer(assetsChangedEvents);
-      console.log('pausing at', (assetsChangedEvents[2].timestamp - assetsChangedEvents[0].timestamp) + 1)
       replayer.pause((assetsChangedEvents[2].timestamp - assetsChangedEvents[0].timestamp) + 1);
     `);
 
@@ -233,7 +232,6 @@ describe('replayer', function () {
       await page.evaluate(`
       const { Replayer } = rrweb;
       window.replayer = new Replayer(assetsChangedEvents);
-      console.log('pausing at', (assetsChangedEvents[1].timestamp - assetsChangedEvents[0].timestamp) + 1)
       replayer.pause((assetsChangedEvents[1].timestamp - assetsChangedEvents[0].timestamp) + 1);
   `);
 

From 5c659bdd849cc0e5ec4f281f84290b8578eb6664 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Wed, 20 Mar 2024 15:21:19 +0100
Subject: [PATCH 084/102] Update
 packages/rrweb/src/replay/asset-manager/index.ts

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
---
 packages/rrweb/src/replay/asset-manager/index.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index e3c9fb0fb5..ec80d03cf0 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -197,6 +197,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
         }
         hijackedAttributes.set(attribute, originalValue);
         if (node.tagName === 'IMG' && attribute === 'src') {
+          // special value to prevent a broken image icon while asset is being loaded
           node.setAttribute('src', '//:0');
         } else {
           node.removeAttribute(attribute);

From 34e8047794ac4f7fcddb774c129d2467a8e31937 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Mon, 25 Mar 2024 11:05:21 +0000
Subject: [PATCH 085/102] Delay call until all attributes are present on the
 new node, in case the AssetManager needs to interrogate other attributes to
 determine how to manage, e.g. <link with href depending on rel="stylesheet"

---
 packages/rrweb-snapshot/src/rebuild.ts | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index 8289c262fa..1c92afcd97 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -190,6 +190,7 @@ function buildNode(
        * We need to parse them last so they can overwrite conflicting attributes.
        */
       const specialAttributes: { [key: string]: string | number } = {};
+      const managedAttributes: [Element, number, string][] = [];
       for (const name in n.attributes) {
         if (!Object.prototype.hasOwnProperty.call(n.attributes, name)) {
           continue;
@@ -286,13 +287,15 @@ function buildNode(
             continue;
           } else {
             node.setAttribute(name, value.toString());
-
-            options.assetManager?.manageAttribute(node, n.id, name);
+            managedAttributes.push([node, n.id, name]);
           }
         } catch (error) {
           // skip invalid attribute
         }
       }
+      for (const ma of managedAttributes) {
+        options.assetManager?.manageAttribute(...ma);
+      }
 
       for (const name in specialAttributes) {
         const value = specialAttributes[name];

From 07be53c8907c94fab02c6cba364631997e99b922 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Mon, 25 Mar 2024 11:44:04 +0000
Subject: [PATCH 086/102] Rename Cacheable -> Capturable

---
 packages/rrdom/src/diff.ts                       |  2 +-
 packages/rrdom/test/diff.test.ts                 |  6 +++---
 packages/rrweb-snapshot/src/snapshot.ts          |  4 ++--
 packages/rrweb-snapshot/src/utils.ts             |  6 +++---
 packages/rrweb-snapshot/test/utils.test.ts       | 16 ++++++++++------
 packages/rrweb/src/record/mutation.ts            |  2 +-
 .../rrweb/src/record/observers/asset-manager.ts  |  6 +++---
 packages/rrweb/src/replay/asset-manager/index.ts | 16 ++++++++--------
 packages/rrweb/test/replay/asset-unit.test.ts    | 16 ++++++++--------
 packages/types/src/index.ts                      |  4 ++--
 10 files changed, 41 insertions(+), 37 deletions(-)

diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index c1c241fdaa..5a65e1a3c3 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -333,7 +333,7 @@ function diffProps(
     } else if (newTree.tagName === 'IFRAME' && name === 'srcdoc') continue;
     else oldTree.setAttribute(name, newValue);
 
-    if (assetManager?.isCacheable(oldTree, name, newValue)) {
+    if (assetManager?.isCapturable(oldTree, name, newValue)) {
       // can possibly remove the attribute again if it hasn't loaded yet
       assetManager.manageAttribute(oldTree, rrnodeMirror.getId(newTree), name);
     }
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index b50646e25e..546f475649 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -470,12 +470,12 @@ describe('diff algorithm for rrdom', () => {
       beforeEach(() => {
         assetManager = fromPartial({
           manageAttribute: jest.fn(),
-          isCacheable: jest.fn(),
+          isCapturable: jest.fn(),
         });
         replayer.assetManager = assetManager;
       });
 
-      it('new properties are managed by asset manager if cacheable', () => {
+      it('new properties are managed by asset manager if capturable', () => {
         const tagName = 'IMG';
         const node = document.createElement(tagName);
         const sn = Object.assign({}, elementSn, { tagName });
@@ -486,7 +486,7 @@ describe('diff algorithm for rrdom', () => {
         const sn2 = Object.assign({}, elementSn, { tagName });
         rrDocument.mirror.add(rrNode, sn2);
 
-        (assetManager.isCacheable as jest.Mock)
+        (assetManager.isCapturable as jest.Mock)
           .mockReturnValueOnce(true)
           .mockReturnValue(false);
         rrNode.attributes = { src: 'image.png', class: 'node' };
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 9e53fa679d..33c6188104 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -28,7 +28,7 @@ import {
   toLowerCase,
   extractFileExtension,
   getUrlsFromSrcset,
-  isAttributeCacheable,
+  isAttributeCapturable,
 } from './utils';
 
 let _id = 1;
@@ -682,7 +682,7 @@ function serializeElementNode(
       ));
 
       // save assets offline
-      if (value && onAssetDetected && isAttributeCacheable(n, attr.name)) {
+      if (value && onAssetDetected && isAttributeCapturable(n, attr.name)) {
         if (attr.name === 'srcset') {
           assets.push(...getUrlsFromSrcset(value));
         } else {
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 2d6e2888d6..4da1649acb 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -376,7 +376,7 @@ export function getUrlsFromSrcset(srcset: string): string[] {
   return urls;
 }
 
-export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
+export const CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['IMG', new Set(['src', 'srcset'])],
   ['VIDEO', new Set(['src'])],
   ['AUDIO', new Set(['src'])],
@@ -398,8 +398,8 @@ export const CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['cursor', new Set(['href'])],
 ]);
 
-export function isAttributeCacheable(n: Element, attribute: string): boolean {
-  const acceptedAttributesSet = CACHEABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
+export function isAttributeCapturable(n: Element, attribute: string): boolean {
+  const acceptedAttributesSet = CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
     n.nodeName,
   );
   if (!acceptedAttributesSet) {
diff --git a/packages/rrweb-snapshot/test/utils.test.ts b/packages/rrweb-snapshot/test/utils.test.ts
index d1b74d50d5..fbf14fe6e9 100644
--- a/packages/rrweb-snapshot/test/utils.test.ts
+++ b/packages/rrweb-snapshot/test/utils.test.ts
@@ -1,7 +1,11 @@
 /**
  * @jest-environment jsdom
  */
-import { extractFileExtension, isAttributeCacheable, isNodeMetaEqual } from '../src/utils';
+import {
+  extractFileExtension,
+  isAttributeCapturable,
+  isNodeMetaEqual,
+} from '../src/utils';
 import { NodeType, serializedNode, serializedNodeWithId } from '@rrweb/types';
 
 describe('utils', () => {
@@ -198,7 +202,7 @@ describe('utils', () => {
     });
   });
 
-  describe('isAttributeCacheable()', () => {
+  describe('isAttributeCapturable()', () => {
     const validAttributeCombinations = [
       ['img', ['src', 'srcset']],
       ['video', ['src']],
@@ -228,8 +232,8 @@ describe('utils', () => {
     validAttributeCombinations.forEach(([tagName, attributes]) => {
       const element = document.createElement(tagName);
       attributes.forEach((attribute) => {
-        it(`should correctly identify <${tagName} ${attribute}> as cacheable`, () => {
-          expect(isAttributeCacheable(element, attribute)).toBe(true);
+        it(`should correctly identify <${tagName} ${attribute}> as capturable`, () => {
+          expect(isAttributeCapturable(element, attribute)).toBe(true);
         });
       });
     });
@@ -237,8 +241,8 @@ describe('utils', () => {
     invalidAttributeCombinations.forEach(([tagName, attributes]) => {
       const element = document.createElement(tagName);
       attributes.forEach((attribute) => {
-        it(`should correctly identify <${tagName} ${attribute}> as NOT cacheable`, () => {
-          expect(isAttributeCacheable(element, attribute)).toBe(false);
+        it(`should correctly identify <${tagName} ${attribute}> as NOT capturable`, () => {
+          expect(isAttributeCapturable(element, attribute)).toBe(false);
         });
       });
     });
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index f1bfcc0cbe..3238192a90 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -634,7 +634,7 @@ export default class MutationBuffer {
             ));
           if (
             transformedValue &&
-            this.assetManager.isAttributeCacheable(target, attributeName)
+            this.assetManager.isAttributeCapturable(target, attributeName)
           ) {
             if (attributeName === 'srcset') {
               getSourcesFromSrcset(transformedValue).forEach((url) => {
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 3dea10ddd1..c7e0981b85 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -10,7 +10,7 @@ import { encode } from 'base64-arraybuffer';
 import { patch } from '../../utils';
 
 import type { recordOptions } from '../../types';
-import { isAttributeCacheable } from 'rrweb-snapshot';
+import { isAttributeCapturable } from 'rrweb-snapshot';
 
 export default class AssetManager {
   private urlObjectMap = new Map<string, File | Blob | MediaSource>();
@@ -190,7 +190,7 @@ export default class AssetManager {
     return { status: 'capturing' };
   }
 
-  public isAttributeCacheable(n: Element, attribute: string): boolean {
-    return isAttributeCacheable(n, attribute);
+  public isAttributeCapturable(n: Element, attribute: string): boolean {
+    return isAttributeCapturable(n, attribute);
   }
 }
diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index ec80d03cf0..478e63a712 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -6,7 +6,7 @@ import type {
   captureAssetsParam,
 } from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
-import { getSourcesFromSrcset, isAttributeCacheable } from 'rrweb-snapshot';
+import { getSourcesFromSrcset, isAttributeCapturable } from 'rrweb-snapshot';
 import type { RRElement } from 'rrdom';
 import { updateSrcset } from './update-srcset';
 
@@ -120,23 +120,23 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     };
   }
 
-  public isCacheable(
+  public isCapturable(
     n: RRElement | Element,
     attribute: string,
     value: string,
   ): boolean {
-    if (!isAttributeCacheable(n as Element, attribute)) return false;
+    if (!isAttributeCapturable(n as Element, attribute)) return false;
 
     if (attribute === 'srcset') {
       return getSourcesFromSrcset(value).some((source) =>
-        this.isURLOfCacheableOrigin(source),
+        this.isURLConfiguredForCapture(source),
       );
     } else {
-      return this.isURLOfCacheableOrigin(value);
+      return this.isURLConfiguredForCapture(value);
     }
   }
 
-  public isURLOfCacheableOrigin(url: string): boolean {
+  public isURLConfiguredForCapture(url: string): boolean {
     if (url.startsWith('data:')) return false;
 
     const { origins: cachedOrigins = false, objectURLs = false } =
@@ -163,7 +163,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     attribute: string,
   ): Promise<unknown> {
     const originalValue = node.getAttribute(attribute);
-    if (!originalValue || !this.isCacheable(node, attribute, originalValue))
+    if (!originalValue || !this.isCapturable(node, attribute, originalValue))
       return false;
 
     const promises: Promise<unknown>[] = [];
@@ -172,7 +172,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
       let expectedValue: string | void = originalValue;
       const values = getSourcesFromSrcset(originalValue);
       values.forEach((value) => {
-        if (!this.isURLOfCacheableOrigin(value)) return;
+        if (!this.isURLConfiguredForCapture(value)) return;
         promises.push(
           this.whenReady(value).then((status) => {
             const isLoaded = status.status === 'loaded';
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 8d87c61d94..0c99244d37 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -180,8 +180,8 @@ describe('AssetManager', () => {
       },
     );
     urls.forEach((url) => {
-      it(`should correctly identify ${url} as cacheable for origin ${origin}`, () => {
-        expect(assetManager.isURLOfCacheableOrigin(url)).toBe(true);
+      it(`should correctly identify ${url} as capturable for origin ${origin}`, () => {
+        expect(assetManager.isURLConfiguredForCapture(url)).toBe(true);
       });
     });
   });
@@ -195,8 +195,8 @@ describe('AssetManager', () => {
       },
     );
     urls.forEach((url) => {
-      it(`should correctly identify ${url} as NOT cacheable for origin ${origin}`, () => {
-        expect(assetManager.isURLOfCacheableOrigin(url)).toBe(false);
+      it(`should correctly identify ${url} as NOT capturable for origin ${origin}`, () => {
+        expect(assetManager.isURLConfiguredForCapture(url)).toBe(false);
       });
     });
   });
@@ -266,8 +266,8 @@ describe('AssetManager', () => {
         objectURLs: false,
       },
     );
-    it(`should correctly identify <${element} ${attribute}=${value} /> as cacheable for origins ${origins}`, () => {
-      expect(assetManager.isCacheable(element, attribute, value)).toBe(true);
+    it(`should correctly identify <${element} ${attribute}=${value} /> as capturable for origins ${origins}`, () => {
+      expect(assetManager.isCapturable(element, attribute, value)).toBe(true);
     });
   });
 
@@ -281,8 +281,8 @@ describe('AssetManager', () => {
         objectURLs: false,
       },
     );
-    it(`should correctly identify <${element} ${attribute}=${value} /> as NOT cacheable for origins ${origins}`, () => {
-      expect(assetManager.isCacheable(element, attribute, value)).toBe(false);
+    it(`should correctly identify <${element} ${attribute}=${value} /> as NOT capturable for origins ${origins}`, () => {
+      expect(assetManager.isCapturable(element, attribute, value)).toBe(false);
     });
   });
 
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index c5e774f50d..ad1d7a235a 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -769,8 +769,8 @@ export declare abstract class RebuildAssetManagerInterface {
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
   abstract reset(config?: captureAssetsParam | undefined): void;
-  abstract isCacheable(n: Element, attribute: string, value: string): boolean;
-  abstract isURLOfCacheableOrigin(url: string): boolean;
+  abstract isCapturable(n: Element, attribute: string, value: string): boolean;
+  abstract isURLConfiguredForCapture(url: string): boolean;
   abstract manageAttribute(n: Element, id: number, attribute: string): void;
 }
 

From a795544da1b5dc547bad52f5e36c05bd87f8b4ef Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Mon, 25 Mar 2024 17:46:40 +0000
Subject: [PATCH 087/102] Indicate the presence of deferred assets using new
 `rr_captured_*` attributes instead of storing config on the Meta event

 - a better approach as discussed with Justin and Yanzhen
---
 packages/rrdom/src/diff.ts                    |  18 +-
 packages/rrdom/test/diff.test.ts              |   7 +-
 packages/rrweb-snapshot/src/rebuild.ts        |  20 +-
 packages/rrweb-snapshot/src/snapshot.ts       |  15 +-
 packages/rrweb/src/record/index.ts            |   1 -
 packages/rrweb/src/record/mutation.ts         |  15 +-
 .../rrweb/src/replay/asset-manager/index.ts   |  82 ++---
 .../src/replay/asset-manager/update-srcset.ts |  11 +-
 packages/rrweb/src/replay/index.ts            |  13 +-
 .../__snapshots__/integration.test.ts.snap    | 318 ++++--------------
 .../test/__snapshots__/record.test.ts.snap    | 132 ++------
 packages/rrweb/test/events/assets-mutation.ts |   6 +-
 .../assets-src-changed-before-asset-loaded.ts |   8 +-
 packages/rrweb/test/events/assets.ts          |   6 +-
 .../cross-origin-iframes.test.ts.snap         |  92 +----
 .../record/__snapshots__/webgl.test.ts.snap   |  48 +--
 packages/rrweb/test/replay/asset-unit.test.ts | 195 ++---------
 packages/rrweb/test/utils.ts                  |  11 +
 packages/types/src/index.ts                   |  10 +-
 19 files changed, 232 insertions(+), 776 deletions(-)

diff --git a/packages/rrdom/src/diff.ts b/packages/rrdom/src/diff.ts
index 5a65e1a3c3..a870b0553b 100644
--- a/packages/rrdom/src/diff.ts
+++ b/packages/rrdom/src/diff.ts
@@ -331,12 +331,20 @@ function diffProps(
         }
       };
     } else if (newTree.tagName === 'IFRAME' && name === 'srcdoc') continue;
-    else oldTree.setAttribute(name, newValue);
-
-    if (assetManager?.isCapturable(oldTree, name, newValue)) {
+    else if (
+      assetManager &&
+      name.startsWith('rr_captured_') &&
+      newValue &&
+      typeof newValue === 'string'
+    ) {
       // can possibly remove the attribute again if it hasn't loaded yet
-      assetManager.manageAttribute(oldTree, rrnodeMirror.getId(newTree), name);
-    }
+      assetManager.manageAttribute(
+        oldTree,
+        rrnodeMirror.getId(newTree),
+        name.substring('rr_captured_'.length),
+        newValue,
+      );
+    } else oldTree.setAttribute(name, newValue);
   }
 
   for (const { name } of Array.from(oldAttributes))
diff --git a/packages/rrdom/test/diff.test.ts b/packages/rrdom/test/diff.test.ts
index 546f475649..81df870c02 100644
--- a/packages/rrdom/test/diff.test.ts
+++ b/packages/rrdom/test/diff.test.ts
@@ -470,7 +470,6 @@ describe('diff algorithm for rrdom', () => {
       beforeEach(() => {
         assetManager = fromPartial({
           manageAttribute: jest.fn(),
-          isCapturable: jest.fn(),
         });
         replayer.assetManager = assetManager;
       });
@@ -486,15 +485,13 @@ describe('diff algorithm for rrdom', () => {
         const sn2 = Object.assign({}, elementSn, { tagName });
         rrDocument.mirror.add(rrNode, sn2);
 
-        (assetManager.isCapturable as jest.Mock)
-          .mockReturnValueOnce(true)
-          .mockReturnValue(false);
-        rrNode.attributes = { src: 'image.png', class: 'node' };
+        rrNode.attributes = { rr_captured_src: 'image.png', class: 'node' };
         diff(node, rrNode, replayer);
         expect(assetManager.manageAttribute).toHaveBeenCalledWith(
           node,
           mirror.getId(node),
           'src',
+          'image.png',
         );
       });
     });
diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts
index 1c92afcd97..64cbc0919e 100644
--- a/packages/rrweb-snapshot/src/rebuild.ts
+++ b/packages/rrweb-snapshot/src/rebuild.ts
@@ -190,7 +190,6 @@ function buildNode(
        * We need to parse them last so they can overwrite conflicting attributes.
        */
       const specialAttributes: { [key: string]: string | number } = {};
-      const managedAttributes: [Element, number, string][] = [];
       for (const name in n.attributes) {
         if (!Object.prototype.hasOwnProperty.call(n.attributes, name)) {
           continue;
@@ -287,18 +286,29 @@ function buildNode(
             continue;
           } else {
             node.setAttribute(name, value.toString());
-            managedAttributes.push([node, n.id, name]);
           }
         } catch (error) {
           // skip invalid attribute
         }
       }
-      for (const ma of managedAttributes) {
-        options.assetManager?.manageAttribute(...ma);
-      }
 
       for (const name in specialAttributes) {
         const value = specialAttributes[name];
+
+        if (
+          name.startsWith('rr_captured_') &&
+          value &&
+          typeof value === 'string'
+        ) {
+          options.assetManager?.manageAttribute(
+            node,
+            n.id,
+            name.substring('rr_captured_'.length),
+            value,
+          );
+          continue;
+        }
+
         // handle internal attributes
         if (tagName === 'canvas' && name === 'rr_dataURL') {
           const image = document.createElement('img');
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 33c6188104..ee4d188468 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -674,21 +674,28 @@ function serializeElementNode(
   for (let i = 0; i < len; i++) {
     const attr = n.attributes[i];
     if (!ignoreAttribute(tagName, attr.name, attr.value)) {
-      const value = (attributes[attr.name] = transformAttribute(
+      const value = transformAttribute(
         doc,
         tagName,
         toLowerCase(attr.name),
         attr.value,
-      ));
-
+      );
+      let { name } = attr;
       // save assets offline
-      if (value && onAssetDetected && isAttributeCapturable(n, attr.name)) {
+      if (
+        value &&
+        typeof value === 'string' &&
+        onAssetDetected &&
+        isAttributeCapturable(n, attr.name)
+      ) {
         if (attr.name === 'srcset') {
           assets.push(...getUrlsFromSrcset(value));
         } else {
           assets.push(value);
         }
+        name = `rr_captured_${name}`;
       }
+      attributes[name] = value;
     }
   }
   // remote css
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index e564bca7a8..3b0f1352a3 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -368,7 +368,6 @@ function record<T = eventWithTime>(
           href: window.location.href,
           width: getWindowWidth(),
           height: getWindowHeight(),
-          captureAssets,
         },
       },
       isCheckout,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 3238192a90..6c583fd320 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -625,13 +625,12 @@ export default class MutationBuffer {
 
         if (!ignoreAttribute(target.tagName, attributeName, value)) {
           // overwrite attribute if the mutations was triggered in same time
-          const transformedValue = (item.attributes[attributeName] =
-            transformAttribute(
-              this.doc,
-              toLowerCase(target.tagName),
-              toLowerCase(attributeName),
-              value,
-            ));
+          const transformedValue = transformAttribute(
+            this.doc,
+            toLowerCase(target.tagName),
+            toLowerCase(attributeName),
+            value,
+          );
           if (
             transformedValue &&
             this.assetManager.isAttributeCapturable(target, attributeName)
@@ -643,7 +642,9 @@ export default class MutationBuffer {
             } else {
               this.assetManager.capture(transformedValue);
             }
+            attributeName = `rr_captured_${attributeName}`;
           }
+          item.attributes[attributeName] = transformedValue;
 
           if (attributeName === 'style') {
             if (!this.unattachedDoc) {
diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index 478e63a712..21f381afe6 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -6,7 +6,7 @@ import type {
   captureAssetsParam,
 } from '@rrweb/types';
 import { deserializeArg } from '../canvas/deserialize-args';
-import { getSourcesFromSrcset, isAttributeCapturable } from 'rrweb-snapshot';
+import { getSourcesFromSrcset } from 'rrweb-snapshot';
 import type { RRElement } from 'rrdom';
 import { updateSrcset } from './update-srcset';
 
@@ -20,15 +20,10 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     string,
     Array<(status: RebuildAssetManagerFinalStatus) => void>
   > = new Map();
-  private config: captureAssetsParam | undefined;
   private liveMode: boolean;
 
-  constructor(
-    { liveMode }: { liveMode: boolean },
-    config?: captureAssetsParam | undefined,
-  ) {
+  constructor({ liveMode }: { liveMode: boolean }) {
     this.liveMode = liveMode;
-    this.config = config;
   }
 
   public async add(event: assetEvent) {
@@ -120,59 +115,20 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     };
   }
 
-  public isCapturable(
-    n: RRElement | Element,
-    attribute: string,
-    value: string,
-  ): boolean {
-    if (!isAttributeCapturable(n as Element, attribute)) return false;
-
-    if (attribute === 'srcset') {
-      return getSourcesFromSrcset(value).some((source) =>
-        this.isURLConfiguredForCapture(source),
-      );
-    } else {
-      return this.isURLConfiguredForCapture(value);
-    }
-  }
-
-  public isURLConfiguredForCapture(url: string): boolean {
-    if (url.startsWith('data:')) return false;
-
-    const { origins: cachedOrigins = false, objectURLs = false } =
-      this.config || {};
-    if (objectURLs && url.startsWith(`blob:`)) {
-      return true;
-    }
-
-    if (Array.isArray(cachedOrigins)) {
-      try {
-        const { origin } = new URL(url);
-        return cachedOrigins.some((o) => o === origin);
-      } catch {
-        return false;
-      }
-    }
-
-    return cachedOrigins;
-  }
-
   public async manageAttribute(
     node: RRElement | Element,
     nodeId: number,
     attribute: string,
+    newValue: string,
   ): Promise<unknown> {
-    const originalValue = node.getAttribute(attribute);
-    if (!originalValue || !this.isCapturable(node, attribute, originalValue))
-      return false;
+    const prevValue = node.getAttribute(attribute);
 
     const promises: Promise<unknown>[] = [];
 
     if (attribute === 'srcset') {
-      let expectedValue: string | void = originalValue;
-      const values = getSourcesFromSrcset(originalValue);
+      const values = getSourcesFromSrcset(newValue);
+      let expectedValue: string | undefined = prevValue;
       values.forEach((value) => {
-        if (!this.isURLConfiguredForCapture(value)) return;
         promises.push(
           this.whenReady(value).then((status) => {
             const isLoaded = status.status === 'loaded';
@@ -182,8 +138,16 @@ export default class AssetManager implements RebuildAssetManagerInterface {
               node.getAttribute(attribute) === expectedValue;
 
             if (!attributeUnchanged) return; // attribute was changed since we started loading the asset
-
-            expectedValue = updateSrcset(node, value, status.url);
+            if (!expectedValue) {
+              // before srcset has been set for the first time
+              expectedValue = newValue;
+            }
+            expectedValue = updateSrcset(
+              node,
+              value,
+              status.url,
+              expectedValue,
+            );
           }),
         );
       });
@@ -195,24 +159,23 @@ export default class AssetManager implements RebuildAssetManagerInterface {
           hijackedAttributes = new Map();
           this.nodeIdAttributeHijackedMap.set(nodeId, hijackedAttributes);
         }
-        hijackedAttributes.set(attribute, originalValue);
+        hijackedAttributes.set(attribute, newValue);
         if (node.tagName === 'IMG' && attribute === 'src') {
           // special value to prevent a broken image icon while asset is being loaded
           node.setAttribute('src', '//:0');
         } else {
-          node.removeAttribute(attribute);
+          node.setAttribute('src', '/\\\:0');
         }
       }
-
       promises.push(
-        this.whenReady(originalValue).then((status) => {
+        this.whenReady(newValue).then((status) => {
           const isLoaded = status.status === 'loaded';
           if (!isLoaded) return; // failed to load asset
 
           const attributeUnchanged = this.liveMode
-            ? originalValue ===
+            ? newValue ===
               this.nodeIdAttributeHijackedMap.get(nodeId)?.get(attribute)
-            : node.getAttribute(attribute) === originalValue;
+            : node.getAttribute(attribute) === prevValue;
 
           if (!attributeUnchanged) return; // attribute was changed since we started loading the asset
 
@@ -224,8 +187,7 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     return Promise.all(promises);
   }
 
-  public reset(config?: captureAssetsParam | undefined): void {
-    this.config = config;
+  public reset(): void {
     this.originalToObjectURLMap.forEach((objectURL) => {
       URL.revokeObjectURL(objectURL);
     });
diff --git a/packages/rrweb/src/replay/asset-manager/update-srcset.ts b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
index 1fcbbdadf4..d8815b8ea0 100644
--- a/packages/rrweb/src/replay/asset-manager/update-srcset.ts
+++ b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
@@ -4,9 +4,12 @@ export function updateSrcset(
   node: Element | RRElement,
   urlToReplace: string,
   newURL: string,
-): string | void {
-  const srcset = node.getAttribute('srcset');
-  if (!srcset) return;
+  expectedValue?: string,
+): string | undefined {
+  if (typeof expectedValue === 'undefined') {
+    expectedValue = node.getAttribute('srcset');
+    if (!expectedValue) return undefined;
+  }
 
   // from https://stackoverflow.com/a/6969486/543604
   const escapedUrlToReplace = urlToReplace.replace(
@@ -14,7 +17,7 @@ export function updateSrcset(
     '\\$&',
   );
   const matcher = new RegExp(`(?<=^|[\\s,])${escapedUrlToReplace}(?=[\\s,]|$)`);
-  const newSrcset = srcset.replace(matcher, newURL);
+  const newSrcset = expectedValue.replace(matcher, newURL);
 
   node.setAttribute('srcset', newSrcset);
 
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index fc62a8d88e..d9804430fd 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -393,9 +393,7 @@ export class Replayer {
       (e) => e.type === EventType.FullSnapshot,
     );
     if (firstMeta) {
-      const { width, height, captureAssets } =
-        firstMeta.data as metaEvent['data'];
-      this.assetManager.reset(captureAssets);
+      const { width, height } = firstMeta.data as metaEvent['data'];
       setTimeout(() => {
         this.emitter.emit(ReplayerEvents.Resize, {
           width,
@@ -678,7 +676,6 @@ export class Replayer {
         break;
       case EventType.Meta:
         castFn = () => {
-          this.assetManager.reset(event.data.captureAssets);
           this.emitter.emit(ReplayerEvents.Resize, {
             width: event.data.width,
             height: event.data.height,
@@ -1819,13 +1816,15 @@ export class Replayer {
                 if (tn) {
                   textarea.appendChild(tn as TNode);
                 }
-              } else {
-                targetEl.setAttribute(attributeName, value);
+              } else if (attributeName.startsWith('rr_captured_') && value) {
                 void this.assetManager.manageAttribute(
                   targetEl,
                   mutation.id,
-                  attributeName,
+                  attributeName.substring('rr_captured_'.length),
+                  value,
                 );
+              } else {
+                targetEl.setAttribute(attributeName, value);
               }
               void this.assetManager.manageAttribute(targetEl, attributeName);
             } catch (error) {
diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 25477d9b71..90ff20bffd 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -15,11 +15,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": true
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -365,7 +361,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 21,
@@ -409,11 +405,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": true
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -818,7 +810,7 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 27,
@@ -862,11 +854,7 @@ exports[`record integration tests [DEPRECATED] should record images with blob ur
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": true
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1045,7 +1033,7 @@ exports[`record integration tests [DEPRECATED] should record images with blob ur
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"id\\": 24
@@ -1088,11 +1076,7 @@ exports[`record integration tests can correctly serialize a shader and multiple
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1506,11 +1490,7 @@ exports[`record integration tests can freeze mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1690,11 +1670,7 @@ exports[`record integration tests can mask character data mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1912,11 +1888,7 @@ exports[`record integration tests can mask character data mutations with regexp
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2138,11 +2110,7 @@ exports[`record integration tests can record attribute mutation 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2313,11 +2281,7 @@ exports[`record integration tests can record character data muatations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2495,11 +2459,7 @@ exports[`record integration tests can record childList mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2675,11 +2635,7 @@ exports[`record integration tests can record clicks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2961,11 +2917,7 @@ exports[`record integration tests can record form interactions 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3748,11 +3700,7 @@ exports[`record integration tests can record node mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -4727,11 +4675,7 @@ exports[`record integration tests can record style changes compactly and preserv
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -4957,11 +4901,7 @@ exports[`record integration tests can record textarea mutations correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -5225,11 +5165,7 @@ exports[`record integration tests can use maskInputOptions to configure which ty
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -6100,11 +6036,7 @@ exports[`record integration tests handles null attribute values 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -6295,11 +6227,7 @@ exports[`record integration tests mutations should work when blocked class is un
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -7064,11 +6992,7 @@ exports[`record integration tests should handle recursive console messages 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -7248,11 +7172,7 @@ exports[`record integration tests should mask inputs via function call 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -8122,11 +8042,7 @@ exports[`record integration tests should mask password value attribute with mask
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -8573,11 +8489,7 @@ exports[`record integration tests should mask texts 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -8875,11 +8787,7 @@ exports[`record integration tests should mask texts using maskTextFn 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -9177,11 +9085,7 @@ exports[`record integration tests should nest record iframe 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -9940,11 +9844,7 @@ exports[`record integration tests should not record blocked elements and its chi
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -10124,11 +10024,7 @@ exports[`record integration tests should not record blocked elements dynamically
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -10334,11 +10230,7 @@ exports[`record integration tests should not record input events on ignored elem
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -10749,11 +10641,7 @@ exports[`record integration tests should not record input values if dynamically
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -10986,11 +10874,7 @@ exports[`record integration tests should not record input values if maskAllInput
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -11860,11 +11744,7 @@ exports[`record integration tests should record DOM node movement 1 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -12131,11 +12011,7 @@ exports[`record integration tests should record DOM node movement 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -12409,11 +12285,7 @@ exports[`record integration tests should record after DOMContentLoaded event 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -12500,11 +12372,7 @@ exports[`record integration tests should record canvas mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -12723,11 +12591,7 @@ exports[`record integration tests should record console messages 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -13233,11 +13097,7 @@ exports[`record integration tests should record dynamic CSS changes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -13634,11 +13494,7 @@ exports[`record integration tests should record images inside iframe with blob u
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -13968,7 +13824,7 @@ exports[`record integration tests should record images inside iframe with blob u
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 21,
@@ -14012,11 +13868,7 @@ exports[`record integration tests should record images inside iframe with blob u
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -14421,7 +14273,7 @@ exports[`record integration tests should record images inside iframe with blob u
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"rootId\\": 27,
@@ -14465,11 +14317,7 @@ exports[`record integration tests should record images with blob url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -14648,7 +14496,7 @@ exports[`record integration tests should record images with blob url 1`] = `
             \\"type\\": 2,
             \\"tagName\\": \\"img\\",
             \\"attributes\\": {
-              \\"src\\": \\"blob:http://localhost:xxxx/...\\"
+              \\"rr_captured_src\\": \\"blob:http://localhost:xxxx/...\\"
             },
             \\"childNodes\\": [],
             \\"id\\": 24
@@ -14691,11 +14539,7 @@ exports[`record integration tests should record input userTriggered values if us
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -15595,11 +15439,7 @@ exports[`record integration tests should record moved shadow DOM 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -15768,11 +15608,7 @@ exports[`record integration tests should record moved shadow DOM 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -15955,11 +15791,7 @@ exports[`record integration tests should record mutations in iframes accross pag
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -16268,11 +16100,7 @@ exports[`record integration tests should record nested iframes and shadow doms 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -16619,11 +16447,7 @@ exports[`record integration tests should record shadow DOM 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -17073,11 +16897,7 @@ exports[`record integration tests should record shadow DOM 2 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -17195,11 +17015,7 @@ exports[`record integration tests should record shadow DOM 3 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -17317,11 +17133,7 @@ exports[`record integration tests should record shadow doms polyfilled by shadyd
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -17567,11 +17379,7 @@ exports[`record integration tests should record shadow doms polyfilled by synthe
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -17845,11 +17653,7 @@ exports[`record integration tests should record webgl canvas mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -18064,11 +17868,7 @@ exports[`record integration tests should unmask texts using maskTextFn 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -18366,11 +18166,7 @@ exports[`record integration tests will serialize node before record 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap
index 01a3796d2a..cb40f3328d 100644
--- a/packages/rrweb/test/__snapshots__/record.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap
@@ -7,11 +7,7 @@ exports[`record aggregates mutations 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -97,11 +93,7 @@ exports[`record can add custom event 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -195,11 +187,7 @@ exports[`record captures CORS stylesheets that are still loading 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -302,11 +290,7 @@ exports[`record captures adopted stylesheets in nested shadow doms and iframes 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -772,11 +756,7 @@ exports[`record captures adopted stylesheets in shadow doms and iframe 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1172,11 +1152,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1276,11 +1252,7 @@ exports[`record captures adopted stylesheets of shadow doms in checkout full sna
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1385,11 +1357,7 @@ exports[`record captures inserted style text nodes correctly 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1519,11 +1487,7 @@ exports[`record captures mutations on adopted stylesheets 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1823,11 +1787,7 @@ exports[`record captures nested stylesheet rules 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -1976,11 +1936,7 @@ exports[`record captures style property changes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2109,11 +2065,7 @@ exports[`record captures stylesheet rules 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2251,11 +2203,7 @@ exports[`record captures stylesheets in iframes with \`blob:\` url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2399,11 +2347,7 @@ exports[`record captures stylesheets with \`blob:\` url 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2491,11 +2435,7 @@ exports[`record iframes captures stylesheet mutations in iframes 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2730,11 +2670,7 @@ exports[`record is safe to checkout during async callbacks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2865,11 +2801,7 @@ exports[`record is safe to checkout during async callbacks 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2994,11 +2926,7 @@ exports[`record loading stylesheets captures stylesheets in iframes that are sti
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3334,11 +3262,7 @@ exports[`record loading stylesheets captures stylesheets that are still loading
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3518,11 +3442,7 @@ exports[`record no need for attribute mutations on adds 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3642,11 +3562,7 @@ exports[`record should record scroll position 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3766,11 +3682,7 @@ exports[`record without CSSGroupingRule support captures nested stylesheet rules
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
diff --git a/packages/rrweb/test/events/assets-mutation.ts b/packages/rrweb/test/events/assets-mutation.ts
index dda8274f84..db9eabafee 100644
--- a/packages/rrweb/test/events/assets-mutation.ts
+++ b/packages/rrweb/test/events/assets-mutation.ts
@@ -7,10 +7,6 @@ const events: eventWithTime[] = [
       href: '',
       width: 1600,
       height: 900,
-      captureAssets: {
-        origins: ['ftp://example.com'],
-        objectURLs: false,
-      },
     },
     timestamp: 1636379531385,
   },
@@ -113,7 +109,7 @@ const events: eventWithTime[] = [
         {
           id: 16,
           attributes: {
-            src: 'ftp://example.com/image.png',
+            rr_captured_src: 'ftp://example.com/image.png',
           },
         },
       ],
diff --git a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
index b6bb0b9992..f4e7c90796 100644
--- a/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
+++ b/packages/rrweb/test/events/assets-src-changed-before-asset-loaded.ts
@@ -8,10 +8,6 @@ const events: eventWithTime[] = [
       href: '',
       width: 1600,
       height: 900,
-      captureAssets: {
-        origins: ['ftp://example.com'],
-        objectURLs: false,
-      },
     },
     timestamp: 100000000,
   },
@@ -77,7 +73,7 @@ const events: eventWithTime[] = [
                       width: '100',
                       height: '100',
                       style: 'border: 1px solid #000000',
-                      src: 'ftp://example.com/red.png',
+                      rr_captured_src: 'ftp://example.com/red.png',
                     },
                     childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
                     id: 16,
@@ -116,7 +112,7 @@ const events: eventWithTime[] = [
         {
           id: 16,
           attributes: {
-            src: 'ftp://example.com/robot.png',
+            rr_captured_src: 'ftp://example.com/robot.png',
           },
         },
       ],
diff --git a/packages/rrweb/test/events/assets.ts b/packages/rrweb/test/events/assets.ts
index b7bc9d516d..c345ed380b 100644
--- a/packages/rrweb/test/events/assets.ts
+++ b/packages/rrweb/test/events/assets.ts
@@ -7,10 +7,6 @@ const events: eventWithTime[] = [
       href: '',
       width: 1600,
       height: 900,
-      captureAssets: {
-        origins: ['ftp://example.com'],
-        objectURLs: false,
-      },
     },
     timestamp: 1636379531385,
   },
@@ -76,7 +72,7 @@ const events: eventWithTime[] = [
                       width: '100',
                       height: '100',
                       style: 'border: 1px solid #000000',
-                      src: 'ftp://example.com/image.png',
+                      rr_captured_src: 'ftp://example.com/image.png',
                     },
                     childNodes: [{ type: 3, textContent: '\n    ', id: 17 }],
                     id: 16,
diff --git a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
index 1dfc3d7e72..74036c6add 100644
--- a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
@@ -7,11 +7,7 @@ exports[`cross origin iframes audio.html should emit contents of iframe once 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -284,7 +280,7 @@ exports[`cross origin iframes audio.html should emit contents of iframe once 1`]
                             \\"type\\": 2,
                             \\"tagName\\": \\"source\\",
                             \\"attributes\\": {
-                              \\"src\\": \\"http://localhost:3030/html/assets/1-minute-of-silence.mp3\\",
+                              \\"rr_captured_src\\": \\"http://localhost:3030/html/assets/1-minute-of-silence.mp3\\",
                               \\"type\\": \\"audio/mpeg\\"
                             },
                             \\"childNodes\\": [],
@@ -349,11 +345,7 @@ exports[`cross origin iframes blank.html should filter out forwarded cross origi
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -615,11 +607,7 @@ exports[`cross origin iframes blank.html should record same-origin iframe in cro
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -863,11 +851,7 @@ exports[`cross origin iframes blank.html should support packFn option in record(
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     },
     \\"v\\": \\"v1\\"
   },
@@ -1036,11 +1020,7 @@ exports[`cross origin iframes form.html should map input events correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2053,11 +2033,7 @@ exports[`cross origin iframes form.html should map scroll events correctly 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -2713,11 +2689,7 @@ exports[`cross origin iframes move-node.html captures mutations on adopted style
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3131,11 +3103,7 @@ exports[`cross origin iframes move-node.html captures mutations on stylesheets 1
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3554,11 +3522,7 @@ exports[`cross origin iframes move-node.html should record DOM attribute changes
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -3846,11 +3810,7 @@ exports[`cross origin iframes move-node.html should record DOM node movement 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -4235,11 +4195,7 @@ exports[`cross origin iframes move-node.html should record DOM node removal 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -4525,11 +4481,7 @@ exports[`cross origin iframes move-node.html should record DOM text changes 1`]
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -4815,11 +4767,7 @@ exports[`cross origin iframes move-node.html should record canvas elements 1`] =
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -5142,11 +5090,7 @@ exports[`cross origin iframes move-node.html should record custom events 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -5428,11 +5372,7 @@ exports[`same origin iframes should emit contents of iframe once 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
diff --git a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
index 7dfc3d0155..c7aeb17e1c 100644
--- a/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/webgl.test.ts.snap
@@ -7,11 +7,7 @@ exports[`record webgl recordCanvas FPS should record snapshots 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -170,11 +166,7 @@ exports[`record webgl should batch events by RAF 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -319,11 +311,7 @@ exports[`record webgl will record changes to a canvas element 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -416,11 +404,7 @@ exports[`record webgl will record changes to a canvas element before the canvas
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -548,11 +532,7 @@ exports[`record webgl will record changes to a canvas element before the canvas
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -680,11 +660,7 @@ exports[`record webgl will record changes to a webgl2 canvas element 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -777,11 +753,7 @@ exports[`record webgl will record webgl variables 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
@@ -894,11 +866,7 @@ exports[`record webgl will record webgl variables in reverse order 1`] = `
     \\"data\\": {
       \\"href\\": \\"about:blank\\",
       \\"width\\": 1920,
-      \\"height\\": 1080,
-      \\"captureAssets\\": {
-        \\"objectURLs\\": true,
-        \\"origins\\": false
-      }
+      \\"height\\": 1080
     }
   },
   {
diff --git a/packages/rrweb/test/replay/asset-unit.test.ts b/packages/rrweb/test/replay/asset-unit.test.ts
index 0c99244d37..00e746448e 100644
--- a/packages/rrweb/test/replay/asset-unit.test.ts
+++ b/packages/rrweb/test/replay/asset-unit.test.ts
@@ -34,13 +34,7 @@ describe('AssetManager', () => {
   });
 
   beforeEach(() => {
-    assetManager = new AssetManager(
-      { liveMode: false },
-      {
-        origins: true,
-        objectURLs: true,
-      },
-    );
+    assetManager = new AssetManager({ liveMode: false });
   });
 
   afterEach(() => {
@@ -153,139 +147,6 @@ describe('AssetManager', () => {
     await expect(promise).resolves.toEqual({ status: 'reset' });
   });
 
-  const validOriginCombinations: Array<
-    [captureAssetsParam['origins'], string[]]
-  > = [
-    [['http://example.com'], ['http://example.com/image.png']],
-    [['https://example.com'], ['https://example.com/image.png']],
-    [['https://example.com:80'], ['https://example.com:80/cgi-bin/image.png']],
-    [true, ['https://example.com:80/cgi-bin/image.png']],
-  ];
-
-  const invalidOriginCombinations: Array<
-    [captureAssetsParam['origins'], string[]]
-  > = [
-    [['http://example.com'], ['https://example.com/image.png']],
-    [['https://example.com'], ['https://example.org/image.png']],
-    [['https://example.com:80'], ['https://example.com:81/image.png']],
-    [false, ['https://example.com:81/image.png']],
-  ];
-
-  validOriginCombinations.forEach(([origin, urls]) => {
-    const assetManager = new AssetManager(
-      { liveMode: false },
-      {
-        origins: origin,
-        objectURLs: false,
-      },
-    );
-    urls.forEach((url) => {
-      it(`should correctly identify ${url} as capturable for origin ${origin}`, () => {
-        expect(assetManager.isURLConfiguredForCapture(url)).toBe(true);
-      });
-    });
-  });
-
-  invalidOriginCombinations.forEach(([origin, urls]) => {
-    const assetManager = new AssetManager(
-      { liveMode: false },
-      {
-        origins: origin,
-        objectURLs: false,
-      },
-    );
-    urls.forEach((url) => {
-      it(`should correctly identify ${url} as NOT capturable for origin ${origin}`, () => {
-        expect(assetManager.isURLConfiguredForCapture(url)).toBe(false);
-      });
-    });
-  });
-
-  const validCombinations: Array<{
-    origins: captureAssetsParam['origins'];
-    tagName: string;
-    attribute: string;
-    value: string;
-  }> = [
-    {
-      origins: ['http://example.com'],
-      tagName: 'IMG',
-      attribute: 'src',
-      value: 'http://example.com/image.png',
-    },
-    {
-      origins: ['https://example.com'],
-      tagName: 'IMG',
-      attribute: 'srcset',
-      value:
-        'https://example.com/image.png x2, https://example.com/image2.png x3',
-    },
-  ];
-
-  const invalidCombinations: Array<{
-    origins: captureAssetsParam['origins'];
-    tagName: string;
-    attribute: string;
-    value: string;
-  }> = [
-    {
-      origins: ['http://example.com'],
-      tagName: 'IMG',
-      attribute: 'src',
-      value: 'http://google.com/image.png',
-    },
-    {
-      origins: ['https://example.com'],
-      tagName: 'IMG',
-      attribute: 'href',
-      value: 'https://example.com/image.png',
-    },
-    {
-      origins: ['https://duckduckgo.com'],
-      tagName: 'IMG',
-      attribute: 'srcset',
-      value:
-        'https://example.com/image.png x2, https://example.com/image2.png x3',
-    },
-    {
-      origins: false,
-      tagName: 'IMG',
-      attribute: 'srcset',
-      value:
-        'https://example.com/image.png x2, https://example.com/image2.png x3',
-    },
-  ];
-
-  validCombinations.forEach(({ origins, tagName, attribute, value }) => {
-    const element = document.createElement(tagName);
-    element.setAttribute(attribute, value);
-    const assetManager = new AssetManager(
-      { liveMode: false },
-      {
-        origins,
-        objectURLs: false,
-      },
-    );
-    it(`should correctly identify <${element} ${attribute}=${value} /> as capturable for origins ${origins}`, () => {
-      expect(assetManager.isCapturable(element, attribute, value)).toBe(true);
-    });
-  });
-
-  invalidCombinations.forEach(({ origins, tagName, attribute, value }) => {
-    const element = document.createElement(tagName);
-    element.setAttribute(attribute, value);
-    const assetManager = new AssetManager(
-      { liveMode: false },
-      {
-        origins,
-        objectURLs: false,
-      },
-    );
-    it(`should correctly identify <${element} ${attribute}=${value} /> as NOT capturable for origins ${origins}`, () => {
-      expect(assetManager.isCapturable(element, attribute, value)).toBe(false);
-    });
-  });
-
   it("should be able to modify a node's attribute once asset is loaded", async () => {
     const url = 'https://example.com/image.png';
     const event: assetEvent = {
@@ -298,9 +159,8 @@ describe('AssetManager', () => {
     jest.spyOn(URL, 'createObjectURL').mockReturnValue('objectURL');
 
     const element = document.createElement('img');
-    element.setAttribute('src', url);
 
-    const promise = assetManager.manageAttribute(element, 1, 'src');
+    const promise = assetManager.manageAttribute(element, 1, 'src', url);
 
     await assetManager.add(event);
     await promise;
@@ -321,9 +181,8 @@ describe('AssetManager', () => {
     await assetManager.add(event);
 
     const element = document.createElement('img');
-    element.setAttribute('src', url);
 
-    await assetManager.manageAttribute(element, 1, 'src');
+    await assetManager.manageAttribute(element, 1, 'src', url);
 
     expect(element.getAttribute('src')).toBe('objectURL');
   });
@@ -341,9 +200,8 @@ describe('AssetManager', () => {
     await assetManager.add(event);
 
     const element = document.createElement('img');
-    element.setAttribute('srcset', url);
 
-    await assetManager.manageAttribute(element, 1, 'srcset');
+    await assetManager.manageAttribute(element, 1, 'srcset', url);
 
     expect(element.getAttribute('srcset')).toBe('objectURL');
   });
@@ -361,9 +219,9 @@ describe('AssetManager', () => {
     await assetManager.add(event);
 
     const element = document.createElement('img');
-    element.setAttribute('srcset', `${url} x2, ${url}?x3 x3`);
+    const value = `${url} x2, ${url}?x3 x3`;
 
-    void assetManager.manageAttribute(element, 1, 'srcset');
+    void assetManager.manageAttribute(element, 1, 'srcset', value);
     await assetManager.whenReady(url);
 
     expect(element.getAttribute('srcset')).toBe(`objectURL x2, ${url}?x3 x3`);
@@ -373,7 +231,6 @@ describe('AssetManager', () => {
     const url = 'https://example.com/image.png';
     const url2 = `${url}?x3`;
     const element = document.createElement('img');
-    element.setAttribute('srcset', `${url} x2, ${url2} x3`);
 
     jest
       .spyOn(URL, 'createObjectURL')
@@ -387,7 +244,12 @@ describe('AssetManager', () => {
       },
     });
 
-    void assetManager.manageAttribute(element, 1, 'srcset');
+    void assetManager.manageAttribute(
+      element,
+      1,
+      'srcset',
+      `${url} x2, ${url2} x3`,
+    );
     await assetManager.whenReady(url);
 
     expect(element.getAttribute('srcset')).toBe(`objectURL1 x2, ${url2} x3`);
@@ -422,30 +284,22 @@ describe('AssetManager', () => {
       'http://www.w3.org/2000/svg',
       'feImage',
     );
-    feImage.setAttribute('href', url);
 
-    await assetManager.manageAttribute(feImage, 1, 'href');
+    await assetManager.manageAttribute(feImage, 1, 'href', url);
 
     expect(feImage.getAttribute('href')).toBe('objectURL');
   });
 
   describe('live mode', () => {
     beforeEach(() => {
-      assetManager = new AssetManager(
-        { liveMode: true },
-        {
-          origins: true,
-          objectURLs: true,
-        },
-      );
+      assetManager = new AssetManager({ liveMode: true });
     });
 
     it("should remove a node's attribute while asset is being loaded", async () => {
       const url = 'https://example.com/image.png';
       const element = document.createElement('embed');
-      element.setAttribute('src', url);
 
-      void assetManager.manageAttribute(element, 1, 'src');
+      void assetManager.manageAttribute(element, 1, 'src', url);
 
       expect(element.getAttribute('src')).toBeNull();
     });
@@ -453,9 +307,8 @@ describe('AssetManager', () => {
     it("should set an image's src attribute to //:0 to prevent a broken image icon while asset is being loaded", async () => {
       const url = 'https://example.com/image.png';
       const element = document.createElement('img');
-      element.setAttribute('src', url);
 
-      void assetManager.manageAttribute(element, 1, 'src');
+      void assetManager.manageAttribute(element, 1, 'src', url);
 
       expect(element.getAttribute('src')).toBe('//:0');
     });
@@ -484,11 +337,11 @@ describe('AssetManager', () => {
       const promises: Promise<unknown>[] = [];
 
       const element = document.createElement('img');
-      element.setAttribute('src', originalUrl);
-      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+      promises.push(
+        assetManager.manageAttribute(element, 1, 'src', originalUrl),
+      );
 
-      element.setAttribute('src', newUrl);
-      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+      promises.push(assetManager.manageAttribute(element, 1, 'src', newUrl));
 
       await assetManager.add(newAsset);
       await assetManager.add(originalAsset);
@@ -521,11 +374,11 @@ describe('AssetManager', () => {
       const promises: Promise<unknown>[] = [];
 
       const element = document.createElement('img');
-      element.setAttribute('src', originalUrl);
-      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+      promises.push(
+        assetManager.manageAttribute(element, 1, 'src', originalUrl),
+      );
 
-      element.setAttribute('src', newUrl);
-      promises.push(assetManager.manageAttribute(element, 1, 'src'));
+      promises.push(assetManager.manageAttribute(element, 1, 'src', newUrl));
 
       await assetManager.add(originalAsset);
       await assetManager.add(newAsset);
diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index a263edbd1f..2d45277b9f 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -246,6 +246,7 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
 function stripBlobURLsFromAttributes(node: {
   attributes: {
     src?: string;
+    rr_captured_src?: string;
   };
 }) {
   if (
@@ -258,6 +259,16 @@ function stripBlobURLsFromAttributes(node: {
       .replace(/[\w-]+$/, '...')
       .replace(/:[0-9]+\//, ':xxxx/');
   }
+  if (
+    'rr_captured_src' in node.attributes &&
+    node.attributes.rr_captured_src &&
+    typeof node.attributes.rr_captured_src === 'string' &&
+    node.attributes.rr_captured_src.startsWith('blob:')
+  ) {
+    node.attributes.rr_captured_src = node.attributes.rr_captured_src
+      .replace(/[\w-]+$/, '...')
+      .replace(/:[0-9]+\//, ':xxxx/');
+  }
 }
 
 function stringifyDomSnapshot(mhtml: string): string {
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index ad1d7a235a..debfb22788 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -41,7 +41,6 @@ export type metaEvent = {
     href: string;
     width: number;
     height: number;
-    captureAssets?: captureAssetsParam;
   };
 };
 
@@ -769,9 +768,12 @@ export declare abstract class RebuildAssetManagerInterface {
   abstract get(url: string): RebuildAssetManagerStatus;
   abstract whenReady(url: string): Promise<RebuildAssetManagerFinalStatus>;
   abstract reset(config?: captureAssetsParam | undefined): void;
-  abstract isCapturable(n: Element, attribute: string, value: string): boolean;
-  abstract isURLConfiguredForCapture(url: string): boolean;
-  abstract manageAttribute(n: Element, id: number, attribute: string): void;
+  abstract manageAttribute(
+    n: Element,
+    id: number,
+    attribute: string,
+    originalValue: string,
+  ): void;
 }
 
 export enum NodeType {

From 7806c45b6da983835b152c06588136005e410e56 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Wed, 27 Mar 2024 14:12:41 +0000
Subject: [PATCH 088/102] Revert attributes in non-live mode and ensure this
 happens when we can determine that Assets won't be arriving later

---
 .../rrweb/src/replay/asset-manager/index.ts   | 31 ++++++++++--
 .../src/replay/asset-manager/update-srcset.ts |  7 +--
 packages/rrweb/src/replay/index.ts            | 10 +++-
 packages/rrweb/test/record/asset.test.ts      | 49 -------------------
 4 files changed, 38 insertions(+), 59 deletions(-)

diff --git a/packages/rrweb/src/replay/asset-manager/index.ts b/packages/rrweb/src/replay/asset-manager/index.ts
index 21f381afe6..19a2ff6e57 100644
--- a/packages/rrweb/src/replay/asset-manager/index.ts
+++ b/packages/rrweb/src/replay/asset-manager/index.ts
@@ -21,9 +21,11 @@ export default class AssetManager implements RebuildAssetManagerInterface {
     Array<(status: RebuildAssetManagerFinalStatus) => void>
   > = new Map();
   private liveMode: boolean;
+  public allAdded: boolean;
 
   constructor({ liveMode }: { liveMode: boolean }) {
     this.liveMode = liveMode;
+    this.allAdded = false;
   }
 
   public async add(event: assetEvent) {
@@ -74,6 +76,14 @@ export default class AssetManager implements RebuildAssetManagerInterface {
       currentStatus.status === 'failed'
     ) {
       return currentStatus;
+    } else if (
+      currentStatus.status === 'unknown' &&
+      this.allAdded &&
+      !this.liveMode
+    ) {
+      return {
+        status: 'failed',
+      };
     }
     let resolve: (status: RebuildAssetManagerFinalStatus) => void;
     const promise = new Promise<RebuildAssetManagerFinalStatus>((r) => {
@@ -127,12 +137,18 @@ export default class AssetManager implements RebuildAssetManagerInterface {
 
     if (attribute === 'srcset') {
       const values = getSourcesFromSrcset(newValue);
-      let expectedValue: string | undefined = prevValue;
+      let expectedValue: string | null = prevValue;
       values.forEach((value) => {
         promises.push(
           this.whenReady(value).then((status) => {
             const isLoaded = status.status === 'loaded';
-            if (!isLoaded) return; // failed to load asset
+            if (!isLoaded) {
+              if (!this.liveMode) {
+                // failed to load asset, revert to recorded value
+                node.setAttribute(attribute, newValue);
+              }
+              return; // failed to load asset
+            }
 
             const attributeUnchanged =
               node.getAttribute(attribute) === expectedValue;
@@ -163,14 +179,18 @@ export default class AssetManager implements RebuildAssetManagerInterface {
         if (node.tagName === 'IMG' && attribute === 'src') {
           // special value to prevent a broken image icon while asset is being loaded
           node.setAttribute('src', '//:0');
-        } else {
-          node.setAttribute('src', '/\\\:0');
         }
       }
       promises.push(
         this.whenReady(newValue).then((status) => {
           const isLoaded = status.status === 'loaded';
-          if (!isLoaded) return; // failed to load asset
+          if (!isLoaded) {
+            if (!this.liveMode) {
+              // failed to load asset, revert to recorded value
+              node.setAttribute(attribute, newValue);
+            }
+            return;
+          }
 
           const attributeUnchanged = this.liveMode
             ? newValue ===
@@ -202,5 +222,6 @@ export default class AssetManager implements RebuildAssetManagerInterface {
       }
     });
     this.callbackMap.clear();
+    this.allAdded = false;
   }
 }
diff --git a/packages/rrweb/src/replay/asset-manager/update-srcset.ts b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
index d8815b8ea0..6f39cedb85 100644
--- a/packages/rrweb/src/replay/asset-manager/update-srcset.ts
+++ b/packages/rrweb/src/replay/asset-manager/update-srcset.ts
@@ -5,10 +5,11 @@ export function updateSrcset(
   urlToReplace: string,
   newURL: string,
   expectedValue?: string,
-): string | undefined {
+): string | null {
   if (typeof expectedValue === 'undefined') {
-    expectedValue = node.getAttribute('srcset');
-    if (!expectedValue) return undefined;
+    let srcsetValue = node.getAttribute('srcset');
+    if (!srcsetValue) return null;
+    expectedValue = srcsetValue;
   }
 
   // from https://stackoverflow.com/a/6969486/543604
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index d9804430fd..754e25d2fc 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -828,7 +828,7 @@ export class Replayer {
       }
     };
 
-    void this.preloadAllAssets(event.timestamp);
+    void this.preloadAllAssets(event.timestamp, this.config.liveMode);
 
     /**
      * Normally rebuilding full snapshot should not be under virtual dom environment.
@@ -1041,7 +1041,10 @@ export class Replayer {
   /**
    * Process all asset events and preload them
    */
-  private async preloadAllAssets(timestamp: number): Promise<void[]> {
+  private async preloadAllAssets(
+    timestamp: number,
+    liveMode: boolean,
+  ): Promise<void[]> {
     const promises: Promise<void>[] = [];
     for (const event of this.service.state.context.events) {
       if (event.timestamp <= timestamp) continue;
@@ -1050,6 +1053,9 @@ export class Replayer {
         promises.push(this.assetManager.add(event));
       }
     }
+    if (!liveMode) {
+      this.assetManager.allAdded = true;
+    }
     return Promise.all(promises);
   }
 
diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index f6c41d75ca..cf31e77e83 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -475,30 +475,6 @@ describe('asset caching', function (this: ISuite) {
       },
     );
 
-    it('adds captureAssets config to meta event', async () => {
-      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
-      await waitForRAF(ctx.page);
-
-      const events = await ctx.page?.evaluate(
-        () => (window as unknown as IWindow).snapshots,
-      );
-
-      expect(events).toContainEqual(
-        expect.objectContaining({
-          type: EventType.Meta,
-          data: {
-            href: expect.any(String),
-            width: expect.any(Number),
-            height: expect.any(Number),
-            captureAssets: {
-              origins: true,
-              objectURLs: false,
-            },
-          },
-        }),
-      );
-    });
-
     it('capture all urls', async () => {
       await ctx.page.waitForNetworkIdle({ idleTime: 100 });
       await waitForRAF(ctx.page);
@@ -689,30 +665,5 @@ describe('asset caching', function (this: ISuite) {
         }),
       );
     });
-
-    it('add recorded origins to meta event', async () => {
-      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
-      await waitForRAF(ctx.page);
-
-      const events = await ctx.page?.evaluate(
-        () => (window as unknown as IWindow).snapshots,
-      );
-
-      // expect an event to be emitted with `event.type` === EventType.Asset
-      expect(events).toContainEqual(
-        expect.objectContaining({
-          type: EventType.Meta,
-          data: {
-            href: expect.any(String),
-            width: expect.any(Number),
-            height: expect.any(Number),
-            captureAssets: {
-              origins: [ctx.serverURL],
-              objectURLs: false,
-            },
-          },
-        }),
-      );
-    });
   });
 });

From b22ed450b0a616cb661dfea1c1bf872916b852e2 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Wed, 27 Mar 2024 15:37:17 +0000
Subject: [PATCH 089/102] Only record certain iframes as assets as we don't
 want to override the existing nested iframe mechanism

---
 packages/rrweb-snapshot/src/utils.ts             | 12 +++++++++++-
 packages/rrweb-snapshot/test/utils.test.ts       |  1 -
 .../test/__snapshots__/integration.test.ts.snap  | 16 ----------------
 3 files changed, 11 insertions(+), 18 deletions(-)

diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 4da1649acb..b80f016ad3 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -384,7 +384,6 @@ export const CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['SOURCE', new Set(['src'])],
   ['TRACK', new Set(['src'])],
   ['INPUT', new Set(['src'])],
-  ['IFRAME', new Set(['src'])],
   ['OBJECT', new Set(['src'])],
   ['BODY', new Set(['background'])],
   ['TABLE', new Set(['background'])],
@@ -399,6 +398,17 @@ export const CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
 ]);
 
 export function isAttributeCapturable(n: Element, attribute: string): boolean {
+  if (n.nodeName === 'IFRAME' && attribute == 'src') {
+    const i = n as HTMLIFrameElement;
+    return (
+      !i.contentDocument &&
+      (i.src.endsWith('.pdf') ||
+        i.src.endsWith('.jpeg') ||
+        i.src.endsWith('.jpg') ||
+        i.src.endsWith('.png') ||
+        i.src.endsWith('.webp'))
+    );
+  }
   const acceptedAttributesSet = CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
     n.nodeName,
   );
diff --git a/packages/rrweb-snapshot/test/utils.test.ts b/packages/rrweb-snapshot/test/utils.test.ts
index fbf14fe6e9..40e88a5132 100644
--- a/packages/rrweb-snapshot/test/utils.test.ts
+++ b/packages/rrweb-snapshot/test/utils.test.ts
@@ -211,7 +211,6 @@ describe('utils', () => {
       ['source', ['src']],
       ['track', ['src']],
       ['input', ['src']],
-      ['iframe', ['src']],
       ['object', ['src']],
     ] as const;
 
diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 90ff20bffd..725bf4bc14 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -330,22 +330,6 @@ exports[`record integration tests [DEPRECATED] should record images inside ifram
       \\"isAttachIframe\\": true
     }
   },
-  {
-    \\"type\\": 7,
-    \\"data\\": {
-      \\"url\\": \\"http://localhost:3030/html/image-blob-url.html\\",
-      \\"payload\\": {
-        \\"rr_type\\": \\"Blob\\",
-        \\"type\\": \\"text/html\\",
-        \\"data\\": [
-          {
-            \\"rr_type\\": \\"ArrayBuffer\\",
-            \\"base64\\": \\"PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04IiAvPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+CiAgICA8dGl0bGU+SW1hZ2Ugd2l0aCBibG9iOnVybDwvdGl0bGU+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHNjcmlwdD4KICAgICAgc2V0VGltZW91dChhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgY29uc3Qgcm9ib3RGaWxlID0gYXdhaXQgZmV0Y2goJy9odG1sL2Fzc2V0cy9yb2JvdC5wbmcnKTsKICAgICAgICBjb25zdCByb2JvdEJsb2IgPSBhd2FpdCByb2JvdEZpbGUuYmxvYigpOwogICAgICAgIGNvbnN0IHJvYm90QmxvYlVybCA9IFVSTC5jcmVhdGVPYmplY3RVUkwocm9ib3RCbG9iKTsKICAgICAgICBjb25zdCBlbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2ltZycpOwogICAgICAgIGVsLnNyYyA9IHJvYm90QmxvYlVybDsKICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZChlbCk7CiAgICAgIH0sIDEwKTsKICAgIDwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPgo=\\"
-          }
-        ]
-      }
-    }
-  },
   {
     \\"type\\": 3,
     \\"data\\": {

From cec8313bd9c1041744dbc98f67ccb6640f80b135 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Thu, 28 Mar 2024 09:34:00 +0000
Subject: [PATCH 090/102] Fixup: we don't need template strings here

---
 packages/rrweb/test/record/asset.test.ts | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index cf31e77e83..e68991b176 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -608,11 +608,11 @@ describe('asset caching', function (this: ISuite) {
 
     [
       '{SERVER_URL}/html/assets/robot.png?body',
-      `{SERVER_URL}/html/assets/robot.png?img`,
-      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio`,
-      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video`,
-      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?source`,
-      `{SERVER_URL}/html/assets/1-minute-of-silence.mp3?embed`,
+      '{SERVER_URL}/html/assets/robot.png?img',
+      '{SERVER_URL}/html/assets/1-minute-of-silence.mp3?audio',
+      '{SERVER_URL}/html/assets/1-minute-of-silence.mp3?video',
+      '{SERVER_URL}/html/assets/1-minute-of-silence.mp3?source',
+      '{SERVER_URL}/html/assets/1-minute-of-silence.mp3?embed',
       '{SERVER_URL}/html/assets/subtitles.vtt',
       '{SERVER_URL}/html/assets/robot.png?1x',
       '{SERVER_URL}/html/assets/robot.png?2x',

From ff49d735827002c19c50148c930a1adeb27b6dfe Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Thu, 28 Mar 2024 09:32:16 +0000
Subject: [PATCH 091/102] Ensure we correctly distinguish between html
 <iframes> and other exotic types that inline images or pdfs

---
 packages/rrweb-snapshot/src/snapshot.ts  |  16 ++++++++++--
 packages/rrweb-snapshot/src/utils.ts     |  31 +++++++++++++++++------
 packages/rrweb/test/html/assets/doc.pdf  | Bin 0 -> 12497 bytes
 packages/rrweb/test/record/asset.test.ts |  24 ++++++++++++++++++
 packages/rrweb/test/utils.ts             |   1 +
 5 files changed, 62 insertions(+), 10 deletions(-)
 create mode 100644 packages/rrweb/test/html/assets/doc.pdf

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index ee4d188468..e1b4613def 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -859,7 +859,11 @@ function serializeElementNode(
     };
   }
   // iframe
-  if (tagName === 'iframe' && !keepIframeSrcFn(attributes.src as string)) {
+  if (
+    tagName === 'iframe' &&
+    attributes.src &&
+    !keepIframeSrcFn(attributes.src as string)
+  ) {
     if (!(n as HTMLIFrameElement).contentDocument) {
       // we can't record it directly as we can't see into it
       // preserve the src attribute so a decision can be taken at replay time
@@ -1185,6 +1189,13 @@ export function serializeNodeWithId(
       (serializedNode as elementNode).attributes.value !== undefined
     ) {
       // value parameter in DOM reflects the correct value, so ignore childNode
+    } else if (
+      serializedNode.type === NodeType.Element &&
+      serializedNode.tagName === 'iframe' &&
+      (serializedNode as elementNode).attributes.rr_captured_src !== undefined
+    ) {
+      // even though we might be able to recurse into it (e.g. browser renders a document with an <embed>)
+      // we will instead capture it using asset management
     } else {
       for (const childN of Array.from(n.childNodes)) {
         const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
@@ -1216,7 +1227,8 @@ export function serializeNodeWithId(
 
   if (
     serializedNode.type === NodeType.Element &&
-    serializedNode.tagName === 'iframe'
+    serializedNode.tagName === 'iframe' &&
+    (serializedNode as elementNode).attributes.rr_captured_src === undefined
   ) {
     onceIframeLoaded(
       n as HTMLIFrameElement,
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index b80f016ad3..4b3656862e 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -400,14 +400,29 @@ export const CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
 export function isAttributeCapturable(n: Element, attribute: string): boolean {
   if (n.nodeName === 'IFRAME' && attribute == 'src') {
     const i = n as HTMLIFrameElement;
-    return (
-      !i.contentDocument &&
-      (i.src.endsWith('.pdf') ||
-        i.src.endsWith('.jpeg') ||
-        i.src.endsWith('.jpg') ||
-        i.src.endsWith('.png') ||
-        i.src.endsWith('.webp'))
-    );
+    if (i.contentDocument && i.contentDocument.contentType) {
+      return (
+        i.contentDocument.contentType.startsWith('image/') ||
+        i.contentDocument.contentType == 'application/pdf'
+      );
+    } else if (i.src) {
+      // fallback to checking filename
+      let ipath;
+      try {
+        ipath = new URL(i.src).pathname;
+      } catch (e) {
+        ipath = i.src.split('?')[0];
+      }
+      return (
+        ipath.endsWith('.pdf') ||
+        ipath.endsWith('.jpeg') ||
+        ipath.endsWith('.jpg') ||
+        ipath.endsWith('.gif') ||
+        ipath.endsWith('.png') ||
+        ipath.endsWith('.webp')
+      );
+    }
+    return false;
   }
   const acceptedAttributesSet = CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS.get(
     n.nodeName,
diff --git a/packages/rrweb/test/html/assets/doc.pdf b/packages/rrweb/test/html/assets/doc.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..9c79504d6e0bb70b838036437b4f0fccac8c854a
GIT binary patch
literal 12497
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

literal 0
HcmV?d00001

diff --git a/packages/rrweb/test/record/asset.test.ts b/packages/rrweb/test/record/asset.test.ts
index e68991b176..f96dff41c6 100644
--- a/packages/rrweb/test/record/asset.test.ts
+++ b/packages/rrweb/test/record/asset.test.ts
@@ -578,6 +578,9 @@ describe('asset caching', function (this: ISuite) {
             <img src="{SERVER_B_URL}/html/assets/robot.png?img" />
             <input type="image" id="image" alt="Login" src="{SERVER_URL}/html/assets/robot.png?input-type-image" />
             <iframe src="{SERVER_URL}/html/assets/robot.png?iframe"></iframe>
+            <iframe xsrc="{SERVER_URL}/html/assets/doc.pdf?iframe"></iframe>
+            <iframe src="{SERVER_URL}/html/assets/robot.png?should-ignore" srcdoc="<html>should override</html>"></iframe>
+            <iframe srcdoc="<svg style=&quot;background:gold&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 100 100&quot;></svg>"></iframe>
             <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
               <image href="{SERVER_URL}/html/assets/robot.png?svg" width="100" height="100" />
                 <defs>
@@ -618,6 +621,7 @@ describe('asset caching', function (this: ISuite) {
       '{SERVER_URL}/html/assets/robot.png?2x',
       '{SERVER_URL}/html/assets/robot.png?input-type-image',
       '{SERVER_URL}/html/assets/robot.png?iframe',
+      //'{SERVER_URL}/html/assets/doc.pdf?iframe',
       '{SERVER_URL}/html/assets/robot.png?svg',
       '{SERVER_URL}/html/assets/robot.png?svg2',
       '{SERVER_URL}/html/assets/robot.png?svg3',
@@ -665,5 +669,25 @@ describe('asset caching', function (this: ISuite) {
         }),
       );
     });
+
+    it("shouldn't capture iframe src assets if srcdoc overrides", async () => {
+      await ctx.page.waitForNetworkIdle({ idleTime: 100 });
+      await waitForRAF(ctx.page);
+
+      const events = await ctx.page?.evaluate(
+        () => (window as unknown as IWindow).snapshots,
+      );
+
+      // expect an event to be emitted with `event.type` === EventType.Asset
+      expect(events).not.toContainEqual(
+        expect.objectContaining({
+          type: EventType.Asset,
+          data: {
+            url: `${ctx.serverBURL}/html/assets/robot.png?should-ignore`,
+            payload: expect.any(Object),
+          },
+        }),
+      );
+    });
   });
 });
diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts
index 2d45277b9f..adaad3ac26 100644
--- a/packages/rrweb/test/utils.ts
+++ b/packages/rrweb/test/utils.ts
@@ -51,6 +51,7 @@ export const startServer = (defaultPort: number = 3031) =>
       '.js': 'text/javascript',
       '.css': 'text/css',
       '.webm': 'video/webm',
+      '.pdf': 'application/pdf',
     };
     const s = http.createServer((req, res) => {
       const parsedUrl = url.parse(req.url!);

From 90af2c6559606a7e26bb7986b869bd03e3f24d21 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Thu, 28 Mar 2024 09:33:24 +0000
Subject: [PATCH 092/102] Bump timeouts here as I was getting failures when
 running whole test suite concurrently

---
 packages/rrweb/test/integration.test.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index 297fd5044a..f696d4830f 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -867,7 +867,7 @@ describe('record integration tests', function (this: ISuite) {
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
-    await page.waitForTimeout(50); // wait for image to get added
+    await page.waitForTimeout(150); // wait for image to get added
     await waitForRAF(page); // wait for image to be captured
 
     const snapshots = (await page.evaluate(
@@ -893,7 +893,7 @@ describe('record integration tests', function (this: ISuite) {
       iframe.setAttribute('src', '/html/image-blob-url.html');
     });
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`); // wait for image to get loaded
-    await page.waitForTimeout(50); // wait for image to get added
+    await page.waitForTimeout(150); // wait for image to get added
     await waitForRAF(page); // wait for image to be captured
 
     const snapshots = (await page.evaluate(
@@ -931,7 +931,7 @@ describe('record integration tests', function (this: ISuite) {
       }),
     );
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`);
-    await page.waitForTimeout(50); // wait for image to get added
+    await page.waitForTimeout(150); // wait for image to get added
     await waitForRAF(page); // wait for image to be captured
 
     const snapshots = (await page.evaluate(
@@ -956,7 +956,7 @@ describe('record integration tests', function (this: ISuite) {
       iframe.setAttribute('src', '/html/image-blob-url.html');
     });
     await page.waitForResponse(`${serverURL}/html/assets/robot.png`); // wait for image to get loaded
-    await page.waitForTimeout(50); // wait for image to get added
+    await page.waitForTimeout(150); // wait for image to get added
     await waitForRAF(page); // wait for image to be captured
 
     const snapshots = (await page.evaluate(

From bd105932cb06d9b61f9c88a5790b8718d1ad44b4 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Thu, 28 Mar 2024 17:28:43 +0000
Subject: [PATCH 093/102] Add an `asset` type which includes the element; a bit
 of prep work for handling stylesheet assets

---
 packages/rrweb-snapshot/src/snapshot.ts       | 25 ++++++---
 packages/rrweb-snapshot/test/snapshot.test.ts | 56 ++++++++++++-------
 packages/rrweb/src/record/index.ts            |  4 +-
 packages/rrweb/src/record/mutation.ts         | 14 +++--
 .../src/record/observers/asset-manager.ts     |  9 ++-
 packages/types/src/index.ts                   |  5 ++
 6 files changed, 78 insertions(+), 35 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index e1b4613def..1f95f065d2 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -14,6 +14,7 @@ import {
   NodeType,
   attributes,
   elementNode,
+  asset,
   DataURLOptions,
 } from '@rrweb/types';
 import {
@@ -479,7 +480,7 @@ function serializeNode(
      *  - `src` attribute in `img` tags.
      *  - `srcset` attribute in `img` tags.
      */
-    onAssetDetected?: (result: { urls: string[] }) => unknown;
+    onAssetDetected?: (assets: asset[]) => unknown;
   },
 ): serializedNode | false {
   const {
@@ -648,7 +649,7 @@ function serializeElementNode(
      *  - `src` attribute in `img` tags.
      *  - `srcset` attribute in `img` tags.
      */
-    onAssetDetected?: (result: { urls: string[] }) => unknown;
+    onAssetDetected?: (assets: asset[]) => unknown;
   },
 ): serializedNode | false {
   const {
@@ -669,7 +670,7 @@ function serializeElementNode(
   const needBlock = _isBlockedElement(n, blockClass, blockSelector);
   const tagName = getValidTagName(n);
   let attributes: attributes = {};
-  const assets: string[] = [];
+  const assets: asset[] = [];
   const len = n.attributes.length;
   for (let i = 0; i < len; i++) {
     const attr = n.attributes[i];
@@ -689,9 +690,17 @@ function serializeElementNode(
         isAttributeCapturable(n, attr.name)
       ) {
         if (attr.name === 'srcset') {
-          assets.push(...getUrlsFromSrcset(value));
+          getUrlsFromSrcset(value).forEach((url) => {
+            assets.push({
+              element: n,
+              url,
+            });
+          });
         } else {
-          assets.push(value);
+          assets.push({
+            element: n,
+            url: value,
+          });
         }
         name = `rr_captured_${name}`;
       }
@@ -880,7 +889,7 @@ function serializeElementNode(
   }
 
   if (assets.length && onAssetDetected) {
-    onAssetDetected({ urls: assets });
+    onAssetDetected(assets);
   }
 
   return {
@@ -1039,7 +1048,7 @@ export function serializeNodeWithId(
      *  - `src` attribute in `img` tags.
      *  - `srcset` attribute in `img` tags.
      */
-    onAssetDetected?: (result: { urls: string[] }) => unknown;
+    onAssetDetected?: (assets: asset[]) => unknown;
   },
 ): serializedNodeWithId | null {
   const {
@@ -1366,7 +1375,7 @@ function snapshot(
      *  - `src` attribute in `img` tags.
      *  - `srcset` attribute in `img` tags.
      */
-    onAssetDetected?: (result: { urls: string[] }) => unknown;
+    onAssetDetected?: (assets: asset[]) => unknown;
   },
 ): serializedNodeWithId | null {
   const {
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index 325bfff9a5..d4fc3b9cb0 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -7,7 +7,7 @@ import {
   serializeNodeWithId,
   _isBlockedElement,
 } from '../src/snapshot';
-import { serializedNodeWithId, elementNode } from '@rrweb/types';
+import { serializedNodeWithId, elementNode, asset } from '@rrweb/types';
 import { Mirror } from '../src/utils';
 
 describe('absolute url to stylesheet', () => {
@@ -261,7 +261,7 @@ describe('form', () => {
 describe('onAssetDetected callback', () => {
   const serializeNode = (
     node: Node,
-    onAssetDetected: (result: { urls: string[] }) => void,
+    onAssetDetected: (result: asset[]) => void,
   ): serializedNodeWithId | null => {
     return serializeNodeWithId(node, {
       doc: document,
@@ -293,9 +293,12 @@ describe('onAssetDetected callback', () => {
 
     const callback = jest.fn();
     serializeNode(el, callback);
-    expect(callback).toHaveBeenCalledWith({
-      urls: ['https://example.com/image.png'],
-    });
+    expect(callback).toHaveBeenCalledWith([
+      {
+        element: el.querySelector('img'),
+        url: 'https://example.com/image.png',
+      },
+    ]);
   });
 
   it('should detect `set` attribute in image with ObjectURL', () => {
@@ -305,9 +308,12 @@ describe('onAssetDetected callback', () => {
 
     const callback = jest.fn();
     serializeNode(el, callback);
-    expect(callback).toHaveBeenCalledWith({
-      urls: ['blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4'],
-    });
+    expect(callback).toHaveBeenCalledWith([
+      {
+        element: el.querySelector('img'),
+        url: 'blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4',
+      },
+    ]);
   });
   it('should detect `srcset` attribute in image', () => {
     const el = render(`<div>
@@ -316,12 +322,16 @@ describe('onAssetDetected callback', () => {
 
     const callback = jest.fn();
     serializeNode(el, callback);
-    expect(callback).toHaveBeenCalledWith({
-      urls: [
-        'https://example.com/images/team-photo.jpg',
-        'https://example.com/images/team-photo-retina.jpg',
-      ],
-    });
+    expect(callback).toHaveBeenCalledWith([
+      {
+        element: el.querySelector('img'),
+        url: 'https://example.com/images/team-photo.jpg',
+      },
+      {
+        element: el.querySelector('img'),
+        url: 'https://example.com/images/team-photo-retina.jpg',
+      },
+    ]);
   });
 
   it('should detect `src` attribute in two images', () => {
@@ -333,11 +343,17 @@ describe('onAssetDetected callback', () => {
     const callback = jest.fn();
     serializeNode(el, callback);
     expect(callback).toBeCalledTimes(2);
-    expect(callback).toHaveBeenCalledWith({
-      urls: ['https://example.com/image.png'],
-    });
-    expect(callback).toHaveBeenCalledWith({
-      urls: ['https://example.com/image2.png'],
-    });
+    expect(callback).toHaveBeenCalledWith([
+      {
+        element: el.querySelectorAll('img')[0],
+        url: 'https://example.com/image.png',
+      },
+    ]);
+    expect(callback).toHaveBeenCalledWith([
+      {
+        element: el.querySelectorAll('img')[1],
+        url: 'https://example.com/image2.png',
+      },
+    ]);
   });
 });
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 3b0f1352a3..e4c9a30441 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -410,8 +410,8 @@ function record<T = eventWithTime>(
         stylesheetManager.attachLinkElement(linkEl, childSn);
       },
       onAssetDetected: (assets) => {
-        assets.urls.forEach((url) => {
-          assetManager.capture(url);
+        assets.forEach((asset) => {
+          assetManager.capture(asset);
         });
       },
       keepIframeSrcFn,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 6c583fd320..c05bf6c560 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -338,8 +338,8 @@ export default class MutationBuffer {
           this.stylesheetManager.attachLinkElement(link, childSn);
         },
         onAssetDetected: (assets) => {
-          assets.urls.forEach((url) => {
-            this.assetManager.capture(url);
+          assets.forEach((asset) => {
+            this.assetManager.capture(asset);
           });
         },
       });
@@ -637,10 +637,16 @@ export default class MutationBuffer {
           ) {
             if (attributeName === 'srcset') {
               getSourcesFromSrcset(transformedValue).forEach((url) => {
-                this.assetManager.capture(url);
+                this.assetManager.capture({
+                  element: target,
+                  url,
+                });
               });
             } else {
-              this.assetManager.capture(transformedValue);
+              this.assetManager.capture({
+                element: target,
+                url: transformedValue,
+              });
             }
             attributeName = `rr_captured_${attributeName}`;
           }
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index c7e0981b85..27fd63f71f 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -3,6 +3,7 @@ import type {
   SerializedCanvasArg,
   eventWithTime,
   listenerHandler,
+  asset,
 } from '@rrweb/types';
 import type { assetCallback } from '@rrweb/types';
 import { encode } from 'base64-arraybuffer';
@@ -125,7 +126,13 @@ export default class AssetManager {
     }
   }
 
-  public capture(url: string): {
+  public capture(asset: asset): {
+    status: 'capturing' | 'captured' | 'error' | 'refused';
+  } {
+    this.captureUrl(asset.url);
+  }
+
+  public captureUrl(url): {
     status: 'capturing' | 'captured' | 'error' | 'refused';
   } {
     if (this.shouldIgnore(url)) return { status: 'refused' };
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index debfb22788..17a2d6f16c 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -81,6 +81,11 @@ export type assetEvent = {
   data: assetParam;
 };
 
+export type asset = {
+  element: HTMLElement;
+  url: string;
+};
+
 export enum IncrementalSource {
   Mutation,
   MouseMove,

From 591698d1f18747aaf1d55add1a6b7bcd852a356a Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Tue, 2 Apr 2024 18:06:04 +0100
Subject: [PATCH 094/102] This function was replaced by getSourcesFromSrcset
 which ultimately derives from
 https://github.com/rrweb-io/rrweb-snapshot/pull/74

---
 packages/rrweb-snapshot/src/snapshot.ts |  3 +--
 packages/rrweb-snapshot/src/utils.ts    | 26 -------------------------
 2 files changed, 1 insertion(+), 28 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 1f95f065d2..421aaa7ce3 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -28,7 +28,6 @@ import {
   getInputType,
   toLowerCase,
   extractFileExtension,
-  getUrlsFromSrcset,
   isAttributeCapturable,
 } from './utils';
 
@@ -690,7 +689,7 @@ function serializeElementNode(
         isAttributeCapturable(n, attr.name)
       ) {
         if (attr.name === 'srcset') {
-          getUrlsFromSrcset(value).forEach((url) => {
+          getSourcesFromSrcset(value).forEach((url) => {
             assets.push({
               element: n,
               url,
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index 4b3656862e..dabdba14cc 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -350,32 +350,6 @@ export function extractFileExtension(
   return match?.[1] ?? null;
 }
 
-/**
- * Extracts the URLs from a srcset attribute.
- * @param srcset - The srcset attribute value. eg. `image.jpg 2x, image2.jpg 3x`
- * @returns An array of URLs. eg. `['image.jpg', 'image2.jpg']`
- */
-export function getUrlsFromSrcset(srcset: string): string[] {
-  const urls: string[] = [];
-  const parts = srcset.split(',');
-  for (let i = 0; i < parts.length; i++) {
-    const trimmed = parts[i].trim();
-    const spaceIndex = trimmed.indexOf(' ');
-    if (spaceIndex === -1) {
-      // If no descriptor is specified, it's a single URL.
-      // eg. `image.jpg`
-      urls.push(trimmed);
-    } else {
-      // Otherwise, it's a URL followed by a single descriptor.
-      // Since we don't know how long the URL will be, we'll assume it's everything
-      // before the first space.
-      // eg. `image.jpg 2x`
-      urls.push(trimmed.substring(0, spaceIndex));
-    }
-  }
-  return urls;
-}
-
 export const CAPTURABLE_ELEMENT_ATTRIBUTE_COMBINATIONS = new Map([
   ['IMG', new Set(['src', 'srcset'])],
   ['VIDEO', new Set(['src'])],

From fc12a99c34e2770e2c840f3c7dcb9db06f701053 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Wed, 3 Apr 2024 15:31:25 +0100
Subject: [PATCH 095/102] Refactor and prepare for some changes: Deal with the
 difference between `srcset` and other url attributes in one place

---
 packages/rrweb-snapshot/src/snapshot.ts       | 18 +++++-----------
 packages/rrweb-snapshot/test/snapshot.test.ts | 21 +++++++++++--------
 packages/rrweb/src/record/mutation.ts         | 19 +++++------------
 .../src/record/observers/asset-manager.ts     | 10 +++++++--
 packages/types/src/index.ts                   |  3 ++-
 5 files changed, 32 insertions(+), 39 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 421aaa7ce3..ba6cd57adf 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -688,19 +688,11 @@ function serializeElementNode(
         onAssetDetected &&
         isAttributeCapturable(n, attr.name)
       ) {
-        if (attr.name === 'srcset') {
-          getSourcesFromSrcset(value).forEach((url) => {
-            assets.push({
-              element: n,
-              url,
-            });
-          });
-        } else {
-          assets.push({
-            element: n,
-            url: value,
-          });
-        }
+        assets.push({
+          element: n,
+          attr: attr.name,
+          value,
+        });
         name = `rr_captured_${name}`;
       }
       attributes[name] = value;
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index d4fc3b9cb0..bf77a0dd2b 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -296,7 +296,8 @@ describe('onAssetDetected callback', () => {
     expect(callback).toHaveBeenCalledWith([
       {
         element: el.querySelector('img'),
-        url: 'https://example.com/image.png',
+        attr: 'src',
+        value: 'https://example.com/image.png',
       },
     ]);
   });
@@ -311,7 +312,8 @@ describe('onAssetDetected callback', () => {
     expect(callback).toHaveBeenCalledWith([
       {
         element: el.querySelector('img'),
-        url: 'blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4',
+        attr: 'src',
+        value: 'blob:https://example.com/e81acc2b-f460-4aec-91b3-ce9732b837c4',
       },
     ]);
   });
@@ -320,16 +322,15 @@ describe('onAssetDetected callback', () => {
       <img srcset="https://example.com/images/team-photo.jpg, https://example.com/images/team-photo-retina.jpg 2x" />
     </div>`);
 
+    // this used to trigger two calls, but now AssetManager is responsible for parsing the args
     const callback = jest.fn();
     serializeNode(el, callback);
     expect(callback).toHaveBeenCalledWith([
       {
         element: el.querySelector('img'),
-        url: 'https://example.com/images/team-photo.jpg',
-      },
-      {
-        element: el.querySelector('img'),
-        url: 'https://example.com/images/team-photo-retina.jpg',
+        attr: 'srcset',
+        value:
+          'https://example.com/images/team-photo.jpg, https://example.com/images/team-photo-retina.jpg 2x',
       },
     ]);
   });
@@ -346,13 +347,15 @@ describe('onAssetDetected callback', () => {
     expect(callback).toHaveBeenCalledWith([
       {
         element: el.querySelectorAll('img')[0],
-        url: 'https://example.com/image.png',
+        attr: 'src',
+        value: 'https://example.com/image.png',
       },
     ]);
     expect(callback).toHaveBeenCalledWith([
       {
         element: el.querySelectorAll('img')[1],
-        url: 'https://example.com/image2.png',
+        attr: 'src',
+        value: 'https://example.com/image2.png',
       },
     ]);
   });
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index c05bf6c560..ea64e90739 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -10,7 +10,6 @@ import {
   isNativeShadowDom,
   getInputType,
   toLowerCase,
-  getSourcesFromSrcset,
 } from 'rrweb-snapshot';
 import type { observerParam, MutationBufferParam } from '../types';
 import type {
@@ -635,19 +634,11 @@ export default class MutationBuffer {
             transformedValue &&
             this.assetManager.isAttributeCapturable(target, attributeName)
           ) {
-            if (attributeName === 'srcset') {
-              getSourcesFromSrcset(transformedValue).forEach((url) => {
-                this.assetManager.capture({
-                  element: target,
-                  url,
-                });
-              });
-            } else {
-              this.assetManager.capture({
-                element: target,
-                url: transformedValue,
-              });
-            }
+            this.assetManager.capture({
+              element: target,
+              attr: attributeName,
+              value: transformedValue,
+            });
             attributeName = `rr_captured_${attributeName}`;
           }
           item.attributes[attributeName] = transformedValue;
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 27fd63f71f..03864123a8 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -11,7 +11,7 @@ import { encode } from 'base64-arraybuffer';
 import { patch } from '../../utils';
 
 import type { recordOptions } from '../../types';
-import { isAttributeCapturable } from 'rrweb-snapshot';
+import { isAttributeCapturable, getSourcesFromSrcset } from 'rrweb-snapshot';
 
 export default class AssetManager {
   private urlObjectMap = new Map<string, File | Blob | MediaSource>();
@@ -129,7 +129,13 @@ export default class AssetManager {
   public capture(asset: asset): {
     status: 'capturing' | 'captured' | 'error' | 'refused';
   } {
-    this.captureUrl(asset.url);
+    if (asset.attr === 'srcset') {
+      getSourcesFromSrcset(asset.value).forEach((url) => {
+        this.captureUrl(url);
+      });
+    } else {
+      this.captureUrl(asset.value);
+    }
   }
 
   public captureUrl(url): {
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 17a2d6f16c..00b5aca43a 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -83,7 +83,8 @@ export type assetEvent = {
 
 export type asset = {
   element: HTMLElement;
-  url: string;
+  attr: string;
+  value: string;
 };
 
 export enum IncrementalSource {

From 2a45198b0bced7620c37551c864b63a090563e61 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Wed, 3 Apr 2024 10:23:21 +0100
Subject: [PATCH 096/102] Create assetStatus type as per Justin's request

---
 packages/rrweb/src/record/observers/asset-manager.ts | 10 +++-------
 packages/rrweb/src/types.ts                          |  4 ++++
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 03864123a8..0ed138c542 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -10,7 +10,7 @@ import { encode } from 'base64-arraybuffer';
 
 import { patch } from '../../utils';
 
-import type { recordOptions } from '../../types';
+import type { recordOptions, assetStatus } from '../../types';
 import { isAttributeCapturable, getSourcesFromSrcset } from 'rrweb-snapshot';
 
 export default class AssetManager {
@@ -126,9 +126,7 @@ export default class AssetManager {
     }
   }
 
-  public capture(asset: asset): {
-    status: 'capturing' | 'captured' | 'error' | 'refused';
-  } {
+  public capture(asset: asset): assetStatus {
     if (asset.attr === 'srcset') {
       getSourcesFromSrcset(asset.value).forEach((url) => {
         this.captureUrl(url);
@@ -138,9 +136,7 @@ export default class AssetManager {
     }
   }
 
-  public captureUrl(url): {
-    status: 'capturing' | 'captured' | 'error' | 'refused';
-  } {
+  public captureUrl(url): assetStatus {
     if (this.shouldIgnore(url)) return { status: 'refused' };
 
     if (this.capturedURLs.has(url)) {
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index e6a41df277..1387485096 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -227,3 +227,7 @@ export type CrossOriginIframeMessageEvent =
   MessageEvent<CrossOriginIframeMessageEventContent>;
 
 export type ErrorHandler = (error: unknown) => void | boolean;
+
+export type assetStatus = {
+  status: 'capturing' | 'captured' | 'error' | 'refused';
+};

From bd9d3c6c20242062561ee287bad1e790a771df82 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Thu, 28 Mar 2024 15:45:31 +0000
Subject: [PATCH 097/102] Default to headless on retest

---
 packages/rrweb/package.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/rrweb/package.json b/packages/rrweb/package.json
index 2309c29df8..ba85fd493c 100644
--- a/packages/rrweb/package.json
+++ b/packages/rrweb/package.json
@@ -5,7 +5,8 @@
   "scripts": {
     "prepare": "npm run prepack",
     "prepack": "npm run bundle",
-    "retest": "jest --testPathIgnorePatterns test/benchmark",
+    "retest": "PUPPETEER_HEADLESS=true yarn retest:headful",
+    "retest:headful": "jest --testPathIgnorePatterns test/benchmark",
     "build-and-test": "yarn bundle:browser && yarn retest",
     "test:headless": "PUPPETEER_HEADLESS=true yarn build-and-test",
     "test:headful": "PUPPETEER_HEADLESS=false yarn build-and-test",

From e9b7f2859db0a02db30197d4cda10043649d1d6c Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Wed, 3 Apr 2024 15:43:43 +0100
Subject: [PATCH 098/102] A convenience command to run prettier against files
 that have just been committed

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 4115e37ff8..b1938f55cc 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "test:watch": "yarn turbo run test:watch",
     "test:update": "yarn turbo run test:update",
     "format": "yarn prettier --write '**/*.{ts,md}'",
+    "format:head": "git diff --name-only HEAD^ |grep '\\.ts\\|\\.md' |xargs yarn prettier --write",
     "dev": "yarn turbo run dev",
     "check-types": "yarn run concurrently --success=all -r -m=1 'yarn workspaces-to-typescript-project-references --check' 'yarn turbo run check-types'",
     "repl": "cd packages/rrweb && npm run repl",

From ba9d5a24c5d549a0656a87325251f8be5e6eebb7 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 5 Apr 2024 11:05:31 +0200
Subject: [PATCH 099/102] Fix types as per suggestions from code review

---
 packages/rrweb/src/record/observers/asset-manager.ts | 10 ++++++----
 packages/rrweb/src/replay/index.ts                   |  1 -
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 0ed138c542..455f7a3413 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -126,17 +126,19 @@ export default class AssetManager {
     }
   }
 
-  public capture(asset: asset): assetStatus {
+  public capture(asset: asset): assetStatus | assetStatus[] {
     if (asset.attr === 'srcset') {
+      const statuses: assetStatus[] = [];
       getSourcesFromSrcset(asset.value).forEach((url) => {
-        this.captureUrl(url);
+        statuses.push(this.captureUrl(url));
       });
+      return statuses;
     } else {
-      this.captureUrl(asset.value);
+      return this.captureUrl(asset.value);
     }
   }
 
-  public captureUrl(url): assetStatus {
+  public captureUrl(url: string): assetStatus {
     if (this.shouldIgnore(url)) return { status: 'refused' };
 
     if (this.capturedURLs.has(url)) {
diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts
index 754e25d2fc..9daa27d72e 100644
--- a/packages/rrweb/src/replay/index.ts
+++ b/packages/rrweb/src/replay/index.ts
@@ -1832,7 +1832,6 @@ export class Replayer {
               } else {
                 targetEl.setAttribute(attributeName, value);
               }
-              void this.assetManager.manageAttribute(targetEl, attributeName);
             } catch (error) {
               this.warn(
                 'An error occurred may due to the checkout feature.',

From 24dc37c7c0df39b6a744bdd2d0ebf6a4487a0052 Mon Sep 17 00:00:00 2001
From: Justin Halsall <Juice10@users.noreply.github.com>
Date: Fri, 5 Apr 2024 11:17:10 +0200
Subject: [PATCH 100/102] Update packages/rrweb/src/replay/canvas/2d.ts

---
 packages/rrweb/src/replay/canvas/2d.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/rrweb/src/replay/canvas/2d.ts b/packages/rrweb/src/replay/canvas/2d.ts
index e797ba518f..2b95d0c35a 100644
--- a/packages/rrweb/src/replay/canvas/2d.ts
+++ b/packages/rrweb/src/replay/canvas/2d.ts
@@ -28,7 +28,7 @@ export default async function canvasMutation({
       return Promise.all(mutation.args.map(deserializeArg(imageMap, ctx)));
     },
   );
-  const args = (await Promise.all(mutationArgsPromises)) as unknown[];
+  const args: unknown[][] = await Promise.all(mutationArgsPromises);
   // step 2 apply all mutations
   args.forEach((args, index) => {
     const mutation = mutations[index];

From 2145f522d3020f7378d89f8bea323660ccee26c4 Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Tue, 16 Apr 2024 16:43:58 +0100
Subject: [PATCH 101/102] Remove function not used since #995

---
 packages/rrweb-snapshot/src/snapshot.ts | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index ba6cd57adf..536bb75b0f 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -416,11 +416,6 @@ function onceIframeLoaded(
   iframeEl.addEventListener('load', listener);
 }
 
-function isStylesheetLoaded(link: HTMLLinkElement) {
-  if (!link.getAttribute('href')) return true; // nothing to load
-  return link.sheet !== null;
-}
-
 function onceStylesheetLoaded(
   link: HTMLLinkElement,
   listener: () => unknown,

From 1470f1a3bf56488dbd86c4fba7061e920d6d59fc Mon Sep 17 00:00:00 2001
From: Eoghan Murray <eoghan@getthere.ie>
Date: Mon, 22 Apr 2024 18:51:10 +0100
Subject: [PATCH 102/102] Don't rename attrs of assets which will be refused
 according to initial recording config, i.e. the origins don't match. Should
 make the 'refused' status redundant

---
 packages/rrweb-snapshot/src/snapshot.ts       | 31 ++++++++++++++++-
 packages/rrweb-snapshot/src/utils.ts          | 28 ++++++++++++++++
 packages/rrweb-snapshot/test/snapshot.test.ts |  4 +++
 packages/rrweb/src/record/index.ts            |  3 ++
 packages/rrweb/src/record/mutation.ts         |  3 ++
 .../src/record/observers/asset-manager.ts     | 33 ++++++-------------
 packages/rrweb/src/types.ts                   |  2 ++
 .../cross-origin-iframes.test.ts.snap         |  2 +-
 8 files changed, 81 insertions(+), 25 deletions(-)

diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 536bb75b0f..d0f93c9ea0 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -16,6 +16,7 @@ import {
   elementNode,
   asset,
   DataURLOptions,
+  captureAssetsParam,
 } from '@rrweb/types';
 import {
   Mirror,
@@ -29,6 +30,7 @@ import {
   toLowerCase,
   extractFileExtension,
   isAttributeCapturable,
+  shouldIgnoreAsset,
 } from './utils';
 
 let _id = 1;
@@ -462,6 +464,7 @@ function serializeNode(
      * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
+    captureAssets?: captureAssetsParam;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
     /**
@@ -489,6 +492,10 @@ function serializeNode(
     maskInputFn,
     dataURLOptions = {},
     inlineImages,
+    captureAssets = {
+      objectURLs: true,
+      origins: false,
+    },
     recordCanvas,
     keepIframeSrcFn,
     newlyAddedElement = false,
@@ -528,6 +535,7 @@ function serializeNode(
         maskInputFn,
         dataURLOptions,
         inlineImages,
+        captureAssets,
         recordCanvas,
         keepIframeSrcFn,
         newlyAddedElement,
@@ -630,6 +638,7 @@ function serializeElementNode(
      * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
+    captureAssets?: captureAssetsParam;
     recordCanvas: boolean;
     keepIframeSrcFn: KeepIframeSrcFn;
     /**
@@ -655,6 +664,10 @@ function serializeElementNode(
     maskInputFn,
     dataURLOptions = {},
     inlineImages,
+    captureAssets = {
+      objectURLs: true,
+      origins: false,
+    },
     recordCanvas,
     keepIframeSrcFn,
     newlyAddedElement = false,
@@ -681,7 +694,8 @@ function serializeElementNode(
         value &&
         typeof value === 'string' &&
         onAssetDetected &&
-        isAttributeCapturable(n, attr.name)
+        isAttributeCapturable(n, attr.name) &&
+        !shouldIgnoreAsset(attr.value, captureAssets)
       ) {
         assets.push({
           element: n,
@@ -1015,6 +1029,7 @@ export function serializeNodeWithId(
      * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
+    captureAssets?: captureAssetsParam;
     recordCanvas?: boolean;
     preserveWhiteSpace?: boolean;
     onSerialize?: (n: Node) => unknown;
@@ -1052,6 +1067,10 @@ export function serializeNodeWithId(
     slimDOMOptions,
     dataURLOptions = {},
     inlineImages = false,
+    captureAssets = {
+      objectURLs: true,
+      origins: false,
+    },
     recordCanvas = false,
     onSerialize,
     onIframeLoad,
@@ -1091,6 +1110,7 @@ export function serializeNodeWithId(
     maskInputFn,
     dataURLOptions,
     inlineImages,
+    captureAssets,
     recordCanvas,
     keepIframeSrcFn,
     newlyAddedElement,
@@ -1167,6 +1187,7 @@ export function serializeNodeWithId(
       slimDOMOptions,
       dataURLOptions,
       inlineImages,
+      captureAssets,
       recordCanvas,
       preserveWhiteSpace,
       onSerialize,
@@ -1246,6 +1267,7 @@ export function serializeNodeWithId(
             slimDOMOptions,
             dataURLOptions,
             inlineImages,
+            captureAssets,
             recordCanvas,
             preserveWhiteSpace,
             onSerialize,
@@ -1298,6 +1320,7 @@ export function serializeNodeWithId(
             slimDOMOptions,
             dataURLOptions,
             inlineImages,
+            captureAssets,
             recordCanvas,
             preserveWhiteSpace,
             onSerialize,
@@ -1341,6 +1364,7 @@ function snapshot(
      * @deprecated please use `captureAssets` instead
      */
     inlineImages?: boolean;
+    captureAssets?: captureAssetsParam;
     recordCanvas?: boolean;
     preserveWhiteSpace?: boolean;
     onSerialize?: (n: Node) => unknown;
@@ -1372,6 +1396,10 @@ function snapshot(
     maskTextSelector = null,
     inlineStylesheet = true,
     inlineImages = false,
+    captureAssets = {
+      objectURLs: true,
+      origins: false,
+    },
     recordCanvas = false,
     maskAllInputs = false,
     maskTextFn,
@@ -1445,6 +1473,7 @@ function snapshot(
     slimDOMOptions,
     dataURLOptions,
     inlineImages,
+    captureAssets,
     recordCanvas,
     preserveWhiteSpace,
     onSerialize,
diff --git a/packages/rrweb-snapshot/src/utils.ts b/packages/rrweb-snapshot/src/utils.ts
index dabdba14cc..71bda1d5df 100644
--- a/packages/rrweb-snapshot/src/utils.ts
+++ b/packages/rrweb-snapshot/src/utils.ts
@@ -9,6 +9,7 @@ import {
   documentTypeNode,
   textNode,
   elementNode,
+  captureAssetsParam,
 } from '@rrweb/types';
 
 export function isElement(n: Node): n is Element {
@@ -406,3 +407,30 @@ export function isAttributeCapturable(n: Element, attribute: string): boolean {
   }
   return acceptedAttributesSet.has(attribute);
 }
+
+export function shouldIgnoreAsset(
+  url: string,
+  config: captureAssetsParam,
+): boolean {
+  const originsToIgnore = ['data:'];
+  const urlIsBlob = url.startsWith(`blob:${window.location.origin}/`);
+
+  // Check if url is a blob and we should ignore blobs
+  if (urlIsBlob) return !config.objectURLs;
+
+  // Check if url matches any ignorable origins
+  for (const origin of originsToIgnore) {
+    if (url.startsWith(origin)) return true;
+  }
+
+  // Check the origins
+  const captureOrigins = config.origins;
+  if (typeof captureOrigins === 'boolean') {
+    return !captureOrigins;
+  } else if (Array.isArray(captureOrigins)) {
+    const urlOrigin = new URL(url).origin;
+    return !captureOrigins.includes(urlOrigin);
+  }
+
+  return false;
+}
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index bf77a0dd2b..f6f4a23c32 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -277,6 +277,10 @@ describe('onAssetDetected callback', () => {
       slimDOMOptions: {},
       newlyAddedElement: false,
       inlineImages: false,
+      captureAssets: {
+        objectURLs: true,
+        origins: ['https://example.com'],
+      },
       onAssetDetected,
     });
   };
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index e4c9a30441..2047a289aa 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -342,6 +342,7 @@ function record<T = eventWithTime>(
       inlineStylesheet,
       maskInputOptions,
       dataURLOptions,
+      captureAssets,
       maskTextFn,
       maskInputFn,
       recordCanvas,
@@ -390,6 +391,7 @@ function record<T = eventWithTime>(
       maskTextFn,
       slimDOM: slimDOMOptions,
       dataURLOptions,
+      captureAssets,
       recordCanvas,
       onSerialize: (n) => {
         if (isSerializedIframe(n, mirror)) {
@@ -550,6 +552,7 @@ function record<T = eventWithTime>(
           keepIframeSrcFn,
           blockSelector,
           slimDOMOptions,
+          captureAssets,
           dataURLOptions,
           mirror,
           iframeManager,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index ea64e90739..6cfd0baa6b 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -180,6 +180,7 @@ export default class MutationBuffer {
   private maskInputFn: observerParam['maskInputFn'];
   private keepIframeSrcFn: observerParam['keepIframeSrcFn'];
   private recordCanvas: observerParam['recordCanvas'];
+  private captureAssets: observerParam['captureAssets'];
   private slimDOMOptions: observerParam['slimDOMOptions'];
   private dataURLOptions: observerParam['dataURLOptions'];
   private doc: observerParam['doc'];
@@ -205,6 +206,7 @@ export default class MutationBuffer {
         'maskTextFn',
         'maskInputFn',
         'keepIframeSrcFn',
+        'captureAssets',
         'recordCanvas',
         'slimDOMOptions',
         'dataURLOptions',
@@ -315,6 +317,7 @@ export default class MutationBuffer {
         maskInputFn: this.maskInputFn,
         slimDOMOptions: this.slimDOMOptions,
         dataURLOptions: this.dataURLOptions,
+        captureAssets: this.captureAssets,
         recordCanvas: this.recordCanvas,
         onSerialize: (currentN) => {
           if (isSerializedIframe(currentN, this.mirror)) {
diff --git a/packages/rrweb/src/record/observers/asset-manager.ts b/packages/rrweb/src/record/observers/asset-manager.ts
index 455f7a3413..a6edb82d4f 100644
--- a/packages/rrweb/src/record/observers/asset-manager.ts
+++ b/packages/rrweb/src/record/observers/asset-manager.ts
@@ -11,7 +11,11 @@ import { encode } from 'base64-arraybuffer';
 import { patch } from '../../utils';
 
 import type { recordOptions, assetStatus } from '../../types';
-import { isAttributeCapturable, getSourcesFromSrcset } from 'rrweb-snapshot';
+import {
+  isAttributeCapturable,
+  getSourcesFromSrcset,
+  shouldIgnoreAsset,
+} from 'rrweb-snapshot';
 
 export default class AssetManager {
   private urlObjectMap = new Map<string, File | Blob | MediaSource>();
@@ -85,27 +89,7 @@ export default class AssetManager {
   }
 
   public shouldIgnore(url: string): boolean {
-    const originsToIgnore = ['data:'];
-    const urlIsBlob = url.startsWith(`blob:${window.location.origin}/`);
-
-    // Check if url is a blob and we should ignore blobs
-    if (urlIsBlob) return !this.config.objectURLs;
-
-    // Check if url matches any ignorable origins
-    for (const origin of originsToIgnore) {
-      if (url.startsWith(origin)) return true;
-    }
-
-    // Check the origins
-    const captureOrigins = this.config.origins;
-    if (typeof captureOrigins === 'boolean') {
-      return !captureOrigins;
-    } else if (Array.isArray(captureOrigins)) {
-      const urlOrigin = new URL(url).origin;
-      return !captureOrigins.includes(urlOrigin);
-    }
-
-    return false;
+    return shouldIgnoreAsset(url, this.config);
   }
 
   public async getURLObject(
@@ -139,7 +123,10 @@ export default class AssetManager {
   }
 
   public captureUrl(url: string): assetStatus {
-    if (this.shouldIgnore(url)) return { status: 'refused' };
+    if (this.shouldIgnore(url)) {
+      console.warn(`snapshot.ts should know to ignore ${url}`);
+      return { status: 'refused' };
+    }
 
     if (this.capturedURLs.has(url)) {
       return { status: 'captured' };
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index 1387485096..336f69f9fa 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -108,6 +108,7 @@ export type observerParam = {
   fontCb: fontCallback;
   sampling: SamplingStrategy;
   recordDOM: boolean;
+  captureAssets: captureAssetsParam;
   recordCanvas: boolean;
   userTriggeredOnInput: boolean;
   collectFonts: boolean;
@@ -145,6 +146,7 @@ export type MutationBufferParam = Pick<
   | 'maskTextFn'
   | 'maskInputFn'
   | 'keepIframeSrcFn'
+  | 'captureAssets'
   | 'recordCanvas'
   | 'slimDOMOptions'
   | 'dataURLOptions'
diff --git a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
index 74036c6add..e160fb38d6 100644
--- a/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
+++ b/packages/rrweb/test/record/__snapshots__/cross-origin-iframes.test.ts.snap
@@ -280,7 +280,7 @@ exports[`cross origin iframes audio.html should emit contents of iframe once 1`]
                             \\"type\\": 2,
                             \\"tagName\\": \\"source\\",
                             \\"attributes\\": {
-                              \\"rr_captured_src\\": \\"http://localhost:3030/html/assets/1-minute-of-silence.mp3\\",
+                              \\"src\\": \\"http://localhost:3030/html/assets/1-minute-of-silence.mp3\\",
                               \\"type\\": \\"audio/mpeg\\"
                             },
                             \\"childNodes\\": [],