diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 673b8fc22..ceb9eafaf 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -947,6 +947,9 @@ "settings_allow_offers": { "message": "Participating in Ghostery Rewards" }, + "settings_allow_abtests": { + "message": "Participating in A/B Tests" + }, "settings_signin_create_header": { "message": "Sign In / Create Account" }, @@ -1037,6 +1040,9 @@ "settings_offers_tooltip": { "message": "Ghostery Rewards is a private-by-design feature that delivers you discounts and special offers from our partner companies as you browse." }, + "settings_abtests_tooltip": { + "message": "Participating in randomized A/B tests helps Ghostery understand which version of a new layout or feature users like you prefer." + }, "settings_opt_in": { "message": "Opt In / Out" }, diff --git a/app/panel/components/Settings/OptIn.jsx b/app/panel/components/Settings/OptIn.jsx index f74262c0b..e84546329 100644 --- a/app/panel/components/Settings/OptIn.jsx +++ b/app/panel/components/Settings/OptIn.jsx @@ -18,62 +18,84 @@ import globals from '../../../../src/classes/Globals'; const { IS_CLIQZ, BROWSER_INFO } = globals; const IS_ANDROID = (BROWSER_INFO.os === 'android'); +const TOOLTIP_SVG_FILEPATH = '../../app/images/panel/icon-information-tooltip.svg'; + /** * @class Implement Opt In subview as a React component. * The view opens from the left-side menu of the main Settings view. * It invites user to opt in for telemetry options, human web and offers * @memberOf SettingsComponents */ -const OptIn = ({ settingsData, toggleCheckbox }) => ( -
-
-
-

{ t('settings_support_ghostery') }

-
- { t('settings_support_ghostery_by') } - : -
-
-
- - -
- -
-
+const OptIn = ({ settingsData, toggleCheckbox }) => { + const checkbox = (opt, name) => ( + + ); + + const labelFor = (opt, text) => ( + + ); + + const tooltipSVG = (text, dir) => ( +
+ +
+ ); + + const option = (cbox, label, tooltip, id = '') => ( +
+
+ {cbox} + {label} + {tooltip} +
+
+ ); + + return ( +
+
+
+

{t('settings_support_ghostery')}

+
+ {t('settings_support_ghostery_by')} + : +
+ {option( + checkbox('share-usage', 'enable_metrics'), + labelFor('share-usage', t('settings_share_usage')), + tooltipSVG(t('settings_share_usage_tooltip'), 'down') + )} + {!IS_CLIQZ && option( + checkbox('share-human-web', 'enable_human_web'), + labelFor('share-human-web', t('settings_share_human_web')), + tooltipSVG(t('settings_human_web_tooltip'), 'up'), + 'human-web-section' + )} + {!IS_CLIQZ && !IS_ANDROID && option( + checkbox('allow-offers', 'enable_offers'), + labelFor('allow-offers', t('settings_allow_offers')), + tooltipSVG(t('settings_offers_tooltip'), 'up'), + 'offers-section' + )} + {option( + checkbox('allow-abtests', 'enable_abtests'), + labelFor('allow-abtests', t('settings_allow_abtests')), + tooltipSVG(t('settings_abtests_tooltip'), 'up'), + 'abtests-section' + )}
- {!IS_CLIQZ && ( -
-
- - -
- -
-
-
- )} - {!IS_CLIQZ && !IS_ANDROID && ( -
-
- - -
- -
-
-
- )}
-
-); + ); +}; OptIn.propTypes = { toggleCheckbox: PropTypes.func.isRequired, @@ -81,6 +103,7 @@ OptIn.propTypes = { enable_metrics: PropTypes.bool.isRequired, enable_human_web: PropTypes.bool.isRequired, enable_offers: PropTypes.bool.isRequired, + enable_abtests: PropTypes.bool.isRequired, }).isRequired, }; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap index 3953f909d..c30d04ec8 100644 --- a/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap +++ b/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap @@ -19,6 +19,7 @@ exports[`app/panel/Settings/OptIn.jsx Snapshot tests with react-test-renderer Op
+
+
+ + +
+ +
+
+
@@ -136,6 +168,7 @@ exports[`app/panel/Settings/OptIn.jsx Snapshot tests with react-test-renderer Op
+
+
+ + +
+ +
+
+
diff --git a/src/background.js b/src/background.js index e8d463035..80e3e63a3 100644 --- a/src/background.js +++ b/src/background.js @@ -72,6 +72,8 @@ const IS_FIREFOX = (BROWSER_INFO.name === 'firefox'); const IS_ANDROID = (BROWSER_INFO.os === 'android'); const VERSION_CHECK_URL = `${CDN_BASE_URL}/update/version`; const REAL_ESTATE_ID = 'ghostery'; +const ONE_DAY_MSEC = 86400000; +const ONE_HOUR_MSEC = 3600000; const onBeforeRequest = events.onBeforeRequest.bind(events); const { onHeadersReceived } = Events; @@ -107,12 +109,12 @@ function setCliqzModuleEnabled(module, enabled) { /** * Pulls down latest version.json and triggers - * updates of all db files. + * updates of all db files. FKA checkLibraryVersion. * @memberOf Background * * @return {Promise} database updated data */ -function checkLibraryVersion() { +function updateDBs() { return new Promise(((resolve, reject) => { const failed = { success: false, updated: false }; utils.getJson(VERSION_CHECK_URL).then((data) => { @@ -139,25 +141,33 @@ function checkLibraryVersion() { }); }); }).catch((err) => { - log('Error in checkLibraryVersion', err); + log('Error in updateDBs', err); reject(failed); }); })); } /** - * Check and fetch a new tracker library every hour as needed + * Call updateDBs if auto updating is enabled and enough time has passed since the last check. + * Debug log that the function was called and when. Called at browser startup and at regular intervals thereafter. + * * @memberOf Background + * + * @param {Boolean} isAutoUpdateEnabled Whether bug db auto updating is enabled. + * @param {Number} bugsLastCheckedMsec The Unix msec timestamp to check against to make sure it is not too soon to call updateDBs again. */ -function autoUpdateBugDb() { - if (conf.enable_autoupdate) { - const result = conf.bugs_last_checked; - const nowTime = Number((new Date()).getTime()); - // offset by 15min so that we don't double fetch - if (!result || nowTime > (Number(result) + 900000)) { - log('autoUpdateBugDb called', new Date()); - checkLibraryVersion(); - } +function autoUpdateDBs(isAutoUpdateEnabled, bugsLastCheckedMsec) { + const date = new Date(); + + log('autoUpdateDBs called', date); + + if (!isAutoUpdateEnabled) return; + + if ( + !bugsLastCheckedMsec // the value is 0, signifying that we have never checked yet + || date.getTime() > (Number(bugsLastCheckedMsec) + ONE_HOUR_MSEC) // guard against double fetching + ) { + updateDBs(); } } @@ -961,7 +971,7 @@ function onMessageHandler(request, sender, callback) { return true; } if (name === 'update_database') { - checkLibraryVersion().then((result) => { + updateDBs().then((result) => { callback(result); }); return true; @@ -1082,38 +1092,6 @@ function onMessageHandler(request, sender, callback) { return false; } -/** - * Determine Antitracking configuration parameters based - * on the results returned from the abtest endpoint. - * @memberOf Background - * - * @return {Object} Antitracking configuration parameters - */ -function getAntitrackingTestConfig() { - if (abtest.hasTest('antitracking_full')) { - return { - qsEnabled: true, - telemetryMode: 2, - }; - } - if (abtest.hasTest('antitracking_half')) { - return { - qsEnabled: true, - telemetryMode: 1, - }; - } - if (abtest.hasTest('antitracking_collect')) { - return { - qsEnabled: false, - telemetryMode: 1, - }; - } - return { - qsEnabled: true, - telemetryMode: 1, - }; -} - /** * Set option for Hub Layout A/B test based * on the results returned from the abtest endpoint. @@ -1133,29 +1111,35 @@ function setupHubLayoutABTest() { } /** - * Adjust antitracking parameters based on the current state - * of ABTest and availability of Human Web. + * Configure A/B tests based on data fetched from the A/B server + * @memberOf Background */ -function setupABTest() { - const antitrackingConfig = getAntitrackingTestConfig(); - if (antitrackingConfig && conf.enable_anti_tracking) { - if (!conf.enable_human_web) { - // force disable anti-tracking telemetry on humanweb opt-out - antitrackingConfig.telemetryMode = 0; - } - Object.keys(antitrackingConfig).forEach((opt) => { - const val = antitrackingConfig[opt]; - log('antitracking', 'set config option', opt, val); - antitracking.action('setConfigOption', opt, val); - }); - } - if (abtest.hasTest('antitracking_whitelist2')) { - cliqz.prefs.set('attrackBloomFilter', false); - } - +function setupABTests() { setupHubLayoutABTest(); } +/** + * @since 8.5.3 + * + * Update config options for the Cliqz antitracking module to match the current human web setting. + * Log out the updates. Returns without doing anything if antitracking is disabled. + * + * @param {Boolean} isAntitrackingEnabled Whether antitracking is currently enabled. + */ +function setCliqzAntitrackingConfig(isAntitrackingEnabled) { + if (!isAntitrackingEnabled) return; + + const antitrackingConfig = { + qsEnabled: true, + telemetryMode: conf.enable_human_web ? 1 : 0, + }; + + Object.entries(antitrackingConfig).forEach(([opt, val]) => { + log('antitracking', 'set config option', opt, val); + antitracking.action('setConfigOption', opt, val); + }); +} + /** * Initialize Dispatcher Events. * All Conf properties trigger a dispatcher pub event @@ -1182,7 +1166,7 @@ function initializeDispatcher() { dispatcher.on('conf.save.enable_human_web', (enableHumanWeb) => { if (!IS_CLIQZ) { setCliqzModuleEnabled(humanweb, enableHumanWeb).then(() => { - setupABTest(); + setCliqzAntitrackingConfig(conf.enable_anti_tracking); }); } else { setCliqzModuleEnabled(humanweb, false); @@ -1208,7 +1192,11 @@ function initializeDispatcher() { }); dispatcher.on('conf.save.enable_anti_tracking', (enableAntitracking) => { if (!IS_CLIQZ) { - setCliqzModuleEnabled(antitracking, enableAntitracking); + setCliqzModuleEnabled(antitracking, enableAntitracking).then(() => { + // enable_human_web could have been toggled while antitracking was off, + // so we want to make sure to update the antitracking telemetry option + setCliqzAntitrackingConfig(conf.enable_anti_tracking); + }); } else { setCliqzModuleEnabled(antitracking, false); } @@ -1710,11 +1698,10 @@ function initializeGhosteryModules() { // auto-fetch from CMP cmp.fetchCMPData(); - if (!IS_CLIQZ) { - // auto-fetch human web offer + if (!IS_CLIQZ && conf.enable_abtests) { abtest.fetch() .then(() => { - setupABTest(); + setupABTests(); }) .catch(() => { log('Unable to reach abtest server'); @@ -1726,13 +1713,17 @@ function initializeGhosteryModules() { }); } - // Check CMP and ABTest every hour. - setInterval(scheduledTasks, 3600000); + // Check CMP and ABTest every day. + setInterval(scheduledTasks, ONE_DAY_MSEC); // Update db right away. - autoUpdateBugDb(); - // Schedule it to run every hour. - setInterval(autoUpdateBugDb, 3600000); + autoUpdateDBs(conf.enable_autoupdate, conf.bugs_last_checked); + + // Schedule it to run every day. + setInterval( + () => autoUpdateDBs(conf.enable_autoupdate, conf.bugs_last_checked), + ONE_DAY_MSEC + ); // listen for changes to specific conf properties initializeDispatcher(); diff --git a/src/classes/ConfData.js b/src/classes/ConfData.js index f50f64145..990db8cd4 100644 --- a/src/classes/ConfData.js +++ b/src/classes/ConfData.js @@ -113,6 +113,7 @@ class ConfData { _initProperty('enable_human_web', !IS_CLIQZ && !IS_FIREFOX); _initProperty('enable_metrics', false); _initProperty('enable_offers', !IS_CLIQZ && !IS_FIREFOX && !IS_ANDROID); + _initProperty('enable_abtests', true); _initProperty('enable_smart_block', true); _initProperty('expand_all_trackers', true); _initProperty('hide_alert_trusted', false); diff --git a/src/classes/Globals.js b/src/classes/Globals.js index 99fedbe9c..8040ed8f5 100644 --- a/src/classes/Globals.js +++ b/src/classes/Globals.js @@ -110,6 +110,7 @@ class Globals { 'enable_human_web', 'enable_metrics', 'enable_offers', + 'enable_abtests', 'enable_smart_block', 'expand_all_trackers', 'hide_alert_trusted', diff --git a/src/classes/Metrics.js b/src/classes/Metrics.js index 2efe7b1a8..9907ad27c 100644 --- a/src/classes/Metrics.js +++ b/src/classes/Metrics.js @@ -369,12 +369,19 @@ class Metrics { `&th=${encodeURIComponent(Metrics._getThemeValue().toString())}` + // New parameters for Ghostery 8.5.2 - // Hub Layout View - `&t2=${encodeURIComponent(Metrics._getHubLayoutView().toString())}` + // Subscription Interval `&si=${encodeURIComponent(Metrics._getSubscriptionInterval().toString())}` + // Product ID Parameter - `&pi=${encodeURIComponent('gbe')}`; + `&pi=${encodeURIComponent('gbe')}` + + + // New parameter for Ghostery 8.5.3 + // AB tests enabled? + `&ts=${encodeURIComponent(conf.enable_abtests ? '1' : '0')}`; + + if (conf.enable_abtests) { + // Hub Layout A/B test. Added in 8.5.3. GH-2097, GH-2100 + metrics_url += `&t2=${encodeURIComponent(Metrics._getHubLayoutView().toString())}`; + } if (CAMPAIGN_METRICS.includes(type)) { // only send campaign attribution when necessary diff --git a/src/classes/PanelData.js b/src/classes/PanelData.js index ef103dca6..eb2734ff3 100644 --- a/src/classes/PanelData.js +++ b/src/classes/PanelData.js @@ -501,7 +501,7 @@ class PanelData { const { alert_bubble_pos, alert_bubble_timeout, block_by_default, cliqz_adb_mode, enable_autoupdate, enable_click2play, enable_click2play_social, enable_human_web, enable_offers, - enable_metrics, hide_alert_trusted, ignore_first_party, notify_library_updates, + enable_metrics, enable_abtests, hide_alert_trusted, ignore_first_party, notify_library_updates, notify_promotions, notify_upgrade_updates, selected_app_ids, show_alert, show_badge, show_cmp, show_tracker_urls, toggle_individual_trackers } = userSettingsSource; @@ -517,6 +517,7 @@ class PanelData { enable_human_web, enable_offers, enable_metrics, + enable_abtests, hide_alert_trusted, ignore_first_party, notify_library_updates,