diff --git a/package.json b/package.json
index d84d7a4..0b9cf13 100644
--- a/package.json
+++ b/package.json
@@ -32,24 +32,25 @@
     "ora": "^8.1.0",
     "please-upgrade-node": "^3.2.0",
     "pretty-bytes": "^6.1.1",
-    "puppeteer": "^23.2.0",
+    "puppeteer": "^23.5.2",
     "quick-lru": "^7.0.0",
     "source-map": "^0.7.4",
     "stacktrace-parser": "^0.1.10",
     "table": "^6.8.2"
   },
   "devDependencies": {
-    "@rollup/plugin-replace": "^5.0.7",
+    "@rollup/plugin-inject": "^5.0.5",
+    "@rollup/plugin-replace": "^6.0.1",
     "@rollup/plugin-strip": "^3.0.4",
-    "@rollup/plugin-typescript": "^11.1.6",
+    "@rollup/plugin-typescript": "^12.1.0",
     "@rollup/plugin-virtual": "^3.0.2",
     "chai": "^5.1.1",
     "conventional-changelog-cli": "^5.0.0",
     "navigo": "^8.11.1",
     "npm-run-all": "^4.1.5",
-    "rollup": "^4.21.1",
+    "rollup": "^4.24.0",
     "serve": "^14.2.3",
-    "standard": "^17.1.0",
+    "standard": "^17.1.2",
     "wait-for-localhost": "^4.1.0"
   },
   "keywords": [
@@ -75,5 +76,5 @@
       "thirdparty"
     ]
   },
-  "packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1"
+  "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4"
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5ea372e..4246566 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -33,8 +33,8 @@ importers:
         specifier: ^6.1.1
         version: 6.1.1
       puppeteer:
-        specifier: ^23.2.0
-        version: 23.2.0(typescript@5.4.5)
+        specifier: ^23.5.2
+        version: 23.5.2(typescript@5.4.5)
       quick-lru:
         specifier: ^7.0.0
         version: 7.0.0
@@ -48,18 +48,21 @@ importers:
         specifier: ^6.8.2
         version: 6.8.2
     devDependencies:
+      '@rollup/plugin-inject':
+        specifier: ^5.0.5
+        version: 5.0.5(rollup@4.24.0)
       '@rollup/plugin-replace':
-        specifier: ^5.0.7
-        version: 5.0.7(rollup@4.21.1)
+        specifier: ^6.0.1
+        version: 6.0.1(rollup@4.24.0)
       '@rollup/plugin-strip':
         specifier: ^3.0.4
-        version: 3.0.4(rollup@4.21.1)
+        version: 3.0.4(rollup@4.24.0)
       '@rollup/plugin-typescript':
-        specifier: ^11.1.6
-        version: 11.1.6(rollup@4.21.1)(tslib@2.6.2)(typescript@5.4.5)
+        specifier: ^12.1.0
+        version: 12.1.0(rollup@4.24.0)(tslib@2.6.2)(typescript@5.4.5)
       '@rollup/plugin-virtual':
         specifier: ^3.0.2
-        version: 3.0.2(rollup@4.21.1)
+        version: 3.0.2(rollup@4.24.0)
       chai:
         specifier: ^5.1.1
         version: 5.1.1
@@ -73,14 +76,14 @@ importers:
         specifier: ^4.1.5
         version: 4.1.5
       rollup:
-        specifier: ^4.21.1
-        version: 4.21.1
+        specifier: ^4.24.0
+        version: 4.24.0
       serve:
         specifier: ^14.2.3
         version: 14.2.3
       standard:
-        specifier: ^17.1.0
-        version: 17.1.0
+        specifier: ^17.1.2
+        version: 17.1.2
       wait-for-localhost:
         specifier: ^4.1.0
         version: 4.1.0
@@ -132,6 +135,7 @@ packages:
   '@humanwhocodes/config-array@0.11.14':
     resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
     engines: {node: '>=10.10.0'}
+    deprecated: Use @eslint/config-array instead
 
   '@humanwhocodes/module-importer@1.0.1':
     resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -139,6 +143,7 @@ packages:
 
   '@humanwhocodes/object-schema@2.0.3':
     resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+    deprecated: Use @eslint/object-schema instead
 
   '@hutson/parse-repository-url@5.0.0':
     resolution: {integrity: sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==}
@@ -162,13 +167,22 @@ packages:
     resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
     engines: {node: '>= 8'}
 
-  '@puppeteer/browsers@2.3.1':
-    resolution: {integrity: sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==}
+  '@puppeteer/browsers@2.4.0':
+    resolution: {integrity: sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==}
     engines: {node: '>=18'}
     hasBin: true
 
-  '@rollup/plugin-replace@5.0.7':
-    resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==}
+  '@rollup/plugin-inject@5.0.5':
+    resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+    peerDependenciesMeta:
+      rollup:
+        optional: true
+
+  '@rollup/plugin-replace@6.0.1':
+    resolution: {integrity: sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -185,8 +199,8 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/plugin-typescript@11.1.6':
-    resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==}
+  '@rollup/plugin-typescript@12.1.0':
+    resolution: {integrity: sha512-Kzs8KGJofe7cfTRODsnG1jNGxSvU8gVoNNd7Z/QaY25AYwe2LSSUpx/kPxqF38NYkpR8de3m51r9uwJpDlz6dg==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       rollup: ^2.14.0||^3.0.0||^4.0.0
@@ -216,83 +230,83 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.21.1':
-    resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==}
+  '@rollup/rollup-android-arm-eabi@4.24.0':
+    resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.21.1':
-    resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==}
+  '@rollup/rollup-android-arm64@4.24.0':
+    resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.21.1':
-    resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==}
+  '@rollup/rollup-darwin-arm64@4.24.0':
+    resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.21.1':
-    resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==}
+  '@rollup/rollup-darwin-x64@4.24.0':
+    resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.21.1':
-    resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.24.0':
+    resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.21.1':
-    resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==}
+  '@rollup/rollup-linux-arm-musleabihf@4.24.0':
+    resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.21.1':
-    resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==}
+  '@rollup/rollup-linux-arm64-gnu@4.24.0':
+    resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.21.1':
-    resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==}
+  '@rollup/rollup-linux-arm64-musl@4.24.0':
+    resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.21.1':
-    resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
+    resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.21.1':
-    resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==}
+  '@rollup/rollup-linux-riscv64-gnu@4.24.0':
+    resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.21.1':
-    resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==}
+  '@rollup/rollup-linux-s390x-gnu@4.24.0':
+    resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.21.1':
-    resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==}
+  '@rollup/rollup-linux-x64-gnu@4.24.0':
+    resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.21.1':
-    resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==}
+  '@rollup/rollup-linux-x64-musl@4.24.0':
+    resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.21.1':
-    resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==}
+  '@rollup/rollup-win32-arm64-msvc@4.24.0':
+    resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.21.1':
-    resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==}
+  '@rollup/rollup-win32-ia32-msvc@4.24.0':
+    resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.21.1':
-    resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==}
+  '@rollup/rollup-win32-x64-msvc@4.24.0':
+    resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==}
     cpu: [x64]
     os: [win32]
 
@@ -302,6 +316,9 @@ packages:
   '@types/estree@1.0.5':
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
 
+  '@types/estree@1.0.6':
+    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
   '@types/json5@0.0.29':
     resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
 
@@ -412,11 +429,9 @@ packages:
     resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
     engines: {node: '>= 0.4'}
 
-  array.prototype.toreversed@1.1.2:
-    resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==}
-
-  array.prototype.tosorted@1.1.3:
-    resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==}
+  array.prototype.tosorted@1.1.4:
+    resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+    engines: {node: '>= 0.4'}
 
   arraybuffer.prototype.slice@1.0.3:
     resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
@@ -529,8 +544,8 @@ packages:
     resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
     engines: {node: '>= 16'}
 
-  chromium-bidi@0.6.4:
-    resolution: {integrity: sha512-8zoq6ogmhQQkAKZVKO2ObFTl4uOkqoX1PlKQX3hZQ5E9cbUotcAb7h4pTNVAGGv8Z36PF3CtdOriEp/Rz82JqQ==}
+  chromium-bidi@0.8.0:
+    resolution: {integrity: sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==}
     peerDependencies:
       devtools-protocol: '*'
 
@@ -705,8 +720,8 @@ packages:
       supports-color:
         optional: true
 
-  debug@4.3.4:
-    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+  debug@4.3.6:
+    resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
     engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
@@ -714,8 +729,8 @@ packages:
       supports-color:
         optional: true
 
-  debug@4.3.6:
-    resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
     engines: {node: '>=6.0'}
     peerDependencies:
       supports-color: '*'
@@ -746,8 +761,8 @@ packages:
     resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
     engines: {node: '>= 14'}
 
