+
+
+
+
diff --git a/packages/astro/e2e/fixtures/dev-toolbar/src/pages/audits-mutations.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/audits-mutations.astro
new file mode 100644
index 0000000000..1c4950a2ff
--- /dev/null
+++ b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/audits-mutations.astro
@@ -0,0 +1,28 @@
+---
+import Layout from "../layout/Layout.astro";
+---
+
+
+
+Go to Mutations 2
+
+
+
+
+
diff --git a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts
index e07e6c6ac8..1e7e3009c2 100644
--- a/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts
+++ b/packages/astro/src/runtime/client/dev-toolbar/apps/audit/index.ts
@@ -10,6 +10,7 @@ import {
import { closeOnOutsideClick, createWindowElement } from '../utils/window.js';
import { a11y } from './a11y.js';
import { perf } from './perf.js';
+import { settings } from '../../settings.js';
const icon =
'';
@@ -69,8 +70,51 @@ export default {
await lint();
- document.addEventListener('astro:after-swap', async () => lint());
- document.addEventListener('astro:page-load', async () => refreshLintPositions);
+ let mutationDebounce: ReturnType;
+ const observer = new MutationObserver(() => {
+ // We don't want to rerun the audit lints on every single mutation, so we'll debounce it.
+ if (mutationDebounce) {
+ clearTimeout(mutationDebounce);
+ }
+
+ mutationDebounce = setTimeout(() => {
+ settings.logger.verboseLog('Rerunning audit lints because the DOM has been updated.');
+
+ // Even though we're ready to run the lints, we'll wait for the next idle period to do so, as it is less likely
+ // to interfere with any other work the browser is doing post-mutation. For instance, the page or the user might
+ // be interacting with the newly added elements, or the browser might be doing some work (layout, paint, etc.)
+ if ('requestIdleCallback' in window) {
+ window.requestIdleCallback(
+ async () => {
+ lint();
+ },
+ { timeout: 300 }
+ );
+ } else {
+ // Fallback for old versions of Safari, we'll assume that things are less likely to be busy after 150ms.
+ setTimeout(() => {
+ lint();
+ }, 150);
+ }
+ }, 250);
+ });
+
+ setupObserver();
+
+ document.addEventListener('astro:before-preparation', () => {
+ observer.disconnect();
+ });
+ document.addEventListener('astro:after-swap', async () => {
+ lint();
+ });
+ document.addEventListener('astro:page-load', async () => {
+ refreshLintPositions();
+
+ // HACK: View transitions add a route announcer after this event, so we need to wait for it to be added
+ setTimeout(() => {
+ setupObserver();
+ }, 100);
+ });
closeOnOutsideClick(eventTarget);
@@ -380,5 +424,12 @@ export default {
.replace(/"/g, '"')
.replace(/'/g, ''');
}
+
+ function setupObserver() {
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ });
+ }
},
} satisfies DevToolbarApp;