diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ec921e741..7b98b7bba 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -2207,6 +2207,24 @@ "panel_stats_pitch_modal_tooltip": { "message": "Erase all statistics history up until this point in time." }, + "cookie": { + "message": "cookie" + }, + "cookies": { + "message": "cookies" + }, + "fingerprint": { + "message": "fingerprint" + }, + "fingerprints": { + "message": "fingerprints" + }, + "ad": { + "message": "ad" + }, + "ads": { + "message": "ads" + }, "cliqz_feature_status_on": { "message": "On" }, diff --git a/app/images/panel/tracker-detail-cliqz-ads-icon.svg b/app/images/panel/tracker-detail-cliqz-ads-icon.svg new file mode 100644 index 000000000..2674f8621 --- /dev/null +++ b/app/images/panel/tracker-detail-cliqz-ads-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/images/panel/tracker-detail-cliqz-cookies-and-fingerprints-icon.svg b/app/images/panel/tracker-detail-cliqz-cookies-and-fingerprints-icon.svg new file mode 100644 index 000000000..8a5c467ad --- /dev/null +++ b/app/images/panel/tracker-detail-cliqz-cookies-and-fingerprints-icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/panel/components/Blocking/Tracker.jsx b/app/panel/components/Blocking/Tracker.jsx index 84b839908..5486858a7 100644 --- a/app/panel/components/Blocking/Tracker.jsx +++ b/app/panel/components/Blocking/Tracker.jsx @@ -14,6 +14,7 @@ /* eslint react/no-array-index-key: 0 */ import React from 'react'; +import ReactSVG from 'react-svg'; import globals from '../../../../src/classes/Globals'; import { log } from '../../../../src/utils/common'; import { sendMessageInPromise } from '../../utils/msg'; @@ -200,6 +201,57 @@ class Tracker extends React.Component { reload: true, }); } + + _renderCliqzStatsContainer() { + const { tracker } = this.props; + const { cliqzAdCount, cliqzCookieCount, cliqzFingerprintCount } = tracker; + + const oneOrMoreCookies = cliqzCookieCount >= 1; + const oneOrMoreFingerprints = cliqzFingerprintCount >= 1; + const oneOrMoreAds = cliqzAdCount >= 1; + + return ( +
+ {(oneOrMoreCookies || oneOrMoreFingerprints) && +
+ {this._renderCliqzCookiesAndFingerprintsIcon()} + {oneOrMoreCookies && this._renderCliqzCookieStat(cliqzCookieCount)} + {oneOrMoreFingerprints && this._renderCliqzFingerprintStat(cliqzFingerprintCount)} +
+ } + {oneOrMoreAds && +
+ {this._renderCliqzAdsIcon()} + {this._renderCliqzAdStat(cliqzAdCount)} +
+ } +
+ ); + } + _renderCliqzCookiesAndFingerprintsIcon() { return this._renderCliqzStatsIcon('cookies-and-fingerprints'); } + _renderCliqzAdsIcon() { return this._renderCliqzStatsIcon('ads'); } + _renderCliqzStatsIcon(type) { + const path = `/app/images/panel/tracker-detail-cliqz-${type}-icon.svg`; + + return ( + + ); + } + _renderCliqzCookieStat(count) { return this._renderCliqzStat(count, 'cookie'); } + _renderCliqzFingerprintStat(count) { return this._renderCliqzStat(count, 'fingerprint'); } + _renderCliqzAdStat(count) { return this._renderCliqzStat(count, 'ad'); } + _renderCliqzStat(count, type) { + const exactlyOne = count === 1; + const label = exactlyOne ? + t(`${type}`) : + t(`${type}s`); + const cssClass = `trk-cliqz-stat trk-cliqz-stat-${type}s-count`; + + return ( + {count} {label} + ); + } + /** * Render a tracker in Blocking view. * @return {ReactComponent} ReactComponent instance @@ -230,6 +282,7 @@ class Tracker extends React.Component {
{ tracker.name }
+ {this._renderCliqzStatsContainer()}
diff --git a/app/scss/partials/_blocking_tracker.scss b/app/scss/partials/_blocking_tracker.scss index 3aef6b1a3..08668f579 100644 --- a/app/scss/partials/_blocking_tracker.scss +++ b/app/scss/partials/_blocking_tracker.scss @@ -19,17 +19,52 @@ border-top: none; } .trk-header { - padding-top: 5px; - padding-bottom: 5px; - height: 39px; + height: 54px; .trk-name { @extend %pointer; font-size: 14px; - line-height: 19px; + line-height: 25px; font-weight: 400; color: #4a4a4a; @extend %nowrap; } + .trk-cliqz-stats-outer-container { + // prevent jitter from breaking to new line + // if all three of cookies fingerprints and ads are present + // and we hover over the ad icon while the cookies & fingerprints section is expanded + white-space: nowrap; + + font-size: 10px; + font-family: "Open Sans", "Roboto", Arial, Helvetica, sans-serif; + font-weight: 600; + } + .trk-cliqz-stats-icon { + // vertical alignment with text label + position: relative; + top: -2px; + + display: inline-block; + padding-right: 6px; + } + .trk-cliqz-stat { + color: #1dafed; + text-transform: capitalize; + display: inline-block; + max-width: 0px; + padding-right: 0px; + transition: max-width 0.6s, padding 0.6s; + transition-delay: 0.2s; + overflow-x: hidden; + white-space: nowrap; + } + .trk-cliqz-stats-container { + display: inline-block; + } + .trk-cliqz-stats-container:hover .trk-cliqz-stat { + max-width: 100px; + padding-right: 4px; + } + .svg-container { height: 20px; } diff --git a/src/classes/FoundBugs.js b/src/classes/FoundBugs.js index 759b91554..452d362b0 100644 --- a/src/classes/FoundBugs.js +++ b/src/classes/FoundBugs.js @@ -276,6 +276,23 @@ class FoundBugs { return apps_arr; } + /** + * Returns an object where the keys are the ids of the apps found on this tab + * and the values are their indices in the array returned by #getApps + * + * @param {number} tab_id tab id + * @return {Object} object of app-id-to-#getApps-array-index mappings + */ + getAppsById(tab_id) { + if (!this._ensure(tab_id)) { + return []; + } + + const { appsById } = this._foundApps[tab_id]; + + return appsById; + } + /** * Get the categories from BugsDb that match bugs found * on a tab_id. diff --git a/src/classes/PanelData.js b/src/classes/PanelData.js index 750e4bfc7..2145acd09 100644 --- a/src/classes/PanelData.js +++ b/src/classes/PanelData.js @@ -26,7 +26,7 @@ import tabInfo from './TabInfo'; import rewards from './Rewards'; import account from './Account'; import dispatcher from './Dispatcher'; -import { sendCliqzModulesData } from '../utils/cliqzModulesData'; +import { getCliqzGhosteryStats, sendCliqzModulesData } from '../utils/cliqzModulesData'; import { getActiveTab, flushChromeMemoryCache, processUrl } from '../utils/utils'; import { objectEntries, log } from '../utils/common'; @@ -738,7 +738,16 @@ class PanelData { */ _buildTracker(tracker, trackerState, smartBlock) { const { - id, name, cat, sources, hasCompatibilityIssue, hasInsecureIssue, hasLatencyIssue + cat, + cliqzAdCount, + cliqzCookieCount, + cliqzFingerprintCount, + hasCompatibilityIssue, + hasInsecureIssue, + hasLatencyIssue, + id, + name, + sources, } = tracker; const { blocked, ss_allowed, ss_blocked } = trackerState; @@ -755,7 +764,10 @@ class PanelData { warningCompatibility: hasCompatibilityIssue, warningInsecure: hasInsecureIssue, warningSlow: hasLatencyIssue, - warningSmartBlock: (smartBlock.blocked.hasOwnProperty(id) && 'blocked') || (smartBlock.unblocked.hasOwnProperty(id) && 'unblocked') || false + warningSmartBlock: (smartBlock.blocked.hasOwnProperty(id) && 'blocked') || (smartBlock.unblocked.hasOwnProperty(id) && 'unblocked') || false, + cliqzAdCount, + cliqzCookieCount, + cliqzFingerprintCount, }; } @@ -807,7 +819,7 @@ class PanelData { } /** - * Store the tracker list and categories values to reduce code duplicdation between the blocking and summary data getters, + * Store the tracker list and categories values to reduce code duplication between the blocking and summary data getters, * and since these values may be accessed 2+ times in a single updatePanelUI call */ _setTrackerListAndCategories() { @@ -816,6 +828,27 @@ class PanelData { const { id, url } = this._activeTab; this._trackerList = foundBugs.getApps(id, false, url) || []; + + const ghosteryStats = getCliqzGhosteryStats(id); + + if (ghosteryStats && ghosteryStats.bugs) { + const gsBugs = ghosteryStats.bugs; + const bugsIds = Object.keys(gsBugs); + const appsById = foundBugs.getAppsById(id); + + bugsIds.forEach((bugsId) => { + const trackerId = conf.bugs.bugs[bugsId]; + if (!trackerId) return; + + const trackerListIndex = appsById[trackerId.aid]; + if (!trackerListIndex) return; + + this._trackerList[trackerListIndex].cliqzCookieCount = gsBugs[bugsId].cookies; + this._trackerList[trackerListIndex].cliqzFingerprintCount = gsBugs[bugsId].fingerprints; + this._trackerList[trackerListIndex].cliqzAdCount = gsBugs[bugsId].ads; + }); + } + this._categories = this._buildCategories(); } // [/DATA SETTING] diff --git a/src/utils/cliqzModulesData.js b/src/utils/cliqzModulesData.js index 57cc2512f..46e6485c9 100644 --- a/src/utils/cliqzModulesData.js +++ b/src/utils/cliqzModulesData.js @@ -57,10 +57,46 @@ export function getCliqzAdblockingData(tabId) { return adBlocking || { totalCount: 0 }; } +/** + * TODO: Add a test that verifies the following structure so that we automatically know if Cliqz changes it and we need to updated it + The returned object has the following structure: + { + bugs: { + 4147: { cookies: 3, fingerprints: 4, ads: 0 }, + another_bug_id: { cookies: 2, ..... + .... + }, + others: { + CloudFlare: { + ads: 0, + cat: "cdn", + cookies: 3, + domains: ["cdnjs.cloudlare.com", ...], + fingerprints: 4, + name: "CloudFlare", + wtm: "cloudflare", + }, + ... + } + } + */ +export function getCliqzGhosteryStats(tabId) { + if (!conf.enable_anti_tracking) { + return { + bugs: {}, + others: {}, + }; + } + + const ghosteryStats = antitracking.background.actions.getGhosteryStats(tabId); + return ghosteryStats; +} + export function sendCliqzModulesData(tabId, callback) { const modules = { adblock: {}, antitracking: {} }; modules.adblock = getCliqzAdblockingData(tabId); + // TODO convert to use finally to avoid duplication (does our Babel transpile it?) getCliqzAntitrackingData(tabId).then((antitrackingData) => { modules.antitracking = antitrackingData;