-  devtools-protocol@0.0.1330662:
-    resolution: {integrity: sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw==}
+  devtools-protocol@0.0.1342118:
+    resolution: {integrity: sha512-75fMas7PkYNDTmDyb6PRJCH7ILmHLp+BhrZGeMsa4bCh40DTxgCz2NRy5UDzII4C5KuD0oBMZ9vXKhEl6UD/3w==}
 
   doctrine@2.1.0:
     resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
@@ -898,11 +913,11 @@ packages:
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
 
-  eslint-plugin-react@7.34.1:
-    resolution: {integrity: sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==}
+  eslint-plugin-react@7.37.1:
+    resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==}
     engines: {node: '>=4'}
     peerDependencies:
-      eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+      eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
 
   eslint-scope@7.2.2:
     resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
@@ -933,6 +948,7 @@ packages:
   eslint@8.57.0:
     resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
   espree@9.6.1:
@@ -1096,6 +1112,7 @@ packages:
 
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
 
   globals@13.24.0:
     resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
@@ -1189,6 +1206,7 @@ packages:
 
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
 
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -1558,10 +1576,6 @@ packages:
     resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
     engines: {node: '>= 0.4'}
 
-  object.hasown@1.1.4:
-    resolution: {integrity: sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==}
-    engines: {node: '>= 0.4'}
-
   object.values@1.2.0:
     resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
     engines: {node: '>= 0.4'}
@@ -1742,12 +1756,12 @@ packages:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
 
-  puppeteer-core@23.2.0:
-    resolution: {integrity: sha512-OFyPp2oolGSesx6ZrpmorE5tCaCKY1Z5e/h8f6sB0NpiezenB72jdWBdOrvBO/bUXyq14XyGJsDRUsv0ZOPdZA==}
+  puppeteer-core@23.5.2:
+    resolution: {integrity: sha512-UwPAX29EID8lJmxeL7JT3Gz35D1BHn5o9ZXpBLoR24W7gtUg1dLx7OUPsUTR5Tlxf+1Yeqw9W3qP4uqWThqXgg==}
     engines: {node: '>=18'}
 
-  puppeteer@23.2.0:
-    resolution: {integrity: sha512-MP7kLOdCfx1BJaGN5sgRo5fTYwAyGrlwWtrNphjKcwv/HO91+m90gbbwpRHbGl0rCvrmylq6vljn+zrjukniVg==}
+  puppeteer@23.5.2:
+    resolution: {integrity: sha512-7OOGEIoCjGP9lQ6QHvRSBTO3VRDPvu+YGl6rLCKOfYNMp1Lqc1U+s3lS1JdyR+jee1pZ55sxf+TEKsmqOopO1A==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -1833,10 +1847,11 @@ packages:
 
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
-  rollup@4.21.1:
-    resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==}
+  rollup@4.24.0:
+    resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -1971,8 +1986,8 @@ packages:
     resolution: {integrity: sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
-  standard@17.1.0:
-    resolution: {integrity: sha512-jaDqlNSzLtWYW4lvQmU0EnxWMUGQiwHasZl5ZEIwx3S/ijZDjZOzs1y1QqKwKs5vqnFpGtizo4NOYX2s0Voq/g==}
+  standard@17.1.2:
+    resolution: {integrity: sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
 
@@ -2003,6 +2018,9 @@ packages:
     resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==}
     engines: {node: '>= 0.4'}
 
+  string.prototype.repeat@1.0.0:
+    resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
   string.prototype.trim@1.2.9:
     resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
     engines: {node: '>= 0.4'}
@@ -2292,7 +2310,7 @@ snapshots:
   '@eslint/eslintrc@2.1.4':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.4
+      debug: 4.3.6
       espree: 9.6.1
       globals: 13.24.0
       ignore: 5.3.1
@@ -2308,7 +2326,7 @@ snapshots:
   '@humanwhocodes/config-array@0.11.14':
     dependencies:
       '@humanwhocodes/object-schema': 2.0.3
-      debug: 4.3.4
+      debug: 4.3.6
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -2335,7 +2353,7 @@ snapshots:
       '@nodelib/fs.scandir': 2.1.5
       fastq: 1.17.1
 
-  '@puppeteer/browsers@2.3.1':
+  '@puppeteer/browsers@2.4.0':
     dependencies:
       debug: 4.3.6
       extract-zip: 2.0.1
@@ -2348,94 +2366,104 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@rollup/plugin-replace@5.0.7(rollup@4.21.1)':
+  '@rollup/plugin-inject@5.0.5(rollup@4.24.0)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.21.1)
+      '@rollup/pluginutils': 5.1.0(rollup@4.24.0)
+      estree-walker: 2.0.2
       magic-string: 0.30.10
     optionalDependencies:
-      rollup: 4.21.1
+      rollup: 4.24.0
 
-  '@rollup/plugin-strip@3.0.4(rollup@4.21.1)':
+  '@rollup/plugin-replace@6.0.1(rollup@4.24.0)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.21.1)
+      '@rollup/pluginutils': 5.1.0(rollup@4.24.0)
+      magic-string: 0.30.10
+    optionalDependencies:
+      rollup: 4.24.0
+
+  '@rollup/plugin-strip@3.0.4(rollup@4.24.0)':
+    dependencies:
+      '@rollup/pluginutils': 5.1.0(rollup@4.24.0)
       estree-walker: 2.0.2
       magic-string: 0.30.10
     optionalDependencies:
-      rollup: 4.21.1
+      rollup: 4.24.0
 
-  '@rollup/plugin-typescript@11.1.6(rollup@4.21.1)(tslib@2.6.2)(typescript@5.4.5)':
+  '@rollup/plugin-typescript@12.1.0(rollup@4.24.0)(tslib@2.6.2)(typescript@5.4.5)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.21.1)
+      '@rollup/pluginutils': 5.1.0(rollup@4.24.0)
       resolve: 1.22.8
       typescript: 5.4.5
     optionalDependencies:
-      rollup: 4.21.1
+      rollup: 4.24.0
       tslib: 2.6.2
 
-  '@rollup/plugin-virtual@3.0.2(rollup@4.21.1)':
+  '@rollup/plugin-virtual@3.0.2(rollup@4.24.0)':
     optionalDependencies:
-      rollup: 4.21.1
+      rollup: 4.24.0
 
-  '@rollup/pluginutils@5.1.0(rollup@4.21.1)':
+  '@rollup/pluginutils@5.1.0(rollup@4.24.0)':
     dependencies:
       '@types/estree': 1.0.5
       estree-walker: 2.0.2
       picomatch: 2.3.1
     optionalDependencies:
-      rollup: 4.21.1
+      rollup: 4.24.0
 
-  '@rollup/rollup-android-arm-eabi@4.21.1':
+  '@rollup/rollup-android-arm-eabi@4.24.0':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.21.1':
+  '@rollup/rollup-android-arm64@4.24.0':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.21.1':
+  '@rollup/rollup-darwin-arm64@4.24.0':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.21.1':
+  '@rollup/rollup-darwin-x64@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.21.1':
+  '@rollup/rollup-linux-arm-gnueabihf@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.21.1':
+  '@rollup/rollup-linux-arm-musleabihf@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.21.1':
+  '@rollup/rollup-linux-arm64-gnu@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.21.1':
+  '@rollup/rollup-linux-arm64-musl@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.21.1':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.21.1':
+  '@rollup/rollup-linux-riscv64-gnu@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.21.1':
+  '@rollup/rollup-linux-s390x-gnu@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.21.1':
+  '@rollup/rollup-linux-x64-gnu@4.24.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.21.1':
+  '@rollup/rollup-linux-x64-musl@4.24.0':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.21.1':
+  '@rollup/rollup-win32-arm64-msvc@4.24.0':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.21.1':
+  '@rollup/rollup-win32-ia32-msvc@4.24.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.21.1':
+  '@rollup/rollup-win32-x64-msvc@4.24.0':
     optional: true
 
   '@tootallnate/quickjs-emscripten@0.23.0': {}
 
   '@types/estree@1.0.5': {}
 
+  '@types/estree@1.0.6': {}
+
   '@types/json5@0.0.29': {}
 
   '@types/node@20.12.12':
@@ -2568,14 +2596,7 @@ snapshots:
       es-abstract: 1.23.3
       es-shim-unscopables: 1.0.2
 
-  array.prototype.toreversed@1.1.2:
-    dependencies:
-      call-bind: 1.0.7
-      define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-shim-unscopables: 1.0.2
-
-  array.prototype.tosorted@1.1.3:
+  array.prototype.tosorted@1.1.4:
     dependencies:
       call-bind: 1.0.7
       define-properties: 1.2.1
@@ -2666,7 +2687,7 @@ snapshots:
 
   builtins@5.1.0:
     dependencies:
-      semver: 7.6.2
+      semver: 7.6.3
 
   bytes@3.0.0: {}
 
@@ -2711,9 +2732,9 @@ snapshots:
 
   check-error@2.1.1: {}
 
-  chromium-bidi@0.6.4(devtools-protocol@0.0.1330662):
+  chromium-bidi@0.8.0(devtools-protocol@0.0.1342118):
     dependencies:
-      devtools-protocol: 0.0.1330662
+      devtools-protocol: 0.0.1342118
       mitt: 3.0.1
       urlpattern-polyfill: 10.0.0
       zod: 3.23.8
@@ -2908,13 +2929,13 @@ snapshots:
     dependencies:
       ms: 2.1.3
 
-  debug@4.3.4:
+  debug@4.3.6:
     dependencies:
       ms: 2.1.2
 
-  debug@4.3.6:
+  debug@4.3.7:
     dependencies:
-      ms: 2.1.2
+      ms: 2.1.3
 
   deep-eql@5.0.1: {}
 
@@ -2940,7 +2961,7 @@ snapshots:
       escodegen: 2.1.0
       esprima: 4.0.1
 
-  devtools-protocol@0.0.1330662: {}
+  devtools-protocol@0.0.1342118: {}
 
   doctrine@2.1.0:
     dependencies:
@@ -3078,10 +3099,10 @@ snapshots:
     optionalDependencies:
       source-map: 0.6.1
 
-  eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.34.1(eslint@8.57.0))(eslint@8.57.0):
+  eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.37.1(eslint@8.57.0))(eslint@8.57.0):
     dependencies:
       eslint: 8.57.0
