Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor broken site report message handlers #2939

Merged
merged 11 commits into from
Feb 17, 2025
7 changes: 6 additions & 1 deletion shared/js/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import DebuggerConnection from './components/debugger-connection';
import Devtools from './components/devtools';
import DNRListeners from './components/dnr-listeners';
import RemoteConfig from './components/remote-config';
import DashboardMessaging from './components/dashboard-messaging';
import initDebugBuild from './devbuild';
import initReloader from './devbuild-reloader';
import tabManager from './tab-manager';
Expand All @@ -51,14 +52,17 @@ const remoteConfig = new RemoteConfig({ settings });
const abnMetrics = BUILD_TARGET !== 'firefox' ? new AbnExperimentMetrics({ remoteConfig }) : null;
const tds = new TDSStorage({ settings, remoteConfig, abnMetrics });
const devtools = new Devtools({ tds });
const dashboardMessaging = new DashboardMessaging({ settings, tds, tabManager });
/**
* @type {{
* autofill: EmailAutofill;
* dashboardMessaging: DashboardMessaging
* omnibox: OmniboxSearch;
* fireButton?: FireButton;
* internalUser: InternalUserDetector;
* tds: TDSStorage;
* tabTracking: TabTracker;
* toggleReports: ToggleReports;
* trackers: TrackersGlobal;
* remoteConfig: RemoteConfig;
* abnMetrics: AbnExperimentMetrics?;
Expand All @@ -67,11 +71,12 @@ const devtools = new Devtools({ tds });
*/
const components = {
autofill: new EmailAutofill({ settings }),
dashboardMessaging,
omnibox: new OmniboxSearch(),
internalUser: new InternalUserDetector({ settings }),
tabTracking: new TabTracker({ tabManager, devtools }),
tds,
toggleReports: new ToggleReports(),
toggleReports: new ToggleReports({ dashboardMessaging }),
trackers: new TrackersGlobal({ tds }),
debugger: new DebuggerConnection({ tds, devtools }),
devtools,
Expand Down
42 changes: 0 additions & 42 deletions shared/js/background/broken-site-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@
* @typedef {import('@duckduckgo/privacy-dashboard/schema/__generated__/schema.types').DataItemId} DisclosureParamId
*/

const browser = require('webextension-polyfill');
const load = require('./load');
const browserWrapper = require('./wrapper');
const settings = require('./settings');
const parseUserAgentString = require('../shared-utils/parse-user-agent-string');
const { getCurrentTab, getURLWithoutQueryString } = require('./utils');
const { getURL } = require('./pixels');
const tdsStorage = require('./storage/tds').default;
const tabManager = require('./tab-manager');
const maxPixelLength = 7000;

/**
Expand Down Expand Up @@ -307,45 +304,6 @@ export async function breakageReportForTab({
return fire(pixelName, brokenSiteParams.toString());
}

/**
* Attempt to send a breakage report for the currently focused tab.
*
* @param {Object} arg
* @prop {string} pixelName
* @prop {import("./classes/tab") | undefined} arg.currentTab
* @prop {string | undefined} arg.category
* @prop {string | undefined} arg.description
* @prop {string | undefined} arg.reportFlow
* String detailing the UI flow that this breakage report came from.
*/
export async function sendBreakageReportForCurrentTab({ pixelName, currentTab, category, description, reportFlow }) {
await settings.ready();
await tdsStorage.ready('config');

const tab = currentTab || (await tabManager.getOrRestoreCurrentTab());
if (!tab) {
return;
}

const pageParams = (await browser.tabs.sendMessage(tab.id, { getBreakagePageParams: true })) || {};

const tds = settings.getSetting('tds-etag');
const remoteConfigEtag = settings.getSetting('config-etag');
const remoteConfigVersion = tdsStorage.config.version;

return await breakageReportForTab({
pixelName,
tab,
tds,
remoteConfigEtag,
remoteConfigVersion,
category,
description,
pageParams,
reportFlow,
});
}

/**
* Returns the breakage report details as expected by the
* "getBreakageFormOptions" and "getToggleReportOptions" messages.
Expand Down
92 changes: 92 additions & 0 deletions shared/js/background/components/dashboard-messaging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import browser from 'webextension-polyfill';
import { breakageReportForTab, getDisclosureDetails } from '../broken-site-report';
import { dashboardDataFromTab } from '../classes/privacy-dashboard-data';
import { registerMessageHandler } from '../message-handlers';
import { getCurrentTab } from '../utils';
import { isFireButtonEnabled } from './fire-button';

/**
* Message handlers for communication from the dashboard to the extension background.
*
* Note, additional message handles for toggle reports is in the separate ToggleReports component.
sammacbeth marked this conversation as resolved.
Show resolved Hide resolved
kzar marked this conversation as resolved.
Show resolved Hide resolved
*/
sammacbeth marked this conversation as resolved.
Show resolved Hide resolved
export default class DashboardMessaging {
/**
* @param {{
* settings: import('../settings.js');
* tds: import('./tds').default;
* tabManager: import('../tab-manager.js');
* }} args
*/
constructor({ settings, tds, tabManager }) {
this.settings = settings;
this.tds = tds;
this.tabManager = tabManager;

registerMessageHandler('submitBrokenSiteReport', (report) => this.submitBrokenSiteReport(report));
registerMessageHandler('getPrivacyDashboardData', this.getPrivacyDashboardData.bind(this));
registerMessageHandler('getBreakageFormOptions', getDisclosureDetails);
}

/**
* Only the dashboard sends this message, so we import the types from there.
* @param {import('@duckduckgo/privacy-dashboard/schema/__generated__/schema.types').BreakageReportRequest} breakageReport
* @param {string} [pixelName]
* @param {string} [reportFlow]
* @returns {Promise<void>}
*/
async submitBrokenSiteReport(breakageReport, pixelName = 'epbf', reportFlow = undefined) {
// wait for config and TDS so we can get etags and config version
await Promise.all([this.tds.remoteConfig.allLoadingFinished, this.tds.tds.ready]);
sammacbeth marked this conversation as resolved.
Show resolved Hide resolved
const { category, description } = breakageReport;
const tab = await this.tabManager.getOrRestoreCurrentTab();
if (!tab) {
return;
}
const pageParams = (await browser.tabs.sendMessage(tab.id, { getBreakagePageParams: true })) || {};
const tds = this.tds.tds.etag;
const remoteConfigEtag = this.tds.remoteConfig.etag;
const remoteConfigVersion = this.tds.remoteConfig.config?.version;
return breakageReportForTab({
pixelName,
tab,
tds,
remoteConfigEtag,
remoteConfigVersion,
category,
description,
pageParams,
reportFlow,
});
}

/**
* This message is here to ensure the privacy dashboard can render
* from a single call to the extension.
*
* Currently, it will collect data for the current tab and email protection
* user data.
*/
async getPrivacyDashboardData(options) {
let { tabId } = options;
if (tabId === null) {
const currentTab = await getCurrentTab();
if (!currentTab?.id) {
throw new Error('could not get the current tab...');
}
tabId = currentTab?.id;
}

// Await for storage to be ready; this happens on service worker closing mostly.
await this.settings.ready();
await this.tds.config.ready;

const tab = await this.tabManager.getOrRestoreTab(tabId);
if (!tab) throw new Error('unreachable - cannot access current tab with ID ' + tabId);
const userData = this.settings.getSetting('userData');
const fireButtonData = {
enabled: isFireButtonEnabled,
};
return dashboardDataFromTab(tab, userData, fireButtonData);
}
}
20 changes: 14 additions & 6 deletions shared/js/background/components/toggle-reports.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { registerMessageHandler } from '../message-handlers';
import { postPopupMessage } from '../popup-messaging';
import settings from '../settings';
import { getFeatureSettings, reloadCurrentTab, resolveAfterDelay } from '../utils';
import { getDisclosureDetails, sendBreakageReportForCurrentTab } from '../broken-site-report';
import { getDisclosureDetails } from '../broken-site-report';
import { createAlarm } from '../wrapper';
import tabManager from '../tab-manager';

Expand All @@ -23,7 +23,14 @@ import tabManager from '../tab-manager';
export default class ToggleReports {
static ALARM_NAME = 'toggleReportsClearExpired';

constructor() {
/**
*
* @param {{
* dashboardMessaging: import('./dashboard-messaging').default
* }} args
*/
constructor({ dashboardMessaging }) {
this.dashboardMessaging = dashboardMessaging;
this.onDisconnect = this.toggleReportFinished.bind(this, false);

registerMessageHandler('getToggleReportOptions', (_, sender) => this.toggleReportStarted(sender));
Expand Down Expand Up @@ -81,10 +88,11 @@ export default class ToggleReports {
try {
// Send the breakage report before reloading the page, to ensure
// the correct page details are sent with the report.
await sendBreakageReportForCurrentTab({
pixelName: 'protection-toggled-off-breakage-report',
reportFlow: 'on_protections_off_dashboard_main',
});
await this.dashboardMessaging.submitBrokenSiteReport(
{},
'protection-toggled-off-breakage-report',
'on_protections_off_dashboard_main',
);
} catch (e) {
// Catch this, mostly to ensure the page is still reloaded if
// sending the breakage report fails.
Expand Down
47 changes: 0 additions & 47 deletions shared/js/background/message-handlers.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import browser from 'webextension-polyfill';
import { dashboardDataFromTab } from './classes/privacy-dashboard-data';
import { getDisclosureDetails, sendBreakageReportForCurrentTab } from './broken-site-report';
import parseUserAgentString from '../shared-utils/parse-user-agent-string';
import { getExtensionURL } from './wrapper';
import { isFeatureEnabled, reloadCurrentTab } from './utils';
import { ensureClickToLoadRuleActionDisabled } from './dnr-click-to-load';
import tdsStorage from './storage/tds';
import { getArgumentsObject } from './helpers/arguments-object';
import { isFireButtonEnabled } from './components/fire-button';
import { postPopupMessage } from './popup-messaging';
import ToggleReports from './components/toggle-reports';
const utils = require('./utils');
Expand Down Expand Up @@ -99,47 +96,6 @@ export function openOptions() {
}
}

/**
* Only the dashboard sends this message, so we import the types from there.
* @param {import('@duckduckgo/privacy-dashboard/schema/__generated__/schema.types').BreakageReportRequest} breakageReport
* @returns {Promise<void>}
*/
export function submitBrokenSiteReport(breakageReport) {
const pixelName = 'epbf';
const { category, description } = breakageReport;
return sendBreakageReportForCurrentTab({ pixelName, category, description });
}

/**
* This message is here to ensure the privacy dashboard can render
* from a single call to the extension.
*
* Currently, it will collect data for the current tab and email protection
* user data.
*/
export async function getPrivacyDashboardData(options) {
let { tabId } = options;
if (tabId === null) {
const currentTab = await utils.getCurrentTab();
if (!currentTab?.id) {
throw new Error('could not get the current tab...');
}
tabId = currentTab?.id;
}

// Await for storage to be ready; this happens on service worker closing mostly.
await settings.ready();
await tdsStorage.ready('config');

const tab = await tabManager.getOrRestoreTab(tabId);
if (!tab) throw new Error('unreachable - cannot access current tab with ID ' + tabId);
const userData = settings.getSetting('userData');
const fireButtonData = {
enabled: isFireButtonEnabled,
};
return dashboardDataFromTab(tab, userData, fireButtonData);
}

export function getTopBlockedByPages(options) {
return Companies.getTopBlockedByPages(options);
}
Expand Down Expand Up @@ -344,9 +300,6 @@ const messageHandlers = {
allowlistOptIn,
getBrowser,
openOptions,
submitBrokenSiteReport,
getBreakageFormOptions: getDisclosureDetails,
getPrivacyDashboardData,
getTopBlockedByPages,
getClickToLoadState,
getYouTubeVideoDetails,
Expand Down
3 changes: 2 additions & 1 deletion shared/js/background/storage/tds.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default {
_config: { features: {} },
_tds: { entities: {}, trackers: {}, domains: {}, cnames: {} },
_surrogates: '',
/** @type {import('@duckduckgo/privacy-configuration/schema/config').GenericV4Config} */
get config() {
return globalThis.components?.remoteConfig.config || this._config;
},
Expand Down Expand Up @@ -60,7 +61,7 @@ export default {
return Promise.resolve();
}
if (configName && listNames.includes(configName)) {
return tdsStorage[configName].ready;
return tdsStorage[configName].allLoadingFinished;
}
return Promise.all(listNames.map((n) => tdsStorage[n].ready));
},
Expand Down
Loading
Loading