diff --git a/shared/js/background/background.js b/shared/js/background/background.js index 0e74b1fc7..8bdc5bc6a 100644 --- a/shared/js/background/background.js +++ b/shared/js/background/background.js @@ -46,7 +46,8 @@ settings.ready().then(() => { }); const remoteConfig = new RemoteConfig({ settings }); -const tds = new TDSStorage({ settings, remoteConfig }); +const abnMetrics = BUILD_TARGET !== 'firefox' ? new AbnExperimentMetrics({ remoteConfig }) : null; +const tds = new TDSStorage({ settings, remoteConfig, abnMetrics }); const devtools = new Devtools({ tds }); /** * @type {{ @@ -72,12 +73,11 @@ const components = { debugger: new DebuggerConnection({ tds, devtools }), devtools, remoteConfig, + abnMetrics, }; // Chrome-only components if (BUILD_TARGET === 'chrome' || BUILD_TARGET === 'chrome-mv2') { - const abnMetrics = new AbnExperimentMetrics({ remoteConfig }); - components.abnMetrics = abnMetrics; components.metrics = [new AppUseMetric({ abnMetrics }), new SearchMetric({ abnMetrics }), new PixelMetric({ abnMetrics })]; components.fireButton = new FireButton({ settings, tabManager }); } diff --git a/shared/js/background/components/tds.js b/shared/js/background/components/tds.js index 4111f6894..2e75ff79e 100644 --- a/shared/js/background/components/tds.js +++ b/shared/js/background/components/tds.js @@ -1,20 +1,28 @@ import ResourceLoader from './resource-loader.js'; import constants from '../../../data/constants'; +import { generateRetentionMetrics } from './abn-experiments.js'; /** * @typedef {import('../settings.js')} Settings * @typedef {import('./remote-config.js').default} RemoteConfig + * @typedef {import('./abn-experiments.js').default} AbnExperimentMetrics */ +const TDS_OVERRIDE_SETTINGS_KEY = 'tdsOverride'; +const CONTENT_BLOCKING = 'contentBlocking'; + export default class TDSStorage { /** * @param {{ * settings: Settings, - * remoteConfig: RemoteConfig + * remoteConfig: RemoteConfig, + * abnMetrics: AbnExperimentMetrics? * }} opts */ - constructor({ settings, remoteConfig }) { + constructor({ settings, remoteConfig, abnMetrics }) { + this.settings = settings; this.remoteConfig = remoteConfig; + this.abnMetrics = abnMetrics; /** @deprecated config is an alias of remoteConfig */ this.config = this.remoteConfig; this.surrogates = new ResourceLoader( @@ -28,14 +36,60 @@ export default class TDSStorage { this.tds = new ResourceLoader( { name: 'tds', - remoteUrl: constants.tdsLists[1].url, + remoteUrl: this.getTDSUrl.bind(this), updateIntervalMinutes: 15, }, { settings }, ); + this.remoteConfig.onUpdate(this.checkShouldOverrideTDS.bind(this)); } ready() { return Promise.all([this.tds.ready, this.surrogates.ready, this.remoteConfig.ready]); } + + async getTDSUrl() { + await this.config.ready; + const overridePath = this.settings.getSetting(TDS_OVERRIDE_SETTINGS_KEY); + if (overridePath) { + return `https://staticcdn.duckduckgo.com/trackerblocking/${overridePath}`; + } + return constants.tdsLists[1].url; + } + + checkShouldOverrideTDS() { + const contentBlockingSubFeatures = this.config.config?.features[CONTENT_BLOCKING].features || {}; + const enabledBlocklistOverrides = Object.keys(contentBlockingSubFeatures).filter( + (k) => k.startsWith('TDS') && this.config.isSubFeatureEnabled(CONTENT_BLOCKING, k), + ); + if (enabledBlocklistOverrides.length > 0 && this.abnMetrics) { + const subFeatureName = enabledBlocklistOverrides[0]; + const overrideSubFeature = contentBlockingSubFeatures[subFeatureName]; + // If this is enabled via an experiment, the override URL is defined as `${cohortName}Url`, otherwise, for a normal rollout use `nextUrl`. + const settingsKey = `${this.remoteConfig.getCohortName(CONTENT_BLOCKING, subFeatureName) || 'next'}Url`; + const overridePath = overrideSubFeature.settings && overrideSubFeature.settings[settingsKey]; + if (!overridePath) { + console.warn(`Couldn't find TDS override path in subfeature settings.`); + return; + } + if (overridePath !== this.settings.getSetting(TDS_OVERRIDE_SETTINGS_KEY)) { + console.log('TDS URL override changed to ', overridePath); + this.settings.updateSetting(TDS_OVERRIDE_SETTINGS_KEY, overridePath); + this.tds.checkForUpdates(true); + } + this.abnMetrics.markExperimentEnrolled(CONTENT_BLOCKING, subFeatureName, [ + ...generateRetentionMetrics(), + { + metric: 'brokenSiteReport', + conversionWindowStart: 0, + conversionWindowEnd: 14, + value: 1, + }, + ]); + } else if (this.settings.getSetting(TDS_OVERRIDE_SETTINGS_KEY)) { + // User removed from experiment/rollout, reset TDS override and fetch default list + this.settings.removeSetting(TDS_OVERRIDE_SETTINGS_KEY); + this.tds.checkForUpdates(true); + } + } }