-      eslint-plugin-react: 7.34.1(eslint@8.57.0)
+      eslint-plugin-react: 7.37.1(eslint@8.57.0)
 
   eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint-plugin-n@15.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0):
     dependencies:
@@ -3148,33 +3169,33 @@ snapshots:
       is-core-module: 2.13.1
       minimatch: 3.1.2
       resolve: 1.22.8
-      semver: 7.6.2
+      semver: 7.6.3
 
   eslint-plugin-promise@6.1.1(eslint@8.57.0):
     dependencies:
       eslint: 8.57.0
 
-  eslint-plugin-react@7.34.1(eslint@8.57.0):
+  eslint-plugin-react@7.37.1(eslint@8.57.0):
     dependencies:
       array-includes: 3.1.8
       array.prototype.findlast: 1.2.5
       array.prototype.flatmap: 1.3.2
-      array.prototype.toreversed: 1.1.2
-      array.prototype.tosorted: 1.1.3
+      array.prototype.tosorted: 1.1.4
       doctrine: 2.1.0
       es-iterator-helpers: 1.0.19
       eslint: 8.57.0
       estraverse: 5.3.0
+      hasown: 2.0.2
       jsx-ast-utils: 3.3.5
       minimatch: 3.1.2
       object.entries: 1.1.8
       object.fromentries: 2.0.8
-      object.hasown: 1.1.4
       object.values: 1.2.0
       prop-types: 15.8.1
       resolve: 2.0.0-next.5
       semver: 6.3.1
       string.prototype.matchall: 4.0.11
+      string.prototype.repeat: 1.0.0
 
   eslint-scope@7.2.2:
     dependencies:
@@ -3209,7 +3230,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4
+      debug: 4.3.6
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -3854,12 +3875,6 @@ snapshots:
       define-properties: 1.2.1
       es-abstract: 1.23.3
 
-  object.hasown@1.1.4:
-    dependencies:
-      define-properties: 1.2.1
-      es-abstract: 1.23.3
-      es-object-atoms: 1.0.0
-
   object.values@1.2.0:
     dependencies:
       call-bind: 1.0.7
@@ -4046,12 +4061,12 @@ snapshots:
 
   punycode@2.3.1: {}
 
-  puppeteer-core@23.2.0:
+  puppeteer-core@23.5.2:
     dependencies:
-      '@puppeteer/browsers': 2.3.1
-      chromium-bidi: 0.6.4(devtools-protocol@0.0.1330662)
-      debug: 4.3.6
-      devtools-protocol: 0.0.1330662
+      '@puppeteer/browsers': 2.4.0
+      chromium-bidi: 0.8.0(devtools-protocol@0.0.1342118)
+      debug: 4.3.7
+      devtools-protocol: 0.0.1342118
       typed-query-selector: 2.12.0
       ws: 8.18.0
     transitivePeerDependencies:
@@ -4059,13 +4074,13 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  puppeteer@23.2.0(typescript@5.4.5):
+  puppeteer@23.5.2(typescript@5.4.5):
     dependencies:
-      '@puppeteer/browsers': 2.3.1
-      chromium-bidi: 0.6.4(devtools-protocol@0.0.1330662)
+      '@puppeteer/browsers': 2.4.0
+      chromium-bidi: 0.8.0(devtools-protocol@0.0.1342118)
       cosmiconfig: 9.0.0(typescript@5.4.5)
-      devtools-protocol: 0.0.1330662
-      puppeteer-core: 23.2.0
+      devtools-protocol: 0.0.1342118
+      puppeteer-core: 23.5.2
       typed-query-selector: 2.12.0
     transitivePeerDependencies:
       - bufferutil
@@ -4167,26 +4182,26 @@ snapshots:
     dependencies:
       glob: 7.2.3
 
-  rollup@4.21.1:
+  rollup@4.24.0:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.21.1
-      '@rollup/rollup-android-arm64': 4.21.1
-      '@rollup/rollup-darwin-arm64': 4.21.1
-      '@rollup/rollup-darwin-x64': 4.21.1
-      '@rollup/rollup-linux-arm-gnueabihf': 4.21.1
-      '@rollup/rollup-linux-arm-musleabihf': 4.21.1
-      '@rollup/rollup-linux-arm64-gnu': 4.21.1
-      '@rollup/rollup-linux-arm64-musl': 4.21.1
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1
-      '@rollup/rollup-linux-riscv64-gnu': 4.21.1
-      '@rollup/rollup-linux-s390x-gnu': 4.21.1
-      '@rollup/rollup-linux-x64-gnu': 4.21.1
-      '@rollup/rollup-linux-x64-musl': 4.21.1
-      '@rollup/rollup-win32-arm64-msvc': 4.21.1
-      '@rollup/rollup-win32-ia32-msvc': 4.21.1
-      '@rollup/rollup-win32-x64-msvc': 4.21.1
+      '@rollup/rollup-android-arm-eabi': 4.24.0
+      '@rollup/rollup-android-arm64': 4.24.0
+      '@rollup/rollup-darwin-arm64': 4.24.0
+      '@rollup/rollup-darwin-x64': 4.24.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.24.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.24.0
+      '@rollup/rollup-linux-arm64-gnu': 4.24.0
+      '@rollup/rollup-linux-arm64-musl': 4.24.0
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.24.0
+      '@rollup/rollup-linux-s390x-gnu': 4.24.0
+      '@rollup/rollup-linux-x64-gnu': 4.24.0
+      '@rollup/rollup-linux-x64-musl': 4.24.0
+      '@rollup/rollup-win32-arm64-msvc': 4.24.0
+      '@rollup/rollup-win32-ia32-msvc': 4.24.0
+      '@rollup/rollup-win32-x64-msvc': 4.24.0
       fsevents: 2.3.3
 
   run-parallel@1.2.0:
@@ -4340,15 +4355,15 @@ snapshots:
       pkg-conf: 3.1.0
       xdg-basedir: 4.0.0
 
-  standard@17.1.0:
+  standard@17.1.2:
     dependencies:
       eslint: 8.57.0
       eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint-plugin-n@15.7.0(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0)
-      eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.34.1(eslint@8.57.0))(eslint@8.57.0)
+      eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.37.1(eslint@8.57.0))(eslint@8.57.0)
       eslint-plugin-import: 2.29.1(eslint@8.57.0)
       eslint-plugin-n: 15.7.0(eslint@8.57.0)
       eslint-plugin-promise: 6.1.1(eslint@8.57.0)
-      eslint-plugin-react: 7.34.1(eslint@8.57.0)
+      eslint-plugin-react: 7.37.1(eslint@8.57.0)
       standard-engine: 15.1.0
       version-guard: 1.1.2
     transitivePeerDependencies:
@@ -4406,6 +4421,11 @@ snapshots:
       es-abstract: 1.23.3
       es-object-atoms: 1.0.0
 
+  string.prototype.repeat@1.0.0:
+    dependencies:
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+
   string.prototype.trim@1.2.9:
     dependencies:
       call-bind: 1.0.7
diff --git a/scripts/devtools.rollup.config.js b/scripts/devtools.rollup.config.js
index 0694556..8e2e4e9 100644
--- a/scripts/devtools.rollup.config.js
+++ b/scripts/devtools.rollup.config.js
@@ -2,6 +2,7 @@ import typescript from '@rollup/plugin-typescript'
 import virtual from '@rollup/plugin-virtual'
 import replace from '@rollup/plugin-replace'
 import strip from '@rollup/plugin-strip'
+import inject from '@rollup/plugin-inject'
 
 // Stub out some modules to reduce bundle size
 const makeStub = (...names) => names
