From 487842006c9fae65e9ef8f0efdf8556ab39e28a4 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 3 May 2026 13:59:38 +0800 Subject: [PATCH] feat(gdpr): configurable privacy banner (PR4 of #6701) (#7549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: PR4 GDPR privacy banner design spec * docs: PR4 GDPR privacy banner implementation plan * feat(gdpr): typed privacyBanner setting block + public getter exposure * feat(gdpr): send privacyBanner config to the browser via clientVars * feat(gdpr): privacy banner DOM (hidden by default) * feat(gdpr): render privacy banner on pad load when enabled * style(gdpr): privacy banner layout * test+fix(gdpr): privacy banner Playwright + hidden-attr CSS override * docs(gdpr): privacyBanner configuration section * fix(gdpr): reject unsafe learnMoreUrl schemes Qodo review: showPrivacyBannerIfEnabled assigned config.learnMoreUrl directly to , so a misconfigured settings.privacyBanner. learnMoreUrl of `javascript:alert(1)` or `data:…', + dismissal: 'sticky', + }); + await expect(page.locator(`${NOTICE} a`)).toHaveCount(0); + }); + + test('unknown dismissal value is treated as dismissible (defense-in-depth)', + async ({page}) => { + // Server-side reloadSettings() coerces unknown strings to + // 'dismissible' with a warn, but the client guards too in case a + // hot-reload or custom build path skips that validation. + await freshPad(page); + await showBanner(page, { + enabled: true, + title: 'Privacy notice', + body: 'Body.', + learnMoreUrl: null, + dismissal: 'wat' as any, + }); + const item = page.locator(NOTICE); + await expect(item).toBeVisible(); + await item.locator('.gritter-close').click(); + await expect(page.locator(NOTICE)).toHaveCount(0); + const flag = await page.evaluate( + (prefix) => localStorage.getItem(`${prefix}${location.origin}`), + STORAGE_PREFIX); + expect(flag).toBe('1'); + }); + + test('mailto: learnMoreUrl is allowed', async ({page}) => { + await freshPad(page); + await showBanner(page, { + enabled: true, + title: 'Privacy notice', + body: 'Body.', + learnMoreUrl: 'mailto:privacy@example.com', + dismissal: 'sticky', + }); + await expect(page.locator(`${NOTICE} a`)) + .toHaveAttribute('href', 'mailto:privacy@example.com'); + }); +});