diff --git a/.changeset/gentle-gorillas-sit.md b/.changeset/gentle-gorillas-sit.md new file mode 100644 index 000000000000..10f75d19ff37 --- /dev/null +++ b/.changeset/gentle-gorillas-sit.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes an issue where dev toolbar x-ray didn't escape props content. diff --git a/packages/astro/e2e/dev-toolbar.test.js b/packages/astro/e2e/dev-toolbar.test.js index b30d2709819d..ac8d7b4dff59 100644 --- a/packages/astro/e2e/dev-toolbar.test.js +++ b/packages/astro/e2e/dev-toolbar.test.js @@ -100,6 +100,34 @@ test.describe('Dev Toolbar', () => { await expect(xrayHighlightTooltip).not.toBeVisible(); }); + test('xray escapes props content', async ({ page, astro }) => { + let isAlertCalled = false; + page.on('dialog', async (dialog) => { + isAlertCalled = true; + await dialog.accept(); + }); + + await page.goto(astro.resolveUrl('/xray-props-escape')); + + const toolbar = page.locator('astro-dev-toolbar'); + const appButton = toolbar.locator('button[data-app-id="astro:xray"]'); + await appButton.click(); + + const xrayCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:xray"]'); + const xrayHighlight = xrayCanvas.locator('astro-dev-toolbar-highlight'); + await expect(xrayHighlight).toBeVisible(); + + await xrayHighlight.hover(); + const xrayHighlightTooltip = xrayHighlight.locator('astro-dev-toolbar-tooltip'); + await expect(xrayHighlightTooltip).toBeVisible(); + + const code = xrayHighlightTooltip.locator('pre > code'); + await expect(code).toHaveText( + JSON.stringify({ name: `` }, undefined, 2) + ); + expect(isAlertCalled).toBe(false); + }); + test('xray shows no islands message when there are none', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/xray-no-islands')); diff --git a/packages/astro/e2e/fixtures/dev-toolbar/src/pages/xray-props-escape.astro b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/xray-props-escape.astro new file mode 100644 index 000000000000..f2e5049dc4ef --- /dev/null +++ b/packages/astro/e2e/fixtures/dev-toolbar/src/pages/xray-props-escape.astro @@ -0,0 +1,9 @@ +--- +import { HelloWorld } from '../components/HelloWorld'; +--- + +

Hello World

+`} + client:load +/> diff --git a/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts b/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts index ae1910502d1d..893f6f6b46fb 100644 --- a/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts +++ b/packages/astro/src/runtime/client/dev-toolbar/apps/xray.ts @@ -1,3 +1,4 @@ +import { escape as escapeHTML } from 'html-escaper'; import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js'; import type { DevToolbarHighlight } from '../ui-library/highlight.js'; import { @@ -137,13 +138,14 @@ export default { (prop: any) => !prop[0].startsWith('data-astro-cid-') ); if (islandPropsEntries.length > 0) { + const stringifiedProps = JSON.stringify( + Object.fromEntries(islandPropsEntries.map((prop: any) => [prop[0], prop[1][1]])), + undefined, + 2 + ); tooltip.sections.push({ title: 'Props', - content: `
${JSON.stringify(
-						Object.fromEntries(islandPropsEntries.map((prop: any) => [prop[0], prop[1][1]])),
-						undefined,
-						2
-					)}
`, + content: `
${escapeHTML(stringifiedProps)}
`, }); }