@@ -35,21 +36,65 @@ export default {
       __entry__: `
         export { HeapSnapshotLoader } from './front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker.ts'
         export { HeapSnapshotModel } from './front_end/models/heap_snapshot_model/heap_snapshot_model.ts'
+      `,
+      // Promise.withResolvers minimal shim
+      // TODO: remove when we set our minimum Node to v22+
+      __promise_with_resolvers__: `
+        export const withResolvers = () => {
+          let resolve
+          let reject
+          const promise = new Promise((_resolve, _reject) => {
+            resolve = _resolve
+            reject = _reject
+          })
+          return { resolve, reject, promise }
+        }
+      `,
+      // Array.prototype.toSorted minimal shim
+      // TODO: remove when we set our minimum Node to v22+
+      __to_sorted__: `
+        export const toSorted = (arr) => {
+          const res = [...arr]
+          res.sort()
+          return res
+        }
       `
     }),
     typescript({
       compilerOptions: {
         lib: ['esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.iterable'],
-        target: 'esnext'
+        target: 'esnext',
+        outDir: '../fuite/src/thirdparty/devtools-frontend'
       },
       noEmitOnError: false
     }),
     replace({
       values: {
-        'location.search': ''
+        'location.search': '',
+        // TODO: remove when we set our minimum Node to v22+
+        'Promise.withResolvers': 'PromiseWithResolversPolyfill'
       },
       preventAssignment: true
     }),
+
+    // TODO: remove when we set our minimum Node to v22+
+    replace({
+      values: {
+        'definition.properties.toSorted()': 'toSortedPolyfill(definition.properties)'
+      },
+      delimiters: ['', '']
+    }),
+    // TODO: remove when we set our minimum Node to v22+
+    inject({
+      PromiseWithResolversPolyfill: [
+        '__promise_with_resolvers__',
+        'withResolvers'
+      ],
+      toSortedPolyfill: [
+        '__to_sorted__',
+        'toSorted'
+      ]
+    }),
     strip({
       include: ['**/*.js', '**/*.ts'],
       // remove console.* calls
diff --git a/src/metrics/heapsnapshots/analyzeHeapsnapshots.js b/src/metrics/heapsnapshots/analyzeHeapsnapshots.js
index 4d0645f..2fba28f 100644
--- a/src/metrics/heapsnapshots/analyzeHeapsnapshots.js
+++ b/src/metrics/heapsnapshots/analyzeHeapsnapshots.js
@@ -40,9 +40,13 @@ export async function analyzeHeapSnapshots (startSnapshotFilename, endSnapshotFi
   let startSnapshot = await createHeapSnapshotModel(startSnapshotFilename)
   const startSnapshotUid = startSnapshot.uid
   const startStatistics = { ...startSnapshot.getStatistics() }
-  const aggregatesForDiff = await startSnapshot.aggregatesForDiff()
+  // For reference see:
+  // https://github.com/ChromeDevTools/devtools-frontend/blob/898fd09/front_end/panels/profiler/HeapSnapshotDataGrids.ts#L999-L1016
+  let interfaceDefinitions = await startSnapshot.interfaceDefinitions()
+  const aggregatesForDiff = await startSnapshot.aggregatesForDiff(interfaceDefinitions)
   const startAggregates = startSnapshot.aggregatesWithFilter(new HeapSnapshotModel.NodeFilter())
   startSnapshot = undefined // free memory
+  interfaceDefinitions = undefined // free memory
 
   const endSnapshot = await createHeapSnapshotModel(endSnapshotFilename)
   const endStatistics = { ...endSnapshot.getStatistics() }
diff --git a/src/thirdparty/devtools-frontend/index.js b/src/thirdparty/devtools-frontend/index.js
index e338829..6b75e69 100644
--- a/src/thirdparty/devtools-frontend/index.js
+++ b/src/thirdparty/devtools-frontend/index.js
@@ -602,6 +602,12 @@ class FunctionAllocationInfo {
     }
 }
 
+const toSorted = (arr) => {
+          const res = [...arr];
+          res.sort();
+          return res
+        };
+
 // Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -948,24 +954,6 @@ class Multimap {
     }
 }
 
-// Copyright 2023 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * Returns a new pending promise together with it's resolve and reject functions.
- *
- * Polyfill for https://github.com/tc39/proposal-promise-with-resolvers.
- */
-function promiseWithResolvers() {
-    let resolve;
-    let reject;
-    const promise = new Promise((res, rej) => {
-        resolve = res;
-        reject = rej;
-    });
-    return { promise, resolve, reject };
-}
-
 // Copyright (c) 2020 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -1139,6 +1127,12 @@ class BitVectorImpl extends Uint8Array {
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 new URLSearchParams();
+var HostConfigFreestylerExecutionMode;
+(function (HostConfigFreestylerExecutionMode) {
+    HostConfigFreestylerExecutionMode["ALL_SCRIPTS"] = "ALL_SCRIPTS";
+    HostConfigFreestylerExecutionMode["SIDE_EFFECT_FREE_SCRIPTS_ONLY"] = "SIDE_EFFECT_FREE_SCRIPTS_ONLY";
+    HostConfigFreestylerExecutionMode["NO_SCRIPTS"] = "NO_SCRIPTS";
+})(HostConfigFreestylerExecutionMode || (HostConfigFreestylerExecutionMode = {}));
 
 const LOCALES = ['en-GB'];
         const DEFAULT_LOCALE =  'en-GB';
