From dc9895f3aaceb68a643702ee40cc31e58bdb7913 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Wed, 25 Sep 2024 08:33:22 +0100 Subject: [PATCH 1/3] android support for toggle report --- integration-tests/helpers.js | 5 +- shared/js/browser/android-communication.js | 63 +++++++++++++++++++ shared/js/browser/common.js | 14 +++-- shared/js/browser/macos-communication.js | 4 +- .../js/browser/utils/communication-mocks.mjs | 47 +++++++++----- shared/js/ui/components/toggle-report.jsx | 12 ++-- .../toggle-report/toggle-report.scss | 6 +- .../toggle-report/use-ios-animation.js | 4 +- types.ts | 4 ++ 9 files changed, 129 insertions(+), 30 deletions(-) diff --git a/integration-tests/helpers.js b/integration-tests/helpers.js index e85e8f26..61d501e2 100644 --- a/integration-tests/helpers.js +++ b/integration-tests/helpers.js @@ -27,6 +27,9 @@ export async function playTimeline(page, state, platform) { if (platform.name === 'windows') { messages.windowsViewModel = state.toWindowsViewModel() } + if (platform.name === 'ios' || platform.name === 'macos') { + messages.privacyDashboardGetToggleReportOptions = toggleReportScreen + } await page.evaluate(mockDataProvider, { state, platform, messages }) return messages } @@ -52,7 +55,7 @@ export function installWindowsMocks(page) { export async function installWebkitMocks(page, _args) { await page.waitForFunction(() => typeof window.onChangeRequestData === 'function') return page.evaluate(webkitMockApis, { - responses: { + messages: { privacyDashboardGetToggleReportOptions: toggleReportScreen, }, }) diff --git a/shared/js/browser/android-communication.js b/shared/js/browser/android-communication.js index ed474b00..473ceebb 100644 --- a/shared/js/browser/android-communication.js +++ b/shared/js/browser/android-communication.js @@ -18,7 +18,11 @@ import { setupBlurOnLongPress, setupGlobalOpenerListener } from '../ui/views/uti import { CheckBrokenSiteReportHandledMessage, CloseMessage, + FetchToggleReportOptions, OpenSettingsMessages, + RejectToggleBreakageReport, + SeeWhatIsSent, + SendToggleBreakageReport, SetListsMessage, setupColorScheme, SubmitBrokenSiteReportMessage, @@ -322,6 +326,49 @@ export class PrivacyDashboardJavascriptInterface { invariant(typeof window.PrivacyDashboard?.submitBrokenSiteReport, 'window.PrivacyDashboard.submitBrokenSiteReport required') window.PrivacyDashboard.submitBrokenSiteReport(JSON.stringify(payload)) } + + /** + * {@inheritDoc common.getToggleReportOptions} + * @type {import("./common.js").getToggleReportOptions} + * @returns {Promise} + */ + getToggleReportOptions() { + invariant(typeof window.PrivacyDashboard?.getToggleReportOptions, 'window.PrivacyDashboard.getToggleReportOptions required') + window.PrivacyDashboard.getToggleReportOptions() + return new Promise((resolve) => { + window.onGetToggleReportOptionsResponse = (data) => { + resolve(data) + Reflect.deleteProperty(window, 'onGetToggleReportOptionsResponse') + } + }) + } + + /** + * {@inheritDoc common.sendToggleReport} + * @type {import("./common.js").sendToggleReport} + */ + sendToggleReport() { + invariant(window.PrivacyDashboard?.sendToggleReport, 'sendToggleReport missing') + window.PrivacyDashboard.sendToggleReport() + } + + /** + * {@inheritDoc common.rejectToggleReport} + * @type {import("./common.js").rejectToggleReport} + */ + rejectToggleReport() { + invariant(window.PrivacyDashboard?.rejectToggleReport, 'rejectToggleReport missing') + window.PrivacyDashboard.rejectToggleReport() + } + + /** + * {@inheritDoc common.seeWhatIsSent} + * @type {import("./common.js").seeWhatIsSent} + */ + seeWhatIsSent() { + invariant(window.PrivacyDashboard?.seeWhatIsSent, 'seeWhatIsSent missing') + window.PrivacyDashboard.seeWhatIsSent() + } } /** @@ -375,6 +422,22 @@ async function fetchAndroid(message) { }) } + if (message instanceof FetchToggleReportOptions) { + return privacyDashboardApi.getToggleReportOptions() + } + + if (message instanceof SendToggleBreakageReport) { + return privacyDashboardApi.sendToggleReport() + } + + if (message instanceof RejectToggleBreakageReport) { + return privacyDashboardApi.rejectToggleReport() + } + + if (message instanceof SeeWhatIsSent) { + return privacyDashboardApi.seeWhatIsSent() + } + console.warn('unhandled message', message) } diff --git a/shared/js/browser/common.js b/shared/js/browser/common.js index f7f4b957..a3e7fcb7 100644 --- a/shared/js/browser/common.js +++ b/shared/js/browser/common.js @@ -280,6 +280,7 @@ export async function refreshAlias() { /** * Fetch the data needed to display the toggle report screen + * @returns {Promise} */ export async function getToggleReportOptions() { throw new Error('base impl') @@ -287,17 +288,22 @@ export async function getToggleReportOptions() { /** * Send the toggle report - * @returns {Promise} */ -export async function sendToggleReport() { +export function sendToggleReport() { throw new Error('base impl') } /** * Reject sending the toggle report - * @returns {Promise} */ -export async function rejectToggleReport() { +export function rejectToggleReport() { + throw new Error('base impl') +} + +/** + * Sent when the user expands the disclosure + */ +export function seeWhatIsSent() { throw new Error('base impl') } diff --git a/shared/js/browser/macos-communication.js b/shared/js/browser/macos-communication.js index a3b08749..621daa97 100644 --- a/shared/js/browser/macos-communication.js +++ b/shared/js/browser/macos-communication.js @@ -317,7 +317,7 @@ export function privacyDashboardGetToggleReportOptions() { export function privacyDashboardSendToggleReport() { invariant(window.webkit?.messageHandlers, 'webkit.messageHandlers required') invariant(window.webkit.messageHandlers.privacyDashboardSendToggleReport, 'privacyDashboardSendToggleReport required') - return window.webkit.messageHandlers.privacyDashboardSendToggleReport.postMessage({}) + window.webkit.messageHandlers.privacyDashboardSendToggleReport.postMessage({}) } /** @@ -335,7 +335,7 @@ export function privacyDashboardSendToggleReport() { export function privacyDashboardRejectToggleReport() { invariant(window.webkit?.messageHandlers, 'webkit.messageHandlers required') invariant(window.webkit.messageHandlers.privacyDashboardRejectToggleReport, 'privacyDashboardRejectToggleReport required') - return window.webkit.messageHandlers.privacyDashboardRejectToggleReport.postMessage({}) + window.webkit.messageHandlers.privacyDashboardRejectToggleReport.postMessage({}) } /** diff --git a/shared/js/browser/utils/communication-mocks.mjs b/shared/js/browser/utils/communication-mocks.mjs index 2e6a006e..8d40f8f4 100644 --- a/shared/js/browser/utils/communication-mocks.mjs +++ b/shared/js/browser/utils/communication-mocks.mjs @@ -87,16 +87,13 @@ export function windowsMockApis() { /** * @param {object} params - * @param {Partial>} params.responses + * @param {Partial>} params.messages */ -export function webkitMockApis({ responses = {} }) { - const merged = { - ...responses, - } +export function webkitMockApis({ messages = {} }) { try { window.__playwright = { - messages: {}, - responses: merged, + messages, + responses: {}, mocks: { outgoing: [], incoming: [], @@ -180,7 +177,7 @@ export function webkitMockApis({ responses = {} }) { window.__playwright.mocks.outgoing.push(['privacyDashboardGetToggleReportOptions', arg]) setTimeout(() => { window.onGetToggleReportOptionsResponse?.( - window.__playwright.responses['privacyDashboardGetToggleReportOptions'] + window.__playwright.messages['privacyDashboardGetToggleReportOptions'] ) }, 0) }, @@ -193,10 +190,14 @@ export function webkitMockApis({ responses = {} }) { } } -export function mockAndroidApis() { +/** + * @param {object} params + * @param {Partial>} params.messages + */ +export function mockAndroidApis({ messages = {} }) { try { window.__playwright = { - messages: {}, + messages, responses: {}, mocks: { outgoing: [], @@ -223,6 +224,22 @@ export function mockAndroidApis() { submitBrokenSiteReport(arg) { window.__playwright.mocks.outgoing.push(['submitBrokenSiteReport', arg]) }, + getToggleReportOptions() { + const response = window.__playwright.messages['getToggleReportOptions'] + if (!response) throw new Error('unreachable, missing mock for getToggleReportOptions') + setTimeout(() => { + window.onGetToggleReportOptionsResponse?.(response) + }, 0) + }, + sendToggleReport() { + window.__playwright.mocks.outgoing.push(['sendToggleReport']) + }, + rejectToggleReport() { + window.__playwright.mocks.outgoing.push(['rejectToggleReport']) + }, + seeWhatIsSent() { + window.__playwright.mocks.outgoing.push(['seeWhatIsSent']) + }, } } catch (e) { console.error("❌couldn't set up mocks") @@ -348,21 +365,23 @@ export function mockBrowserApis(params = { messages: {} }) { * @return {Promise} */ export async function installMocks(platform) { - console.log('instaling...') if (window.__playwright) { - console.log('instaling... NOE') return console.log('❌ mocked already there') } if (platform.name === 'windows') { windowsMockApis() } else if (platform.name === 'ios' || platform.name === 'macos') { webkitMockApis({ - responses: { + messages: { privacyDashboardGetToggleReportOptions: toggleReportScreen, }, }) } else if (platform.name === 'android') { - mockAndroidApis() + mockAndroidApis({ + messages: { + getToggleReportOptions: toggleReportScreen, + }, + }) } else if (platform.name === 'browser') { mockBrowserApis() } diff --git a/shared/js/ui/components/toggle-report.jsx b/shared/js/ui/components/toggle-report.jsx index 72fd8f51..d2b722fc 100644 --- a/shared/js/ui/components/toggle-report.jsx +++ b/shared/js/ui/components/toggle-report.jsx @@ -16,7 +16,8 @@ import { ToggleReportTitle } from './toggle-report/toggle-report-title' import { getContentHeight, setupMutationObserverForExtensions } from '../../browser/common' export function ToggleReport() { - const innerGap = platform.name === 'ios' ? '24px' : '16px' + const mobile = platform.name === 'android' || platform.name === 'ios' + const innerGap = mobile ? '24px' : '16px' const desktop = platform.name === 'macos' || platform.name === 'windows' const extension = platform.name === 'browser' @@ -65,7 +66,7 @@ export function ToggleReport() { ) } - if (platform.name === 'ios') { + if (mobile) { return ( @@ -127,9 +128,10 @@ function SetAutoHeight() { } function ToggleReportButtons({ send, reject }) { - const buttonVariant = platform.name === 'ios' ? 'ios-secondary' : 'macos-standard' - const buttonLayout = platform.name === 'ios' ? 'vertical' : 'horizontal' - const buttonSize = platform.name === 'ios' ? 'big' : 'small' + const mobile = platform.name === 'ios' || platform.name === 'android' + const buttonVariant = mobile ? 'ios-secondary' : 'macos-standard' + const buttonLayout = mobile ? 'vertical' : 'horizontal' + const buttonSize = mobile ? 'big' : 'small' return ( diff --git a/shared/js/ui/components/toggle-report/toggle-report.scss b/shared/js/ui/components/toggle-report/toggle-report.scss index 7fc7109e..31c969c8 100644 --- a/shared/js/ui/components/toggle-report/toggle-report.scss +++ b/shared/js/ui/components/toggle-report/toggle-report.scss @@ -7,11 +7,13 @@ } } -.environment--ios [data-toggle-report='child'] { +.environment--ios [data-toggle-report='child'], +.environment--android [data-toggle-report='child'] { opacity: 0; visibility: hidden; } -.environment--ios [data-toggle-report='child'][data-ready='true'] { +.environment--ios [data-toggle-report='child'][data-ready='true'], +.environment--android [data-toggle-report='child'][data-ready='true'] { opacity: 1; visibility: visible; } diff --git a/shared/js/ui/components/toggle-report/use-ios-animation.js b/shared/js/ui/components/toggle-report/use-ios-animation.js index 2329adad..0c803fb4 100644 --- a/shared/js/ui/components/toggle-report/use-ios-animation.js +++ b/shared/js/ui/components/toggle-report/use-ios-animation.js @@ -13,7 +13,7 @@ import { useToggleReportState } from './use-toggle-report-state' */ export function useIosAnimation(state, dispatch) { useEffect(() => { - if (platform.name !== 'ios') return + if (platform.name !== 'ios' && platform.name !== 'android') return if (state.value === 'animating') { const child = /** @type {HTMLDivElement | null} */ (document.querySelector('[data-toggle-report="child"]')) if (!child) return @@ -25,7 +25,7 @@ export function useIosAnimation(state, dispatch) { }, [state.value]) useEffect(() => { - if (platform.name !== 'ios') return + if (platform.name !== 'ios' && platform.name !== 'android') return const child = /** @type {HTMLDivElement | null} */ (document.querySelector('[data-toggle-report="child"]')) const parent = /** @type {HTMLDivElement | null} */ (document.querySelector('[data-toggle-report="parent"]')) diff --git a/types.ts b/types.ts index a3875323..ab5bead1 100644 --- a/types.ts +++ b/types.ts @@ -32,6 +32,10 @@ interface Window { openInNewTab: (payload: string) => void openSettings: (payload: string) => void submitBrokenSiteReport: (payload: string) => void + getToggleReportOptions: () => void + sendToggleReport: () => void + rejectToggleReport: () => void + seeWhatIsSent: () => void } /** * This is set in Playwright tests From 9d88283a7c75894dca8018e30978f783b5ed2802 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Wed, 25 Sep 2024 08:35:06 +0100 Subject: [PATCH 2/3] build --- build/app/public/css/popup.css | 6 ++- build/app/public/js/base.js | 71 +++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/build/app/public/css/popup.css b/build/app/public/css/popup.css index 9c7a8c1e..52d82f65 100644 --- a/build/app/public/css/popup.css +++ b/build/app/public/css/popup.css @@ -3939,12 +3939,14 @@ body.environment--macos, body.environment--browser, body.environment--windows, b visibility: hidden; } -.environment--ios [data-toggle-report=child] { +.environment--ios [data-toggle-report=child], +.environment--android [data-toggle-report=child] { opacity: 0; visibility: hidden; } -.environment--ios [data-toggle-report=child][data-ready=true] { +.environment--ios [data-toggle-report=child][data-ready=true], +.environment--android [data-toggle-report=child][data-ready=true] { opacity: 1; visibility: visible; } diff --git a/build/app/public/js/base.js b/build/app/public/js/base.js index f44690c8..7a470b58 100644 --- a/build/app/public/js/base.js +++ b/build/app/public/js/base.js @@ -14039,12 +14039,12 @@ function privacyDashboardSendToggleReport() { invariant(window.webkit?.messageHandlers, "webkit.messageHandlers required"); invariant(window.webkit.messageHandlers.privacyDashboardSendToggleReport, "privacyDashboardSendToggleReport required"); - return window.webkit.messageHandlers.privacyDashboardSendToggleReport.postMessage({}); + window.webkit.messageHandlers.privacyDashboardSendToggleReport.postMessage({}); } function privacyDashboardRejectToggleReport() { invariant(window.webkit?.messageHandlers, "webkit.messageHandlers required"); invariant(window.webkit.messageHandlers.privacyDashboardRejectToggleReport, "privacyDashboardRejectToggleReport required"); - return window.webkit.messageHandlers.privacyDashboardRejectToggleReport.postMessage({}); + window.webkit.messageHandlers.privacyDashboardRejectToggleReport.postMessage({}); } function privacyDashboardSeeWhatIsSent() { invariant(window.webkit?.messageHandlers, "webkit.messageHandlers required"); @@ -14425,6 +14425,45 @@ invariant(typeof window.PrivacyDashboard?.submitBrokenSiteReport, "window.PrivacyDashboard.submitBrokenSiteReport required"); window.PrivacyDashboard.submitBrokenSiteReport(JSON.stringify(payload)); } + /** + * {@inheritDoc common.getToggleReportOptions} + * @type {import("./common.js").getToggleReportOptions} + * @returns {Promise} + */ + getToggleReportOptions() { + invariant(typeof window.PrivacyDashboard?.getToggleReportOptions, "window.PrivacyDashboard.getToggleReportOptions required"); + window.PrivacyDashboard.getToggleReportOptions(); + return new Promise((resolve) => { + window.onGetToggleReportOptionsResponse = (data) => { + resolve(data); + Reflect.deleteProperty(window, "onGetToggleReportOptionsResponse"); + }; + }); + } + /** + * {@inheritDoc common.sendToggleReport} + * @type {import("./common.js").sendToggleReport} + */ + sendToggleReport() { + invariant(window.PrivacyDashboard?.sendToggleReport, "sendToggleReport missing"); + window.PrivacyDashboard.sendToggleReport(); + } + /** + * {@inheritDoc common.rejectToggleReport} + * @type {import("./common.js").rejectToggleReport} + */ + rejectToggleReport() { + invariant(window.PrivacyDashboard?.rejectToggleReport, "rejectToggleReport missing"); + window.PrivacyDashboard.rejectToggleReport(); + } + /** + * {@inheritDoc common.seeWhatIsSent} + * @type {import("./common.js").seeWhatIsSent} + */ + seeWhatIsSent() { + invariant(window.PrivacyDashboard?.seeWhatIsSent, "seeWhatIsSent missing"); + window.PrivacyDashboard.seeWhatIsSent(); + } }; var privacyDashboardApi; async function fetchAndroid(message) { @@ -14461,6 +14500,18 @@ target: message.target }); } + if (message instanceof FetchToggleReportOptions) { + return privacyDashboardApi.getToggleReportOptions(); + } + if (message instanceof SendToggleBreakageReport) { + return privacyDashboardApi.sendToggleReport(); + } + if (message instanceof RejectToggleBreakageReport) { + return privacyDashboardApi.rejectToggleReport(); + } + if (message instanceof SeeWhatIsSent) { + return privacyDashboardApi.seeWhatIsSent(); + } console.warn("unhandled message", message); } var getBackgroundTabDataAndroid = () => { @@ -17053,7 +17104,7 @@ // shared/js/ui/components/toggle-report/use-ios-animation.js function useIosAnimation(state, dispatch) { p2(() => { - if (platform.name !== "ios") + if (platform.name !== "ios" && platform.name !== "android") return; if (state.value === "animating") { const child = ( @@ -17069,7 +17120,7 @@ } }, [state.value]); p2(() => { - if (platform.name !== "ios") + if (platform.name !== "ios" && platform.name !== "android") return; const child = ( /** @type {HTMLDivElement | null} */ @@ -17192,7 +17243,8 @@ // shared/js/ui/components/toggle-report.jsx function ToggleReport() { - const innerGap = platform.name === "ios" ? "24px" : "16px"; + const mobile = platform.name === "android" || platform.name === "ios"; + const innerGap = mobile ? "24px" : "16px"; const desktop = platform.name === "macos" || platform.name === "windows"; const extension = platform.name === "browser"; const { value, didClickSuccessScreen } = q2(ToggleReportContext); @@ -17204,7 +17256,7 @@ if (desktop || extension) { return /* @__PURE__ */ y(ToggleReportWrapper, { state: state.value }, extension && /* @__PURE__ */ y(SetAutoHeight, null), /* @__PURE__ */ y(Stack, { gap: "40px" }, /* @__PURE__ */ y(Stack, { gap: "24px" }, /* @__PURE__ */ y(Stack, { gap: innerGap }, /* @__PURE__ */ y("div", { className: "medium-icon-container hero-icon--toggle-report" }), /* @__PURE__ */ y(ToggleReportTitle, null, ns.toggleReport("siteNotWorkingTitle.title")), /* @__PURE__ */ y("div", null, /* @__PURE__ */ y("h2", { className: "token-title-3 text--center" }, ns.toggleReport("siteNotWorkingSubTitle.title")), /* @__PURE__ */ y(DesktopRevealText, { state, toggle: () => dispatch("toggle") }))), state.value === "showing" && /* @__PURE__ */ y(Scrollable, null, /* @__PURE__ */ y(ToggleReportDataList, { rows: value.data })), /* @__PURE__ */ y(ToggleReportButtons, { send: () => dispatch("send"), reject: () => dispatch("reject") })))); } - if (platform.name === "ios") { + if (mobile) { return /* @__PURE__ */ y(ToggleReportWrapper, { state: state.value }, /* @__PURE__ */ y(Stack, { gap: "40px" }, /* @__PURE__ */ y(Stack, { gap: "24px" }, /* @__PURE__ */ y(Stack, { gap: innerGap }, /* @__PURE__ */ y("div", { className: "medium-icon-container hero-icon--toggle-report" }), /* @__PURE__ */ y(ToggleReportTitle, null, ns.toggleReport("siteNotWorkingTitle.title")), /* @__PURE__ */ y("div", null, /* @__PURE__ */ y("h2", { className: "token-title-3 text--center" }, ns.toggleReport("siteNotWorkingSubTitle.title")))), /* @__PURE__ */ y(ToggleReportButtons, { send: () => dispatch("send"), reject: () => dispatch("reject") }), state.value !== "showing" && /* @__PURE__ */ y(RevealText, { toggle: () => dispatch("toggle-ios") })), state.value === "showing" && /* @__PURE__ */ y("div", { className: "ios-separator" }, /* @__PURE__ */ y(ToggleReportDataList, { rows: value.data })))); } return /* @__PURE__ */ y("p", null, "unsupported platform: ", platform.name); @@ -17232,9 +17284,10 @@ return null; } function ToggleReportButtons({ send, reject }) { - const buttonVariant = platform.name === "ios" ? "ios-secondary" : "macos-standard"; - const buttonLayout = platform.name === "ios" ? "vertical" : "horizontal"; - const buttonSize = platform.name === "ios" ? "big" : "small"; + const mobile = platform.name === "ios" || platform.name === "android"; + const buttonVariant = mobile ? "ios-secondary" : "macos-standard"; + const buttonLayout = mobile ? "vertical" : "horizontal"; + const buttonSize = mobile ? "big" : "small"; return /* @__PURE__ */ y(ButtonBar, { layout: buttonLayout }, /* @__PURE__ */ y(Button, { variant: buttonVariant, btnSize: buttonSize, onClick: reject }, ns.toggleReport("dontSendReport.title")), /* @__PURE__ */ y(Button, { variant: buttonVariant, btnSize: buttonSize, onClick: send }, ns.report("sendReport.title"))); } function RevealText({ toggle }) { From 70be7eb9b84d432df04cf54cd33a897c442fedb0 Mon Sep 17 00:00:00 2001 From: Shane Osbourne Date: Wed, 25 Sep 2024 08:45:00 +0100 Subject: [PATCH 3/3] docs --- build/app/public/js/base.js | 5 ++++- shared/js/browser/android-communication.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build/app/public/js/base.js b/build/app/public/js/base.js index 7a470b58..52ca2836 100644 --- a/build/app/public/js/base.js +++ b/build/app/public/js/base.js @@ -14392,7 +14392,6 @@ /** * {@inheritDoc common.openInNewTab} * @type {import("./common.js").openInNewTab} - * * ```js * const payload = JSON.stringify({ * "url": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/" @@ -14443,6 +14442,10 @@ /** * {@inheritDoc common.sendToggleReport} * @type {import("./common.js").sendToggleReport} + * @example + * ```js + * window.PrivacyDashboard.sendToggleReport() + * ``` */ sendToggleReport() { invariant(window.PrivacyDashboard?.sendToggleReport, "sendToggleReport missing"); diff --git a/shared/js/browser/android-communication.js b/shared/js/browser/android-communication.js index 473ceebb..ececf445 100644 --- a/shared/js/browser/android-communication.js +++ b/shared/js/browser/android-communication.js @@ -291,7 +291,6 @@ export class PrivacyDashboardJavascriptInterface { /** * {@inheritDoc common.openInNewTab} * @type {import("./common.js").openInNewTab} - * * ```js * const payload = JSON.stringify({ * "url": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/" @@ -346,6 +345,10 @@ export class PrivacyDashboardJavascriptInterface { /** * {@inheritDoc common.sendToggleReport} * @type {import("./common.js").sendToggleReport} + * @example + * ```js + * window.PrivacyDashboard.sendToggleReport() + * ``` */ sendToggleReport() { invariant(window.PrivacyDashboard?.sendToggleReport, 'sendToggleReport missing')