diff --git a/extension-manifest-v3/src/background/adblocker.js b/extension-manifest-v3/src/background/adblocker.js index 090eecca7..3b0f5313b 100644 --- a/extension-manifest-v3/src/background/adblocker.js +++ b/extension-manifest-v3/src/background/adblocker.js @@ -16,10 +16,10 @@ import { import { parse } from 'tldts-experimental'; import { observe, ENGINES } from '/store/options.js'; +import * as engines from '/utils/engines.js'; import Request from './utils/request.js'; import asyncSetup from './utils/setup.js'; -import * as engines from './utils/engines.js'; import { updateTabStats } from './stats.js'; diff --git a/extension-manifest-v3/src/background/devtools.js b/extension-manifest-v3/src/background/devtools.js index 6b6bcf7ee..5235f3ef4 100644 --- a/extension-manifest-v3/src/background/devtools.js +++ b/extension-manifest-v3/src/background/devtools.js @@ -16,7 +16,7 @@ import Options from '/store/options.js'; import { deleteDatabases } from '/utils/indexeddb.js'; -import * as engines from './utils/engines.js'; +import * as engines from '../utils/engines.js'; chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { switch (msg.action) { diff --git a/extension-manifest-v3/src/background/external.js b/extension-manifest-v3/src/background/external.js index a60dd43c3..49f9c5147 100644 --- a/extension-manifest-v3/src/background/external.js +++ b/extension-manifest-v3/src/background/external.js @@ -12,8 +12,7 @@ import { store } from 'hybrids'; import Session from '/store/session.js'; - -import { getStatsWithMetadata } from './stats.js'; +import { MergedStats } from '/store/daily-stats.js'; if (__PLATFORM__ !== 'safari') { // Listen for messages from Ghostery Search extension @@ -46,18 +45,10 @@ if (__PLATFORM__ !== 'safari') { switch (message?.name) { case 'getDashboardStats': { (async () => { - const stats = await getStatsWithMetadata(); + const stats = await store.resolve(MergedStats); - sendResponse({ - adsBlocked: stats.adsBlocked, - cookiesBlocked: 0, - fingerprintsRemoved: 0, - timeSaved: 0, - trackersBlocked: stats.trackersBlocked, - trackersDetailed: stats.patternsDetailed.map( - ({ name, category }) => ({ name, cat: category }), - ), - }); + // Firefox does not serialize correctly objects with getters + sendResponse(JSON.parse(JSON.stringify(stats))); })(); return true; @@ -65,7 +56,9 @@ if (__PLATFORM__ !== 'safari') { case 'getUser': { (async () => { const session = await store.resolve(Session); - sendResponse(session.user && session); + + // Firefox does not serialize correctly objects with getters + sendResponse(session.user && JSON.parse(JSON.stringify(session))); })(); return true; diff --git a/extension-manifest-v3/src/background/stats.js b/extension-manifest-v3/src/background/stats.js index 7761adbe1..ad85ed16c 100644 --- a/extension-manifest-v3/src/background/stats.js +++ b/extension-manifest-v3/src/background/stats.js @@ -14,14 +14,14 @@ import { store } from 'hybrids'; import { getOffscreenImageData } from '@ghostery/ui/wheel'; import { order } from '@ghostery/ui/categories'; -import DailyStats, { getMergedStats } from '/store/daily-stats.js'; +import DailyStats from '/store/daily-stats.js'; import Options, { observe } from '/store/options.js'; import { shouldShowOperaSerpAlert } from '/notifications/opera-serp.js'; import AutoSyncingMap from '/utils/map.js'; import Request from './utils/request.js'; -import * as trackerDb from './utils/trackerdb.js'; +import * as trackerDb from '/utils/trackerdb.js'; export const tabStats = new AutoSyncingMap({ storageKey: 'tabStats:v1' }); @@ -204,69 +204,33 @@ export function updateTabStats(tabId, requests) { }); } -const DAILY_STATS_ADS_CATEGORY = 'advertising'; async function flushTabStatsToDailyStats(tabId) { const stats = tabStats.get(tabId); if (!stats || !stats.trackers.length) return; - const adsDetected = new Map(); - const trackersDetected = new Map(); + let trackersBlocked = 0; + let trackersModified = 0; for (const tracker of stats.trackers) { - if (tracker.category === DAILY_STATS_ADS_CATEGORY) { - adsDetected.set( - tracker.name, - adsDetected.get(tracker.name) || tracker.blocked, - ); - } else { - trackersDetected.set( - tracker.name, - trackersDetected.get(tracker.name) || tracker.blocked, - ); - } + trackersBlocked += tracker.blocked ? 1 : 0; + trackersModified += tracker.modified ? 1 : 0; } - const adsBlocked = [...adsDetected.values()].filter(Boolean).length; - const trackersBlocked = [...trackersDetected.values()].filter(Boolean).length; - const dailyStats = await store.resolve( DailyStats, new Date().toISOString().split('T')[0], ); - const patterns = [ - ...new Set([...dailyStats.patterns, ...stats.trackers.map((t) => t.id)]), - ]; - await store.set(dailyStats, { - adsDetected: dailyStats.adsDetected + adsDetected.size, - adsBlocked: dailyStats.adsBlocked + adsBlocked, - trackersDetected: dailyStats.trackersDetected + trackersDetected.size, trackersBlocked: dailyStats.trackersBlocked + trackersBlocked, - requestsDetected: - dailyStats.requestsDetected + - stats.trackers.reduce((acc, tracker) => { - acc += tracker.requests.length; - return acc; - }, 0), - requestsBlocked: dailyStats.requestsBlocked + adsBlocked + trackersBlocked, + trackersModified: dailyStats.trackersModified + trackersModified, pages: dailyStats.pages + 1, - patterns, + patterns: [ + ...new Set([...dailyStats.patterns, ...stats.trackers.map((t) => t.id)]), + ], }); } -export async function getStatsWithMetadata(since) { - const result = await getMergedStats(since); - - const patternsDetailed = []; - for (const key of result.patterns) { - const pattern = await trackerDb.getPattern(key); - if (pattern) patternsDetailed.push(pattern); - } - - return Object.assign(result, { patternsDetailed }); -} - chrome.tabs.onRemoved.addListener((tabId) => { flushTabStatsToDailyStats(tabId); tabStats.delete(tabId); diff --git a/extension-manifest-v3/src/store/daily-stats.js b/extension-manifest-v3/src/store/daily-stats.js index 4dad127fe..70bd55274 100644 --- a/extension-manifest-v3/src/store/daily-stats.js +++ b/extension-manifest-v3/src/store/daily-stats.js @@ -13,6 +13,7 @@ import { store } from 'hybrids'; import * as IDB from 'idb'; import { registerDatabase } from '/utils/indexeddb.js'; +import * as trackerDb from '/utils/trackerdb.js'; // Synchronously register name of the database // so if a user don't open any page, it is still possible @@ -69,14 +70,9 @@ async function getDb() { await daily.put({ id: stats.day, day: stats.day, - adsBlocked: stats.adsBlocked || 0, - adsDetected: stats.adsBlocked || 0, trackersBlocked: stats.trackersBlocked || 0, - trackersDetected: stats.trackersDetected || 0, - requestsBlocked: stats.trackerRequestsBlocked || 0, - requestsDetected: stats.trackerRequestsBlocked || 0, - cookiesBlocked: stats.cookiesBlocked || 0, - fingerprintsBlocked: stats.fingerprintsRemoved || 0, + trackersModified: + (stats.cookiesBlocked || 0) + (stats.fingerprintsRemoved || 0), pages: stats.pages || 0, patterns: stats.trackers || [], }); @@ -118,17 +114,12 @@ async function flush(id) { const DailyStats = { id: true, day: '', - adsBlocked: 0, - adsDetected: 0, trackersBlocked: 0, - trackersDetected: 0, - requestsBlocked: 0, - requestsDetected: 0, - cookiesBlocked: 0, - fingerprintsBlocked: 0, + trackersModified: 0, pages: 0, patterns: [String], [store.connect]: { + loose: true, async get(id) { const db = await getDb(); return (await db.get('daily', id)) || { id, day: id }; @@ -137,38 +128,56 @@ const DailyStats = { flush(id); return values; }, + async list() { + const db = await getDb(); + return db.getAllFromIndex('daily', 'day'); + }, }, }; export default DailyStats; -export async function getMergedStats(since) { - const db = await getDb(); - const list = await db.getAllFromIndex('daily', 'day', since); - - const result = list.reduce( - (acc, stats) => { - for (const key of Object.keys(stats)) { - if (key === 'id' || key === 'day') continue; - - if (key === 'patterns') { +export const MergedStats = { + trackersBlocked: 0, + trackersModified: 0, + trackersDetailed: [{ id: true, category: '' }], + [store.connect]: { + cache: false, + async get() { + const list = await store.resolve([DailyStats]); + const patterns = new Set(); + + // Merge stats + const mergedStats = list.reduce( + (acc, stats) => { for (const id of stats.patterns) { - acc.patterns.add(id); + patterns.add(id); } - } else { - acc[key] = acc[key] + stats[key]; - } + + acc.trackersBlocked += stats.trackersBlocked; + acc.trackersModified += stats.trackersModified; + + return acc; + }, + { + trackersBlocked: 0, + trackersModified: 0, + trackersDetailed: [], + }, + ); + + // Add metadata + for (const id of patterns) { + const { category = 'unidentified' } = + (await trackerDb.getPattern(id)) || {}; + + mergedStats.trackersDetailed.push({ + id, + category, + }); } - return acc; + return mergedStats; }, - { ...DailyStats, patterns: new Set() }, - ); - - // clean up model definition related properties - delete result.id; - delete result.day; - delete result[store.connect]; - - return Object.assign(result, { patterns: [...result.patterns] }); -} + }, +}; diff --git a/extension-manifest-v3/src/background/utils/engines.js b/extension-manifest-v3/src/utils/engines.js similarity index 100% rename from extension-manifest-v3/src/background/utils/engines.js rename to extension-manifest-v3/src/utils/engines.js diff --git a/extension-manifest-v3/src/background/utils/trackerdb.js b/extension-manifest-v3/src/utils/trackerdb.js similarity index 100% rename from extension-manifest-v3/src/background/utils/trackerdb.js rename to extension-manifest-v3/src/utils/trackerdb.js