@@ -1180,16 +1174,22 @@ function serializeUIString(string, values = {}) {
 const UIStrings$2 = {
     /**
      *@description μs is the short form of micro-seconds and the placeholder is a number
+     * The shortest form or abbreviation of micro-seconds should be used, as there is
+     * limited room in this UI.
      *@example {2} PH1
      */
     fmms: '{PH1} μs',
     /**
      *@description ms is the short form of milli-seconds and the placeholder is a decimal number
+     * The shortest form or abbreviation of milli-seconds should be used, as there is
+     * limited room in this UI.
      *@example {2.14} PH1
      */
     fms: '{PH1} ms',
     /**
      *@description s is short for seconds and the placeholder is a decimal number
+     * The shortest form or abbreviation of seconds should be used, as there is
+     * limited room in this UI.
      *@example {2.14} PH1
      */
     fs: '{PH1} s',
@@ -1208,40 +1208,15 @@ const UIStrings$2 = {
      *@example {2.2} PH1
      */
     fdays: '{PH1} days',
+    /**
+     *@description describes a number of milliseconds (the unit should not abbreviated)
+     *@example {2.14} PH1
+     */
+    fmsExpanded: '{PH1} milliseconds',
 };
 const str_$2 = registerUIStrings('core/i18n/time-utilities.ts', UIStrings$2);
 getLocalizedString.bind(undefined, str_$2);
 
-/*
- * Copyright (C) 2011 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-/* eslint-disable rulesdir/use_private_class_members */
 class HeapSnapshotEdge {
     snapshot;
     edges;
@@ -1513,7 +1488,7 @@ class HeapSnapshotNode {
         throw new Error('Not implemented');
     }
     rawName() {
-        return this.snapshot.strings[this.nameInternal()];
+        return this.snapshot.strings[this.rawNameIndex()];
     }
     isRoot() {
         return this.nodeIndex === this.snapshot.rootNodeIndex;
@@ -1564,7 +1539,7 @@ class HeapSnapshotNode {
     serialize() {
         return new Node(this.id(), this.name(), this.distance(), this.nodeIndex, this.retainedSize(), this.selfSize(), this.type());
     }
-    nameInternal() {
+    rawNameIndex() {
         const snapshot = this.snapshot;
         return snapshot.nodes.getValue(this.nodeIndex + snapshot.nodeNameOffset);
     }
@@ -1731,6 +1706,19 @@ class HeapSnapshotProblemReport {
 const BITMASK_FOR_DOM_LINK_STATE = 3;
 // The class index is stored in the upper 30 bits of the detachedness field.
 const SHIFT_FOR_CLASS_INDEX = 2;
+// The maximum number of results produced by inferInterfaceDefinitions.
+const MAX_INTERFACE_COUNT = 1000;
+// After this many properties, inferInterfaceDefinitions can stop adding more
+// properties to an interface definition if the name is getting too long.
+const MIN_INTERFACE_PROPERTY_COUNT = 1;
+// The maximum length of an interface name produced by inferInterfaceDefinitions.
+// This limit can be exceeded if the first MIN_INTERFACE_PROPERTY_COUNT property
+// names are long.
+const MAX_INTERFACE_NAME_LENGTH = 120;
+// Each interface definition produced by inferInterfaceDefinitions will match at
+// least this many objects. There's no point in defining interfaces which match
+// only a single object.
+const MIN_OBJECT_COUNT_PER_INTERFACE = 2;
 class HeapSnapshot {
     nodes;
     containmentEdges;
@@ -1803,6 +1791,8 @@ class HeapSnapshot {
     #edgeNamesThatAreNotWeakMaps;
     detachednessAndClassIndexArray;
     #essentialEdges;
+    #interfaceNames;
+    #interfaceDefinitions;
     constructor(profile, progress) {
         this.nodes = profile.nodes;
         this.containmentEdges = profile.edges;
@@ -1824,6 +1814,7 @@ class HeapSnapshot {
         this.#ignoredNodesInRetainersView = new Set();
         this.#ignoredEdgesInRetainersView = new Set();
         this.#edgeNamesThatAreNotWeakMaps = createBitVector(this.strings.length);
+        this.#interfaceNames = new Map();
     }
     initialize() {
         const meta = this.#metaNode;
@@ -1874,8 +1865,6 @@ class HeapSnapshot {
         this.retainingEdges = new Uint32Array(this.#edgeCount);
         this.firstRetainerIndex = new Uint32Array(this.nodeCount + 1);
         this.nodeDistances = new Int32Array(this.nodeCount);
-        this.firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1);
-        this.dominatedNodes = new Uint32Array(this.nodeCount - 1);
         this.#progress.updateStatus('Building edge indexes…');
         this.buildEdgeIndexes();
         this.#progress.updateStatus('Building retainers…');
@@ -1886,19 +1875,17 @@ class HeapSnapshot {
         this.calculateFlags();
         this.#progress.updateStatus('Calculating distances…');
         this.calculateDistances(/* isForRetainersView=*/ false);
-        this.#progress.updateStatus('Building postorder index…');
-        const result = this.buildPostOrderIndex();
-        // Actually it is array that maps node ordinal number to dominator node ordinal number.
-        this.#progress.updateStatus('Building dominator tree…');
-        this.dominatorsTree = this.buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex);
         this.#progress.updateStatus('Calculating shallow sizes…');
         this.calculateShallowSizes();
         this.#progress.updateStatus('Calculating retained sizes…');
-        this.calculateRetainedSizes(result.postOrderIndex2NodeOrdinal);
+        this.buildDominatorTreeAndCalculateRetainedSizes();
         this.#progress.updateStatus('Building dominated nodes…');
+        this.firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1);
+        this.dominatedNodes = new Uint32Array(this.nodeCount - 1);
         this.buildDominatedNodes();
         this.#progress.updateStatus('Calculating object names…');
         this.calculateObjectNames();
+        this.applyInterfaceDefinitions(this.inferInterfaceDefinitions());
         this.#progress.updateStatus('Calculating statistics…');
         this.calculateStatistics();
         this.#progress.updateStatus('Calculating samples…');
@@ -2220,12 +2207,16 @@ class HeapSnapshot {
         }
         return this.#allocationProfile.serializeAllocationStack(allocationNodeId);
     }
-    aggregatesForDiff() {
-        if (this.#aggregatesForDiffInternal) {
-            return this.#aggregatesForDiffInternal;
+    aggregatesForDiff(interfaceDefinitions) {
+        if (this.#aggregatesForDiffInternal?.interfaceDefinitions === interfaceDefinitions) {
+            return this.#aggregatesForDiffInternal.aggregates;
         }
+        // Temporarily apply the interface definitions from the other snapshot.
+        const originalInterfaceDefinitions = this.#interfaceDefinitions;
+        this.applyInterfaceDefinitions(JSON.parse(interfaceDefinitions));
         const aggregatesByClassName = this.getAggregatesByClassName(true, 'allObjects');
-        this.#aggregatesForDiffInternal = {};
+        this.applyInterfaceDefinitions(originalInterfaceDefinitions ?? []);
+        const result = {};
         const node = this.createNode();
         for (const className in aggregatesByClassName) {
             const aggregate = aggregatesByClassName[className];
@@ -2237,9 +2228,10 @@ class HeapSnapshot {
                 ids[i] = node.id();
                 selfSizes[i] = node.selfSize();
             }
-            this.#aggregatesForDiffInternal[className] = { indexes: indexes, ids: ids, selfSizes: selfSizes };
+            result[className] = { indexes, ids, selfSizes };
         }
-        return this.#aggregatesForDiffInternal;
+        this.#aggregatesForDiffInternal = { interfaceDefinitions, aggregates: result };
+        return result;
     }
     isUserRoot(_node) {
         return true;
@@ -2351,7 +2343,7 @@ class HeapSnapshot {
                 const nameMatters = nodeType === 'object' || nodeType === 'native';
                 const value = {
                     count: 1,
-                    distance: distance,
+                    distance,
                     self: selfSize,
                     maxRet: 0,
                     type: nodeType,
@@ -2382,7 +2374,7 @@ class HeapSnapshot {
             }
             classIndexValues.idxs = classIndexValues.idxs.slice();
         }
-        return { aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates };
+        return { aggregatesByClassName, aggregatesByClassIndex: aggregates };
     }
     calculateClassesRetainedSize(aggregates, filter) {
         const rootNodeIndex = this.rootNodeIndexInternal;
@@ -2467,6 +2459,11 @@ class HeapSnapshot {
         if (edgeType === this.edgeWeakType) {
             return false;
         }
+        const childNodeIndex = this.containmentEdges.getValue(edgeIndex + this.edgeToNodeOffset);
+        // Ignore self edges.
+        if (nodeIndex === childNodeIndex) {
+            return false;
+        }
         if (nodeIndex !== this.rootNodeIndex) {
             // Shortcuts at the root node have special meaning of marking user global objects.
             if (edgeType === this.edgeShortcutType) {
@@ -2475,7 +2472,6 @@ class HeapSnapshot {
             const flags = userObjectsMapAndFlag ? userObjectsMapAndFlag.map : null;
             const userObjectFlag = userObjectsMapAndFlag ? userObjectsMapAndFlag.flag : 0;
             const nodeOrdinal = nodeIndex / this.nodeFieldCount;
-            const childNodeIndex = this.containmentEdges.getValue(edgeIndex + this.edgeToNodeOffset);
             const childNodeOrdinal = childNodeIndex / this.nodeFieldCount;
             const nodeFlag = !flags || (flags[nodeOrdinal] & userObjectFlag);
             const childNodeFlag = !flags || (flags[childNodeOrdinal] & userObjectFlag);
@@ -2487,10 +2483,9 @@ class HeapSnapshot {
         }
         return true;
     }
-    /**
-     * The function checks whether the edge should be considered during building
-     * postorder iterator and dominator tree.
-     */
+    // Returns whether the edge should be considered when building the dominator tree.
+    // The first call to this function computes essential edges and caches them.
+    // Subsequent calls just lookup from the cache and are much faster.
     isEssentialEdge(edgeIndex) {
         let essentialEdges = this.#essentialEdges;
         if (!essentialEdges) {
@@ -2511,105 +2506,6 @@ class HeapSnapshot {
         }
         return essentialEdges.getBit(edgeIndex / this.edgeFieldsCount);
     }
-    buildPostOrderIndex() {
-        const nodeFieldCount = this.nodeFieldCount;
-        const nodeCount = this.nodeCount;
-        const rootNodeOrdinal = this.rootNodeIndexInternal / nodeFieldCount;
-        const edgeFieldsCount = this.edgeFieldsCount;
-        const edgeToNodeOffset = this.edgeToNodeOffset;
-        const firstEdgeIndexes = this.firstEdgeIndexes;
-        const containmentEdges = this.containmentEdges;
-        const stackNodes = new Uint32Array(nodeCount);
-        const stackCurrentEdge = new Uint32Array(nodeCount);
-        const postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount);
-        const nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount);
-        const visited = new Uint8Array(nodeCount);
-        let postOrderIndex = 0;
-        let stackTop = 0;
-        stackNodes[0] = rootNodeOrdinal;
-        stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal];
-        visited[rootNodeOrdinal] = 1;
-        let iteration = 0;
-        while (true) {
-            ++iteration;
-            while (stackTop >= 0) {
-                const nodeOrdinal = stackNodes[stackTop];
-                const edgeIndex = stackCurrentEdge[stackTop];
-                const edgesEnd = firstEdgeIndexes[nodeOrdinal + 1];
-                if (edgeIndex < edgesEnd) {
-                    stackCurrentEdge[stackTop] += edgeFieldsCount;
-                    if (!this.isEssentialEdge(edgeIndex)) {
-                        continue;
-                    }
-                    const childNodeIndex = containmentEdges.getValue(edgeIndex + edgeToNodeOffset);
-                    const childNodeOrdinal = childNodeIndex / nodeFieldCount;
-                    if (visited[childNodeOrdinal]) {
-                        continue;
-                    }
-                    ++stackTop;
-                    stackNodes[stackTop] = childNodeOrdinal;
-                    stackCurrentEdge[stackTop] = firstEdgeIndexes[childNodeOrdinal];
-                    visited[childNodeOrdinal] = 1;
-                }
-                else {
-                    // Done with all the node children
-                    nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex;
-                    postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal;
-                    --stackTop;
-                }
-            }
-            if (postOrderIndex === nodeCount || iteration > 1) {
-                break;
-            }
-            const errors = new HeapSnapshotProblemReport(`Heap snapshot: ${nodeCount - postOrderIndex} nodes are unreachable from the root. Following nodes have only weak retainers:`);
-            const dumpNode = this.rootNode();
-            // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
-            // visited after all orphan nodes and their subgraphs.
-            --postOrderIndex;
-            stackTop = 0;
-            stackNodes[0] = rootNodeOrdinal;
-            stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal + 1]; // no need to reiterate its edges
-            for (let i = 0; i < nodeCount; ++i) {
-                if (visited[i] || !this.hasOnlyWeakRetainers(i)) {
-                    continue;
-                }
-                // Add all nodes that have only weak retainers to traverse their subgraphs.
-                stackNodes[++stackTop] = i;
-                stackCurrentEdge[stackTop] = firstEdgeIndexes[i];
-                visited[i] = 1;
-                dumpNode.nodeIndex = i * nodeFieldCount;
-                const retainers = [];
-                for (let it = dumpNode.retainers(); it.hasNext(); it.next()) {
-                    retainers.push(`${it.item().node().name()}@${it.item().node().id()}.${it.item().name()}`);
-                }
-                errors.addError(`${dumpNode.name()} @${dumpNode.id()}  weak retainers: ${retainers.join(', ')}`);
-            }
-        }
-        // If we already processed all orphan nodes that have only weak retainers and still have some orphans...
-        if (postOrderIndex !== nodeCount) {
-            const errors = new HeapSnapshotProblemReport('Still found ' + (nodeCount - postOrderIndex) + ' unreachable nodes in heap snapshot:');
-            const dumpNode = this.rootNode();
-            // Remove root from the result (last node in the array) and put it at the bottom of the stack so that it is
-            // visited after all orphan nodes and their subgraphs.
-            --postOrderIndex;
-            for (let i = 0; i < nodeCount; ++i) {
-                if (visited[i]) {
-                    continue;
-                }
-                dumpNode.nodeIndex = i * nodeFieldCount;
-                errors.addError(dumpNode.name() + ' @' + dumpNode.id());
-                // Fix it by giving the node a postorder index anyway.
-                nodeOrdinal2PostOrderIndex[i] = postOrderIndex;
-                postOrderIndex2NodeOrdinal[postOrderIndex++] = i;
-            }
-            nodeOrdinal2PostOrderIndex[rootNodeOrdinal] = postOrderIndex;
-            postOrderIndex2NodeOrdinal[postOrderIndex++] = rootNodeOrdinal;
-        }
-        return {
-            postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal,
-            nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex,
-        };
-    }
     hasOnlyWeakRetainers(nodeOrdinal) {
         const retainingEdges = this.retainingEdges;
         const beginRetainerIndex = this.firstRetainerIndex[nodeOrdinal];
@@ -2622,148 +2518,195 @@ class HeapSnapshot {
         }
         return true;
     }
-    // The algorithm is based on the article:
-    // K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm"
-    // Softw. Pract. Exper. 4 (2001), pp. 1-10.
-    buildDominatorTree(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex) {
+    // The algorithm for building the dominator tree is from the paper:
+    // Thomas Lengauer and Robert Endre Tarjan. 1979. A fast algorithm for finding dominators in a flowgraph.
+    // ACM Trans. Program. Lang. Syst. 1, 1 (July 1979), 121–141. https://doi.org/10.1145/357062.357071
+    buildDominatorTreeAndCalculateRetainedSizes() {
+        // Preload fields into local variables for better performance.
+        const nodeCount = this.nodeCount;
+        const firstEdgeIndexes = this.firstEdgeIndexes;
+        const edgeFieldsCount = this.edgeFieldsCount;
+        const containmentEdges = this.containmentEdges;
+        const edgeToNodeOffset = this.edgeToNodeOffset;
         const nodeFieldCount = this.nodeFieldCount;
         const firstRetainerIndex = this.firstRetainerIndex;
-        const retainingNodes = this.retainingNodes;
         const retainingEdges = this.retainingEdges;
-        const edgeFieldsCount = this.edgeFieldsCount;
-        const edgeToNodeOffset = this.edgeToNodeOffset;
-        const firstEdgeIndexes = this.firstEdgeIndexes;
-        const containmentEdges = this.containmentEdges;
-        const nodesCount = postOrderIndex2NodeOrdinal.length;
-        const rootPostOrderedIndex = nodesCount - 1;
-        const noEntry = nodesCount;
-        const dominators = new Uint32Array(nodesCount);
-        for (let i = 0; i < rootPostOrderedIndex; ++i) {
-            dominators[i] = noEntry;
-        }
-        dominators[rootPostOrderedIndex] = rootPostOrderedIndex;
-        // The affected array is used to mark entries which dominators have to be
-        // recalculated because of changes in their retainers. This is just a
-        // heuristic to guide the fixpoint algorithm below so that it can visit
-        // nodes that are more likely to need modification; see crbug.com/361372448
-        // for an example where visiting only affected nodes is insufficient.
-        const affected = createBitVector(nodesCount);
-        let nodeOrdinal;
-        // Time wasters are nodes with lots of predecessors which didn't change the
-        // last time we visited them. We'll avoid marking such nodes as affected.
-        const timeWasters = createBitVector(nodesCount);
-        { // Mark the root direct children as affected.
-            nodeOrdinal = this.rootNodeIndexInternal / nodeFieldCount;
-            const endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
-            for (let edgeIndex = firstEdgeIndexes[nodeOrdinal]; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
-                if (!this.isEssentialEdge(edgeIndex)) {
-                    continue;
-                }
-                const childNodeOrdinal = containmentEdges.getValue(edgeIndex + edgeToNodeOffset) / nodeFieldCount;
-                affected.setBit(nodeOrdinal2PostOrderIndex[childNodeOrdinal]);
-            }
-        }
-        let changed = true;
-        let shouldVisitEveryNode = false;
-        while (changed || !shouldVisitEveryNode) {
-            // The original Cooper-Harvey-Kennedy algorithm visits every node on every traversal,
-            // but that is far too expensive for the graph shapes encountered in heap snapshots.
-            // Instead, we use the `affected` bitvector to guide iteration most of the time, and
-            // only do a full traversal if the previous bitvector-based traversal found nothing
-            // to change. The order in which nodes are visited doesn't matter for correctness.
-            shouldVisitEveryNode = !changed;
-            function getPrevious(postOrderIndex) {
-                return shouldVisitEveryNode ? postOrderIndex - 1 : affected.previous(postOrderIndex);
-            }
-            changed = false;
-            for (let postOrderIndex = getPrevious(rootPostOrderedIndex); postOrderIndex >= 0; postOrderIndex = getPrevious(postOrderIndex)) {
-                affected.clearBit(postOrderIndex);
-                // If dominator of the entry has already been set to root,
-                // then it can't propagate any further.
-                if (dominators[postOrderIndex] === rootPostOrderedIndex) {
-                    continue;
+        const retainingNodes = this.retainingNodes;
+        const rootNodeOrdinal = this.rootNodeIndexInternal / nodeFieldCount;
+        const isEssentialEdge = this.isEssentialEdge.bind(this);
+        const hasOnlyWeakRetainers = this.hasOnlyWeakRetainers.bind(this);
+        // The Lengauer-Tarjan algorithm expects vectors to be numbered from 1 to n
+        // and uses 0 as an invalid value, so use 1-indexing for all the arrays.
+        // Convert between ordinals and vertex numbers by adding/subtracting 1.
+        const arrayLength = nodeCount + 1;
+        const parent = new Uint32Array(arrayLength);
+        const ancestor = new Uint32Array(arrayLength);
+        const vertex = new Uint32Array(arrayLength);
+        const label = new Uint32Array(arrayLength);
+        const semi = new Uint32Array(arrayLength);
+        const bucket = new Array(arrayLength);
+        let n = 0;
+        // Iterative DFS since the recursive version can cause stack overflows.
+        // Use an array to keep track of the next edge index to be examined for each node.
+        const nextEdgeIndex = new Uint32Array(arrayLength);
+        const dfs = (root) => {
+            const rootOrdinal = root - 1;
+            nextEdgeIndex[rootOrdinal] = firstEdgeIndexes[rootOrdinal];
+            let v = root;
+            while (v !== 0) {
+                // First process v if not done already.
+                if (semi[v] === 0) {
+                    semi[v] = ++n;
+                    vertex[n] = label[v] = v;
                 }
-                nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
-                let newDominatorIndex = noEntry;
-                const beginRetainerIndex = firstRetainerIndex[nodeOrdinal];
-                const endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1];
-                const edgeCount = endRetainerIndex - beginRetainerIndex;
-                let orphanNode = true;
-                for (let retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) {
-                    const retainerEdgeIndex = retainingEdges[retainerIndex];
-                    const retainerNodeIndex = retainingNodes[retainerIndex];
-                    if (!this.isEssentialEdge(retainerEdgeIndex)) {
+                // The next node to process is the first unprocessed successor w of v,
+                // or parent[v] if all of v's successors have already been processed.
+                let vNext = parent[v];
+                const vOrdinal = v - 1;
+                for (; nextEdgeIndex[vOrdinal] < firstEdgeIndexes[vOrdinal + 1]; nextEdgeIndex[vOrdinal] += edgeFieldsCount) {
+                    const edgeIndex = nextEdgeIndex[vOrdinal];
+                    if (!isEssentialEdge(edgeIndex)) {
                         continue;
                     }
-                    orphanNode = false;
-                    const retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount;
-                    let retainerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal];
-                    if (dominators[retainerPostOrderIndex] !== noEntry) {
-                        if (newDominatorIndex === noEntry) {
-                            newDominatorIndex = retainerPostOrderIndex;
-                        }
-                        else {
-                            while (retainerPostOrderIndex !== newDominatorIndex) {
-                                while (retainerPostOrderIndex < newDominatorIndex) {
-                                    retainerPostOrderIndex = dominators[retainerPostOrderIndex];
-                                }
-                                while (newDominatorIndex < retainerPostOrderIndex) {
-                                    newDominatorIndex = dominators[newDominatorIndex];
-                                }
-                            }
-                        }
-                        // If item has already reached the root, it doesn't make sense
-                        // to check other retainers.
-                        if (newDominatorIndex === rootPostOrderedIndex) {
-                            break;
-                        }
+                    const wOrdinal = containmentEdges.getValue(edgeIndex + edgeToNodeOffset) / nodeFieldCount;
+                    const w = wOrdinal + 1;
+                    if (semi[w] === 0) {
+                        parent[w] = v;
+                        nextEdgeIndex[wOrdinal] = firstEdgeIndexes[wOrdinal];
+                        vNext = w;
+                        break;
                     }
                 }
-                // Make root dominator of orphans.
-                if (orphanNode) {
-                    newDominatorIndex = rootPostOrderedIndex;
+                v = vNext;
+            }
+        };
+        // Iterative version since the recursive version can cause stack overflows.
+        // Preallocate a stack since compress() is called several times.
+        // The stack cannot grow larger than the number of nodes since we walk up
+        // the tree represented by the ancestor array.
+        const compressionStack = new Uint32Array(arrayLength);
+        const compress = (v) => {
+            let stackPointer = 0;
+            while (ancestor[ancestor[v]] !== 0) {
+                compressionStack[++stackPointer] = v;
+                v = ancestor[v];
+            }
+            while (stackPointer > 0) {
+                const w = compressionStack[stackPointer--];
+                if (semi[label[ancestor[w]]] < semi[label[w]]) {
+                    label[w] = label[ancestor[w]];
                 }
-                if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) {
-                    timeWasters.clearBit(postOrderIndex); // It's not a waste of time if we changed something.
-                    dominators[postOrderIndex] = newDominatorIndex;
-                    changed = true;
-                    nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
-                    const beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset;
-                    const endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1];
-                    for (let toNodeFieldIndex = beginEdgeToNodeFieldIndex; toNodeFieldIndex < endEdgeToNodeFieldIndex; toNodeFieldIndex += edgeFieldsCount) {
-                        const childNodeOrdinal = containmentEdges.getValue(toNodeFieldIndex) / nodeFieldCount;
-                        const childPostOrderIndex = nodeOrdinal2PostOrderIndex[childNodeOrdinal];
-                        // Mark the child node as affected, unless it's unlikely to be beneficial.
-                        if (childPostOrderIndex !== postOrderIndex && !timeWasters.getBit(childPostOrderIndex)) {
-                            affected.setBit(childPostOrderIndex);
-                        }
-                    }
+                ancestor[w] = ancestor[ancestor[w]];
+            }
+        };
+        // Simple versions of eval and link from the paper.
+        const evaluate = (v) => {
+            if (ancestor[v] === 0) {
+                return v;
+            }
+            compress(v);
+            return label[v];
+        };
+        const link = (v, w) => {
+            ancestor[w] = v;
+        };
+        // Algorithm begins here. The variable names are as per the paper.
+        const r = rootNodeOrdinal + 1;
+        n = 0;
+        const dom = new Uint32Array(arrayLength);
+        // First perform DFS from the root.
+        dfs(r);
+        // Then perform DFS from orphan nodes (ones with only weak retainers) if any.
+        if (n < nodeCount) {
+            const errors = new HeapSnapshotProblemReport(`Heap snapshot: ${nodeCount - n} nodes are unreachable from the root.`);
+            errors.addError('The following nodes have only weak retainers:');
+            const dumpNode = this.rootNode();
+            for (let v = 1; v <= nodeCount; v++) {
+                const vOrdinal = v - 1;
+                if (semi[v] === 0 && hasOnlyWeakRetainers(vOrdinal)) {
+                    dumpNode.nodeIndex = vOrdinal * nodeFieldCount;
+                    errors.addError(`${dumpNode.name()} @${dumpNode.id()}`);
+                    parent[v] = r;
+                    dfs(v);
+                }
+            }
+        }
+        // If there are unreachable nodes still, visit them individually from the root.
+        // This can happen when there is a clique of nodes retained by one another.
+        if (n < nodeCount) {
+            const errors = new HeapSnapshotProblemReport(`Heap snapshot: Still found ${nodeCount - n} unreachable nodes:`);
+            const dumpNode = this.rootNode();
+            for (let v = 1; v <= nodeCount; v++) {
+                if (semi[v] === 0) {
+                    const vOrdinal = v - 1;
+                    dumpNode.nodeIndex = vOrdinal * nodeFieldCount;
+                    errors.addError(`${dumpNode.name()} @${dumpNode.id()}`);
+                    parent[v] = r;
+                    semi[v] = ++n;
+                    vertex[n] = label[v] = v;
                 }
-                else if (edgeCount > 1000) {
-                    timeWasters.setBit(postOrderIndex);
+            }
+        }
+        // Main loop. Process the vertices in decreasing order by DFS number.
+        for (let i = n; i >= 2; --i) {
+            const w = vertex[i];
+            // Iterate over all predecessors v of w.
+            const wOrdinal = w - 1;
+            let isOrphanNode = true;
+            for (let retainerIndex = firstRetainerIndex[wOrdinal]; retainerIndex < firstRetainerIndex[wOrdinal + 1]; retainerIndex++) {
+                if (!isEssentialEdge(retainingEdges[retainerIndex])) {
+                    continue;
+                }
+                isOrphanNode = false;
+                const vOrdinal = retainingNodes[retainerIndex] / nodeFieldCount;
+                const v = vOrdinal + 1;
+                const u = evaluate(v);
+                if (semi[u] < semi[w]) {
+                    semi[w] = semi[u];
+                }
+            }
+            if (isOrphanNode) {
+                // We treat orphan nodes as having a single predecessor - the root.
+                // semi[r] is always less than any semi[w] so set it unconditionally.
+                semi[w] = semi[r];
+            }
+            if (bucket[vertex[semi[w]]] === undefined) {
+                bucket[vertex[semi[w]]] = new Set();
+            }
+            bucket[vertex[semi[w]]].add(w);
+            link(parent[w], w);
+            // Process all vertices v in bucket(parent(w)).
+            if (bucket[parent[w]] !== undefined) {
+                for (const v of bucket[parent[w]]) {
+                    const u = evaluate(v);
+                    dom[v] = semi[u] < semi[v] ? u : parent[w];
                 }
+                bucket[parent[w]].clear();
             }
         }
-        const dominatorsTree = new Uint32Array(nodesCount);
-        for (let postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) {
-            nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
-            dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]];
+        // Final step. Fill in the immediate dominators not explicitly computed above.
+        // Unlike the paper, we consider the root to be its own dominator and
+        // set dom[0] to r to propagate the root as the dominator of unreachable nodes.
+        dom[0] = dom[r] = r;
+        for (let i = 2; i <= n; i++) {
+            const w = vertex[i];
+            if (dom[w] !== vertex[semi[w]]) {
+                dom[w] = dom[dom[w]];
+            }
         }
-        return dominatorsTree;
-    }
-    calculateRetainedSizes(postOrderIndex2NodeOrdinal) {
-        const nodeCount = this.nodeCount;
+        // Algorithm ends here.
+        // Transform the dominators into an ordinal-indexed array and populate the self sizes.
         const nodes = this.nodes;
         const nodeSelfSizeOffset = this.nodeSelfSizeOffset;
-        const nodeFieldCount = this.nodeFieldCount;
-        const dominatorsTree = this.dominatorsTree;
+        const dominatorsTree = this.dominatorsTree = new Uint32Array(nodeCount);
         const retainedSizes = this.retainedSizes;
-        for (let nodeOrdinal = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) {
+        for (let nodeOrdinal = 0; nodeOrdinal < nodeCount; nodeOrdinal++) {
+            dominatorsTree[nodeOrdinal] = dom[nodeOrdinal + 1] - 1;
             retainedSizes[nodeOrdinal] = nodes.getValue(nodeOrdinal * nodeFieldCount + nodeSelfSizeOffset);
         }
-        // Propagate retained sizes for each node excluding root.
-        for (let postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) {
-            const nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
+        // Then propagate up the retained sizes for each traversed node excluding the root.
+        for (let i = n; i > 1; i--) {
+            const nodeOrdinal = vertex[i] - 1;
             const dominatorOrdinal = dominatorsTree[nodeOrdinal];
             retainedSizes[dominatorOrdinal] += retainedSizes[nodeOrdinal];
         }
@@ -2883,6 +2826,177 @@ class HeapSnapshot {
             node.nodeIndex = node.nextNodeIndex();
         }
     }
+    interfaceDefinitions() {
+        return JSON.stringify(this.#interfaceDefinitions ?? []);
+    }
+    isPlainJSObject(node) {
+        return node.rawType() === this.nodeObjectType && node.rawName() === 'Object';
+    }
+    inferInterfaceDefinitions() {
+        const { edgePropertyType } = this;
+        // A map from interface names to their definitions.
+        const candidates = new Map();
+        for (let it = this.allNodes(); it.hasNext(); it.next()) {
+            const node = it.item();
+            if (!this.isPlainJSObject(node)) {
+                continue;
+            }
+            let interfaceName = '{';
+            const properties = [];
+            for (let edgeIt = node.edges(); edgeIt.hasNext(); edgeIt.next()) {
+                const edge = edgeIt.item();
+                const edgeName = edge.name();
+                if (edge.rawType() !== edgePropertyType || edgeName === '__proto__') {
+                    continue;
+                }
+                const formattedEdgeName = JSHeapSnapshotNode.formatPropertyName(edgeName);
+                if (interfaceName.length > MIN_INTERFACE_PROPERTY_COUNT &&
+                    interfaceName.length + formattedEdgeName.length > MAX_INTERFACE_NAME_LENGTH) {
+                    break; // The interface name is getting too long.
+                }
+                if (interfaceName.length !== 1) {
+                    interfaceName += ', ';
+                }
+                interfaceName += formattedEdgeName;
+                properties.push(edgeName);
+            }
+            // The empty interface is not a very meaningful, and can be sort of misleading
+            // since someone might incorrectly interpret it as objects with no properties.
+            if (properties.length === 0) {
+                continue;
+            }
+            interfaceName += '}';
+            const candidate = candidates.get(interfaceName);
+            if (candidate) {
+                ++candidate.count;
+            }
+            else {
+                candidates.set(interfaceName, { name: interfaceName, properties, count: 1 });
+            }
+        }
+        // Next, sort the candidates and select the most popular ones. It's possible that
+        // some candidates represent the same properties in different orders, but that's
+        // okay: by sorting here, we ensure that the most popular ordering appears first
+        // in the result list, and the rules for applying interface definitions will prefer
+        // the first matching definition if multiple matches contain the same properties.
+        const sortedCandidates = Array.from(candidates.values());
+        sortedCandidates.sort((a, b) => b.count - a.count);
+        const result = [];
+        const maxResultSize = Math.min(sortedCandidates.length, MAX_INTERFACE_COUNT);
+        for (let i = 0; i < maxResultSize; ++i) {
+            const candidate = sortedCandidates[i];
+            if (candidate.count < MIN_OBJECT_COUNT_PER_INTERFACE) {
+                break;
+            }
+            result.push(candidate);
+        }
+        return result;
+    }
+    applyInterfaceDefinitions(definitions) {
+        const { edgePropertyType } = this;
+        this.#interfaceDefinitions = definitions;
+        // Any computed aggregate data will be wrong after recategorization, so clear it.
+        this.#aggregates = {};
+        this.#aggregatesSortedFlags = {};
+        function selectBetterMatch(a, b) {
+            if (!b || a.propertyCount > b.propertyCount) {
+                return a;
+            }
+            if (b.propertyCount > a.propertyCount) {
+                return b;
+            }
+            return a.index <= b.index ? a : b;
+        }
+        // The root node of the tree.
+        const propertyTree = {
+            next: new Map(),
+            matchInfo: null,
+            greatestNext: null,
+        };
+        // Build up the property tree.
+        for (let interfaceIndex = 0; interfaceIndex < definitions.length; ++interfaceIndex) {
+            const definition = definitions[interfaceIndex];
+            const properties = toSorted(definition.properties);
+            let currentNode = propertyTree;
+            for (const property of properties) {
+                const nextMap = currentNode.next;
+                let nextNode = nextMap.get(property);
+                if (!nextNode) {
+                    nextNode = {
+                        next: new Map(),
+                        matchInfo: null,
+                        greatestNext: null,
+                    };
+                    nextMap.set(property, nextNode);
+                    if (currentNode.greatestNext === null || currentNode.greatestNext < property) {
+                        currentNode.greatestNext = property;
+                    }
+                }
+                currentNode = nextNode;
+            }
+            // Only set matchInfo on this node if it wasn't already set, to ensure that
+            // interfaces defined earlier in the list have priority.
+            if (!currentNode.matchInfo) {
+                currentNode.matchInfo = {
+                    name: definition.name,
+                    propertyCount: properties.length,
+                    index: interfaceIndex,
+                };
+            }
+        }
+        // The fallback match for objects which don't match any defined interface.
+        const initialMatch = {
+            name: 'Object',
+            propertyCount: 0,
+            index: Infinity,
+        };
+        // Iterate all nodes and check whether they match a named interface, using
+        // the tree constructed above. Then update the class name for each node.
+        for (let it = this.allNodes(); it.hasNext(); it.next()) {
+            const node = it.item();
+            if (!this.isPlainJSObject(node)) {
+                continue;
+            }
+            // Collect and sort the properties of this object.
+            const properties = [];
+            for (let edgeIt = node.edges(); edgeIt.hasNext(); edgeIt.next()) {
+                const edge = edgeIt.item();
+                if (edge.rawType() === edgePropertyType) {
+                    properties.push(edge.name());
+                }
+            }
+            properties.sort();
+            // We may explore multiple possible paths through the tree, so this set tracks
+            // all states that match with the properties iterated thus far.
+            const states = new Set();
+            states.add(propertyTree);
+            // This variable represents the best match found thus far. We start by checking
+            // whether there is an interface definition for the empty object.
+            let match = selectBetterMatch(initialMatch, propertyTree.matchInfo);
+            // Traverse the tree to find any matches.
+            for (const property of properties) {
+                // Iterate only the states that already exist, not the ones added during the loop below.
+                for (const currentState of Array.from(states.keys())) {
+                    if (currentState.greatestNext === null || property >= currentState.greatestNext) {
+                        // No further transitions are possible from this state.
+                        states.delete(currentState);
+                    }
+                    const nextState = currentState.next.get(property);
+                    if (nextState) {
+                        states.add(nextState);
+                        match = selectBetterMatch(match, nextState.matchInfo);
+                    }
+                }
+            }
+            // Update the node's class name accordingly.
+            let classIndex = match === initialMatch ? node.rawNameIndex() : this.#interfaceNames.get(match.name);
+            if (classIndex === undefined) {
+                classIndex = this.addString(match.name);
+                this.#interfaceNames.set(match.name, classIndex);
+            }
+            node.setClassIndex(classIndex);
+        }
+    }
     /**
      * Iterates children of a node.
      */
@@ -3567,7 +3681,7 @@ class JSHeapSnapshot extends HeapSnapshot {
     constructor(profile, progress) {
         super(profile, progress);
         this.nodeFlags = {
-            // bit flags
+            // bit flags in 8-bit value
             canBeQueried: 1,
             detachedDOMTreeNode: 2,
             pageObject: 4, // The idea is to track separately the objects owned by the page and the objects owned by debugger.
@@ -3594,7 +3708,7 @@ class JSHeapSnapshot extends HeapSnapshot {
         return filter;
     }
     calculateFlags() {
-        this.flags = new Uint32Array(this.nodeCount);
+        this.flags = new Uint8Array(this.nodeCount);
         this.markDetachedDOMTreeNodes();
         this.markQueriableHeapObjects();
         this.markPageOwnedNodes();
@@ -4225,6 +4339,16 @@ class JSHeapSnapshotRetainerEdge extends HeapSnapshotRetainerEdge {
     }
 }
 
+const withResolvers = () => {
+          let resolve;
+          let reject;
+          const promise = new Promise((_resolve, _reject) => {
+            resolve = _resolve;
+            reject = _reject;
+          });
+          return { resolve, reject, promise }
+        };
+
 /*
  * Copyright (C) 2013 Google Inc. All rights reserved.
  *
@@ -4477,35 +4601,6 @@ const UIStrings = {
 const str_ = registerUIStrings('core/common/SettingRegistration.ts', UIStrings);
 getLocalizedString.bind(undefined, str_);
 
-/*
- * Copyright (C) 2012 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
 class HeapSnapshotLoader {
     #progress;
     #buffer;
@@ -4615,7 +4710,7 @@ class HeapSnapshotLoader {
         if (this.#buffer.length > 0) {
             return Promise.resolve(this.#buffer.shift());
         }
-        const { promise, resolve } = promiseWithResolvers();
+        const { promise, resolve } = withResolvers();
         this.#dataCallback = resolve;
         return promise;
     }